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