Files
PolyLoop/tests/unit/test_no_business_terms.py
iomgaa e017160c45 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>
2026-08-09 21:28:59 -04:00

68 lines
2.5 KiB
Python
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.
"""硬约束 §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",
# dissectagent 自我进化实验
"实验轮次",
"因子",
"预注册",
"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}"