docs: hand off Windows history and question takeover fixes

This commit is contained in:
Codex committed 2026-10-21 18:40:02 +09:00
1 parent 23537aaf28
commit 3e62385a2f
3 files changed
+119

No files matched your search

@@ -0,0 +1,36 @@
# Windows 历史路径与问答独占输入区:交付 01
2026-10-21。源码提交:`23537aaf28ce9f743495c12c3750e8e43556c496`。
基线:`a4e77cacbeb39e000592cde16f4ff39712b921ae`。对应用户 0.0.10 发布前反馈;本提交不修改版本号、不发布安装程序。
## 历史路径 fail-closed
原缺陷:`file/history.json` 中的父路径是普通文件时,Windows 可能返回 ENOENT,POSIX 通常返回 ENOTDIR。旧代码将所有 ENOENT(包括读取阶段)视为空历史,因此 Windows 会绕过防御性断言。
- 从根路径逐级检查父目录,区分目录真实缺失和父组件非法;合法系统目录链接/junction 允许跟随,悬空链接和普通文件父组件拒绝。
- 只有确认父路径后,叶文件 lstat 的 ENOENT 可以作为新历史;readFile 的 ENOENT、EACCES、损坏 JSON 等仍失败。
- 保留叶文件软链接拒绝、大小/格式限制、私有临时文件 wx 写入、原子 rename 和串行合并;创建父目录后再次检查。
- 测试增加跨平台 Windows ENOENT 注入、读取阶段 ENOENT/EACCES、缺失嵌套目录、链接/junction、并发保存等。
- 将新测试对照旧实现运行,Windows ENOENT 模拟用例确实失败;没有放宽原失败断言。
## 问答卡独占
原缺陷:输入区组合器只让工具授权卡接管输入区,问答选择卡仍浮在普通输入框之上。
- 当前会话、当前活跃 run 的 pending/responding/error 问答沿用已有授权卡独占机制,不新增样式或第二个停止按钮。
- 普通编辑器保持挂载,但 hidden/inert/aria-hidden 且禁用;草稿、附件、引用及能力选择保留,回答/取消后恢复。
- 历史问答、其他 run/其他会话和加载中页面不触发接管。授权卡行为保持不变。
- 问答选择与自由输入仍在问答卡内部提交,未更改工具授权或消息发送/排队语义。
## 验证
- `npm run test:model-bridge`:111 项本地协议检查 + 16 组 mock SDK host 检查通过。
- 新问答 composer ReactDOM 回归:21 项通过;已有授权 DOM 49、问答交互 24、问答身份 30 项通过。
- `npm test`、严格 alignment 11 offline + 9 DOM、完整生产构建通过;独立审查通过。
- [证据](evidence/release-0010-fixes)。保留 CRLF 文件,CRLF-aware diff 检查通过。
未在真实 Windows、Electron 原生界面或真实 SDK/模型上验证。111 为本次 POSIX + 注入 Windows 错误语义的检查数;不同平台的权限/链接专项计数可能不同,不能用数量代替具体用例结果。文件检查和写入之间的并发文件系统替换风险不作为本次完整解决的安全承诺。
## 0.0.10 验收要求
拉取本交付后,在 Windows 重新运行 `npm run test:model-bridge`,不得仅跳过/放宽 invalid history path 断言;核对问答出现时只有问答卡,普通输入框不可见且不可操作,回答后草稿/附件恢复。历史问答不可劫持新任务输入区。发布前记录具体测试结果;发布流水线未包含测试不代表测试失败可忽略。
@@ -0,0 +1,56 @@
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.8 test:alignment:dom
> node scripts/run-alignment-regression.mjs --require-dom
PASS model alignment: fresh/legacy migration, new/edit/default/delete/reload, valid selection (offline host fixtures)
PASS model settings: deferred owner switch, rename mapping, addition selection, empty deletion, immutable history
PASS session factory: first-send preference, mode preservation, invalid permission rejection (isolated ports)
{"historyMessages":10000,"bytes":747781,"parseMs":2.7394040000000075,"storeHydrationMs":0.15000999999999465,"note":"Node parsing/store benchmark only, not browser rendering or Windows IPC performance"}
session-composer-alignment: loading/cache/retry/races/model/permissions/modes/SSR passed
alignment-conversation: local groups, original expanded order, question projection and truthful status passed
A01 native MessageBubble identity aliases, immutable inputs and ordinary raw output passed
A01 imported native card, linked dedup and unknown-position compatibility passed
question-answer-shell: structural and recovered CSS contracts passed
question-answer-shell: DOM hierarchy, controls linkage, click, Enter, Space and inert contracts passed
Runtime progress, session-grant scope and approval lifecycle regressions passed
Permission intercept static rendering passed; interactive keyboard/focus remains UI acceptance
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"full-access","agentMode":"ask","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"mode-or-inactive-deny"}
[claude-permission] {"permissionMode":"full-access","agentMode":"plan","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"mode-or-inactive-deny"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":true,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"subcommandResults","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"safetyCheck","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":true,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":true,"branch":"rule-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"error-deny"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"85aafbf52a5144b5","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"business-question"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"85aafbf52a5144b5","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"business-question"}
Claude host approval policy unit checks passed (no SDK process/application/matching verification)
[claude-permission] {"permissionMode":"full-access","agentMode":"plan","sdkMode":"plan","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"hook","toolCall":"unknown","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":"unknown","branch":"mode-or-inactive-deny"}
Mock SDK replay lifecycle passed: fail closed, retry, session isolation, independent mode guard
B03 offline host policy: 276 checks passed; no real SDK/CLI/model execution.
task popconfirm DOM regression: 34 checks passed (jsdom, not pixel validation)
question-interaction: 24 DOM behavioral checks passed
question-identity: 30 A01 DOM checks passed (not Electron/pixel/SDK acceptance)
Question composer DOM: 21 checks passed (not pixel/SDK verification)
completed-fold-nested: real question/tool/thinking/local-summary remount, event, keyboard and identity contracts passed
completed-fold-native-identity: real reducer done correction, native IDs, insertion/reorder, repeated outer toggles, message isolation and legacy fallback passed
Permission intercept DOM: 49 checks passed (not pixel/SDK verification)
{"result":"PASS","suite":"reasoning-follow-dom","assertions":63,"evidence":"Actual ReactDOM with synthetic geometry/RAF/resize; not native scroll or pixel evidence"}
{"checks":37,"passed":true,"scope":"Isolated React DOM and CSS contracts; no native keyboard/layout/pixel acceptance"}
{"result":"PASS","phase":"alignment-offline","suites":11}
{"result":"PASS","phase":"alignment-dom","suites":9}
npm notice
npm notice New major version of npm available! 11.9.0 -> 12.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0
npm notice To update run: npm install -g npm@12.2.0
npm notice
@@ -0,0 +1,27 @@
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.8 test:model-bridge
> esbuild scripts/protocol-bridge-regression.ts --bundle --platform=node --format=esm --outfile=.tmp/protocol-bridge-regression.mjs --log-level=warning && node .tmp/protocol-bridge-regression.mjs && node scripts/claude-bridge-host-regression.mjs
Protocol bridge offline regression: 111 checks passed (no SDK/CLI or external API used)
PASS both OpenAI APIs use isolated bridge env, not upstream credentials
PASS bridge lease receives effective low/off thinking without mutating resolved config
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"default","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"hook","toolCall":"d1022618b99a974b","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":"unknown","branch":"mode-or-inactive-deny"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"hook","toolCall":"d1022618b99a974b","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":"unknown","branch":"mode-or-inactive-deny"}
PASS Anthropic remains direct and permission/tool hooks remain identical
PASS two simultaneous sessions have separate leases and independent cleanup
PASS concurrent ensureStarted acquires exactly one lease and SDK query
PASS API/model/base URL/key configuration changes retire old leases
PASS failed SDK startup releases lease and permits clean retry
PASS failed session grant replay releases producer and bridge before retry
PASS runtime environment failure cannot strand a bridge lease
PASS close during asynchronous acquisition releases lease without SDK startup
PASS unsupported Gemini fails before bridge or SDK creation
PASS resolved model snapshot cannot be mutated after kernel creation
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"default","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"hook","toolCall":"d1e9567da9a8a4b0","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":"unknown","branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"default","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"hook","toolCall":"7aca79e91fb3e780","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":"unknown","branch":"business-question"}
PASS bridge preserves full-access tool preauthorization and question approval boundary
PASS unexpected producer termination releases bridge lease
PASS cancellation during bridge acquisition releases newly started producer
PASS turn cancellation retires producer and releases its bridge lease
PASS 16 offline Claude/bridge host groups; no actual SDK startup, network or credentials