docs: record fail-closed ETag and viewer comment hardening

Keep the reject-routing task in_progress; no real UI rerun this round.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-13 17:08:35 +00:00
co-authored by Cursor
parent cf3d126436
commit d45efa2ff1
4 changed files with 26 additions and 5 deletions
@@ -25,7 +25,16 @@ severity: critical
| 保存 | `handleSaveBlockEdit` 在 PATCH 前 `getRevisionState` 取最新 ETag | 打开时基线被丢弃;后写覆盖先写 |
| E2E | UI-13b 在 A1 完整保存后才打开 A2 编辑器 | 不是真实竞争,无法钉住上述 ETag 缺陷 |
安全含义:职责伪造是**越权写**(viewer/reviewer 可改受控正文并送审)。ETag 刷新是**完整性破坏**(并发编辑丢失且无冲突提示)。
独立审查补强(同一分支):
| 层 | 问题 | 风险 |
|---|---|---|
| PATCH | `resolvePatchEtag` 在 controlled 缺基线时仍返回 `0:0` | 服务异常被当成合法 bootstrap,可能覆盖已有账本 |
| hydrate | revision-state 5xx/网络失败只写 undefined 仍返回可编辑 AST | 用户能改正文,保存再 PATCH `0:0` |
| 批注 | viewer 被 cast 为 reviewer/approver,`canAnnotate` 对 viewer 开放 | 查阅者可新增评审批注 |
| 职责 | `isLegacyLocalDoc = !ids`:服务端 duty id 为 0 仍走 name fallback | 同名用户被提权为编制人 |
安全含义:职责伪造是**越权写**(viewer/reviewer 可改受控正文并送审)。ETag 刷新或 `0:0` 误用是**完整性破坏**。viewer 批注是**越权写元数据**。
## 严重度
@@ -41,6 +50,7 @@ severity: critical
- 代码(修复前):`internal/docgov/project_docs.go` summary 无职责列;`web/src/lib/deliverableSync.ts` `author: fallbackUser.name`;`web/src/App.tsx` 默认 `'author'` 与保存前 `getRevisionState`
- 单测钉住:B/陌生人不成 author;stale hydrate ETag 用于 PATCH
- 补强单测:controlled 缺 ETag/5xx 不可保存或不可打开;freeform 可 `0:0`;viewer 不能批注;服务端 duty id=0 不按 name 提权
- 本轮不跑真实 E2E、不提交 evidence assets
## 开放问题
+1 -1
View File
@@ -5,7 +5,7 @@
## 焦点
1. **DOCREV-RT**: [E2E UI-05b + 职责/ETag 产品修复](short-term/2026-09-13-document-revision-e2e-reject-routing.md)(`fix/document-revision-e2e-reject-routing`:权威职责 + 打开时基线 ETag;不重跑 E2E / 不操作容器;仍 `in_progress`)
1. **DOCREV-RT**: [E2E UI-05b + 职责/ETag 产品修复](short-term/2026-09-13-document-revision-e2e-reject-routing.md)(`fix/document-revision-e2e-reject-routing`:权威职责 + 打开时基线 ETag + fail-closed/viewer 补强;不重跑 E2E / 不操作容器;仍 `in_progress`)
2. **CHAT**: [刷新恢复整改已实施](short-term/2026-09-13-chat-history-refresh-remediation.md)(待合 dev + PI E2E 补录)
3. **CI**: `TEST_DATABASE_URL` + 真实 PI E2E 实录;INTENT 发布 blocker 见 conversation-intent short-term
@@ -36,7 +36,8 @@ mutex: document-revision-runtime-ui
4. [x] 仅修 05a/05b/10 遗留弹窗对 UI-06 的确切遮挡
5. [x] 产品修复:权威职责 + viewer fail-closed + hydrate 基线 ETag
6. [x] UI-13b:A1 保存前 A2 编辑器已打开并填入文本;两窗口同一 r/etag
7. [ ] 占槽后真实 UI 重跑(本轮不做)
7. [x] 审查补强:controlled 缺 ETag fail-closed;viewer 只读批注;服务端零 id 不按 name 提权
8. [ ] 占槽后真实 UI 重跑(本轮不做)
## 验收标准
@@ -46,6 +47,9 @@ mutex: document-revision-runtime-ui
- [x] UI-13a/13b 结果分别记录;B 不作正文编辑者
- [x] B/陌生人不会变 author;A 仍 author(Go + 前端单测)
- [x] stale hydrate ETag 用于 PATCH(不能用最新 ETag 掩盖冲突)
- [x] controlled 缺打开时 ETag 不得 PATCH 0:0;hydrate 5xx 进加载失败屏障
- [x] viewer 无新增批注入口
- [x] 服务端受控 duty id=0 不按 name 提权
- [x] web tests + `tsc`/build + `git diff --check`
- [ ] 真实 UI-05b+ 后段及 UI-13a/13b PASS 后才可将本任务标 `done`
@@ -17,8 +17,11 @@ updated: 2026-09-13
1. 列表 summary 返回台账 author/reviewer/approver 的 id+name;前端保留权威字段,禁止用当前登录用户伪造 author。
2. `currentDocRole` 按权威 user id 解析;name 仅兼容旧本地文档;未知身份 fail-closed 为 `viewer`;viewer 无编辑/审核/批准动作。
3. 受控正文 hydrate 同时取 revision-state ETag 写入 AST/缓存;人工 PATCH 只用该基线;成功后用响应 `state.etag` 更新;冲突保留编辑器与文本。无账本 bootstrap/freeform 仍可用 `0:0`。
4. UI-13b:A1 保存前两窗口已加载相同 r/etag,且 A2 编辑器已打开并填入文本。
3. 受控正文 hydrate 同时取 revision-state ETag 写入 AST/缓存;人工 PATCH 只用该基线;成功后用响应 `state.etag` 更新;冲突保留编辑器与文本。
4. **受控 fail-closed**:缺打开时 ETag 禁止 PATCH `0:0`;hydrate 遇 revision-state 网络/5xx/NOT_FOUND/NEEDS_CONTENT 走正文加载失败屏障。仅 freeform 允许 `0:0`。
5. viewer 只读批注:无右键/浮标新增入口,面板不可提交;author/reviewer/approver 语义不变。
6. 服务端受控 / `dutiesAuthoritative` 一律按 id fail-closed;name 回退仅真正旧本地文档。duty id 为 0 不得当成本地旧文档。
7. UI-13b:A1 保存前两窗口已加载相同 r/etag,且 A2 编辑器已打开并填入文本。
## 分波次计划
@@ -28,6 +31,7 @@ updated: 2026-09-13
| Wave 2 | 前端 merge / `UserRole=viewer` / `resolveCurrentDocRole` | B/陌生人不成 author;viewer 无动作 |
| Wave 3 | hydrate 基线 ETag + PATCH 不再 GET | stale ETag 单测;不能用最新 ETag 掩盖冲突 |
| Wave 4 | UI-13b 真实竞争脚本 | A2 先开编辑器再 A1/A2 保存 |
| Wave 4b | ETag/viewer/零 id 补强 | controlled 缺基线不可保存;viewer 只读批注;id=0 不按 name 提权 |
| Wave 5 | 占槽真实 UI(本轮不做) | UI-13a/13b 实战 PASS 后才可 done |
## 验收标准
@@ -36,6 +40,9 @@ updated: 2026-09-13
- [x] 前端 merge + `resolveCurrentDocRole` 单测
- [x] stale hydrate ETag 用于保存(纯函数 + hydrate 单测)
- [x] UI-13b 脚本不降级(本轮不跑真实 E2E)
- [x] controlled 缺 hydrate ETag / 5xx 不可保存或打开;freeform 可 `0:0`
- [x] viewer 不能新增批注
- [x] 服务端 duty id=0 不按 name 提权
- [ ] 真实 UI 重跑 PASS
## 相关链接