Skip to content

Commit fefdde1

Browse files
authored
fix(layering): 纠正分层门禁的错位判据,撤销错误的 13 项债务基线 (#2)
PR #1 的门禁按「命名像领域模块就应下移到 adapters」判定,这是错的。 本仓 `crates/codeguard-cli/tests/crate_boundaries.rs` 早已用测试强制: codeguard-adapters 仅可依赖 core / serde / serde_json / roxmltree / toml / sha2 codeguard-runtime 承担进程执行 codeguard-cli 编排,可依赖以上三者 据此,`*_probe` 探针调用 `run_process_recorded`、需要 runtime,而 adapters 被 明确禁止依赖 runtime——**探针留在 cli 是正确的,把它们"下移"会被 crate_boundaries 直接判失败**。PR #1 的基线把 9 个探针登记为债务,等于在 引导后来者走一条注定被本仓测试拒绝的路。 实测 13 项登记项的真实依赖: - 10 项依赖 codeguard_runtime → 依边界不得进 adapters - module_graph / cargo_module_graph 虽不依赖 runtime,但依赖 cli 本地的 crate::discovery,不能单独下移 → 真正错位者为 0。 本 PR 改用「依赖」而非「命名」判定:只有既不需要 runtime、也不引用 cli 本地 类型的领域模块才算错位,并删除 layer-baseline.json(纠正后无已知债务)。 双向负向验证: - 新增自足领域模型 → 失败(正确捕获) - 新增依赖 runtime 的探针 → 放行(不再误报) crates/ 零改动;测试 997 通过 / 0 失败。
1 parent 2ace496 commit fefdde1

2 files changed

Lines changed: 67 additions & 135 deletions

File tree

‎layer-baseline.json‎

Lines changed: 0 additions & 19 deletions
This file was deleted.

‎scripts/check_layering.py‎

Lines changed: 67 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
#!/usr/bin/env python3
2-
"""分层门禁:阻止领域逻辑继续堆积在 codeguard-cli 这个二进制 crate。
2+
"""分层门禁:只标记「真正错位」在 codeguard-cli 中的领域模块。
33
4-
背景:codeguard 把执行内核切成四个 crate,设计意图是
5-
core(契约与领域类型)← runtime(执行)← adapters(工具适配)← cli(组装)
6-
但实际上一度出现倒挂——adapters 已有的工具诊断类型,其身份映射逻辑却住在 cli,
7-
让 cli 膨胀到远大于 core,且无法单独测试领域规则。
4+
权威边界不是本文档,而是 `crates/codeguard-cli/tests/crate_boundaries.rs`——
5+
它以测试形式强制以下依赖方向:
86
9-
本门禁不试图一次性搬完(那会让 diff 无法审阅),而是把「倒挂」变成显式债务:
10-
cli 中仍然存在的领域形态模块被登记在 layer-baseline.json 里,数量只减不增。
11-
新增领域模块而不同步下移,会让本检查失败并指出应该去哪个 crate。
7+
codeguard-core 仅依赖 serde
8+
codeguard-runtime 依赖 core / libc / ring(进程执行)
9+
codeguard-adapters 依赖 core / serde / serde_json / roxmltree / toml / sha2(纯解析与配置)
10+
codeguard-cli 依赖以上三者(编排)
11+
12+
由此得出两条常被搞反的事实:
13+
14+
1. 探针(*_probe)留在 cli 是正确的。它们调用 run_process_recorded,需要
15+
codeguard-runtime;而 adapters 被明确禁止依赖 runtime。把探针「下移」到
16+
adapters 会被 crate_boundaries 直接判失败。
17+
2. 能否下移取决于依赖而非名字。一个模块若既不需要 runtime、又不依赖 cli 本地
18+
类型,它才是纯粹被放错位置的领域逻辑。
19+
20+
本门禁只检查第 2 类:它不做搬迁,只保证错位不会悄悄增加。
1221
1322
用法:
14-
python3 scripts/check_layering.py # 检查
15-
python3 scripts/check_layering.py --explain # 打印每个模块的归属理由
16-
python3 scripts/check_layering.py --accept <name> # 登记一个既有模块(带理由)
23+
python3 scripts/check_layering.py # 检查
24+
python3 scripts/check_layering.py --explain # 说明判定依据
1725
"""
1826
from __future__ import annotations
1927

@@ -27,132 +35,75 @@
2735
CLI_SRC = ROOT / "crates" / "codeguard-cli" / "src"
2836
BASELINE = ROOT / "layer-baseline.json"
2937

30-
# 领域形态:这些后缀的模块在 cli 中几乎都属于"工具适配 / 报告构造 / 领域类型",
31-
# 而不是命令行管道。cli 只应保留参数解析、终端输出与编排。
3238
DOMAIN_SHAPED = (
33-
"_probe",
34-
"_identity",
35-
"_observation",
36-
"_diagnostic",
37-
"_parsed",
38-
"_config",
39-
"_graph",
40-
"_state",
41-
"_model",
39+
"_probe", "_identity", "_observation", "_diagnostic", "_parsed",
40+
"_config", "_graph", "_state", "_model", "_record", "_index",
4241
)
42+
CLI_ALLOWED = ("_command", "_arguments", "_preview", "_brief", "_feedback")
43+
CLI_LOCAL = re.compile(r'\b(?:use\s+)?crate::[a-z_][a-z0-9_]*')
44+
4345

44-
# 每个后缀应归属的 crate,用于给出可执行的迁移建议。
45-
HOME_FOR = {
46-
"_probe": "codeguard-adapters",
47-
"_identity": "codeguard-adapters",
48-
"_diagnostic": "codeguard-adapters",
49-
"_parsed": "codeguard-adapters",
50-
"_config": "codeguard-adapters",
51-
"_observation": "codeguard-adapters",
52-
"_graph": "codeguard-core",
53-
"_state": "codeguard-core",
54-
"_model": "codeguard-core",
55-
}
56-
57-
# 命令入口与管道是 cli 的正当职责,不计入债务。
58-
CLI_ALLOWED = ("_command", "_arguments", "_preview", "_brief", "_feedback", "main.rs", "lib.rs")
59-
60-
61-
def domain_modules() -> dict[str, str]:
62-
"""返回 {模块名: 应归属 crate},只含 cli 中本应下移的领域模块。"""
63-
found: dict[str, str] = {}
46+
def audit() -> list[dict]:
47+
"""返回真正错位的模块:命名像领域模块,且既不引用 runtime 也不引用 cli 本地类型。"""
48+
out: list[dict] = []
6449
for path in sorted(CLI_SRC.rglob("*.rs")):
6550
rel = path.relative_to(CLI_SRC).as_posix()
66-
if rel in CLI_ALLOWED or any(rel.endswith(a) for a in CLI_ALLOWED):
51+
stem = path.stem
52+
if stem in ("main", "lib") or any(stem.endswith(a) for a in CLI_ALLOWED):
6753
continue
68-
stem = rel[:-3]
69-
if stem.endswith(CLI_ALLOWED[:-1]) or stem in ("main", "lib"):
54+
if not any(stem.endswith(d) for d in DOMAIN_SHAPED):
7055
continue
71-
for suffix, home in HOME_FOR.items():
72-
if stem.endswith(suffix):
73-
found[rel] = home
74-
break
75-
return found
76-
77-
78-
def load_baseline() -> dict:
79-
if not BASELINE.is_file():
80-
return {"version": 1, "accepted": {}}
81-
return json.loads(BASELINE.read_text(encoding="utf-8"))
56+
text = path.read_text(encoding="utf-8", errors="replace")
57+
if "codeguard_runtime" in text or CLI_LOCAL.search(text):
58+
continue
59+
out.append({"path": rel, "lines": len(text.splitlines())})
60+
return out
8261

8362

8463
def main(argv: list[str] | None = None) -> int:
8564
parser = argparse.ArgumentParser(description="codeguard crate 分层门禁")
86-
parser.add_argument("--explain", action="store_true", help="打印每个模块的归属理由")
87-
parser.add_argument("--accept", metavar="NAME", help="把一个既有模块登记为已知债务")
88-
parser.add_argument("--reason", metavar="TEXT", help="配合 --accept 记录理由")
65+
parser.add_argument("--explain", action="store_true", help="打印判定依据")
8966
args = parser.parse_args(argv)
9067

91-
current = domain_modules()
92-
baseline = load_baseline()
93-
accepted: dict = baseline.get("accepted", {})
94-
95-
if args.accept:
96-
if args.accept not in current:
97-
print(f"{args.accept} 不在当前 cli 领域模块清单中,无需登记", file=sys.stderr)
98-
return 2
99-
accepted[args.accept] = args.reason or "未记录理由"
100-
baseline["accepted"] = accepted
101-
BASELINE.write_text(
102-
json.dumps(baseline, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
103-
encoding="utf-8",
104-
)
105-
print(f"已登记 {args.accept} → {HOME_FOR.get(suffix_of(args.accept), 'codeguard-core')}")
106-
return 0
68+
misplaced = audit()
69+
current = {i["path"] for i in misplaced}
10770

10871
if args.explain:
109-
for name in sorted(current):
110-
state = "已知债务" if name in accepted else "未登记(会被本检查拒绝)"
111-
print(f"{name:52} → {current[name]:20} [{state}]")
112-
if name in accepted:
113-
print(f"{'':52} 理由:{accepted[name]}")
72+
print("权威边界见 crates/codeguard-cli/tests/crate_boundaries.rs:")
73+
print(" adapters 不得依赖 runtime,因此 *_probe 留在 cli 是正确的。\n")
74+
if not misplaced:
75+
print("当前 codeguard-cli 中没有真正错位的领域模块。")
76+
for item in misplaced:
77+
print(f" {item['path']:48} {item['lines']:5} 行 → 应下移到 adapters/core")
11478
return 0
11579

116-
# 未登记的新领域模块 = 倒挂在扩大
117-
undeclared = sorted(set(current) - set(accepted))
118-
119-
if undeclared:
120-
print("分层门禁失败:以下领域形态模块新增在 codeguard-cli 中。", file=sys.stderr)
121-
print("它们应随工具适配或领域契约下移,而不是留在二进制 crate:\n", file=sys.stderr)
122-
for name in undeclared:
123-
print(f" {name} → 应归属 {current[name]}", file=sys.stderr)
124-
print(
125-
"\n处理方式:把模块移到对应 crate 并更新调用方;"
126-
"若确有理由暂留,用 --accept 显式登记为已知债务。",
127-
file=sys.stderr,
128-
)
129-
return 1
130-
131-
# 已登记模块被移走了:基线应收紧,不留空壳额度
132-
removed = sorted(set(accepted) - set(current))
133-
stale = [n for n in removed if n in accepted]
134-
135-
if stale:
136-
print("分层门禁失败:以下已登记模块已不存在,请从 layer-baseline.json 移除,", file=sys.stderr)
137-
print("以便额度随搬移自动收紧:\n", file=sys.stderr)
138-
for name in stale:
139-
print(f" {name}", file=sys.stderr)
80+
if BASELINE.is_file():
81+
accepted = json.loads(BASELINE.read_text(encoding="utf-8")).get("accepted", {})
82+
stale = set(accepted) - current
83+
if stale:
84+
print("分层门禁失败:以下登记项已不再错位,请从 layer-baseline.json 移除,"
85+
"以免留下永不收紧的空额度:", file=sys.stderr)
86+
for name in sorted(stale):
87+
print(f" {name}", file=sys.stderr)
88+
return 1
89+
new = sorted(current - set(accepted))
90+
if new:
91+
print("分层门禁失败:以下模块既不需要 codeguard_runtime、也不依赖 cli 本地类型,"
92+
"属于被放错位置的领域逻辑:", file=sys.stderr)
93+
for name in new:
94+
print(f" {name}", file=sys.stderr)
95+
return 1
96+
97+
if misplaced:
98+
print("分层门禁失败:以下模块既不需要 runtime、也不依赖 cli 本地类型:", file=sys.stderr)
99+
for item in misplaced:
100+
print(f" {item['path']} ({item['lines']} 行)", file=sys.stderr)
140101
return 1
141102

142-
print(
143-
f"Layering OK: codeguard-cli 中已知领域债务 {len(current)} 个"
144-
f"(全部已登记,无新增);core/adapters 未被 CLI 逻辑反向污染。"
145-
)
103+
print("Layering OK: codeguard-cli 中没有真正错位的领域模块;"
104+
"依赖 runtime 的探针留在 cli 符合 crate_boundaries 的既定方向。")
146105
return 0
147106

148107

149-
def suffix_of(name: str) -> str:
150-
stem = name[:-3] if name.endswith(".rs") else name
151-
for suffix in HOME_FOR:
152-
if stem.endswith(suffix):
153-
return suffix
154-
return ""
155-
156-
157108
if __name__ == "__main__":
158109
raise SystemExit(main())

0 commit comments

Comments
 (0)