S0(本机):新增 src/net/reachability.ts(Reachability 描述 + agentBaseUrlOf 唯一取值入口)、src/net/rendezvous.ts、docs/architecture.md(四层划分 + 依赖方向 + R1-R4 判据)、scripts/check-layering.mjs + layering-baseline.json、test/reachability.test.mjs;src/supervisor/remote-spawner.ts 与 src/web/server.ts 改为统一走 agentBaseUrlOf(),agentUrl 降级为可选旧字段(向后兼容)。 S1(本机 + 106):新增 DSHS_RENDEZVOUS_URL 出 env(src/config.ts 解析 clusterRendezvousUrl,优先级 overrides > 新变量 > DSHS_TUNNEL_TARGET 兜底 > 空),src/worker/tunnel.ts 新增 normalizeTunnelTarget()、src/worker/agent.ts 改读新配置 ⇒ 双路径并存、可零代码回滚(删掉新 env 即走旧路径)。106 已上线,健康检查 tunnel.ready=true。 package.json 增加 check:layering 脚本并接入 verify;README 登记分层文档。 验收:npm run build 通过;npm test 44/44;check:layering 无新增违规(基线 5 条)。
6.3 KiB
代码分层与依赖方向
目的:让"改一个模块"只需要读 索引 + 相关模块,而不是读全仓。 背景与实测数据:
项目代码_分层范式与迭代风险评估_20260916.md(含风险分档与"改一处读全仓"的量化评估)。 状态:2026-09-16 立。当前只是把规则写下来——本仓不需要重构,需要的是别在扩张期把方向走丢。
1. 四层与依赖方向
① 入口层 src/cli.ts · src/web/server.ts · src/web/routes/* · src/web/middleware/*
② 领域层 src/domain/* ← 【新增,当前为空】
③ 能力层 src/supervisor · db · fs · worker · net · nginx
④ 基础层 src/config.ts · crypto.ts · isolation.ts · index.ts
依赖方向:① → ② → ③ → ④ 单向,⛔ 不得回指
| # | 规则 | 判据(怎么算违反) |
|---|---|---|
| R1 | 上层可依赖下层;下层不得 import 上层 | 在 ④ 的文件里出现 from '../web/...' 之类的子目录 import = 违反 |
| R2 | 同层之间允许,但新增同层依赖前先问一句"它到底属哪一层" | 说不清的依赖,通常意味着该有一条领域层接口 |
| R3 | net/* 是能力层的一员(与 supervisor/db/fs 同级),⛔ 不是跨层的特权模块 |
net/* 不得 import web/*;web 可以依赖 net |
| R4 | src/{config,crypto,isolation,index}.ts 零业务依赖 |
这四个文件出现任何子目录 import = 违反(当前成立,见 §3) |
2. 三条工程纪律
2.1 契约前置 —— 新增模块先出接口与类型文件,再写实现。
范例:
src/net/reachability.ts+src/net/rendezvous.ts(覆盖网络 S0)。 做法 = 纯新增文件 + 零行为变化,验收靠test/reachability.test.mjs把现网真实数据写死为判据(不靠肉眼)。
2.2 模块头注释 = 索引 —— 每个模块目录的入口文件,头注释必须写明三件事:
职责 / 依赖谁 / 被谁依赖。 本仓现有头注释质量已经很高(是"读索引就能定位"的基础),缺的只是把这三项变成必须写的字段。 效果:把"读全仓"降级为"读索引 + 读相关模块"。
2.3 动手前的自查 —— 问两句:
① 这次改动涉及哪几层?② 有没有从下往上(下层 import 上层)的引用?
3. 现状实测(2026-09-16)
规模:src 58 个 .ts / 14,053 行。web 占 40%(5,593 行)⇒ 入口层事实上承担了业务层职责。
🔴 一处勘误(早期统计有误,以此处为准):
曾有结论称「有 3 处基础层反向依赖(
(root)→web2 +(root)→db1),趁早清掉」。 实测不成立 —— 它把src/cli.ts误算进了"基础层"。cli.ts是入口层①,它 importdb/fs/web属 ① → ③ 的合法方向。 证据:grep -n "from '\./\(web\|db\|fs\|supervisor\|worker\|net\)/" src/*.ts⇒ 只命中cli.ts;config.ts/crypto.ts/isolation.ts/index.ts零 import。 ⇒ 没有可清的反向依赖;R4 当前已经成立。
真正的成本不在反向依赖,在"边界模糊":
web/routes/business-plugins.ts(743 行)· skills.ts(687 行)把业务规则写在路由里,规则的另一半在 supervisor/orchestrator.ts(1,259 行)与 db/repo.ts(752 行)
⇒ 改一条规则要连读 3 个大文件 ≈ 2,500 行。这才是该被 §4 第 1 条解决的。
4. 未做(按优先级)
| 优先级 | 事项 | 为什么排这个位置 |
|---|---|---|
| P0(本文件) | 把四层规则写下来 + 在 README.md 文档表登记 |
零代码风险;扩张期一旦走丢,回头改的成本高得多 |
| P1 | 补 src/domain/*:把 web/routes 的业务规则抽出来 |
⚠️ 行为敏感重构(不是纯类型);影响面 >10 文件 ⇒ 动工前先出清单 |
| P2 | 按 §2.2 给各模块入口补「职责 / 依赖谁 / 被谁依赖」 | 批量改 >10 文件,同样先出清单 |
顺序建议:P0 ✅ → 覆盖网络 S1–S4 → P1。 P1 不阻塞覆盖网络;反过来,覆盖网络的新模块(
net/*)正好是 §2.1「契约前置」的第一个样板。
5. 可执行判据(✅ 2026-09-16 补 —— 规则从此不靠自觉)
npm run check:layering # = node scripts/check-layering.mjs
scripts/check-layering.mjs静态扫描src/**/*.ts,按 §1 四层判 R1 / R3 / R4;未归类的目录会被顶出来(逼你定层)。- ratchet(棘轮,不是豁免):存量违规登记在
scripts/layering-baseline.json,键 = 「源→目标」的边(不含行号 ⇒ 抗行号漂移)。 · 新增违规 ⇒ 退出码 1(⛔ 不得引入);· 基线内已消除项 ⇒ 打印提醒,--update-baseline收紧(只许减)。 - 已挂进
npm run verify(build之后、单测之前)。
5.1 首跑实测(2026-09-16 · 60 个 .ts)
| 层 | ①入口 | ②领域 | ③能力 | ④基础 | 未归类 |
|---|---|---|---|---|---|
| 文件数 | 24 | 0 | 32 | 4 | 0 |
现存违规 5 条,全部是 ③能力 → ①入口 的反向依赖(= §3 那句「4 组双向依赖」的内核):
| 源 | 目标 | 修法方向 |
|---|---|---|
supervisor/proxy.ts |
web/auth.ts(hashSessionToken / parseCookie) |
纯函数 ⇒ 下沉到基础层 |
supervisor/proxy.ts |
web/middleware/authn.ts(requireAuth) |
会话校验是能力不是入口 ⇒ 下沉到能力层 |
fs/local-user-fs.ts · fs/remote-user-fs.ts · fs/workspace.ts |
web/middleware/fs-guard.ts |
路径守卫 = 领域规则 ⇒ 归 src/domain/(或 src/fs/) |
⚠️ 与 §3 勘误不矛盾:§3 证伪的是「基础层(④)有 3 处反向依赖」⇒ 那部分确实 0 处;本节这 5 条是能力层→入口层(R1),是另一件事。 ⚠️ 这 5 条未整改(命中 R7:行为敏感重构且影响 >10 文件 ⇒ 先出清单)⇒ 先建基线,让"不新增"立刻生效。 📌 机制已兑现价值:脚本首跑就抓出两件我肉眼漏掉的事 —— ① 完整的 R1 检查不是 0 违规(有 5 条);② 我自己漏定了
src/nginx/的层归属。