docs(audit): diagnose dev runtime data root split-brain

This commit is contained in:
weichen
2026-09-14 09:06:00 +00:00
parent a8f9451410
commit f1edb70fbd
8 changed files with 377 additions and 7 deletions
@@ -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 只读核对;本轮未扩大操作范围。
+8
View File
@@ -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)
+8 -4
View File
@@ -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/<slug>` 或 `fix/<slug>`,**禁止** `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) |
@@ -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。
@@ -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}/<env>/...`,不得再写 `./data/<env>`。
- `.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/<env>` | 从两个不同 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`
+1
View File
@@ -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 |
+2 -1
View File
@@ -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) |
+2 -2
View File
@@ -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` |
---