Files
aetherbound-guild/tools/m89_page_coverage.py
T

157 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""M89 view -> probe -> marker coverage audit (read-only, docs-grade evidence).
Discovery is by preload: a probe is the tool/test file that preloads the view's
script. Markers are read out of the probe source, so a renamed probe is caught
instead of being silently trusted.
"""
import argparse
import pathlib
import re
import sys
ROOT = pathlib.Path(__file__).resolve().parents[1]
RUNTIME = ROOT / "runtime"
VIEWS = RUNTIME / "entry" / "views"
SEARCH_DIRS = [RUNTIME / "tools", RUNTIME / "tests"]
LEDGER = ROOT / "docs" / "EVIDENCE_LEDGER.md"
BATTLE_DRIVEN = "m60+m79+battle_ink"
BATTLE_VIEWS = {"battle_live_page_view", "battle_opening_page_view"}
def probe_for(view_stem: str):
needle = "res://entry/views/%s.gd" % view_stem
for base in SEARCH_DIRS:
for path in sorted(base.rglob("*.gd")):
if path.name.endswith(".uid"):
continue
try:
text = path.read_text(encoding="utf-8", errors="replace")
except OSError:
continue
if needle in text:
return path, text
return None, ""
def markers(text: str):
found = re.findall(r'(ABG_[A-Z0-9_]+)', text)
primaries = [m for m in found if m.endswith("_OK") and "SELF_TEST" not in m]
return primaries
LABEL_RE = re.compile(r"`(m\d+-[a-z0-9-]+)`")
ID_RE = re.compile(r"^\d{8}T\d{6}Z-\d+$")
TOKEN_RE = re.compile(r"`([^`]+)`")
def ledger_bad_ids(text: str):
"""The recurring defect class: a label used where the artifact id belongs.
Columns vary across rows, so the gate only fires on unambiguous shapes:
the same label twice in one row, or a label immediately followed by
another label. A truncated id can never pass silently because every label
occurrence is checked against its neighbour.
"""
bad = []
for number, line in enumerate(text.splitlines(), 1):
labels = LABEL_RE.findall(line)
if not labels:
continue
counts = {}
for label in labels:
counts[label] = counts.get(label, 0) + 1
for label, count in counts.items():
if count > 1:
bad.append("line %d: label repeated %d times (%s)" % (number, count, label))
tokens = TOKEN_RE.findall(line)
for index, token in enumerate(tokens[:-1]):
if LABEL_RE.fullmatch("`%s`" % token) and LABEL_RE.fullmatch("`%s`" % tokens[index + 1]):
bad.append("line %d: label %s followed by label %s" % (number, token, tokens[index + 1]))
return bad
def self_test() -> int:
"""Negative self-test for the ledger-id gate (ui_ink_check precedent)."""
bad = "| x | `m89-page-coverage` `m89-page-coverage` |"
clean = "| x | `m89-page-coverage` `20260918T060232Z-36554` |"
also_bad = "| y | `m84-notice-semantic` `m85-market-locale-copy` |"
bad_hits = ledger_bad_ids(bad)
clean_hits = ledger_bad_ids(clean)
pair_hits = ledger_bad_ids(also_bad)
if bad_hits and pair_hits and not clean_hits:
print("ABG_M89_LEDGER_SELFTEST_OK duplicate=%d pair=%d clean=0" % (len(bad_hits), len(pair_hits)))
return 0
print("ABG_M89_LEDGER_SELFTEST_FAIL duplicate=%d pair=%d clean=%d" % (
len(bad_hits), len(pair_hits), len(clean_hits)))
return 1
def styleboxflat_count(view_text: str) -> int:
return len(re.findall(r"StyleBoxFlat", view_text))
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--matrix", help="write the refreshed matrix markdown here")
parser.add_argument("--self-test", action="store_true", help="run the ledger-id gate self-test")
args = parser.parse_args()
if args.self_test:
return self_test()
ledger = LEDGER.read_text(encoding="utf-8") if LEDGER.exists() else ""
views = sorted(p for p in VIEWS.glob("*.gd") if not p.name.endswith(".uid"))
rows, uncovered, no_marker, no_ledger = [], [], [], []
probes = set()
for view in views:
stem = view.stem
probe_path, probe_text = probe_for(stem)
view_text = view.read_text(encoding="utf-8", errors="replace")
flats = styleboxflat_count(view_text)
if stem in BATTLE_VIEWS and probe_path is None:
rows.append((stem, "driven", "-", flats, "battle ink (driver)", BATTLE_DRIVEN))
continue
if probe_path is None:
uncovered.append(stem)
rows.append((stem, "?", "-", flats, "NONE", "-"))
continue
probes.add(str(probe_path.relative_to(ROOT)))
found = markers(probe_text)
if not found:
no_marker.append(stem)
in_ledger = [m for m in found if m in ledger]
if found and not in_ledger:
no_ledger.append(stem)
rows.append((stem, "shared", str(probe_path.relative_to(ROOT)), flats,
found[0] if found else "NO_MARKER",
"ledger" if in_ledger else "not_in_ledger"))
if args.matrix:
out = ["# M62 页面一致性矩阵(M89 刷新)", "",
"> 由 `tools/m89_page_coverage.py --matrix` 生成;StyleBoxFlat 数为视图源码 grep 实测。", "",
"| 视图 | chrome 族 | 探针 | 页面私有 StyleBoxFlat | marker | 账本 |",
"| --- | --- | --- | --- | --- | --- |"]
for row in rows:
out.append("| `%s` | %s | `%s` | %d | `%s` | %s |" % row)
pathlib.Path(args.matrix).write_text("\n".join(out) + "\n", encoding="utf-8")
bad_ids = ledger_bad_ids(ledger)
ok = not uncovered and not no_marker and not no_ledger and not bad_ids
detail = "uncovered=%s markers_missing=%s ledger_missing=%s" % (
",".join(uncovered) or "none", ",".join(no_marker) or "none", ",".join(no_ledger) or "none")
if bad_ids:
detail = "%s ledger_bad_ids=%d" % (detail, len(bad_ids))
if ok:
print("ABG_M89_PAGE_COVERAGE_OK views=%d probes=%d uncovered=none battle=driven:%s" % (
len(views), len(probes), BATTLE_DRIVEN))
else:
print("ABG_M89_PAGE_COVERAGE_FAIL views=%d probes=%d %s" % (len(views), len(probes), detail))
print("ABG_M89_PAGE_COVERAGE_DETAIL " + detail)
for problem in bad_ids:
print("ABG_M89_PAGE_COVERAGE_BADID " + problem)
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())