Files
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

102 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 代码分层与依赖方向
> **目的**:让"改一个模块"只需要读 **索引 + 相关模块**,而不是读全仓。
> **背景与实测数据**:`项目代码_分层范式与迭代风险评估_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 补 —— 规则从此**不靠自觉**)
```bash
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/` 的层归属。