Files
Agent-System/.doc/adr/2026-09-14-modular-monolith-decoupling.md

5.9 KiB
Raw Permalink Blame History

ADR:以纵向绞杀方式实施模块化单体解耦

状态

已接受(2026-09-14 提议;2026-09-18 确认——Phase 1 纵向绞杀与 Phase 2 持久事件交付已落地并验收,见 gap-matrix DOC-RELIABILITY-PILOT PASS、Phase 1 计划、Phase 2 修订计划、Track E 复盘)

背景

internal/server、internal/bizdev、共享 UserProjectContext、工作区路径和多表写入形成隐式耦合。 历史受控正文 404/409、刷新后投影缺失和任务终态不完整说明,单纯搬包不会修复事实源、事务与恢复边界。

决策

  1. 保持模块化单体:不拆微服务;进程内调用使用窄端口,跨进程可靠性只用于持久事件和 FAV。
  2. 纵向绞杀:先在现有包边界插入端口和适配器,迁完一条业务链并验证后再移动或重命名包;禁止“先全量搬包、以后再接口化”。
  3. 契约分层:外部 API DTO 归 server/api;持久 schema、公共事件和稳定共享词汇进入按领域划分的 contracts;一对一内部接口由消费者拥有。
  4. 命令与事务同属一个应用用例:一个业务命令只有一个事务 owner;跨表属主通过同一不透明事务作用域协作,业务状态、账本、当前指针、staging 和 outbox 原子提交。
  5. 单一写者不等于单独事务:表属主控制 SQL 和不变量;应用 UnitOfWork 控制跨属主提交边界。属主端口不得私自嵌套提交。
  6. 并发正确性以数据库为准:用户编辑使用乐观版本/ETag;状态转换使用行锁和条件更新;项目级不变量可使用事务级 advisory lock;进程内 mutex 只能优化,不能作为多副本正确性依据。
  7. 锁顺序固定:同一事务按 project/workflow instance → document record → document version/snapshot → task run → outbox/staging 的顺序取锁;事务内禁止 LLM、对象存储、网络调用和长文件 I/O。
  8. 可靠事件采用 transactional outbox:事件按至少一次投递设计,具有稳定 ID、schema version、环境/项目范围、聚合序号、因果链和幂等键;逐消费者记录 delivery、重试、租约和死信。
  9. 工作区是可清理投影:跨模块只传不可伪造 Workspace ID 和受限 artifact key;物理路径只在 workspace/squadrun 适配器内解析和校验。
  10. 最终成果进入 FAV:PostgreSQL 保存业务关系和授权,FAV 只保存已确认终态的精确 payload、manifest、回执和删除状态,workspace 不承担最终成果事实源。
  11. 授权逐动作校验:认证适配器构造不可由请求覆盖的 Principal;HTTP、CLI、后台订阅者和恢复流程都按 action/resource/current state fail closed。
  12. 先完成文档可靠性试点:只有受控正文刷新、重启、并发提交、投影缺失降级和授权负例通过后,才批准 chat、squad、编排层和传输层扩展。
  13. 迁移采用 expand/migrate/contract:允许新增 Goose migration;不得修改已发布迁移;回滚只保证回到最近的兼容阶段,不能任意独立 revert 早期依赖。
  14. 协议改造独立立项:统一错误信封和 PI Runner result.json 不作为核心模块解耦前置条件;它们使用独立兼容与 E2E 验收。

并发与失败语义

  • 普通聚合状态转换默认 READ COMMITTED + 明确行锁/条件更新;涉及跨聚合唯一不变量时才使用 Serializable,并对 40001/40P01 重跑完整事务。
  • outbox worker 使用小批次 FOR UPDATE SKIP LOCKED 认领,处理发生在事务外;完成、续租和失败回填使用 fencing token 防止过期 worker 覆盖新 owner。
  • 每个写命令必须同时有 idempotency key、expected version 或两者;相同 key 不同 payload 返回 typed conflict。
  • 锁等待、事务时长、重试次数、队列积压和死信必须可观测;超过预算时失败返回,不无限等待。

授权与恢复语义

  • Principal 至少携带 actor、环境、认证来源和请求关联 ID;项目、会话、文档、阶段与 duty 从数据库权威关系重新解析。
  • 持久事件中的 actor 只用于审计,不能替代订阅者对当前资源状态的授权检查。
  • FAV 降级读取要求运行库授权图完整;授权图缺失时禁止面向用户返回内容。
  • 恢复只允许进入 scratch identity;promotion 需要系统管理员与数据责任人确认,并通过各属主端口提交,禁止恢复 CLI 直写运行表。

后果

  • 首个实现里程碑的改动面小于原 Wave 1–6,但会先增加 UnitOfWork、事件投递和授权基础设施。
  • “单表唯一写者”检查改为生产 SQL 写权限检查,并允许 migration、测试和经批准运维脚本存在表名。
  • feature/final-artifact-vault 是候选依赖,不直接假定可合入;必须先按当前 dev 重放测试、迁移和权限审计。
  • 包物理移动、错误信封、SSE 和 PI Runner 协议可延后,不阻塞文档一致性整改。

替代方案

  • 一次性横向搬包:拒绝。冲突面大,不能先证明用户故障减少。
  • 仅增加 arch-check:拒绝。门禁不能替代事务、锁和授权契约。
  • 拆微服务:拒绝。当前问题是模块边界和数据一致性,不需要引入分布式事务与跨服务部署成本。
  • 继续把 workspace 作为恢复来源:拒绝。工作区属于可清理计算现场,不能成为最终事实源。

相关文档