build(repo): 落成工具链、依赖契约与十个模块的空骨架
第 ③ 阶段剩下的那半:架构文档之外,import-linter 契约也落地了。 pyproject.toml 把九条依赖规则里的七条写成五条 import-linter 契约。 分层用一条 layers 契约表达规则 1、2、3、8,`|` 表示同层互不 import; 另外四条 forbidden 分别管下游反向 import、polygateway 的唯一入口、 以及三个纯逻辑模块不碰 asyncio 与 pathlib。 契约不是平凡的绿:故意注入两处违规验证过,都被点名到行号。 先建十个模块的空包,是为了避开 PolyGateway bootstrap 期那段 Makefile 门控—— 它当时没有包,lint-imports 报 module not found 而红,只好加一段跳过逻辑。 空包让契约从第一天就真的在跑。 剩下两条规则落不进契约,写成了 tests/unit 下的测试: 规则 6「types 与 ports 不许 import 任何第三方」判据要反过来写(只许标准库和自己), 规则 9「import polyloop 之后 sys.modules 里没有 polygateway」是运行时事实。 另加硬约束 §1.1 零业务假设的黑名单扫描——它第一次跑就抓到 _assembly 的 docstring 里写了 dissect 的业务词,已改。三个扫描类测试都带 fail-closed 守卫, 防止目录搬走之后扫到空列表安静地绿。 工程约定取自实验室已有项目:setuptools + src layout、ruff 十一项 select、 line-length 100 来自 PolyGateway;dev 工具链版本钉死、--strict-markers 与 --import-mode=importlib 来自 CHSAnalyzer 与 dissect 踩过的坑,各自的理由写在配置注释里。 e2e 默认不跑,它打真实网关要花钱。 CLAUDE.md §0 那句「契约还没写,要等 src/ 落地」已过期,改掉; README 勾掉第 ②③ 阶段,补上本地检查命令与 GovDoc-Editor 那一行。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""硬约束 §1.1 零业务假设:库内不许出现下游的业务词汇。
|
||||
|
||||
三个下游的领域互不相交(公文审查、超声诊断、agent 自我进化实验),一个业务词进来就等于
|
||||
替其中一个项目做了另外两个不需要的假设。这类假设很难删——它会长出配套的字段、分支和测试,
|
||||
删的时候要一起动。
|
||||
|
||||
**这个文件只读源码文本,不执行被测代码。** 它仍然属于 unit,因为分层判据是「依赖什么」
|
||||
(`CLAUDE.md` §1.9),而它不连任何外部服务。
|
||||
|
||||
**黑名单挡不住没被列出来的词。** 它拦得住最可能发生的那种——从下游搬代码时把词一起搬
|
||||
过来;拦不住一个新造的业务词。真正守住这条的是评审,这里只是把最常见的入口堵上。
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
PACKAGE_ROOT = REPO_ROOT / "src" / "polyloop"
|
||||
|
||||
#: 词来自三个下游各自的领域。加词的时机是「从某个下游搬了一段代码进来」,那时把它领域里
|
||||
#: 最扎眼的几个词补进来。
|
||||
BANNED_TERMS = (
|
||||
# GovDoc:公文审查
|
||||
"公文",
|
||||
"审核点",
|
||||
"招标",
|
||||
"投标",
|
||||
"标书",
|
||||
"checkpoint_id",
|
||||
"tender",
|
||||
# CHSAnalyzer:超声诊断
|
||||
"超声",
|
||||
"切面",
|
||||
"病灶",
|
||||
"ultrasound",
|
||||
# dissect:agent 自我进化实验
|
||||
"实验轮次",
|
||||
"因子",
|
||||
"预注册",
|
||||
"benchmark",
|
||||
"rollout",
|
||||
"appworld",
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_source_tree_is_scannable() -> None:
|
||||
"""守卫自身的 fail-closed 检查:包目录必须存在且有 `.py` 文件。
|
||||
|
||||
没有这一条,包被改名或搬走之后这条断言会扫到空列表然后安静地绿,而绿的含义从
|
||||
「没有业务词」变成了「没有检查」。
|
||||
"""
|
||||
assert PACKAGE_ROOT.is_dir(), f"扫描目标不存在:{PACKAGE_ROOT}"
|
||||
assert list(PACKAGE_ROOT.rglob("*.py")), f"扫描目标里没有任何 .py 文件:{PACKAGE_ROOT}"
|
||||
|
||||
|
||||
def test_no_business_terms_in_source() -> None:
|
||||
"""库源码(含 docstring 与注释)里不许出现下游业务词汇。"""
|
||||
violations: list[str] = []
|
||||
for path in sorted(PACKAGE_ROOT.rglob("*.py")):
|
||||
text = path.read_text(encoding="utf-8").casefold()
|
||||
for term in BANNED_TERMS:
|
||||
if term.casefold() in text:
|
||||
violations.append(f"{path.relative_to(REPO_ROOT)}: {term}")
|
||||
assert violations == [], f"库内出现业务词汇:{violations}"
|
||||
Reference in New Issue
Block a user