diff --git a/.doc/ops/short-term/2026-09-14-durable-event-fav-integration.md b/.doc/archive/ops/2026-09-14-durable-event-fav-integration.md similarity index 82% rename from .doc/ops/short-term/2026-09-14-durable-event-fav-integration.md rename to .doc/archive/ops/2026-09-14-durable-event-fav-integration.md index 44bcfdda..22b15e1c 100644 --- a/.doc/ops/short-term/2026-09-14-durable-event-fav-integration.md +++ b/.doc/archive/ops/2026-09-14-durable-event-fav-integration.md @@ -1,8 +1,8 @@ --- title: "可靠事件与 FAV 集成 M2–M3" -status: planned +status: cancelled created: 2026-09-14 -updated: 2026-09-14 +updated: 2026-09-15 owner: TBD branch: refactor/durable-event-fav-integration worktree: "-" @@ -10,6 +10,7 @@ mutex: event-delivery --- # 可靠事件与 FAV 集成 M2–M3 +> 2026-09-15:本任务把 durable event 与未审计 FAV 绑成同一关闭条件,且步骤状态已与 U8/U9 后续交付漂移。任务停止使用;持久事件由 [Track E](../../ops/short-term/2026-09-15-durable-event-track-e.md) 接续,FAV subscriber 继续走独立审计,不把本条已勾选步骤视为生产 dispatcher 验收。 ## 目标 @@ -50,7 +51,7 @@ mutex: event-delivery ## 相关链接 -- Checklist: [../checklists/2026-09-14-module-decoupling-refactor-acceptance.md](../checklists/2026-09-14-module-decoupling-refactor-acceptance.md) +- Checklist: [../checklists/2026-09-14-module-decoupling-refactor-acceptance.md](../../ops/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) diff --git a/.doc/archive/ops/2026-09-15-module-decoupling-phase2-plan-review.md b/.doc/archive/ops/2026-09-15-module-decoupling-phase2-plan-review.md new file mode 100644 index 00000000..f01cdd86 --- /dev/null +++ b/.doc/archive/ops/2026-09-15-module-decoupling-phase2-plan-review.md @@ -0,0 +1,57 @@ +--- +title: "审阅并修订模块解耦 Phase 2 方案" +status: done +created: 2026-09-15 +updated: 2026-09-15 +owner: Codex +branch: docs/module-decoupling-ipc-assessment +worktree: "-" +mutex: architecture-docs +--- + +# 审阅并修订模块解耦 Phase 2 方案 + +## 目标 + +以当前代码、migration、ADR 和 Phase 1 计划为证据,修正调研与 Phase 2 方案,并补齐高并发、互斥、权限、历史数据、灰度和回滚设计;不实施业务代码。 + +## 范围 + +- 复核调研报告与原 Phase 2 方案的事实、依赖和完成边界。 +- 将事件可靠性、结构解耦、HTTP/PI 协议拆成独立轨道。 +- 补充 PG 状态机、fencing、幂等、顺序、连接预算、权限、replay、脱敏、迁移和 browser/restart 验收。 +- 同步 audit、remediation、CURRENT、FEATURE-MATRIX 与 gap-matrix。 + +## 非目标 + +- 不实现 dispatcher、consumer、migration 或协议代码。 +- 不合并、部署、重启容器、删除数据或审计/合入 FAV 分支。 +- 不把文档完成写成系统能力完成。 + +## 步骤 + +1. [x] 完成仓库启动链和上位 ADR/Phase 1 证据复核。 +2. [x] 完成一致性、可行性、范围、安全、产品与对抗视角审阅。 +3. [x] 修正调研报告中的 PG、依赖方向、allowlist 与进程边界事实。 +4. [x] 重写 Phase 2 为 Track E/S/P,补齐详细实施和停走门。 +5. [x] 同步索引与状态 SSOT。 +6. [x] 执行文档链接、diff、冲突标记和术语检查。 + +## 验收标准 + +- [x] 调研不再把 SELECT-only Claim、MemoryBus 测试或 allowlist 基线写成生产闭环。 +- [x] Track E 可独立关闭,FAV/D8/D9 不阻塞。 +- [x] 方案明确 at-least-once 与 exactly-one visible outcome、最终提交点 fencing、集群级切换和授权模型。 +- [x] 历史脏数据只分类/隔离/修复,不授权删除。 +- [x] 文档状态一致且没有实施或发布声明。 + +## 相关链接 + +- Audit: [方案审阅](../../audit/2026-09-15-module-decoupling-phase2-plan-review.md) +- Remediation: [Phase 2 修订计划](../../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) +- Research: [IPC/RPC 现状评估](../../ops/research/2026-09-15-module-decoupling-ipc-assessment.md) +- Test report: 不适用(文档审阅,无业务实现) + +## 证据 / 备注 + +本任务沿用用户已创建的 `docs/module-decoupling-ipc-assessment` 分支。该分支名早于当前 `feature/*|fix/*` 规则;未改写或重命名用户分支。起点提交为 `a3392e3` 与 `5cef856`,本轮未获授权 commit/push。 diff --git a/.doc/audit/2026-09-15-module-decoupling-phase2-plan-review.md b/.doc/audit/2026-09-15-module-decoupling-phase2-plan-review.md new file mode 100644 index 00000000..662c377e --- /dev/null +++ b/.doc/audit/2026-09-15-module-decoupling-phase2-plan-review.md @@ -0,0 +1,111 @@ +--- +title: "模块解耦 Phase 2 方案审阅" +status: done +created: 2026-09-15 +updated: 2026-09-15 +severity: high +--- + +# 模块解耦 Phase 2 方案审阅 + +## 结论 + +模块化单体、消费者端口、组合根和持久事件的方向成立,但原 Phase 2 方案不能直接进入实施。它把事件可靠性、结构整理和两个外部协议绑成一个完成目标,并基于数项不成立的代码事实设计切换门。修订版已把 Track E(持久事件)、Track S(结构解耦)、Track P(外部协议)拆开,Track E 必须先完成生产者与历史数据门,再启动 dispatcher。 + +## 高优先级发现 + +### A1. PG delivery 尚未形成生产闭环 + +`internal/events/pg.go` 的 `ClaimIDs` 只做 SELECT + `FOR UPDATE SKIP LOCKED`,没有在同一事务写 lease owner/token/until;也没有 PG ack/fail/retry/dead-letter 的完整生产实现。`internal/events/dispatcher_test.go` 验证的是 MemoryBus。原方案把这些能力写成“已有”,会低估 D1 的 migration、状态机和故障测试范围。 + +整改:D1 增加 additive migration、原子 lease、heartbeat、fenced side-effect/ack、retry/dead/quarantine、不可变 attempt 表和独立 `cmd/events` 进程。 + +### A2. “恰好一次投递”不可由当前算法保证 + +副作用成功后、ack 前崩溃会导致重放。Fencing 若只拦 ack,也拦不住租约过期的旧 worker 提交副作用。 + +整改:契约改为 at-least-once delivery + exactly-one visible outcome。Token 进入 consumer command 和最终提交点;数据库副作用与 inbox/token 校验同事务,projection 使用 expected version、digest 和单调 marker。 + +### A3. 原 shadow 定义会双写,且按天零差异可空跑通过 + +旧方案同时写闭包和消费者结果,却又称“不产生可见变化”;两者矛盾。三天 dev 流量也可能没有低频文档事件或失败分支。 + +整改:闭包是 shadow 期间唯一真实写者。消费者只计算 normalized intended outcome 并写 reconciliation。停走门同时要求预登记样本、分支覆盖、最短时间和零未解释 diff;生产入口未证明出事件前不开始计时。 + +### A4. 文档事件源与作用域尚不可信 + +部分生产 HTTP 路径临时构造 `docflow.Commands`,没有稳定注入 EventSink/UoW。`Environment:"dev"`、`ProjectID:0` 和 FAV stage project 0 会使跨环境/项目 delivery 失真。 + +整改:E0A 先由组合根注入唯一命令对象;领域变化、envelope、delivery 同一 UoW;缺 project/env fail closed;旧数据先 inventory。 + +### A5. AggregateVersion 不能直接当无缺口事件序号 + +当前只在部分领域转换发事件。历史文档首次事件可能从较高版本开始;按缺号等待会永久阻塞。 + +整改:D11 在独立 event sequence 与 consumer checkpoint/bootstrap 中二选一,审计现有 aggregate/version 唯一约束并迁移。未决前禁止开启 gap blocking。 + +### A6. docgov/docrevision 不是双向 import + +生产方向为 `docgov → docrevision`,用途集中在 canonical Markdown 与 SHA helper。原方案“提升无行为类型到 vocabulary”不能承载 canonicalization 行为。 + +整改:D6 改为提取内聚 `doccontent` 行为模块和 golden vectors;D6 可独立执行,不依赖 bizdev/stageflow。 + +### A7. event allowlist 已存在 + +`module-boundaries.yaml` 已有 `event_producers`/`event_consumers`。问题是映射稀疏,且运行时注册没有与静态清单做 parity 检查。 + +整改:D10 提升到 E0A,补 payload schema/version、enabled state 与 registry parity;FAV 标记 disabled。 + +### A8. FAV、HTTP、PI 不应阻塞核心 + +FAV 分支未完成独立审计。ADR 已把 HTTP 错误信封和 PI result protocol 定义为独立工作。把三者放入 Track E DoD 会延迟最有价值的刷新恢复闭环。 + +整改:Track E 只激活 notify、workflow advance、projection。FAV 只预留 disabled 契约;D8/D9 分别另建 remediation。 + +### A9. Replay、后台权限与日志脱敏不足 + +延迟事件不能沿用历史用户权限;当前 replay 校验不足以支持生产 apply。Raw payload/error 进入 dead-letter 或 reconciliation 会泄露正文、路径或凭证。 + +整改:每 consumer 独立 service principal,处理时重读资源和当前权限。Replay 采用 capability + 精确范围 + dry-run digest/apply exact digest + 审计;多项目/批量 apply 两人批准。日志只存 ID/digest/typed code/sanitized summary。 + +### A10. 滚动切换缺少集群级权威写者 + +删除新版本 hook 不会阻止旧实例继续执行。若 dispatcher 同时 live,滚动部署会双写。 + +整改:`off|shadow|live` 使用带 revision 的集群级状态;先部署兼容代码、drain 全部旧实例、记录 backlog 水位,再原子切换。回滚先暂停 claim并核对 receipt/backlog。 + +### A11. Locator 门禁原文过宽 + +全局禁止 `*pgxpool.Pool`/`pgx.Tx` 会拦截组合根注入与登记表属主的合法 UoW unwrap。 + +整改:只禁止领域/用例 API 暴露、返回、转发 pool/tx;明确允许 app/cmd 构造注入和登记属主 unwrap。 + +## 范围修订 + +| 轨道 | 内容 | 关闭条件 | +|---|---|---| +| Track E | D1/D2/D3/D10/D11 | PG + browser + restart + auth/replay 证据完成后独立关闭 | +| Track S | D4/D5/D6/D7a/D7b | 各单元独立 go/no-go 与报告 | +| Track P | HTTP error、PI typed result | 各自独立 remediation,不阻塞 Track E/S | + +## 关联整改 + +- [修订后的 Phase 2 计划](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) +- [修订后的现状调研](../ops/research/2026-09-15-module-decoupling-ipc-assessment.md) + +## 审阅方法与证据 + +- 代码证据:`internal/events/pg.go`、`bus.go`、`dispatcher_test.go`、`replay.go`、`internal/app/docflow/commands.go`、`internal/docgov/lease.go`、`internal/bizdev/service.go`。 +- Schema:`deploy/postgres/goose/00035_2026-09-14-durable-event-delivery.sql`。 +- 边界:`.doc/system-design/module-boundaries.yaml`。 +- 上位决策:模块化单体 ADR 与 Phase 1 U6。 +- 审阅视角:一致性、可行性、范围、安全、产品价值、对抗失败预演;外部 reviewer 的有效结论仅用于交叉验证,不替代仓库证据。 + +## 开放问题 + +以下问题必须由 E0A 的代码/数据审计回答,不能在计划阶段猜测: + +- projection consumer 的最终 owner 与 no-replace 提交接口。 +- event sequence 或 checkpoint/bootstrap 的选择及迁移成本。 +- 每类事件前 7 天基线流量、shadow 样本 N 和 worker 连接预算。 +- 现存错误 scope/schema delivery 的数量和 repair/quarantine/expire 分类。 diff --git a/.doc/audit/INDEX.md b/.doc/audit/INDEX.md index 058e4ec8..d959cf0a 100644 --- a/.doc/audit/INDEX.md +++ b/.doc/audit/INDEX.md @@ -13,6 +13,15 @@ ## 核心文档(按阅读顺序推荐) +### 模块解耦 Phase 2 方案复核(2026-09-15) + +**文件**: [2026-09-15-module-decoupling-phase2-plan-review.md](./2026-09-15-module-decoupling-phase2-plan-review.md) + +**结论**: PG delivery 尚未形成生产闭环;原计划把事件、结构和协议三轨绑成一次交付,并误判 docgov/docrevision 依赖方向。修订版先完成事件生产与历史数据门,再以 PG dispatcher、逐消费者 shadow 和真实刷新/重启恢复收口。 + +**整改**: [Phase 2 持久事件交付与后续解耦路线](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) + + ### arch-check U1 可重放基线(2026-09-14) **文件**: [2026-09-14-arch-check-baseline.md](./2026-09-14-arch-check-baseline.md) diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index 4996d802..7fc2ce9b 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -5,8 +5,8 @@ ## 焦点 -1. **CHAT-INTENT**: [对话意图运行时状态](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md)(双标签 busy 已签:项目 **6275** / `DOC_BUSY` 单 run;不删 `sess-p143-u9` 探针) -2. **DOC-RELIABILITY**: [U11–U12 组合根与传输门禁](../archive/ops/2026-09-15-app-composition-u11-u12.md)(已合入 `dev@bf59cce`;rebuild digest `a8e3e940…`;schema 38) +1. **TRACK-E**: [持久事件交付](./short-term/2026-09-15-durable-event-track-e.md)(方案已审阅;下一开工为 E0A 生产者/契约/历史数据门;dispatcher 保持 off) +2. **CHAT-INTENT**: [对话意图运行时状态](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md)(双标签 busy 已签:项目 **6275** / `DOC_BUSY` 单 run;不删 `sess-p143-u9` 探针) 3. **CHAT**: [对话历史刷新活验已补](../test-reports/2026-09-13-chat-history-refresh-test-report.md)(项目 6252 闲聊刷新同集 + guest 403;编制任务链仍开放) ## 并行认领 @@ -20,6 +20,8 @@ | Cursor | taskrun-runmanifest-u8 | feature/taskrun-runmanifest | - | taskrun | done | | Cursor | document-reliability-m1-live-and-m2 | refactor/document-reliability-pilot | - | workspace | done | | Codex | module-decoupling-remediation-plan | feature/module-decoupling-remediation | - | architecture-docs | done | +| Codex | module-decoupling-phase2-plan-review | docs/module-decoupling-ipc-assessment | - | architecture-docs | done | +| Cursor | module-decoupling-phase2-docs | feature/module-decoupling-phase2-docs | - | architecture-docs | in_progress | | verifier_antigravity | controlled-content-ui-acceptance | fix/controlled-content-ui-acceptance | - | document-content-ui-acceptance | done | | implementer_cursor | controlled-content-canonical-hash | fix/controlled-content-canonical-hash | - | docgov | done | | Codex | project143-controlled-content-404-diagnosis | fix/project143-controlled-content-404-diagnosis | - | docgov-content-diagnosis | done | @@ -98,6 +100,8 @@ | 纯软件项目自举设计 | [../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) | +| Phase 2 方案审阅 | [../audit/2026-09-15-module-decoupling-phase2-plan-review.md](../audit/2026-09-15-module-decoupling-phase2-plan-review.md) | +| Phase 2 修订计划 | [../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) | | 文档可靠性 M1 报告 | [../test-reports/2026-09-14-document-reliability-pilot-test-report.md](../test-reports/2026-09-14-document-reliability-pilot-test-report.md) | | 文档可靠性 M1 活验 | [../test-reports/2026-09-14-document-reliability-pilot-live-test-report.md](../test-reports/2026-09-14-document-reliability-pilot-live-test-report.md) | | 文档可靠性 M2 | [../test-reports/2026-09-14-document-reliability-m2-test-report.md](../test-reports/2026-09-14-document-reliability-m2-test-report.md) | diff --git a/.doc/ops/research/2026-09-15-module-decoupling-ipc-assessment.md b/.doc/ops/research/2026-09-15-module-decoupling-ipc-assessment.md index 90be56f2..fc9dfdbc 100644 --- a/.doc/ops/research/2026-09-15-module-decoupling-ipc-assessment.md +++ b/.doc/ops/research/2026-09-15-module-decoupling-ipc-assessment.md @@ -1,5 +1,5 @@ --- -title: "模块解耦与 IPC/RPC 交互现状评估(耦合点清点与进一步解耦方案)" +title: "模块解耦与 IPC/RPC 交互现状评估(复核修订版)" status: done created: 2026-09-15 updated: 2026-09-15 @@ -11,133 +11,139 @@ confidence: high ## 结论 -1. **系统走的是"模块化单体"路线,而不是全量 IPC/RPC 微服务**——这是 [ADR 2026-09-14](../../adr/2026-09-14-modular-monolith-decoupling.md) 的明确决策(非目标:不拆微服务、不引入分布式事务协调器)。模块间解耦靠三件套:**消费者拥有的 Go 端口接口 + 唯一组合根(`internal/app`)装配 + PostgreSQL outbox 事件**。真正的跨进程边界只有三条:前端↔server(HTTP/SSE)、Go↔PI Agent Runner 容器(HTTP + Bearer)、规划中的独立事件 dispatcher 进程。 -2. **调用契约已大部分"机器可执行地"约定**:`.doc/system-design/module-boundaries.yaml` + `internal/archtest` 四类静态规则(import / event 两类已在生产门禁 `--mode block` 下清零;path 16 条、sql_write 5 条为带 owner/到期日的例外账本),外加 `events.Envelope` schema 1、`taskrun.RunManifest` schema 1、SSE transport、幂等协议(idempotency key + request digest;ETag = `head_snapshot_id:head_revision_no`)。 -3. **残留的强耦合集中在五处**:bizdev 门面兼 service locator、docgov↔docrevision 双向 import、chat/coordinator 各依赖 ~10 个包、进程内闭包 hook 桥(崩溃即丢、无幂等)、durable dispatcher 未接线(事件写进了 outbox 但没有消费者 worker 在跑)。另有 PI Runner stdout/result.json 旧协议与 HTTP 错误信封未统一两项已声明的 deferred 债。 +1. 系统采用模块化单体,不应把所有模块调用改成 IPC/RPC。当前已运行的外部传输边界有两条:Web 与 server 的 HTTP/SSE、Go server 与 PI Agent Runner 的 HTTP。`cmd/events` 和 FAV worker 是计划中的独立进程,不是已运行事实。 +2. 端口、组合根、事件信封、RunManifest、幂等键和 arch-check 提供了可执行的边界基础;`import=0/event=0` 只表示没有超出现有 allowlist/baseline 的新违规,不能证明语义解耦已经完成。 +3. 首要可靠性缺口是事件“部分生产、没有生产级消费闭环”。`internal/events/pg.go` 尚无原子 lease、ack/fail、retry/dead-letter 的完整实现;现有 dispatcher 测试覆盖 MemoryBus,不是 PostgreSQL 运行证据。 +4. 最大结构扩散源仍是 bizdev service locator。另有 `docgov → docrevision` 单向 canonical/hash 工具依赖、chat/coordinator 扇出和 `intent → bizdev` 依赖。此前“docgov↔docrevision 双向 import”的结论不成立。 +5. HTTP 错误信封与 PI Runner typed result 是独立协议债,不应阻塞持久事件链关闭。FAV 分支也不应成为 notify/projection 交付的前置条件。 -对用户目标的直接回答:**"彻底解耦、一处修改不扩散"已大体达成**(import 违规为 0、单一写者、契约带版本与 SHA),**但"所有交互都走 IPC/RPC"并非本仓库的方向也不建议照搬**;错误排查能力的短板不在进程边界,而在事件链路只写不消费、闭包桥无审计痕迹。 +## 1. 通信边界 ---- +| 边界 | 机制 | 当前状态 | +|---|---|---| +| Web ↔ server | `/api/v1/*` HTTP + SSE | 已运行;错误信封仍未统一 | +| Go ↔ PI Agent Runner | HTTP + Bearer;health/run/cancel/search | 已运行;结果仍依赖 legacy stdout/result.json 路径 | +| 单体内模块 | 消费者端口 + `internal/app` 组合根 | 已运行;bizdev locator 和部分直依赖仍扩大改动面 | +| 领域事件 | PG envelope/delivery schema | 写侧部分存在;生产 dispatcher 未闭环 | +| `cmd/events` | 计划中的独立 worker | 尚未实现/部署 | +| FAV worker | 独立审计轨道 | 分支未整支合入;不得作为本轮验收前置 | -## 1. 通信边界清点(谁和谁怎么交互) +不建议把单体内端口改成 RPC。那会增加序列化、延迟、部分失败与运维成本,却不消除当前 locator、授权、幂等和事件消费缺口。 -| 边界 | 机制 | 契约位置 | 状态 | -|---|---|---|---| -| Web 前端 ↔ server | HTTP `/api/v1/*`(~110 路由)+ SSE | `internal/server`(wire DTO)、`internal/stream/transport/sse.go` | ✅ 已约定;错误信封统一 deferred | -| Go ↔ PI Agent Runner 容器 | HTTP + `Bearer RUNNER_AUTH_TOKEN`;`/health`、`/v1/run`、`/v1/cancel`、`/v1/search`(`deploy/pi-agent/runner.mjs:319-405`) | backend allowlist(`internal/taskrun/backend.go`:stub/pi-agent/cursor);结果经 stdout/result.json | ⚠️ 传输已约定;**结果协议仍是 stdout/result.json**(整改计划明确 deferred 不本轮替换) | -| 模块 ↔ 模块(进程内) | Go 端口接口(消费者所有)+ `internal/app` 组合根注入 | `module-boundaries.yaml` imports allowlist + archtest | ✅ 机器可执行;bizdev/coordinator/chat 例外见 §3 | -| 领域事件 | PG outbox + envelope + 逐消费者 delivery + fencing token | `internal/events/envelope.go`(schema 1、digest、secret 扫描)、`pg.go`、`replay.go` | ⚠️ **写侧已落地、消费侧未接线**(见 §3.4) | -| 终态产物 | RunManifest(canonical JSON + SHA-256)+ FAV | `internal/taskrun/manifest.go`(schema 1) | ✅ U8 已合入 dev;FAV 分支未整支 merge(KTD12) | +## 2. 已有契约与真实边界 -## 2. 契约约定情况(输入/输出/协议是否"约好") +### 已有且可校验 -**已约定且可执行/可校验:** +- `.doc/system-design/module-boundaries.yaml` 与 `internal/archtest` 管理 import、SQL write、path 和 event 规则。 +- `events.Envelope` schema 1 包含 event/aggregate/correlation/causation/idempotency/digest/producer 等字段,并校验 digest 与敏感字段。 +- `taskrun.RunManifest` schema 1 使用 canonical JSON 与 SHA-256。 +- TaskRun 同 idempotency key 异 request digest 返回冲突;文档写使用 expected head/ETag。 +- backend 选择由服务端 allowlist 控制,出站数据经过 secret stripping。 +- `module-boundaries.yaml` 已有 `event_producers` 与 `event_consumers` 段。后续工作是补全并校验 runtime registry parity,不是新建段落。 -- 静态边界:`scripts/arch-check.sh --mode block` 通过(`block_kinds: [import, event]`,baseline import=0、event=0);例外必须有 owner/milestone/expires。 -- 事件信封:`EnvelopeSchema=1`,字段齐全(event_id/aggregate_type/version/correlation/causation/idempotency_key/payload_digest/producer),`ValidateEnvelope` 拒绝伪造 producer、digest 不符、payload 携带 secret。 -- 投递协议:`Delivery`(pending/leased/acked/dead/quarantine)+ lease + fencing token + `MaxAttempts=12` + aggregate version gap 等待(`events/bus.go`,PG 版 `SKIP LOCKED` 同语义)。 -- 幂等协议:TaskRun `Claim(idempotency_key, request_digest)` 同 key 异 digest 返回 `IDEMPOTENCY_CONFLICT`;文档 ETag/expected head。 -- 持久 schema:RunManifest v1(member kinds 枚举 + canonical 排序 + SHA);`configs/schemas/change-report-v1.json`。 -- 后端选择:`ResolveBackend` 服务端 allowlist,调用方不可注入任意字符串;`StripSecrets` 出站脱敏。 +### 尚未闭合 -**未约定 / 弱约定:** +- `PGSink.Append`/`InsertEnvelope` 已存在,但 `ClaimIDs` 仍是 SELECT-only claim;没有生产级 lease 获取、heartbeat、fenced ack/fail、retry、dead-letter 和 immutable attempt audit 闭环。 +- `event_deliveries` 每个 event/consumer 一行,只保存当前状态和累计 attempt;无法还原每次 lease/attempt 历史。 +- `internal/events/replay.go` 只检查 dry-run 与 consumer/operator/reason 等基础字段,尚未形成带认证主体、env/project/event scope、plan digest 和 apply 审计的应用用例。 +- `docflow` envelope 仍存在 `Environment:"dev"`、`ProjectID:0`,且 `Fav.StageAgentCommit(ctx, 0, ...)` 使用错误项目作用域。 +- 部分 server 路径临时构造 `docflow.Commands`,没有稳定注入 EventSink/UoW;不能假设 candidate-ready、downstream-impact、approved 三条链已经产生可供 shadow 的事件。 +- 闭包 hook 没有版本、重试、delivery 或 replay 记录。 -- PI Runner 结果解析(stdout/result.json)——deferred。 -- HTTP 错误信封统一——deferred。 -- 闭包 hook(`OnOutbox`、`AddOnApprovedHook`)无 schema、无版本、无审计记录。 -- docflow 发事件的 `Environment: "dev"` 与 `ProjectID: 0` 硬编码(`internal/app/docflow/commands.go:167`),未从 Principal/命令注入——协议字段存在但值来源失真(test/main 环境下事件环境标注错误)。 +## 3. 耦合点 -## 3. 强/弱耦合点清点(附证据) +### 3.1 bizdev service locator -### 3.1 强耦合:bizdev 门面 + service locator(最大枢纽) +`bizdev.Service` 暴露 `Pool()/CAS()/DocGov()/DocRevision()/ProjectWorkflow()/FreeformDoc()/KB()/ContextManager()`。调用方可以绕过端口取得数据库池和其他模块实现,令签名或构造变化向多包扩散。 -- ~4.8k LOC,被 **12 个生产包 import(78 处引用)**;自身 import 18 个内部包。 -- `bizdev.Service` 暴露 `Pool()/CAS()/DocGov()/DocRevision()/ProjectWorkflow()/FreeformDoc()/KB()/ContextManager()`(`service.go:229-260`)——**service locator 反模式**:任何拿到 `*bizdev.Service` 的包都能直取 `*pgxpool.Pool` 和其他模块的具体实现,绕过所有端口抽象。 -- `app/stageflow` 已开始绞杀(只依赖 bizdev),但 `coordinator.StageTaskRunner` 仍以 `bizdev.TaskRunRequest/Result` 为词汇(`engine.go:85-88`),即传输类型与门面绑死。 -- 影响:改 bizdev 任何签名会波及 chat/coordinator/taskrun/projectinstance/intent 等全部下游——正是"一处修改多处出问题"的活样本。 +整改应先加 no-new-callers 门禁,再逐调用方迁移,最后在一个 contract 提交删除 8 个 getter。门禁应禁止领域/用例 API 暴露或转发 pool/tx,同时允许 app/cmd 组合根向表属主构造器注入 pool,并允许登记属主从 opaque TxScope 解出 tx。 -### 3.2 强耦合:docgov ↔ docrevision 双向 import +### 3.2 docgov 到 docrevision 的单向工具依赖 -- `docgov` imports `docrevision`,`docrevision` imports `docgov`(boundaries.yaml 双向 allowlist)。两个"单一写者"模块互相拿到对方类型,表所有权边界在类型层面没有闭合。 +生产依赖方向是 `docgov → docrevision`。直接用途集中在 Markdown canonicalization 与 SHA helper;`docrevision` 不生产 import `docgov`。 -### 3.3 强耦合:chat / coordinator 扇出过大 +目标不是“破双向环”,而是把 canonical Markdown + digest 提取为有行为、内聚、带 golden vectors 的共享模块,例如 `internal/doccontent`。它不应进入只放无行为值对象的 vocabulary。 -- `chat` 依赖 11 个包(bizdev、coordinator、ctxmgr、docgov、intent、projectinstance、replyexport、squad、stream、taskrun…);`coordinator` 依赖 10 个包。U10 已完成 chatlog/contextasm 端口化,但 `internal/chat` 本体尚未收编进 `app/chatflow`。 -- `ctxmgr` 被 5 个包共享(56 处引用),是共享可变上下文状态的汇聚点。 -- `intent`(纯规则模块)依赖 `bizdev`——与架构文档 §3 的模块表(intent 只准依赖 vocabulary)直接矛盾,属于基线豁免债。 +### 3.3 chat、coordinator 与 intent -### 3.4 强耦合:进程内闭包桥替代事件消费(可靠性缺口) +- coordinator 仍以 `bizdev.TaskRunRequest/Result` 为词汇。 +- chat 编排跨多个模块,需按用例逐批迁入 chatflow。 +- intent 与 bizdev 的解绑是较小且独立的迁移,不应和 chat 大迁移绑成一个分支。 -- 生产发布者只有两处:`app/docflow`(AgentCommit)与 `taskrun/finalize.go`(`events.PGSink{}.Append`,consumers 声明为 projection/fav/notify)。 -- **没有任何生产代码构造/运行 PG dispatcher**(全仓 grep `events.New|Dispatcher` 无命中;`MemoryBus` 仅 characterization 用;U6 规划的 `cmd/events/main.go` 不存在)。`Runtime.Start` 只跑 reminder loop 并绑定闭包。 -- 消费侧仍是闭包:`docnotify.Bind` 直接改写 `docrevision.Service.OnOutbox` 字段;`docgov.AddOnApprovedHook → app.go:180 onWorkflowDocumentApproved` 在组合根内联编排 projectinstance + projectworkflow + notification。 -- 后果:进程崩溃/重启时 hook 注册丢失、无 delivery 记录、无重试/死信/replay——设计目标 5(事件在崩溃、重复投递下可追踪可重试)**未达成**;这也正是错误排查最痛的盲区(事件有表可查,hook 无迹可寻)。 +### 3.4 闭包桥与未接线事件 -### 3.5 弱耦合(已在例外账本内、有到期日) +当前批准后的推进与通知仍可通过 `docrevision.OnOutbox`、`docgov.AddOnApprovedHook` 及组合根闭包执行。闭包在提交后的进程内调用不能提供 durable delivery、重试、dead-letter、replay 或 attempt audit。 -- **path 16 条**:bizdev(4)/coordinator(4)/docgov(2)/docrevision(2)/server(3)/storage(1) 直接拼 `output.md`、`data/dev`、`workspaces/` 物理路径;U4 到期 2026-09-28,U5 到期 2026-10-12。 -- **sql_write 5 条**:生产写越界,按属主里程碑清零。 -- **server 适配层**:102 文件 / 17,211 LOC,allowlist 28 个包——架构上允许(server 是唯一传输适配层),但 `chatflow_http` 等编排仍在 server 内,U12 传输收口未完全做完。 -- **uow.AsPG unwrap owner**:已有 archtest 规则约束谁能解出 `pgx.Tx`,方向正确。 +不能直接删除闭包。正确顺序是: -## 4. 进一步解耦方案(按优先级) +1. 证明生产入口在同一 UoW 写入领域变化、envelope 和目标 delivery。 +2. 对历史事件做 dry-run inventory,隔离 scope/schema/sequence 异常。 +3. 完成真实 PG dispatcher。 +4. shadow 只计算 intended outcome,不执行业务副作用。 +5. 以时间、样本和状态分支覆盖作为切换门。 +6. 按 notify、workflow advance、projection 独立切换和回滚。 -### P0 正确性/可排查性(先做,收益最大) +## 4. 并发、数据与权限风险 -1. **接线 durable dispatcher,替换闭包桥(U6 收尾)**: - - 新增 `cmd/events` worker(claim → deliver → ack/dead-letter,`SKIP LOCKED` + fencing token 已有实现 `internal/events/pg.go`); - - 在组合根把 `projection`/`notify`/`fav` 注册为真实消费者,逐个删除 `docrevision.OnOutbox` 与 `docgov.AddOnApprovedHook` 闭包桥(U6 Approach 第 7 步原文); - - 验收即架构文档 §9.2 行为门禁:worker 崩溃/lease 过期/重复投递后副作用恰好一次。 - - 这一步同时解决"崩溃丢通知"和"排查无审计痕迹"两个问题。 -2. **修 docflow 事件字段注入**:`Environment/ProjectID` 从 Principal 与命令上下文取值,禁止硬编码 `"dev"`/`0`;可加 archtest 规则(production 禁止 `Environment: "dev"` 字面量出现在 envelope 构造)。 +### 并发 +- `FOR UPDATE SKIP LOCKED` 只有在 claim 事务同时更新 lease owner/token/until 并 RETURNING 时才完成所有权转移;当前 SELECT-only `ClaimIDs` 不够。 +- Fencing 不能只保护 ack。token 必须进入 consumer command,并在数据库副作用或 projection marker 的最终提交点校验。heartbeat 失败应取消 handler。 +- 同一 consumer/aggregate 最多一个 inflight;不同 aggregate 并行。领域版本可能稀疏,不能直接当无缺口事件序号。 +- at-least-once delivery 通过 inbox/唯一键/CAS/no-replace 得到 exactly-one visible outcome;不能声称 delivery 本身 exactly once。 -### P1 结构解耦(消除"一处改、多处坏") +### 历史数据 -3. **绞杀 bizdev**:删除 `Service` 上的 `Pool()/CAS()/ContextManager()` 等 locator 访问器,组合根直接装配具体服务;`ExecuteTask/ExecuteStage/InspectWorkspace` 全面移交 `app/stageflow`;`coordinator.StageTaskRunner` 改用 stageflow 自有请求/结果类型,切断对 `bizdev.*` 传输类型的依赖。目标:bizdev 退化为纯领域包(docduty/stage_access 等)或拆散归零。 -4. **打破 docgov↔docrevision 循环**:共享类型提升到 `contracts/vocabulary`(仅无行为值对象)或改为消费者端口;随后在 boundaries.yaml 中互相移出 allowlist 并转 block。 -5. **收编 `internal/chat`**:把剩余编排并入 `app/chatflow`,chat 包依赖从 11 收敛到端口集合。 -6. **解除 `intent → bizdev`**:与架构表对齐,intent 只依赖 vocabulary(现为豁免债)。 +旧记录可能含 project 0、错误 environment、未知 consumer、未知 payload schema 或稀疏 aggregate version。启用 live claim 前必须生成 manifest,并分类 safe/repair/quarantine/expire。任何修复保留 old/new digest、operator、reason、approval;本计划不删历史数据。 -### P2 契约完善(对外接口收口) +### 权限 -7. **path/sql_write 例外按期清零转 block**(2026-09-28 / 2026-10-12 到期,owner 已登记);到期未清零的按 archtest 语义直接红。 -8. **HTTP 错误信封统一**(deferred 项单独立项):typed `VERSION_CONFLICT`/`IDEMPOTENCY_CONFLICT` 已有领域错误,补 wire 映射规范。 -9. **PI Runner 协议 v2**(deferred 项单独立项):以 RunManifest schema 为基础定义 typed result 契约 + JSON Schema 入 `configs/schemas/`,替换 stdout/result.json 解析;保留 `/v1/run|cancel|search|health` + Bearer 传输不变。 -10. **事件 producer/consumer allowlist 落地为 yaml**(架构文档 §9.1 已规划):event name → producer/consumer 映射进 `module-boundaries.yaml`,archtest 校验,防运行时动态注册旁路。 +后台 consumer 使用按 env/consumer/action 约束的 service principal。处理事件时按 ProjectID/DocID 重读资源归属、成员/duty、workflow config revision、活动 stage 和当前状态。事件 actor 只用于审计,不能把历史用户权限带到延迟执行时。 -### 关于"要不要全面 IPC/RPC 化"的总体建议 +Replay 必须是应用用例:管理员 capability、精确范围、reason/approval、dry-run plan digest、apply exact digest 和不可变审计。多项目或批量 apply 需要操作者与批准人分离。 -- **不建议**把单体内的端口调用改成 RPC:ADR 与 KTD1–KTD12 的判断在当前规模下成立——进程内端口 + archtest 门禁 + 带版本契约已实现"改一处不扩散"(import 违规 0、单一写者、schema 版本 + SHA 校验),RPC 只会引入序列化、部分失败、延迟与运维成本。 -- **值得保持/推进的进程边界**恰好是三条已有/已规划的:PI Runner(已独立容器 + HTTP 契约)、durable dispatcher 独立进程(`cmd/events`,本次 P0-1)、FAV 发布 worker(U7 后续)。 -- 若未来确需拆服务,先决条件清单即 P2 三项:错误信封、PI 协议 schema 化、事件 allowlist——完成后每条进程边界都有显式契约。 +日志、dead-letter 和 reconciliation 只能保存 ID、digest、typed error code 与字段 allowlist 生成的摘要,不能保存原始 payload、正文、SQL error、绝对路径、token 或 prompt。 -## 证据 +## 5. 优先级建议 -| 项 | 路径/命令 | 结果 | -|----|-----------|------| -| 架构决策 | `.doc/adr/2026-09-14-modular-monolith-decoupling.md`、`.doc/system-design/2026-09-14-module-decoupling-architecture.md` | 模块化单体;非目标含"不拆微服务" | -| 静态门禁 | `scripts/arch-check.sh` + `internal/archtest` | `--mode block` 通过;import/event=0;path=16、sql_write=5 例外 | -| 里程碑状态 | `.doc/status/FEATURE-MATRIX.md:30`、CURRENT | U5–U12 已合入 `dev@bf59cce`(schema 38);path/sql_write 例外保留 | -| PI Runner 协议 | `deploy/pi-agent/runner.mjs:81-89,319-405` | Bearer 鉴权 + 4 端点 | -| 事件信封/投递 | `internal/events/envelope.go`、`bus.go`、`pg.go` | schema 1 + fencing + inbox;无生产 dispatcher 装配点 | -| 闭包桥 | `internal/app/docnotify/subscriber.go:30-36`、`internal/docgov/engine.go:39-53`、`internal/app/app.go:170-208` | OnOutbox / AddOnApprovedHook 进程内直绑 | -| bizdev locator | `internal/bizdev/service.go:229-260` | Pool/CAS/DocGov 等 8 个访问器 | -| 扇出统计 | `grep` import 扫描(生产包) | bizdev 被 12 包 78 处引用;chat 11 包;coordinator 10 包;ctxmgr 5 包 56 处 | -| 双向依赖 | `module-boundaries.yaml` | docgov↔docrevision 互在 allowlist | -| 事件字段硬编码 | `internal/app/docflow/commands.go:160-177` | `Environment:"dev"`、`ProjectID:0` | -| RunManifest | `internal/taskrun/manifest.go` | schema 1 + canonical + SHA-256 | +### P0:Track E 持久事件交付 -## 开放问题 +1. 修正 docflow 生产入口、env/project 作用域和 UoW 原子写。 +2. 补全既有 event allowlist 与 runtime registry parity。 +3. 审计历史事件、sequence/checkpoint 模型和现有唯一约束。 +4. 实现 `cmd/events` 的 PG lease/heartbeat/fencing/attempt/dead-letter。 +5. notify、workflow advance、projection 逐个 shadow、live、回滚。 +6. 用真实 browser + PG + worker restart 证明刷新后正文恢复,无 projection-missing。 -- `projection`/`fav` 两个已声明的 outbox 消费者当前没有任何注册实现——dispatcher 接线时需先确认 projection subscriber(workspace 投影重建)归属哪个用例包。 -- FAV 分支 `feature/final-artifact-vault` 未整支合入(KTD12 要求先审后用);U7 降级读取依赖它,需单独排期审计。 -- `*pgxpool.Pool` 跨包传递(bizdev.Pool() 暴露)是否已被 archtest 覆盖未见显式规则;绞杀 bizdev 时应同步补规则,防止 locator 以新形式回潮。 +### P1:Track S 结构解耦 + +- D4:bizdev locator 调用方迁移与最终 getter 原子删除。 +- D5:stageflow 自有 use-case 类型,coordinator 解绑 bizdev DTO。 +- D6:提取 doccontent canonical 行为模块,移除单向工具依赖。 +- D7a:intent 单独解绑 bizdev。 +- D7b:chat 按用例迁入 chatflow。 + +这些单元分别 go/no-go,不作为事件可靠性 blocker 的关闭条件。 + +### P2:Track P 独立协议 + +- HTTP/SSE 错误信封另立 remediation;公开 details 必须按 code allowlist 并脱敏。 +- PI Runner 任务命名为 typed-result-v1 rollout,不混称“协议 v2”。typed 字段存在但无效时 fail closed;只有字段缺失且握手声明旧 Runner 时才允许 legacy。 + +## 6. 证据索引 + +| 事实 | 证据 | +|---|---| +| 模块化单体决策 | `.doc/adr/2026-09-14-modular-monolith-decoupling.md` | +| PG delivery 当前实现 | `internal/events/pg.go`、`deploy/postgres/goose/00035_2026-09-14-durable-event-delivery.sql` | +| MemoryBus 测试边界 | `internal/events/dispatcher_test.go`、`internal/events/bus.go` | +| replay 当前校验 | `internal/events/replay.go` | +| 事件字段硬编码 | `internal/app/docflow/commands.go` | +| 闭包桥 | `internal/app/docnotify/subscriber.go`、`internal/docgov/engine.go`、`internal/app/app.go` | +| bizdev locator | `internal/bizdev/service.go:229-260` | +| 单向 canonical 依赖 | `internal/docgov/lease.go`、`internal/docrevision` canonical helper | +| allowlist 已存在 | `.doc/system-design/module-boundaries.yaml` | ## 建议动作 -1. 立项 P0-1(durable dispatcher 接线 + 闭包桥下线)为下一个 short-term 任务,占 `events`/`app` mutex。 -2. P0-2 可作为小修复随 P0-1 同批提交。 -3. P1-3(bizdev 绞杀)建议拆为多支 short-term,先从删除 locator 访问器开始(编译器会列出全部受影响点,天然的工作清单)。 -4. P2 三项各走独立 remediation 计划(与既有 deferred 清单一致,避免与在途任务抢 mutex)。 - +下一项只立 Track E 的 E0A:事件生产、契约与历史数据门。Dispatcher 必须保持 off,直到 PG 测试证明事件源和旧数据分类正确。完整执行计划见 [Phase 2 持久事件交付与后续解耦路线](../../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md)。 diff --git a/.doc/ops/short-term/2026-09-15-durable-event-track-e.md b/.doc/ops/short-term/2026-09-15-durable-event-track-e.md new file mode 100644 index 00000000..77c5f639 --- /dev/null +++ b/.doc/ops/short-term/2026-09-15-durable-event-track-e.md @@ -0,0 +1,52 @@ +--- +title: "Track E 持久事件交付" +status: planned +created: 2026-09-15 +updated: 2026-09-15 +owner: TBD +branch: fix/event-production-scope-contract +worktree: "-" +mutex: events +--- + +# Track E 持久事件交付 + +## 目标 + +按 E0A→E0B→E1A→E1B→E1C 完成 notify、workflow advance、projection 的 PG 持久交付,并用授权页面刷新/重启恢复证据关闭 `DOC-RELIABILITY-PILOT` 的事件部分。 + +## 范围 + +- E0A:生产入口/UoW、event contract、runtime registry、历史数据与 sequence 门。 +- E0B:PG dispatcher、attempt audit、独立进程、并发与故障注入。 +- E1A/B/C:三个消费者独立 shadow、switch、rollback 和真实恢复验收。 + +## 非目标 + +- FAV consumer 激活、bizdev/chat/intent 结构重构、HTTP 错误信封、PI typed result。 +- 删除或覆盖历史事件;在 E0A 通过前启动 dispatcher。 + +## 步骤 + +1. [ ] E0A 单独建 short-term/分支,dispatcher 保持 off。 +2. [ ] E0A 报告通过后再认领 E0B。 +3. [ ] 依次交付 E1A notify、E1B workflow advance、E1C projection;每步独立 go/no-go。 +4. [ ] 一个兼容发布窗口后 contract 删除闭包 API。 +5. [ ] 同步 test report、CURRENT、FEATURE-MATRIX、gap-matrix。 + +## 验收标准 + +- [ ] 满足 [Phase 2 Track E Definition of Done](../../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md#track-e-definition-of-done)。 +- [ ] 每个波次拥有独立 owner、mutex、分支、报告和回滚证据。 +- [ ] 未把计划、MemoryBus、stub 或日志冒充生产 PG/browser/restart 验收。 + +## 相关链接 + +- Checklist: 按每个波次建立 +- Audit: [Phase 2 方案审阅](../../audit/2026-09-15-module-decoupling-phase2-plan-review.md) +- Remediation: [Phase 2 修订计划](../../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) +- Test report: 待实施 + +## 证据 / 备注 + +本条只建立执行入口,不代表已批准 E0A 或已认领 mutex。实施前必须按 CURRENT 检查冲突并从当时最新 dev 建分支。 diff --git a/.doc/remediation/2026-09-15-http-error-envelope-remediation.md b/.doc/remediation/2026-09-15-http-error-envelope-remediation.md new file mode 100644 index 00000000..a7eb23ef --- /dev/null +++ b/.doc/remediation/2026-09-15-http-error-envelope-remediation.md @@ -0,0 +1,56 @@ +--- +title: "HTTP 与 SSE 公开错误信封整改" +status: planned +created: 2026-09-15 +updated: 2026-09-15 +--- + +# HTTP 与 SSE 公开错误信封整改 + +## 背景 + +Phase 2 复核确认错误信封涉及全部 HTTP 路由、SSE transport 和前端解析,属于独立 wire-contract 改造,不应阻塞持久事件交付。审阅见 [模块解耦 Phase 2 方案审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md)。 + +## 目标 + +所有公开 HTTP/SSE 错误使用版本化、可脱敏、可测试的信封;客户端按 typed code 分支,不解析任意后端文案。迁移保持 additive,breaking 状态码变化必须另写 ADR。 + +## 契约 + +- 基础字段:`code`、固定安全 `message`、`request_id`、可选 `details`。 +- 每个 code 单独维护 `details` 公开字段 allowlist;默认空。 +- 禁止输出 raw error、SQL、堆栈、绝对路径、凭证、prompt、正文和跨租户资源 ID。 +- 授权失败保持资源存在性不泄露;404/403 选择由现有授权策略决定。 +- `VERSION_CONFLICT`、`IDEMPOTENCY_CONFLICT`、`VALIDATION`、`AUTHZ` 先建立领域错误到 wire code 的唯一映射。 +- SSE error frame 与 HTTP 使用同一 code registry 和脱敏器,保留 transport 所需 event metadata。 + +## 分波次计划 + +| 波次 | 内容 | 验收 | +|---|---|---| +| H0 | 盘点路由、现有状态码、前端文本匹配和 SSE error frame | 路由矩阵与兼容风险签字 | +| H1 | 新增 envelope/code registry/request ID/details allowlist,旧字段 additive 保留 | schema/golden/negative tests | +| H2 | 前端逐 code 迁移,未知 code 显示安全通用错误 | 浏览器覆盖冲突、鉴权、校验、未知 code | +| H3 | 删除文本匹配和旧字段;若改变状态码先 ADR | 全路由 contract test;回滚窗口关闭 | + +## 权限与安全验收 + +- 未授权用户无法从 code/message/details 区分资源存在性、owner 或项目成员。 +- 任何 internal error 都只返回 request ID;完整诊断仅在受控服务端日志,且经过字段脱敏。 +- 结构化 validation details 只包含公开字段名和稳定错误码,不回显原始输入。 +- 日志按 request ID 关联,不把响应脱敏前对象序列化。 + +## 验收标准 + +- [ ] HTTP 与 SSE schema/golden 入 CI,所有公开端点有矩阵。 +- [ ] 前端不再按 message substring 分支。 +- [ ] XSS、路径/SQL/token/prompt/跨租户 ID 泄露负例通过。 +- [ ] additive、switch、contract、rollback 各有 test report。 +- [ ] 未改变的状态码保持兼容;breaking 变更有 ADR 与同步发布计划。 + +## 相关链接 + +- Audit: [Phase 2 方案审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md) +- 上位路线: [Phase 2 修订计划](./2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) +- Short-term: 实施前另建 +- gap-matrix 行: 当前为工程债,不单独作为 Track E 发布 blocker diff --git a/.doc/remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md b/.doc/remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md index 6bc33bdb..9fcae4ee 100644 --- a/.doc/remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md +++ b/.doc/remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md @@ -1,5 +1,5 @@ --- -title: "模块解耦 Phase 2 整改方案(事件消费接线、bizdev 绞杀、双向依赖破环与契约收口)" +title: "模块解耦 Phase 2:持久事件交付与后续解耦路线" status: planned created: 2026-09-15 updated: 2026-09-15 @@ -7,255 +7,336 @@ type: refactor owner: TBD --- -# 模块解耦 Phase 2 整改方案 +# 模块解耦 Phase 2:持久事件交付与后续解耦路线 -## 背景 +## 决策摘要 -- 调研与耦合清点:[2026-09-15 模块解耦与 IPC/RPC 交互现状评估](../ops/research/2026-09-15-module-decoupling-ipc-assessment.md)(下称"调研报告")。 -- 上位约束:[模块化单体解耦 ADR](../adr/2026-09-14-modular-monolith-decoupling.md)、[模块化单体解耦架构](../system-design/2026-09-14-module-decoupling-architecture.md)、[Phase 1 重构计划](./2026-09-14-module-decoupling-refactor-plan.md)(U1–U12 已合入 `dev@bf59cce`,path/sql_write 例外保留)。 -- 调研结论:静态边界(import/event)已 block 清零,但事件链路**只写不消费**、bizdev 仍是 service locator 枢纽、docgov↔docrevision 双向 import、chat/coordinator 扇出过大、两项 deferred 契约债(HTTP 错误信封、PI Runner 结果协议)未立项。本方案即 Phase 1 "Deferred to Follow-Up Work" 的正式接续。 +Phase 2 的核心交付只包含持久事件链:修正事件生产、补齐 PostgreSQL 投递实现、用无副作用 shadow 对账替换闭包桥,并以授权用户刷新/重启后的正文恢复作为停走门。 -## Goal Capsule +结构解耦(D4–D7)是独立轨道;HTTP 错误信封与 PI Runner typed result 各自单独立项。三条轨道分别验收,不能互相阻塞关闭。 -- **Objective:** 让模块间副作用全部经持久事件可恢复、可重放、可审计;消除 bizdev locator 与双向依赖两类结构性耦合;把剩余对外契约(HTTP 错误、PI 结果协议、事件 allowlist)显式化。 -- **Means:** durable dispatcher 接线并 shadow 对账后下线闭包桥;编译器驱动的 bizdev 访问器移除;vocabulary 提升破环;allowlist 落地为 archtest 规则。 -- **Authority:** 上列 ADR / 架构文档 / Phase 1 计划;本方案不推翻其任何 KTD,只接续未完成项。 -- **Stop condition:** 任一波次出现"事件至少一次语义无法与现有闭包副作用等价"、"bizdev 移除导致行为回归无法用 characterization 锁定"、或"错误信封变更破坏既有前端契约"时,停止扩面回设计评审。 -- **Execution profile:** 高风险、跨模块、事件与授权敏感;characterization-first,逐波次独立 short-term / 分支 / 验收。 -- **Delivery:** 每个单元独立验收报告;本方案本身不授权合并、部署或删数据。 +本计划不拆微服务,不引入外部消息基础设施,不合入 FAV 分支,不删除历史数据,也不授权实施、合并或部署。 -## Scope +## 背景与纠偏 -**包含**:调研报告 P0(D1–D3)、P1(D4–D7)、P2(D8–D10)。 +- 上位约束:[模块化单体解耦 ADR](../adr/2026-09-14-modular-monolith-decoupling.md)、[架构设计](../system-design/2026-09-14-module-decoupling-architecture.md)、[Phase 1 计划](./2026-09-14-module-decoupling-refactor-plan.md)。 +- 复核结论:[Phase 2 方案审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md)。 +- 现状调研:[IPC/RPC 现状评估](../ops/research/2026-09-15-module-decoupling-ipc-assessment.md)。 +- `internal/events/pg.go` 目前只有信封写入与 SELECT 型 `ClaimIDs`,没有原子 lease、ack/fail、重试、dead-letter 的生产闭环。`internal/events/dispatcher_test.go` 只证明 `MemoryBus` 语义,不能作为 PG 运行证据。 +- `docgov` 仅单向依赖 `docrevision` 的 Markdown canonical/hash 工具,不存在生产双向 import 环。 +- `module-boundaries.yaml` 已有 `event_producers`/`event_consumers`;缺口是映射不完整、运行时注册未与静态清单校验。 +- `docflow.Commands` 在部分生产 HTTP 路径临时构造,事件 sink 未稳定注入;其 envelope 仍写入 `Environment:"dev"`、`ProjectID:0`。未先修生产入口就开始 shadow,会得到虚假的零差异。 -**不包含**(已有 owner,不重复立项): -- path 16 条 / sql_write 5 条例外清零——归 U4(2026-09-28)/ U5(2026-10-12)里程碑 owner。 -- FAV 分支 `feature/final-artifact-vault` 审计与整支合入——归 KTD12 独立流程。 -- 微服务拆分与跨服务消息基础设施——维持 ADR 非目标。 +## 目标与边界 -## Requirements +### Track E:持久事件交付(本计划核心) -- **R1(事件可恢复)** projection/notify/fav 三类消费者的副作用在进程崩溃、重启、重复投递后恰好一次;delivery 表可查每次 attempt/lease/fencing。 -- **R2(闭包桥下线)** `docrevision.Service.OnOutbox` 与 `docgov` `OnApprovedHook(s)` 不再被生产代码注册;等价行为由事件消费者提供,shadow 对账零差异后才切换。 -- **R3(事件字段真实)** envelope 的 `Environment/ProjectID` 来源于服务端上下文,production 禁止硬编码字面量。 -- **R4(无 locator)** 生产包之间不传递 `*pgxpool.Pool`;`bizdev.Service` 不再暴露 `Pool()/CAS()/ContextManager()/DocGov()/DocRevision()/ProjectWorkflow()/FreeformDoc()/KB()`。 -- **R5(单向依赖)** `docgov` 与 `docrevision` 互不 import 对方实现;共享类型只经 vocabulary/端口。 -- **R6(扇出收敛)** `internal/chat` 并入 `app/chatflow` 或其依赖降到端口集合;`intent` 不再依赖 `bizdev`。 -- **R7(错误信封)** HTTP 错误响应有统一 typed 信封(含 `VERSION_CONFLICT`/`IDEMPOTENCY_CONFLICT` 映射),前端按信封分支,breaking change 走 ADR。 -- **R8(PI 结果协议)** Runner 结果以带 schema 版本的 typed JSON 契约交付(JSON Schema 入 `configs/schemas/`),stdout/result.json 解析仅作 legacy 兼容一个发布窗口。 -- **R9(事件 allowlist)** event name → producer/consumer 映射进 `module-boundaries.yaml`,archtest 校验;无运行时动态注册旁路。 -- **R10(门禁收紧)** 每个单元完成后把对应规则加入 `block_kinds` 或新增规则;新违规立即 block,不带无主例外。 -- **R11(兼容迁移)** 闭包→事件、错误信封、PI 协议均 expand → shadow → switch → contract,保留一个发布窗口的旧路径。 -- **R12(行为等价证据)** 每波次交付前提供 characterization 基线(旧路径行为快照)+ 切换后 diff 为零或差异逐条解释。 +目标:让 notify 与 projection 副作用在重复投递、进程崩溃、租约过期和重启后达到“至少一次投递、恰好一个可见结果”,并能从数据库追查每次 attempt。 -## Planning Contract +包含 D1、D2、D3、D10、D11。Track E 完成后即可单独关闭,不等待结构或协议轨道。 -### Key Technical Decisions +### Track S:结构解耦(后续独立 go/no-go) -- **KTD1(dispatcher 先行,桥后下线)。** 先接线 dispatcher 与消费者并 shadow 运行(闭包与事件双写、只以闭包为准),对账连续 N 天零差异后才删除闭包注册;不允许直接切换。 -- **KTD2(编译器驱动绞杀)。** 删除 bizdev 访问器是原子提交,编译错误清单即受影响点工作清单;禁止"保留访问器但少用"的半吊子状态。 -- **KTD3(vocabulary 只放无行为类型)。** 破环时共享类型提升到 vocabulary 包仅限枚举/值对象;带方法的类型改消费者端口,不得把 vocabulary 变成新垃圾桶。 -- **KTD4(传输类型与用例同包)。** `stageflow` 定义自己的请求/结果类型,coordinator 依赖该端口而非 `bizdev.*` DTO;一对一端口留在消费者包(沿用 Phase 1 §2.2 契约分类)。 -- **KTD5(错误信封 additive 优先)。** 新信封字段 additive;旧客户端忽略未知字段不破坏;状态码语义变化走 ADR + 前端同步发布。 -- **KTD6(PI 协议双轨一个窗口)。** `/v1/run` 响应同时携带 legacy 字段与 typed result(RunManifest 风格 schema v1);服务端优先解析 typed,解析失败按 legacy;一个发布窗口后删除 legacy。 -- **KTD7(allowlist 即代码)。** producer/consumer 映射与 import allowlist 同文件同规则同门禁,不另建运行时注册机制。 -- **KTD8(逐消费者灰度)。** 事件消费者按 notify → projection → fav 顺序逐个上线(风险与可观测性递增),每个消费者独立验收,不整批切换。 +目标:移除 bizdev locator,收敛 stageflow/chat/intent 依赖,并消除 `docgov → docrevision` 的剩余工具依赖。包含 D4、D5、D6、D7a、D7b。 -### High-Level Topology(目标态) +Track S 只有在 Track E 复盘后重新认领 mutex 才能开始;每个单元独立关闭。 + +### Track P:外部协议(另立 remediation) + +- P1:HTTP/SSE 错误信封。 +- P2:PI Runner typed-result-v1 rollout。 + +D8、D9 在本文件保留设计约束和立项入口,不属于 Track E/S 的 Definition of Done。独立方案见 [HTTP/SSE 错误信封](./2026-09-15-http-error-envelope-remediation.md) 与 [PI typed-result-v1](./2026-09-15-pi-typed-result-v1-remediation.md)。 + +### 明确排除 + +- U4/U5 的 path/sql_write 例外清零及其既有 owner。 +- `feature/final-artifact-vault` 的审计、合入、subscriber 激活与真实副作用验收。本计划只预留 FAV event schema 和 disabled allowlist 条目。 +- 全量 IPC/RPC、服务拆分、分布式事务协调器。 +- 破坏性历史清理。旧事件只允许分类、隔离、修复或明确过期;任何删除另走审批计划。 + +## 需求与可追踪性 + +| ID | 要求 | 交付单元 | 核心验收 | +|---|---|---|---| +| E1 | PG delivery 状态机可生产运行 | D1 | 真实 PG 多 worker、崩溃点、fencing 测试 | +| E2 | 至少一次投递、一个可见结果 | D1/D2 | 副作用幂等键/事务 receipt;重复投递无重复可见结果 | +| E3 | 每次 attempt 可审计 | D1 | 不可变 attempt 表,日志不代替数据库审计 | +| E4 | 事件作用域真实且授权闭合 | D3/D11 | env/project/service principal/资源复核全部 fail closed | +| E5 | 闭包桥安全下线 | D2 | shadow 覆盖门通过;逐消费者切换;一窗口后删 API | +| E6 | 静态契约与运行注册一致 | D10 | allowlist 已有段补全;构建时 registry parity 门禁 | +| E7 | 历史脏数据不会误消费 | D11 | dry-run 清单;repair/quarantine/expire 决策可审计 | +| E8 | 用户刷新恢复 | D2 | 授权 UI 刷新/重启正文一致,无 projection-missing | +| S1 | bizdev 不再充当 locator | D4 | 8 个 getter 最终同批删除;无新调用方 | +| S2 | 用例类型归属清楚 | D5 | coordinator 不依赖 bizdev DTO | +| S3 | 剩余工具依赖消除 | D6 | docgov/docrevision 互不 import;canonical golden 一致 | +| S4 | intent/chat 可独立迁移 | D7a/D7b | 两条独立分支、mutex、验收与回滚 | + +## 关键技术决策 + +1. **语义不写“exactly once delivery”。** Dispatcher 提供 at-least-once delivery。消费者用唯一键、事务 inbox/receipt、CAS 或 no-replace 提交保证 exactly-one visible outcome。 +2. **shadow 不执行业务副作用。** 旧闭包仍是唯一真实写路径;shadow 只计算规范化 intended outcome,并写对账记录。禁止“闭包与消费者双写后比较”。 +3. **独立 worker 进程。** 事件消费落在 `cmd/events`,与 HTTP 进程分开扩缩容;`internal/app` 不重复启动 dispatcher。该选择沿用 Phase 1 U6,避免两种拓扑并存。 +4. **先证明事件源,再开始计时。** 三条文档链必须在同一 UoW 写入领域变化、envelope 和目标 delivery。PG 测试证明生产入口确实产生事件后,shadow 窗口才有效。 +5. **每个消费者独立灰度。** 状态为 `off|shadow|live`,按 environment + consumer 配置。notify、workflow advance、projection 分开切换;FAV 保持 off。 +6. **静态 allowlist 与运行 registry 同源。** `module-boundaries.yaml` 现有映射补全后生成或校验只读 registry manifest;生产禁止未登记的动态注册。 +7. **历史事件先盘点。** 作用域为 0、环境错误、未知 consumer、序列不连续或 payload schema 未知的旧记录不得自动 live 消费。 +8. **结构与协议不绑核心关闭。** Track E、S、P 分别有状态、停走门、测试报告和回滚点。 + +## 目标拓扑 ```mermaid -flowchart TB - UC[app use cases] -->|Append envelope| OB[(PG outbox + delivery)] - WK[cmd/events dispatcher] -->|claim SKIP LOCKED| OB - WK --> N1[notify consumer] - WK --> N2[projection consumer] - WK --> N3[fav consumer] - N1 --> Notif[notification] - N2 --> WS[workspace projection] - N3 --> FAV[artifactvault] - SF[app/stageflow] -->|ports| DOM[domain modules] - CO[coordinator] -->|StageTaskRunner port| SF +flowchart LR + HTTP[server/use case] -->|domain change + envelope + deliveries / one UoW| PG[(PostgreSQL)] + WK[cmd/events] -->|atomic lease| PG + WK --> N[notify] + WK --> A[workflow advance] + WK --> P[projection] + N -->|idempotency key| PG + A -->|transactional inbox| PG + P -->|expected head + digest| WS[workspace projection] + F[FAV contract] -. disabled .-> WK ``` -## Phased Delivery +## 数据模型与并发协议 -| 波次 | 交付边界 | 单元 | 进入条件 | 退出/停走门 | -|---|---|---|---|---| -| W0 | 事件消费接线 + 字段修正 | D1, D3 | 在途 mutex 不冲突;占 `events`/`app` | dispatcher 双 worker 并发 claim 无双 owner;shadow 对账启动 | -| W1 | 闭包桥下线 | D2, D10 | W0 shadow 连续零差异(建议 ≥3 天 dev 流量) | R1/R2/R9 全 PASS;hook 注册代码删除 | -| W2 | bizdev 绞杀 | D4, D5 | W1 完成;占 `bizdev`/`stageflow`/`coordinator` | R4 PASS;`go list` 证明 bizdev 出口只剩领域类型 | -| W3 | 双向破环 + 扇出收敛 | D6, D7 | W2 完成;逐个占 `docgov`/`docrevision`/`chat`/`intent` | R5/R6 PASS;boundaries.yaml 互相移出并 block | -| W4 | 契约收口(独立 go/no-go) | D8, D9 | W0–W3 复盘批准;各占 `server`/`taskrun`+`deploy/pi-agent` | R7/R8 PASS;前端与 Runner 双轨窗口开闭明确 | +### Delivery 状态机 -W0–W1 是首个批准范围;W2–W4 需前一波次验收报告后重新 go/no-go,不构成一次性整体授权。 +```text +pending -> leased -> acked + |-> pending transient failure / lease expiry + |-> dead max attempts or explicit permanent failure + |-> quarantine scope, schema, integrity or ordering violation +``` ---- +- Claim 必须在一个短事务内 `WITH candidates AS (SELECT ... FOR UPDATE SKIP LOCKED LIMIT ...) UPDATE ... FROM candidates ... RETURNING`,同时写 `lease_owner`、递增 `fencing_token`、`lease_until`、`attempt`。现有 SELECT-only `ClaimIDs` 不满足此条件。 +- Handler 在 claim 事务外执行。Fencing token 必须随 consumer command 传到最终副作用提交点;数据库副作用在同一事务校验 token,projection marker 使用 expected version + 单调 token。Ack/fail 也必须匹配 `event_id + consumer + lease_owner + fencing_token`。旧 token 的副作用和 ack/fail 都必须失败。 +- 长任务按受保护的 token 续租;heartbeat 失败立即取消 handler。停机先停止 claim,再等待 inflight 或安全释放租约。 +- 同一 consumer + aggregate 同时最多一个 inflight;不同 aggregate 可并行。不能把领域版本号直接假设为无缺口事件序号。 +- D11 必须在以下两种模型中选一并写 ADR/迁移:独立无缺口 event sequence;或 consumer checkpoint + 首事件 bootstrap。未决前禁止启用 gap 阻塞。 -## Implementation Units +### 副作用原子性 -### Unit Index - -| ID | 标题 | 主要文件 | 依赖 | 对应调研项 | -|---|---|---|---|---| -| D1 | dispatcher worker 与消费者注册 | `cmd/events/main.go`、`internal/events/pg.go`、`internal/app` | 无 | P0-1 | -| D2 | 闭包桥 shadow 对账与下线 | `internal/docrevision/service.go`、`internal/docgov/engine.go`、`internal/app/app.go`、`internal/app/docnotify` | D1 | P0-1 | -| D3 | docflow 事件字段注入 + archtest 规则 | `internal/app/docflow/commands.go`、`internal/archtest` | 无 | P0-2 | -| D4 | bizdev locator 访问器移除 | `internal/bizdev/service.go`、全部调用方 | D2(避免同批改 app) | P1-3 | -| D5 | stageflow 接管与 coordinator 解绑 | `internal/app/stageflow`、`internal/coordinator/engine.go` | D4 | P1-3 | -| D6 | docgov↔docrevision 破环 | `internal/contracts/vocabulary`(新)、docgov/docrevision | 无硬依赖,建议 W2 后 | P1-4 | -| D7 | chat 收编与 intent 解绑 | `internal/chat`、`internal/app/chatflow`、`internal/intent` | D4 | P1-5/6 | -| D8 | HTTP 错误信封统一 | `internal/server`、`internal/stream/transport`、`web/src` | 独立 | P2-8 | -| D9 | PI Runner 协议 v2 | `deploy/pi-agent/runner.mjs`、`internal/taskrun`、`configs/schemas` | 独立 | P2-9 | -| D10 | 事件 producer/consumer allowlist | `module-boundaries.yaml`、`internal/archtest` | D1 | P2-10 | - -### D1. dispatcher worker 与消费者注册 - -- **Goal:** 让 outbox 事件有真实消费者在跑,副作用可恢复、可审计。 -- **Requirements:** R1、R10;KTD1、KTD8。 -- **Files:** `cmd/events/main.go`(新)、`internal/events/pg.go`(补 pool 版 Claim/Apply 封装与 dead-letter 落库)、`internal/events/dispatcher.go`(新,worker 循环)、`internal/app/app.go`(Runtime.Start 注册 worker 生命周期)。 -- **Approach:** - 1. worker:`ClaimIDs(consumer, limit)`(已有 `FOR UPDATE SKIP LOCKED`)→ 逐条调用注册的 consumer handler → `Apply` ack/fail;fail 走退避与 `MaxAttempts` 死信。 - 2. 消费者按 KTD8 顺序上线:先 `notify`(等价 docnotify.Subscriber.Handle 行为),再 `projection`,最后 `fav`。 - 3. W0 阶段消费者只写 side-effect 审计日志 + inbox receipt,不产生业务可见变化(shadow 态)。 - 4. 生产先内嵌 Runtime goroutine 托管;独立进程 `cmd/events` 在 W4 后评估。 -- **Test scenarios:** 双 worker 并发 claim 无双 lease;worker 在副作用后 ack 前崩溃重启恰好一次;一个消费者 permanent failure 不阻塞其他消费者;lease 过期后旧 worker ack 被 fencing token 拒绝;aggregate version gap 等待不跳号。 -- **Verification:** PG 集成测试(`TEST_DATABASE_URL`)全绿;dev shadow 运行日志含 delivery attempt 记录。 - -### D2. 闭包桥 shadow 对账与下线 - -- **Goal:** 删除 `docrevision.OnOutbox` 与 `docgov OnApprovedHook(s)` 的生产注册,行为由事件消费者等价提供。 -- **Requirements:** R2、R11、R12;KTD1。 -- **Files:** `internal/docrevision/service.go:38,535,682`、`internal/docgov/engine.go:39-69`、`internal/app/app.go:170-208`(bindWorkflowApprovalHook/onWorkflowDocumentApproved)、`internal/app/docnotify/subscriber.go`。 -- **Approach:** - 1. characterization:对 `document.agent_candidate_ready`、`document.downstream_impact_detected`、批准→阶段推进→`EmitStageReadyToAdvance` 三条链录制旧路径行为快照(收件人、去重键、payload)。 - 2. shadow:事件消费者与闭包同时执行,闭包结果为准,消费者结果入对账表;dev 连续 ≥3 天 diff=0。 - 3. 切换:删除闭包注册;批准→阶段推进改为消费 `document.approved` 类事件。 - 4. contract:一个发布窗口后删除 `OnOutbox` 字段与 hook 方法本体。 -- **Test scenarios:** 崩溃重启后批准通知不丢;同一事件重复投递不重复通知(inbox 幂等);对账 diff≠0 时切换被停走门拦截;删除注册后 grep 生产代码无 `AddOnApprovedHook(` 调用。 -- **Verification:** R2 PASS;架构文档 §9.2 行为门禁(worker 崩溃/lease 过期/重复投递)PASS;test report 归档。 - -### D3. docflow 事件字段注入 + archtest 规则 - -- **Goal:** envelope 的 `Environment/ProjectID` 反映真实上下文。 -- **Requirements:** R3、R10。 -- **Files:** `internal/app/docflow/commands.go:160-183`、`internal/taskrun/finalize.go`(核对同问题)、`internal/archtest/scan.go`(新增规则:production 禁止 `Environment: "dev"` 字面量出现在 envelope 构造)。 -- **Approach:** Principal/命令结构携带 environment 与 projectID(从 docgov record 取),构造 envelope 时注入;缺 projectID 时 fail closed 拒绝发布而非填 0;archtest 规则随 PR 转 block。 -- **Test scenarios:** test/main 环境发布事件 environment 正确;缺 projectID 拒绝发布;archtest 迷你仓违规样例报错。 -- **Verification:** 新规则入 `block_kinds`;单测 + archtest 双绿。 - - -### D4. bizdev locator 访问器移除 - -- **Goal:** 消灭 service locator,模块只能经端口拿能力。 -- **Requirements:** R4、R10;KTD2。 -- **Files:** `internal/bizdev/service.go:229-260`(8 个访问器)、全部调用方(编译器列出)、`internal/app/app.go`(组合根直接构造各服务)、`module-boundaries.yaml`(新增规则:生产包间禁传 `*pgxpool.Pool`/`pgx.Tx`)。 -- **Approach:** - 1. characterization 锁定 `ExecuteTask/ExecuteStage` 关键行为。 - 2. 组合根直接持有 `docgov`、`docrevision`、`projectworkflow`、`kb`、`storage.CAS` 等构造结果(部分已具备,如 `authStores`、`wfDefSvc`)。 - 3. 原子删除 8 个访问器;逐调用方改为构造注入或端口;必要时按访问器分两批(先 `Pool()/CAS()`,后服务 getter)。 - 4. archtest 新规则转 block。 -- **Test scenarios:** 编译零错误即范围闭合;`go vet`/`-short` 全绿;跨包无 `Pool()` 调用残留;archtest 迷你仓样例(跨包传 pool)被 block。 -- **Verification:** R4 PASS;受影响文件清单进 test report。 - -### D5. stageflow 接管与 coordinator 解绑 - -- **Goal:** 任务/阶段执行用例 owner 是 `app/stageflow`;coordinator 只依赖其端口与自有类型。 -- **Requirements:** R4(延伸);KTD4。 -- **Files:** `internal/app/stageflow/usecase.go`(扩展)、`internal/coordinator/engine.go:83-88`(StageTaskRunner 词汇替换)、`internal/bizdev/service.go:1071+`(编排迁移源)。 -- **Approach:** stageflow 定义本包 `TaskRunRequest/StageRunRequest` 类型;bizdev 同名类型 deprecated 一个窗口;coordinator 端口签名改用 stageflow 类型;迁移后 bizdev 保留纯领域逻辑(docduty 解析、stage_access、质量门禁)。 -- **Test scenarios:** coordinator/stageflow/bizdev 现有测试全绿无行为 diff;`go list -deps` 证明 coordinator 不再 import bizdev 实现。 -- **Verification:** coordinator import 列表收敛进 boundaries.yaml 并 block。 - -### D6. docgov↔docrevision 破环 - -- **Goal:** 两个写者模块类型层单向化。 -- **Requirements:** R5;KTD3。 -- **Files:** `internal/contracts/vocabulary`(新包,仅无行为枚举/值对象)、`internal/docgov`、`internal/docrevision`、`module-boundaries.yaml`。 -- **Approach:** 实施前先出符号级双向 import 清单(调研仅确认包级事实);值对象提升 vocabulary,带方法类型改消费者端口;随后互相移出 allowlist 转 block。 -- **Test scenarios:** archtest 对新增互相 import 立即报错;两包测试全绿。 -- **Verification:** boundaries.yaml 中两者互不出现;规则 block。 - -### D7. chat 收编与 intent 解绑 - -- **Goal:** `internal/chat` 编排并入 `app/chatflow`;`intent` 只依赖 vocabulary。 -- **Requirements:** R6。 -- **Files:** `internal/chat/*.go`、`internal/app/chatflow/`、`internal/intent/`、`module-boundaries.yaml`。 -- **Approach:** chat 剩余编排逐个挂到 chatflow 用例(U10 已完成 chatlog/contextasm 端口,本轮只搬编排);`intent→bizdev` 引用点改为注入只读端口;boundaries 收紧。 -- **Test scenarios:** chat handler 测试全绿;intent 测试不再构造 bizdev;`chat` 包消失或只剩端口。 -- **Verification:** 扇出复测:chat ≤ 端口集合,intent = vocabulary。 - -### D8. HTTP 错误信封统一 - -- **Goal:** 对外错误响应统一 typed 信封。 -- **Requirements:** R7、R11;KTD5。 -- **Files:** `internal/server`(信封类型与 handler 收敛)、`internal/stream/transport`(SSE error frame)、`web/src/api`(解析层)、ADR(状态码语义变更时)。 -- **Approach:** 信封含 `code`(typed:`VERSION_CONFLICT`/`IDEMPOTENCY_CONFLICT`/`VALIDATION`/`AUTHZ` 等)、`message`、`details?`;先 additive(新旧并存),前端按 code 分支迁移;contract 阶段删旧文本匹配;SSE error frame 同步定义。 -- **Test scenarios:** 全量路由错误响应符合信封 schema(golden 文件);前端 e2e 版本冲突/幂等冲突分支;未识别 code 的客户端 fail closed。 -- **Verification:** wire 契约测试入 CI;ADR 归档。 - - -### D9. PI Runner 协议 v2 - -- **Goal:** Runner 结果从 stdout/result.json 升级为带版本的 typed JSON。 -- **Requirements:** R8、R11;KTD6。 -- **Files:** `deploy/pi-agent/runner.mjs`(响应扩展)、`configs/schemas/pi-run-result-v1.json`(新)、`internal/taskrun`(结果解析器)、`internal/agent`(backend 适配)、`deploy/pi-agent/tests/`。 -- **Approach:** `/v1/run` 响应新增 `result_v1`(对齐 RunManifest 风格:schema_version、run_id、members[]、digest、error 分类、retryable);服务端优先 typed、失败回退 legacy;JSON Schema 校验入 CI;一个发布窗口后删 legacy 解析。 -- **Test scenarios:** stub/真实 Runner 双轨输出一致;未知 schema major fail closed;敏感字段扫描(不含 token/prompt);legacy 窗口到期后解析删除。 -- **Verification:** 真实 PI E2E(`RUN_PI_E2E=1`)在 dev 验证 v2 路径;schema 文件入仓。 - -### D10. 事件 producer/consumer allowlist - -- **Goal:** 事件名与生产者/消费者的映射成为机器可执行契约。 -- **Requirements:** R9、R10;KTD7。 -- **Files:** `.doc/system-design/module-boundaries.yaml`(新增 `event_producers:`/`event_consumers:` 段)、`internal/archtest`(扫描 envelope 构造点与消费者注册点)。 -- **Approach:** 以 D1/D2 落地后的事件集合生成初始映射(`document.agent_version_committed`→docflow、`document.agent_candidate_ready`→docrevision、批准类→docgov、finalize 类→taskrun,实施时以代码为准);archtest 校验 producer 构造点包名一致、消费者注册无映射外条目。 -- **Test scenarios:** 迷你仓新增未登记 producer/consumer 被 block;映射变更需同时改 yaml 与代码。 -- **Verification:** 规则入 block;event findings 持续为 0。 - ---- - -## Mutex 与分支安排 - -| 单元 | mutex | 建议分支 | +| 消费者 | 可见结果契约 | 崩溃窗口处理 | |---|---|---| -| D1/D3 | events + app | `feature/events-dispatcher-wiring` | -| D2 | app + docnotify | `fix/closure-bridge-offline` | -| D4 | bizdev | `refactor/bizdev-locator-removal` | -| D5 | stageflow + coordinator | `refactor/stageflow-takeover` | -| D6 | docgov + docrevision | `refactor/docgov-docrevision-uncycle` | -| D7 | chat + intent | `refactor/chat-chatflow-merge` | -| D8 | server | `feature/http-error-envelope` | -| D9 | taskrun + deploy/pi-agent | `feature/pi-runner-protocol-v2` | -| D10 | arch | `feature/event-allowlist`(可与 D2 同批) | +| notify | `event_id + recipient + notification_kind` 唯一键 | 通知写入与 inbox 同一 PG 事务;重放命中唯一键即视为成功 | +| workflow advance | `event_id + transition` 唯一 receipt;重新读取当前状态和权限 | 领域转换、outbox、inbox 同一 UoW;状态已前进时按 receipt 判定 | +| projection | `document_id + source_snapshot_id + digest` no-replace;expected head | 文件先写临时对象并 fsync,再原子提交 marker;重放核对 digest | +| FAV | 本计划不激活 | 由 FAV 审计定义 source-event/digest no-replace 契约 | -每单元独立 short-term 条目 + 独立验收报告;合回 dev 占集成槽位并跑默认冒烟(`go vet`、`go test -short`、`env-service.sh dev rebuild`、health)。 +副作用完成后、delivery ack 前必须注入崩溃。恢复后可重复调用 handler,但最终只有一个可见结果。 -## 验收标准(Definition of Done) +### 审计表 -- [ ] R1–R12 逐条有证据链接;W0–W4 各有独立 test report 归档 `.doc/test-reports/`。 -- [ ] 生产代码 grep 无 `OnOutbox =`、无 `AddOnApprovedHook(` 注册点;delivery 表可查全部事件副作用。 -- [ ] bizdev 出口仅领域类型;跨包无 `*pgxpool.Pool` 传递;coordinator 不 import bizdev 实现。 -- [ ] docgov/docrevision 互不 import;`intent` 依赖只剩 vocabulary;chat 编排全部在 chatflow。 -- [ ] HTTP 错误信封与 PI result v2 各有 schema/golden 测试与 ADR(如涉 breaking)。 -- [ ] `module-boundaries.yaml` 新规则全部 block;新 findings=0;例外均有 owner/到期日。 -- [ ] arch-check `--mode block`、`go test -short ./...`、PG 集成在 CI 全绿。 -- [ ] CURRENT / FEATURE-MATRIX / gap-matrix 与本计划状态一致。 +Additive migration 新增不可变 `event_delivery_attempts`,至少记录:event、consumer、attempt、lease owner、fencing token、started/finished、outcome、typed error code、correlation ID。`event_deliveries` 只保存当前游标和状态。 -## 风险与缓解 +attempt、dead-letter、shadow reconciliation 只存 ID、digest、typed code 和字段 allowlist 生成的摘要。禁止保存原始 payload、SQL error、绝对路径、token、prompt 或正文。 -| 风险 | 缓解 | +## 权限模型 + +| 动作 | 主体 | 必须校验 | 禁止 | +|---|---|---|---| +| 普通消费 | 每个 consumer 独立 service principal | environment、project、consumer、action;副作用前重读资源归属和当前状态 | 继承原用户会话、跨项目查询、共享万能 principal | +| shadow | ops service principal | 只写 reconciliation;不持有业务写权限 | 调用真实通知/推进/投影写端口 | +| replay dry-run | 具备事件运维 capability 的管理员 | env/project/consumer/event 范围、reason、approval ref | 无范围全库扫描、原始 payload 回显 | +| replay apply | 同一操作者能力 + 未过期 plan digest | apply 参数必须与 dry-run digest 完全相同 | 现场改范围;绕过资源当前授权 | +| 多项目/批量 replay | 两人批准 | operator 与 approver 分离;不可变审计 | 单人直接执行 | + +worker 数据库角色只授予其拥有表和调用端口所需权限。Replay 是应用用例,不提供裸 SQL 运维入口。授权失败、资源已转移、project 不存在或 env 不匹配一律 quarantine,不重试为 transient。 + +## 分波计划 + +### E0A:生产者、契约与旧数据门(D3、D10、D11) + +进入条件:认领 `events/app/arch` mutex;dispatcher 保持 off。 + +交付: + +1. 组合根构造唯一 `docflow.Commands`,向 server 注入 UoW、PGSink、environment与 project resolver;禁止 HTTP handler 临时漏注入。 +2. candidate-ready、downstream-impact、approved 三条链在领域提交同一 UoW 内写 envelope 和目标 delivery。 +3. 修正 `Environment/ProjectID`,缺值 fail closed;先清点历史 `0/dev` 记录。 +4. 补全现有 event allowlist,校验生产者、消费者、payload schema、运行 registry parity。 +5. 生成历史事件 dry-run manifest,逐条分类为 safe、repair、quarantine、expire;不删数据。 +6. 决定连续性模型,审计 `(aggregate_type, aggregate_id, aggregate_version)` 唯一约束是否允许同一版本多个事件;必要时 additive migration。 + +退出门:真实 PG 测试证明每条生产链产生正确 envelope/delivery;历史清单有审批结论;未登记事件和动态 consumer 构建失败。 + +### E0B:PG dispatcher 基座(D1) + +进入条件:E0A 全部通过;旧数据不会进入 live claim。 + +交付: + +1. 完成原子 lease、heartbeat、fenced ack/fail、backoff、dead/quarantine 和 attempt audit。 +2. 新建 `cmd/events`;每个环境单独容器、配置和 DB 连接预算。HTTP Runtime 不启动 worker。 +3. 增加 readiness(DB、registry、schema)、liveness、queue age、oldest pending、claim/ack/fail/fenced、attempt latency、dead/quarantine 指标。 +4. 默认从 Phase 1 基线起步:batch 50、lease 30s、heartbeat 10s、max attempts 12、backoff ceiling 15m;压测报告才能调整。 +5. 以 1/2/4 worker 验证无双 lease、跨 aggregate 并行、同 aggregate 串行、连接池有界和公平性。 + +退出门:PG 集成与 race 测试通过;在副作用后/ack 前、续租前后、停机 drain 等崩溃点均可恢复;资源预算和 p50/p95/p99 基线归档。 + +### E1A:notify shadow 与切换(D2-notify) + +1. 旧闭包产生 canonical outcome;shadow handler 产生 intended outcome。两者经同一规范化器比较收件人、类型、去重键、payload digest。 +2. E0A 根据经作用域校验和审批的前 7 天有效事件基线为每个 event type 预登记最小样本 N,N 不低于 30;低流量分支补确定性 replay fixture。窗口不少于 3 个完整日,并覆盖成功、授权拒绝、重复和 permanent failure。 +3. 任一未解释 diff、越权、原始 payload 泄露或样本不足均重置窗口。 +4. `off|shadow|live` 是带 revision 的集群级受控状态,不是单实例环境变量。先部署同时理解新旧模式的兼容代码,drain 并确认旧实例全部退出,再原子切换权威写者。 +5. 先把 notify 置 live;闭包 notify 路径置 off。保留显式回滚开关一个发布窗口。 + +退出门:重复投递只有一个通知;重启不丢;correlation ID 可查 producer→attempt→receipt;回滚演练通过。 + +### E1B:workflow advance shadow 与切换(D2-advance) + +批准事务必须发布 typed approved event。Consumer 在副作用前重读 document/project/workflow 状态和 service principal 权限。按 E1A 同样的 coverage gate 切换,随后移除批准 hook 注册。 + +退出门:并发批准只推进一次;陈旧或跨项目事件 quarantine;阶段推进产生的下游事件保持 causation/correlation;回滚演练通过。 + +### E1C:projection 恢复纵切(D2-projection) + +在副作用前、副作用后/ack 前、lease 过期后三个位置终止 worker并重启。授权用户通过真实页面刷新和重新打开文档,正文必须与数据库 head/snapshot digest 一致,不出现 `projection missing`,不产生重复版本。未授权用户仍得到非泄露式拒绝。 + +退出门:真实 PG + browser + worker restart 报告通过;按 correlation ID 能还原完整链路。FAV 仍为 off。 + +### E2:核心收口 + +- 删除 `OnOutbox =`、`AddOnApprovedHook(` 等生产注册点。 +- 保留 API 一个发布窗口后,再独立 contract 提交删除字段/方法本体。 +- Track E 的 release blocker 关闭;Track S/P 保持各自状态,不拖延此关闭。 + +## 实施单元明细 + +### D1:PG dispatcher 与 attempt audit + +主要文件:`deploy/postgres/goose/_event_dispatcher.sql`、`internal/events/pg.go`、`internal/events/dispatcher.go`、`cmd/events/main.go`、环境 compose/config。 + +必须交付 E0B 的状态机、进程拓扑、连接预算、指标和真实 PG 故障注入。MemoryBus 测试只作快速 characterization,不计生产验收。 + +### D2:闭包迁移 + +主要文件:`internal/docrevision/service.go`、`internal/docgov/engine.go`、`internal/app/app.go`、`internal/app/docnotify`、projection owner。 + +拆成 notify、workflow advance、projection 三个 short-term。Shadow 只写 reconciliation;每个消费者都有独立 `off|shadow|live`、停走门和回滚演练。最后一个 live 窗口结束后才能删除 hook API。 + +### D3:事件生产与作用域 + +主要文件:`internal/app/docflow/commands.go`、server 注入点、`internal/taskrun/finalize.go`。 + +修复生产入口漏注入和硬编码。事件构造必须从已授权服务端上下文与持久记录取得 environment/project;客户端字段不可信。FAV staging 调用不属于 Track E;FAV disabled 时,生产链不得写 staging、artifact 或 publication 记录。`Fav.StageAgentCommit(ctx, 0, ...)` 的真实 project scope 修复随独立 FAV 审计实施。 + +### D10:事件契约与 registry parity + +主要文件:`.doc/system-design/module-boundaries.yaml`、`internal/archtest`、registry manifest 生成/校验代码。 + +不是“新增 allowlist 段”,而是补全已有段并把 payload schema/version、合法 producer、合法 consumer、enabled state 一并校验。运行 registry 必须是静态清单的子集;FAV 条目标记 disabled。 + +### D11:历史数据与序列安全 + +主要文件:additive migration、只读 inventory/replay use case、审计报告。 + +交付历史清单、序列模型、约束审计、repair/quarantine/expire 规则。修复必须保留 old/new digest、operator、reason、approval;禁止原地无审计改值。 + +### D4:bizdev locator 最终移除 + +先加 no-new-callers 门禁,再分批把调用方迁移到消费者端口。准备工作可分批;8 个 getter 的最终删除必须在一个 contract 提交完成。规则只禁止领域/用例 API 暴露、返回或转发 pool/tx;允许 app/cmd 组合根向表属主构造器注入 pool,也允许登记属主从 opaque TxScope 解出 tx。 + +### D5:stageflow 类型归属 + +`stageflow` 定义 use-case request/result,并在 app 边界适配 bizdev 领域类型。行为方法不能机械复制成 DTO。用 mapping golden 和 characterization 锁定语义;coordinator 最终不 import bizdev。 + +### D6:移除单向 canonical 工具依赖 + +当前不是循环。把 Markdown canonicalization 与 SHA 作为一个内聚的共享行为模块(建议 `internal/doccontent`)提取,提供 golden vectors。禁止把带行为的 canonicalizer 塞入 vocabulary。完成后 docgov/docrevision 互不 import。 + +D6 不依赖 D4/D5,只受 docgov/docrevision mutex 与 U4/U5 排期约束。 + +### D7a:intent 与 bizdev 解绑 + +为 intent 注入最小只读消费者端口;单独分支、characterization、mutex 和验收。不得等待 chat 迁移。 + +### D7b:chat 编排迁入 chatflow + +逐用例迁移,保留 handler characterization。只有 D4 提供所需端口后才开始;禁止“一次搬空包”。每批收紧一条 import 规则并可独立回滚。 + +### D8:HTTP/SSE 错误信封立项约束 + +另建 remediation。公开 wire 至少为 `code/message/request_id/details?`;`message` 使用固定安全文案,`details` 按 code 维护公开字段 allowlist。不得回传 raw error、SQL、路径、凭证、prompt、正文或跨租户 ID。授权拒绝保持资源存在性不泄露。全路由、SSE 与前端迁移各有 golden/negative test。 + +### D9:PI typed-result-v1 rollout 立项约束 + +另建 remediation,不称“协议 v2”。只有 typed 字段完全缺失且握手明确声明旧 Runner 时才允许 legacy;typed 字段存在但 schema/digest/path/run_id/size 校验失败必须 fail closed,禁止降级。真实 PI E2E 覆盖取消、超时、未知 schema、超限输出、secret/prompt 注入与 downgrade attack。 + +## 迁移、部署与回滚 + +| 阶段 | 部署顺序 | 可回滚动作 | 禁止 | +|---|---|---|---| +| expand | additive migration → producer 修复 → registry | worker 保持 off;回滚应用不删表 | 旧数据直接 live claim | +| shadow | worker shadow → reconciliation | consumer 置 off;保留记录 | 执行业务副作用;清空 diff | +| switch | 部署兼容代码 → drain 旧实例 → 记录 backlog 水位 → 原子切换单 consumer live/闭包 off | 暂停 claim,核对已执行 receipt 与 backlog,再 live off + 对应闭包 on;只在兼容窗口内 | 滚动部署时同时开启两条真实写路径 | +| contract | 一发布窗口后删 hook API | 通过新提交恢复,不回滚 schema | 删除 delivery/attempt 历史 | + +回滚期间禁止同时 replay。恢复 live 前必须重新做 backlog dry-run,并确认旧 lease 已失效。Migration 只 additive;任何表/列删除另立计划。 + +## 分支、mutex 与交付件 + +| 单元 | mutex | 分支示例 | 独立报告 | +|---|---|---|---| +| D3/D10/D11 | events + app + arch | `fix/event-production-scope-contract` | E0A | +| D1 | events + deploy | `feature/events-dispatcher-pg` | E0B | +| D2-notify | events + docnotify | `fix/event-notify-cutover` | E1A | +| D2-advance | events + workflow | `fix/event-workflow-cutover` | E1B | +| D2-projection | events + docgov | `fix/event-projection-recovery` | E1C | +| D4 | bizdev | `fix/bizdev-locator-removal` | S1 | +| D5 | stageflow + coordinator | `fix/stageflow-type-ownership` | S2 | +| D6 | docgov + docrevision | `fix/doccontent-canonical-boundary` | S3 | +| D7a | intent | `fix/intent-bizdev-boundary` | S4a | +| D7b | chat + chatflow | `fix/chatflow-orchestration-boundary` | S4b | + +每行先建 short-term 并登记 CURRENT。合入 dev 与共享容器验证必须占集成槽位。 + +## 验收与停走门 + +### Track E Definition of Done + +- [ ] E1–E8 均有证据链接;E0A/E0B/E1A/E1B/E1C 各有独立 test report。 +- [ ] PG 状态机、attempt audit、1/2/4 worker、race、崩溃点、fencing、顺序、背压和连接预算通过。 +- [ ] notify/workflow/projection 分别完成 shadow coverage、切换和回滚演练;FAV 保持 disabled。 +- [ ] 生产注册代码无 `OnOutbox =`、无 `AddOnApprovedHook(`;不存在 HTTP 与 worker 双重接线。 +- [ ] 授权 UI 在三类 worker 重启点刷新/重开正文一致;未授权与跨项目负例通过。 +- [ ] replay dry-run/apply digest、service principal、字段脱敏、双人批量审批负例通过。 +- [ ] arch-check block、`go test -race` 目标包、`go test -short ./...`、PG 集成、browser/restart 全绿。 +- [ ] CURRENT、FEATURE-MATRIX、gap-matrix 只关闭事件可靠性 blocker,不把 Track S/P 写成已完成。 + +### Track S 单元 DoD + +D4–D7b 各自满足需求表中的 S1–S4、独立测试报告和 arch rule 收紧即可关闭;无需等待其他结构单元。 + +### Track P 立项门 + +D8、D9 各有独立 remediation、owner、范围、兼容矩阵、negative tests 和真实验收计划。本 Phase 2 不要求其实现完成。 + +## 风险与控制 + +| 风险 | 控制 | |---|---| -| 事件切换重复/丢通知 | KTD1 shadow 对账 + 停走门;inbox 幂等测试先行 | -| bizdev 原子删除波及面失控 | KTD2 编译器清单即范围;先 characterization;可按访问器分两批 | -| vocabulary 变垃圾桶 | KTD3 只允许无行为类型;评审含"新增类型无方法"检查 | -| 错误信封破坏旧前端 | KTD5 additive + golden + 前端同步窗口 | -| PI v2 与真实 Runner 漂移 | KTD6 双轨 + `RUN_PI_E2E=1` 真实验收,禁止 stub 冒充 | -| 与 U4/U5 到期(09-28/10-12)撞车 | D6 与 path 例外清理批次属主协调排期,避免同占 docgov/docrevision | +| shadow 因无事件源虚假通过 | E0A 先证明每条生产入口写 envelope/delivery,之后才开始计时 | +| 领域版本缺号永久阻塞 | D11 明确 event sequence 或 checkpoint/bootstrap,不把 version 当连续事件号 | +| 副作用成功但 ack 丢失 | 每消费者幂等契约;真实崩溃注入;fenced ack | +| 重试覆盖历史 | immutable attempts;delivery 只存当前状态 | +| 脏旧事件跨环境/项目执行 | inventory + quarantine;资源当前授权复核;service principal 最小权限 | +| worker 抢光连接 | 独立 pool 预算、inflight 上限、queue/backpressure 指标、压测后调参 | +| poison event 阻塞 aggregate | quarantine 需显式审批;不静默跳号;其他 aggregate 继续 | +| shadow/replay 泄密 | schema 字段 allowlist、digest 摘要、无 raw payload/error | +| FAV 拖住核心 | 只预留 disabled 契约;激活归独立 FAV 计划 | +| 结构改造扩大爆炸半径 | no-new-callers 先行;小批迁移;最终 getter 原子删除 | ## 相关链接 -- Audit/调研: [模块解耦与 IPC/RPC 交互现状评估](../ops/research/2026-09-15-module-decoupling-ipc-assessment.md) -- 上位设计: [模块化单体解耦架构](../system-design/2026-09-14-module-decoupling-architecture.md) · [ADR](../adr/2026-09-14-modular-monolith-decoupling.md) -- Phase 1: [模块解耦与文档可靠性重构计划](./2026-09-14-module-decoupling-refactor-plan.md) -- gap-matrix 行: ARCH-DECOUPLE-PHASE2 - +- [方案审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md) +- [现状调研](../ops/research/2026-09-15-module-decoupling-ipc-assessment.md) +- [Phase 1 计划](./2026-09-14-module-decoupling-refactor-plan.md) +- [模块化单体 ADR](../adr/2026-09-14-modular-monolith-decoupling.md) +- [模块边界清单](../system-design/module-boundaries.yaml) +- [Track E 任务入口](../ops/short-term/2026-09-15-durable-event-track-e.md) +- [HTTP/SSE 错误信封独立方案](./2026-09-15-http-error-envelope-remediation.md) +- [PI typed-result-v1 独立方案](./2026-09-15-pi-typed-result-v1-remediation.md) +- [发布缺口 SSOT](./gap-matrix.md) diff --git a/.doc/remediation/2026-09-15-pi-typed-result-v1-remediation.md b/.doc/remediation/2026-09-15-pi-typed-result-v1-remediation.md new file mode 100644 index 00000000..2ca7f9b4 --- /dev/null +++ b/.doc/remediation/2026-09-15-pi-typed-result-v1-remediation.md @@ -0,0 +1,58 @@ +--- +title: "PI Runner typed-result-v1 迁移方案" +status: planned +created: 2026-09-15 +updated: 2026-09-15 +--- + +# PI Runner typed-result-v1 迁移方案 + +## 背景 + +PI Runner 已有 HTTP/Bearer 传输边界,但结果仍兼容 stdout/result.json。该工作是 Runner、taskrun、agent adapter 和真实 PI E2E 的独立协议迁移,不应称“协议 v2”,也不阻塞持久事件交付。 + +## 目标 + +`/v1/run` 返回带 schema version、run identity、member 清单、digest 和 typed outcome 的 `result_v1`。服务端严格校验后持久化;legacy 只在明确的旧 Runner 能力握手下保留一个发布窗口。 + +## 契约与降级规则 + +- JSON Schema:`configs/schemas/pi-run-result-v1.json`,canonical JSON 与 digest 规则固定。 +- 结果至少包含 `schema_version`、`run_id`、`members[]`、typed success/error、retryable、content digest;member kind 使用 allowlist。 +- Runner capability/handshake 明确声明是否支持 typed result,并与响应绑定。 +- typed 字段完全缺失且握手声明旧 Runner 时,才允许 legacy。 +- typed 字段存在但 schema、digest、run ID、member path、size 或 secret 检查失败时 fail closed,禁止 fallback。 +- 文件路径必须落在本次 run 的受控输出根;拒绝绝对路径、`..`、symlink escape 和超限成员。 +- error 分类和 retryable 由 allowlist 决定,不接受模型任意字符串控制重试。 + +## 分波次计划 + +| 波次 | 内容 | 验收 | +|---|---|---| +| P0 | 盘点 legacy 输出、最大体积、取消/超时语义和真实 Runner 版本 | compatibility matrix 与 schema review | +| P1 | Runner additive 输出 `result_v1`,旧字段保留 | Runner schema/golden、大小/路径/secret 负例 | +| P2 | Go parser typed-first 严格校验,按握手决定 legacy | downgrade、未知 major、digest/run mismatch fail closed | +| P3 | dev 真实 PI 双轨对账一个发布窗口 | `RUN_PI_E2E=1`;取消、超时、成功、失败均一致 | +| P4 | 删除 legacy parser/字段 | contract test 与前滚/回滚说明归档 | + +## 高并发与安全 + +- `run_id`、request id、project/environment 和 backend identity 全链关联;禁止一个 run 接受另一个 run 的结果。 +- Parser 在分配大对象前执行 body/member 数量与尺寸上限;压缩输入限制解压比。 +- cancel 与 result 竞争使用单调终态:只允许一个 terminal outcome,晚到结果审计后丢弃。 +- Runner 输出视为不可信输入;字段、路径、MIME、编码和文本均需验证。不得把 prompt、token 或完整模型 stderr 放入结果或公开日志。 + +## 验收标准 + +- [ ] Schema/golden 在 Runner 与 Go 两侧共享并入 CI。 +- [ ] typed-present-invalid 永不 fallback;handshake downgrade attack 负例通过。 +- [ ] 并发 run/result 串线、cancel/result 竞争、超限与 path escape 负例通过。 +- [ ] 真实 PI E2E 覆盖成功、typed error、取消、超时与未知 schema。 +- [ ] legacy 删除前完成一个发布窗口对账和回滚演练。 + +## 相关链接 + +- Audit: [Phase 2 方案审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md) +- 上位路线: [Phase 2 修订计划](./2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) +- Short-term: 实施前另建 +- gap-matrix 行: 当前为工程债,不单独作为 Track E 发布 blocker diff --git a/.doc/remediation/INDEX.md b/.doc/remediation/INDEX.md index b8bad9a6..dfa056e1 100644 --- a/.doc/remediation/INDEX.md +++ b/.doc/remediation/INDEX.md @@ -4,7 +4,9 @@ |------|------| | [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-15-module-decoupling-ipc-phase2-remediation-plan.md](./2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) | 解耦 Phase 2:dispatcher 接线与闭包桥下线、bizdev locator 绞杀、docgov↔docrevision 破环、HTTP 错误信封与 PI Runner 协议 v2(调研见 [../ops/research/2026-09-15-module-decoupling-ipc-assessment.md](../ops/research/2026-09-15-module-decoupling-ipc-assessment.md)) | +| [2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md](./2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) | Phase 2 修订版:Track E 持久事件交付先行;Track S 结构解耦独立 go/no-go;HTTP 与 PI 协议另立 remediation([审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md);[调研](../ops/research/2026-09-15-module-decoupling-ipc-assessment.md)) | +| [2026-09-15-http-error-envelope-remediation.md](./2026-09-15-http-error-envelope-remediation.md) | HTTP/SSE typed error envelope、公开 details allowlist、前端 additive 迁移与安全负例 | +| [2026-09-15-pi-typed-result-v1-remediation.md](./2026-09-15-pi-typed-result-v1-remediation.md) | PI Runner typed-result-v1 schema、严格降级、并发终态与真实 PI 双轨验收 | | [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 bbacad29..010e64eb 100644 --- a/.doc/remediation/gap-matrix.md +++ b/.doc/remediation/gap-matrix.md @@ -4,7 +4,7 @@ **分支**: dev **production_ready**: no(CI 真实 PI 三工作流 E2E / 前端人工抽检未完成。`CHAT-INTENT-STATE` 双标签 busy 已 PASS。文档账本运行时 UI Round20 已 PASS,不把 fallback 报告当成完整 PI) **协作焦点**: [../ops/CURRENT.md](../ops/CURRENT.md) -**Agent 手册**: [AGENTS.md](../AGENTS.md) · [PROJECT.md](../PROJECT.md) +**Agent 手册**: [AGENTS.md](../../AGENTS.md) · [PROJECT.md](../../PROJECT.md) > Codex 2026-09-12 再审计项与本轮回环证据见下表「Codex 再审计」区段。 @@ -70,8 +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);[M1 停止扩面](../test-reports/2026-09-14-document-reliability-pilot-test-report.md);[M1 活验](../test-reports/2026-09-14-document-reliability-pilot-live-test-report.md);[M2](../test-reports/2026-09-14-document-reliability-m2-test-report.md);[M3 U8](../test-reports/2026-09-15-taskrun-runmanifest-u8-test-report.md);[M3 U9](../test-reports/2026-09-15-dataset-lifecycle-u9-test-report.md);[U10 报告](../test-reports/2026-09-15-chatlog-contextasm-u10-test-report.md);[U11–U12 报告](../test-reports/2026-09-15-app-composition-u11-u12-test-report.md) | U11–U12 已合入 `dev@bf59cce` 并 rebuild(digest `a8e3e940…`,schema 38)。path/sql_write 未到期例外保留。不整支 merge FAV。项目 143 只读 | -| ARCH-DECOUPLE-PHASE2 | 事件 outbox 只写不消费(无 dispatcher worker,消费侧仍闭包桥,崩溃丢通知且无审计);bizdev service locator(8 访问器);docgov↔docrevision 双向 import;chat/coordinator 扇出过大;docflow 事件 Environment/ProjectID 硬编码;HTTP 错误信封与 PI Runner 结果协议未约定 | OPEN | [调研报告](../ops/research/2026-09-15-module-decoupling-ipc-assessment.md);[Phase 2 方案](./2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) | W0(D1 dispatcher 接线 + D3 字段注入)为首个批准范围;W1–W4 逐波次 go/no-go。path/sql_write 例外仍归 U4/U5 owner,不在本行范围 | +| DOC-RELIABILITY-PILOT | 受控文档写路径与 PG 事件交付尚未形成生产闭环;闭包副作用在崩溃/重启后不可重试审计,错误 scope 或 projection 缺失仍可能导致刷新失败 | OPEN | [初审](../audit/2026-09-14-module-decoupling-plan-review.md);[Phase 1](./2026-09-14-module-decoupling-refactor-plan.md);[Phase 2 复核](../audit/2026-09-15-module-decoupling-phase2-plan-review.md);[Phase 2 修订](./2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md);[U11–U12 报告](../test-reports/2026-09-15-app-composition-u11-u12-test-report.md) | 下一 go/no-go 仅为 Track E E0A:生产者、event contract、历史数据与 sequence 门;dispatcher 保持 off。FAV、结构整理、HTTP/PI 协议不纳入本 blocker 关闭条件。项目 143 只读 | --- diff --git a/.doc/status/FEATURE-MATRIX.md b/.doc/status/FEATURE-MATRIX.md index 2d2a873d..70bc26c1 100644 --- a/.doc/status/FEATURE-MATRIX.md +++ b/.doc/status/FEATURE-MATRIX.md @@ -27,7 +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 与选择性模块化 | partial | [架构](../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);[M1 停止扩面](../test-reports/2026-09-14-document-reliability-pilot-test-report.md);[M1 活验](../test-reports/2026-09-14-document-reliability-pilot-live-test-report.md);[M2](../test-reports/2026-09-14-document-reliability-m2-test-report.md);[M3 U8](../test-reports/2026-09-15-taskrun-runmanifest-u8-test-report.md);[M3 U9](../test-reports/2026-09-15-dataset-lifecycle-u9-test-report.md);[U10 报告](../test-reports/2026-09-15-chatlog-contextasm-u10-test-report.md);[U11–U12 报告](../test-reports/2026-09-15-app-composition-u11-u12-test-report.md) | U5–U12 合同已签并合入 `dev@bf59cce`(U11–U12 rebuild digest `a8e3e940…`,schema 38)。path/sql_write 未到期例外保留。未整支 merge FAV | `architecture-docs` | +| 模块解耦与可靠事件 | 文档可靠性纵向试点、PG 持久事件交付与选择性模块化 | partial | [架构](../system-design/2026-09-14-module-decoupling-architecture.md);[ADR](../adr/2026-09-14-modular-monolith-decoupling.md);[Phase 1](../remediation/2026-09-14-module-decoupling-refactor-plan.md);[Phase 2 审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md);[Phase 2 修订计划](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md);[U11–U12 报告](../test-reports/2026-09-15-app-composition-u11-u12-test-report.md) | U5–U12 已合入 `dev@bf59cce`。PG dispatcher/闭包迁移仍未实施;Track E 是当前可靠性缺口。Track S 与 HTTP/PI 协议分别规划,不阻塞 Track E;FAV 保持独立审计 | `architecture-docs` | ---