#!/usr/bin/env python3 """Validate auditor assertions and deterministically construct semantic candidate pairs.""" from __future__ import annotations import argparse from collections import defaultdict from itertools import combinations import hashlib import json from pathlib import Path import re import sys from typing import Any, Iterable, Mapping import semantic_surface_extractor DEFAULT_ROOT = Path(__file__).resolve().parents[2] DEFAULT_ONTOLOGY = Path("harness/source/semantic-ontology.json") RESULT_SCHEMA = "semantic-candidate-result/v1" ASSERTION_SCHEMA = "semantic-assertion-result/v1" ONTOLOGY_SCHEMA = "semantic-ontology/v1" STABLE_REF_RE = re.compile(r"\b(?:ART|DELEG|FLOW|[A-Z][A-Z0-9]*)(?:-[A-Z0-9]+)+-\d{3}(?:@[1-9]\d*)?\b") SHARED_LITERAL_RE = re.compile(r"`([^`\n]+)`|(? bytes: return semantic_surface_extractor.canonical_json_bytes(value) def load_ontology(root: Path, ontology_path: Path = DEFAULT_ONTOLOGY) -> Mapping[str, Any]: source = ontology_path if ontology_path.is_absolute() else root / ontology_path try: data = json.loads(source.read_text(encoding="utf-8")) except (OSError, UnicodeError, json.JSONDecodeError) as exc: raise SemanticCandidateError(f"semantic ontology error: {exc}") from exc required = {"schema_version", "auditor_contract_version", "predicates", "modalities", "verdicts", "blocking", "candidate_rules"} if not isinstance(data, dict) or set(data) != required or data.get("schema_version") != ONTOLOGY_SCHEMA: raise SemanticCandidateError(f"expected exact {ONTOLOGY_SCHEMA} schema") expected_predicates = { "owns", "produces", "consumes", "returns", "validates", "maps_to", "runs_before", "runs_after", "uses", "requires", "forbids", "enforces", "delegates", "has_schema", "has_threshold", "has_cardinality", "has_failure_behavior", "other", } expected_verdicts = { "CONSISTENT", "COMPLEMENTARY", "CONTEXTUAL_VARIANT", "AMBIGUOUS_AUTHORITY", "RESTATEMENT_DRIFT", "CONTRADICTION", } if set(data["predicates"]) != expected_predicates: raise SemanticCandidateError("predicate ontology differs from the required exact set") if set(data["verdicts"]) != expected_verdicts: raise SemanticCandidateError("semantic verdict set differs from the required exact set") rule_ids = [item.get("id") for item in data["candidate_rules"] if isinstance(item, dict)] if rule_ids != [f"C{index}" for index in range(1, 8)]: raise SemanticCandidateError("candidate rules must be exactly C1..C7 in order") return data def _assertion(value: Any, index: int, surfaces: Mapping[str, Mapping[str, Any]], ontology: Mapping[str, Any], root: Path) -> dict[str, Any]: fields = { "assertion_id", "source_surface", "subject", "predicate", "object", "condition", "modality", "scope", "quote", "line_start", "line_end", } if not isinstance(value, dict) or set(value) != fields: raise SemanticCandidateError(f"assertions[{index}] must contain exactly {sorted(fields)}") for field in fields - {"line_start", "line_end"}: if not isinstance(value[field], str) or not value[field].strip(): raise SemanticCandidateError(f"assertions[{index}].{field} must be non-empty") if value["predicate"] not in ontology["predicates"]: raise SemanticCandidateError(f"assertions[{index}].predicate is outside ontology") if value["modality"] not in ontology["modalities"]: raise SemanticCandidateError(f"assertions[{index}].modality is outside ontology") if not isinstance(value["line_start"], int) or not isinstance(value["line_end"], int) or value["line_start"] < 1 or value["line_end"] < value["line_start"]: raise SemanticCandidateError(f"assertions[{index}] has invalid line range") surface = surfaces.get(value["source_surface"]) if surface is None: raise SemanticCandidateError(f"assertions[{index}] references unknown source surface") if value["line_start"] < surface["line_start"] or value["line_end"] > surface["line_end"]: raise SemanticCandidateError(f"assertions[{index}] quote range escapes its source surface") document = root / str(surface["path"]) lines = document.read_text(encoding="utf-8").splitlines() observed = "\n".join(lines[value["line_start"] - 1 : value["line_end"]]) if observed != value["quote"]: raise SemanticCandidateError(f"assertions[{index}] exact UTF-8 quote/line verification failed") return {field: value[field] for field in sorted(fields)} def validate_assertions(root: Path, extraction: Mapping[str, Any], assertion_result: Any, ontology: Mapping[str, Any]) -> list[dict[str, Any]]: if not isinstance(assertion_result, dict) or set(assertion_result) != {"schema_version", "subject", "mode", "surface_manifest_sha256", "assertions"}: raise SemanticCandidateError("assertion result has missing or unknown fields") if assertion_result.get("schema_version") != ASSERTION_SCHEMA: raise SemanticCandidateError(f"expected {ASSERTION_SCHEMA}") if assertion_result.get("subject") != extraction.get("path") or assertion_result.get("mode") != extraction.get("mode"): raise SemanticCandidateError("assertion result subject/mode does not match extraction") manifest_hash = hashlib.sha256(_canonical(extraction)).hexdigest() if assertion_result.get("surface_manifest_sha256") != manifest_hash: raise SemanticCandidateError("assertion result is not bound to current surface manifest") raw_assertions = assertion_result.get("assertions") if not isinstance(raw_assertions, list): raise SemanticCandidateError("assertions must be an array") surfaces = {item["surface_id"]: item for item in extraction.get("surfaces", [])} assertions = [_assertion(item, index, surfaces, ontology, root) for index, item in enumerate(raw_assertions)] identifiers = [item["assertion_id"] for item in assertions] if len(identifiers) != len(set(identifiers)): raise SemanticCandidateError("assertion_id values must be unique") covered = {item["source_surface"] for item in assertions} missing = sorted(set(surfaces) - covered) if missing: raise SemanticCandidateError(f"auditor silently dropped surfaces without assertions: {missing}") return sorted(assertions, key=lambda item: item["assertion_id"]) def _norm(value: str) -> str: return re.sub(r"\s+", " ", value.strip().casefold()) def _literals(assertion: Mapping[str, Any]) -> set[str]: text = f"{assertion['object']} {assertion['quote']}" result: set[str] = set() for match in SHARED_LITERAL_RE.finditer(text): captured = next((group for group in match.groups() if group is not None), match.group(0)) result.add(_norm(captured)) return {item for item in result if len(item) >= 3} def _pairs(values: Iterable[Mapping[str, Any]]) -> Iterable[tuple[Mapping[str, Any], Mapping[str, Any]]]: yield from combinations(sorted(values, key=lambda item: str(item["assertion_id"])), 2) def build(root: Path, extraction: Mapping[str, Any], assertion_result: Any, ontology_path: Path = DEFAULT_ONTOLOGY) -> dict[str, Any]: root = root.resolve(strict=True) ontology = load_ontology(root, ontology_path) assertions = validate_assertions(root, extraction, assertion_result, ontology) reasons: dict[tuple[str, str], set[str]] = defaultdict(set) by_base: dict[tuple[str, str, str, str], list[dict[str, Any]]] = defaultdict(list) for item in assertions: by_base[(_norm(item["subject"]), item["predicate"], _norm(item["condition"]), _norm(item["scope"]))].append(item) for values in by_base.values(): for left, right in _pairs(values): reasons[(left["assertion_id"], right["assertion_id"])].add("BASE") for left, right in _pairs(assertions): predicates = {left["predicate"], right["predicate"]} same_subject = _norm(left["subject"]) == _norm(right["subject"]) same_object = _norm(left["object"]) == _norm(right["object"]) same_context = _norm(left["condition"]) == _norm(right["condition"]) and _norm(left["scope"]) == _norm(right["scope"]) pair = (left["assertion_id"], right["assertion_id"]) if same_object and predicates <= {"produces", "consumes"} and predicates == {"produces", "consumes"}: reasons[pair].add("C1") if same_subject and "stage" in _norm(left["subject"]) and predicates <= {"owns", "consumes", "produces", "returns", "runs_before", "runs_after"}: reasons[pair].add("C2") if same_subject and ("gate" in _norm(left["subject"]) or "contract" in _norm(left["subject"])) and predicates <= {"requires", "enforces", "uses", "forbids"}: reasons[pair].add("C3") if same_subject and same_object and same_context and {left["modality"], right["modality"]} == {"must", "must_not"}: reasons[pair].add("C4") if same_subject and predicates <= {"returns", "validates", "maps_to"}: reasons[pair].add("C5") if _literals(left).intersection(_literals(right)): reasons[pair].add("C6") left_refs = set(STABLE_REF_RE.findall(f"{left['object']} {left['quote']}")) right_refs = set(STABLE_REF_RE.findall(f"{right['object']} {right['quote']}")) if left_refs.intersection(right_refs): reasons[pair].add("C7") by_id = {item["assertion_id"]: item for item in assertions} candidates: list[dict[str, Any]] = [] for pair, rule_ids in sorted(reasons.items()): left, right = (by_id[pair[0]], by_id[pair[1]]) digest = hashlib.sha256((pair[0] + "\0" + pair[1] + "\0" + ",".join(sorted(rule_ids))).encode("utf-8")).hexdigest() candidates.append({ "candidate_id": "SEM-" + digest[:20].upper(), "assertion_a": pair[0], "assertion_b": pair[1], "rule_ids": sorted(rule_ids, key=lambda item: (item != "BASE", item)), "grouping_key": { "subject": left["subject"] if _norm(left["subject"]) == _norm(right["subject"]) else "", "predicate": left["predicate"] if left["predicate"] == right["predicate"] else "", "condition": left["condition"] if _norm(left["condition"]) == _norm(right["condition"]) else "", "scope": left["scope"] if _norm(left["scope"]) == _norm(right["scope"]) else "", }, }) return { "schema_version": RESULT_SCHEMA, "status": "PASS", "subject": extraction["path"], "mode": extraction["mode"], "document_sha256": extraction["document_sha256"], "surface_manifest_sha256": hashlib.sha256(_canonical(extraction)).hexdigest(), "ontology_sha256": hashlib.sha256(_canonical(ontology)).hexdigest(), "assertions_sha256": hashlib.sha256(_canonical(assertions)).hexdigest(), "coverage": { "eligible_surfaces": extraction["coverage"]["eligible_surface_blocks"], "processed_surfaces": len({item["source_surface"] for item in assertions}) + extraction["coverage"]["explicitly_excluded_blocks"], "assertions": len(assertions), "candidate_pairs": len(candidates), }, "assertions": assertions, "candidates": candidates, "findings": [], } def structural_check(root: Path, ontology_path: Path = DEFAULT_ONTOLOGY) -> dict[str, Any]: root = root.resolve(strict=True) ontology = load_ontology(root, ontology_path) surfaces = semantic_surface_extractor.check(root) return { "schema_version": RESULT_SCHEMA, "status": surfaces["status"], "check_kind": "STRUCTURE_ONLY", "semantic_verdict": "NOT_EVALUATED", "ontology_sha256": hashlib.sha256(_canonical(ontology)).hexdigest(), "surface_document_count": surfaces["document_count"], "coverage": surfaces["coverage"], "findings": surfaces["findings"], } def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--root", type=Path, default=DEFAULT_ROOT) parser.add_argument("--ontology", type=Path, default=DEFAULT_ONTOLOGY) parser.add_argument("--document", type=Path) parser.add_argument("--assertions", type=Path) parser.add_argument("--check", action="store_true") args = parser.parse_args(argv) try: root = args.root.resolve(strict=True) if args.document is None and args.assertions is None: result = structural_check(root, args.ontology) elif args.document is not None and args.assertions is not None: policy = semantic_surface_extractor.load_policy(root) document = args.document if args.document.is_absolute() else root / args.document extraction = semantic_surface_extractor.extract_document(root, document, policy) assertion_result = json.loads(args.assertions.read_text(encoding="utf-8")) result = build(root, extraction, assertion_result, args.ontology) else: raise SemanticCandidateError("--document and --assertions must be supplied together") exit_code = 0 if result["status"] == "PASS" else 1 except (SemanticCandidateError, semantic_surface_extractor.SemanticSurfaceError, OSError, UnicodeError, json.JSONDecodeError) as exc: result = {"schema_version": RESULT_SCHEMA, "status": "ERROR", "errors": [{"code": "SEMANTIC_CANDIDATE_ERROR", "message": str(exc)}]} exit_code = 2 json.dump(result, sys.stdout, ensure_ascii=False, indent=2, sort_keys=True) sys.stdout.write("\n") return exit_code if __name__ == "__main__": raise SystemExit(main())