Files
2026-03-29 09:08:01 +08:00

108 lines
3.9 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.
#!/usr/bin/env python3
"""校验执行域 verify/doc/files 一致性。
该脚本是“离线门禁”的一部分:发现 doc_id 三元契约不一致时应 fail-fast
并输出可定位的冲突清单doc_id + 路径集合)。
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent.parent
VERIFY_DIR = ROOT / "ansible" / "playbooks" / "verify"
DOCS_DIR = ROOT / "docs"
EXEC_ID_RE = re.compile(r"^(0[1-9]|[1-9][0-9])-(0[1-9]|[1-9][0-9])$")
def is_exec_domain(doc_id: str) -> bool:
return EXEC_ID_RE.fullmatch(doc_id) is not None
def main() -> None:
if not VERIFY_DIR.is_dir() or not DOCS_DIR.is_dir():
print("ERR: verify/docs 目录缺失", file=sys.stderr)
sys.exit(2)
# --- scan verify playbooks (.yml/.yaml) ---
verify_by_doc_id: dict[str, list[Path]] = {}
invalid_verify_names: list[str] = []
for p in sorted(VERIFY_DIR.iterdir()):
if not p.is_file():
continue
if p.name.startswith("_"):
# private helpers are allowed but not part of doc_id contract
continue
if p.suffix not in {".yml", ".yaml"}:
continue
# Only accept <doc_id>.yml|yaml
stem = p.stem
if len(stem) != len("00-00") or stem[2:3] != "-":
invalid_verify_names.append(p.name)
continue
if not is_exec_domain(stem):
invalid_verify_names.append(p.name)
continue
verify_by_doc_id.setdefault(stem, []).append(p)
# Same doc_id with multiple verify entrypoints is a hard conflict (EC1).
verify_conflicts: dict[str, list[str]] = {}
for did, paths in verify_by_doc_id.items():
if len(paths) > 1:
verify_conflicts[did] = [str(p.relative_to(ROOT)) for p in sorted(paths)]
missing_docs: list[str] = []
missing_files_dir: list[str] = []
weak_doc_exec_refs: list[str] = []
multi_docs: dict[str, list[str]] = {}
for did in sorted(verify_by_doc_id.keys()):
matches = sorted(DOCS_DIR.glob(f"{did}-*.md"))
if not matches:
missing_docs.append(did)
continue
if len(matches) > 1:
multi_docs[did] = [str(p.relative_to(ROOT)) for p in matches]
doc = matches[0]
content = doc.read_text(encoding="utf-8", errors="ignore")
if f"ansible/files/{did}/" not in content and "```yaml" in content:
weak_doc_exec_refs.append(did)
# Execution-domain doc_id must always have a files truth-source directory (ADR-002 / R4).
if not (ROOT / "ansible" / "files" / did).is_dir():
missing_files_dir.append(did)
if invalid_verify_names:
print(f"ERR: verify 仅允许执行域命名: {sorted(invalid_verify_names)}", file=sys.stderr)
sys.exit(2)
if verify_conflicts:
print("ERR: verify 入口冲突(同一 doc_id 多个入口,必须 fail-fast:", file=sys.stderr)
for did in sorted(verify_conflicts.keys()):
paths = verify_conflicts[did]
print(f" - {did}: {paths}", file=sys.stderr)
sys.exit(2)
if missing_docs:
print(f"ERR: 缺少 docs/<doc_id>-*.md: {missing_docs}", file=sys.stderr)
sys.exit(2)
if multi_docs:
print("ERR: docs 命名冲突(同一 doc_id 匹配到多篇 docs/<doc_id>-*.md:", file=sys.stderr)
for did in sorted(multi_docs.keys()):
print(f" - {did}: {multi_docs[did]}", file=sys.stderr)
sys.exit(2)
if missing_files_dir:
print(f"ERR: 缺少 ansible/files/<doc_id>/ 目录: {missing_files_dir}", file=sys.stderr)
sys.exit(2)
if weak_doc_exec_refs:
print(f"ERR: 文档 YAML 未映射 ansible/files/<doc_id>/ 真源: {weak_doc_exec_refs}", file=sys.stderr)
sys.exit(2)
print(f"[OK] 执行域 verify/doc/files 一致性通过({len(verify_by_doc_id)} 条)")
if __name__ == "__main__":
main()