merge: feature/module-decoupling-phase2-docs

This commit is contained in:
weichen
2026-09-15 08:12:47 +00:00
13 changed files with 852 additions and 10 deletions
@@ -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)
@@ -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。
@@ -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 分类。
+9
View File
@@ -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)
+7 -3
View File
@@ -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 | done |
| 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 |
@@ -67,7 +69,7 @@
| holder | phase | since | scope | queue |
|--------|-------|-------|-------|-------|
| idle | idle | 2026-09-15 | leftover-branch-cleanup 已收口 | - |
| idle | idle | 2026-09-15 | Phase 2 方案文档已合入;下一槽位留给 E0A | - |
**phase**: `idle` | `merging` | `smoke` | `e2e`
@@ -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) |
@@ -0,0 +1,149 @@
---
title: "模块解耦与 IPC/RPC 交互现状评估(复核修订版)"
status: done
created: 2026-09-15
updated: 2026-09-15
author: Cline
confidence: high
---
# 调研:模块解耦与 IPC/RPC 交互现状评估
## 结论
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 交付的前置条件。
## 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 | 独立审计轨道 | 分支未整支合入;不得作为本轮验收前置 |
不建议把单体内端口改成 RPC。那会增加序列化、延迟、部分失败与运维成本,却不消除当前 locator、授权、幂等和事件消费缺口。
## 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,不是新建段落。
### 尚未闭合
- `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 记录。
## 3. 耦合点
### 3.1 bizdev service locator
`bizdev.Service` 暴露 `Pool()/CAS()/DocGov()/DocRevision()/ProjectWorkflow()/FreeformDoc()/KB()/ContextManager()`。调用方可以绕过端口取得数据库池和其他模块实现,令签名或构造变化向多包扩散。
整改应先加 no-new-callers 门禁,再逐调用方迁移,最后在一个 contract 提交删除 8 个 getter。门禁应禁止领域/用例 API 暴露或转发 pool/tx,同时允许 app/cmd 组合根向表属主构造器注入 pool,并允许登记属主从 opaque TxScope 解出 tx。
### 3.2 docgov 到 docrevision 的单向工具依赖
生产依赖方向是 `docgov → docrevision`。直接用途集中在 Markdown canonicalization 与 SHA helper;`docrevision` 不生产 import `docgov`。
目标不是“破双向环”,而是把 canonical Markdown + digest 提取为有行为、内聚、带 golden vectors 的共享模块,例如 `internal/doccontent`。它不应进入只放无行为值对象的 vocabulary。
### 3.3 chat、coordinator 与 intent
- coordinator 仍以 `bizdev.TaskRunRequest/Result` 为词汇。
- chat 编排跨多个模块,需按用例逐批迁入 chatflow。
- intent 与 bizdev 的解绑是较小且独立的迁移,不应和 chat 大迁移绑成一个分支。
### 3.4 闭包桥与未接线事件
当前批准后的推进与通知仍可通过 `docrevision.OnOutbox`、`docgov.AddOnApprovedHook` 及组合根闭包执行。闭包在提交后的进程内调用不能提供 durable delivery、重试、dead-letter、replay 或 attempt audit。
不能直接删除闭包。正确顺序是:
1. 证明生产入口在同一 UoW 写入领域变化、envelope 和目标 delivery。
2. 对历史事件做 dry-run inventory,隔离 scope/schema/sequence 异常。
3. 完成真实 PG dispatcher。
4. shadow 只计算 intended outcome,不执行业务副作用。
5. 以时间、样本和状态分支覆盖作为切换门。
6. 按 notify、workflow advance、projection 独立切换和回滚。
## 4. 并发、数据与权限风险
### 并发
- `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。
### 历史数据
旧记录可能含 project 0、错误 environment、未知 consumer、未知 payload schema 或稀疏 aggregate version。启用 live claim 前必须生成 manifest,并分类 safe/repair/quarantine/expire。任何修复保留 old/new digest、operator、reason、approval;本计划不删历史数据。
### 权限
后台 consumer 使用按 env/consumer/action 约束的 service principal。处理事件时按 ProjectID/DocID 重读资源归属、成员/duty、workflow config revision、活动 stage 和当前状态。事件 actor 只用于审计,不能把历史用户权限带到延迟执行时。
Replay 必须是应用用例:管理员 capability、精确范围、reason/approval、dry-run plan digest、apply exact digest 和不可变审计。多项目或批量 apply 需要操作者与批准人分离。
日志、dead-letter 和 reconciliation 只能保存 ID、digest、typed error code 与字段 allowlist 生成的摘要,不能保存原始 payload、正文、SQL error、绝对路径、token 或 prompt。
## 5. 优先级建议
### 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。
### 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` |
## 建议动作
下一项只立 Track E 的 E0A:事件生产、契约与历史数据门。Dispatcher 必须保持 off,直到 PG 测试证明事件源和旧数据分类正确。完整执行计划见 [Phase 2 持久事件交付与后续解耦路线](../../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.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 建分支。
@@ -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
@@ -0,0 +1,342 @@
---
title: "模块解耦 Phase 2:持久事件交付与后续解耦路线"
status: planned
created: 2026-09-15
updated: 2026-09-15
type: refactor
owner: TBD
---
# 模块解耦 Phase 2:持久事件交付与后续解耦路线
## 决策摘要
Phase 2 的核心交付只包含持久事件链:修正事件生产、补齐 PostgreSQL 投递实现、用无副作用 shadow 对账替换闭包桥,并以授权用户刷新/重启后的正文恢复作为停走门。
结构解耦(D4–D7)是独立轨道;HTTP 错误信封与 PI Runner typed result 各自单独立项。三条轨道分别验收,不能互相阻塞关闭。
本计划不拆微服务,不引入外部消息基础设施,不合入 FAV 分支,不删除历史数据,也不授权实施、合并或部署。
## 背景与纠偏
- 上位约束:[模块化单体解耦 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,会得到虚假的零差异。
## 目标与边界
### Track E:持久事件交付(本计划核心)
目标:让 notify 与 projection 副作用在重复投递、进程崩溃、租约过期和重启后达到“至少一次投递、恰好一个可见结果”,并能从数据库追查每次 attempt。
包含 D1、D2、D3、D10、D11。Track E 完成后即可单独关闭,不等待结构或协议轨道。
### Track S:结构解耦(后续独立 go/no-go)
目标:移除 bizdev locator,收敛 stageflow/chat/intent 依赖,并消除 `docgov → docrevision` 的剩余工具依赖。包含 D4、D5、D6、D7a、D7b。
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 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
```
## 数据模型与并发协议
### Delivery 状态机
```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 阻塞。
### 副作用原子性
| 消费者 | 可见结果契约 | 崩溃窗口处理 |
|---|---|---|
| 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 契约 |
副作用完成后、delivery ack 前必须注入崩溃。恢复后可重复调用 handler,但最终只有一个可见结果。
### 审计表
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/<next>_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 不要求其实现完成。
## 风险与控制
| 风险 | 控制 |
|---|---|
| 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/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)
@@ -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
+3
View File
@@ -4,6 +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 修订版: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 |
+3 -3
View File
@@ -1,10 +1,10 @@
# 整改缺口复核矩阵(SSOT)
**更新**: 2026-09-14
**更新**: 2026-09-15
**分支**: 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,7 +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 只读 |
| 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 只读 |
---
+1 -1
View File
@@ -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` |
---