From 0504884e044de099b59bd9d729fdea0d46d63386 Mon Sep 17 00:00:00 2001 From: weichen Date: Sun, 20 Sep 2026 13:21:36 +0000 Subject: [PATCH] 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 --- .../audit/2026-09-20-module-coupling-audit.md | 142 ++++++++++++++++++ .doc/audit/INDEX.md | 8 + 2 files changed, 150 insertions(+) create mode 100644 .doc/audit/2026-09-20-module-coupling-audit.md diff --git a/.doc/audit/2026-09-20-module-coupling-audit.md b/.doc/audit/2026-09-20-module-coupling-audit.md new file mode 100644 index 00000000..7cae646d --- /dev/null +++ b/.doc/audit/2026-09-20-module-coupling-audit.md @@ -0,0 +1,142 @@ +--- +title: "模块耦合现状审计(规范与分批整改基线)" +status: done +created: 2026-09-20 +updated: 2026-09-20 +severity: 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/路径例外仍允许非属主写入或拼接工作区路径。 + +本审计只记录证据与分级,不改代码。整改批次见 [分批计划](../remediation/2026-09-20-module-decoupling-batch-plan.md)。 + +## 根因分析 + +### 已有隔离基线(不要推倒) + +| 机制 | 位置 | 现状 | +|------|------|------| +| 模块化单体 ADR | [2026-09-14-modular-monolith-decoupling](../adr/2026-09-14-modular-monolith-decoupling.md) | 不拆微服务;窄端口 + 组合根 + 领域事件 | +| 静态边界 | [module-boundaries.yaml](../system-design/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](../system-design/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 分散 | 变更面大,不立刻产生跨表写入 | + +## 关联整改 + +- Remediation: [2026-09-20-module-decoupling-batch-plan.md](../remediation/2026-09-20-module-decoupling-batch-plan.md) +- 开发规范: [2026-09-20-module-development-standards.md](../system-design/2026-09-20-module-development-standards.md) +- 上位约束(不重复制定): [架构](../system-design/2026-09-14-module-decoupling-architecture.md)、[Phase 2](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md)、[影响面守则 §2](../remediation/2026-09-16-partial-deferred-completion-plan.md) + +## 证据 + +- 结构体与 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 再决定是新建该包还是把稳定枚举留在消费者端口。 diff --git a/.doc/audit/INDEX.md b/.doc/audit/INDEX.md index dd355ad4..f91f8266 100644 --- a/.doc/audit/INDEX.md +++ b/.doc/audit/INDEX.md @@ -13,6 +13,14 @@ ## 核心文档(按阅读顺序推荐) +### 模块耦合现状(2026-09-20) + +**文件**: [2026-09-20-module-coupling-audit.md](./2026-09-20-module-coupling-audit.md) + +**结论**: 模块化单体与事件总线已在;残留扩散点是 server 具体类型装配、chat/app/coordinator 逻辑环、docgov 词汇泄漏,以及 2026-09-28 到期的 sql_write/path 例外。不改 `production_ready`。 + +**整改**: [分批计划](../remediation/2026-09-20-module-decoupling-batch-plan.md) · [开发规范](../system-design/2026-09-20-module-development-standards.md) + ### 系统全量体检(2026-09-20) **文件**: [2026-09-20-system-triage-audit.md](./2026-09-20-system-triage-audit.md)