Files
Agent-System/.doc/remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md
T
weichenandCursor 7647215513 docs(ops): land Phase 2 Track E/S/P plans onto a legal feature branch
Move the reviewed durable-event, HTTP envelope, and PI typed-result
designs off the docs/ prefix so Track E can start from latest dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 08:11:29 +00:00

23 KiB
Raw Blame History

title, status, created, updated, type, owner
title status created updated type owner
模块解耦 Phase 2:持久事件交付与后续解耦路线 planned 2026-09-15 2026-09-15 refactor TBD

模块解耦 Phase 2:持久事件交付与后续解耦路线

决策摘要

Phase 2 的核心交付只包含持久事件链:修正事件生产、补齐 PostgreSQL 投递实现、用无副作用 shadow 对账替换闭包桥,并以授权用户刷新/重启后的正文恢复作为停走门。

结构解耦(D4–D7)是独立轨道;HTTP 错误信封与 PI Runner typed result 各自单独立项。三条轨道分别验收,不能互相阻塞关闭。

本计划不拆微服务,不引入外部消息基础设施,不合入 FAV 分支,不删除历史数据,也不授权实施、合并或部署。

背景与纠偏

  • 上位约束:模块化单体解耦 ADR、架构设计、Phase 1 计划。
  • 复核结论:Phase 2 方案审阅。
  • 现状调研:IPC/RPC 现状评估。
  • 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 错误信封 与 PI typed-result-v1。

明确排除

  • 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 分别有状态、停走门、测试报告和回滚点。

目标拓扑

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 状态机

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 原子删除

相关链接