Files
dsh_shenxian/docs/architecture.md
T
admin 640813e84e 覆盖网络线 S0+S1 落地:可达性单一入口 + 会合地址出 env
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 条)。
2026-09-16 15:02:52 +08:00

6.3 KiB
Raw Blame History

代码分层与依赖方向

目的:让"改一个模块"只需要读 索引 + 相关模块,而不是读全仓。 背景与实测数据:项目代码_分层范式与迭代风险评估_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)→web 2 + (root)→db 1),趁早清掉」。 实测不成立 —— 它把 src/cli.ts 误算进了"基础层"。cli.ts 是入口层①,它 import db/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/ 的层归属。