diff --git a/.doc/audit/2026-09-14-dev-runtime-data-root-split-brain.md b/.doc/audit/2026-09-14-dev-runtime-data-root-split-brain.md new file mode 100644 index 00000000..49743b3f --- /dev/null +++ b/.doc/audit/2026-09-14-dev-runtime-data-root-split-brain.md @@ -0,0 +1,161 @@ +--- +title: "dev rebuild 从临时 worktree 挂载空数据根导致正文投影 409" +status: done +created: 2026-09-14 +updated: 2026-09-14 +severity: critical +--- + +# dev rebuild 从临时 worktree 挂载空数据根导致正文投影 409 + +## 现象 + +用户刷新项目 143 后,前端请求: + +```text +GET /api/v1/docgov/documents/doc_proj143_intake/content 409 +DOCUMENT_CONTENT_PROJECTION_MISSING +docgov: document content projection missing +``` + +错误发生在 `projectDocuments.ts` → `controlledDocHydrate.ts` 的受控正文水合链。它不是并发编辑的 `HEAD_CONFLICT`,也不是此前已修过的 canonical hash drift。 + +本轮只做只读现场核对与整改设计:没有操作用户会话、没有修改数据库/正文、没有重建共享容器。 + +## 结论 + +直接根因是 **dev 应用、PI Runner、Cursor Runner 被重建成挂载临时集成 worktree 的空 `data/dev`,而 PostgreSQL 仍沿用原 dev 持久卷**,形成数据库与文件事实源的 split-brain: + +```text +PostgreSQL persistent volume + document_records.work_dir=/app/data/workspaces/bizdev/intake_1789270446856290128 + │ + ├─ 主仓 data/dev:output.md 存在,SHA 与 DB/head 一致 + │ + └─ 当前容器 /app/data:绑定 /tmp/as-dev-wf409-merge/data/dev,文件不存在 +``` + +`loadControlledDocumentContent` 先取得账本 head,再调用 `InspectProjection`;容器内看不到 `output.md`,因此正确分类为 `MISSING` 并返回 409。前端只是如实暴露后端错误,不是本次根因。 + +## 现场证据 + +### E1 — 容器挂载源是临时 worktree + +2026-09-14 08:54 UTC 的 `docker inspect shuimu-agent-app-dev`: + +| 项 | 现场值 | +|---|---| +| Compose working dir | `/tmp/as-dev-wf409-merge` | +| Compose config | `/tmp/as-dev-wf409-merge/docker-compose.dev.yml` | +| app `/app/data` source | `/tmp/as-dev-wf409-merge/data/dev` | +| pi-agent `/app/data/workspaces` source | `/tmp/as-dev-wf409-merge/data/dev/workspaces` | +| cursor-agent `/app/data/workspaces` source | `/tmp/as-dev-wf409-merge/data/dev/workspaces` | +| app created | `2026-09-14T08:40:44Z` | + +数据库容器的数据仍来自命名卷 `agent-dev-context-pgdata`,不会随 Compose 工作目录切换。由此产生“旧数据库 + 新空文件根”的不一致组合。 + +### E2 — 项目 143 数据本身当前完整 + +`document_records`: + +| 字段 | 值 | +|---|---| +| doc_id | `doc_proj143_intake` | +| work_dir | `/app/data/workspaces/bizdev/intake_1789270446856290128` | +| content_sha256 | `f283ba94c9c037897f021960f51abacee7242c3db8d10f9f2a5e32cde6acf039` | +| updated_at | `2026-09-14 05:14:10.302146+00` | + +账本 open version 1 的 head snapshot 302 是人工修订,SHA 同为 `f283...f039`;主仓文件: + +```text +/home/weichen/projects/Agent-System/data/dev/workspaces/bizdev/intake_1789270446856290128/output.md +size=40075 +sha256=f283ba94c9c037897f021960f51abacee7242c3db8d10f9f2a5e32cde6acf039 +``` + +同一路径在当前 app 容器内 `stat` 返回 `No such file or directory`。所以无需再次对项目 143 做内容修复;需要恢复正确数据根挂载。 + +### E3 — 影响不是单文档 + +对 dev 数据库 100 条“非模板且 work_dir 非空”的记录做只读比较: + +| 根目录 | aligned | missing | +|---|---:|---:| +| 主仓 `/home/weichen/projects/Agent-System/data/dev` | 32 | 68 | +| 当前容器挂载 `/tmp/as-dev-wf409-merge/data/dev` | 0 | 100 | + +主仓下的 68 条 missing 包含历史测试/临时路径,不能直接等同于 68 个真实用户缺陷;但当前容器对本次抽样 **100/100 都不可见**,足以确认环境级事故。恢复前任何真实文档读取或 PI 写入都不可信。 + +错误根在容器启动后已出现 27 个文件,其中 9 个 CAS blob 的 mtime 为 `08:40:45 UTC`;虽然 08:54 UTC 尚未找到新的 `output.md`,错误根已经不是可直接丢弃的“绝对空目录”,恢复前仍须做完整差异保全。 + +只读核对显示 test/main 当前 app 与 PI Runner 仍挂主仓 `data/test`、`data/main`,本轮没有观察到同样的运行事故;但其 Compose 仍使用相对 bind,仍受同一设计缺陷影响。 + +### E4 — 健康检查假阳性 + +同一时刻 `/health` 与 `/ready` 都返回 200,只检查数据库、schema 和 task store;未验证: + +- 容器绑定的数据根身份; +- app 与 runner 是否挂载同一根; +- 活跃项目的正文投影是否可达; +- 数据库记录与文件根是否属于同一环境实例。 + +因此“healthy + schema 33”不能证明文档运行态可用。 + +## 根因分析 + +### R1 — Compose 相对 bind mount 与调用 worktree 耦合(直接根因) + +`docker-compose.dev.yml` 使用: + +```yaml +- ./data/dev:/app/data +- ./data/dev/workspaces:/app/data/workspaces +``` + +Compose 按 project working directory 解析相对路径。`scripts/env-service.sh` 又把 `ROOT_DIR` 定义为脚本所在 worktree 并 `cd` 进去,所以从任何复制/linked worktree 执行 `dev rebuild`,都会把该 worktree 自己的 `data/dev` 当成运行数据根。 + +### R2 — 纪律是文档约束,不是可执行门禁 + +`scripts/agent-worktree.sh` 只打印“禁止在此目录运行 env-service”,`AGENTS.md` 也规定 worktree 不得操作共享 Docker;但 `env-service.sh` 没有检测 linked worktree、主 worktree、分支、数据根或集成槽位。一次误调用即可静默替换全套容器挂载。 + +### R3 — 数据库与文件没有共享存储身份 + +数据库使用稳定 named volume;正文使用随调用目录变化的 bind mount。两者没有共同的 environment/storage UUID,也没有启动握手,因此任意一个被指向另一份状态时,进程仍可启动并连接成功。 + +### R4 — 账本权威仍被兼容投影阻断 + +当前设计声明 ledger head 是权威正文,但 GET 在账本存在时仍要求 `output.md` 投影必须存在且 aligned。于是兼容投影丢失会阻断 UI 读取权威账本。该 fail-closed 对真实 fork 有价值,却把“可安全重建的 missing”与“内容分叉”按同样的可用性后果处理。 + +### R5 — 正文更新仍是非原子双写 + +人工 PATCH 先提交 PostgreSQL 账本,再单独写 `output.md`;候选 commit/reject 也采用 DB 状态与文件投影分步更新。失败后只依赖同幂等键重试,没有持久 projection job/status 和后台补偿。即使修复本次挂载问题,进程崩溃、权限错误或磁盘故障仍可能再次留下 head 已更新、投影缺失的状态。 + +### R6 — 发布验收没有“从不同源码目录 rebuild 后仍接回同一状态”的负例 + +既有真实 UI 验收验证过 restart 后恢复,但 restart 不会重新解析 Compose bind mount;本次事故由 rebuild/recreate 触发。现有门禁没有覆盖 linked worktree、临时集成目录或错误数据根的启动拒绝。 + +## 为什么会“每次修改后又出现” + +修改本身并不会删除项目 143 正文。本次时间线表明:最新人工修订和主仓 `output.md` 在 05:14 UTC 已一致落盘;08:40 UTC 后续 dev rebuild 把运行容器切到另一份空数据根,刷新才出现 409。 + +因此用户感知的“改完再刷新就坏”来自两个事件经常相邻:开发修改 → Agent 在临时 worktree 合并/rebuild → Compose 改绑数据目录。只修某个文档、某个 hydrate effect 或某次 hash 漂移,都无法阻止它复发。 + +## 严重度与影响 + +`critical`: + +- dev 环境所有受控正文、工作流产物和 runner 工作区均可能不可见或写入错误根; +- 错误根上的新输出可能与主仓状态分叉; +- health/ready 仍为绿色,集成报告可能产生假 PASS; +- test/main 使用同类相对 bind 写法,设计缺陷可横向复制,虽本轮未观察其被错挂载。 + +## 关联整改 + +- Remediation: [dev 运行数据根隔离与文档投影自愈整改](../remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md) +- Short-term: [dev runtime data root guard 诊断与设计](../ops/short-term/2026-09-14-dev-runtime-data-root-guard.md) +- 前序问题: [项目 143 canonical hash 漂移](./2026-09-14-project143-controlled-content-404.md) + +## 开放问题 + +- 恢复前需再次冻结并备份错误根,确认 08:40 UTC 后是否已有新会话、附件、runner 输出或 agent session 写入;08:54 UTC 抽样未发现 `output.md`,不能替代完整差异清单。 +- test/main 当前挂载源需在实施 Wave 0 只读核对;本轮未扩大操作范围。 diff --git a/.doc/audit/INDEX.md b/.doc/audit/INDEX.md index ab5873db..fbd112ba 100644 --- a/.doc/audit/INDEX.md +++ b/.doc/audit/INDEX.md @@ -13,6 +13,14 @@ ## 核心文档(按阅读顺序推荐) +### dev 临时 worktree 数据根 split-brain(2026-09-14) + +**文件**: [2026-09-14-dev-runtime-data-root-split-brain.md](./2026-09-14-dev-runtime-data-root-split-brain.md) + +**结论**: dev rebuild 从 `/tmp/as-dev-wf409-merge` 解析相对 bind mount,app/pi/cursor 全部切到空数据根,PostgreSQL 却保留原卷;项目 143 文件和账本本身一致,但容器不可见,因而刷新返回 `DOCUMENT_CONTENT_PROJECTION_MISSING` 409。health/ready 对 storage identity 无检查,形成假绿色。 + +**整改**: [运行数据根隔离与文档投影自愈](../remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md) + ### 项目 143 workflow-plan 409 与三权全有或全无(2026-09-14) **文件**: [2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](./2026-09-14-workflow-plan-409-frozen-and-optional-duties.md) diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index 37098ad7..392442e2 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -1,12 +1,13 @@ # 当前焦点 **更新**: 2026-09-14 -**production_ready**: no(`CHAT-INTENT-STATE` 等无关 blocker 仍开放。文档账本运行时 UI Round20 **PASS**,不把 fallback change report 写成完整 PI,也不把本轮写成全仓可发布) +**production_ready**: no(新增 `RUNTIME-DATA-ROOT-SPLIT-BRAIN` critical:dev 数据库与正文文件根分裂,health/ready 假绿色;历史文档 UI PASS 不代表当前运行态) ## 焦点 -1. **CHAT-INTENT**: [对话意图运行时状态](../audit/2026-09-13-conversation-intent-runtime-state.md)(`fix/conversation-intent-state`;与文档账本运行时无关) -2. **CHAT**: [对话历史刷新](../audit/2026-09-13-chat-history-refresh-disappears.md)(认领仍 `in_progress`;gap 行已 PASS,以代码/handoff 为准收敛) +1. **RUNTIME-DATA-ROOT**: [dev 临时 worktree 数据根 split-brain](../audit/2026-09-14-dev-runtime-data-root-split-brain.md)(critical;当前 app/pi/cursor 挂到 `/tmp/as-dev-wf409-merge/data/dev*`,DB 仍为原 dev 卷;待批准后占槽恢复并系统整改) +2. **CHAT-INTENT**: [对话意图运行时状态](../audit/2026-09-13-conversation-intent-runtime-state.md)(`fix/conversation-intent-state`;与文档账本运行时无关) +3. **CHAT**: [对话历史刷新](../audit/2026-09-13-chat-history-refresh-disappears.md)(认领仍 `in_progress`;gap 行已 PASS,以代码/handoff 为准收敛) ## 并行认领 @@ -47,6 +48,7 @@ | Cursor-Auto | vite-frp-allowed-hosts | fix/vite-frp-allowed-hosts | - | web-devserver | done | | implementer_cursor | workflow-plan-409-frozen-and-optional-duties | fix/workflow-plan-409-frozen-and-optional-duties | .worktrees/fix-workflow-plan-409-frozen-and-optional-duties | workflow | done | | implementer_cursor | governance-action-level-authz | fix/governance-action-level-authz | .worktrees/fix-governance-action-level-authz | workflow | done | +| Codex | dev-runtime-data-root-guard | fix/dev-runtime-data-root-guard | .worktrees/fix-dev-runtime-data-root-guard | infra-runtime-state | done | > 新任务开始前登记。`branch` 必须为 `feature/` 或 `fix/`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。 @@ -66,7 +68,7 @@ | 环境 | 端口 | schema | 状态 | |------|------|--------|------| -| dev | 8080 | **33** | healthy(`pi-agent`;治理鉴权 G5 页面+API PASS) | +| dev | 8080 | **33** | **incident**:health/ready 200 但 app/pi/cursor 错挂临时 worktree 空数据根;正文运行态不可用,禁止以 healthy 签字 | | test | 8081 | 25→27* | healthy | | main | 8000 | 25→27* | healthy(app + runner + DB) | @@ -106,3 +108,5 @@ | UI-03 首次生成 PG ID 误删 baseline | [../audit/2026-09-13-document-revision-pg-id-remap.md](../audit/2026-09-13-document-revision-pg-id-remap.md) | | UI-06 刷新后缓存竞争死锁审计 | [../audit/2026-09-14-document-revision-refresh-cache-race.md](../audit/2026-09-14-document-revision-refresh-cache-race.md) | | 受控正文真实 UI 验收报告 | [../test-reports/2026-09-14-controlled-content-ui-acceptance.md](../test-reports/2026-09-14-controlled-content-ui-acceptance.md) | +| dev 数据根 split-brain 审计 | [../audit/2026-09-14-dev-runtime-data-root-split-brain.md](../audit/2026-09-14-dev-runtime-data-root-split-brain.md) | +| dev 数据根与投影系统整改 | [../remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md](../remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md) | diff --git a/.doc/ops/short-term/2026-09-14-dev-runtime-data-root-guard.md b/.doc/ops/short-term/2026-09-14-dev-runtime-data-root-guard.md new file mode 100644 index 00000000..15533ddd --- /dev/null +++ b/.doc/ops/short-term/2026-09-14-dev-runtime-data-root-guard.md @@ -0,0 +1,57 @@ +--- +title: "dev runtime data root split-brain 诊断与系统整改设计" +status: done +created: 2026-09-14 +updated: 2026-09-14 +owner: Codex +branch: fix/dev-runtime-data-root-guard +worktree: .worktrees/fix-dev-runtime-data-root-guard +mutex: infra-runtime-state +--- + +# dev runtime data root split-brain 诊断与系统整改设计 + +## 目标 + +定位项目 143 刷新后正文 409 的当前根因,量化影响,并形成防复发的系统整改方案;不实施业务代码或共享环境变更。 + +## 范围 + +- 用户报错链、正文读取/投影代码; +- dev PostgreSQL、主仓文件、容器实际 mount、health/readiness; +- Compose 与 `env-service.sh` 的工作目录语义; +- 即时恢复、state-root 隔离、storage identity、projection outbox 与验收设计。 + +## 非目标 + +- 不操作真实用户会话; +- 不写数据库、不覆盖正文、不删除错误根; +- 不占共享集成槽、不 rebuild; +- 不变更 test/main。 + +## 步骤 + +1. [x] 完成仓库启动链、既有 canonical hash/refresh 事故文档复核。 +2. [x] 比对项目 143 DB record、ledger head、主仓 `output.md` 与容器内路径。 +3. [x] 审计 app/pi/cursor mounts、Compose working dir、健康检查与影响面。 +4. [x] 完成 audit、remediation、gap/feature/current SSOT 更新。 + +## 验收标准 + +- [x] 直接根因有数据库、文件和容器三方证据。 +- [x] 明确区分旧 hash drift、真正 ETag 409 与本次 projection missing。 +- [x] 方案覆盖部署根因、运行时双写、自愈安全边界、负例与真实 UI 验收。 +- [x] 未污染真实会话或修改共享运行态。 + +## 相关链接 + +- Audit: [../../audit/2026-09-14-dev-runtime-data-root-split-brain.md](../../audit/2026-09-14-dev-runtime-data-root-split-brain.md) +- Remediation: [../../remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md](../../remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md) +- Test report: 待方案获批并实施后创建 + +## 证据 / 备注 + +- 诊断分支:`fix/dev-runtime-data-root-guard`,基线 `dev@a8f9451`。 +- 2026-09-14 08:54 UTC:app/pi/cursor 均挂 `/tmp/as-dev-wf409-merge/data/dev*`;DB 仍为 `agent-dev-context-pgdata`。 +- 项目 143 主仓 `output.md` SHA 与 DB/head 一致;容器内同路径 missing。 +- `/health`、`/ready` 均错误地返回 200。 diff --git a/.doc/remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md b/.doc/remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md new file mode 100644 index 00000000..24b5d950 --- /dev/null +++ b/.doc/remediation/2026-09-14-dev-runtime-data-root-and-projection-remediation.md @@ -0,0 +1,138 @@ +--- +title: "dev 运行数据根隔离与文档投影自愈整改方案" +status: planned +created: 2026-09-14 +updated: 2026-09-14 +--- + +# dev 运行数据根隔离与文档投影自愈整改方案 + +## 背景 + +现场根因见 [dev rebuild 从临时 worktree 挂载空数据根](../audit/2026-09-14-dev-runtime-data-root-split-brain.md)。当前 dev 的数据库仍是原持久卷,但 app、PI Runner、Cursor Runner 已绑定 `/tmp/as-dev-wf409-merge/data/dev`,导致项目 143 等正文投影在容器中不可见,GET 返回 409。 + +本方案不仅恢复项目 143,而是消除“源码 checkout/worktree 决定运行数据根”的系统耦合,并让账本到兼容文件投影成为可观测、可重试、可恢复的持久状态机。 + +## 目标 + +1. 任意 build source/worktree 都不能改变 dev/test/main 的持久数据根。 +2. 数据库、app、PI Runner、Cursor Runner 必须对同一环境 storage identity 达成一致,否则进程不 ready、部署脚本失败。 +3. ledger head 可用时,安全的投影 missing 不再使 UI 正文不可读;真实 fork 仍 fail closed。 +4. DB head 与 `output.md` 的双写失败可持久观测、幂等重试和自动补偿,不依赖用户反复刷新。 +5. rebuild、restart、宿主重启、linked worktree、并发保存和磁盘故障均有自动化负例;最终以真实页面刷新/重登/重建后持久化验收。 + +## 核心决策 + +### D1 — 代码根与状态根彻底分离 + +- `SOURCE_ROOT`:本次构建源码,可来自受控的集成 worktree。 +- `RUNTIME_STATE_ROOT`:环境持久状态根,必须是绝对、canonical、非 worktree 相对路径;dev/test/main 各自固定。 +- Compose 的 app/runner bind mount 全部改为 `${RUNTIME_STATE_ROOT}//...`,不得再写 `./data/`。 +- `.regulations`、agent sessions、attachments 等可变状态一并纳入状态根清单;只读代码资源继续来自 source root。 + +建议短期兼容现有数据,将 primary repository 的 `data/` 解析为默认状态根;中期迁移到仓库外(例如 `/var/lib/agent-system` 或管理员配置路径)。禁止硬编码个人 HOME,脚本通过显式配置或 Git common-dir 推导 primary root。 + +### D2 — 存储身份握手,而不只比较路径字符串 + +- 每个环境状态根创建受保护的 `.storage-identity.json`:`environment`、随机 UUID、schema version;不含 secret。 +- PostgreSQL 新增单行环境 identity(或复用 settings 表),首次受控初始化时写入同一 UUID。 +- app 启动读取文件 identity 并与数据库 identity 比对;app、PI Runner、Cursor Runner 都暴露/上报相同 UUID。 +- UUID 缺失、环境名不符、根不可写、runner identity 不一致时 fail startup/readiness;不得自动初始化一个已含业务数据库对应的空文件根。 + +该机制可以识别“路径看起来合法但实际是另一份空目录/备份目录”的场景。 + +### D3 — ledger 是读取真源,projection 是受管兼容视图 + +- 已有 ledger 的 `GET .../content` 从 immutable blob/head 返回正文,不再因单纯 `MISSING` 阻断 UI。 +- 同时返回 `projection_status` 或响应头,标识 `aligned | pending | missing | fork | failed`;真实 `CONTENT_FORK` 仍 409,并禁止覆盖双方。 +- 只有 storage identity 已通过且 work_dir 在允许根内时,`MISSING` 才允许后台从 head 原子重建;挂载身份错误时不自愈,避免在错误空根批量制造“看似恢复”的第二份数据。 +- 无 ledger 的 legacy 文档仍从文件 bootstrap,并沿用 hard-break 安全 canonicalization 与迁移审计。 + +### D4 — 用 transactional outbox 收口双写 + +新增/扩展持久 projection job: + +```text +doc_id, target_snapshot_id, target_sha256, storage_uuid, +state(pending/applying/aligned/failed/fork), attempts, +last_error_code, next_retry_at, updated_at +``` + +- 人工 PATCH、agent commit/reject 在同一 PostgreSQL 事务中更新 head 并 upsert projection job。 +- projector 使用 `doc_id + target_snapshot_id` 幂等执行,校验 storage UUID 与允许路径,临时文件 fsync + rename + parent fsync 后再标 aligned。 +- 同文档任务合并为最新 head;旧任务不得回写覆盖新 head。 +- missing 可重建;mismatch/fork 保存两侧 hash/路径后转人工处理;失败采用有上限退避并告警。 +- 同步写成功可快速返回 200;超时/失败返回明确 `projection_status=pending/failed`,不得让用户误以为账本未保存。 + +## 分波次计划 + +| 波次 | 内容 | 验收 | +|---|---|---| +| Wave 0 事故遏制与恢复 | 占集成槽;暂停 dev 文档写入/PI;导出 app/pi/cursor 实际 mounts、DB storage 状态;分别备份主仓根与错误 `/tmp` 根的 08:40 后增量;生成冲突清单;确认无分叉后从固定状态根重建三容器 | 项目 143 文件不改内容且三方 SHA 仍为 `f283...f039`;容器 source 指向固定状态根;目标 content 200;错误根只读保全,不盲拷贝/删除 | +| Wave 1 脚本硬门禁 | `env-service.sh` 解析 primary/common Git root与显式 `RUNTIME_STATE_ROOT`;mutating action 校验绝对路径、环境 identity、分支/集成模式;打印 build source 与 state root;worktree 未显式指定稳定 state root 时直接失败 | linked worktree 默认执行 rebuild 失败且不替换现有容器;合法集成构建的所有 bind source 保持固定 | +| Wave 2 Compose 解耦 | dev/test/main/shared compose 的可变 bind 全部改为绝对 state-root 变量;为 app/pi/cursor 添加 storage UUID/环境标签;CI 检查 `docker compose config` 不出现工作树内 `data/` | 从两个不同 source roots 渲染 config,数据挂载 source 完全相同;代码 build context 可不同 | +| Wave 3 启动与 readiness | 引入文件/DB storage identity;readiness 增加 `storage_ready`、`storage_id_match`、runner mount/identity;部署后脚本核对 container labels + mounts + sentinel + active-doc projection 摘要 | 错误空根启动被拒绝;本次 split-brain 复现实验 `/ready` 必须 503,不能再显示 healthy | +| Wave 4 投影状态机 | ledger read 与兼容 projection 解耦;实现 projection outbox、幂等 projector、missing 安全重建、fork 隔离和结构化状态/告警 | DB commit 后 kill -9、只读目录、磁盘满、延迟重试均不丢 head;恢复后自动 aligned;fork 不被覆盖 | +| Wave 5 数据治理 | 清理/标注 dev 中历史临时 `work_dir`;真实项目、测试 fixture、临时 runner 目录分 namespace;增加 orphan/missing/fork 定期巡检和保留策略 | active 非模板文档 projection 100% 可解释;测试临时路径不污染生产统计;巡检报告可追溯 | +| Wave 6 验收与回滚演练 | 工程测试、容器 rebuild/recreate、宿主重启、真实 UI 修改/刷新/重登、双用户冲突、真实 PI 修改、回滚演练 | 下述验收矩阵全 PASS,报告归档后才恢复 FEATURE-MATRIX `done` 和 gap PASS | + +## 即时恢复 Runbook(批准实施后执行) + +1. 在 CURRENT 占 `infra-runtime-state` mutex 与集成槽,记录容器 ID/created/mount source;暂停售写。 +2. 对主仓状态根和错误 `/tmp/as-dev-wf409-merge/data/dev` 做只读 inventory:相对路径、size、mtime、SHA;特别核对 08:40 UTC 后新增/修改项。 +3. 若错误根存在新业务写入,按 DB task run、snapshot、source_run_id 和文件 hash 逐项判定;不得 `cp -r` 覆盖主根。 +4. 备份差异和 Compose inspect 证据;用固定 state root rebuild app/pi/cursor。数据库 named volume不变。 +5. 验证 storage identity、mount source、`/ready`、项目 143 content/revision、活跃项目 projection 摘要。 +6. 仅在页面完成项目 143 打开、保存唯一 marker、硬刷新、退出重登;随后真实 PI 修改一份新文档并验证 app rebuild 后恢复。 +7. 错误根在报告签字前只读保留;确认无独有数据后再按独立授权处置,本方案不授权删除。 + +## 自动化验收矩阵 + +### 脚本与 Compose + +- [ ] primary repo、linked worktree、`/tmp` integration checkout 三种 source root 下,合法 config 的 state bind source 相同。 +- [ ] 未配置/相对/不存在/环境名不符的 state root 均 fail fast,已有容器不被 recreate。 +- [ ] dev 命令不能挂 test/main 根;storage UUID 交叉时拒绝。 +- [ ] app、pi-agent、cursor-agent 三者 mount source 和 storage UUID 一致。 + +### 后端与故障注入 + +- [ ] ledger head + projection missing + identity correct:GET 200 返回 head,job pending 后原子重建并 aligned。 +- [ ] ledger head + projection missing + identity mismatch:readiness 503,不执行自愈写入。 +- [ ] ledger head + projection fork:409 `DOCUMENT_CONTENT_PROJECTION_DRIFT`,保全双方。 +- [ ] PATCH 在 ledger commit 后 projector 失败:状态可见、同幂等键不重复增 r、后台恢复后 aligned。 +- [ ] projector 处理中又产生新 head:旧 job 不得覆盖新 snapshot。 +- [ ] app/projector/runner restart 与 kill -9 后任务可恢复;告警包含 doc_id、snapshot、storage UUID、correlation id,不含正文或 secret。 + +### 真实页面验收 + +- [ ] 项目 143 正文首次打开无 409;人工保存后 r+1、硬刷新、退出重登仍可见。 +- [ ] 保存后立即刷新与连续刷新不会触发 loading 死锁或重复错误风暴。 +- [ ] 真实 PI/小队修改新项目文档,候选确认/拒绝、change report、最终正文均可见;fallback 明确标识。 +- [ ] dev app + runner rebuild/recreate 后,旧项目和新项目正文/版本/overlay 都恢复。 +- [ ] 双浏览器旧 ETag 保存仍显示真正的 `HEAD_CONFLICT`,不能与 projection 409 混淆。 + +## 监控与 SLO + +- 指标:`document_projection_total{state}`、`projection_repair_total{result}`、`projection_age_seconds`、`storage_identity_mismatch_total`、`content_read_fallback_total`。 +- 告警:任一 storage identity mismatch 立即 P0;active 文档 `pending/failed` 超过 60 秒告警;fork 立即人工处置。 +- 日志:部署开始/结束必须记录 source commit、compose working dir、state root identity、实际 mount source hash;禁止记录正文与凭证。 +- `/ready`:数据库/schema/task store/storage identity/runner identity 为硬门禁;projection 汇总按 active 用户文档设阈值并列出计数,不泄露 doc 内容。 + +## 回滚策略 + +- Wave 1–3 可回滚代码,但不得回滚/重建 storage UUID;旧相对 bind mount 禁止作为回滚方案。 +- Wave 4 使用 feature flag 分阶段启用 ledger-read 与 projector;关闭 projector 时保留 pending jobs,不删除。 +- 所有自动修复只覆盖 `MISSING + identity matched`;任何 fork 保留证据并停机式处理。 + +## 方案边界 + +- 本轮不修改业务代码、不重建容器、不修复/覆盖任何正文。 +- 当前 409 的即时恢复与系统实现必须由用户批准后,在独占集成槽执行。 +- test/main 仅做只读挂载审计;未经环境晋升授权不得改动。 + +## 相关链接 + +- Audit: [dev rebuild 从临时 worktree 挂载空数据根](../audit/2026-09-14-dev-runtime-data-root-split-brain.md) +- Short-term: [dev runtime data root guard 诊断与设计](../ops/short-term/2026-09-14-dev-runtime-data-root-guard.md) +- gap-matrix 行: `RUNTIME-DATA-ROOT-SPLIT-BRAIN` diff --git a/.doc/remediation/INDEX.md b/.doc/remediation/INDEX.md index d948d641..1ef2f73f 100644 --- a/.doc/remediation/INDEX.md +++ b/.doc/remediation/INDEX.md @@ -3,6 +3,7 @@ | 文档 | 说明 | |------|------| | [gap-matrix.md](./gap-matrix.md) | 发布缺口 SSOT | +| [2026-09-14-dev-runtime-data-root-and-projection-remediation.md](./2026-09-14-dev-runtime-data-root-and-projection-remediation.md) | 临时 worktree 错挂 dev 数据根、storage identity、readiness 与 projection outbox 系统整改 | | [2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md](./2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md) | 项目设置 409 冻结态守卫 + 非必需文档三权全有或全无 + 成员/门禁/权限一致性 | | [2026-09-14-controlled-content-canonical-hash-remediation.md](./2026-09-14-controlled-content-canonical-hash-remediation.md) | 项目 143 正文 canonical/raw 哈希漂移、错误语义与重复 hydration 整改 | | [2026-09-13-document-revision-reviewer-duty-sync.md](./2026-09-13-document-revision-reviewer-duty-sync.md) | UI-01 已分配 B=reviewer,受控文档 reviewer_id 硬编码 2,矩阵降为 viewer | diff --git a/.doc/remediation/gap-matrix.md b/.doc/remediation/gap-matrix.md index 249e4218..ddc172c2 100644 --- a/.doc/remediation/gap-matrix.md +++ b/.doc/remediation/gap-matrix.md @@ -2,7 +2,7 @@ **更新**: 2026-09-14 **分支**: dev -**production_ready**: no(`CHAT-INTENT-STATE` 等无关 blocker、CI 真实 PI 三工作流 E2E / 前端人工抽检未完成;文档账本运行时 UI Round20 已 PASS,不把 fallback 报告当成完整 PI) +**production_ready**: no(`RUNTIME-DATA-ROOT-SPLIT-BRAIN` critical:当前 dev DB 与正文文件根分裂且 readiness 假阳性;另有 `CHAT-INTENT-STATE`、CI 真实 PI 与前端抽检 blocker) **协作焦点**: [../ops/CURRENT.md](../ops/CURRENT.md) **Agent 手册**: [AGENTS.md](../AGENTS.md) · [PROJECT.md](../PROJECT.md) @@ -57,6 +57,7 @@ | ID | 描述 | 状态 | 证据 | 计划动作 | |---|---|---|---|---| +| RUNTIME-DATA-ROOT-SPLIT-BRAIN | dev rebuild 从临时集成 worktree 解析 `./data/dev`,app/pi/cursor 挂到空 `/tmp` 数据根,而 PostgreSQL 沿用原持久卷;100 条非模板记录在当前容器根抽样均 missing,health/ready 仍 200 | **FAIL / CRITICAL** | [audit](../audit/2026-09-14-dev-runtime-data-root-split-brain.md)。项目 143 主仓文件、DB、ledger head SHA 一致,容器内同路径不存在;Compose working dir 与三容器 mount source 均为 `/tmp/as-dev-wf409-merge` | [remediation](./2026-09-14-dev-runtime-data-root-and-projection-remediation.md)。先占槽冻结/差异保全/恢复固定根,再实施绝对 state root、storage identity、readiness 和 projection outbox;恢复前禁止用 dev healthy 或文档 E2E 签字 | | WORKFLOW-PLAN-FROZEN-OPTIONAL-DUTIES | active 项目仍展示可编辑职责表单,409 提示在长表单视口外;现有规划又不支持非必需文档“三权全空”,且后端缺少职责人在任成员复核 | **PASS(设置页)** | [audit](../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md);[test-report](../test-reports/2026-09-14-workflow-plan-409-frozen-and-optional-duties-test-report.md)。项目 143 只读 frozen、A full+none 激活、B partial 不发 PUT、C 零治理拒绝、双标签转 frozen、成员无配置入口、刷新后 143 仍 frozen | 真实 PI 生成 full/none 并走审批/阶段不阻塞未纳入本轮页面矩阵,不作为本 gap 关闭的替代证据,另立项 | | DOC-CONTENT-CANONICAL-HASH-DRIFT | 旧受控文档 bootstrap 裁剪 Markdown 双空格硬换行并将 canonical SHA 写入账本/DocGov,但未同步文件;正文读取把完整性冲突误报 404,lease/list sync 又放大并制造刷新后加载死锁 | **PASS(真实 UI)** | [audit](../audit/2026-09-14-project143-controlled-content-404.md);[UI report](../test-reports/2026-09-14-controlled-content-ui-acceptance.md)。代码已合 dev、schema 32、项目 143 定向 `migration_repair`;项目 143 `clean-run3` 与新项目 `full-chain-final` 主链及 restart 全部纯页面 PASS,目标 404=0 | [remediation](./2026-09-14-controlled-content-canonical-hash-remediation.md)。隔离 `TestReconcile_LegacyRepair_PG` 未执行,按用户“本轮只允许页面测试”的边界保留为工程测试债,不作为本轮 UI PASS 的替代证据 | | CHAT-HISTORY-REFRESH | 工作流激活后刷新,对话正文被阶段过滤器清空,但分类数量与锚点仍按全量返回 | **PASS** | [test-report](../test-reports/2026-09-13-chat-history-refresh-test-report.md);代码:`message_scope.go`、`chat_history_auth.go`、goose 00026/00027 | 真实 PI 刷新 E2E 待 dev 容器补录(自动化/PG 已 PASS) | diff --git a/.doc/status/FEATURE-MATRIX.md b/.doc/status/FEATURE-MATRIX.md index f56e25b4..43cc436d 100644 --- a/.doc/status/FEATURE-MATRIX.md +++ b/.doc/status/FEATURE-MATRIX.md @@ -21,11 +21,11 @@ | 工作流治理 | 三正式工作流 + 阶段门禁 | partial | [remediation/three-workflows](../remediation/2026-09-12-three-workflows-strict-governance-remediation.md);[test 2026-09-12](../test-reports/2026-09-12-three-workflows-governance-remediation-test-report.md);[冻结态/职责可选整改](../remediation/2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md);[Wave 5 页面矩阵](../test-reports/2026-09-14-workflow-plan-409-frozen-and-optional-duties-test-report.md);[职责-动作鉴权](../test-reports/2026-09-14-governance-action-level-authz-test-report.md) | 设置页/DutyMode + 动作级鉴权/未激活 202-blocked/关闭创建直通已落地。后续:none 文档真实 PI 审批长链路、CI 三工作流 E2E | `workflow` | | 纯软件项目自举 | 软件项目创建、Bootstrap 基线、Delivery 迭代与受控工程交接 | planned | [详细设计](../system-design/2026-09-14-software-project-bootstrap-workflow.md);[现状调研](../ops/research/2026-09-14-software-project-bootstrap-current-state.md);[验收计划](../ops/checklists/2026-09-14-software-project-bootstrap-acceptance.md) | 当前仅完成方案;目录/模板/执行器/创建向导/真实 PI UI 均未实施。受控代码执行单列后续 Epic | `workflow-design` | | IAM / 鉴权 | 项目级 API 鉴权 + RBAC | partial | [audit/authorization](../audit/authorization-matrix-v2.md);[刷新恢复专项审计](../audit/2026-09-13-chat-history-refresh-disappears.md) | 历史读取端点补统一会话 owner + 阶段授权;旧 API 410 标注(BUG-11 partial) | `authz` | -| 文档治理 | 法定文档生命周期 + 租约 + 受控正文读取 | done | [test docgov](../test-reports/2026-09-08-docgov-lifecycle-and-claiming-framework-test-report.md);[项目 143 正文 404 审计](../audit/2026-09-14-project143-controlled-content-404.md);[受控正文真实 UI 验收报告](../test-reports/2026-09-14-controlled-content-ui-acceptance.md);[UI-06 缓存竞争审计](../audit/2026-09-14-document-revision-refresh-cache-race.md);[整改](../remediation/2026-09-14-controlled-content-canonical-hash-remediation.md) | 生命周期/租约保留。W1–W6 + 审查修补 + UI hydrate 资格 + 晚到 list sync 修复均已合入 dev。项目 143 纯 UI 回归(clean-run3)与新项目全链路(main + post-restart)已全量真实页面验证通过(PASS) | `docgov` | +| 文档治理 | 法定文档生命周期 + 租约 + 受控正文读取 | partial | [数据根 split-brain 审计](../audit/2026-09-14-dev-runtime-data-root-split-brain.md);[test docgov](../test-reports/2026-09-08-docgov-lifecycle-and-claiming-framework-test-report.md);[项目 143 canonical 漂移审计](../audit/2026-09-14-project143-controlled-content-404.md);[既有真实 UI 报告](../test-reports/2026-09-14-controlled-content-ui-acceptance.md) | canonical/hash 与 UI 竞态旧缺陷已修;但当前 dev 因临时 worktree 数据根 split-brain 再次不可读。须完成固定 state root、storage identity、projection outbox/自愈和 rebuild 负例后恢复 `done` | `docgov` | | 文档版本与修改认知 | 智能体版本边界、人工修订标记、任意版本比较、变更报告与负责人通知 | partial | [管理测试](../test-reports/2026-09-13-document-revision-management-test-report.md);[运行时 UI Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md);[运行时审计](../audit/2026-09-13-document-revision-runtime-404.md);[ADR](../adr/2026-09-13-document-version-boundary.md) | **运行时 UI PASS**(6159 / V3/r2)。仍 `partial`:change report=`fallback`(真实 diff+lineage,非完整 PI 叙事)。系统级 `CHAT-INTENT` 见 gap-matrix | `docgov` | | 前端工作台 | 三栏 + 块文档 + SSE | partial | [test workbench](../test-reports/2026-09-08-three-column-workbench-and-block-document-test-report.md);[现场专项审计](../audit/2026-09-13-conversation-intent-runtime-state.md) | 错误卡片/任务进度/文档 busy 状态刷新恢复与人工抽检待完成 | `web` | | PI Runner / 配置链 | pi-agent + MiniMax 真实链路 | done | [test piagent](../test-reports/2026-09-13-codex-audit-p0-remediation-test-report.md);[container-env](../test-reports/2026-09-13-container-env-configuration-test-report.md) | — | `pi-agent` | -| 三环境运维 | dev/test/main + 共享 KB | done | [PROJECT.md](../../PROJECT.md) §4;四环境 healthy | — | `infra` | +| 三环境运维 | dev/test/main + 共享 KB | partial | [PROJECT.md](../../PROJECT.md) §4;[数据根 split-brain 审计](../audit/2026-09-14-dev-runtime-data-root-split-brain.md) | dev app/pi/cursor 当前错挂 `/tmp` 空数据根且 readiness 假阳性;同类相对 bind 设计覆盖 test/main,须完成绝对 state root 与身份门禁 | `infra` | | CI / E2E | PG 集成 + 真实 PI E2E | partial | [gap-matrix BUG-08](../remediation/gap-matrix.md);[test pg-ci](../test-reports/2026-09-12-pg-ci-soak-e2e-test-report.md);[运行时 UI Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) | CI `TEST_DATABASE_URL` 隔离库仍缺;文档运行时真实 UI 已按 UI_PHASE 分文件且 post-restart 断言最终 A1 正文。`-race` 全包并行 TempDir flake 为基建残留 | `ci` | ---