| 项 | 内容 |
|---|---|
| 状态 | 已采纳(Accepted) |
| 日期 | 2026-10-18(初版)· 2026-11-11(下沉到 docs/adr/) |
| 背景 | 拆分做完后最现实的风险不是拆不动,而是业务代码一年年渗回内核,等发现时内核已经退化成「换了个名字的 monolith」;而「不许长出领域知识」这句话写在散文里就会烂掉。 |
| 决策 | 把这条红线做成机器判据:8 条正则规则 + 5 条 go/ast 结构性规则,挂在 make check 上,CI 只执行这一条;散文与门禁的分界线是「散文不是门禁」。 |
| 后果 | 新增能力前先过「这是机制还是资产」这一问;域词污染、宿主依赖、裸 goroutine、printf 回调、双 errors 包、包间依赖方向等违规在提交前即红;声明面(policy)与执行体(archcheck)分离,宿主可复用同一套判据。 |
| 依据 | architecture-policy.yaml(声明面)+ archcheck 包(执行面);纪律与事故来源见 AGENTS.md。 |
> 一句话结论:领域知识只能住在宿主/领域包,本仓只留机制面——这条边界靠机器判,不靠自觉。
> ⚠️ 本文件正文原样搬自 README「头号红线:不得长出领域知识」一节。其中出现的少数词面是 > 被引用的反例(规则要拦什么),不是本仓的用法——本仓生产代码里一个都不许有。
边界不是文档,是门禁
「内核不许长出领域知识」这句话写在散文里就会烂掉。本仓把它做成了 8 条正则规则
- 5 条
go/ast结构性规则,全部挂在make check上,CI 只执行这一条。 散文与门禁的分界线很清楚:散文不是门禁——真有人往门面包里塞一份实现, 规则不响,结果是与上游并存的第二份实现。
> 2026-11-11 现状注记:上文的条数是初版(2026-10-18)时的快照。按本目录的 > 写作约定「搬过来时不改判据、不改数字」,正文保留初版原貌;现行口径是: > go/ast 结构判定共 6 条(门包不变 / 依赖方向 / 沙箱档位登记 / 执行路径 / > 子进程出口 harness-subprocess-egress / 未受管逃逸 > harness-ptc-unmanaged-escape),另有 1 条 policy 自校验 > (harness-policy-self-check)不属这两类。正文「另有三条」与其后表格列出的 > 4 行不一致,同属初版原貌。现行清单以仓内 architecture-policy.yaml 与 > README「工程底座」为准——那两处由门禁判,不会漂。
policy/ 只有类型别名、常量别名、单条上游转发函数 —— 多一条语句就红
modules[] 包间依赖方向逐条登记,未登记的包目录对本门禁隐形(所以必须同步登记)
rules[] 域词污染、裸 goroutine、printf 回调、双 errors 包
头号红线:不得长出领域知识
拆分做完后最现实的风险不是拆不动,而是业务代码一年年渗回内核,等发现时 harness 已经退化成「换了个名字的 monolith」。所以:
make architecture-check # 域词污染 + 依赖方向,违反即非零退出
规则源 architecture-policy.yaml,执行体是 archcheck 包 (cmd/archcheck 只是它的 CLI 装配,make architecture-check 调的就是它):
| 规则 | 级别 | 拦什么 |
|---|---|---|
harness-no-domain-vocabulary | error | 生产代码里的运维领域词(故障链/巡检/告警/本机/内存分析…) |
harness-test-fixture-domain-vocabulary | error | 测试夹具用了真实运维词面(须改合成数据)。原 15 处 warn 级债已于 2026-10-18 清偿并升级为阻断 |
harness-no-host-dependency | error | 任何对宿主仓的 import |
harness-no-domain-runtime-deps | error | 对宿主仓 / 旧门面仓 / 已退役的 agentkit 门面 的 import |
harness-no-bare-goroutine | error | 生产代码裸 go func(goroutine 内 panic 无法被外层 recover,直接杀进程) |
harness-no-printf-log-callback | error | 公开 API 里的 logf func(string, ...any)(printf 回调一旦焊进签名就是不可逆 API 债) |
harness-single-errors-package | error | stdlib "errors" 与 github.com/pkg/errors(全仓只允许 oekit/pkg/errors 一套,混用会打断 As/Is 判定链) |
harness-sandbox-execution-single-entry | error | 执行沙箱的后端不得多入口(执行路径与声明路径必须一致) |
另有三条不走正则表、由 go/ast 判定的结构性规则(判据是语法结构,逐行看文本判不出):
| 规则 | 级别 | 拦什么 | 落点 |
|---|---|---|---|
harness-facade-stays-facade | error | dispatch/policy/reminder/toolfail/sandbox 五个门包长出实现(声明新类型、给类型挂方法、函数体超过一条转发语句、调用非上游包、import 非允许依赖) | policy.yaml 的 facades 段 + archcheck/facade.go |
harness-module-dependency-direction | error | 包间依赖超出 modules[].requires 声明的方向;越界的跨仓依赖(ext: 面已登记却没在 requires 声明);以及未登记的包目录(它对本门禁隐形,任何模块都能 import 而不判违规) | policy.yaml 的 modules / dependency_direction 段 + archcheck/deps.go |
| 沙箱档位登记 | error | 执行沙箱后端未在 sandbox_providers 段登记,或登记的档位与代码事实漂移(档位声明要有人签字) | policy.yaml 的 sandbox_providers 段 + archcheck/sandbox.go |
| 执行路径 | error | 执行绕过 sandbox 登记簿(直接调裸执行入口,如 ptc.Run)⇒ 档位下限、预算可兑现性、审批门三层同时失效,而门禁全绿 | policy.yaml 的 execution_paths 段 + archcheck/execution.go |
> 依赖方向这条是 2026-10-18 补的执行体。modules 段此前把 22 个包的依赖拓扑 > 写得清清楚楚,README 也宣称门禁查「域词污染 + 依赖方向」——但执行器把 > modules 解析成 []struct{},requires 连内存都没进,于是那一半是空的, > 且声明已经开始漂移(watchdog 写 requires: [],实际 import 了 eventbus)。 > 新规则上线第一次跑就抓出 commitgate 从未登记过——它早已在 README 包表里, > 却在依赖拓扑上完全隐形。这类问题零报错、零告警、构建全绿,只有把全图摊开才看得见, > 正是它必须由机器而不是 review 来看的原因。
> 2026-10-20 补:本仓的上游依赖面也纳入这张表。 此前它只管内部包,管不到 > 「本仓用了哪个上游矩阵模块」——而上游把能力搬家的频率不低(这一轮就把 > toolgate/invoke 挪到了 guardx/invoke、obs/obsx 挪到了 llm/obsx), > 搬家在本仓门禁眼里什么都没发生。现在第一方矩阵(github.com/oagentkit + > github.com/oekit)整体登记为受管包面,各包在 requires 里声明自己实际用了 > 哪个矩阵模块(ext:github.com/oagentkit/guardx 这种粒度)。