Files
Agent-System/.doc/system-design/2026-09-14-module-decoupling-architecture.md
weichenandCursor 2269cabf43 docs(arch): archive document reliability pilot plan and claim docgov
Land the revised modular-monolith scheme on the implementation branch so U1–U4 can proceed against a frozen design, and register the docgov mutex without occupying the integration slot.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 13:43:54 +00:00

21 KiB
Raw Permalink Blame History

模块化单体解耦架构

状态:设计已修订,首个文档可靠性试点待实施

决策:模块化单体解耦 ADR

实施:模块解耦整改与重构计划

1. 目标与边界

本设计把 Agent-System 收敛为模块化单体,使项目文档在刷新、服务重启、并发修改和工作区清理后仍能从稳定逻辑标识定位到正确正文。 模块边界必须同时约束依赖、事务、并发、授权、文件和最终成果;只减少 Go import 或移动目录不算解耦完成。

1.1 目标

  1. 每个业务命令有唯一应用用例、事务 owner 和幂等语义。
  2. 每张运行表和每类文件有唯一写入属主,但跨表不变量仍能在同一事务提交。
  3. HTTP、SSE、CLI、worker 和恢复工具经过同一授权决策,不信任调用方填写的身份或资源归属。
  4. workspace 只保存计算现场和可重建投影;最终文档、最终回复和 manifest 进入 FAV。
  5. 事件在崩溃、重复投递、多订阅者和水平扩容下可追踪、可重试、可隔离。
  6. 首个纵向试点直接消除受控正文刷新/重启后 404、409 和 projection missing 类故障。

1.2 非目标

  • 不拆微服务,不引入分布式事务协调器。
  • 不在本轮统一全部 HTTP 错误响应。
  • 不在本轮替换 PI Runner stdout/result 协议。
  • 不把所有历史数据永久保留,也不把生产数据复制为测试 fixture。
  • 不允许 FAV 绕过数据库授权或自动重建活动工作流、lease、pending task。

2. 目标架构

flowchart TB
  Client[Web or CLI] --> Adapter[server adapters]
  Worker[Background workers] --> App[application use cases]
  Adapter --> App
  App --> Authz[authorization policy]
  App --> UoW[UnitOfWork]
  App --> ReadModels[read use cases]
  UoW --> Owners[domain table owners]
  Owners --> PG[(PostgreSQL)]
  UoW --> Outbox[(outbox and staging)]
  Outbox --> Dispatcher[event dispatcher]
  Dispatcher --> Subscribers[idempotent subscribers]
  Subscribers --> Vault[artifactvault]
  App --> Workspace[workspace capability service]
  Workspace --> Scratch[(scratch workspace)]
  ReadModels --> PG
  ReadModels --> Vault

2.1 分层职责

层 职责 禁止
internal/server 参数解析、认证信息转 Principal、调用应用用例、序列化 HTTP/SSE SQL、事务、路径拼接、跨模块编排、后台重试
internal/app/*flow 用例编排、授权、UnitOfWork、命令幂等、事件发布 领域 SQL、物理路径、传输格式
消费者侧 ports 表达一个用例需要的最小能力 聚合所有模块方法的万能接口
领域模块 业务不变量、表/文件写入所有权、事务绑定 repository import 其他领域实现、开启不受控嵌套事务
read model 授权后的跨模块只读聚合 业务状态变更、修复式 GET 写入
基础设施 PostgreSQL、CAS、FAV、workspace、事件 dispatcher 推断业务终态或自行授予权限

2.2 契约分类

internal/contracts 不再作为所有 DTO 的公共仓库。

契约 所有位置 版本规则
HTTP/SSE wire DTO internal/server/api、internal/stream/transport 对外兼容,breaking change 走 ADR
一对一调用端口 消费者 use case 包内 随用例演化,不带 JSON tag,不自动走 ADR
公共领域事件 internal/contracts/events/<domain> 有 schema version;生产者和多个消费者共同评审
持久文件/manifest schema 所属模块,如 artifactvault、workspace major/minor 兼容;未知 major fail closed
稳定共享词汇 internal/contracts/vocabulary 仅无行为枚举和值对象;禁止依赖业务实现
错误分类 领域错误留领域包;wire 映射留 server 不把数据库/路径/第三方原始错误暴露给客户端

接口默认由消费者拥有,应用组合根只注入实现。 只有三个以上消费者确实共享相同语义时,才提升为公共端口。

3. 模块边界

模块 唯一职责与写入所有权 可依赖 禁止
app/docflow 文档读取、提交、批准、投影和归档用例;相关事务 owner 消费端口、authz、uow SQL、物理路径
app/stageflow 阶段推进和任务执行用例 消费端口、authz、uow bizdev 门面、HTTP DTO
app/chatflow 一轮对话识别、协调、消息落账和任务触发 消费端口、authz、uow 直接写共享上下文字段
docgov 文档治理状态、角色、lease、document_records 写者 自有 repository、稳定词汇 直调 docrevision 实现、读 workspace 文件
docrevision immutable snapshot/version/revision/event/report 账本写者 自有 repository 直接写 document_records、freeform_documents
freedoc 自由文档根表和发布状态写者 自有 repository 被 docrevision 反向更新
docquery 授权后文档列表、正文、版本聚合读取 只读端口、artifact read fallback 任何写入、目录扫描、授权推断
chatlog chat_* 表族读写和会话/消息/turn 查询 自有 repository prompt 装配、外部导出策略
contextasm 记忆、摘要、召回和 prompt 上下文组装 消费者侧 ChatLog/KB read ports HTTP、SQL 越界写入
intent 纯规则意图分类 vocabulary bizdev、server、IO
taskrun task run 状态机、claim/finalize、幂等写者 自有 repository server 补协议、workspace 路径
squadrun 在受限 workspace capability 内执行角色与步骤 agent/template/quote 消费端口 DB、SSE、业务授权、任意路径
workspace Workspace ID 签发、物理路径解析、projection 生命周期 fsutil、storage identity 业务终态、最终归档
artifactvault 终态 payload/manifest/receipt/tombstone 权威 独立存储、registry 活动业务关系、运行授权、workspace 扫描
notification 通知偏好、投递和状态 事件消费、repository 业务状态推进
app 唯一组合根、worker 生命周期、事件订阅注册 所有构造函数 被 server 反向装配

模块“可依赖”只表示通过端口调用,不表示允许 import 对方实现包。

4. 事务与单一写者

4.1 两种所有权

  • 表属主决定 SQL、约束和单表不变量。
  • 事务 owner决定一个业务命令需要哪些属主共同提交。

两者不能合并为“每个模块各开一个事务”。 应用用例开启 UnitOfWork,取得绑定同一 pgx.Tx 的 repository capabilities;领域服务只能使用这些 capabilities,不能 commit、rollback 或启动嵌套事务。

4.2 文档提交原子集合

一次受控文档提交必须原子包含:

  1. 校验 Principal、项目成员、duty、lease 和 expected head。
  2. 锁定 document_records 与当前 open version。
  3. 写 immutable content blob/snapshot/revision/change event。
  4. 更新版本 head 与 document_records 当前指针/哈希。
  5. 冻结应归档 payload 或不可变 blob 引用。
  6. 写领域事件和 FAV publish request。
  7. 提交后再进行 workspace 投影、通知和外部存储发布。

任一步失败,步骤 2–6 全部回滚。 投影或发布失败不会回滚业务提交,而是进入可重试队列并在 UI 暴露 degraded 状态。

4.3 事务隔离选择

场景 机制 原因
单文档编辑/批准/提交 READ COMMITTED + SELECT ... FOR UPDATE + expected head 竞争范围明确,冲突可向用户解释
同幂等键并发创建 唯一约束 + payload digest 校验 数据库决定唯一赢家
跨表项目级 duty/heal pg_advisory_xact_lock(project_id) + 固定行锁顺序 复用现有项目级互斥,事务结束自动释放
复杂跨聚合唯一不变量 SERIALIZABLE,完整事务有限重试 仅在显式证明需要时使用
outbox 多 worker claim FOR UPDATE SKIP LOCKED + lease/fencing token 提高队列吞吐,不作为普通读取语义

4.4 锁顺序

所有跨属主事务按下列顺序获得锁:

  1. project 或 workflow instance 事务级 advisory/row lock;
  2. document/freeform/chat aggregate root;
  3. version/snapshot/turn;
  4. task run;
  5. staging/outbox/delivery。

禁止在持锁期间调用 LLM、PI Runner、对象存储、HTTP、SSE 或执行大文件复制。 需要外部工作时采用 prepare → 外部执行 → compare-and-commit;commit 阶段重新验证 expected version。

4.5 超时与重试

  • 每个写事务设置独立 context deadline 和数据库 lock_timeout;初始预算由配置管理,不在 handler 硬编码。
  • 40001 与 40P01 只允许重跑完整幂等事务,采用带抖动退避和有限次数。
  • expected-version 冲突、权限拒绝、唯一键 payload 不同不是瞬时错误,不自动重试。
  • 事务重试期间不得重复发送消息、SSE、通知或对象存储写入;这些副作用只能由提交后的 outbox 驱动。

5. 并发协议

5.1 文档并发

客户端读取正文时获得 ETag = head_snapshot_id:head_revision_no。 PATCH、agent commit、approve 和 projection repair 都必须携带 expected head 或稳定 idempotency key。 旧 ETag 返回 typed VERSION_CONFLICT,响应包含当前 head 元数据但不泄露无权正文。

5.2 TaskRun 并发

Claim(idempotency_key, request_digest) 通过唯一约束保证只创建一个 run。 相同 key、相同 digest 返回既有 run;相同 key、不同 digest 返回 IDEMPOTENCY_CONFLICT。 状态迁移使用 WHERE status = expected AND version = expected_version,影响行数为零即重新读取并分类冲突。 幂等键按 environment、project、actor、action 和客户端 key 作用域化,并与规范化 request digest 绑定。 幂等记录的保留期不得短于客户端重试窗口、事件重放窗口和对应业务记录的审计期。

5.3 Workspace 并发

Workspace ID 由服务端签发并绑定 environment、project、run、storage identity 和生命周期。 同一 workspace 同时只有一个写 lease;续租返回 fencing token,所有写操作校验 token。 本地 sync.Mutex 可减少同进程竞争,但跨副本 lease 和 token 才是正确性依据。 文件写入采用同目录临时文件、校验、原子 rename 和目录同步;reader 只读取完整 projection marker。

5.4 FAV 并发

逻辑 artifact 的幂等键由终态事件、资源、payload SHA 和 policy version 决定。 对象写入使用 content-addressed key 和 no-replace 语义;blob、manifest 全部校验完成后最后写 commit marker。 重复 publisher 可共享已有 blob,但只有一个 registry 状态迁移为 verified。

5.5 查询与背压

docquery 的列表和版本历史必须分页,BatchGet 首期硬上限 100 个引用;超过上限返回 typed validation error,不自动拆成无界并发查询。 批量读取先按 Principal 和 project scope 分组授权,再使用有界并发和数据库 statement deadline。 API 对 actor/project/action 维度限流,后台 backfill 和 dispatcher 使用独立连接预算,不能耗尽在线读写连接池。 SSE 慢客户端只影响传输缓冲;不能持有业务 transaction、数据库锁或阻止事件持久化。

6. 可靠事件

6.1 事件信封

每个持久事件至少包含:

event_id
event_name
schema_version
environment
project_id
aggregate_type / aggregate_id / aggregate_version
actor_ref
correlation_id / causation_id
idempotency_key
payload_digest
occurred_at

事件不携带 secret、认证 token、Prompt、正文或任意文件路径。 大 payload 先冻结到不可变 staging,事件只保存引用和 digest。

6.2 投递模型

sequenceDiagram
  participant UC as Use case transaction
  participant DB as PostgreSQL
  participant W as Dispatcher worker
  participant S as Subscriber
  UC->>DB: business state + staging + outbox
  DB-->>UC: commit
  W->>DB: claim delivery with SKIP LOCKED
  W->>S: deliver event and fencing token
  S->>DB: idempotent side effect and inbox receipt in one transaction
  S-->>W: success or typed failure
  W->>DB: ack, retry schedule, or dead letter
  • 每个订阅者有独立 delivery 状态,不能共享单一 published_at。
  • 投递语义为至少一次;消费者以 event ID/inbox 唯一约束幂等。
  • 同一 aggregate 按 aggregate version 有序;不同 aggregate 可并行。
  • claim 按 next_attempt_at, event_id 排序并采用 aging,防止热点失败事件令旧事件永久饥饿。
  • 数据库 subscriber 的业务副作用与 inbox receipt 必须同事务;外部副作用使用业务幂等键并在回执落库前允许安全重放。
  • transient failure 指数退避;永久失败进入 dead letter,不能阻塞其他订阅者。
  • worker 认领带 lease、attempt 和 fencing token;过期 worker 无权提交 ack。
  • replay 必须指定事件范围、目标消费者、操作者和原因,并写审计记录。
  • delivery/outbox 的清理只能在所有必需消费者完成、重放窗口结束且恢复 checkpoint 已覆盖后执行;审计类事件按独立保留策略处理。

7. Workspace、账本与 FAV

7.1 三层权威

层 权威内容 清理规则
PostgreSQL 项目、权限、workflow、task、文档关系、版本账本、当前指针 依完整备份/PITR 和环境生命周期处理
workspace/state 执行现场、临时材料、可重建 projection 终态冻结和归档门禁通过后按 TTL 清理
FAV 已归档 payload、manifest、receipt、tombstone 按 retention/legal hold/delete ledger 处理

7.2 Workspace capability

跨模块 DTO 只包含:

workspace_id
project_scope
run_id
lease_fencing_token
schema_version

调用方使用 ReadArtifact(key)、OpenAtomicWriter(key)、CommitProjection(key, digest) 等受限端口。 artifact key 必须是受控相对标识,禁止绝对路径、..、空段、跨作用域 ID、symlink 和 hardlink 逃逸。

7.3 RunManifest

RunManifest 的规范字节、SHA、workspace ID、storage identity 和输出成员在 taskrun finalize 时持久化。 Evidence map[string]string 和 Gaps []string 不作为核心协议;核心字段类型化,扩展只能进入带 namespace 和 schema 的 extensions。 workspace 内 manifest 是可重建副本,不是唯一事实源。

7.4 FAV 接入

artifactvault 只接收服务端确认的终态文档、终态助手回复和必要附件。 流式 chunk、候选、Prompt、工具轨迹、修复过程、临时报告和 workspace 草稿准入率必须为零。 发布从终态事务的 staging/outbox 驱动,禁止目录扫描。

docquery 的读取优先级为:

  1. 校验 Principal 和数据库 resource→project 授权图;
  2. 读取数据库 immutable ledger/current pointer;
  3. 检查并校验 workspace projection,仅作为缓存;
  4. 正文缺失但授权图完整时,可按 feature flag 从 FAV 只读降级;
  5. 权限图缺失、vault tombstone、SHA 冲突或来源不一致时 fail closed。

GET 不得修复数据库或覆盖 head。 修复和恢复是独立命令,并产生审计记录。

8. 授权架构

8.1 Principal

Principal 由 HTTP session、服务身份或恢复凭证适配器构造,至少包含 actor ID、actor type、environment、认证来源、request/correlation ID 和明确 capabilities。 UserID、ProjectID、role 和 duty 不能直接从业务请求体覆盖 Principal。

8.2 决策模型

授权决策输入为 Principal + Action + Resource + CurrentState。

动作类别 强制条件
文档读取 项目成员/管理员 + 分类可见性 + stage/duty 范围
文档编辑/提交 写权限 + 当前 duty + 合法状态 + 有效 lease + expected head
批准/驳回 对应 action-level duty + 冻结实例配置 + 当前状态
TaskRun 执行/恢复 项目权限 + 合法 workflow/stage + 服务端解析的 backend capability
FAV 降级读取 正常读取授权已成功 + artifact scope/hash/删除状态一致
replay/backfill 运维 capability + 精确范围 + dry-run + 审计
scratch recovery promotion 系统管理员 + 数据责任人确认 + scratch identity + 冲突计划无未决项

应用用例可以在事务前做快速拒绝,但状态改变必须在取得资源锁后重新读取授权相关事实并再次判定,防止成员、duty、stage 或 lease 在检查后变化。 授权服务不可用或关系不完整时默认拒绝。 事件携带的 actor 只用于审计;订阅者产生持久或外部副作用前重新读取当前权威状态。

8.3 后端与数据最小化

调用方不能提交任意 Backend string。 应用层按环境、项目策略和输入敏感级别解析 BackendRef,agent 适配器再注入受管凭证。 凭证不得进入 DTO、日志、事件、manifest、result 文件或 FAV。 InputBundle 只包含完成任务必需的材料,并标记来源、分类、允许用途和生命周期。

9. 架构门禁

9.1 静态规则

规则 检查对象 例外
领域实现互不 import Go production packages 消费端口和稳定 vocabulary
server 不 import repository/领域实现 server production packages app facade、wire DTO
表写入唯一属主 production SQL 的 INSERT/UPDATE/DELETE/MERGE Goose、测试 fixture、批准的修复工具
工作区物理路径唯一属主 production path construction/open/write workspace/squadrun adapter
事件发布者 allowlist event name → producer package 无运行时动态注册旁路
订阅者 allowlist event name → consumer use case replay 工具需显式 capability

例外必须记录 owner、原因、范围、到期日和移除里程碑。 warn/block 不按固定“两周”切换;每条规则在对应纵向切片清零后单独转 block。

9.2 行为门禁

架构检查不能替代以下测试:

  • 同文档并发编辑、批准和 agent commit;
  • 任意事务步骤失败时全量回滚;
  • worker 崩溃、lease 过期、重复投递和多订阅者部分失败;
  • workspace 删除、换根、重启和 projection 重建;
  • FAV 降级读取与 scratch 恢复;
  • 匿名、非成员、跨用户、跨项目、错误 duty、错误 stage、授权依赖失败;
  • 真实浏览器刷新/重登/rebuild 与真实 PI 主链。

10. 可观测性与容量

10.1 必须指标

  • 数据库事务时长、锁等待、deadlock、serialization retry、版本冲突率。
  • outbox pending/oldest age、delivery attempts、lease steal、dead letter 数。
  • projection missing/mismatch/repair、FAV degraded read、vault verification lag。
  • 授权 deny 按 action/reason 计数,不记录正文或敏感资源名。
  • workspace active lease、过期清理、fencing rejection、storage identity mismatch。

10.2 高并发基线

首个试点必须建立并保存基线,而不是直接承诺未经测量的容量数字:

  • 同一文档热点写入与不同文档并行写入分开压测。
  • 1、2、4 个 app 实例下验证进程内锁不影响正确性。
  • 1、2、4 个 dispatcher 下验证无重复可见副作用、无饥饿和有界积压。
  • p50/p95/p99 延迟、锁等待、事务重试、CPU/连接池和队列年龄进入 test report。
  • 正确性门禁固定;吞吐阈值在基线评审后写入环境配置与发布 SLO,不能靠降低一致性通过。

11. 迁移与兼容

采用 expand → migrate → contract:

  1. 新增兼容 schema、端口和读路径,不改变旧写入。
  2. 新写路径 shadow/dual-read 验证,禁止无审计双写。
  3. 按 checkpoint 幂等回填,异常进入 quarantine。
  4. 切主写者,保留旧 reader 一个明确发布窗口。
  5. 验证真实刷新/restart/rebuild、FAV coverage 和回滚。
  6. 删除旧路径前再次审计引用和数据差异。

旧 workspace 文件缺少 schema_ver 时按只读 legacy-v0 解析。 首次受控 Resume 先校验并原子升级到 v1,保留可回滚备份;未知 major 版本 fail closed。

回滚只保证回到最近兼容点。 已被下游里程碑消费的 schema 或契约必须按逆依赖顺序回滚,不能单独 revert 早期提交。

12. 首个交付边界

首个里程碑只覆盖受控文档可靠性:

flowchart TB
  Read[Authorized content read] --> Ledger[Immutable ledger]
  Ledger --> Projection{Projection aligned?}
  Projection -->|yes| Return[Return canonical content]
  Projection -->|no| Vault{Authorized FAV fallback enabled?}
  Vault -->|yes| Degraded[Return verified degraded content]
  Vault -->|no| Conflict[Typed consistency error]
  Commit[Edit or agent commit] --> Tx[Single UnitOfWork]
  Tx --> Ledger
  Tx --> Root[Document root pointer]
  Tx --> Stage[Artifact staging]
  Tx --> Event[Outbox]

只有这个切片完成刷新、重启、并发、权限和恢复验收后,才启动其他包的物理拆分。

13. 后续扩展顺序

  1. 文档可靠性试点。
  2. taskrun finalize、RunManifest 持久化和 FAV 集成。
  3. 逐个事件替换闭包桥。
  4. chatlog/contextasm 原地端口化,再按证据移动包。
  5. squadrun capability 化和 backend policy。
  6. app 组合根迁移。
  7. UserProjectContext 增量收权。
  8. server/SSE 收口。
  9. 错误信封与 PI Runner 协议作为独立项目实施。

详细依赖、文件和测试场景见整改计划。