"""Differential test: mas_core against the independent JS implementation (MAS-WP-0009). Generated local-parts go through both. Every disagreement is a spec finding or a bug. Known, recorded divergences are listed in KNOWN with their reason. """ import json import random import shutil import subprocess from pathlib import Path import pytest from mas_conformance import statute_aware_fixtures from mas_core.generator import CannotExpress, for_destination from mas_core.parser import MasSyntaxError, parse_address, parse_for_profile from mas_core.policy import resolve from mas_core.statute import build_statute ROOT = Path(__file__).resolve().parents[1] BATCH = ROOT / "impl/js/src/batch.mjs" pytestmark = pytest.mark.skipif(not shutil.which("node"), reason="node not available") TOKENS = ["ticket", "Ticket", "invoice", "support", "case", "gs-1", "GS-1", "x", "a.b", "--", "---", "=", "==", "+", "!", "comments", "Comments", "callback", "chat", ".", "%2B", "_", "-", "$", '"', "ï", "RE=2026", "PR42", "q"] TRANSFORMS = ["resource-alternate", "interaction-alternate", "lowercase", "drop-facet", "drop-interaction"] def corpus(n, seed=20261019): rnd = random.Random(seed) out = set() while len(out) < n: out.add("".join(rnd.choice(TOKENS) for _ in range(rnd.randint(1, 6)))) return sorted(out) def js(statutes, cases): proc = subprocess.run(["node", str(BATCH)], input=json.dumps({"statutes": statutes, "cases": cases}), capture_output=True, text=True, check=True) return json.loads(proc.stdout) def py_parse(local, profile): try: return {"ok": True, "parsed": parse_for_profile(local, profile).to_fixture_dict()} except MasSyntaxError: return {"ok": False} def py_interpret(local, st): try: sem = resolve(parse_address(f"{local}@{st.namespace}"), st) except MasSyntaxError: return {"ok": False} d = sem.to_dict() return {"ok": True, "canonical": d["canonical_local_part"], "stand_ins": d["stand_ins"], "key": sem.equivalence_key} def py_dest(local, st, needs, lossy): try: return {"ok": True, "text": for_destination(local, st, needs, allow_lossy=lossy).local_part} except (CannotExpress, MasSyntaxError): return {"ok": False} def strip(r): return {k: v for k, v in r.items() if k != "error"} DOCS = statute_aware_fixtures()["statutes"] STATUTES = {k: build_statute(d, d["namespace"]) for k, d in DOCS.items()} LOCALS = corpus(1500) def disagreements(cases, py): got = js(DOCS, cases) return [(c, strip(g), p) for c, g, p in zip(cases, got, (py(c) for c in cases)) if strip(g) != p] def test_parser_agrees(): cases = [{"op": "parse", "local": lp, "profile": prof} for lp in LOCALS for prof in ("p1", "p2")] bad = disagreements(cases, lambda c: py_parse(c["local"], c["profile"])) assert bad == [], bad[:10] def test_interpretation_and_equivalence_agree(): cases = [{"op": "interpret", "local": lp, "statute": s} for lp in LOCALS for s in DOCS] bad = disagreements(cases, lambda c: py_interpret(c["local"], STATUTES[c["statute"]])) assert bad == [], bad[:10] def test_destination_transforms_agree(): rnd = random.Random(7) valid = [lp for lp in LOCALS if py_parse(lp, "p2")["ok"]] cases = [] for lp in valid: for s in DOCS: needs = rnd.sample(TRANSFORMS, rnd.randint(0, 3)) cases.append({"op": "dest", "local": lp, "statute": s, "needs": needs, "lossy": rnd.random() < 0.5}) bad = disagreements(cases, lambda c: py_dest(c["local"], STATUTES[c["statute"]], c["needs"], c["lossy"])) assert bad == [], bad[:10]