diff --git a/.doc/adr/2026-09-14-modular-monolith-decoupling.md b/.doc/adr/2026-09-14-modular-monolith-decoupling.md new file mode 100644 index 00000000..d337fd64 --- /dev/null +++ b/.doc/adr/2026-09-14-modular-monolith-decoupling.md @@ -0,0 +1,62 @@ +# ADR:以纵向绞杀方式实施模块化单体解耦 + +## 状态 + +提议(2026-09-14;架构与实施计划已修订,等待用户确认后进入首个实现里程碑) + +## 背景 + +`internal/server`、`internal/bizdev`、共享 `UserProjectContext`、工作区路径和多表写入形成隐式耦合。 +历史受控正文 404/409、刷新后投影缺失和任务终态不完整说明,单纯搬包不会修复事实源、事务与恢复边界。 + +## 决策 + +1. **保持模块化单体**:不拆微服务;进程内调用使用窄端口,跨进程可靠性只用于持久事件和 FAV。 +2. **纵向绞杀**:先在现有包边界插入端口和适配器,迁完一条业务链并验证后再移动或重命名包;禁止“先全量搬包、以后再接口化”。 +3. **契约分层**:外部 API DTO 归 `server/api`;持久 schema、公共事件和稳定共享词汇进入按领域划分的 contracts;一对一内部接口由消费者拥有。 +4. **命令与事务同属一个应用用例**:一个业务命令只有一个事务 owner;跨表属主通过同一不透明事务作用域协作,业务状态、账本、当前指针、staging 和 outbox 原子提交。 +5. **单一写者不等于单独事务**:表属主控制 SQL 和不变量;应用 UnitOfWork 控制跨属主提交边界。属主端口不得私自嵌套提交。 +6. **并发正确性以数据库为准**:用户编辑使用乐观版本/ETag;状态转换使用行锁和条件更新;项目级不变量可使用事务级 advisory lock;进程内 mutex 只能优化,不能作为多副本正确性依据。 +7. **锁顺序固定**:同一事务按 project/workflow instance → document record → document version/snapshot → task run → outbox/staging 的顺序取锁;事务内禁止 LLM、对象存储、网络调用和长文件 I/O。 +8. **可靠事件采用 transactional outbox**:事件按至少一次投递设计,具有稳定 ID、schema version、环境/项目范围、聚合序号、因果链和幂等键;逐消费者记录 delivery、重试、租约和死信。 +9. **工作区是可清理投影**:跨模块只传不可伪造 Workspace ID 和受限 artifact key;物理路径只在 workspace/squadrun 适配器内解析和校验。 +10. **最终成果进入 FAV**:PostgreSQL 保存业务关系和授权,FAV 只保存已确认终态的精确 payload、manifest、回执和删除状态,workspace 不承担最终成果事实源。 +11. **授权逐动作校验**:认证适配器构造不可由请求覆盖的 Principal;HTTP、CLI、后台订阅者和恢复流程都按 action/resource/current state fail closed。 +12. **先完成文档可靠性试点**:只有受控正文刷新、重启、并发提交、投影缺失降级和授权负例通过后,才批准 chat、squad、编排层和传输层扩展。 +13. **迁移采用 expand/migrate/contract**:允许新增 Goose migration;不得修改已发布迁移;回滚只保证回到最近的兼容阶段,不能任意独立 revert 早期依赖。 +14. **协议改造独立立项**:统一错误信封和 PI Runner `result.json` 不作为核心模块解耦前置条件;它们使用独立兼容与 E2E 验收。 + +## 并发与失败语义 + +- 普通聚合状态转换默认 `READ COMMITTED` + 明确行锁/条件更新;涉及跨聚合唯一不变量时才使用 Serializable,并对 `40001`/`40P01` 重跑完整事务。 +- outbox worker 使用小批次 `FOR UPDATE SKIP LOCKED` 认领,处理发生在事务外;完成、续租和失败回填使用 fencing token 防止过期 worker 覆盖新 owner。 +- 每个写命令必须同时有 idempotency key、expected version 或两者;相同 key 不同 payload 返回 typed conflict。 +- 锁等待、事务时长、重试次数、队列积压和死信必须可观测;超过预算时失败返回,不无限等待。 + +## 授权与恢复语义 + +- Principal 至少携带 actor、环境、认证来源和请求关联 ID;项目、会话、文档、阶段与 duty 从数据库权威关系重新解析。 +- 持久事件中的 actor 只用于审计,不能替代订阅者对当前资源状态的授权检查。 +- FAV 降级读取要求运行库授权图完整;授权图缺失时禁止面向用户返回内容。 +- 恢复只允许进入 scratch identity;promotion 需要系统管理员与数据责任人确认,并通过各属主端口提交,禁止恢复 CLI 直写运行表。 + +## 后果 + +- 首个实现里程碑的改动面小于原 Wave 1–6,但会先增加 UnitOfWork、事件投递和授权基础设施。 +- “单表唯一写者”检查改为生产 SQL 写权限检查,并允许 migration、测试和经批准运维脚本存在表名。 +- `feature/final-artifact-vault` 是候选依赖,不直接假定可合入;必须先按当前 dev 重放测试、迁移和权限审计。 +- 包物理移动、错误信封、SSE 和 PI Runner 协议可延后,不阻塞文档一致性整改。 + +## 替代方案 + +- **一次性横向搬包**:拒绝。冲突面大,不能先证明用户故障减少。 +- **仅增加 arch-check**:拒绝。门禁不能替代事务、锁和授权契约。 +- **拆微服务**:拒绝。当前问题是模块边界和数据一致性,不需要引入分布式事务与跨服务部署成本。 +- **继续把 workspace 作为恢复来源**:拒绝。工作区属于可清理计算现场,不能成为最终事实源。 + +## 相关文档 + +- [模块解耦架构](../system-design/2026-09-14-module-decoupling-architecture.md) +- [模块解耦整改与重构计划](../remediation/2026-09-14-module-decoupling-refactor-plan.md) +- [方案复核](../audit/2026-09-14-module-decoupling-plan-review.md) +- [文档版本边界 ADR](./2026-09-13-document-version-boundary.md) diff --git a/.doc/archive/ops/2026-09-14-module-decoupling-remediation-plan.md b/.doc/archive/ops/2026-09-14-module-decoupling-remediation-plan.md new file mode 100644 index 00000000..2ba98905 --- /dev/null +++ b/.doc/archive/ops/2026-09-14-module-decoupling-remediation-plan.md @@ -0,0 +1,55 @@ +--- +title: "模块解耦架构与重构计划修订" +status: done +created: 2026-09-14 +updated: 2026-09-14 +owner: Codex +branch: feature/module-decoupling-remediation +worktree: .worktrees/feature/module-decoupling-remediation +mutex: architecture-docs +--- + +# 模块解耦架构与重构计划修订 + +## 目标 + +把模块解耦方案修订为可以分段实施、验证和回滚的架构与重构计划,补齐事务、并发互斥、授权、可靠事件、FAV、数据迁移和真实页面验收。 + +## 范围 + +- 修订模块化单体架构设计与 ADR。 +- 新增详细整改与重构计划。 +- 建立首个文档可靠性纵向试点及后续里程碑任务。 +- 更新 CURRENT、FEATURE-MATRIX 和相关索引。 + +## 非目标 + +- 本任务不修改业务代码、数据库迁移或部署配置。 +- 本任务不合并 `feature/final-artifact-vault`,只定义其集成依赖和边界。 +- 本任务不执行共享容器验证,也不占用集成槽位。 + +## 步骤 + +1. [x] 对照当前源码、已接受 ADR、FAV 分支和外部权威资料复核原方案。 +2. [x] 修订架构设计与 ADR。 +3. [x] 编写详细整改计划、里程碑任务和验收清单。 +4. [x] 更新协作 SSOT 与索引并完成文档一致性检查。 + +## 验收标准 + +- [x] 计划明确事务所有权、锁顺序、事件投递语义、授权主体和恢复边界。 +- [x] 首个独立交付边界直接覆盖受控文档刷新、重启、投影缺失和授权负例。 +- [x] 每个后续里程碑有依赖、文件范围、并发风险、测试矩阵、停走门和回滚边界。 +- [x] FAV、迁移语料库和完整备份的职责不重叠,工作区不再承担最终成果事实源。 + +## 相关链接 + +- Checklist: [../../ops/checklists/2026-09-14-module-decoupling-refactor-acceptance.md](../../ops/checklists/2026-09-14-module-decoupling-refactor-acceptance.md) +- Handoff: 不需要;实现阶段使用两个 planned short-term 接续 +- Test report: 本任务仅产出计划,不生成业务验收报告 + +## 证据 / 备注 + +- 原方案位于主工作区未跟踪文件;本任务在独立 worktree 重建并修订,未覆盖原工作区。 +- `feature/final-artifact-vault` 已有实现与测试证据,但尚未合入本任务基线,实施前必须重新审计分支差异。 +- 文档校验:本地相对链接、R/KTD/U ID 唯一性、Mermaid fence 和 `git diff --check` 均通过。 diff --git a/.doc/audit/2026-09-14-module-decoupling-plan-review.md b/.doc/audit/2026-09-14-module-decoupling-plan-review.md new file mode 100644 index 00000000..d7dcbd64 --- /dev/null +++ b/.doc/audit/2026-09-14-module-decoupling-plan-review.md @@ -0,0 +1,98 @@ +--- +title: "模块解耦方案复核" +status: done +created: 2026-09-14 +updated: 2026-09-14 +scope: architecture, transactions, concurrency, authorization, events, artifact recovery +--- + +# 模块解耦方案复核 + +## 结论 + +模块化单体、单一写者、瘦传输层和应用编排层的方向成立,但原方案不能直接批准 Wave 1–6。 +原方案把事务、事件、工作区和最终成果四条边界拆开设计,可能在包依赖减少后产生新的数据不一致、跨租户授权旁路和不可恢复投影。 +整改应先打通一条真实文档纵向链路,再按证据决定是否扩展到 chat、squad、SSE 和 PI Runner。 + +## 已确认问题 + +### A1. 单一写者拆分会破坏跨表原子性 + +`internal/docrevision/pg_mutate.go` 当前在一个事务中写版本账本并投影 `document_records` 根指针。 +原方案要求 docrevision 改调 docgov/freedoc 普通端口,却没有定义跨属主事务或命令所有权。 +如果各端口各自提交,会出现版本账本、正文 head、治理状态和 outbox 部分成功。 + +### A2. 现有 outbox 不是可泛化的可靠事件总线 + +`internal/docrevision/service.go` 在版本事务完成后调用 `OnOutbox`,并忽略回调错误。 +`outbox_events` 只有全局 `published_at`,没有逐消费者确认、租约、重试、死信和幂等收件箱。 +拟议的 `Publish(ctx, event)` 也没有事务作用域,不能兑现“与业务同事务”。 + +### A3. 工作区仍被当成事实源 + +原 `WorkspaceRef` 暴露 `Root`、`OutputDir` 和 `ManifestPath`,`OutputContract` 继续接受裸路径。 +这不能从类型上阻止路径拼接、跨工作区访问或链接逃逸。 +docquery 若继续在运行时从 `output.md` 选择正文,会保留导致历史 404/409 的 split-brain 根因。 + +### A4. 最终成果保险库未进入模块边界 + +`feature/final-artifact-vault` 已设计并实现终态归档、只读降级和 scratch 恢复,但尚未合入当前 dev 基线。 +原解耦方案没有为 artifactvault 分配端口、事件、授权和恢复边界,后续容易由 docquery 或 CLI 重建旁路。 + +### A5. contracts 可能成为新的高扇入万能包 + +原方案把内部接口、HTTP DTO、事件、错误码和共享枚举集中到一个包,并要求所有 DTO 版本化。 +这会把语义耦合搬到 `contracts` 和 `app`,使普通内部调整承担不必要的 ADR 成本。 + +### A6. 并发控制只覆盖了进程内互斥 + +当前代码同时存在 `sync.Mutex`、行锁、乐观 ETag、唯一幂等键和 `pg_advisory_xact_lock`。 +原方案没有规定哪些锁用于正确性、锁顺序、超时、重试边界和多副本行为。 +进程内锁只能优化单实例竞争,不能作为跨实例正确性条件。 + +### A7. 授权主体和后台执行边界缺失 + +“在应用层入口统一授权”没有定义可信 Principal 的来源,也没有覆盖 CLI、事件订阅者、恢复工具和后台任务。 +若端口继续接受调用方填写的 `UserID`、`ProjectID` 或裸 `DocRef`,可能形成 confused-deputy 或跨项目访问。 + +### A8. 横向 Wave 缺少最小可交付边界 + +原方案先物理搬包,再接口化,最后才处理单写者和事件语义。 +这会重复修改高扇出代码,并在真正验证边界之前冻结大量无消费者契约。 +错误信封和 PI Runner 协议与文档一致性解耦没有直接依赖,不应阻塞核心路线。 + +### A9. 迁移与回滚承诺不成立 + +可靠多消费者 outbox、FAV 集成和必要的状态字段需要增量 migration,因此“数据零迁移”不成立。 +后续里程碑会依赖前序契约,不能任意独立 revert;只能按 expand/migrate/contract 阶段和兼容窗口回退。 + +### A10. 架构检查范围会产生误报 + +表名和路径字面量合法存在于 Goose migration、fixture、测试和运维脚本。 +R5/R6 应检查生产运行时的越权写入和路径解析,不应以全仓 grep 的零出现作为完成条件。 + +## 外部规则核对 + +- PostgreSQL 行锁和事务级 advisory lock 会在事务结束时释放,适合短期业务互斥;session 级 advisory lock 不跟随回滚,不用于本方案的业务锁。[PostgreSQL explicit locking](https://www.postgresql.org/docs/17/explicit-locking.html) +- `SKIP LOCKED` 适合多消费者队列表,不适合作为普通一致性查询语义。[PostgreSQL SELECT](https://www.postgresql.org/docs/current/sql-select.html) +- Serializable 或 deadlock 重试必须重跑完整事务,包括决定 SQL 和参数的业务逻辑。[PostgreSQL serialization failure handling](https://www.postgresql.org/docs/16/mvcc-serialization-failure-handling.html) +- 授权必须默认拒绝,并在每次请求或后台动作上重新校验,不能信任客户端传入的身份和资源归属。[OWASP Authorization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html) + +## 整改结论 + +1. 暂停批准原 Wave 1–6,保留只读架构基线。 +2. 先定义事务、锁、授权、事件和持久成果契约。 +3. 首个交付只覆盖受控文档读取与终态提交纵向链。 +4. FAV、迁移语料和完整备份作为三个互补层接入。 +5. 通过真实页面刷新、重启、并发、权限和恢复停走门后,再批准其他模块拆分。 + +## 证据入口 + +- `internal/docrevision/pg_mutate.go` +- `internal/docrevision/service.go` +- `internal/workspace/resolver.go` +- `internal/docduty/revoke.go` +- `internal/taskrun/manager.go` +- `deploy/postgres/goose/00018_2026-09-12-outbox-notifications-stage-advance.sql` +- `.doc/adr/2026-09-13-document-version-boundary.md` +- `feature/final-artifact-vault` 分支中的 FAV ADR、整改方案与测试报告 diff --git a/.doc/audit/INDEX.md b/.doc/audit/INDEX.md index ab5873db..5e5bc69c 100644 --- a/.doc/audit/INDEX.md +++ b/.doc/audit/INDEX.md @@ -13,6 +13,14 @@ ## 核心文档(按阅读顺序推荐) +### 模块解耦方案复核(2026-09-14) + +**文件**: [2026-09-14-module-decoupling-plan-review.md](./2026-09-14-module-decoupling-plan-review.md) + +**结论**: 模块化单体方向成立,但原横向 Wave 缺少跨属主事务、可靠事件、多副本互斥、逐动作授权和 FAV 边界;应先完成受控文档可靠性纵向试点,再按证据扩面。 + +**整改**: [模块解耦与文档可靠性重构计划](../remediation/2026-09-14-module-decoupling-refactor-plan.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..836387b5 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -5,13 +5,16 @@ ## 焦点 -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. **DOC-RELIABILITY**: [文档可靠性纵向试点 M0–M1](../ops/short-term/2026-09-14-document-reliability-pilot.md)(`refactor/document-reliability-pilot`;`mutex=docgov`;不占集成槽、不 rebuild) +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 为准收敛) ## 并行认领 | owner | task | branch | worktree | mutex | status | |-------|------|--------|----------|-------|--------| +| Cursor | document-reliability-pilot | refactor/document-reliability-pilot | .worktrees/refactor-document-reliability-pilot | docgov | in_progress | +| Codex | module-decoupling-remediation-plan | feature/module-decoupling-remediation | .worktrees/feature/module-decoupling-remediation | architecture-docs | done | | verifier_antigravity | controlled-content-ui-acceptance | fix/controlled-content-ui-acceptance | .worktrees/fix-controlled-content-ui-acceptance | document-content-ui-acceptance | done | | implementer_cursor | controlled-content-canonical-hash | fix/controlled-content-canonical-hash | .worktrees/fix-controlled-content-canonical-hash | docgov | done | | Codex | project143-controlled-content-404-diagnosis | fix/project143-controlled-content-404-diagnosis | - | docgov-content-diagnosis | done | @@ -51,6 +54,7 @@ > 新任务开始前登记。`branch` 必须为 `feature/` 或 `fix/`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。 > workflow-plan 409 与治理鉴权 G1–G5 已收口(见 [test-report](../test-reports/2026-09-14-governance-action-level-authz-test-report.md))。禁止终止或改写项目 143。 +> `document-reliability-pilot` 仅占 `docgov` 编码;合 `dev` + `env-service rebuild` 必须另占集成槽位。M1 报告未签字前不得 rebuild。 ## 集成槽位 @@ -84,6 +88,8 @@ | DOCREV 管理测试 | [../test-reports/2026-09-13-document-revision-management-test-report.md](../test-reports/2026-09-13-document-revision-management-test-report.md) | | DOCREV 运行时 UI Round20 | [../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) | | 纯软件项目自举设计 | [../system-design/2026-09-14-software-project-bootstrap-workflow.md](../system-design/2026-09-14-software-project-bootstrap-workflow.md) | +| 模块化单体解耦架构 | [../system-design/2026-09-14-module-decoupling-architecture.md](../system-design/2026-09-14-module-decoupling-architecture.md) | +| 模块解耦整改计划 | [../remediation/2026-09-14-module-decoupling-refactor-plan.md](../remediation/2026-09-14-module-decoupling-refactor-plan.md) | | SSE/幂等审计 | [../audit/2026-09-13-document-revision-sse-idempotency.md](../audit/2026-09-13-document-revision-sse-idempotency.md) | | 质量门禁自污染审计 | [../audit/2026-09-13-document-revision-quality-gate-self-pollution.md](../audit/2026-09-13-document-revision-quality-gate-self-pollution.md) | | 当前文档路由审计 | [../audit/2026-09-13-document-revision-active-doc-routing.md](../audit/2026-09-13-document-revision-active-doc-routing.md) | diff --git a/.doc/ops/checklists/2026-09-14-module-decoupling-refactor-acceptance.md b/.doc/ops/checklists/2026-09-14-module-decoupling-refactor-acceptance.md new file mode 100644 index 00000000..38f408e0 --- /dev/null +++ b/.doc/ops/checklists/2026-09-14-module-decoupling-refactor-acceptance.md @@ -0,0 +1,74 @@ +--- +title: "模块解耦重构验收清单" +status: planned +created: 2026-09-14 +updated: 2026-09-14 +owner: TBD +--- + +# 模块解耦重构验收清单 + +## M0 架构与基线 + +- [ ] contracts 分类、事务 owner、表属主、锁顺序、事件信封、Principal 和 FAV 边界均有唯一规范来源。 +- [ ] arch-check 分离 production import、SQL write、path 和 event registration;例外有 owner 与到期日。 +- [ ] 当前违规基线可重复生成,新违规立即阻断。 + +## M1 文档可靠性 + +- [ ] 人工编辑、agent commit、approve/reject 的跨表写入同事务。 +- [ ] fault injection 任一点失败均无部分提交。 +- [ ] 同 ETag 并发写只有一个赢家;不同文档不被全局锁串行。 +- [ ] 1、2、4 app replicas 结果一致。 +- [ ] GET/refresh/reload 零写入,projection 缺失不伪装成 404。 +- [ ] 项目 143 等价 hazard 与新项目真实页面刷新、重登、restart、rebuild PASS。 +- [ ] 匿名、非成员、跨用户、跨项目、错误 stage/duty、授权失败负例 PASS。 + +## M2 事件与 FAV + +- [ ] 业务终态、staging 和 outbox 同事务。 +- [ ] 每个 consumer 有独立 delivery/inbox/attempt/lease/dead-letter。 +- [ ] 1、2、4 dispatcher 无重复可见副作用和永久饥饿。 +- [ ] crash-before/after-side-effect/ack 全部可恢复。 +- [ ] stale fencing、跨项目事件、错误 producer、坏 digest 均拒绝。 +- [ ] FAV 中间结果、Prompt、工具轨迹和 secret 准入率为零。 +- [ ] DB 授权成功后才允许 degraded read;授权图缺失 fail closed。 +- [ ] tombstone/删除账本 union-deny PASS。 + +## M3 TaskRun、workspace 与数据生命周期 + +- [ ] RunManifest 规范字节、SHA、成员和 storage identity 持久化。 +- [ ] 重复 finalize、SSE 断线和 cancel race 不重复消息或 artifact。 +- [ ] WorkspaceRef 无物理路径,路径遍历和链接逃逸负例 PASS。 +- [ ] 旧 lease writer 被 fencing token 拒绝。 +- [ ] workspace 清空后最终成果仍可定位和校验。 +- [ ] backfill 可暂停、续跑和重放;异常进入 quarantine。 +- [ ] 八条 migration/reload/recovery 轨道 PASS。 +- [ ] 数据销毁前完整备份、corpus、FAV coverage 和人工批准全部满足。 + +## M4–M6 选择性扩展 + +- [ ] 每个子切片在原包边界先获得行为收益,再决定物理搬包。 +- [ ] chatlog/contextasm 保持授权可见集合和计数一致。 +- [ ] app 是唯一组合根,subscriber 重复启动不重复注册。 +- [ ] UserProjectContext 不再被多个模块字段级直改。 +- [ ] server 无 SQL、路径拼接和业务闭包。 +- [ ] 对应架构规则全部 block,过期例外和 flags 已删除。 + +## 证据分层 + +- [ ] Unit/stub 证据。 +- [ ] PostgreSQL integration 证据。 +- [ ] race/concurrency/load 证据。 +- [ ] fault injection/crash 证据。 +- [ ] 真实 PI 证据。 +- [ ] 真实浏览器证据。 +- [ ] restart/rebuild 证据。 +- [ ] cross-user/project authorization negative 证据。 +- [ ] scratch recovery 与完整 DB restore 证据。 + +## 关闭条件 + +- [ ] test report 回链具体 U-ID、commit、环境 identity 和 fixture/dataset。 +- [ ] CURRENT、FEATURE-MATRIX、gap-matrix 与任务状态一致。 +- [ ] 未实施后续仍为 planned/deferred,不用局部测试冒充全计划完成。 diff --git a/.doc/ops/long-term/2026-09-14-selective-module-decoupling.md b/.doc/ops/long-term/2026-09-14-selective-module-decoupling.md new file mode 100644 index 00000000..11458be0 --- /dev/null +++ b/.doc/ops/long-term/2026-09-14-selective-module-decoupling.md @@ -0,0 +1,47 @@ +--- +title: "选择性模块解耦 M4–M6" +status: planned +created: 2026-09-14 +updated: 2026-09-14 +owner: TBD +mutex: architecture +review_cycle: milestone +--- + +# 选择性模块解耦 M4–M6 + +## 目标 + +在文档可靠性、可靠事件和 FAV 已证明收益后,按 U10–U12 分别收敛 chatlog/contextasm、应用组合根、共享上下文和传输层。 + +## 为什么是长期任务 + +这些改动不直接修复当前受控正文事实源问题,且会触及仍在进行的 chat/intent 工作。 +M1–M3 未通过前执行会扩大冲突面,并提前冻结未经真实消费者验证的契约。 + +## 触发条件 + +- M1–M3 的 test report 全部 PASS。 +- 同类缺陷触达文件数和运行故障率表明继续解耦具有收益。 +- `chat`、`intent`、`bizdev/coordinator`、`server/stream` mutex 按子切片释放。 +- 每个子切片有独立 owner、回滚点和行为验收,不批准一个巨型 Wave。 + +## 候选子任务 + +1. U10 chatlog/contextasm 原地端口化。 +2. U11a 唯一 app composition root。 +3. U11b 按事件逐个替换闭包桥。 +4. U11c 逐 use case 迁移 ExecuteTask/executeStageInternal。 +5. U11d UserProjectContext snapshot/update 收权。 +6. U12 server/SSE 边界和架构门禁收口。 + +## 不包含 + +- 错误信封统一。 +- PI Runner result.json 协议。 +- 微服务拆分。 + +## 相关链接 + +- [模块解耦整改与重构计划](../../remediation/2026-09-14-module-decoupling-refactor-plan.md) +- [模块化单体解耦架构](../../system-design/2026-09-14-module-decoupling-architecture.md) diff --git a/.doc/ops/short-term/2026-09-14-document-reliability-pilot.md b/.doc/ops/short-term/2026-09-14-document-reliability-pilot.md new file mode 100644 index 00000000..6983f3cd --- /dev/null +++ b/.doc/ops/short-term/2026-09-14-document-reliability-pilot.md @@ -0,0 +1,61 @@ +--- +title: "文档可靠性纵向试点 M0–M1" +status: in_progress +created: 2026-09-14 +updated: 2026-09-14 +owner: Cursor +branch: refactor/document-reliability-pilot +worktree: .worktrees/refactor-document-reliability-pilot +mutex: docgov +--- + +# 文档可靠性纵向试点 M0–M1 + +## 目标 + +在不横向搬包的前提下,完成 U1–U4,使受控文档在并发提交、页面刷新、重登、restart、rebuild 和 projection 缺失后仍从数据库账本返回唯一授权正文。 + +## 范围 + +- U1 架构基线与 production writer/path 门禁。 +- U2 应用 UnitOfWork 和跨属主事务能力。 +- U3 人工编辑、agent commit、批准/驳回事务化。 +- U4 授权后的文档统一读模型。 +- 项目 143 等价脱敏 hazard 与新项目真实 PI/UI 验收。 + +## 非目标 + +- 不启用 FAV degraded read。 +- 不拆 chatlog/contextasm/squadrun 包。 +- 不统一错误信封,不修改 PI Runner 协议。 +- 不清理历史 dev 数据。 + +## 步骤 + +1. [x] 从最新 `dev`(`a8f9451`)创建实现分支,登记 CURRENT 并确认 `docgov` mutex 已释放。 +2. [ ] 为当前跨表提交、刷新读取、ETag/idempotency 和权限负例补 characterization。 +3. [ ] 实现 U1–U2,验证任意属主写入/commit 故障整体回滚。 +4. [ ] 实现 U3,删除提交后 outbox 正确性回调。 +5. [ ] 实现 U4,保证 GET 零写入和 projection 缺失不再伪装成 404。 +6. [ ] 执行多实例并发、PG、真实 PI、真实浏览器和 restart/rebuild 验收。 +7. [ ] 写 test report,更新验收清单、FEATURE-MATRIX、CURRENT 和 gap-matrix。 + +## 验收标准 + +- [ ] 同一 ETag 并发编辑只有一个合法赢家,账本/head/outbox 无孤儿。 +- [ ] 任一事务故障点不会留下部分提交。 +- [ ] 项目 143 等价 hazard 和新项目刷新/重启目标 404/409/projection missing 为 0。 +- [ ] 匿名、非成员、跨用户、跨项目、错误 stage/duty 和授权依赖失败均 fail closed。 +- [ ] 1、2、4 个 app 实例的最终状态一致。 +- [ ] M1 test report 给出继续 M2 或停止扩面的明确结论。 + +## 相关链接 + +- Checklist: [../checklists/2026-09-14-module-decoupling-refactor-acceptance.md](../checklists/2026-09-14-module-decoupling-refactor-acceptance.md) +- Handoff: 实施时创建 +- Test report: 实施后创建 `.doc/test-reports/-document-reliability-pilot.md` +- Plan: [../../remediation/2026-09-14-module-decoupling-refactor-plan.md](../../remediation/2026-09-14-module-decoupling-refactor-plan.md) + +## 证据 / 备注 + +只有本任务真实验收通过后,才允许启动 durable event/FAV 与横向模块扩展。 diff --git a/.doc/ops/short-term/2026-09-14-durable-event-fav-integration.md b/.doc/ops/short-term/2026-09-14-durable-event-fav-integration.md new file mode 100644 index 00000000..bad952dd --- /dev/null +++ b/.doc/ops/short-term/2026-09-14-durable-event-fav-integration.md @@ -0,0 +1,60 @@ +--- +title: "可靠事件与 FAV 集成 M2–M3" +status: planned +created: 2026-09-14 +updated: 2026-09-14 +owner: TBD +branch: refactor/durable-event-fav-integration +worktree: "-" +mutex: event-delivery +--- + +# 可靠事件与 FAV 集成 M2–M3 + +## 目标 + +完成 U5–U9,使投影、通知、RunManifest 和最终成果在多 worker、崩溃、重放和 workspace 清理后仍可恢复且不越权。 + +## 范围 + +- opaque workspace capability、lease 和 fencing。 +- durable outbox、逐消费者 delivery/inbox、重试和 dead letter。 +- 审计并集成 `feature/final-artifact-vault` 的适用实现。 +- taskrun finalize、RunManifest 持久化、FAV coverage 和迁移 corpus。 + +## 非目标 + +- 不盲目整分支 cherry-pick FAV。 +- 不启用跨授权图的 vault 读取。 +- 不自动销毁 dev/main 数据。 +- 不执行 U10–U12。 + +## 步骤 + +1. [ ] 读取 M1 test report,确认继续 M2 的 go/no-go 已通过。 +2. [ ] 重新分配 Goose 序号并审计 U6/FAV schema 组合。 +3. [ ] 实现 U5–U6,多 app/worker 和 crash matrix 先通过。 +4. [ ] 逐文件审计 FAV 分支,按 U7 边界迁移合格实现。 +5. [ ] 实现 U8,收敛 taskrun terminal finalize 与 RunManifest。 +6. [ ] 实现 U9 的 checkpoint backfill、corpus、quarantine 和 cleanup gates。 +7. [ ] 完成真实 PI、浏览器、workspace 清理、scratch recovery 和 rebuild 演练。 + +## 验收标准 + +- [ ] 多订阅者有独立 delivery,失败者不阻塞其他订阅者。 +- [ ] worker crash/retry/replay 不造成重复可见副作用。 +- [ ] workspace 清空后,verified 最终文档/回复仍可定位并校验。 +- [ ] 授权图缺失、跨项目、tombstone、坏 digest 和 stale fencing 全部拒绝。 +- [ ] clean/oldest/previous/hazard/import/reload/rollback/artifact recovery 八轨有独立证据。 +- [ ] 任何数据销毁仍需完整备份、corpus、FAV coverage 和用户批准。 + +## 相关链接 + +- Checklist: [../checklists/2026-09-14-module-decoupling-refactor-acceptance.md](../checklists/2026-09-14-module-decoupling-refactor-acceptance.md) +- Handoff: 实施时创建 +- Test report: 实施后按 M2、M3 分开创建 +- Plan: [../../remediation/2026-09-14-module-decoupling-refactor-plan.md](../../remediation/2026-09-14-module-decoupling-refactor-plan.md) + +## 证据 / 备注 + +本任务依赖 M1,不得与 M1 同时修改 docgov/docrevision 的事务主链。 diff --git a/.doc/remediation/2026-09-14-module-decoupling-refactor-plan.md b/.doc/remediation/2026-09-14-module-decoupling-refactor-plan.md new file mode 100644 index 00000000..e9bfc070 --- /dev/null +++ b/.doc/remediation/2026-09-14-module-decoupling-refactor-plan.md @@ -0,0 +1,677 @@ +--- +title: "模块解耦与文档可靠性重构计划" +status: planned +created: 2026-09-14 +updated: 2026-09-14 +type: refactor +owner: TBD +deepened: 2026-09-14 +--- + +# 模块解耦与文档可靠性重构计划 + +## Goal Capsule + +- **Objective:** 项目文档、任务最终产物和最终智能体回复在并发写入、页面刷新、服务重启、工作区清理和受控恢复后仍能按权限定位到唯一、可校验的内容。 +- **Means:** 采用模块化单体、纵向绞杀、应用 UnitOfWork、数据库互斥、transactional outbox、opaque workspace capability 和 FAV。 +- **Authority:** 本计划受 [模块化单体解耦 ADR](../adr/2026-09-14-modular-monolith-decoupling.md)、[文档版本边界 ADR](../adr/2026-09-13-document-version-boundary.md) 和 [工作流运行时权威 ADR](../adr/2026-09-12-workflow-runtime-authority.md) 约束。 +- **Stop condition:** 任一里程碑出现数据不变量无法在单事务维持、授权只能通过放宽实现、旧数据无法安全迁移,或真实页面故障率没有下降时,停止扩面并回到设计评审。 +- **Execution profile:** 高风险、跨模块、迁移和授权敏感;characterization-first,逐纵向切片发布。 +- **Delivery:** 每个里程碑独立 short-term、独立分支和独立验收报告;本计划本身不授权合并、部署或删数据。 + +--- + +## Product Contract + +### Summary + +重构先解决文档事实源、事务与恢复链,再扩展其他模块。 +架构改造必须用用户可见的刷新、重启、并发和授权结果证明价值,不能只用包数量或代码行数证明完成。 + +### Problem Frame + +系统当前同时依赖 PostgreSQL 账本、`document_records` 指针、workspace `output.md` 和若干内存状态。 +这些状态在提交、刷新和重启时没有统一事务与恢复协议,导致历史上出现正文 404/409、projection missing、缓存竞争和任务完成后文档仍为空。 + +原解耦方案提出 contracts、单一写者和事件总线,但没有解释单一写者如何维持现有跨表原子性,也没有为多个 event subscriber、跨实例互斥和 FAV 恢复定义可执行协议。 + +### Requirements + +**事实源与持久成果** + +- R1. PostgreSQL immutable ledger 是文档版本与业务关系的权威,workspace 只作为可重建投影。 +- R2. FAV 只归档服务端确认的终态文档、终态助手回复、类型化 RunManifest 和必要附件,中间结果准入率为零。 +- R3. 数据库授权图完整但正文不可用时,系统可按 feature flag 从已验证 FAV 只读降级;授权图不完整时必须拒绝。 +- R4. GET、刷新和重载不得隐式修复数据库、覆盖 head 或创建归档。 + +**事务与并发** + +- R5. 一个业务命令只有一个事务 owner;业务状态、账本、当前指针及该里程碑已启用的 staging/outbox 必须原子提交。 +- R6. 单一写者只约束 SQL 和不变量所有权,不允许各属主各自提交导致跨表半完成。 +- R7. 多副本正确性必须依赖数据库行锁、事务级 advisory lock、唯一约束、expected version 和 fencing token;进程内 mutex 不能成为唯一正确性条件。 +- R8. 所有写命令必须具有 idempotency key、expected version 或两者;相同 key 不同 payload 必须返回冲突。 +- R9. 事务不得包含 LLM、PI Runner、对象存储、HTTP/SSE 或长文件 I/O。 + +**事件与恢复** + +- R10. 持久事件采用至少一次投递、逐消费者 delivery、幂等 inbox、租约、fencing token、重试和死信。 +- R11. 同一聚合事件按 aggregate version 有序,不同聚合允许并发;失败消费者不阻塞其他消费者。 +- R12. 旧 workspace、旧数据库和 FAV 回填必须可断点续跑、可重复、可审计,异常进入 quarantine。 +- R13. 恢复只进入 scratch identity,promotion 需要系统管理员和数据责任人确认,并通过属主端口提交。 + +**授权与隔离** + +- R14. HTTP、CLI、worker、event subscriber 和恢复工具必须使用服务端构造的 Principal,并按 action/resource/current state 逐动作授权。 +- R15. 客户端或事件 payload 中的 user、project、role、duty、backend 和路径不能作为授权事实。 +- R16. WorkspaceRef 只能暴露不可伪造 ID、scope 和 fencing token;物理路径不得跨模块传播。 +- R17. 路径解析必须拒绝绝对路径、`..`、跨项目 ID、symlink/hardlink 逃逸和 storage identity 不匹配。 +- R18. 事件、日志、metrics、manifest 和 result 文件不得包含 secret、认证材料、Prompt 或不必要正文。 + +**交付与验证** + +- R19. 首个独立交付边界是受控文档可靠性纵向试点,未通过停走门前不启动横向包搬移。 +- R20. 每个里程碑必须分别提供 unit、PostgreSQL、并发/故障注入、真实 PI、真实浏览器、restart/rebuild 和跨用户负例证据;不适用项必须说明原因。 +- R21. 迁移采用 expand/migrate/contract,允许新的 Goose migration,禁止修改已发布 migration。 +- R22. 数据清理必须同时满足完整备份恢复、migration corpus、FAV coverage、精确删除 manifest 和用户批准,不得因“已有最终成果”跳过其他门禁。 + +### Success Criteria + +- 项目 143 等价脱敏 hazard 和新建项目两条链路在刷新、重登、restart 与 rebuild 后正文成功率为 100%,目标 404/409/projection missing 为 0。 +- 同一文档并发写只有一个合法赢家;失败请求不产生孤儿 snapshot、错 head、重复消息、重复通知或重复 artifact。 +- 每个应发布终态都存在 publish request;已验证 artifact 可在 workspace 删除后按稳定逻辑 ID 定位并校验 SHA。 +- 匿名、非成员、跨用户、跨项目、错误 duty、错误 stage、篡改事件和授权依赖失败全部 fail closed。 +- 1、2、4 个应用实例和 dispatcher 下,状态与副作用结果一致;性能阈值由基线报告确认后固化,不能通过降低一致性达标。 +- 旧 schema corpus、legacy-v0 workspace 和 previous-release corpus 均可升级、重载和回滚到最近兼容点。 + +### Scope Boundaries + +**本计划包含** + +- 文档、taskrun、workspace、artifactvault、可靠事件和授权基础设施。 +- chatlog/contextasm、squadrun、app 组合根和 UserProjectContext 的后续选择性扩展。 +- 数据生命周期、迁移 corpus、回填、quarantine 和 scratch 恢复的集成门禁。 + +**Deferred to Follow-Up Work** + +- HTTP 错误信封统一。 +- PI Runner stdout/result.json 协议替换。 +- 全量 SSE transport 重写。 +- 微服务拆分和跨服务消息基础设施。 +- main 数据自动清理和未来 tenant 实体。 + +--- + +## Planning Contract + +### Key Technical Decisions + +- KTD1. **纵向绞杀优先。** 先在原包边界增加窄端口和适配器,迁完消费者并通过行为验收后再物理移动包。 +- KTD2. **事务 owner 与表属主分离。** 应用用例拥有事务,领域模块拥有 SQL;同一 `pgx.Tx` 通过不透明 TxScope 绑定 repository。 +- KTD3. **显式锁策略。** 普通状态转换使用 `READ COMMITTED`、行锁和条件更新;项目级不变量使用事务级 advisory lock;Serializable 只用于经过证明的复杂不变量。[PostgreSQL explicit locking](https://www.postgresql.org/docs/17/explicit-locking.html) +- KTD4. **固定锁顺序。** project/workflow instance → document root → version/snapshot/turn → task run → staging/outbox/delivery。 +- KTD5. **事务外副作用。** 网络、LLM、对象存储、通知和投影由提交后的持久事件驱动。 +- KTD6. **至少一次事件。** outbox 不承诺 exactly-once;event ID、consumer inbox 和业务唯一约束共同提供幂等结果。 +- KTD7. **opaque workspace capability。** 跨模块不传物理路径,所有读写使用 scope-bound ID、artifact key 和 fencing token。 +- KTD8. **三层权威。** PostgreSQL 管业务关系与授权,workspace 管临时现场,FAV 管已验证终态 payload/manifest/receipt/tombstone。 +- KTD9. **授权逐动作重算。** Principal 来自受信适配器;状态改变在取得资源锁后重读当前授权事实,subscriber 的 actor 快照只用于审计。[OWASP Authorization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html) +- KTD10. **依赖感知回滚。** 每个里程碑只保证回到最近兼容点;进入 contract 阶段后须按逆依赖顺序回滚。 +- KTD11. **按证据收紧门禁。** arch-check 先记录基线,每条规则在对应纵向切片清零后转 block,不按日历统一切换。 +- KTD12. **FAV 分支先审后用。** `feature/final-artifact-vault` 的现有实现作为候选,不直接视为 dev 已有能力;集成前重放迁移、测试、安全和并发审计。 + +### High-Level Technical Design + +#### Component topology + +```mermaid +flowchart TB + Transport[HTTP SSE CLI] --> UseCases[application use cases] + Jobs[workers and recovery] --> UseCases + UseCases --> Policy[authorization policy] + UseCases --> Unit[UnitOfWork] + Unit --> DG[docgov owner] + Unit --> DR[docrevision owner] + Unit --> TR[taskrun owner] + Unit --> OB[outbox and staging] + UseCases --> WSC[workspace capability] + OB --> Dispatch[delivery dispatcher] + Dispatch --> Projection[projection subscriber] + Dispatch --> AV[artifactvault subscriber] + Dispatch --> Notify[notification subscriber] +``` + +#### Write sequence + +```mermaid +sequenceDiagram + participant C as Caller + participant A as App use case + participant P as Policy + participant DB as PostgreSQL + participant O as Outbox worker + participant V as FAV + C->>A: command + idempotency + expected version + A->>P: authorize principal/action/resource + P-->>A: allow or deny + A->>DB: begin and acquire locks in canonical order + A->>DB: ledger + root + staging + outbox + DB-->>A: commit + A-->>C: committed result + O->>DB: claim delivery + O->>V: publish immutable artifact + V-->>O: verified receipt + O->>DB: idempotent ack +``` + +#### Event delivery lifecycle + +```mermaid +stateDiagram-v2 + [*] --> pending + pending --> leased + leased --> delivered + leased --> retryable_failed + leased --> dead_letter + retryable_failed --> leased + leased --> pending: lease expired + delivered --> [*] +``` + +#### Migration lifecycle + +```mermaid +flowchart TB + Expand[Expand schema and compatible readers] --> Shadow[Shadow validation] + Shadow --> Backfill[Checkpointed backfill] + Backfill --> Switch[Switch writer] + Switch --> Observe[Observe and restart test] + Observe --> Contract{Go or rollback?} + Contract -->|go| Remove[Remove old path] + Contract -->|rollback| Previous[Return to prior compatible point] +``` + +### Concurrency and Lock Matrix + +| Resource/operation | Correctness primitive | Conflict result | Retry policy | Forbidden while held | +|---|---|---|---|---| +| Project duty/heal/revoke | transaction advisory lock keyed by project | wait then typed timeout | whole idempotent tx for deadlock only | network, LLM, filesystem copy | +| Workflow stage advance | instance row lock + expected config revision | stale-state conflict | caller refresh; no blind retry | external execution | +| Controlled document edit | document row + open version row + ETag | version conflict with current head metadata | user/client rebases | projection and notification | +| Agent candidate commit | candidate/document/version locks + idempotency key | stale candidate or idempotency conflict | exact same request may replay | PI/LLM call | +| TaskRun claim/finalize | unique idempotency + conditional status/version update | existing run or state conflict | same digest returns prior result | message/SSE side effect | +| Workspace write | DB lease + fencing token + atomic file commit | stale lease/token | reacquire then re-resolve state | arbitrary path access | +| Outbox delivery | row lease + `SKIP LOCKED` + fencing token | another worker owns item | retry at next schedule | subscriber work inside claim tx | +| FAV publish | artifact idempotency unique key + no-replace object write | existing verified artifact or digest conflict | same digest resumes | reading mutable workspace | + +### Authorization Matrix + +| Surface | Principal source | Resource facts | Required checks | Failure behavior | +|---|---|---|---|---| +| HTTP/SSE | authenticated session/middleware | DB project/session/document/workflow | membership, owner, duty, stage, action | 401/403/typed conflict; no data | +| CLI ops | service identity + operator session | explicit environment/dataset/resource manifest | capability, exact scope, dry-run where destructive | fail closed and audit | +| Event subscriber | registered service identity; event actor for audit only | DB current aggregate + event scope/digest | producer allowlist, project/resource match, current policy | dead letter/quarantine | +| FAV degraded read | current authenticated Principal | DB relation + vault manifest/tombstone | normal read permission first, then integrity | no fallback if auth graph incomplete | +| Recovery promotion | recovery credential + admin + data steward approval | scratch identity + signed plan + conflict map | two-party approval, no unresolved conflicts | refuse promotion | +| Agent backend | app-resolved BackendRef | project policy + InputBundle sensitivity | backend allowlist, least data, secret injection boundary | reject before dispatch | + +### Delivery dependency graph + +```mermaid +flowchart TB + M0[M0 baseline and contract correction] --> M1[M1 document reliability pilot] + M1 --> M2[M2 durable events and FAV integration] + M2 --> M3[M3 taskrun and workspace finalization] + M3 --> M4[M4 selective domain decoupling] + M4 --> M5[M5 app composition and context ownership] + M5 --> M6[M6 transport boundary cleanup] + M2 --> D[Data corpus and lifecycle rollout] + D --> M3 +``` + +### Assumptions + +- 当前 dev 基线为 schema 33;任何实现分支必须在创建 migration 时重新查询最新序号。 +- `feature/final-artifact-vault` 尚未合 dev,现有代码与测试只能作为候选实现和证据来源。 +- 当前无独立 tenant 实体,权限范围沿用 environment + project membership/admin;未来 tenant 模型另立 ADR。 +- 性能容量目标需要在 M1 压测后固化;本计划先固定正确性和观测项,不伪造吞吐数字。 + +--- + +## Phased Delivery + +| 里程碑 | 交付边界 | 预计范围 | 进入条件 | 退出/停走门 | +|---|---|---|---|---| +| M0 | 基线与契约纠偏 | 文档/门禁/characterization | 在途 mutex 不冲突 | 事务/锁/授权/事件/FAV 契约评审通过 | +| M1 | 文档可靠性纵向试点 | docflow/docgov/docrevision/docquery | M0 完成,独占 docgov | 刷新、重启、并发、权限和故障注入全 PASS | +| M2 | 持久事件与 FAV | outbox/delivery/artifactvault | M1 数据边界稳定 | 多 worker、重放、归档、降级读取和 tombstone PASS | +| M3 | taskrun/workspace 终态 | taskrun/squadrun/workspace | M2 verified receipt 可用 | 清 workspace 后最终产物仍可定位,重复 finalize 无副作用 | +| M4 | 选择性领域端口化 | chatlog/contextasm/squadrun | M1–M3 复盘证明收益 | 每个子切片独立回归;无纯搬包 | +| M5 | 组合根与上下文收权 | app/chatflow/stageflow/UserProjectContext | M4 稳定 | 每个 use case 单 owner;闭包按事件逐个替换 | +| M6 | 传输收口 | server/SSE | M5 完成 | server 无 SQL/编排/路径;R1–R7 对应规则全 block | + +M0–M1 是首个批准范围。 +M2–M6 需要在前一里程碑验收报告后重新 go/no-go,不构成一次性整体授权。 + +--- + +## Implementation Units + +### Unit Index + +| ID | 标题 | 主要文件 | 依赖 | +|---|---|---|---| +| U1 | 固化架构基线和门禁 | `scripts/arch-check.sh`, `.doc/system-design/module-boundaries.yaml` | 无 | +| U2 | 建立应用 UnitOfWork | `internal/app/uow`, owner repositories | U1 | +| U3 | 文档命令原子化 | docgov/docrevision/docflow | U2 | +| U4 | 文档授权读模型 | docquery/server/authz | U1, U3 | +| U5 | Workspace capability | workspace/squadrun | U1 | +| U6 | Durable event delivery | outbox/dispatcher/inbox | U2 | +| U7 | FAV 集成与降级读取 | artifactvault/docflow/docquery | U3, U4, U6 | +| U8 | TaskRun/RunManifest 终态 | taskrun/squadrun/workspace | U5, U6, U7 | +| U9 | 数据回填与迁移 corpus | dataset/FAV tooling | U6–U8 | +| U10 | chatlog/contextasm 端口化 | ctxmgr/chat/contextasm | M1–M3 go/no-go | +| U11 | 组合根和 use case 收权 | app/server/bizdev/coordinator | U10 | +| U12 | 传输边界与最终门禁 | server/stream/CI | U11 | + +### U1. 固化架构基线和可执行门禁 + +- **Goal:** 建立不会把现有违规合法化、也不会误报 migration/fixture 的边界基线。 +- **Requirements:** R19–R21;KTD1、KTD11。 +- **Dependencies:** 无;必须避开 `intent`、`chat` 等在途 mutex,当前只占 `arch`。 +- **Files:** `scripts/arch-check.sh`、`.doc/system-design/module-boundaries.yaml`、`internal/archtest/arch_test.go`、`internal/archtest/arch_test_test.go`、`.doc/audit/-arch-check-baseline.md`。 +- **Approach:** + 1. 分离 production import、production SQL write、path construction、event producer/consumer 四类规则。 + 2. 表名扫描只识别 production Go 中的写语句;Goose、测试、fixture 和受控运维脚本进入显式类别,不以全仓零出现为目标。 + 3. 为每项既有违规记录 owner、原因、截止里程碑和到期日;新违规立即 block。 + 4. 只为已完成纵向切片的规则切换 block,不能以“两周后统一 block”代替清零。 +- **Execution note:** 先对当前依赖和 SQL 写者生成可重复 characterization 报告,再改门禁。 +- **Patterns to follow:** `go list` 依赖图、现有 AGENTS mutex、Goose 不可改写规则。 +- **Test scenarios:** + - 合法的 Goose/fixture 表名不触发生产写者违规。 + - 非属主 production 包新增 `UPDATE document_records` 时测试失败并定位文件。 + - 路径通过字符串拼接、`filepath.Join` 和间接常量构造时均被检测。 + - 无过期日例外、过期例外、新增越权 event producer 均阻断。 +- **Verification:** 基线报告可重放;新增违规阻断;现有违规数只能下降。 + +### U2. 建立应用 UnitOfWork 和事务能力 + +- **Goal:** 在单一写者边界内维持跨表业务不变量。 +- **Requirements:** R5–R9;KTD2–KTD5。 +- **Dependencies:** U1。 +- **Files:** `internal/app/uow/uow.go`、`internal/app/uow/pg_uow.go`、`internal/app/uow/pg_uow_test.go`、`internal/docgov/repository.go`、`internal/docrevision/repository.go`、`internal/taskrun/repository.go`、`internal/freedoc/repository.go`。 +- **Approach:** + 1. UnitOfWork 开启、提交和回滚唯一 `pgx.Tx`,向每个属主提供绑定该 transaction 的窄 repository capability。 + 2. TxScope 不暴露通用 `Exec` 给应用层;只有属主 repository 能执行自己的 SQL。 + 3. 禁止 repository 在已有 TxScope 内嵌套 transaction 或提前 commit。 + 4. 实现统一的完整事务重试 wrapper,只重试明确的 serialization/deadlock 错误,并保持 idempotency context。 + 5. 为锁超时、事务时长、重试和 rollback reason 加结构化指标,不记录业务正文。 +- **Test scenarios:** + - 第二个属主写入失败时第一个属主写入回滚。 + - outbox/staging 写入失败时业务状态和账本均回滚。 + - commit 失败时不会返回成功或执行提交后副作用。 + - `40001`/`40P01` 重跑完整事务并最终只产生一个业务结果。 + - version conflict、permission denied、payload digest conflict 不进入自动重试。 + - 尝试跨属主执行任意 SQL 无法通过类型边界或架构测试。 +- **Verification:** fault injection 覆盖 begin、每个 owner write、outbox、commit;无部分提交。 + +### U3. 文档提交纵向事务化 + +- **Goal:** 将人工编辑、agent commit、批准/驳回和根指针投影收敛到文档用例事务。 +- **Requirements:** R1、R5–R9、R19;KTD2–KTD5。 +- **Dependencies:** U2;实施时独占 `docgov` mutex。 +- **Files:** `internal/app/docflow/commands.go`、`internal/app/docflow/commands_pg_test.go`、`internal/docgov/engine.go`、`internal/docgov/lease.go`、`internal/docrevision/pg_mutate.go`、`internal/docrevision/service.go`、`internal/docrevision/document_commit_concurrency_pg_test.go`、`internal/server/document_revision_handlers.go`。 +- **Approach:** + 1. 为每个文档命令明确 aggregate、expected head、idempotency key 和授权 action。 + 2. 版本账本与 `document_records` 指针先在同一 UoW 写入;M1 保留现有通知兼容适配,但任何核心正确性不得依赖提交后的 `OnOutbox` 回调。 + 3. 按 KTD4 锁定 document root 与 open version;状态转换采用条件更新。 + 4. 提交返回 ledger/root 已持久化结果;workspace projection 和通知由提交后事件处理。 + 5. 保留现有 ETag 语义,统一 agent、人工与批准路径的冲突分类。 +- **Execution note:** 先补当前行为的 PG characterization,再移动写入所有权;不得先删除旧事务路径。 +- **Patterns to follow:** `internal/docrevision/pg_mutate.go` 当前同事务投影、`internal/docgov/lease.go` expected SHA、现有 document revision idempotency tests。 +- **Test scenarios:** + - 同一 ETag 两个用户并发编辑,只有一个成功,另一个得到当前 head。 + - 人工编辑与 agent commit 并发,账本 event_no 连续且 head 指向赢家。 + - approve 与 reject 并发,只允许一个合法终态。 + - candidate commit 在 root/version/outbox 任一故障点整体回滚。 + - 相同 idempotency key 和相同 digest 返回原结果;不同 digest 返回冲突。 + - 锁超时返回 typed retryable busy,不泄露 SQL 或路径。 + - 两个 app 实例执行同一请求,结果与单实例一致。 +- **Verification:** 文档四类命令的跨表不变量由 PostgreSQL 测试证明;`OnOutbox` 不再承担正确性。 + +### U4. 建立授权后的文档统一读模型 + +- **Goal:** 正文读取不再依赖 handler 拼路径或把一致性错误伪装成 404。 +- **Requirements:** R1、R3、R4、R14、R15、R19;KTD1、KTD8、KTD9。 +- **Dependencies:** U1、U3;实施时按 CURRENT 排队使用 `docgov` 和 `authz` mutex。 +- **Files:** `internal/app/docflow/query.go`、`internal/app/docflow/query_test.go`、`internal/docquery/service.go`、`internal/docquery/service_pg_test.go`、`internal/server/project_doc_handlers.go`、`internal/server/document_revision_authz_test.go`、`web/src/api/projectDocuments.ts`、`web/src/lib/controlledDocHydrate.ts`、`web/e2e/controlled-document-reload.spec.ts`。 +- **Approach:** + 1. Principal 由认证适配器构造,DocRef 不携带可覆盖 Principal 的 user/role/duty。 + 2. 先解析数据库 resource→project、workflow、stage 和 duty,再读取 ledger/current pointer。 + 3. workspace projection 只作为校验缓存;缺失时不阻止读取数据库 immutable content。 + 4. ledger/root、projection 和 FAV 的差异映射为 typed consistency state,不再统一变成 404。 + 5. FAV 降级在 U7 接入前保持关闭;接口先保留明确 extension point。 + 6. 列表和版本查询强制分页,BatchGet 首期上限 100;批量授权按 Principal/project 分组并使用有界并发。 +- **Test scenarios:** + - 正常 projection、projection 缺失、projection hash 漂移、ledger 缺失分别返回正确结果或 typed error。 + - GET、刷新、重登不产生数据库写入、workspace 文件或归档事件。 + - 匿名、非成员、跨用户会话、跨项目、错误 stage/duty 均拒绝且不泄露文档存在性。 + - 授权依赖超时/错误时 fail closed。 + - BatchGet 的 100/101 边界、重复引用、跨项目混合引用和限流拒绝不泄露资源存在性。 + - 项目 143 等价 hazard 和新项目在页面刷新、restart、rebuild 后正文一致。 +- **Verification:** 控制台和服务器目标 404/409/projection missing 为零;所有来源分支有服务端与浏览器证据。 + +### U5. Workspace capability、lease 与文件提交 + +- **Goal:** 从类型和运行时同时禁止跨模块裸路径与跨租户工作区访问。 +- **Requirements:** R1、R7、R9、R16–R18;KTD7。 +- **Dependencies:** U1。 +- **Files:** `internal/workspace/types.go`、`internal/workspace/resolver.go`、`internal/workspace/capability.go`、`internal/workspace/capability_test.go`、`internal/workspace/lease_pg_test.go`、`internal/squad/workspace_adapter.go`、`internal/bizdev/service.go`、`internal/server/project_doc_handlers.go`。 +- **Approach:** + 1. WorkspaceRef 删除 Root/OutputDir/ManifestPath,只保留 scope-bound ID、schema version 和 fencing token。 + 2. workspace service 提供限定 artifact key 的 read、atomic writer、commit projection 和 inspect 能力。 + 3. ID 与 lease 绑定 environment、project、run 和 storage identity;所有写入校验 fencing token。 + 4. 使用 realpath/descriptor-based 检查,拒绝 `..`、绝对路径、symlink/hardlink 和根外 rename。 + 5. legacy-v0 workspace 只读;首次受控 Resume 校验后原子升级 v1,并保留兼容备份一个发布周期。 +- **Test scenarios:** + - `..`、绝对路径、编码后的分隔符、空 key、跨 project Workspace ID 全部拒绝。 + - symlink 和 hardlink 在校验后替换的 TOCTOU 场景仍不能逃逸。 + - 旧 lease writer 在新 lease 签发后因 fencing token 过期而失败。 + - 两个 app 实例并发提交同一 projection,reader 只看到完整 marker。 + - v0 读取、升级失败回滚、未知 major 版本拒绝。 +- **Verification:** production 包中 workspace 物理路径只存在于 workspace/squadrun adapter;多实例测试不依赖 `sync.Mutex`。 + +### U6. Durable outbox、逐消费者 delivery 与 replay + +- **Goal:** 让后台投影、通知和 FAV 发布在崩溃与水平扩容下可恢复。 +- **Requirements:** R5、R9–R11、R14、R18、R21;KTD5、KTD6。 +- **Dependencies:** U2;需要新的 additive Goose migration。 +- **Files:** `deploy/postgres/goose/000xx_*durable_event_delivery.sql`、`internal/events/envelope.go`、`internal/events/outbox.go`、`internal/events/dispatcher.go`、`internal/events/inbox.go`、`internal/events/dispatcher_pg_test.go`、`internal/events/replay.go`、`cmd/events/main.go`。 +- **Approach:** + 1. 保留业务 outbox record,新增 event envelope、每消费者 delivery/inbox、attempt、lease、next_attempt_at 和 dead-letter 数据。 + 2. `FOR UPDATE SKIP LOCKED` 只用于小批次 claim;worker 在 claim transaction 外执行 subscriber。 + 3. ack/fail 使用 delivery version/fencing token,过期 worker 不能覆盖新 lease owner。 + 4. 同 aggregate 依据 aggregate version 串行,不同 aggregate 并行;缺号进入等待或 quarantine。 + 5. retry policy 区分 transient、permanent、authorization/integrity failures;后两者默认 dead letter/quarantine。 + 6. replay CLI 先 dry-run,必须限定 consumer/event/project/time/id 范围并记录 operator、reason 和计划 hash。 + 7. 逐个迁移并删除 `OnOutbox` 与闭包桥;切换前 shadow 对账,切换后旧回调不再注册。 +- **Test scenarios:** + - 两个和四个 worker 并发 claim,没有同一 lease 的双 owner。 + - worker 在副作用前、后、ack 前崩溃,重启后最终只有一个可见业务结果。 + - 一个 subscriber 永久失败不阻塞其他 subscriber。 + - lease 过期后旧 worker ack 被 fencing token 拒绝。 + - 相同 event 重放被 inbox 去重;相同 aggregate 乱序事件不越过缺失版本。 + - 伪造 producer、跨项目 scope、payload digest 不符进入 quarantine。 + - dead-letter replay 未授权、无 dry-run 或范围过宽时拒绝。 +- **Verification:** 删除旧单一 `published_at` 语义前完成双读对账;pending age、attempt、lease steal、dead letter 可观测。 + +### U7. FAV 分支审计、终态发布与授权降级读取 + +- **Goal:** 将最终成果与 workspace 清理解耦,同时不形成第三套业务真相源。 +- **Requirements:** R1–R4、R10–R15、R18、R21、R22;KTD8、KTD9、KTD12。 +- **Dependencies:** U3、U4、U6;先审计 `feature/final-artifact-vault` 相对当前 dev 的完整差异。 +- **Files:** `internal/artifactvault/`、`deploy/postgres/goose/000xx_*final_artifact*.sql`、`internal/app/docflow/artifact.go`、`internal/docquery/service.go`、`internal/server/vault.go`、`cmd/artifact-vault/`、`internal/artifactvault/*_test.go`。 +- **Approach:** + 1. 重用通过审计的 FAV eligibility、staging、publisher、manifest、tombstone 和 scratch recovery;不直接 cherry-pick 未复核的组合根/handler 改动。 + 2. 重新分配最新 Goose 序号,并验证 migration 与 U6 delivery schema 的组合和 Down 边界。 + 3. 文档终态 transaction 冻结精确 payload/SHA 或 immutable blob ref 并写 publish request;publisher 不重读 workspace 或可变消息行。 + 4. docquery 只在正常数据库授权成功后使用 FAV;manifest 只做 scope/integrity 交叉校验,不能授予权限。 + 5. union-deny:运行删除账本或 vault tombstone 任一侧声明删除即拒绝读取/恢复。 +- **Execution note:** 现有 FAV 分支包含业务代码和 migration,必须先 rebase/merge audit,再按本计划拆分原子提交。 +- **Test scenarios:** + - committed/sealed/approved 文档按准入表归档;ready/rejected/stale/quality_failed 和 workspace 草稿为零。 + - publish 重复、对象已存在、每个 fsync/rename/marker 崩溃点都可恢复。 + - workspace 删除后,授权用户可读 verified payload;未授权用户看不到 artifact 存在性。 + - DB 授权关系缺失、manifest scope 不符、坏 SHA、tombstone、vault 不可用均 fail closed 或返回明确 degraded error。 + - GET 零数据库写入、零 workspace 写入、零隐式恢复。 + - 跨项目相同 SHA 不共享删除或密钥边界。 +- **Verification:** FAV test report 与当前 dev 重放通过;`FAV_DEGRADED_READ` 默认关闭,直到 M2 全部负例通过。 + +### U8. TaskRun、RunManifest 与 workspace 清理门禁 + +- **Goal:** 任务终态和最终产物在重复 finalize、SSE 断线和 workspace 清理后保持一致。 +- **Requirements:** R1、R2、R5–R12、R16–R18、R22;KTD5–KTD8。 +- **Dependencies:** U5、U6、U7。 +- **Files:** `internal/taskrun/manager.go`、`internal/taskrun/store.go`、`internal/taskrun/finalize.go`、`internal/taskrun/finalize_pg_test.go`、`internal/squad/manifest.go`、`internal/app/stageflow/execute.go`、`internal/server/task_finalize.go`、`internal/server/task_run_idempotent_sse_test.go`。 +- **Approach:** + 1. RunManifest 核心字段类型化并持久化规范字节、SHA、workspace/storage identity 和成员列表。 + 2. taskrun terminal finalize transaction 写状态、assistant terminal turn、manifest、artifact staging 和 outbox。 + 3. SSE done 只投影已提交状态;客户端断线与重试复用 task run idempotency key。 + 4. workspace 清理只依赖已冻结 staging 和 policy;需长期保留的最终成果还必须取得 verified receipt。 + 5. backend 由服务端 BackendRef policy 解析,InputBundle 携带数据分类和最小权限范围。 +- **Test scenarios:** + - 同一 finalize 重复调用返回同结果,不重复消息、事件、manifest 或 artifact。 + - finalize 与 cancel 并发只有一个合法终态。 + - SSE 在 commit 前后断线并重试,历史中只有一个 assistant turn。 + - workspace 在 finalize 后立即清理,publisher 仍从 staging 归档原 SHA。 + - manifest digest、storage identity、artifact member 不一致阻止 verified/cleanup。 + - 未授权 backend、敏感 InputBundle、凭证回显和 result 残留全部拒绝或清理。 +- **Verification:** “完成但没有 durable manifest/publish request”和“已显示完成但正文仍模板”两类状态不可产生。 + +### U9. 存量回填、migration corpus 与数据生命周期 + +- **Goal:** 只保留可解释的演示/回归数据,并持续验证旧数据迁移、重载和受控恢复。 +- **Requirements:** R2、R12、R13、R21、R22;KTD8、KTD10。 +- **Dependencies:** U6–U8。 +- **Files:** `cmd/dataset/`、`internal/dataset/`、`internal/artifactvault/backfill.go`、`scripts/backup-system-db.sh`、`scripts/restore-system-db-drill.sh`、`.doc/test-fixtures/`、`.doc/test-reports/`。 +- **Approach:** + 1. 建立 dataset registry,分类 pinned、demo、regression、migration baseline、ephemeral 和 quarantine。 + 2. backfill 只枚举 immutable document snapshot、approved/commit event 和 completed chat turn,禁止扫描 workspace。 + 3. 大表按稳定主键 checkpoint 分批处理;多个 worker 使用 `SKIP LOCKED` 或互斥 shard,限速避免挤占在线连接池。 + 4. 每个 batch 记录 source schema/commit/storage identity、范围、成功、quarantine 和重试水位。 + 5. dev 先蓝绿建立新数据根,导入 6–10 个批准 dataset;旧库/状态根只读 quarantine 7–14 天后,需用户再次批准才销毁。 + 6. 每个 release 验证 clean、oldest-supported、previous、hazard、import、reload、rollback 和 artifact recovery 八条轨道。 +- **Test scenarios:** + - backfill 中断、重复和并发新终态时分母/分子可复算且不重复 artifact。 + - legacy-v0 workspace、旧 schema、高 ID、projection missing/fork、缺 snapshot 分别升级或 quarantine。 + - restore 强制 scratch,拒绝 dev/test/main DSN、错误 storage identity、篡改 manifest 和缺权限凭证。 + - FAV coverage、完整备份 restore 或 migration corpus 任一失败都阻止 destroy。 + - dev 选择性导入后项目关系、文件 SHA、sequence、授权和 reload 一致。 +- **Verification:** 数据清理有精确 manifest、可恢复位置、观察窗口和批准记录;不修改历史 Goose。 + +### U10. chatlog/contextasm 原地端口化 + +- **Goal:** 在不重复付费搬包的前提下分离对话记录和 prompt 上下文装配。 +- **Requirements:** R7、R14、R15、R19、R20;KTD1、KTD9。 +- **Dependencies:** M1–M3 go/no-go;必须等待当前 `chat` mutex 释放。 +- **Files:** `internal/ctxmgr/`、`internal/chat/`、`internal/app/chatflow/`、`internal/ctxmgr/chat_auth_pg_test.go`、`internal/server/chat_history_auth_test.go`。 +- **Approach:** + 1. 先定义 chatflow 所需的消费者侧 ChatLog 和 ContextAssembler ports。 + 2. 在现有 ctxmgr 内提供 adapters,迁移调用者并保持表所有权不变。 + 3. 统计接口 fan-in、DTO 转换和同类改动触达文件数;收益成立后再物理拆为 chatlog/contextasm。 + 4. 所有读取携带 Principal/action,不接受请求体 user/project 作为权限事实。 +- **Test scenarios:** + - 刷新前后同一授权消息集合的正文、分类和计数一致。 + - 跨用户、跨项目、已退出成员和服务端后台导出负例。 + - context assembly 只读取允许集合,摘要/缓存不放大不可见消息。 + - 原地 adapter 与最终拆包实现的 contract tests 等价。 +- **Verification:** 同类对话变更触达面相对基线下降;拆包前已获得独立行为收益。 + +### U11. 组合根迁移、事件替换和共享上下文收权 + +- **Goal:** 让每个跨模块业务流程具有唯一应用用例 owner,而不是集中在 server/bizdev 闭包和共享指针。 +- **Requirements:** R5、R7、R9–R11、R14、R15、R19、R20;KTD1、KTD5、KTD6、KTD9。 +- **Dependencies:** U10;按四个独立子切片执行,不建立一个巨型 Wave。 +- **Files:** `internal/app/app.go`、`internal/app/chatflow/`、`internal/app/stageflow/`、`internal/app/docflow/`、`internal/server/server.go`、`internal/bizdev/service.go`、`internal/coordinator/`、对应 contract/integration tests。 +- **Approach:** + 1. U11a 只把构造和生命周期迁到唯一 app composition root。 + 2. U11b 一次只替换一种闭包事件;每种事件单独迁移、对账和回滚。 + 3. U11c 逐个迁移 ExecuteTask/executeStageInternal 用例,不同时修改无关领域。 + 4. U11d 将 UserProjectContext 改为只读 snapshot + 类型化 ContextUpdate,单一 use case 应用增量。 +- **Test scenarios:** + - app start/stop/restart 不丢 worker,重复启动不创建重复 subscriber。 + - 每个闭包新旧路径 shadow 结果一致,切换后旧路径无调用。 + - ContextUpdate 并发应用使用版本检查,不发生字段级 lost update。 + - server、CLI 和 worker 调用同一 use case 得到一致授权和事务结果。 +- **Verification:** 每个子切片独立发布、独立回滚;server 不再保存业务闭包,UserProjectContext 不被多方直接修改。 + +### U12. 传输边界收口和最终架构门禁 + +- **Goal:** 在领域行为稳定后缩减 server/stream 耦合并全面启用已清零规则。 +- **Requirements:** R14、R18–R21;KTD1、KTD11。 +- **Dependencies:** U11。 +- **Files:** `internal/server/`、`internal/stream/transport/`、`scripts/arch-check.sh`、`.doc/system-design/module-boundaries.yaml`、CI 配置、`web/e2e/`。 +- **Approach:** + 1. server handler 只保留参数、Principal、use case 调用和 wire serialization。 + 2. SSE transport 只编码中性 progress/event frame,不拥有业务状态或重试决策。 + 3. 对已清零的 import、SQL writer、path、producer/consumer 规则逐项转 block。 + 4. 错误信封和 PI Runner 协议保持独立 follow-up,不进入本单元。 +- **Test scenarios:** + - HTTP、SSE 断线重连和 CLI 对同一业务状态观察一致。 + - server 新增领域实现 import、SQL 或路径拼接时 CI 失败。 + - SSE 慢客户端、断开和重复连接不阻塞业务 transaction 或 dispatcher。 + - 全套授权、重启和多实例回归保持通过。 +- **Verification:** R1–R7 的修订版规则全 block;server 行数只作观察指标,不作替代行为验收。 + +--- + +## Verification Contract + +### Evidence layers + +| 层级 | 必须证明 | 不得替代 | +|---|---|---| +| Unit | 状态机、错误分类、schema、路径和 policy | PostgreSQL 原子性 | +| PostgreSQL integration | 事务回滚、锁、幂等、migration、并发 | 浏览器用户体验 | +| Fault injection | 每个 commit/delivery/publish 崩溃窗口 | 真实进程 restart | +| Concurrency/load | 多 app/worker、热点与非热点资源、积压和公平性 | 正确性断言 | +| Real PI | 真实 agent 生成、manifest、finalize、归档 | stub 或固定假输出 | +| Browser | 页面创建/打开/编辑/刷新/重登、错误与 degraded 状态 | API 直接调用 | +| Restart/rebuild | app、worker、workspace root、存储 identity 重载 | health 单点检查 | +| Authorization negative | 匿名/跨用户/跨项目/阶段/duty/后台/recovery | 前端隐藏按钮 | +| Recovery | 完整 DB restore、migration corpus、FAV scratch 恢复 | 仅列出 dump 内容 | + +### Repository gates + +- `go vet ./...` +- `go test -short ./...` +- 目标模块 PostgreSQL 集成和 race 测试;若全包 race 存在已知 TempDir flake,报告必须分离目标模块结果和基建债。 +- `scripts/arch-check.sh` 对当前里程碑已收紧规则为 block,对未迁移规则输出基线差异。 +- `web` 单元测试与 production build。 +- dev 容器必须 rebuild 后检查 `/health` 与 `/ready`,不能用 restart 证明新代码已部署。 +- 真实 PI、真实浏览器、post-restart 和多用户负例按独立证据分栏。 + +### Concurrency test matrix + +| 维度 | 场景 | 必须断言 | +|---|---|---| +| App replicas | 1/2/4 | 结果和不变量一致,进程内 mutex 关闭仍正确 | +| Dispatcher replicas | 1/2/4 | 无双 owner ack,无重复可见副作用,无永久饥饿 | +| Resource shape | 同一文档热点/不同文档并行 | 热点正确冲突,非热点不被全局锁串行化 | +| Failure point | before commit/after commit/before ack/after side effect | 无丢事件,重复交付幂等 | +| Lock behavior | timeout/deadlock/serialization | 分类和完整事务重试正确,无无限等待 | +| Lease behavior | expiry/renew/steal/stale fencing | 旧 owner 无法覆盖新 owner | +| Load | steady/burst/backlog recovery | 记录 p50/p95/p99、锁等待、连接池、oldest event age | + +### Authorization test matrix + +每个状态改变和读取接口至少覆盖:匿名、已登录非成员、错误项目成员、会话非 owner、错误 duty、错误 stage、过期 lease、跨环境 ID、服务身份越权、授权依赖失败和系统管理员合法路径。 +FAV 额外覆盖 manifest project 不符、tombstone、删除账本不一致和授权图缺失。 +状态改变必须覆盖授权检查后的成员撤销、duty 变化、stage 推进和 lease 失效,证明用例在取得资源锁后重新验证当前事实。 + +### Migration and rollback matrix + +| 阶段 | Forward proof | Rollback proof | +|---|---|---| +| Expand | 新 schema 与旧代码兼容 | migration Down 或旧代码继续运行 | +| Shadow | 新旧读取差异为零或有解释的 quarantine | 关闭 flag 回旧路径 | +| Backfill | checkpoint、幂等、并发新写一致 | 停止 worker,不撤销已安全扩展列 | +| Switch | 新 writer 唯一、旧 writer 只读 | flag 回最近兼容 writer | +| Observe | restart/rebuild/浏览器/PI/指标稳定 | 切回旧 reader/writer 与只读 quarantine 数据 | +| Contract | 引用扫描和用户批准 | contract 后只能逆序部署兼容版本,不能单 commit revert | + +--- + +## Risk Analysis and Mitigation + +| 风险 | 触发信号 | 缓解和停止条件 | +|---|---|---| +| contracts 成为新万能包 | fan-in、共享 DTO、ADR 数持续上升 | 消费者侧端口;按域拆公共契约;超过阈值回评边界 | +| 跨属主事务过大 | 锁等待、事务 p99、deadlock 上升 | 缩小原子集合;事务外副作用;固定锁序;停止扩面 | +| advisory lock 形成全局瓶颈 | 非热点资源也排队 | 只对项目级不变量使用;key 分区;检查 `pg_locks` | +| outbox backlog 放大 | oldest age/attempt/dead letter 超阈值 | 独立 worker 连接预算、批次和退避;按 aggregate 并行 | +| poison event 无限重试 | 同 event 高频失败 | typed permanent failure、dead letter、受控 replay | +| FAV 成为授权旁路 | DB 关系缺失仍返回内容 | 正常授权先行、union-deny、默认关闭 degraded read | +| 工作区链接逃逸 | realpath/scope mismatch | opaque ID、descriptor 检查、fencing、负例测试 | +| 回填压垮在线库 | lock wait/replication lag/连接池饱和 | checkpoint、小批次、限速、独立连接预算、可暂停 | +| 双读长期不收口 | exception 数和差异长期不降 | 明确 owner、到期日和 go/no-go;禁止无限兼容 | +| 物理搬包造成大冲突 | import churn 大于行为收益 | 原地端口化;行为稳定后再移动;按 mutex 排队 | +| FAV 分支漂移 | migration 冲突、dev API 已变化 | rebase audit,不整分支盲合;按单元摘取并重测 | +| 数据清理误删 | coverage/backup/corpus 任一未知 | 默认不删;蓝绿迁移;quarantine 观察;用户二次批准 | + +--- + +## Operational and Rollout Notes + +### Feature flags + +- 新 docquery reader、单写路径、dispatcher consumer、FAV degraded read 和 legacy workspace reader 分别使用独立 flag。 +- flag 只选择已兼容路径,不能跳过授权、完整性或终态准入。 +- 每个 flag 有 owner、默认值、环境范围、启用前置和删除里程碑。 + +### Resource budgets + +- app transaction、dispatcher、backfill 和 FAV publisher 使用分离的连接池或明确配额,避免后台积压耗尽在线请求连接。 +- worker batch、lease、heartbeat、retry 和 concurrency 由配置管理;M1/M2 基线报告确定初值和告警阈值。 +- 对同 aggregate 设置最大在途数 1;跨 aggregate 并行度按数据库与 vault 指标调节。 + +以下是首轮试点的安全起始值,不是产品 SLO;压测报告必须记录调整依据: + +| 参数 | 起始值 | 调整约束 | +|---|---|---| +| 写事务 lock wait | 最多 2 秒且不超过请求剩余 deadline | 不允许无限等待;热点冲突优先返回 typed busy | +| serialization/deadlock retry | 最多 3 次,指数退避并带 jitter | 只重跑完整幂等事务 | +| dispatcher claim batch | 50 | claim transaction 只认领,不执行 subscriber | +| delivery lease/heartbeat | 30 秒 / 10 秒 | heartbeat 失败后旧 worker 不得 ack | +| delivery attempts | 12 次后 dead letter | permanent/auth/integrity failure 不做 12 次盲重试 | +| retry backoff ceiling | 15 分钟 | oldest age 仍受告警和人工处置约束 | +| backfill batch | 每批 200 个 aggregate | 按 lock wait、replication lag 和在线池占用自动降速 | +| backfill workers | dev 初始 1,验证后最多 4 | 每个 shard/checkpoint 不重叠 | + +### Observability + +- Dashboard:transaction/lock、document consistency、outbox delivery、workspace lease、FAV publication、authorization deny、backfill/quarantine。 +- Alert:oldest event age、dead letter、storage identity mismatch、projection mismatch、FAV coverage gap、恢复/删除拒绝。 +- Trace 使用 correlation/causation ID 串联 HTTP/use case/transaction/event/subscriber,但不得携带正文或 secret。 + +### Documentation lifecycle + +- 每个里程碑开始前新建 short-term 和 CURRENT claim。 +- 每个里程碑结束生成 test report,回链本计划 U-ID 和验收清单。 +- 完成项移入 archive;未解决问题进入 audit/remediation,不通过口头说明关闭。 + +--- + +## Sources and Research + +- [模块解耦方案复核](../audit/2026-09-14-module-decoupling-plan-review.md)记录当前源码中的事务、outbox、workspace 和授权缺口。 +- `internal/docrevision/pg_mutate.go` 是现有账本与根指针同事务模式,U2–U3 不得降低其原子性。 +- `internal/docduty/revoke.go` 已使用事务级 project advisory lock,是 KTD3 的本地先例。 +- `internal/taskrun/manager.go`、`internal/taskrun/store.go` 已有进程内幂等锁与数据库唯一键,U8 需要把正确性完全落到跨实例可用的数据库协议。 +- `feature/final-artifact-vault` 已实现候选 FAV 能力;U7 必须按当前 dev 重新审计,而不是把分支状态当成当前发布事实。 +- [PostgreSQL `SKIP LOCKED`](https://www.postgresql.org/docs/current/sql-select.html)只用于队列式 claim,不用于普通一致性读取。 +- [PostgreSQL serialization failure handling](https://www.postgresql.org/docs/16/mvcc-serialization-failure-handling.html)要求 serialization/deadlock 重试覆盖完整事务逻辑。 +- [OWASP Business Logic Security](https://cheatsheetseries.owasp.org/cheatsheets/Business_Logic_Security_Cheat_Sheet.html)支持服务端重新推导身份、所有权和安全相关状态。 + +--- + +## Definition of Done + +- [ ] U1–U9 完成并通过 M0–M3 的逐里程碑 go/no-go;U10–U12 只在收益复盘批准后执行。 +- [ ] 文档提交不存在跨表半完成,所有提交后副作用来自持久事件。 +- [ ] 多实例、多 worker、崩溃和 lease 过期场景保持相同业务结果。 +- [ ] workspace 不再是正文或最终 manifest 的唯一事实源,裸路径不跨模块传播。 +- [ ] FAV 只含终态成果,中间结果准入率为零,授权缺失时不可降级读取。 +- [ ] 项目 143 等价 hazard 与新项目完成真实 PI、页面刷新、重登、restart/rebuild 和跨用户负例。 +- [ ] clean/oldest/previous/hazard/import/reload/rollback/artifact recovery 八条迁移轨通过。 +- [ ] arch-check 规则按纵向切片清零后全部 block,例外有 owner 和到期日。 +- [ ] CURRENT、FEATURE-MATRIX、gap-matrix、ADR、系统设计、test report 和 checklist 状态一致。 +- [ ] 放弃方案、临时代码、双写旁路、过期 flags 和无消费者契约已移除,不留在最终 diff。 + +## Related Links + +- Audit: [模块解耦方案复核](../audit/2026-09-14-module-decoupling-plan-review.md) +- Architecture: [模块化单体解耦架构](../system-design/2026-09-14-module-decoupling-architecture.md) +- ADR: [模块化单体解耦](../adr/2026-09-14-modular-monolith-decoupling.md) +- Planning task: [本次计划修订任务](../archive/ops/2026-09-14-module-decoupling-remediation-plan.md) +- Acceptance: [重构验收清单](../ops/checklists/2026-09-14-module-decoupling-refactor-acceptance.md) +- FAV dependency: `feature/final-artifact-vault` 中的 `.doc/remediation/2026-09-14-final-artifact-vault-remediation.md` +- Data lifecycle dependency: `feature/final-artifact-vault` 中的 `.doc/remediation/2026-09-14-runtime-data-lifecycle-and-migration-corpus-remediation.md` diff --git a/.doc/remediation/INDEX.md b/.doc/remediation/INDEX.md index d948d641..cfc6e762 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-module-decoupling-refactor-plan.md](./2026-09-14-module-decoupling-refactor-plan.md) | 纵向绞杀、UnitOfWork、高并发互斥、可靠事件、逐动作授权、FAV、迁移与恢复的详细重构计划 | | [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..f9f37351 100644 --- a/.doc/remediation/gap-matrix.md +++ b/.doc/remediation/gap-matrix.md @@ -70,6 +70,7 @@ | DOCREV-ACTIVE-DOC-ROUTING | UI-05「当前受控文档」被法规关键词改路由到 `feasibility_reg`,intake 无 pending | **PASS** | [audit](../audit/2026-09-13-document-revision-active-doc-routing.md);Round20 UI-05 pending 停在 intake V2 | **不改弱 E2E**;不删证据 | | DOCREV-QUALITY-STATUS-FP | 行动计划表状态列「待续」被门禁当敷衍,两轮 repair 后 quality_failed 80 | **PASS** | [audit](../audit/2026-09-13-document-revision-quality-status-false-positive.md);Round20 UI-03 | **不删禁词、不放宽正文**;不删证据 | | GOVERNANCE-ACTION-AUTHZ | 文档动作按职责鉴权;未激活 202-blocked;关闭创建直通 | **PASS** | [test-report](../test-reports/2026-09-14-governance-action-level-authz-test-report.md);[audit 异态](../audit/2026-09-14-planning-with-workflow-key-anomaly.md) | 不改 83 条 planning+key 夹具;none 长 PI 审批链另开 | +| DOC-RELIABILITY-PILOT | 受控文档写路径跨表非同事务、读路径被 projection 绑架、授权 TOCTOU;刷新/重启后可能 404/409/projection-missing | OPEN | [复核](../audit/2026-09-14-module-decoupling-plan-review.md);[重构计划](./2026-09-14-module-decoupling-refactor-plan.md);[试点任务](../ops/short-term/2026-09-14-document-reliability-pilot.md) | M0–M1=U1–U4;未占槽不得 rebuild;M2+ 需独立 go/no-go | --- diff --git a/.doc/status/FEATURE-MATRIX.md b/.doc/status/FEATURE-MATRIX.md index f56e25b4..abec2c5f 100644 --- a/.doc/status/FEATURE-MATRIX.md +++ b/.doc/status/FEATURE-MATRIX.md @@ -27,6 +27,7 @@ | 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` | | 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` | +| 模块解耦与可靠事件 | 文档可靠性纵向试点、跨属主事务、多副本互斥、FAV 与选择性模块化 | planned | [架构](../system-design/2026-09-14-module-decoupling-architecture.md);[ADR](../adr/2026-09-14-modular-monolith-decoupling.md);[重构计划](../remediation/2026-09-14-module-decoupling-refactor-plan.md);[试点任务](../ops/short-term/2026-09-14-document-reliability-pilot.md) | 方案已入库。M0–M1=U1–U4 为首个批准范围;M2+ 需 M1 go/no-go | `docgov` | --- diff --git a/.doc/system-design/2026-09-14-module-decoupling-architecture.md b/.doc/system-design/2026-09-14-module-decoupling-architecture.md new file mode 100644 index 00000000..43444f69 --- /dev/null +++ b/.doc/system-design/2026-09-14-module-decoupling-architecture.md @@ -0,0 +1,422 @@ +# 模块化单体解耦架构 + +> 状态:**设计已修订,首个文档可靠性试点待实施** +> +> 决策:[模块化单体解耦 ADR](../adr/2026-09-14-modular-monolith-decoupling.md) +> +> 实施:[模块解耦整改与重构计划](../remediation/2026-09-14-module-decoupling-refactor-plan.md) + +## 1. 目标与边界 + +本设计把 Agent-System 收敛为模块化单体,使项目文档在刷新、服务重启、并发修改和工作区清理后仍能从稳定逻辑标识定位到正确正文。 +模块边界必须同时约束依赖、事务、并发、授权、文件和最终成果;只减少 Go import 或移动目录不算解耦完成。 + +### 1.1 目标 + +1. 每个业务命令有唯一应用用例、事务 owner 和幂等语义。 +2. 每张运行表和每类文件有唯一写入属主,但跨表不变量仍能在同一事务提交。 +3. HTTP、SSE、CLI、worker 和恢复工具经过同一授权决策,不信任调用方填写的身份或资源归属。 +4. workspace 只保存计算现场和可重建投影;最终文档、最终回复和 manifest 进入 FAV。 +5. 事件在崩溃、重复投递、多订阅者和水平扩容下可追踪、可重试、可隔离。 +6. 首个纵向试点直接消除受控正文刷新/重启后 404、409 和 projection missing 类故障。 + +### 1.2 非目标 + +- 不拆微服务,不引入分布式事务协调器。 +- 不在本轮统一全部 HTTP 错误响应。 +- 不在本轮替换 PI Runner stdout/result 协议。 +- 不把所有历史数据永久保留,也不把生产数据复制为测试 fixture。 +- 不允许 FAV 绕过数据库授权或自动重建活动工作流、lease、pending task。 + +## 2. 目标架构 + +```mermaid +flowchart TB + Client[Web or CLI] --> Adapter[server adapters] + Worker[Background workers] --> App[application use cases] + Adapter --> App + App --> Authz[authorization policy] + App --> UoW[UnitOfWork] + App --> ReadModels[read use cases] + UoW --> Owners[domain table owners] + Owners --> PG[(PostgreSQL)] + UoW --> Outbox[(outbox and staging)] + Outbox --> Dispatcher[event dispatcher] + Dispatcher --> Subscribers[idempotent subscribers] + Subscribers --> Vault[artifactvault] + App --> Workspace[workspace capability service] + Workspace --> Scratch[(scratch workspace)] + ReadModels --> PG + ReadModels --> Vault +``` + +### 2.1 分层职责 + +| 层 | 职责 | 禁止 | +|---|---|---| +| `internal/server` | 参数解析、认证信息转 Principal、调用应用用例、序列化 HTTP/SSE | SQL、事务、路径拼接、跨模块编排、后台重试 | +| `internal/app/*flow` | 用例编排、授权、UnitOfWork、命令幂等、事件发布 | 领域 SQL、物理路径、传输格式 | +| 消费者侧 ports | 表达一个用例需要的最小能力 | 聚合所有模块方法的万能接口 | +| 领域模块 | 业务不变量、表/文件写入所有权、事务绑定 repository | import 其他领域实现、开启不受控嵌套事务 | +| read model | 授权后的跨模块只读聚合 | 业务状态变更、修复式 GET 写入 | +| 基础设施 | PostgreSQL、CAS、FAV、workspace、事件 dispatcher | 推断业务终态或自行授予权限 | + +### 2.2 契约分类 + +`internal/contracts` 不再作为所有 DTO 的公共仓库。 + +| 契约 | 所有位置 | 版本规则 | +|---|---|---| +| HTTP/SSE wire DTO | `internal/server/api`、`internal/stream/transport` | 对外兼容,breaking change 走 ADR | +| 一对一调用端口 | 消费者 use case 包内 | 随用例演化,不带 JSON tag,不自动走 ADR | +| 公共领域事件 | `internal/contracts/events/` | 有 schema version;生产者和多个消费者共同评审 | +| 持久文件/manifest schema | 所属模块,如 `artifactvault`、`workspace` | major/minor 兼容;未知 major fail closed | +| 稳定共享词汇 | `internal/contracts/vocabulary` | 仅无行为枚举和值对象;禁止依赖业务实现 | +| 错误分类 | 领域错误留领域包;wire 映射留 server | 不把数据库/路径/第三方原始错误暴露给客户端 | + +接口默认由消费者拥有,应用组合根只注入实现。 +只有三个以上消费者确实共享相同语义时,才提升为公共端口。 + +## 3. 模块边界 + +| 模块 | 唯一职责与写入所有权 | 可依赖 | 禁止 | +|---|---|---|---| +| `app/docflow` | 文档读取、提交、批准、投影和归档用例;相关事务 owner | 消费端口、authz、uow | SQL、物理路径 | +| `app/stageflow` | 阶段推进和任务执行用例 | 消费端口、authz、uow | bizdev 门面、HTTP DTO | +| `app/chatflow` | 一轮对话识别、协调、消息落账和任务触发 | 消费端口、authz、uow | 直接写共享上下文字段 | +| `docgov` | 文档治理状态、角色、lease、`document_records` 写者 | 自有 repository、稳定词汇 | 直调 docrevision 实现、读 workspace 文件 | +| `docrevision` | immutable snapshot/version/revision/event/report 账本写者 | 自有 repository | 直接写 `document_records`、`freeform_documents` | +| `freedoc` | 自由文档根表和发布状态写者 | 自有 repository | 被 docrevision 反向更新 | +| `docquery` | 授权后文档列表、正文、版本聚合读取 | 只读端口、artifact read fallback | 任何写入、目录扫描、授权推断 | +| `chatlog` | `chat_*` 表族读写和会话/消息/turn 查询 | 自有 repository | prompt 装配、外部导出策略 | +| `contextasm` | 记忆、摘要、召回和 prompt 上下文组装 | 消费者侧 ChatLog/KB read ports | HTTP、SQL 越界写入 | +| `intent` | 纯规则意图分类 | vocabulary | bizdev、server、IO | +| `taskrun` | task run 状态机、claim/finalize、幂等写者 | 自有 repository | server 补协议、workspace 路径 | +| `squadrun` | 在受限 workspace capability 内执行角色与步骤 | agent/template/quote 消费端口 | DB、SSE、业务授权、任意路径 | +| `workspace` | Workspace ID 签发、物理路径解析、projection 生命周期 | fsutil、storage identity | 业务终态、最终归档 | +| `artifactvault` | 终态 payload/manifest/receipt/tombstone 权威 | 独立存储、registry | 活动业务关系、运行授权、workspace 扫描 | +| `notification` | 通知偏好、投递和状态 | 事件消费、repository | 业务状态推进 | +| `app` | 唯一组合根、worker 生命周期、事件订阅注册 | 所有构造函数 | 被 server 反向装配 | + +模块“可依赖”只表示通过端口调用,不表示允许 import 对方实现包。 + +## 4. 事务与单一写者 + +### 4.1 两种所有权 + +- **表属主**决定 SQL、约束和单表不变量。 +- **事务 owner**决定一个业务命令需要哪些属主共同提交。 + +两者不能合并为“每个模块各开一个事务”。 +应用用例开启 `UnitOfWork`,取得绑定同一 `pgx.Tx` 的 repository capabilities;领域服务只能使用这些 capabilities,不能 commit、rollback 或启动嵌套事务。 + +### 4.2 文档提交原子集合 + +一次受控文档提交必须原子包含: + +1. 校验 Principal、项目成员、duty、lease 和 expected head。 +2. 锁定 `document_records` 与当前 open version。 +3. 写 immutable content blob/snapshot/revision/change event。 +4. 更新版本 head 与 `document_records` 当前指针/哈希。 +5. 冻结应归档 payload 或不可变 blob 引用。 +6. 写领域事件和 FAV publish request。 +7. 提交后再进行 workspace 投影、通知和外部存储发布。 + +任一步失败,步骤 2–6 全部回滚。 +投影或发布失败不会回滚业务提交,而是进入可重试队列并在 UI 暴露 degraded 状态。 + +### 4.3 事务隔离选择 + +| 场景 | 机制 | 原因 | +|---|---|---| +| 单文档编辑/批准/提交 | `READ COMMITTED` + `SELECT ... FOR UPDATE` + expected head | 竞争范围明确,冲突可向用户解释 | +| 同幂等键并发创建 | 唯一约束 + payload digest 校验 | 数据库决定唯一赢家 | +| 跨表项目级 duty/heal | `pg_advisory_xact_lock(project_id)` + 固定行锁顺序 | 复用现有项目级互斥,事务结束自动释放 | +| 复杂跨聚合唯一不变量 | `SERIALIZABLE`,完整事务有限重试 | 仅在显式证明需要时使用 | +| outbox 多 worker claim | `FOR UPDATE SKIP LOCKED` + lease/fencing token | 提高队列吞吐,不作为普通读取语义 | + +### 4.4 锁顺序 + +所有跨属主事务按下列顺序获得锁: + +1. project 或 workflow instance 事务级 advisory/row lock; +2. document/freeform/chat aggregate root; +3. version/snapshot/turn; +4. task run; +5. staging/outbox/delivery。 + +禁止在持锁期间调用 LLM、PI Runner、对象存储、HTTP、SSE 或执行大文件复制。 +需要外部工作时采用 prepare → 外部执行 → compare-and-commit;commit 阶段重新验证 expected version。 + +### 4.5 超时与重试 + +- 每个写事务设置独立 context deadline 和数据库 `lock_timeout`;初始预算由配置管理,不在 handler 硬编码。 +- `40001` 与 `40P01` 只允许重跑完整幂等事务,采用带抖动退避和有限次数。 +- expected-version 冲突、权限拒绝、唯一键 payload 不同不是瞬时错误,不自动重试。 +- 事务重试期间不得重复发送消息、SSE、通知或对象存储写入;这些副作用只能由提交后的 outbox 驱动。 + +## 5. 并发协议 + +### 5.1 文档并发 + +客户端读取正文时获得 `ETag = head_snapshot_id:head_revision_no`。 +PATCH、agent commit、approve 和 projection repair 都必须携带 expected head 或稳定 idempotency key。 +旧 ETag 返回 typed `VERSION_CONFLICT`,响应包含当前 head 元数据但不泄露无权正文。 + +### 5.2 TaskRun 并发 + +`Claim(idempotency_key, request_digest)` 通过唯一约束保证只创建一个 run。 +相同 key、相同 digest 返回既有 run;相同 key、不同 digest 返回 `IDEMPOTENCY_CONFLICT`。 +状态迁移使用 `WHERE status = expected AND version = expected_version`,影响行数为零即重新读取并分类冲突。 +幂等键按 environment、project、actor、action 和客户端 key 作用域化,并与规范化 request digest 绑定。 +幂等记录的保留期不得短于客户端重试窗口、事件重放窗口和对应业务记录的审计期。 + +### 5.3 Workspace 并发 + +Workspace ID 由服务端签发并绑定 environment、project、run、storage identity 和生命周期。 +同一 workspace 同时只有一个写 lease;续租返回 fencing token,所有写操作校验 token。 +本地 `sync.Mutex` 可减少同进程竞争,但跨副本 lease 和 token 才是正确性依据。 +文件写入采用同目录临时文件、校验、原子 rename 和目录同步;reader 只读取完整 projection marker。 + +### 5.4 FAV 并发 + +逻辑 artifact 的幂等键由终态事件、资源、payload SHA 和 policy version 决定。 +对象写入使用 content-addressed key 和 no-replace 语义;blob、manifest 全部校验完成后最后写 commit marker。 +重复 publisher 可共享已有 blob,但只有一个 registry 状态迁移为 verified。 + +### 5.5 查询与背压 + +docquery 的列表和版本历史必须分页,BatchGet 首期硬上限 100 个引用;超过上限返回 typed validation error,不自动拆成无界并发查询。 +批量读取先按 Principal 和 project scope 分组授权,再使用有界并发和数据库 statement deadline。 +API 对 actor/project/action 维度限流,后台 backfill 和 dispatcher 使用独立连接预算,不能耗尽在线读写连接池。 +SSE 慢客户端只影响传输缓冲;不能持有业务 transaction、数据库锁或阻止事件持久化。 + +## 6. 可靠事件 + +### 6.1 事件信封 + +每个持久事件至少包含: + +```text +event_id +event_name +schema_version +environment +project_id +aggregate_type / aggregate_id / aggregate_version +actor_ref +correlation_id / causation_id +idempotency_key +payload_digest +occurred_at +``` + +事件不携带 secret、认证 token、Prompt、正文或任意文件路径。 +大 payload 先冻结到不可变 staging,事件只保存引用和 digest。 + +### 6.2 投递模型 + +```mermaid +sequenceDiagram + participant UC as Use case transaction + participant DB as PostgreSQL + participant W as Dispatcher worker + participant S as Subscriber + UC->>DB: business state + staging + outbox + DB-->>UC: commit + W->>DB: claim delivery with SKIP LOCKED + W->>S: deliver event and fencing token + S->>DB: idempotent side effect and inbox receipt in one transaction + S-->>W: success or typed failure + W->>DB: ack, retry schedule, or dead letter +``` + +- 每个订阅者有独立 delivery 状态,不能共享单一 `published_at`。 +- 投递语义为至少一次;消费者以 event ID/inbox 唯一约束幂等。 +- 同一 aggregate 按 aggregate version 有序;不同 aggregate 可并行。 +- claim 按 `next_attempt_at, event_id` 排序并采用 aging,防止热点失败事件令旧事件永久饥饿。 +- 数据库 subscriber 的业务副作用与 inbox receipt 必须同事务;外部副作用使用业务幂等键并在回执落库前允许安全重放。 +- transient failure 指数退避;永久失败进入 dead letter,不能阻塞其他订阅者。 +- worker 认领带 lease、attempt 和 fencing token;过期 worker 无权提交 ack。 +- replay 必须指定事件范围、目标消费者、操作者和原因,并写审计记录。 +- delivery/outbox 的清理只能在所有必需消费者完成、重放窗口结束且恢复 checkpoint 已覆盖后执行;审计类事件按独立保留策略处理。 + +## 7. Workspace、账本与 FAV + +### 7.1 三层权威 + +| 层 | 权威内容 | 清理规则 | +|---|---|---| +| PostgreSQL | 项目、权限、workflow、task、文档关系、版本账本、当前指针 | 依完整备份/PITR 和环境生命周期处理 | +| workspace/state | 执行现场、临时材料、可重建 projection | 终态冻结和归档门禁通过后按 TTL 清理 | +| FAV | 已归档 payload、manifest、receipt、tombstone | 按 retention/legal hold/delete ledger 处理 | + +### 7.2 Workspace capability + +跨模块 DTO 只包含: + +```text +workspace_id +project_scope +run_id +lease_fencing_token +schema_version +``` + +调用方使用 `ReadArtifact(key)`、`OpenAtomicWriter(key)`、`CommitProjection(key, digest)` 等受限端口。 +artifact key 必须是受控相对标识,禁止绝对路径、`..`、空段、跨作用域 ID、symlink 和 hardlink 逃逸。 + +### 7.3 RunManifest + +RunManifest 的规范字节、SHA、workspace ID、storage identity 和输出成员在 taskrun finalize 时持久化。 +`Evidence map[string]string` 和 `Gaps []string` 不作为核心协议;核心字段类型化,扩展只能进入带 namespace 和 schema 的 extensions。 +workspace 内 manifest 是可重建副本,不是唯一事实源。 + +### 7.4 FAV 接入 + +artifactvault 只接收服务端确认的终态文档、终态助手回复和必要附件。 +流式 chunk、候选、Prompt、工具轨迹、修复过程、临时报告和 workspace 草稿准入率必须为零。 +发布从终态事务的 staging/outbox 驱动,禁止目录扫描。 + +docquery 的读取优先级为: + +1. 校验 Principal 和数据库 resource→project 授权图; +2. 读取数据库 immutable ledger/current pointer; +3. 检查并校验 workspace projection,仅作为缓存; +4. 正文缺失但授权图完整时,可按 feature flag 从 FAV 只读降级; +5. 权限图缺失、vault tombstone、SHA 冲突或来源不一致时 fail closed。 + +GET 不得修复数据库或覆盖 head。 +修复和恢复是独立命令,并产生审计记录。 + +## 8. 授权架构 + +### 8.1 Principal + +Principal 由 HTTP session、服务身份或恢复凭证适配器构造,至少包含 actor ID、actor type、environment、认证来源、request/correlation ID 和明确 capabilities。 +`UserID`、`ProjectID`、role 和 duty 不能直接从业务请求体覆盖 Principal。 + +### 8.2 决策模型 + +授权决策输入为 `Principal + Action + Resource + CurrentState`。 + +| 动作类别 | 强制条件 | +|---|---| +| 文档读取 | 项目成员/管理员 + 分类可见性 + stage/duty 范围 | +| 文档编辑/提交 | 写权限 + 当前 duty + 合法状态 + 有效 lease + expected head | +| 批准/驳回 | 对应 action-level duty + 冻结实例配置 + 当前状态 | +| TaskRun 执行/恢复 | 项目权限 + 合法 workflow/stage + 服务端解析的 backend capability | +| FAV 降级读取 | 正常读取授权已成功 + artifact scope/hash/删除状态一致 | +| replay/backfill | 运维 capability + 精确范围 + dry-run + 审计 | +| scratch recovery promotion | 系统管理员 + 数据责任人确认 + scratch identity + 冲突计划无未决项 | + +应用用例可以在事务前做快速拒绝,但状态改变必须在取得资源锁后重新读取授权相关事实并再次判定,防止成员、duty、stage 或 lease 在检查后变化。 +授权服务不可用或关系不完整时默认拒绝。 +事件携带的 actor 只用于审计;订阅者产生持久或外部副作用前重新读取当前权威状态。 + +### 8.3 后端与数据最小化 + +调用方不能提交任意 `Backend string`。 +应用层按环境、项目策略和输入敏感级别解析 `BackendRef`,agent 适配器再注入受管凭证。 +凭证不得进入 DTO、日志、事件、manifest、result 文件或 FAV。 +InputBundle 只包含完成任务必需的材料,并标记来源、分类、允许用途和生命周期。 + +## 9. 架构门禁 + +### 9.1 静态规则 + +| 规则 | 检查对象 | 例外 | +|---|---|---| +| 领域实现互不 import | Go production packages | 消费端口和稳定 vocabulary | +| server 不 import repository/领域实现 | server production packages | app facade、wire DTO | +| 表写入唯一属主 | production SQL 的 INSERT/UPDATE/DELETE/MERGE | Goose、测试 fixture、批准的修复工具 | +| 工作区物理路径唯一属主 | production path construction/open/write | workspace/squadrun adapter | +| 事件发布者 allowlist | event name → producer package | 无运行时动态注册旁路 | +| 订阅者 allowlist | event name → consumer use case | replay 工具需显式 capability | + +例外必须记录 owner、原因、范围、到期日和移除里程碑。 +warn/block 不按固定“两周”切换;每条规则在对应纵向切片清零后单独转 block。 + +### 9.2 行为门禁 + +架构检查不能替代以下测试: + +- 同文档并发编辑、批准和 agent commit; +- 任意事务步骤失败时全量回滚; +- worker 崩溃、lease 过期、重复投递和多订阅者部分失败; +- workspace 删除、换根、重启和 projection 重建; +- FAV 降级读取与 scratch 恢复; +- 匿名、非成员、跨用户、跨项目、错误 duty、错误 stage、授权依赖失败; +- 真实浏览器刷新/重登/rebuild 与真实 PI 主链。 + +## 10. 可观测性与容量 + +### 10.1 必须指标 + +- 数据库事务时长、锁等待、deadlock、serialization retry、版本冲突率。 +- outbox pending/oldest age、delivery attempts、lease steal、dead letter 数。 +- projection missing/mismatch/repair、FAV degraded read、vault verification lag。 +- 授权 deny 按 action/reason 计数,不记录正文或敏感资源名。 +- workspace active lease、过期清理、fencing rejection、storage identity mismatch。 + +### 10.2 高并发基线 + +首个试点必须建立并保存基线,而不是直接承诺未经测量的容量数字: + +- 同一文档热点写入与不同文档并行写入分开压测。 +- 1、2、4 个 app 实例下验证进程内锁不影响正确性。 +- 1、2、4 个 dispatcher 下验证无重复可见副作用、无饥饿和有界积压。 +- p50/p95/p99 延迟、锁等待、事务重试、CPU/连接池和队列年龄进入 test report。 +- 正确性门禁固定;吞吐阈值在基线评审后写入环境配置与发布 SLO,不能靠降低一致性通过。 + +## 11. 迁移与兼容 + +采用 expand → migrate → contract: + +1. 新增兼容 schema、端口和读路径,不改变旧写入。 +2. 新写路径 shadow/dual-read 验证,禁止无审计双写。 +3. 按 checkpoint 幂等回填,异常进入 quarantine。 +4. 切主写者,保留旧 reader 一个明确发布窗口。 +5. 验证真实刷新/restart/rebuild、FAV coverage 和回滚。 +6. 删除旧路径前再次审计引用和数据差异。 + +旧 workspace 文件缺少 `schema_ver` 时按只读 legacy-v0 解析。 +首次受控 Resume 先校验并原子升级到 v1,保留可回滚备份;未知 major 版本 fail closed。 + +回滚只保证回到最近兼容点。 +已被下游里程碑消费的 schema 或契约必须按逆依赖顺序回滚,不能单独 revert 早期提交。 + +## 12. 首个交付边界 + +首个里程碑只覆盖受控文档可靠性: + +```mermaid +flowchart TB + Read[Authorized content read] --> Ledger[Immutable ledger] + Ledger --> Projection{Projection aligned?} + Projection -->|yes| Return[Return canonical content] + Projection -->|no| Vault{Authorized FAV fallback enabled?} + Vault -->|yes| Degraded[Return verified degraded content] + Vault -->|no| Conflict[Typed consistency error] + Commit[Edit or agent commit] --> Tx[Single UnitOfWork] + Tx --> Ledger + Tx --> Root[Document root pointer] + Tx --> Stage[Artifact staging] + Tx --> Event[Outbox] +``` + +只有这个切片完成刷新、重启、并发、权限和恢复验收后,才启动其他包的物理拆分。 + +## 13. 后续扩展顺序 + +1. 文档可靠性试点。 +2. taskrun finalize、RunManifest 持久化和 FAV 集成。 +3. 逐个事件替换闭包桥。 +4. chatlog/contextasm 原地端口化,再按证据移动包。 +5. squadrun capability 化和 backend policy。 +6. app 组合根迁移。 +7. UserProjectContext 增量收权。 +8. server/SSE 收口。 +9. 错误信封与 PI Runner 协议作为独立项目实施。 + +详细依赖、文件和测试场景见整改计划。 diff --git a/.doc/system-design/README.md b/.doc/system-design/README.md index 651ea9c6..4c88309f 100644 --- a/.doc/system-design/README.md +++ b/.doc/system-design/README.md @@ -99,6 +99,26 @@ --- +### 6. 模块化单体解耦架构 + +| 项 | 链接 | +|-----|------| +| 主报告 | [2026-09-14-module-decoupling-architecture.md](2026-09-14-module-decoupling-architecture.md) | +| ADR | [../adr/2026-09-14-modular-monolith-decoupling.md](../adr/2026-09-14-modular-monolith-decoupling.md) | +| 整改计划 | [../remediation/2026-09-14-module-decoupling-refactor-plan.md](../remediation/2026-09-14-module-decoupling-refactor-plan.md) | +| 一句话总结 | 先以文档可靠性纵向试点验证事务、锁、授权、事件与 FAV,再按证据扩展模块边界 | + +**核心要点**: + +- 单一写者负责 SQL 和不变量,应用 UnitOfWork 负责跨属主原子提交; +- 数据库行锁、事务级 advisory lock、唯一约束和 fencing token 支撑多副本并发; +- workspace 是可清理投影,FAV 只保存已确认终态成果; +- 事件采用逐消费者 delivery、幂等 inbox、租约、重试和 dead letter; +- HTTP、CLI、worker、subscriber 和恢复工具使用同一逐动作授权模型; +- 先原地端口化,行为稳定后才物理搬包。 + +--- + ## 🏗️ 在整体架构中的位置 ``` @@ -161,6 +181,7 @@ ├── 04-项目多工作流与智能产品经理-测试经理工作流-实施计划.md ├── 05-Phase0-基线确认与契约冻结.md └── 06-Phase7-全链路验收与发布基线.md +├── 2026-09-14-module-decoupling-architecture.md ← 模块化单体解耦 .tmp/ ← 中间分析文件 ├── intent-recognition/