Files
Agent-System/.doc/audit/2026-09-20-module-coupling-audit.md
T
weichenandCursor 0504884e04 docs(audit): record remaining module coupling hotspots
Keep the modular-monolith baseline and name the seven places where a fault still crosses a module boundary.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 13:21:36 +00:00

9.3 KiB
Raw Blame History

title, status, created, updated, severity
title status created updated severity
模块耦合现状审计(规范与分批整改基线) done 2026-09-20 2026-09-20 high

模块耦合现状审计(规范与分批整改基线)

现象

系统已经是模块化单体,不是未治理的大泥球:module-boundaries.yaml + internal/archtest 已把 import 与事件登记清到零违规(baseline_counts.import=0、event=0,二者在 block_kinds),可靠事件由独立 cmd/events worker 经 PostgreSQL lease 消费。

故障仍会跨模块扩散的原因不在“缺总线”,而在四类残留:

  1. HTTP 适配层直接持有领域具体类型,而不是只调用用例端口。
  2. chat、app 子包与 coordinator 用子包拆分避开了 Go 编译环,逻辑上仍互相依赖。
  3. docgov 的状态、角色和错误被多个模块当成本模块词汇使用。
  4. 带到期日的 SQL/路径例外仍允许非属主写入或拼接工作区路径。

本审计只记录证据与分级,不改代码。整改批次见 分批计划。

根因分析

已有隔离基线(不要推倒)

机制 位置 现状
模块化单体 ADR 2026-09-14-modular-monolith-decoupling 不拆微服务;窄端口 + 组合根 + 领域事件
静态边界 module-boundaries.yaml import allowlist、表唯一写者、路径属主、事件生产者/消费者
架构门禁 internal/archtest、scripts/arch-check.sh --mode block import/event 零容忍
进程内端口 internal/app/*flow(例:internal/app/chatflow/ports.go) 消费者拥有窄接口;组合根注入实现
跨进程事件 internal/events + cmd/events PG outbox/inbox;dev :8088 / test :8089 / main :8090
事件契约 internal/events/registry.go StaticContracts() 11 个事件名、schema version、payload 字段 allowlist;与 YAML parity

StaticContracts 现有事件:document.agent_candidate_ready、document.agent_version_committed、document.downstream_impact_detected、document.change_report_degraded、document.approved、taskrun.completed、taskrun.cancelled、fav.artifact_staged(Enabled: false)、intent.plan.awaiting_input、intent.plan.step_changed、intent.plan.completed。

热点 1(P1):server 上帝适配层

internal/server/server.go 的 Server(约 40–80 行)同时持有 *bizdev.Service、*coordinator.CoordinatorEngine、*docgov.GovernanceEngine、*docrevision.Service、*taskrun.Manager、*projectinstance.Service、*pgxpool.Pool 等具体类型。同结构体里只有少数字段是端口(chatflow.ChatLog、chatflow.ContextAssembler、intentflow.Service、intent.Recognizer)。

生产 import 覆盖约 30 个内部包(server.go 11–34 行)。handler 直接解释领域错误,例如 internal/server/handlers.go 对 docgov.ErrDocumentBusy 的分支(426、449 行)以及 docgov.RoleAuthor(702、1291 行)。

后果:领域包改一个错误或角色常量,HTTP 层必须跟着改;pool 还让适配层有机会绕过表属主写 SQL。

热点 2(P2):chat ↔ app ↔ coordinator 逻辑环

编译环被子包拆开了,依赖方向没有:

  • internal/chat/session.go 11–13 行 import app/chatflow、app/intentflow、coordinator。
  • internal/chat/action_executor.go 10–14 行 import app/intentflow、app/stageflow、coordinator、docgov、taskrun,并持有具体引擎指针。
  • internal/app/chatflow/ports.go 又 import coordinator、ctxmgr、projectinstance(AssembleRequest 直接嵌 coordinator.UserInfo)。

chat 既是领域会话,又反向依赖用例包。用例一变,会话执行器就会编译失败,问题不会停在单一模块。

热点 3(P3):docgov 词汇泄漏

生产代码(不含本包测试)直接使用 docgov 状态/角色/错误的位置包括:

  • internal/coordinator/engine.go:ErrDocumentBusy(955)、StateDrafting(1596、1598)。
  • internal/bizdev/service.go:ErrDocumentLocked / ErrDocumentBusy(718)。
  • internal/server/handlers.go、document_revision_handlers.go、auth_helpers.go:RoleAuthor、ErrDocumentBusy。
  • internal/chat/action_executor.go:动作名字符串 "docgov.review_approve"(58、150 行),以及 docgov.StateDrafting(90)。

这些不是稳定共享词汇(internal/contracts/vocabulary 尚未承接它们),而是领域实现类型被当公共 API。

热点 4(P3):工作流定义与实例网

  • internal/workflowdef/catalog.go import projectworkflow。
  • internal/workflowdef/validator_v2.go import workflow。
  • internal/projectinstance 生产文件(store.go、service.go、gate.go、types.go 等)import workflowdef。
  • internal/workflow/runtime/orchestrator.go import projectinstance。

定义、目录、实例和运行时互相看得见对方的包。改一份工作流校验会同时碰到定义包和实例包。

热点 5(P4):巨型文件

非测试生产文件行数(2026-09-20 wc -l 口径,探索盘点):

行数 文件
1844 internal/coordinator/engine.go
1590 internal/bizdev/service.go
1395 internal/server/handlers.go
1291 internal/projectinstance/store.go
1245 internal/intent/recognizer.go
1235 internal/projectworkflow/store.go
1207 internal/docgov/engine.go
1172 internal/docrevision/service.go
1151 internal/squad/workspace.go
1028 internal/freedoc/service.go

单文件过大本身不是耦合,但它把多个用例绑在同一变更面上,评审和回滚半径变大。拆分应跟在解环之后,避免边搬边改依赖。

热点 6(P4):前端 API 层无统一客户端

web/src/api/ 约 37 个文件按域各写 fetch(),API_BASE 与鉴权头分散。错误信封、重试和 401 处理无法在一处收口,后端错误契约一变就会在多个 api 文件重复漏改。这是适配层问题,不是把前端改成 RPC。

热点 7(P2,有到期日):边界例外未清零

module-boundaries.yaml exceptions 共 21 条,baseline_counts 为 sql_write 5、path 16。

种类 条数 里程碑 到期 风险
sql_write 4 U3 2026-09-28 8 天后到期。docduty、docrevision 仍写 document_records / freeform_documents
sql_write 1 U8 2026-10-26 projectinstance 终止流程直接取消 task_runs
path 2 U4 2026-09-28 同样 8 天后到期。server 与 docrevision 仍点名 output.md
path 14 U5 2026-10-12 agent、bizdev、coordinator、docgov、storage 仍拼接工作区路径或把 data/dev 写进提示

到期后若门禁把这些 kind 升为 block 而代码未迁走,构建会失败;若继续延期而不迁,非属主写入会继续让文档/任务故障跨包扩散。本轮不改 YAML,清零归整改批次 G。

严重度

整体 high:不会单独打穿已签发布门禁(production_ready 不因本审计回退),但 P1/P2 使“问题停在单一模块”尚未成立。

级别 热点 扩散方式
P1 server 具体类型装配 领域改动直接编译失败到 HTTP
P2 chat/app/coordinator 逻辑环 会话、意图用例、协调引擎必须同改
P2 2026-09-28 到期的 sql_write/path 例外 非属主写表或读工作区文件
P3 docgov 词汇泄漏 状态机常量变成跨包协议
P3 workflow 定义/实例网 校验与运行时同改
P4 巨型文件、前端 fetch 分散 变更面大,不立刻产生跨表写入

关联整改

证据

  • 结构体与 import:internal/server/server.go、internal/chat/session.go、internal/chat/action_executor.go、internal/app/chatflow/ports.go。
  • 事件契约:internal/events/registry.go StaticContracts。
  • 例外账本:module-boundaries.yaml exceptions 与 baseline_counts(import 0 / sql_write 5 / path 16 / event 0)。
  • 盘点方式:2026-09-20 只读检索生产 import 与 docgov 符号引用;行数来自当时文件长度,后续拆分后以 wc -l 重测为准。

开放问题

  • U3/U4 例外在 2026-09-28 到期后,是先延长豁免并写原因,还是立刻占用 docgov mutex 做批次 G 的第一刀。本审计不代决,批次 G 开工前必须二选一,禁止静默改到期日。
  • internal/contracts/vocabulary 在架构文档中已定义,代码侧尚未作为 docgov 状态/角色的唯一出口。批次 C 再决定是新建该包还是把稳定枚举留在消费者端口。