@andresmassello/uscha 1.71.0 → 1.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4598,8 +4598,25 @@ def cmd_fidelity(args):
4598
4598
  % (_scope, okc, len(static_canon)))
4599
4599
  else:
4600
4600
  dims["contracts"] = _fid_dim(None, "UNMEASURED: no static-class canonical items")
4601
- # curation_closure: curated OBS / total OBS in the active delta
4602
- if obs:
4601
+ # curation_closure: curated OBS / total OBS in the active delta. With --ir (ADR-015) it is
4602
+ # answered as a path query over the graph -- OBS nodes reachable to a CURATION node via an
4603
+ # OBS->CURATION edge / OBS nodes. It reproduces v0 when the graph's OBS set equals the
4604
+ # delta's (the FIELD-RUN-001 case); a canonical carrying OBS beyond the active delta would
4605
+ # widen the denominator, which is the graph's honest answer, not v0's.
4606
+ if args.ir:
4607
+ graph = _extract_ir(repo_path, ledger)
4608
+ obs_nodes = [nd["id"] for nd in graph["nodes"] if nd["type"] == "OBS"]
4609
+ cured = {e["from"] for e in graph["edges"] if e["type"] == "OBS->CURATION"}
4610
+ if obs_nodes:
4611
+ hit = sum(1 for oid in obs_nodes if oid in cured)
4612
+ dims["curation_closure"] = _fid_dim(
4613
+ round(hit / len(obs_nodes), 4),
4614
+ "IR path query: %d/%d OBS node(s) reach a CURATION node (IR v%s)"
4615
+ % (hit, len(obs_nodes), IR_SCHEMA))
4616
+ else:
4617
+ dims["curation_closure"] = _fid_dim(
4618
+ None, "UNMEASURED: no OBS nodes in the IR graph")
4619
+ elif obs:
4603
4620
  cur = sum(1 for o in obs if o["id"] in verdicts)
4604
4621
  dims["curation_closure"] = _fid_dim(round(cur / len(obs), 4),
4605
4622
  "%d/%d OBS in %s carry a ledger verdict"
@@ -4655,6 +4672,632 @@ def cmd_fidelity(args):
4655
4672
  sys.exit(0)
4656
4673
 
4657
4674
 
4675
+ # --------------------------------------------------------------------------- #
4676
+ # IR (Diamond M2: the canonical package extracts into a typed graph. ADR-015.
4677
+ # Markdown stays canonical; the IR is a derived index. What cannot be typed
4678
+ # deterministically is UNTYPED -- visible, counted, never guessed.)
4679
+ # --------------------------------------------------------------------------- #
4680
+
4681
+ IR_DIR = "ir"
4682
+ IR_FILE = "IR.json"
4683
+ IR_TWIN = "IR.md"
4684
+ IR_SCHEMA = "0.1"
4685
+ IR_NODE_TYPES = ("REQ", "INV", "AC", "CONTRACT", "DECISION", "NFR",
4686
+ "GOLDEN", "OBS", "CURATION", "EVIDENCE")
4687
+ IR_EDGE_TYPES = ("REQ->AC", "AC->EVIDENCE", "DECISION->INV", "OBS->CURATION",
4688
+ "CURATION->canonical", "supersedes", "derived_from")
4689
+ # broader than _AC_ID: the forward package uses sub-namespaced ids (AC-DD-07, AC-FV-06)
4690
+ _IR_AC_LINE = re.compile(r"^\s*[-*]\s*\[[ xX]\]\s*(AC-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\b\s*[-—:]*\s*(.*)$")
4691
+ _IR_INV_LINE = re.compile(r"^\s*[-*]\s*\*\*(INV-[A-Za-z0-9-]+)\s*[—-]+\s*(.*?)\*\*")
4692
+ _IR_ADR_REF = re.compile(r"\bADR-0*(\d+)\b")
4693
+ _IR_INV_REF = re.compile(r"\bINV-[A-Za-z0-9-]+\b")
4694
+ _IR_SUPERSEDE = re.compile(r"(?i)supersed\w*\s+(ADR-0*\d+)")
4695
+ _IR_BANNER = ("GENERATED by qa_ledger.py ir-extract (ADR-015). Rendered view of "
4696
+ + IR_FILE + " -- hand edits are overwritten on regeneration.")
4697
+
4698
+
4699
+ def _ir_node_id(ntype, text, source):
4700
+ """Content-address the ID-less (ADR-015 option B): nodes with a native human id keep it;
4701
+ only nodes with nothing human to anchor to get NODE-sha256(type+text+source)[:12]."""
4702
+ norm = re.sub(r"\s+", " ", (text or "").strip().lower())
4703
+ return "NODE-" + hashlib.sha256(
4704
+ (ntype + "\n" + norm + "\n" + source).encode("utf-8")).hexdigest()[:12]
4705
+
4706
+
4707
+ def _ir_read_lines(path):
4708
+ try:
4709
+ with open(path, encoding="utf-8-sig", errors="replace") as fh:
4710
+ return fh.read().splitlines()
4711
+ except OSError:
4712
+ return None
4713
+
4714
+
4715
+ def _extract_ir(repo_path, ledger):
4716
+ """Deterministic extraction of the forward canonical package into a typed graph. Every
4717
+ node/edge is derived from an EXISTING structural convention or reference -- nothing is
4718
+ inferred. A line that fills a structural slot but cannot be typed lands in `untyped`."""
4719
+ nodes, edges, untyped = [], [], []
4720
+ seen_ids = set()
4721
+
4722
+ def add(node):
4723
+ if node["id"] not in seen_ids:
4724
+ seen_ids.add(node["id"])
4725
+ nodes.append(node)
4726
+
4727
+ # AC nodes <- ACCEPTANCE.md checkboxes carrying an AC id; a checkbox WITHOUT an id is an
4728
+ # acceptance slot the conventions do not type -> untyped (never a guessed id).
4729
+ acc_lines = _ir_read_lines(os.path.join(repo_path, "ACCEPTANCE.md")) or []
4730
+ for n, ln in enumerate(acc_lines, 1):
4731
+ s = ln.strip()
4732
+ if s[:5].lower() not in ("- [x]", "- [ ]", "* [x]", "* [ ]"):
4733
+ continue
4734
+ m = _IR_AC_LINE.match(ln)
4735
+ src = {"file": "ACCEPTANCE.md", "line": n}
4736
+ if m:
4737
+ add({"id": m.group(1).upper(), "type": "AC",
4738
+ "statement": m.group(2).strip(), "source": src})
4739
+ else:
4740
+ untyped.append({"text": s[5:].strip(), "source": src,
4741
+ "reason": "acceptance checkbox without a traceable AC-id"})
4742
+
4743
+ # INV nodes <- CONSTITUTION.md `- **INV-XXX-NN — Title.**` headings
4744
+ con_lines = _ir_read_lines(os.path.join(repo_path, "CONSTITUTION.md")) or []
4745
+ inv_ids = set()
4746
+ for n, ln in enumerate(con_lines, 1):
4747
+ m = _IR_INV_LINE.match(ln)
4748
+ if m:
4749
+ add({"id": m.group(1).upper(), "type": "INV",
4750
+ "statement": m.group(2).strip().rstrip("."),
4751
+ "source": {"file": "CONSTITUTION.md", "line": n}})
4752
+ inv_ids.add(m.group(1).upper())
4753
+
4754
+ # DECISION nodes <- docs/adr/*.md (native ADR-NNN id, title, file); edges to the INVs the
4755
+ # ADR governs/states, and supersedes edges -- both from references already in the body.
4756
+ adr_dir = os.path.join(repo_path, "docs", "adr")
4757
+ ac_ids = {nd["id"] for nd in nodes if nd["type"] == "AC"}
4758
+ for row in _mirador_adrs(adr_dir):
4759
+ add({"id": row["id"], "type": "DECISION", "statement": row["t"],
4760
+ "source": {"file": os.path.relpath(row["file"], repo_path).replace(os.sep, "/"),
4761
+ "line": 1}})
4762
+ body = _ir_read_lines(row["file"]) or []
4763
+ text = "\n".join(body)
4764
+ for inv in set(_IR_INV_REF.findall(text)):
4765
+ if inv.upper() in inv_ids:
4766
+ edges.append({"from": row["id"], "to": inv.upper(), "type": "DECISION->INV"})
4767
+ for sup in set(_IR_SUPERSEDE.findall(text)):
4768
+ edges.append({"from": row["id"],
4769
+ "to": "ADR-%03d" % int(re.search(r"\d+", sup).group()),
4770
+ "type": "supersedes"})
4771
+ # an ADR's Verification checklist references its ACs -> REQ->AC is absent here (no REQ
4772
+ # layer yet), but AC nodes referenced by an ADR are real edges DECISION mentions AC:
4773
+ for aid in {m.group(0)[1:-1].upper()
4774
+ for m in re.finditer(r"\(AC-[A-Za-z0-9-]+\)", text)}:
4775
+ if aid in ac_ids: # set(): an AC cited twice is one edge
4776
+ edges.append({"from": row["id"], "to": aid, "type": "REQ->AC"})
4777
+
4778
+ # GOLDEN nodes <- git-tracked approved fixtures (the path is a native, stable id)
4779
+ tracked = _tracked_files(repo_path) or []
4780
+ gold_marker = ".appro" + "ved."
4781
+ for f in tracked:
4782
+ if gold_marker in f:
4783
+ add({"id": f, "type": "GOLDEN", "statement": "approved golden fixture",
4784
+ "source": {"file": f, "line": 1}})
4785
+
4786
+ # OBS / CURATION <- the active delta's observations (all of them, so curation_closure is
4787
+ # a real path query over the graph) + the ledger's curation records. Absent in a repo
4788
+ # that never ran a field run -> simply no such nodes.
4789
+ delta, derrs = _load_delta(repo_path)
4790
+ if delta and not derrs:
4791
+ for o in delta.get("observations") or []:
4792
+ prov = (o.get("provenance") or {}).get("files") or []
4793
+ add({"id": o["id"], "type": "OBS", "statement": o.get("statement") or "",
4794
+ "source": {"file": prov[0].split(":")[0] if prov else "delta", "line": 1}})
4795
+ canon_path = os.path.join(repo_path, CANDIDATE_DIR, CANONICAL_FILE)
4796
+ canon_items = []
4797
+ if os.path.isfile(canon_path):
4798
+ try:
4799
+ with open(canon_path, encoding="utf-8-sig") as fh:
4800
+ canon_items = json.load(fh).get("items") or []
4801
+ except (OSError, ValueError):
4802
+ canon_items = []
4803
+ for it in canon_items:
4804
+ oid = it.get("derived_from")
4805
+ if not oid:
4806
+ continue
4807
+ prov = (it.get("provenance") or {}).get("files") or []
4808
+ add({"id": oid, "type": "OBS", "statement": it.get("statement") or "",
4809
+ "source": {"file": prov[0].split(":")[0] if prov else "canonical", "line": 1}})
4810
+ for rec in ledger.get("curation") or []:
4811
+ oid = rec.get("obs_id")
4812
+ if not oid:
4813
+ continue
4814
+ cid = "CUR-" + hashlib.sha256(
4815
+ (oid + "\n" + (rec.get("at") or "")).encode("utf-8")).hexdigest()[:12]
4816
+ add({"id": cid, "type": "CURATION",
4817
+ "statement": "%s: %s" % (rec.get("verdict"), oid),
4818
+ "source": {"file": "QA-LEDGER.json", "line": 0}})
4819
+ if oid in seen_ids:
4820
+ edges.append({"from": oid, "to": cid, "type": "OBS->CURATION"})
4821
+
4822
+ node_ids = {nd["id"] for nd in nodes}
4823
+ kept, dropped = [], 0
4824
+ for e in edges:
4825
+ if e["from"] in node_ids and e["to"] in node_ids:
4826
+ kept.append(e)
4827
+ elif e["type"] == "supersedes" and e["from"] in node_ids:
4828
+ kept.append(e) # a superseded ADR may be archived; keep it
4829
+ else:
4830
+ dropped += 1
4831
+ nodes.sort(key=lambda nd: (nd["type"], nd["id"]))
4832
+ kept.sort(key=lambda e: (e["type"], e["from"], e["to"]))
4833
+ untyped.sort(key=lambda u: (u["source"]["file"], u["source"]["line"]))
4834
+ counts = {t: sum(1 for nd in nodes if nd["type"] == t) for t in IR_NODE_TYPES}
4835
+ stats = {"nodes": len(nodes), "edges": len(kept),
4836
+ "edges_dropped": dropped, "untyped": len(untyped),
4837
+ "by_type": {t: c for t, c in counts.items() if c},
4838
+ "untyped_rate": round(len(untyped) / (len(nodes) + len(untyped)), 4)
4839
+ if (nodes or untyped) else 0.0}
4840
+ graph = {"schema_version": IR_SCHEMA,
4841
+ "_generated_by": "qa_ledger.py ir-extract (ADR-015) -- derived index of the "
4842
+ "canonical package; never hand-edit",
4843
+ "nodes": nodes, "edges": kept, "untyped": untyped, "stats": stats}
4844
+ # the seal covers stats too (fresh-review MEDIUM): a doctored summary that ir-render
4845
+ # would faithfully print must trip the strict loader, not slip past it -- the
4846
+ # derived-but-unsealed lesson, applied a third time (path @1.70, evidence_class @1.69).
4847
+ graph["_integrity"] = _ir_seal(graph)
4848
+ return graph
4849
+
4850
+
4851
+ def _ir_seal(graph):
4852
+ return _integrity_hash({k: graph.get(k) for k in
4853
+ ("schema_version", "nodes", "edges", "untyped", "stats")})
4854
+
4855
+
4856
+ def _load_ir(repo_path):
4857
+ """Strict loader: an unknown schema_version or a broken graph is exit-2 class, never
4858
+ mis-read (AC-IR-05). Returns (graph, errors); graph None when absent."""
4859
+ path = os.path.join(repo_path, IR_DIR, IR_FILE)
4860
+ if not os.path.isfile(path):
4861
+ return None, []
4862
+ try:
4863
+ with open(path, encoding="utf-8-sig") as fh:
4864
+ g = json.load(fh)
4865
+ except (OSError, ValueError) as exc:
4866
+ return {}, ["unreadable: %s" % exc]
4867
+ errors = []
4868
+ if g.get("schema_version") != IR_SCHEMA:
4869
+ errors.append("schema_version %r != %r (a version this engine does not know is not "
4870
+ "read, it is refused)" % (g.get("schema_version"), IR_SCHEMA))
4871
+ return g, errors
4872
+ if not isinstance(g.get("nodes"), list) or not isinstance(g.get("edges"), list):
4873
+ return g, ["nodes/edges missing or not lists"]
4874
+ if g.get("_integrity") != _ir_seal(g):
4875
+ errors.append("integrity seal does not match the graph -- nodes, edges, untyped or "
4876
+ "stats was hand-edited (regenerate via ir-extract)")
4877
+ return g, errors
4878
+
4879
+
4880
+ def _render_ir_md(graph):
4881
+ st = graph.get("stats") or {}
4882
+ lines = ["<!-- %s -->" % _IR_BANNER, "", "# Uscha IR v%s (rendered view)" % IR_SCHEMA, "",
4883
+ "%d nodes · %d edges · %d UNTYPED (rate %.2f)"
4884
+ % (st.get("nodes", 0), st.get("edges", 0), st.get("untyped", 0),
4885
+ st.get("untyped_rate", 0.0)), "",
4886
+ "## Nodes", "", "| id | type | statement | source |",
4887
+ "|----|------|-----------|--------|"]
4888
+ for nd in graph.get("nodes") or []:
4889
+ src = "%s:%s" % (nd["source"]["file"], nd["source"]["line"])
4890
+ stmt = (nd.get("statement") or "").replace("|", "\\|")
4891
+ lines.append("| %s | %s | %s | %s |" % (nd["id"], nd["type"], stmt, src))
4892
+ lines += ["", "## Edges", "", "| from | type | to |", "|------|------|----|"]
4893
+ for e in graph.get("edges") or []:
4894
+ lines.append("| %s | %s | %s |" % (e["from"], e["type"], e["to"]))
4895
+ if graph.get("untyped"):
4896
+ lines += ["", "## UNTYPED (conventions the human layer is missing)", "",
4897
+ "| text | source | reason |", "|------|--------|--------|"]
4898
+ for u in graph["untyped"]:
4899
+ src = "%s:%s" % (u["source"]["file"], u["source"]["line"])
4900
+ txt = (u.get("text") or "").replace("|", "\\|")[:80]
4901
+ lines.append("| %s | %s | %s |" % (txt, src, u.get("reason", "")))
4902
+ lines.append("")
4903
+ return "\n".join(lines)
4904
+
4905
+
4906
+ def cmd_ir_extract(args):
4907
+ ledger = _load(args.ledger)
4908
+ _repo_node(ledger, args.repo)
4909
+ repo_path = _scope_path(ledger, args.repo)
4910
+ graph = _extract_ir(repo_path, ledger)
4911
+ ir_dir = os.path.join(repo_path, IR_DIR)
4912
+ os.makedirs(ir_dir, exist_ok=True)
4913
+ with open(os.path.join(ir_dir, IR_FILE), "w", encoding="utf-8", newline="\n") as fh:
4914
+ fh.write(json.dumps(graph, indent=2, ensure_ascii=False) + "\n")
4915
+ twin_path = os.path.join(ir_dir, IR_TWIN)
4916
+ twin = _render_ir_md(graph)
4917
+ prev = None
4918
+ if os.path.isfile(twin_path):
4919
+ try:
4920
+ with open(twin_path, encoding="utf-8-sig") as fh:
4921
+ prev = fh.read()
4922
+ except OSError:
4923
+ prev = None
4924
+ with open(twin_path, "w", encoding="utf-8", newline="\n") as fh:
4925
+ fh.write(twin)
4926
+ if prev is not None and prev != twin:
4927
+ print("[qa_ledger] ir-extract: %s regenerated (rendered view, never a source)"
4928
+ % IR_TWIN, file=sys.stderr)
4929
+ st = graph["stats"]
4930
+ if args.json:
4931
+ print(json.dumps(st, indent=2, ensure_ascii=False))
4932
+ else:
4933
+ print("IR-EXTRACT %s: %d nodes, %d edges -> %s"
4934
+ % (args.repo, st["nodes"], st["edges"],
4935
+ os.path.join(IR_DIR, IR_FILE)))
4936
+ print(" by type: " + ", ".join("%s=%d" % (t, c)
4937
+ for t, c in st["by_type"].items()))
4938
+ print(" UNTYPED: %d (rate %.2f) -- the size of what the conventions cannot yet type"
4939
+ % (st["untyped"], st["untyped_rate"]))
4940
+ if st["edges_dropped"]:
4941
+ print(" %d edge(s) dropped (endpoint not a node) -- counted, never dangling"
4942
+ % st["edges_dropped"])
4943
+ sys.exit(0)
4944
+
4945
+
4946
+ def cmd_ir_render(args):
4947
+ ledger = _load(args.ledger)
4948
+ _repo_node(ledger, args.repo)
4949
+ repo_path = _scope_path(ledger, args.repo)
4950
+ graph, errors = _load_ir(repo_path)
4951
+ if graph is None:
4952
+ print("[qa_ledger] ir-render: no %s -- run ir-extract first."
4953
+ % os.path.join(IR_DIR, IR_FILE), file=sys.stderr)
4954
+ sys.exit(2)
4955
+ if errors:
4956
+ for e in errors:
4957
+ print("[qa_ledger] ir-render: %s" % e, file=sys.stderr)
4958
+ sys.exit(2)
4959
+ twin = _render_ir_md(graph)
4960
+ with open(os.path.join(repo_path, IR_DIR, IR_TWIN), "w", encoding="utf-8",
4961
+ newline="\n") as fh:
4962
+ fh.write(twin)
4963
+ print("IR-RENDER %s: %s regenerated from the graph"
4964
+ % (args.repo, os.path.join(IR_DIR, IR_TWIN)))
4965
+ sys.exit(0)
4966
+
4967
+
4968
+ # --------------------------------------------------------------------------- #
4969
+ # compiler contract (Diamond M3: the LLM is a compiler with a validated output
4970
+ # contract. The engine VALIDATES and INGESTS compilations; it NEVER compiles and
4971
+ # never calls a model. Only mechanical violations gate; every statistic is
4972
+ # advisory (INV-ADVISORY-01). ADR-016.)
4973
+ # --------------------------------------------------------------------------- #
4974
+ COMPILE_SCHEMA = "compile/0.1"
4975
+ COMPILE_REQUIRED = ("schema_version", "canonical_ir", "target_stack", "source",
4976
+ "tests", "trace_manifest", "unresolved_intent",
4977
+ "compilation_report")
4978
+ # The seal covers the load-bearing contract, NOT compilation_report: model, versions and
4979
+ # timestamps legitimately vary and never change WHAT was compiled. A hand edit of the
4980
+ # substance (source/tests/manifest/unresolved_intent) after production must trip the seal.
4981
+ COMPILE_SEALED = ("schema_version", "canonical_ir", "target_stack",
4982
+ "implementation_constraints", "source", "tests",
4983
+ "trace_manifest", "unresolved_intent")
4984
+ # Advisory degeneracy dial: a manifest of >=2 units where EVERY unit claims >= this share
4985
+ # of ALL IR nodes is "everything traces to everything" -- no unit discriminates. ADVISORY
4986
+ # ONLY: printed, never gates. The exact value is a reporting choice, not a contract,
4987
+ # precisely because it can never block (a threshold on a smell would be a judgment).
4988
+ _COMPILE_DEGENERATE_FANOUT = 0.8
4989
+
4990
+
4991
+ def _compile_seal(c):
4992
+ return _integrity_hash({k: c.get(k) for k in COMPILE_SEALED})
4993
+
4994
+
4995
+ def _sha256_file(path):
4996
+ try:
4997
+ with open(path, "rb") as fh:
4998
+ return hashlib.sha256(fh.read()).hexdigest()
4999
+ except OSError:
5000
+ return None
5001
+
5002
+
5003
+ def _contained_unit(base, unit):
5004
+ """A compilation's units must be RELATIVE paths CONTAINED within the compilation
5005
+ directory: the manifest references what was compiled, and what was compiled lives with
5006
+ the compilation. An absolute path or a `..` escape lets a manifest name any file on the
5007
+ filesystem as a "source" unit and pass on that file's hash -- exactly the "manifest
5008
+ cannot lie about what was compiled" guarantee, defeated. Returns the resolved path, or
5009
+ None if the unit is absolute or escapes `base`. Both sides are `realpath`-normalized
5010
+ before comparison -- the Windows 8.3 short-path trap this repo already paid for once
5011
+ (a file INSIDE a tree judged outside it) applies to any containment check."""
5012
+ if os.path.isabs(unit):
5013
+ return None
5014
+ full = os.path.realpath(os.path.join(base, unit.replace("/", os.sep)))
5015
+ root = os.path.realpath(base)
5016
+ if full == root or full.startswith(root + os.sep):
5017
+ return full
5018
+ return None
5019
+
5020
+
5021
+ def _load_ir_at(path):
5022
+ """Strict IR loader for an ARBITRARY IR.json. The repo's own ir/ is gitignored (a
5023
+ regenerable index), so the M3 reference IR is a committed fixture loaded here with the
5024
+ same posture as _load_ir: an unknown schema or a broken/hand-edited seal is refused,
5025
+ never mis-read. Returns (graph, errors); graph None when the file is absent."""
5026
+ if not os.path.isfile(path):
5027
+ return None, ["no reference IR at %s" % path]
5028
+ try:
5029
+ with open(path, encoding="utf-8-sig") as fh:
5030
+ g = json.load(fh)
5031
+ except (OSError, ValueError) as exc:
5032
+ return {}, ["unreadable: %s" % exc]
5033
+ if g.get("schema_version") != IR_SCHEMA:
5034
+ return g, ["schema_version %r != %r (an IR version this engine does not know is "
5035
+ "refused, not read)" % (g.get("schema_version"), IR_SCHEMA)]
5036
+ if not isinstance(g.get("nodes"), list) or not isinstance(g.get("edges"), list):
5037
+ return g, ["nodes/edges missing or not lists"]
5038
+ if g.get("_integrity") != _ir_seal(g):
5039
+ return g, ["reference IR integrity seal does not match -- it was hand-edited "
5040
+ "(regenerate via ir-extract)"]
5041
+ return g, []
5042
+
5043
+
5044
+ def _validate_compilation(comp_path, ir_graph):
5045
+ """The deterministic checker (ADR-016). Returns (blocking_errors, advisory).
5046
+
5047
+ BLOCKS only on FACTS: unknown schema, a missing/mistyped section, a broken seal, an
5048
+ ir_hash that does not match the reference IR, a trace_manifest id that is not an IR
5049
+ node, a unit whose file is absent or whose hash does not match the bytes on disk, a
5050
+ malformed unresolved_intent entry. Every STATISTIC (fan-out degeneracy, empty/generic
5051
+ unresolved_intent, coverage) is advisory and can NEVER change the outcome -- a
5052
+ threshold on a smell is a judgment, and no judgment gates (INV-ADVISORY-01)."""
5053
+ errors, advisory = [], {}
5054
+ base = os.path.dirname(os.path.abspath(comp_path))
5055
+ try:
5056
+ with open(comp_path, encoding="utf-8-sig") as fh:
5057
+ c = json.load(fh)
5058
+ except (OSError, ValueError) as exc:
5059
+ return ["unreadable compilation: %s" % exc], advisory
5060
+ if c.get("schema_version") != COMPILE_SCHEMA:
5061
+ return ["schema_version %r != %r (a contract version this engine does not know is "
5062
+ "refused, not read)" % (c.get("schema_version"), COMPILE_SCHEMA)], advisory
5063
+ for k in COMPILE_REQUIRED:
5064
+ if k not in c:
5065
+ errors.append("required section missing: %s" % k)
5066
+ if errors:
5067
+ return errors, advisory
5068
+ for k, typ in (("source", list), ("tests", list), ("trace_manifest", list),
5069
+ ("unresolved_intent", list), ("canonical_ir", dict),
5070
+ ("compilation_report", dict)):
5071
+ if not isinstance(c.get(k), typ):
5072
+ errors.append("%s must be a %s" % (k, typ.__name__))
5073
+ if errors:
5074
+ return errors, advisory
5075
+ # ELEMENT shapes, checked before any `.get` on them: an adversarial/buggy compiler that
5076
+ # emits a list of strings (or a non-list `implements`) is a mechanical violation -> exit
5077
+ # 2 with a named fault, NEVER an AttributeError traceback (exit 1). This is the trust
5078
+ # boundary the contract exists to defend; the loops below assume dict elements.
5079
+ for k in ("source", "tests"):
5080
+ for i, u in enumerate(c.get(k)):
5081
+ if not isinstance(u, dict):
5082
+ errors.append("%s[%d] must be an object with unit/sha256" % (k, i))
5083
+ for i, e in enumerate(c.get("trace_manifest")):
5084
+ if not isinstance(e, dict):
5085
+ errors.append("trace_manifest[%d] must be an object with unit/implements" % i)
5086
+ elif not isinstance(e.get("implements"), list):
5087
+ errors.append("trace_manifest[%d].implements must be a list of IR node ids" % i)
5088
+ for i, ui in enumerate(c.get("unresolved_intent")):
5089
+ if not isinstance(ui, dict):
5090
+ errors.append("unresolved_intent[%d] must be an object with ir_region/decision"
5091
+ % i)
5092
+ if errors:
5093
+ return errors, advisory
5094
+ if c.get("_integrity") != _compile_seal(c):
5095
+ errors.append("compilation integrity seal does not match -- source/tests/manifest/"
5096
+ "unresolved_intent was hand-edited after production")
5097
+ # the compilation NAMES which IR it compiled; a stale or foreign ir_hash is refused --
5098
+ # a compilation cannot be measured against a graph this repo does not reproduce.
5099
+ ir_seal = ir_graph.get("_integrity")
5100
+ named = (c.get("canonical_ir") or {}).get("ir_hash")
5101
+ if named != ir_seal:
5102
+ errors.append("canonical_ir.ir_hash %s.. does not match the reference IR seal %s.. "
5103
+ "-- a stale or foreign IR is refused, never assumed"
5104
+ % (str(named)[:12], str(ir_seal)[:12]))
5105
+ node_ids = {nd["id"] for nd in ir_graph.get("nodes") or []}
5106
+ # units resolve on disk and their hashes match the bytes: the manifest cannot lie about
5107
+ # what was compiled.
5108
+ units = {}
5109
+ for section in ("source", "tests"):
5110
+ for u in c.get(section) or []:
5111
+ unit, want = u.get("unit"), u.get("sha256")
5112
+ if not unit:
5113
+ errors.append("%s entry without a unit path" % section)
5114
+ continue
5115
+ # source classification is sticky: a unit listed in BOTH source and tests stays
5116
+ # source, so the degeneracy detector (over source units) cannot be dodged by also
5117
+ # listing a source file under tests. Advisory-only, but the detector stays honest.
5118
+ if units.get(unit) != "source":
5119
+ units[unit] = section
5120
+ full = _contained_unit(base, unit)
5121
+ if full is None:
5122
+ errors.append("%s unit escapes the compilation directory -- a relative, "
5123
+ "contained path is required (the manifest references what was "
5124
+ "compiled): %s" % (section, unit))
5125
+ continue
5126
+ got = _sha256_file(full)
5127
+ if got is None:
5128
+ errors.append("%s unit missing on disk: %s" % (section, unit))
5129
+ elif got != want:
5130
+ errors.append("%s unit hash mismatch (manifest lies about the bytes): %s"
5131
+ % (section, unit))
5132
+ # every manifest id is an IR node (THE named mechanical violation); every manifest unit
5133
+ # is a real source/test unit.
5134
+ for entry in c.get("trace_manifest") or []:
5135
+ unit = entry.get("unit")
5136
+ if unit not in units:
5137
+ errors.append("trace_manifest unit is not a declared source/test unit: %s" % unit)
5138
+ for nid in entry.get("implements") or []:
5139
+ if nid not in node_ids:
5140
+ errors.append("trace_manifest implements an id that is not an IR node: %s"
5141
+ % nid)
5142
+ # unresolved_intent SHAPE blocks; its richness (count, specificity) is advisory only.
5143
+ for ui in c.get("unresolved_intent") or []:
5144
+ if not ui.get("ir_region") or not ui.get("decision"):
5145
+ errors.append("unresolved_intent entry missing ir_region or decision")
5146
+ # ---- ADVISORY (computed always, gates never) ----
5147
+ manifest_units = {e.get("unit") for e in c.get("trace_manifest") or []}
5148
+ per_unit = [len(e.get("implements") or []) for e in c.get("trace_manifest") or []]
5149
+ total_nodes = len(node_ids) or 1
5150
+ mean_fanout = (sum(per_unit) / len(per_unit)) if per_unit else 0.0
5151
+ covered = {nid for e in c.get("trace_manifest") or []
5152
+ for nid in (e.get("implements") or []) if nid in node_ids}
5153
+ # Degeneracy is a property of the SOURCE units: >=2 source units that EACH claim >= the
5154
+ # threshold share of all nodes -> the manifest cannot tell you which code implements
5155
+ # which intent (everything traces to everything). Min-based, not mean-based, and over
5156
+ # source only: one comprehensive source file legitimately implements a whole tiny
5157
+ # package, and tests naturally exercise everything, so neither is a degeneracy signal.
5158
+ source_units = {u for u, s in units.items() if s == "source"}
5159
+ src_fanout = [len(e.get("implements") or []) for e in c.get("trace_manifest") or []
5160
+ if e.get("unit") in source_units]
5161
+ advisory = {
5162
+ "trace_units": len(manifest_units),
5163
+ "mean_nodes_per_unit": round(mean_fanout, 3),
5164
+ "node_coverage": round(len(covered) / total_nodes, 3),
5165
+ "unexplained_units": sorted(u for u in units if u not in manifest_units),
5166
+ "unresolved_intent_count": len(c.get("unresolved_intent") or []),
5167
+ "degenerate": len(src_fanout) >= 2 and all(
5168
+ (pu / total_nodes) >= _COMPILE_DEGENERATE_FANOUT for pu in src_fanout),
5169
+ "empty_unresolved": len(c.get("unresolved_intent") or []) == 0,
5170
+ }
5171
+ return errors, advisory
5172
+
5173
+
5174
+ def _print_compile_advisory(advisory):
5175
+ if not advisory:
5176
+ return
5177
+ print(" advisory (never gates): coverage %.2f, mean %.2f nodes/unit, %d unresolved_intent%s"
5178
+ % (advisory.get("node_coverage", 0.0), advisory.get("mean_nodes_per_unit", 0.0),
5179
+ advisory.get("unresolved_intent_count", 0),
5180
+ ", DEGENERATE manifest" if advisory.get("degenerate") else ""))
5181
+ if advisory.get("empty_unresolved"):
5182
+ print(" advisory: unresolved_intent is EMPTY -- suspicious (a compiler that made no "
5183
+ "choices is rare); reported, never blocked")
5184
+ for u in advisory.get("unexplained_units") or []:
5185
+ print(" advisory: %s has no trace_manifest entry -- unexplained by construction" % u)
5186
+
5187
+
5188
+ def cmd_compile_validate(args):
5189
+ ir_graph, ir_errors = _load_ir_at(args.ir)
5190
+ if ir_graph is None or ir_errors:
5191
+ for e in ir_errors:
5192
+ print("[qa_ledger] compile-validate: reference IR: %s" % e, file=sys.stderr)
5193
+ sys.exit(2)
5194
+ errors, advisory = _validate_compilation(args.compilation, ir_graph)
5195
+ if args.json:
5196
+ print(json.dumps({"compilation": args.compilation, "ir": args.ir,
5197
+ "valid": not errors, "errors": errors, "advisory": advisory},
5198
+ indent=2, ensure_ascii=False))
5199
+ elif errors:
5200
+ # diagnostics to stderr (Unix convention, and consistent with compile-ingest); the
5201
+ # machine signal is the exit code. stdout stays clean for the VALID payload path.
5202
+ print("COMPILE-VALIDATE %s: REFUSED (%d mechanical violation(s))"
5203
+ % (args.compilation, len(errors)), file=sys.stderr)
5204
+ for e in errors:
5205
+ print(" x %s" % e, file=sys.stderr)
5206
+ else:
5207
+ print("COMPILE-VALIDATE %s: VALID (contract conformant)" % args.compilation)
5208
+ _print_compile_advisory(advisory)
5209
+ sys.exit(2 if errors else 0)
5210
+
5211
+
5212
+ def cmd_compile_ingest(args):
5213
+ """Record a VALIDATED compilation into the ledger (ADR-016). Ingesting an invalid
5214
+ compilation is a refusal. unresolved_intent becomes append-only, content-addressed
5215
+ UINT objects + an ISSUES-DEFERRED.md mirror (the house convention `fix` uses); the
5216
+ by-construction unexplained_code is the set of units with no trace_manifest entry."""
5217
+ ledger = _load(args.ledger)
5218
+ _repo_node(ledger, args.repo)
5219
+ repo_path = _scope_path(ledger, args.repo)
5220
+ ir_graph, ir_errors = _load_ir_at(args.ir)
5221
+ if ir_graph is None or ir_errors:
5222
+ for e in ir_errors:
5223
+ print("[qa_ledger] compile-ingest: reference IR: %s" % e, file=sys.stderr)
5224
+ sys.exit(2)
5225
+ errors, advisory = _validate_compilation(args.compilation, ir_graph)
5226
+ if errors:
5227
+ print("[qa_ledger] compile-ingest: REFUSED -- ingesting an invalid compilation is a "
5228
+ "refusal (%d mechanical violation(s)); run compile-validate." % len(errors),
5229
+ file=sys.stderr)
5230
+ for e in errors:
5231
+ print(" x %s" % e, file=sys.stderr)
5232
+ sys.exit(2)
5233
+ with open(args.compilation, encoding="utf-8-sig") as fh:
5234
+ c = json.load(fh)
5235
+ comp_seal = c.get("_integrity")
5236
+ uints, seen_uint = [], set()
5237
+ for ui in c.get("unresolved_intent") or []:
5238
+ uid = "UINT-" + hashlib.sha256(
5239
+ (ui.get("ir_region", "") + "\n" + ui.get("decision", "")).encode("utf-8")
5240
+ ).hexdigest()[:12]
5241
+ # content-addressed: two entries that resolve to the same id ARE the same intent gap
5242
+ # (same ir_region + decision), deduped within this ingest just as across ingests --
5243
+ # otherwise ISSUES-DEFERRED and the ledger record carry the id twice.
5244
+ if uid in seen_uint:
5245
+ continue
5246
+ seen_uint.add(uid)
5247
+ uints.append({"id": uid, "ir_region": ui.get("ir_region"),
5248
+ "decision": ui.get("decision"), "rationale": ui.get("rationale", "")})
5249
+ comp_rec = {"id": comp_seal[:12], "repo": args.repo,
5250
+ "ir_hash": (c.get("canonical_ir") or {}).get("ir_hash"),
5251
+ "model": (c.get("compilation_report") or {}).get("model"),
5252
+ "target_stack": c.get("target_stack"), "seal": comp_seal,
5253
+ "unexplained_units": advisory.get("unexplained_units") or [],
5254
+ "node_coverage": advisory.get("node_coverage"),
5255
+ "unresolved_intent": uints, "at": _now()}
5256
+ comps = ledger.setdefault("compilations", [])
5257
+ # re-ingest is idempotent PER REPO: the seal is the identity within a repo, and a
5258
+ # byte-identical compilation has nothing to supersede. The compilations list is flat and
5259
+ # cross-repo, so the supersede check MUST be scoped by repo -- otherwise two repos that
5260
+ # legitimately produce the same compilation (a shared/small canonical package -- exactly
5261
+ # this milestone's own fixtures) collide, and the second repo's first ingest is dropped
5262
+ # as a false "superseded". A CHANGED compilation reseals -> a new record. Never a dup.
5263
+ superseded = any(x.get("seal") == comp_seal and x.get("repo") == args.repo for x in comps)
5264
+ if not superseded:
5265
+ comps.append(comp_rec)
5266
+ _save(args.ledger, ledger)
5267
+ new_items = []
5268
+ if uints:
5269
+ dpath = os.path.join(repo_path, ISSUES_DEFERRED_FILE)
5270
+ existing = ""
5271
+ if os.path.isfile(dpath):
5272
+ with open(dpath, encoding="utf-8-sig", errors="replace") as fh:
5273
+ existing = fh.read()
5274
+ add = [u for u in uints if u["id"] not in existing]
5275
+ if add:
5276
+ with open(dpath, "a", encoding="utf-8", newline="\n") as fh:
5277
+ if existing and not existing.endswith("\n"):
5278
+ fh.write("\n")
5279
+ for u in add:
5280
+ fh.write("- [ ] %s (unresolved_intent): %s -- the compiler decided "
5281
+ "'%s' on its own; a candidate canonical improvement (ADR-016)\n"
5282
+ % (u["id"], u["ir_region"], u["decision"]))
5283
+ new_items = [u["id"] for u in add]
5284
+ out = {"repo": args.repo, "compilation": comp_rec["id"], "superseded": superseded,
5285
+ "unresolved_intent": [u["id"] for u in uints],
5286
+ "issues_deferred_new": new_items,
5287
+ "unexplained_units": comp_rec["unexplained_units"]}
5288
+ if args.json:
5289
+ print(json.dumps(out, indent=2, ensure_ascii=False))
5290
+ else:
5291
+ print("COMPILE-INGEST %s: compilation %s%s"
5292
+ % (args.repo, comp_rec["id"],
5293
+ " (byte-identical to a prior ingest -- nothing superseded)"
5294
+ if superseded else ""))
5295
+ print(" %d unresolved_intent -> %d new %s item(s); %d unexplained unit(s)"
5296
+ % (len(uints), len(new_items), ISSUES_DEFERRED_FILE,
5297
+ len(comp_rec["unexplained_units"])))
5298
+ sys.exit(0)
5299
+
5300
+
4658
5301
  # --------------------------------------------------------------------------- #
4659
5302
  # facts (T0 / SYSTEM-FACTS: public claims become compiled artifacts of repo
4660
5303
  # facts -- Diamond applied to Diamond. ADR-012.)
@@ -8702,9 +9345,51 @@ def build_parser():
8702
9345
  pfv.add_argument("--repo", required=True)
8703
9346
  pfv.add_argument("--config", default="uscha.config.json",
8704
9347
  help="checked for defaults.fidelity.gate -- advisory there is a refusal")
9348
+ pfv.add_argument("--ir", action="store_true",
9349
+ help="answer curation_closure as a path query over the IR graph "
9350
+ "(ADR-015); reproduces v0 from the derived index")
8705
9351
  pfv.add_argument("--json", action="store_true")
8706
9352
  pfv.set_defaults(func=cmd_fidelity)
8707
9353
 
9354
+ pie = sub.add_parser(
9355
+ "ir-extract",
9356
+ help="extract the canonical package into a typed graph (ir/IR.json); what cannot be "
9357
+ "typed deterministically is UNTYPED, counted, never guessed (ADR-015)")
9358
+ pie.add_argument("--ledger", default="QA-LEDGER.json")
9359
+ pie.add_argument("--repo", required=True)
9360
+ pie.add_argument("--json", action="store_true")
9361
+ pie.set_defaults(func=cmd_ir_extract)
9362
+
9363
+ pir = sub.add_parser(
9364
+ "ir-render",
9365
+ help="regenerate the human view (ir/IR.md) from the graph; round-trip content-stable "
9366
+ "for the structured parts (ADR-015)")
9367
+ pir.add_argument("--ledger", default="QA-LEDGER.json")
9368
+ pir.add_argument("--repo", required=True)
9369
+ pir.set_defaults(func=cmd_ir_render)
9370
+
9371
+ pcmv = sub.add_parser(
9372
+ "compile-validate",
9373
+ help="validate a COMPILATION.json against a reference IR (ADR-016); only mechanical "
9374
+ "violations gate, degeneracy stats are advisory and NEVER block")
9375
+ pcmv.add_argument("--ir", required=True,
9376
+ help="the reference IR.json the compilation targets")
9377
+ pcmv.add_argument("--compilation", required=True, help="path to COMPILATION.json")
9378
+ pcmv.add_argument("--json", action="store_true")
9379
+ pcmv.set_defaults(func=cmd_compile_validate)
9380
+
9381
+ pcmi = sub.add_parser(
9382
+ "compile-ingest",
9383
+ help="record a VALIDATED compilation into the ledger (ADR-016): by-construction "
9384
+ "unexplained_code + unresolved_intent as append-only UINT objects + backlog")
9385
+ pcmi.add_argument("--ledger", default="QA-LEDGER.json")
9386
+ pcmi.add_argument("--repo", required=True)
9387
+ pcmi.add_argument("--ir", required=True,
9388
+ help="the reference IR.json the compilation targets")
9389
+ pcmi.add_argument("--compilation", required=True, help="path to COMPILATION.json")
9390
+ pcmi.add_argument("--json", action="store_true")
9391
+ pcmi.set_defaults(func=cmd_compile_ingest)
9392
+
8708
9393
  pcr = sub.add_parser("cleanroom",
8709
9394
  help="run a command against a CLEAN checkout of one commit in a throwaway worktree (ADR-008)")
8710
9395
  pcr.add_argument("--ledger", default="QA-LEDGER.json")