Files
Agent-System/.doc/system-design/2026-09-20-module-development-standards.md
T
weichen fed87ad14b feat(governance): enforce managed worktrees and scoped agent operations
Consolidate approved policies and add local lease, document CAS, commit, candidate and environment gates. Preserve conservative recovery and cleanup limits.
2026-09-21 14:41:40 +00:00

9.9 KiB
Raw Blame History

title, status, created, updated
title status created updated
模块开发规范:通信契约、接口与故障隔离 active 2026-09-20 2026-09-20

模块开发规范:通信契约、接口与故障隔离

本文件是开发者入口,不是新的架构 SSOT。 决策仍以 模块化单体 ADR 为准。 机器强制仍以 module-boundaries.yaml 与 ./scripts/arch-check.sh --mode block 为准。 影响面守则不在此重复,见 partial-deferred 计划 §2。

2026-09-20 确认:继续模块化单体。模块之间用进程内窄端口或 PostgreSQL 领域事件通信。不新增通用 IPC/RPC,不拆微服务。跨进程只允许两类既有边界:HTTP(Web↔server、server↔PI Runner)和 cmd/events 经 PG lease 消费。

1. 问题必须停在本模块

一次故障只允许表现为下面之一,不允许连带改第二个领域包才能编译或才能回滚数据:

故障位置 允许看见的影响 禁止
领域不变量 本包返回 typed error;同一 UnitOfWork 回滚 别的领域包 import 本包实现后自行改表
用例编排 组合根换一个端口实现 handler 直接 new 领域服务或拿 *pgxpool.Pool 写别人的表
异步副作用 该消费者 delivery 重试、死信或 quarantine 一个消费者失败卡住其他消费者,或在持锁时调用 LLM/HTTP
对外协议 server 把领域错误映射成固定错误信封 把 SQL、路径、prompt、正文放进 HTTP message

静态底线已经在门禁里:领域实现互不 import(消费端口和稳定词汇除外)、每张表一个写者、每个事件名有生产者/消费者 allowlist。

2. 三种通信契约

只允许这三种。新增第四种(包括模块间 gRPC、进程内全局 bus 单例、跨包直接调未导出帮助函数)先写 ADR。

2.1 进程内窄端口

消费者拥有接口,组合根注入实现。参照 internal/app/chatflow/ports.go:接口只表达这一个用例要的能力。

规则 说明
谁定义 调用方包,不放在被调用的领域包里“顺便导出”
宽度 一个用例一组方法。三个以上消费者语义相同时才提升为公共端口
禁止万能接口 不得出现聚合所有模块方法的 Store / Engine 门面
禁止传输细节 端口结构体不带 JSON tag,不返回 http.Response、SQL 行、pgx.Tx
身份 Principal 只来自已认证会话或服务身份,不从请求体覆盖 UserID/ProjectID
装配 只有 internal/app 与 cmd/* 构造实现。internal/server 只拿用例或端口

2.2 领域事件(跨进程总线)

生产路径是 PostgreSQL outbox + cmd/events worker(FOR UPDATE SKIP LOCKED),不是内存 MemoryBus。MemoryBus 只用于 dispatcher 特征测试。

契约只有一处运行时副本:internal/events/registry.go 的 StaticContracts(),并且必须与 YAML 的 event 段 parity。未登记的事件名或消费者在构建期失败。fav.artifact_staged 保持 Enabled: false,直到独立 FAV 计划放量。

信封至少包含:event_id、event_name、schema_version、environment、project_id、aggregate 标识与版本、actor_ref、correlation_id / causation_id、idempotency_key、payload_digest、occurred_at。

事件不得携带 secret、token、prompt、正文或绝对路径。大 payload 先冻结为不可变引用,事件只留 digest。

投递是至少一次。消费者用唯一键或 inbox receipt 保证对外只看见一次结果。副作用与 receipt 能进同一数据库事务的必须同事务。永久失败进 dead letter,不得阻塞其他订阅者。

2.3 对外 wire DTO

HTTP/SSE 的请求响应只存在于 internal/server(及 internal/stream 的传输类型)。领域包不 import server。

错误信封沿用已合入的 typed envelope:code / message / request_id / 可选 details。message 用固定安全文案;details 只允许该 code 的公开字段。授权拒绝不得泄露资源是否存在。

PI Runner 继续 typed-result-v1。typed 字段存在但校验失败必须 fail closed,不得降级成旧解析。

3. 输入与输出

3.1 新增或修改端口时的 checklist

  • 接口放在消费者用例包,方法名是业务动作而不是表操作。
  • 入参是该动作的命令/查询结构;出参是结果或本用例的 typed error。
  • 无 JSON tag,无 pgx 类型,无物理路径,无另一领域包的具体 struct(稳定词汇除外)。
  • 错误值留在领域包或用例包;server 只做 errors.Is 到 HTTP code 的映射,映射表集中在一处,不散落在每个 handler。
  • 组合根测试用假实现替换端口,不 import 对方的 SQL。

3.2 新增事件时的 checklist

  • event_name 形如 <aggregate>.<fact>,表达已发生的事实,不是命令。
  • schema_version 从 1 起;破坏性字段变更升版本,未知 major fail closed。
  • payload 字段写入 StaticContracts().PayloadFields,并同步 YAML allowlist。
  • 生产者、消费者包名写入同一条契约;运行时注册必须是静态清单的子集。
  • 领域提交、envelope、delivery 行在同一个 UnitOfWork。缺 environment 或 project_id 时 fail closed,禁止写死 "dev" / 0。
  • 消费者有幂等键;重复投递测试证明只有一个可见结果。
  • ./scripts/arch-check.sh --mode block 与 registry parity 测试通过。

3.3 稳定词汇

无行为的枚举和值对象才可以进共享词汇。带解析、哈希、路径拼接的函数留在所属模块(现例:internal/doccontent),禁止塞进 vocabulary。

在批次 C 完成前,不要新增对 docgov.State* / docgov.Role* / docgov.Err* 的跨包引用。已有引用记在 耦合审计,按整改批次收敛。

4. 故障隔离红线

  1. 表唯一写者。INSERT/UPDATE/DELETE 只能出现在 YAML table_owners 指定的包。跨表原子性由用例打开的 UnitOfWork 提供,不是每个模块各开事务。
  2. 解包事务的包有名单。只有登记过的属主可以从 uow.TxScope 取出 pgx.Tx。server 与 chat 不在此列。
  3. 领域实现互不 import。需要对方能力时定义端口,由 internal/app 注入。用子包绕开编译环但保留反向 import,视为未解耦。
  4. 持锁期间不做外部 IO。LLM、PI Runner、对象存储、HTTP、SSE、大文件复制放在提交之后,由 outbox 驱动。
  5. 事件消费者 fail closed。授权失败、环境不符、schema 未知 → quarantine,不当成瞬时错误重试。FAV 消费者保持禁用。
  6. HTTP 不修投影。GET 不得写数据库或覆盖 head。修复是独立命令并写审计。
  7. 单文件变更面。新生产 Go 文件不超过 800 行。已有超过 1000 行的文件(见审计热点 5)只允许在对应整改批次里拆,不借无关功能 PR 顺手重排。

5. 新增功能怎么落

按顺序做。跳过门禁视为未完成,即使功能在本地可用。

5.1 新模块

  1. 在 short-term 写明唯一写入所有权(表、文件或无)。
  2. 在 module-boundaries.yaml 的 imports 里只放允许依赖的包。默认可依赖 config、稳定词汇、本模块 repository。不可依赖另一个领域实现。
  3. 组合根负责构造。server 若需要该能力,只能通过已有用例端口。
  4. FEATURE-MATRIX 加行,mutex 与 CURRENT 认领一致。

5.2 新表

  1. Goose 迁移只 additive。
  2. YAML table_owners 登记唯一写者。
  3. 跨表不变量写在用例的一个 UnitOfWork 里,并写明锁顺序:project/workflow → 聚合根 → version/turn → task run → outbox。
  4. 不允许 server handler 出现该表的 SQL。

5.3 新事件或新消费者

走 §3.2。消费者状态是 off | shadow | live,按环境 + 消费者配置,不靠单个进程的环境变量偷偷打开。shadow 只写对账,不写业务副作用。FAV 不在此流程里放量。

5.4 改已有模块

  1. 先看 耦合审计 该包是否已有 P1/P2。有则优先缩小依赖,而不是再增加一个具体类型字段。
  2. 影响面守则(partial-deferred §2.2)仍然有效:深模块、禁止新增跨模块 import、schema additive、一能力一 flag、事件双登记、改声明范围要留 before/after。
  3. 例外若必须保留,写入 YAML exceptions 时带 owner、原因、范围、到期日和移除里程碑。禁止无到期日的例外。本轮不新增例外来掩盖热点 1–4。

6. 与现有文档的分工

问题 读这里 不要在本文件找
为什么是模块化单体 ADR 实施波次
事务、锁、FAV、授权模型 架构设计 今日残留热点
事件状态机与 Track E/S/P Phase 2 计划 新批次 A–G
改功能时如何收敛影响面 partial-deferred §2 端口字段清单
今天还有哪些耦合 耦合审计 发布门禁
接下来哪一批先改 分批整改 代码

7. 提交前自检

./scripts/arch-check.sh --mode block
go vet ./...
go test -short ./...

改了事件契约时,额外确认 TestRegistryParityWithYAML 在跑到的包里通过。普通工程文档变更按 验证契约 检查差异、链接和状态;不运行无关 Go/PI 套件。运行模板、提示词与代码契约变更按受影响范围运行上述门禁,防止索引或生成物被误改。