From 6eec2d90438e3394a5b7d31534d4e0ca0689d76e Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 08:51:53 -0700 Subject: [PATCH 01/19] Add two-phase held-out chemistry validation harness --- epac_heldout_validation.py | 116 +++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 epac_heldout_validation.py diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py new file mode 100644 index 0000000..c1da665 --- /dev/null +++ b/epac_heldout_validation.py @@ -0,0 +1,116 @@ +"""Two-phase held-out chemistry validation for EPAC. + +The construction side may create a prediction commitment without importing or +reading the oracle corpus. Only the comparison phase accepts oracle facts. +This keeps empirical chemistry labels out of EPAC derivation. +""" +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from typing import Any, Mapping + +STATUSES = ("SURVIVED", "FALSIFIED", "UNRESOLVED") + + +def _canonical(value: Any) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8") + + +def _digest(value: Any) -> str: + return hashlib.sha256(_canonical(value)).hexdigest() + + +def freeze_predictions(predictions: Mapping[str, Any], *, source_identity: str) -> dict[str, Any]: + """Commit predictions before any oracle is supplied.""" + if not source_identity: + raise ValueError("source_identity is required") + normalized = {str(k): v for k, v in sorted(predictions.items())} + envelope = { + "schema": "epac.heldout-prediction-commitment", + "version": "v1", + "source_identity": source_identity, + "predictions": normalized, + } + return {**envelope, "commitment_sha256": _digest(envelope)} + + +def verify_commitment(commitment: Mapping[str, Any]) -> None: + required = {"schema", "version", "source_identity", "predictions", "commitment_sha256"} + if set(commitment) != required: + raise ValueError("invalid prediction commitment envelope") + if commitment["schema"] != "epac.heldout-prediction-commitment" or commitment["version"] != "v1": + raise ValueError("unsupported prediction commitment") + unsigned = {k: commitment[k] for k in ("schema", "version", "source_identity", "predictions")} + if _digest(unsigned) != commitment["commitment_sha256"]: + raise ValueError("prediction commitment digest mismatch") + + +def _compare(expected: Any, predicted: Any, rule: Mapping[str, Any]) -> str: + kind = rule.get("kind", "exact") + if kind == "exact": + return "SURVIVED" if predicted == expected else "FALSIFIED" + if kind == "set-equality": + try: + return "SURVIVED" if set(predicted) == set(expected) else "FALSIFIED" + except TypeError: + return "UNRESOLVED" + if kind == "numeric-tolerance": + tolerance = rule.get("absolute_tolerance") + if not isinstance(tolerance, (int, float)) or isinstance(tolerance, bool) or tolerance < 0: + return "UNRESOLVED" + try: + return "SURVIVED" if abs(float(predicted) - float(expected)) <= tolerance else "FALSIFIED" + except (TypeError, ValueError): + return "UNRESOLVED" + return "UNRESOLVED" + + +def compare_after_freeze(commitment: Mapping[str, Any], oracle: Mapping[str, Any]) -> dict[str, Any]: + """Reveal an independently sourced oracle only after commitment verification.""" + verify_commitment(commitment) + if oracle.get("schema") != "epac.heldout-chemistry-oracle" or oracle.get("version") != "v1": + raise ValueError("unsupported held-out oracle") + cases = oracle.get("cases") + if not isinstance(cases, list): + raise ValueError("oracle cases must be a list") + + predictions = commitment["predictions"] + results = [] + for case in cases: + case_id = case.get("id") + provenance = case.get("provenance", {}) + authoritative = provenance.get("authority") + locator = provenance.get("locator") + if not case_id or not authoritative or not locator: + results.append({"id": case_id, "status": "UNRESOLVED", "reason": "missing case/provenance identity"}) + continue + if case_id not in predictions: + results.append({"id": case_id, "status": "UNRESOLVED", "reason": "no frozen prediction"}) + continue + status = _compare(case.get("expected"), predictions[case_id], case.get("comparison", {"kind": "exact"})) + results.append({ + "id": case_id, + "domain": case.get("domain"), + "status": status, + "predicted": predictions[case_id], + "expected": case.get("expected"), + "provenance": provenance, + }) + + counts = {status: sum(r["status"] == status for r in results) for status in STATUSES} + receipt = { + "schema": "epac.heldout-validation-receipt", + "version": "v1", + "prediction_commitment_sha256": commitment["commitment_sha256"], + "oracle_sha256": _digest(oracle), + "results": results, + "counts": counts, + } + return {**receipt, "receipt_sha256": _digest(receipt)} + + +def load_oracle(path: str | Path) -> dict[str, Any]: + """Comparison-side helper. Construction modules must not call this.""" + return json.loads(Path(path).read_text(encoding="utf-8")) From 579e036ec6f743e5ec3f050bfe1fedfa6df69d87 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 08:51:56 -0700 Subject: [PATCH 02/19] Test held-out freeze/reveal validation boundary --- tests/test_heldout_validation.py | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/test_heldout_validation.py diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py new file mode 100644 index 0000000..96c461e --- /dev/null +++ b/tests/test_heldout_validation.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from copy import deepcopy + +import pytest + +from epac_heldout_validation import compare_after_freeze, freeze_predictions, verify_commitment + + +def oracle(): + return { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [ + {"id": "element:H:valence", "domain": "valence", "expected": 1, + "comparison": {"kind": "exact"}, + "provenance": {"authority": "fixture-authority", "locator": "fixture:H"}}, + {"id": "element:O:oxidation", "domain": "oxidation-state", "expected": [-2], + "comparison": {"kind": "set-equality"}, + "provenance": {"authority": "fixture-authority", "locator": "fixture:O"}}, + {"id": "isotope:H-1:mass", "domain": "isotope", "expected": 1.007825, + "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": 0.000001}, + "provenance": {"authority": "fixture-authority", "locator": "fixture:H-1"}}, + ], + } + + +def test_freeze_is_deterministic_and_oracle_free(): + a = freeze_predictions({"b": 2, "a": 1}, source_identity="epac@test") + b = freeze_predictions({"a": 1, "b": 2}, source_identity="epac@test") + assert a == b + assert "oracle" not in repr(a).lower() + verify_commitment(a) + + +def test_tampered_prediction_cannot_be_compared(): + frozen = freeze_predictions({"element:H:valence": 1}, source_identity="epac@test") + tampered = deepcopy(frozen) + tampered["predictions"]["element:H:valence"] = 2 + with pytest.raises(ValueError, match="digest mismatch"): + compare_after_freeze(tampered, oracle()) + + +def test_comparison_classifies_only_after_freeze(): + frozen = freeze_predictions({ + "element:H:valence": 1, + "element:O:oxidation": [-2, 2], + "isotope:H-1:mass": 1.0078251, + }, source_identity="epac@test") + receipt = compare_after_freeze(frozen, oracle()) + assert receipt["counts"] == {"SURVIVED": 2, "FALSIFIED": 1, "UNRESOLVED": 0} + assert receipt["prediction_commitment_sha256"] == frozen["commitment_sha256"] + assert len(receipt["oracle_sha256"]) == 64 + assert len(receipt["receipt_sha256"]) == 64 + + +def test_missing_prediction_and_bad_provenance_are_unresolved(): + o = oracle() + o["cases"].append({"id": "reaction:x", "expected": "y", "provenance": {}}) + frozen = freeze_predictions({}, source_identity="epac@test") + receipt = compare_after_freeze(frozen, o) + assert receipt["counts"]["UNRESOLVED"] == 4 From 7fa48150bc3b171d0b17869804bf587b83eefebc Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 08:51:59 -0700 Subject: [PATCH 03/19] Add empty held-out chemistry oracle envelope --- data/heldout_chemistry_oracle.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 data/heldout_chemistry_oracle.json diff --git a/data/heldout_chemistry_oracle.json b/data/heldout_chemistry_oracle.json new file mode 100644 index 0000000..c201afe --- /dev/null +++ b/data/heldout_chemistry_oracle.json @@ -0,0 +1,12 @@ +{ + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "policy": { + "role": "comparison-only", + "construction_access": "forbidden", + "discovery_sources": ["Periodic Table app or other chemistry references"], + "acceptance": "Important values require an independently identifiable authoritative source before scoring." + }, + "domains": ["isotope", "oxidation-state", "valence", "reaction", "element-property"], + "cases": [] +} From 0e9591d8c2ab43e845de731b361724e959bbd0cd Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 08:52:02 -0700 Subject: [PATCH 04/19] Document held-out chemistry validation protocol --- docs/heldout-chemistry-validation.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/heldout-chemistry-validation.md diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md new file mode 100644 index 0000000..02250c2 --- /dev/null +++ b/docs/heldout-chemistry-validation.md @@ -0,0 +1,26 @@ +# Held-out chemistry validation + +Purpose: test EPAC against chemistry facts without feeding those facts into EPAC construction. + +## Boundary + +1. Run EPAC construction and produce predictions keyed by stable case IDs. +2. Call `freeze_predictions(..., source_identity=)`. +3. Persist the returned commitment. Its SHA-256 binds the predictions. +4. Only then expose/load an oracle corpus. +5. Call `compare_after_freeze(commitment, oracle)`. +6. Preserve the receipt and its `SURVIVED / FALSIFIED / UNRESOLVED` results. + +The chemistry app is a **discovery/reference surface**, not an EPAC dependency and not the sole authority. Facts used for scoring carry an authority plus locator. Missing prediction, missing provenance, unsupported comparison, or incomparable data is `UNRESOLVED`; it is never silently counted as success. + +The checked-in `data/heldout_chemistry_oracle.json` is intentionally empty. Populate it only with facts selected independently of EPAC outputs. This prevents choosing test cases after seeing what EPAC predicts. + +Recommended stable IDs include `element:H:valence`, `element:O:oxidation`, `isotope:H-1:mass`, and `reaction::products`. The corpus can grow across isotope behavior, oxidation/valence, reaction outcomes, and element properties without changing EPAC construction code. + +## Failure definition + +- **SURVIVED**: frozen prediction satisfies the preregistered comparator. +- **FALSIFIED**: frozen prediction is comparable and disagrees. +- **UNRESOLVED**: the comparison cannot honestly decide. + +The receipt binds both the frozen prediction commitment and the revealed oracle digest. From 1cc98feea010eb2a862c05ce72e37daf33a6b909 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 08:52:09 -0700 Subject: [PATCH 05/19] Package held-out validation harness --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e08ee5b..45b27f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ test = ["pytest==9.1.1"] build = ["build==1.5.0", "twine==7.0.0", "setuptools==84.0.0", "wheel==0.48.0"] [tool.setuptools] -py-modules = ["epac_evidence_cache", "epac_atomic", "epac_atomic_derivation", "epac_b_derivation", "epac_comparison", "epac_dimensional_arity", "epac_molecular", "epac_periodic", "epac_public_gonol", "epac_ucns_provenance", "epac_boundary_minimal_refinement", "epac_boundary_nondegeneracy", "epac_boundary_probe_completeness", "epac_boundary_quotient", "epac_cross_scale_closure"] +py-modules = ["epac_heldout_validation", "epac_evidence_cache", "epac_atomic", "epac_atomic_derivation", "epac_b_derivation", "epac_comparison", "epac_dimensional_arity", "epac_molecular", "epac_periodic", "epac_public_gonol", "epac_ucns_provenance", "epac_boundary_minimal_refinement", "epac_boundary_nondegeneracy", "epac_boundary_probe_completeness", "epac_boundary_quotient", "epac_cross_scale_closure"] packages = ["epac_data", "epac_subatomic", "epac_viz"] package-dir = {epac_data = "data", epac_subatomic = "subatomic", epac_viz = "viz"} From 1281ddd8e3d8c8b5744537fb25944b7343264c41 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 11:58:07 -0700 Subject: [PATCH 06/19] Repair held-out validation review and replay gates --- data/heldout_chemistry_oracle.json | 14 +- docs/heldout-chemistry-validation.md | 118 ++++++- epac_boundary_probe_completeness.py | 3 + epac_heldout_validation.py | 356 +++++++++++++++++++--- tests/test_boundary_probe_completeness.py | 24 +- tests/test_heldout_validation.py | 263 ++++++++++++++-- 6 files changed, 704 insertions(+), 74 deletions(-) diff --git a/data/heldout_chemistry_oracle.json b/data/heldout_chemistry_oracle.json index c201afe..a990e6e 100644 --- a/data/heldout_chemistry_oracle.json +++ b/data/heldout_chemistry_oracle.json @@ -4,9 +4,19 @@ "policy": { "role": "comparison-only", "construction_access": "forbidden", - "discovery_sources": ["Periodic Table app or other chemistry references"], + "selection_binding": "Case IDs, domains, and comparator rules must be frozen and persisted in an epac.heldout-validation-plan commitment before predictions are frozen or revealed.", + "empty_corpus_semantics": "Packaging default only; anti-selection comes from the frozen validation plan, not corpus emptiness.", + "discovery_sources": [ + "Periodic Table app or other chemistry references" + ], "acceptance": "Important values require an independently identifiable authoritative source before scoring." }, - "domains": ["isotope", "oxidation-state", "valence", "reaction", "element-property"], + "domains": [ + "isotope", + "oxidation-state", + "valence", + "reaction", + "element-property" + ], "cases": [] } diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index 02250c2..2e202ee 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -4,23 +4,123 @@ Purpose: test EPAC against chemistry facts without feeding those facts into EPAC ## Boundary -1. Run EPAC construction and produce predictions keyed by stable case IDs. -2. Call `freeze_predictions(..., source_identity=)`. -3. Persist the returned commitment. Its SHA-256 binds the predictions. -4. Only then expose/load an oracle corpus. -5. Call `compare_after_freeze(commitment, oracle)`. -6. Preserve the receipt and its `SURVIVED / FALSIFIED / UNRESOLVED` results. +The selection boundary is committed before EPAC predictions are produced or revealed: -The chemistry app is a **discovery/reference surface**, not an EPAC dependency and not the sole authority. Facts used for scoring carry an authority plus locator. Missing prediction, missing provenance, unsupported comparison, or incomparable data is `UNRESOLVED`; it is never silently counted as success. +1. Choose stable case IDs, domains, and comparator rules only; do not include expected values or provenance. +2. Call `freeze_validation_plan(...)` and persist the returned plan commitment. +3. Run EPAC construction and produce predictions only for IDs in that frozen plan. +4. Call `freeze_predictions(..., source_identity=, validation_plan=)` and persist the prediction commitment. +5. Only then reveal/load a held-out oracle containing the same case IDs, domains, and comparator rules plus expected values and provenance. +6. Call `compare_after_freeze(commitment, oracle, validation_plan=)`. +7. Preserve the receipt and its `SURVIVED / FALSIFIED / UNRESOLVED` results. -The checked-in `data/heldout_chemistry_oracle.json` is intentionally empty. Populate it only with facts selected independently of EPAC outputs. This prevents choosing test cases after seeing what EPAC predicts. +The flow is: + +`frozen validation plan → EPAC derivation → frozen prediction → held-out oracle → comparator → evidence receipt` + +The chemistry app is a **discovery/reference surface**, not an EPAC construction dependency and not the sole authority. Facts used for scoring carry an authority plus locator. Missing predictions, absent expected values, missing/null provenance, unsupported comparisons, and non-finite numeric comparisons are `UNRESOLVED`; they are never silently counted as success. Duplicate oracle IDs or drift in the frozen case inventory/domain/comparator contract are rejected before scoring. + +The checked-in `data/heldout_chemistry_oracle.json` is intentionally empty as a distributable comparison-side resource and smoke-test default. Its emptiness does **not** prove independent case selection. The anti-selection control is the persisted validation-plan commitment that binds case identity and comparator rules before the prediction commitment exists. Stronger chronology or independent custody can be supplied by an external evidence store without changing EPAC construction. Recommended stable IDs include `element:H:valence`, `element:O:oxidation`, `isotope:H-1:mass`, and `reaction::products`. The corpus can grow across isotope behavior, oxidation/valence, reaction outcomes, and element properties without changing EPAC construction code. +## Runnable end-to-end example + +This runs from a source checkout or the installed distribution. It persists and reloads both commitments before revealing a synthetic held-out value, exercises packaged-oracle loading, then persists the evidence receipt. + +```bash +python - <<'PY' +import json +from pathlib import Path +from tempfile import TemporaryDirectory + +from epac_heldout_validation import ( + compare_after_freeze, + freeze_predictions, + freeze_validation_plan, + load_oracle, + load_packaged_oracle, +) + +with TemporaryDirectory() as tmp: + root = Path(tmp) + + plan = freeze_validation_plan([ + { + "id": "example:scalar", + "domain": "example", + "comparison": {"kind": "exact"}, + } + ]) + (root / "plan.json").write_text(json.dumps(plan), encoding="utf-8") + plan = json.loads((root / "plan.json").read_text(encoding="utf-8")) + + # Replace this literal with an EPAC-derived prediction in a real run. + predictions = {"example:scalar": 7} + commitment = freeze_predictions( + predictions, + source_identity="epac@example-exact-head-or-receipt", + validation_plan=plan, + ) + (root / "prediction.json").write_text( + json.dumps(commitment), encoding="utf-8" + ) + commitment = json.loads( + (root / "prediction.json").read_text(encoding="utf-8") + ) + + # The package ships an empty comparison-side resource; loading it works + # identically from a checkout or installed wheel. + packaged = load_packaged_oracle() + assert packaged["schema"] == "epac.heldout-chemistry-oracle" + + # Reveal/write held-out evidence only after the two commitments above. + oracle = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "example:scalar", + "domain": "example", + "comparison": {"kind": "exact"}, + "expected": 7, + "provenance": { + "authority": "example-authority", + "locator": "example:7", + }, + }], + } + (root / "oracle.json").write_text(json.dumps(oracle), encoding="utf-8") + oracle = load_oracle(root / "oracle.json") + + receipt = compare_after_freeze( + commitment, + oracle, + validation_plan=plan, + ) + (root / "receipt.json").write_text(json.dumps(receipt), encoding="utf-8") + preserved = json.loads((root / "receipt.json").read_text(encoding="utf-8")) + + assert preserved["counts"] == { + "SURVIVED": 1, + "FALSIFIED": 0, + "UNRESOLVED": 0, + } + assert preserved["validation_plan_sha256"] == plan["plan_sha256"] + assert preserved["prediction_commitment_sha256"] == commitment["commitment_sha256"] + assert len(preserved["oracle_sha256"]) == 64 + assert len(preserved["receipt_sha256"]) == 64 + print(preserved["counts"]) +PY +``` + ## Failure definition - **SURVIVED**: frozen prediction satisfies the preregistered comparator. - **FALSIFIED**: frozen prediction is comparable and disagrees. - **UNRESOLVED**: the comparison cannot honestly decide. -The receipt binds both the frozen prediction commitment and the revealed oracle digest. +The receipt binds the frozen validation plan, frozen prediction commitment, revealed oracle digest, and detached result evidence. + +## hmmm + +The module proves its own ordering and digest relationships; it does not by itself prove that a human or external curator lacked access to predictions before creating the plan. If that stronger claim matters, persist the plan digest in an independently controlled/timestamped evidence system before EPAC derivation. diff --git a/epac_boundary_probe_completeness.py b/epac_boundary_probe_completeness.py index 0199d0f..c81db34 100644 --- a/epac_boundary_probe_completeness.py +++ b/epac_boundary_probe_completeness.py @@ -113,6 +113,7 @@ ObservableFn = Callable[[StateContext], Observable] OPERATION_SOURCE_FILES = ( + "epac_heldout_validation.py", "epac_evidence_cache.py", "viz/__init__.py", "subatomic/__init__.py", @@ -355,6 +356,8 @@ def _declared_operations() -> tuple[dict[str, str], ...]: def _classify_operation(module: str, name: str) -> str: + if module == "epac_heldout_validation": + return INTERNAL_NON_BOUNDARY if module in { "epac_atomic", "epac_atomic_derivation", diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index c1da665..3202967 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -2,16 +2,51 @@ The construction side may create a prediction commitment without importing or reading the oracle corpus. Only the comparison phase accepts oracle facts. -This keeps empirical chemistry labels out of EPAC derivation. +This keeps empirical chemistry labels out of EPAC derivation while preventing post-hoc case or +comparator selection after predictions are revealed. + +Usage: freeze and persist freeze_validation_plan first; derive EPAC predictions +externally; call and persist freeze_predictions; only then load the held-out +oracle and call compare_after_freeze. A complete runnable source/install example +lives in docs/heldout-chemistry-validation.md. """ from __future__ import annotations +from collections.abc import Mapping +from copy import deepcopy import hashlib +from importlib.resources import files import json +import math from pathlib import Path -from typing import Any, Mapping +from typing import Any + +# === MODULE_BUILD === +# id: epac_heldout_validation +# module_name: epac_heldout_validation +# module_kind: experiment +# summary: comparison-only held-out validation boundary that preregisters case identities and comparator rules before EPAC predictions are frozen and oracle values are revealed +# owner: The Interdependency +# public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_oracle, load_packaged_oracle +# internal_surface: _canonical, _digest, _verify_validation_plan, _json_exact_equal, _json_token, _finite_number, _compare +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: tests.test_heldout_validation +# rollout: opt-in research validation surface; callers persist the validation-plan commitment before prediction commitment and reveal independently sourced oracle values only during comparison +# rollback: remove this module, its package entry, tests, oracle fixture, and documentation without changing EPAC construction modules or chemistry derivation behavior +# since: 2026-09-29 +# unresolved: external oracle authority selection and custody remain outside EPAC; this module binds supplied evidence but does not establish independent custody by itself +# === END MODULE_BUILD === STATUSES = ("SURVIVED", "FALSIFIED", "UNRESOLVED") +_PLAN_SCHEMA = "epac.heldout-validation-plan" +_PREDICTION_SCHEMA = "epac.heldout-prediction-commitment" +_ORACLE_SCHEMA = "epac.heldout-chemistry-oracle" +_RECEIPT_SCHEMA = "epac.heldout-validation-receipt" +_VERSION = "v1" def _canonical(value: Any) -> bytes: @@ -22,87 +57,315 @@ def _digest(value: Any) -> str: return hashlib.sha256(_canonical(value)).hexdigest() -def freeze_predictions(predictions: Mapping[str, Any], *, source_identity: str) -> dict[str, Any]: - """Commit predictions before any oracle is supplied.""" - if not source_identity: - raise ValueError("source_identity is required") - normalized = {str(k): v for k, v in sorted(predictions.items())} +def _required_nonempty_string(value: Any, field: str) -> str: + if not isinstance(value, str) or not value.strip(): + raise ValueError(f"{field} must be a nonempty string") + return value + + +def _normalized_comparison(case: Mapping[str, Any]) -> dict[str, Any]: + comparison = case.get("comparison", {"kind": "exact"}) + if not isinstance(comparison, Mapping): + raise ValueError("comparison must be a mapping") + return deepcopy(dict(comparison)) + + +def freeze_validation_plan(cases: list[Mapping[str, Any]]) -> dict[str, Any]: + """Commit case identity, domain, and comparator rules before predictions.""" + if not isinstance(cases, list): + raise ValueError("validation plan cases must be a list") + normalized: list[dict[str, Any]] = [] + seen: set[str] = set() + allowed = {"id", "domain", "comparison"} + for case in cases: + if not isinstance(case, Mapping): + raise ValueError("validation plan cases must be mappings") + extra = set(case) - allowed + if extra: + raise ValueError("validation plan cannot contain expected values, provenance, or other oracle fields") + case_id = _required_nonempty_string(case.get("id"), "case id") + if case_id in seen: + raise ValueError(f"duplicate validation plan case id: {case_id}") + seen.add(case_id) + normalized.append({ + "id": case_id, + "domain": deepcopy(case.get("domain")), + "comparison": _normalized_comparison(case), + }) + normalized.sort(key=lambda case: case["id"]) envelope = { - "schema": "epac.heldout-prediction-commitment", - "version": "v1", + "schema": _PLAN_SCHEMA, + "version": _VERSION, + "cases": normalized, + } + return {**envelope, "plan_sha256": _digest(envelope)} + + +def _verify_validation_plan(plan: Mapping[str, Any]) -> None: + required = {"schema", "version", "cases", "plan_sha256"} + if set(plan) != required: + raise ValueError("invalid validation plan envelope") + if plan["schema"] != _PLAN_SCHEMA or plan["version"] != _VERSION: + raise ValueError("unsupported validation plan") + cases = plan["cases"] + if not isinstance(cases, list): + raise ValueError("validation plan cases must be a list") + seen: set[str] = set() + for case in cases: + if not isinstance(case, Mapping) or set(case) != {"id", "domain", "comparison"}: + raise ValueError("invalid validation plan case") + case_id = _required_nonempty_string(case.get("id"), "case id") + if case_id in seen: + raise ValueError(f"duplicate validation plan case id: {case_id}") + seen.add(case_id) + if not isinstance(case.get("comparison"), Mapping): + raise ValueError("comparison must be a mapping") + unsigned = {k: plan[k] for k in ("schema", "version", "cases")} + if _digest(unsigned) != plan["plan_sha256"]: + raise ValueError("validation plan digest mismatch") + + +def freeze_predictions( + predictions: Mapping[str, Any], + *, + source_identity: str, + validation_plan: Mapping[str, Any], +) -> dict[str, Any]: + """Commit predictions only after case inventory/comparators are frozen.""" + _verify_validation_plan(validation_plan) + _required_nonempty_string(source_identity, "source_identity") + if not isinstance(predictions, Mapping): + raise ValueError("predictions must be a mapping") + allowed_ids = {case["id"] for case in validation_plan["cases"]} + normalized: dict[str, Any] = {} + for case_id, value in predictions.items(): + case_id = _required_nonempty_string(case_id, "prediction case id") + if case_id not in allowed_ids: + raise ValueError(f"prediction case is absent from frozen validation plan: {case_id}") + normalized[case_id] = deepcopy(value) + normalized = dict(sorted(normalized.items())) + envelope = { + "schema": _PREDICTION_SCHEMA, + "version": _VERSION, "source_identity": source_identity, + "validation_plan_sha256": validation_plan["plan_sha256"], "predictions": normalized, } return {**envelope, "commitment_sha256": _digest(envelope)} def verify_commitment(commitment: Mapping[str, Any]) -> None: - required = {"schema", "version", "source_identity", "predictions", "commitment_sha256"} + required = { + "schema", + "version", + "source_identity", + "validation_plan_sha256", + "predictions", + "commitment_sha256", + } if set(commitment) != required: raise ValueError("invalid prediction commitment envelope") - if commitment["schema"] != "epac.heldout-prediction-commitment" or commitment["version"] != "v1": + if commitment["schema"] != _PREDICTION_SCHEMA or commitment["version"] != _VERSION: raise ValueError("unsupported prediction commitment") - unsigned = {k: commitment[k] for k in ("schema", "version", "source_identity", "predictions")} + _required_nonempty_string(commitment["source_identity"], "source_identity") + _required_nonempty_string(commitment["validation_plan_sha256"], "validation_plan_sha256") + if not isinstance(commitment["predictions"], Mapping): + raise ValueError("predictions must be a mapping") + for case_id in commitment["predictions"]: + _required_nonempty_string(case_id, "prediction case id") + unsigned = { + k: commitment[k] + for k in ("schema", "version", "source_identity", "validation_plan_sha256", "predictions") + } if _digest(unsigned) != commitment["commitment_sha256"]: raise ValueError("prediction commitment digest mismatch") +def _json_exact_equal(left: Any, right: Any) -> bool | None: + """JSON-type-aware equality; None means the values are not comparable JSON.""" + if left is None or right is None: + return left is None and right is None + if isinstance(left, bool) or isinstance(right, bool): + if not isinstance(left, bool) or not isinstance(right, bool): + return False + return left == right + if isinstance(left, (int, float)) or isinstance(right, (int, float)): + if not isinstance(left, (int, float)) or isinstance(left, bool): + return False + if not isinstance(right, (int, float)) or isinstance(right, bool): + return False + try: + if not math.isfinite(float(left)) or not math.isfinite(float(right)): + return None + except (OverflowError, TypeError, ValueError): + return None + return left == right + if isinstance(left, str) or isinstance(right, str): + return left == right if isinstance(left, str) and isinstance(right, str) else False + if isinstance(left, list) or isinstance(right, list): + if not isinstance(left, list) or not isinstance(right, list) or len(left) != len(right): + return False + comparisons = [_json_exact_equal(a, b) for a, b in zip(left, right)] + if any(value is None for value in comparisons): + return None + return all(comparisons) + if isinstance(left, Mapping) or isinstance(right, Mapping): + if not isinstance(left, Mapping) or not isinstance(right, Mapping): + return False + if not all(isinstance(key, str) for key in left) or not all(isinstance(key, str) for key in right): + return None + if set(left) != set(right): + return False + comparisons = [_json_exact_equal(left[key], right[key]) for key in left] + if any(value is None for value in comparisons): + return None + return all(comparisons) + return None + + +def _finite_number(value: Any) -> float | None: + if isinstance(value, bool) or not isinstance(value, (int, float)): + return None + try: + converted = float(value) + except (OverflowError, TypeError, ValueError): + return None + return converted if math.isfinite(converted) else None + + +def _json_token(value: Any) -> tuple[Any, ...] | None: + """Hashable JSON-type token used by set equality without bool/int aliasing.""" + if value is None: + return ("null",) + if isinstance(value, bool): + return ("boolean", value) + if isinstance(value, (int, float)): + finite = _finite_number(value) + return None if finite is None else ("number", value) + if isinstance(value, str): + return ("string", value) + if isinstance(value, list): + items = tuple(_json_token(item) for item in value) + return None if any(item is None for item in items) else ("array", items) + if isinstance(value, Mapping): + if not all(isinstance(key, str) for key in value): + return None + items = tuple((key, _json_token(value[key])) for key in sorted(value)) + return None if any(item is None for _, item in items) else ("object", items) + return None + + def _compare(expected: Any, predicted: Any, rule: Mapping[str, Any]) -> str: kind = rule.get("kind", "exact") if kind == "exact": - return "SURVIVED" if predicted == expected else "FALSIFIED" + equal = _json_exact_equal(predicted, expected) + if equal is None: + return "UNRESOLVED" + return "SURVIVED" if equal else "FALSIFIED" if kind == "set-equality": - try: - return "SURVIVED" if set(predicted) == set(expected) else "FALSIFIED" - except TypeError: + if not isinstance(predicted, list) or not isinstance(expected, list): return "UNRESOLVED" - if kind == "numeric-tolerance": - tolerance = rule.get("absolute_tolerance") - if not isinstance(tolerance, (int, float)) or isinstance(tolerance, bool) or tolerance < 0: + predicted_tokens = {_json_token(item) for item in predicted} + expected_tokens = {_json_token(item) for item in expected} + if None in predicted_tokens or None in expected_tokens: return "UNRESOLVED" - try: - return "SURVIVED" if abs(float(predicted) - float(expected)) <= tolerance else "FALSIFIED" - except (TypeError, ValueError): + return "SURVIVED" if predicted_tokens == expected_tokens else "FALSIFIED" + if kind == "numeric-tolerance": + tolerance = _finite_number(rule.get("absolute_tolerance")) + left = _finite_number(predicted) + right = _finite_number(expected) + if tolerance is None or tolerance < 0 or left is None or right is None: return "UNRESOLVED" + return "SURVIVED" if abs(left - right) <= tolerance else "FALSIFIED" return "UNRESOLVED" -def compare_after_freeze(commitment: Mapping[str, Any], oracle: Mapping[str, Any]) -> dict[str, Any]: - """Reveal an independently sourced oracle only after commitment verification.""" +def compare_after_freeze( + commitment: Mapping[str, Any], + oracle: Mapping[str, Any], + *, + validation_plan: Mapping[str, Any], +) -> dict[str, Any]: + """Reveal independently sourced oracle values only after both commitments.""" + _verify_validation_plan(validation_plan) verify_commitment(commitment) - if oracle.get("schema") != "epac.heldout-chemistry-oracle" or oracle.get("version") != "v1": + if commitment["validation_plan_sha256"] != validation_plan["plan_sha256"]: + raise ValueError("prediction commitment is bound to a different validation plan") + if oracle.get("schema") != _ORACLE_SCHEMA or oracle.get("version") != _VERSION: raise ValueError("unsupported held-out oracle") cases = oracle.get("cases") if not isinstance(cases, list): raise ValueError("oracle cases must be a list") - predictions = commitment["predictions"] - results = [] + oracle_by_id: dict[str, Mapping[str, Any]] = {} for case in cases: - case_id = case.get("id") - provenance = case.get("provenance", {}) - authoritative = provenance.get("authority") - locator = provenance.get("locator") - if not case_id or not authoritative or not locator: - results.append({"id": case_id, "status": "UNRESOLVED", "reason": "missing case/provenance identity"}) + if not isinstance(case, Mapping): + raise ValueError("oracle cases must be mappings") + case_id = _required_nonempty_string(case.get("id"), "oracle case id") + if case_id in oracle_by_id: + raise ValueError(f"duplicate oracle case id: {case_id}") + oracle_by_id[case_id] = case + + plan_by_id = {case["id"]: case for case in validation_plan["cases"]} + if set(oracle_by_id) != set(plan_by_id): + raise ValueError("oracle case inventory does not match frozen validation plan") + + predictions = commitment["predictions"] + results: list[dict[str, Any]] = [] + for case_id in sorted(plan_by_id): + case = oracle_by_id[case_id] + plan_case = plan_by_id[case_id] + oracle_rule = _normalized_comparison(case) + if _canonical(oracle_rule) != _canonical(plan_case["comparison"]): + raise ValueError(f"oracle comparator does not match frozen validation plan: {case_id}") + if _canonical(case.get("domain")) != _canonical(plan_case.get("domain")): + raise ValueError(f"oracle domain does not match frozen validation plan: {case_id}") + + provenance = case.get("provenance") + if not isinstance(provenance, Mapping) or not provenance.get("authority") or not provenance.get("locator"): + results.append({ + "id": case_id, + "domain": deepcopy(case.get("domain")), + "status": "UNRESOLVED", + "reason": "missing provenance identity", + }) + continue + if "expected" not in case: + results.append({ + "id": case_id, + "domain": deepcopy(case.get("domain")), + "status": "UNRESOLVED", + "reason": "missing expected value", + "provenance": deepcopy(provenance), + }) continue if case_id not in predictions: - results.append({"id": case_id, "status": "UNRESOLVED", "reason": "no frozen prediction"}) + results.append({ + "id": case_id, + "domain": deepcopy(case.get("domain")), + "status": "UNRESOLVED", + "reason": "no frozen prediction", + "expected": deepcopy(case["expected"]), + "provenance": deepcopy(provenance), + }) continue - status = _compare(case.get("expected"), predictions[case_id], case.get("comparison", {"kind": "exact"})) + + status = _compare(case["expected"], predictions[case_id], plan_case["comparison"]) results.append({ "id": case_id, - "domain": case.get("domain"), + "domain": deepcopy(case.get("domain")), "status": status, - "predicted": predictions[case_id], - "expected": case.get("expected"), - "provenance": provenance, + "predicted": deepcopy(predictions[case_id]), + "expected": deepcopy(case["expected"]), + "comparison": deepcopy(plan_case["comparison"]), + "provenance": deepcopy(provenance), }) - counts = {status: sum(r["status"] == status for r in results) for status in STATUSES} + counts = {status: sum(result["status"] == status for result in results) for status in STATUSES} receipt = { - "schema": "epac.heldout-validation-receipt", - "version": "v1", + "schema": _RECEIPT_SCHEMA, + "version": _VERSION, + "validation_plan_sha256": validation_plan["plan_sha256"], "prediction_commitment_sha256": commitment["commitment_sha256"], "oracle_sha256": _digest(oracle), "results": results, @@ -114,3 +377,12 @@ def compare_after_freeze(commitment: Mapping[str, Any], oracle: Mapping[str, Any def load_oracle(path: str | Path) -> dict[str, Any]: """Comparison-side helper. Construction modules must not call this.""" return json.loads(Path(path).read_text(encoding="utf-8")) + + +def load_packaged_oracle() -> dict[str, Any]: + """Load the shipped comparison-only oracle in source or installed form.""" + try: + resource = files("epac_data").joinpath("heldout_chemistry_oracle.json") + return json.loads(resource.read_text(encoding="utf-8")) + except ModuleNotFoundError: + return load_oracle(Path(__file__).resolve().parent / "data" / "heldout_chemistry_oracle.json") diff --git a/tests/test_boundary_probe_completeness.py b/tests/test_boundary_probe_completeness.py index 28f3e8f..eb3c8e4 100644 --- a/tests/test_boundary_probe_completeness.py +++ b/tests/test_boundary_probe_completeness.py @@ -63,6 +63,7 @@ from epac_boundary_probe_completeness import ( AMBIGUOUS, BOUNDARY_OBSERVING, + INTERNAL_NON_BOUNDARY, FALSIFIED, SURVIVED, UNRESOLVED, @@ -137,13 +138,34 @@ def test_inventory_covers_packaged_execution_modules(self) -> None: self.assertIn("epac_viz." + name, operations) self.assertIn("epac_viz.spiral_viz." + name, operations) self.assertEqual(_installed_audit._classify_operation("epac_viz", name), AMBIGUOUS) + heldout_exports = { + row["name"] + for row in operations.values() + if row["module"] == "epac_heldout_validation" + } + self.assertEqual( + heldout_exports, + { + "freeze_validation_plan", + "freeze_predictions", + "verify_commitment", + "compare_after_freeze", + "load_oracle", + "load_packaged_oracle", + }, + ) + for name in heldout_exports: + self.assertEqual( + _installed_audit._classify_operation("epac_heldout_validation", name), + INTERNAL_NON_BOUNDARY, + ) for name in ("AtomicRecord", "ElectronState", "atomic_record", "iter_table"): row = operations["epac_atomic." + name] self.assertEqual(_installed_audit._classify_operation(row["module"], row["name"]), AMBIGUOUS) def test_declared_operations_preserve_unresolved_semantics(self) -> None: inventory = self.report["operation_inventory"] - self.assertEqual(inventory["operation_count"], 153) + self.assertEqual(inventory["operation_count"], 159) self.assertEqual(inventory["boundary_relevant_count"], 58) self.assertEqual(inventory["ambiguous_count"], 56) self.assertEqual(inventory["omitted_boundary_relevant_count"], 14) diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index 96c461e..effe070 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -1,10 +1,28 @@ from __future__ import annotations from copy import deepcopy +import hashlib +import json +import math import pytest -from epac_heldout_validation import compare_after_freeze, freeze_predictions, verify_commitment +from epac_heldout_validation import ( + compare_after_freeze, + freeze_predictions, + freeze_validation_plan, + load_packaged_oracle, + verify_commitment, +) + + +def plan_cases(): + return [ + {"id": "element:H:valence", "domain": "valence", "comparison": {"kind": "exact"}}, + {"id": "element:O:oxidation", "domain": "oxidation-state", "comparison": {"kind": "set-equality"}}, + {"id": "isotope:H-1:mass", "domain": "isotope", + "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": 0.000001}}, + ] def oracle(): @@ -25,38 +43,243 @@ def oracle(): } -def test_freeze_is_deterministic_and_oracle_free(): - a = freeze_predictions({"b": 2, "a": 1}, source_identity="epac@test") - b = freeze_predictions({"a": 1, "b": 2}, source_identity="epac@test") - assert a == b - assert "oracle" not in repr(a).lower() - verify_commitment(a) +def frozen(predictions): + plan = freeze_validation_plan(plan_cases()) + commitment = freeze_predictions(predictions, source_identity="epac@test", validation_plan=plan) + return plan, commitment + + +def _digest_envelope(value): + payload = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode() + return hashlib.sha256(payload).hexdigest() + + +def test_freeze_is_deterministic_oracle_free_and_plan_bound(): + plan_a = freeze_validation_plan(list(reversed(plan_cases()))) + plan_b = freeze_validation_plan(plan_cases()) + assert plan_a == plan_b + + predictions_a = {"element:H:valence": 1, "element:O:oxidation": [-2, 2]} + predictions_b = {"element:O:oxidation": [-2, 2], "element:H:valence": 1} + frozen_a = freeze_predictions(predictions_a, source_identity="epac@test", validation_plan=plan_a) + frozen_b = freeze_predictions(predictions_b, source_identity="epac@test", validation_plan=plan_b) + + assert frozen_a == frozen_b + assert frozen_a["validation_plan_sha256"] == plan_a["plan_sha256"] + assert "expected" not in repr(plan_a).lower() + assert "provenance" not in repr(plan_a).lower() + verify_commitment(frozen_a) + + +def test_plan_rejects_duplicate_ids_and_oracle_fields(): + with pytest.raises(ValueError, match="duplicate validation plan"): + freeze_validation_plan([{"id": "x"}, {"id": "x"}]) + with pytest.raises(ValueError, match="cannot contain expected values"): + freeze_validation_plan([{"id": "x", "expected": 1}]) + + +def test_prediction_cannot_escape_frozen_case_inventory(): + plan = freeze_validation_plan([{"id": "x", "comparison": {"kind": "exact"}}]) + with pytest.raises(ValueError, match="absent from frozen validation plan"): + freeze_predictions({"y": 1}, source_identity="epac@test", validation_plan=plan) def test_tampered_prediction_cannot_be_compared(): - frozen = freeze_predictions({"element:H:valence": 1}, source_identity="epac@test") - tampered = deepcopy(frozen) + plan, commitment = frozen({"element:H:valence": 1}) + tampered = deepcopy(commitment) tampered["predictions"]["element:H:valence"] = 2 with pytest.raises(ValueError, match="digest mismatch"): - compare_after_freeze(tampered, oracle()) + compare_after_freeze(tampered, oracle(), validation_plan=plan) + + +def test_commitment_revalidates_nonempty_source_identity_even_with_matching_digest(): + _, commitment = frozen({"element:H:valence": 1}) + forged = deepcopy(commitment) + forged["source_identity"] = "" + unsigned = { + k: forged[k] + for k in ("schema", "version", "source_identity", "validation_plan_sha256", "predictions") + } + forged["commitment_sha256"] = _digest_envelope(unsigned) + with pytest.raises(ValueError, match="source_identity"): + verify_commitment(forged) + + +def test_case_inventory_and_comparator_are_bound_before_comparison(): + plan, commitment = frozen({"element:H:valence": 1}) + + missing_case = oracle() + missing_case["cases"].pop() + with pytest.raises(ValueError, match="case inventory"): + compare_after_freeze(commitment, missing_case, validation_plan=plan) + + changed_rule = oracle() + changed_rule["cases"][2]["comparison"]["absolute_tolerance"] = 1.0 + with pytest.raises(ValueError, match="comparator"): + compare_after_freeze(commitment, changed_rule, validation_plan=plan) def test_comparison_classifies_only_after_freeze(): - frozen = freeze_predictions({ + plan, commitment = frozen({ "element:H:valence": 1, "element:O:oxidation": [-2, 2], "isotope:H-1:mass": 1.0078251, - }, source_identity="epac@test") - receipt = compare_after_freeze(frozen, oracle()) + }) + receipt = compare_after_freeze(commitment, oracle(), validation_plan=plan) assert receipt["counts"] == {"SURVIVED": 2, "FALSIFIED": 1, "UNRESOLVED": 0} - assert receipt["prediction_commitment_sha256"] == frozen["commitment_sha256"] + assert receipt["validation_plan_sha256"] == plan["plan_sha256"] + assert receipt["prediction_commitment_sha256"] == commitment["commitment_sha256"] assert len(receipt["oracle_sha256"]) == 64 assert len(receipt["receipt_sha256"]) == 64 -def test_missing_prediction_and_bad_provenance_are_unresolved(): - o = oracle() - o["cases"].append({"id": "reaction:x", "expected": "y", "provenance": {}}) - frozen = freeze_predictions({}, source_identity="epac@test") - receipt = compare_after_freeze(frozen, o) - assert receipt["counts"]["UNRESOLVED"] == 4 +def test_absent_expected_null_provenance_and_missing_prediction_are_unresolved(): + cases = [ + {"id": "missing:expected", "domain": "fixture", "comparison": {"kind": "exact"}}, + {"id": "null:provenance", "domain": "fixture", "comparison": {"kind": "exact"}}, + {"id": "missing:prediction", "domain": "fixture", "comparison": {"kind": "exact"}}, + ] + plan = freeze_validation_plan(cases) + commitment = freeze_predictions( + {"missing:expected": None, "null:provenance": 1}, + source_identity="epac@test", + validation_plan=plan, + ) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [ + {"id": "missing:expected", "domain": "fixture", "comparison": {"kind": "exact"}, + "provenance": {"authority": "fixture", "locator": "missing"}}, + {"id": "null:provenance", "domain": "fixture", "comparison": {"kind": "exact"}, + "expected": 1, "provenance": None}, + {"id": "missing:prediction", "domain": "fixture", "comparison": {"kind": "exact"}, + "expected": 1, + "provenance": {"authority": "fixture", "locator": "missing-prediction"}}, + ], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 3} + reasons = {row["id"]: row["reason"] for row in receipt["results"]} + assert reasons["missing:expected"] == "missing expected value" + assert reasons["null:provenance"] == "missing provenance identity" + assert reasons["missing:prediction"] == "no frozen prediction" + + +def test_exact_comparison_is_json_type_safe_including_nested_values(): + plan = freeze_validation_plan([ + {"id": "scalar", "comparison": {"kind": "exact"}}, + {"id": "nested", "comparison": {"kind": "exact"}}, + ]) + commitment = freeze_predictions( + {"scalar": True, "nested": [True]}, + source_identity="epac@test", + validation_plan=plan, + ) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [ + {"id": "scalar", "domain": None, "comparison": {"kind": "exact"}, "expected": 1, + "provenance": {"authority": "fixture", "locator": "scalar"}}, + {"id": "nested", "domain": None, "comparison": {"kind": "exact"}, "expected": [1], + "provenance": {"authority": "fixture", "locator": "nested"}}, + ], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 2, "UNRESOLVED": 0} + + +def test_duplicate_oracle_case_ids_are_rejected_before_scoring(): + plan = freeze_validation_plan([{"id": "x", "comparison": {"kind": "exact"}}]) + commitment = freeze_predictions({"x": 1}, source_identity="epac@test", validation_plan=plan) + duplicate = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [ + {"id": "x", "domain": None, "comparison": {"kind": "exact"}, "expected": 1, + "provenance": {"authority": "fixture", "locator": "x1"}}, + {"id": "x", "domain": None, "comparison": {"kind": "exact"}, "expected": 2, + "provenance": {"authority": "fixture", "locator": "x2"}}, + ], + } + with pytest.raises(ValueError, match="duplicate oracle case id"): + compare_after_freeze(commitment, duplicate, validation_plan=plan) + + +@pytest.mark.parametrize( + ("expected", "predicted", "tolerance"), + [ + (1.0, math.inf, 0.1), + (math.inf, 1.0, 0.1), + (1.0, 1.0, math.inf), + (1.0, math.nan, 0.1), + (1.0, 1.0, math.nan), + ], +) +def test_nonfinite_numeric_operands_or_tolerance_are_unresolved(expected, predicted, tolerance): + plan = freeze_validation_plan([ + {"id": "x", "domain": "fixture", + "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": tolerance}} + ]) + commitment = freeze_predictions({"x": predicted}, source_identity="epac@test", validation_plan=plan) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": "fixture", + "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": tolerance}, + "expected": expected, + "provenance": {"authority": "fixture", "locator": "x"}, + }], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} + + +def test_receipt_and_commitment_detach_mutable_evidence_inputs(): + predictions = { + "element:H:valence": 1, + "element:O:oxidation": [-2, 2], + "isotope:H-1:mass": 1.0078251, + } + plan = freeze_validation_plan(plan_cases()) + commitment = freeze_predictions(predictions, source_identity="epac@test", validation_plan=plan) + predictions["element:O:oxidation"].append(99) + verify_commitment(commitment) + + heldout = oracle() + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + preserved = deepcopy(receipt) + heldout["cases"][1]["expected"].append(99) + heldout["cases"][1]["provenance"]["locator"] = "mutated" + commitment["predictions"]["element:O:oxidation"].append(88) + + assert receipt == preserved + assert receipt["receipt_sha256"] == preserved["receipt_sha256"] + + +def test_packaged_oracle_loader_works_from_checkout_or_install(): + packaged = load_packaged_oracle() + assert packaged["schema"] == "epac.heldout-chemistry-oracle" + assert packaged["cases"] == [] + + +def test_set_equality_does_not_alias_boolean_and_numeric_values(): + plan = freeze_validation_plan([{"id": "x", "comparison": {"kind": "set-equality"}}]) + commitment = freeze_predictions({"x": [True]}, source_identity="epac@test", validation_plan=plan) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": None, + "comparison": {"kind": "set-equality"}, + "expected": [1], + "provenance": {"authority": "fixture", "locator": "x"}, + }], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 1, "UNRESOLVED": 0} + From c82325a7e5691fe63a19e6cb7efaf1bd0b166f11 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 12:11:40 -0700 Subject: [PATCH 07/19] Close exact-head validation review findings --- docs/heldout-chemistry-validation.md | 13 ++++ epac_heldout_validation.py | 101 +++++++++++++++++++------- tests/test_heldout_validation.py | 103 ++++++++++++++++++++++++++- 3 files changed, 190 insertions(+), 27 deletions(-) diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index 2e202ee..6231d7a 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -24,6 +24,19 @@ The checked-in `data/heldout_chemistry_oracle.json` is intentionally empty as a Recommended stable IDs include `element:H:valence`, `element:O:oxidation`, `isotope:H-1:mass`, and `reaction::products`. The corpus can grow across isotope behavior, oxidation/valence, reaction outcomes, and element properties without changing EPAC construction code. +## Comparator and evidence input rules + +Validation-plan comparator fields are closed in v1 so expected labels cannot be smuggled into the prediction side: + +- `exact` and `set-equality` accept only `kind`; +- `numeric-tolerance` accepts only `kind` and `absolute_tolerance`; +- an unknown comparator kind may be preregistered with `kind` only and will score `UNRESOLVED`; +- any additional comparator field is rejected before the plan is committed or verified. + +Numeric-tolerance comparison keeps integers as integers and uses exact rational arithmetic over accepted finite Python numeric values, so large integer distinctions are not collapsed by an intermediate float conversion. Non-finite operands or tolerances remain `UNRESOLVED`. + +Provenance `authority` and `locator` must both be nonempty strings after trimming; blank or null identities are `UNRESOLVED`. File-based oracle loading rejects duplicate JSON object keys at any nesting level rather than accepting parser-dependent evidence. In a source checkout, `load_packaged_oracle()` prefers the sibling `data/heldout_chemistry_oracle.json`; in an installed distribution it reads the `epac_data` package resource. + ## Runnable end-to-end example This runs from a source checkout or the installed distribution. It persists and reloads both commitments before revealing a synthetic held-out value, exercises packaged-oracle loading, then persists the evidence receipt. diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 3202967..750be84 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -1,4 +1,4 @@ -"""Two-phase held-out chemistry validation for EPAC. +"""Staged held-out chemistry validation for EPAC. The construction side may create a prediction commitment without importing or reading the oracle corpus. Only the comparison phase accepts oracle facts. @@ -14,6 +14,7 @@ from collections.abc import Mapping from copy import deepcopy +from fractions import Fraction import hashlib from importlib.resources import files import json @@ -28,15 +29,15 @@ # summary: comparison-only held-out validation boundary that preregisters case identities and comparator rules before EPAC predictions are frozen and oracle values are revealed # owner: The Interdependency # public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_oracle, load_packaged_oracle -# internal_surface: _canonical, _digest, _verify_validation_plan, _json_exact_equal, _json_token, _finite_number, _compare +# internal_surface: _canonical, _digest, _required_nonempty_string, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _loads_oracle_json # auth_boundary: none -# storage_boundary: none +# storage_boundary: read # network_boundary: none # user_data_boundary: none # admin_only: false # tests: tests.test_heldout_validation # rollout: opt-in research validation surface; callers persist the validation-plan commitment before prediction commitment and reveal independently sourced oracle values only during comparison -# rollback: remove this module, its package entry, tests, oracle fixture, and documentation without changing EPAC construction modules or chemistry derivation behavior +# rollback: remove this module, its package and boundary-inventory entries, tests, oracle fixture, and documentation without changing EPAC construction modules or chemistry derivation behavior # since: 2026-09-29 # unresolved: external oracle authority selection and custody remain outside EPAC; this module binds supplied evidence but does not establish independent custody by itself # === END MODULE_BUILD === @@ -63,11 +64,31 @@ def _required_nonempty_string(value: Any, field: str) -> str: return value +def _has_nonempty_provenance_identity(provenance: Any) -> bool: + if not isinstance(provenance, Mapping): + return False + return all( + isinstance(provenance.get(field), str) and bool(provenance[field].strip()) + for field in ("authority", "locator") + ) + + def _normalized_comparison(case: Mapping[str, Any]) -> dict[str, Any]: comparison = case.get("comparison", {"kind": "exact"}) if not isinstance(comparison, Mapping): raise ValueError("comparison must be a mapping") - return deepcopy(dict(comparison)) + kind = comparison.get("kind", "exact") + if not isinstance(kind, str) or not kind.strip() or kind != kind.strip(): + raise ValueError("comparison kind must be a nonempty canonical string") + allowed = {"kind", "absolute_tolerance"} if kind == "numeric-tolerance" else {"kind"} + extra = set(comparison) - allowed + if extra: + fields = ", ".join(sorted(str(field) for field in extra)) + raise ValueError(f"unexpected comparator fields for {kind}: {fields}") + normalized: dict[str, Any] = {"kind": kind} + if "absolute_tolerance" in comparison: + normalized["absolute_tolerance"] = deepcopy(comparison["absolute_tolerance"]) + return normalized def freeze_validation_plan(cases: list[Mapping[str, Any]]) -> dict[str, Any]: @@ -118,8 +139,9 @@ def _verify_validation_plan(plan: Mapping[str, Any]) -> None: if case_id in seen: raise ValueError(f"duplicate validation plan case id: {case_id}") seen.add(case_id) - if not isinstance(case.get("comparison"), Mapping): - raise ValueError("comparison must be a mapping") + normalized_comparison = _normalized_comparison(case) + if _canonical(normalized_comparison) != _canonical(case["comparison"]): + raise ValueError("validation plan comparison must be canonical") unsigned = {k: plan[k] for k in ("schema", "version", "cases")} if _digest(unsigned) != plan["plan_sha256"]: raise ValueError("validation plan digest mismatch") @@ -223,14 +245,12 @@ def _json_exact_equal(left: Any, right: Any) -> bool | None: return None -def _finite_number(value: Any) -> float | None: +def _finite_number(value: Any) -> int | float | None: if isinstance(value, bool) or not isinstance(value, (int, float)): return None - try: - converted = float(value) - except (OverflowError, TypeError, ValueError): + if isinstance(value, float) and not math.isfinite(value): return None - return converted if math.isfinite(converted) else None + return value def _json_token(value: Any) -> tuple[Any, ...] | None: @@ -255,6 +275,19 @@ def _json_token(value: Any) -> tuple[Any, ...] | None: return None +def _numeric_within_tolerance( + expected: Any, + predicted: Any, + tolerance_value: Any, +) -> bool | None: + tolerance = _finite_number(tolerance_value) + left = _finite_number(predicted) + right = _finite_number(expected) + if tolerance is None or tolerance < 0 or left is None or right is None: + return None + return abs(Fraction(left) - Fraction(right)) <= Fraction(tolerance) + + def _compare(expected: Any, predicted: Any, rule: Mapping[str, Any]) -> str: kind = rule.get("kind", "exact") if kind == "exact": @@ -271,12 +304,14 @@ def _compare(expected: Any, predicted: Any, rule: Mapping[str, Any]) -> str: return "UNRESOLVED" return "SURVIVED" if predicted_tokens == expected_tokens else "FALSIFIED" if kind == "numeric-tolerance": - tolerance = _finite_number(rule.get("absolute_tolerance")) - left = _finite_number(predicted) - right = _finite_number(expected) - if tolerance is None or tolerance < 0 or left is None or right is None: + within = _numeric_within_tolerance( + expected, + predicted, + rule.get("absolute_tolerance"), + ) + if within is None: return "UNRESOLVED" - return "SURVIVED" if abs(left - right) <= tolerance else "FALSIFIED" + return "SURVIVED" if within else "FALSIFIED" return "UNRESOLVED" @@ -322,7 +357,7 @@ def compare_after_freeze( raise ValueError(f"oracle domain does not match frozen validation plan: {case_id}") provenance = case.get("provenance") - if not isinstance(provenance, Mapping) or not provenance.get("authority") or not provenance.get("locator"): + if not _has_nonempty_provenance_identity(provenance): results.append({ "id": case_id, "domain": deepcopy(case.get("domain")), @@ -374,15 +409,31 @@ def compare_after_freeze( return {**receipt, "receipt_sha256": _digest(receipt)} +def _reject_duplicate_object_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + value: dict[str, Any] = {} + for key, item in pairs: + if key in value: + raise ValueError(f"duplicate JSON key in oracle evidence: {key}") + value[key] = item + return value + + +def _loads_oracle_json(payload: str) -> dict[str, Any]: + value = json.loads(payload, object_pairs_hook=_reject_duplicate_object_pairs) + if not isinstance(value, dict): + raise ValueError("oracle document must be a JSON object") + return value + + def load_oracle(path: str | Path) -> dict[str, Any]: """Comparison-side helper. Construction modules must not call this.""" - return json.loads(Path(path).read_text(encoding="utf-8")) + return _loads_oracle_json(Path(path).read_text(encoding="utf-8")) def load_packaged_oracle() -> dict[str, Any]: - """Load the shipped comparison-only oracle in source or installed form.""" - try: - resource = files("epac_data").joinpath("heldout_chemistry_oracle.json") - return json.loads(resource.read_text(encoding="utf-8")) - except ModuleNotFoundError: - return load_oracle(Path(__file__).resolve().parent / "data" / "heldout_chemistry_oracle.json") + """Load this checkout's oracle first, otherwise the installed package resource.""" + source_oracle = Path(__file__).resolve().parent / "data" / "heldout_chemistry_oracle.json" + if source_oracle.is_file(): + return load_oracle(source_oracle) + resource = files("epac_data").joinpath("heldout_chemistry_oracle.json") + return _loads_oracle_json(resource.read_text(encoding="utf-8")) diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index effe070..8c023ee 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -4,13 +4,17 @@ import hashlib import json import math +from pathlib import Path +from tempfile import TemporaryDirectory import pytest +import epac_heldout_validation as heldout_validation from epac_heldout_validation import ( compare_after_freeze, freeze_predictions, freeze_validation_plan, + load_oracle, load_packaged_oracle, verify_commitment, ) @@ -78,6 +82,24 @@ def test_plan_rejects_duplicate_ids_and_oracle_fields(): freeze_validation_plan([{"id": "x", "expected": 1}]) +def test_plan_rejects_oracle_fields_hidden_in_comparator_rules(): + for comparison in ( + {"kind": "exact", "expected": 7}, + {"kind": "set-equality", "expected": [7]}, + {"kind": "numeric-tolerance", "absolute_tolerance": 0, "expected": 7}, + ): + with pytest.raises(ValueError, match="unexpected comparator fields"): + freeze_validation_plan([{"id": "x", "comparison": comparison}]) + + plan = freeze_validation_plan([{"id": "x", "comparison": {"kind": "exact"}}]) + forged = deepcopy(plan) + forged["cases"][0]["comparison"]["expected"] = 7 + unsigned = {k: forged[k] for k in ("schema", "version", "cases")} + forged["plan_sha256"] = _digest_envelope(unsigned) + with pytest.raises(ValueError, match="unexpected comparator fields"): + freeze_predictions({"x": 1}, source_identity="epac@test", validation_plan=forged) + + def test_prediction_cannot_escape_frozen_case_inventory(): plan = freeze_validation_plan([{"id": "x", "comparison": {"kind": "exact"}}]) with pytest.raises(ValueError, match="absent from frozen validation plan"): @@ -238,6 +260,52 @@ def test_nonfinite_numeric_operands_or_tolerance_are_unresolved(expected, predic assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} +def test_numeric_tolerance_preserves_large_integer_distinctions(): + plan = freeze_validation_plan([ + {"id": "x", "domain": "fixture", + "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": 0}} + ]) + commitment = freeze_predictions( + {"x": 9007199254740993}, + source_identity="epac@test", + validation_plan=plan, + ) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": "fixture", + "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": 0}, + "expected": 9007199254740992, + "provenance": {"authority": "fixture", "locator": "large-int"}, + }], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 1, "UNRESOLVED": 0} + + +def test_blank_provenance_identity_is_unresolved(): + plan = freeze_validation_plan([ + {"id": "x", "domain": "fixture", "comparison": {"kind": "exact"}} + ]) + commitment = freeze_predictions({"x": 1}, source_identity="epac@test", validation_plan=plan) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": "fixture", + "comparison": {"kind": "exact"}, + "expected": 1, + "provenance": {"authority": " ", "locator": "\t"}, + }], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} + assert receipt["results"][0]["reason"] == "missing provenance identity" + + def test_receipt_and_commitment_detach_mutable_evidence_inputs(): predictions = { "element:H:valence": 1, @@ -260,8 +328,39 @@ def test_receipt_and_commitment_detach_mutable_evidence_inputs(): assert receipt["receipt_sha256"] == preserved["receipt_sha256"] -def test_packaged_oracle_loader_works_from_checkout_or_install(): - packaged = load_packaged_oracle() +def test_load_oracle_rejects_duplicate_json_object_keys(): + payload = ( + '{"schema":"epac.heldout-chemistry-oracle","version":"v1","cases":' + '[{"id":"x","expected":1,"expected":2}]}' + ) + with TemporaryDirectory() as tmp: + path = Path(tmp) / "oracle.json" + path.write_text(payload, encoding="utf-8") + with pytest.raises(ValueError, match="duplicate JSON key"): + load_oracle(path) + + +def test_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout(): + source_oracle = ( + Path(heldout_validation.__file__).resolve().parent + / "data" + / "heldout_chemistry_oracle.json" + ) + if source_oracle.is_file(): + original_files = heldout_validation.files + + def unexpected_installed_lookup(*_args, **_kwargs): + raise AssertionError("source checkout must be preferred over installed epac_data") + + heldout_validation.files = unexpected_installed_lookup + try: + packaged = load_packaged_oracle() + finally: + heldout_validation.files = original_files + assert packaged == load_oracle(source_oracle) + else: + packaged = load_packaged_oracle() + assert packaged["schema"] == "epac.heldout-chemistry-oracle" assert packaged["cases"] == [] From a5d7dd5a4da8d0a10a2233c589a18665de5e2eed Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 12:23:56 -0700 Subject: [PATCH 08/19] Stabilize held-out JSON boundary --- docs/heldout-chemistry-validation.md | 7 +- epac_heldout_validation.py | 109 ++++++++++++++++++---- tests/test_heldout_validation.py | 134 +++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 22 deletions(-) diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index 6231d7a..0c719ff 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -26,13 +26,16 @@ Recommended stable IDs include `element:H:valence`, `element:O:oxidation`, `isot ## Comparator and evidence input rules -Validation-plan comparator fields are closed in v1 so expected labels cannot be smuggled into the prediction side: +Validation-plan fields are deliberately narrow so oracle-shaped data cannot cross into the prediction side: -- `exact` and `set-equality` accept only `kind`; +- `domain` is either `null` or one nonempty canonical string; objects, arrays, numbers, blank strings, and padded strings are rejected; +- `exact` and `set-equality` comparators accept only `kind`; - `numeric-tolerance` accepts only `kind` and `absolute_tolerance`; - an unknown comparator kind may be preregistered with `kind` only and will score `UNRESOLVED`; - any additional comparator field is rejected before the plan is committed or verified. +Prediction and programmatic oracle values must use the JSON container/type model: `null`, booleans, numbers, strings, arrays/lists, and string-keyed objects/mappings. Python-only coercible values such as tuples are rejected before commitment or evidence hashing, including when nested. This keeps an in-memory commitment semantically identical to the same commitment after documented JSON persistence/reload. Non-finite Python floats remain admissible only so the comparator can classify them `UNRESOLVED`; they never produce a numeric success. + Numeric-tolerance comparison keeps integers as integers and uses exact rational arithmetic over accepted finite Python numeric values, so large integer distinctions are not collapsed by an intermediate float conversion. Non-finite operands or tolerances remain `UNRESOLVED`. Provenance `authority` and `locator` must both be nonempty strings after trimming; blank or null identities are `UNRESOLVED`. File-based oracle loading rejects duplicate JSON object keys at any nesting level rather than accepting parser-dependent evidence. In a source checkout, `load_packaged_oracle()` prefers the sibling `data/heldout_chemistry_oracle.json`; in an installed distribution it reads the `epac_data` package resource. diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 750be84..8d20c9e 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -29,7 +29,7 @@ # summary: comparison-only held-out validation boundary that preregisters case identities and comparator rules before EPAC predictions are frozen and oracle values are revealed # owner: The Interdependency # public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_oracle, load_packaged_oracle -# internal_surface: _canonical, _digest, _required_nonempty_string, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _loads_oracle_json +# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _loads_oracle_json # auth_boundary: none # storage_boundary: read # network_boundary: none @@ -64,6 +64,47 @@ def _required_nonempty_string(value: Any, field: str) -> str: return value +def _normalize_json_value(value: Any, *, field: str) -> Any: + """Normalize the supported JSON container/type model without coercing tuples.""" + if value is None: + return None + if isinstance(value, bool): + return bool(value) + if isinstance(value, int): + return int(value) + if isinstance(value, float): + return float(value) + if isinstance(value, str): + return str(value) + if isinstance(value, list): + return [ + _normalize_json_value(item, field=f"{field}[]") + for item in value + ] + if isinstance(value, Mapping): + normalized: dict[str, Any] = {} + for key, item in value.items(): + if not isinstance(key, str): + raise ValueError(f"{field} object keys must be strings") + normalized[key] = _normalize_json_value( + item, + field=f"{field}.{key}", + ) + return normalized + raise ValueError( + f"{field} must use JSON-shaped values; unsupported type: " + f"{type(value).__name__}" + ) + + +def _normalized_domain(value: Any) -> str | None: + if value is None: + return None + if not isinstance(value, str) or not value.strip() or value != value.strip(): + raise ValueError("domain must be null or a nonempty canonical string") + return value + + def _has_nonempty_provenance_identity(provenance: Any) -> bool: if not isinstance(provenance, Mapping): return False @@ -87,7 +128,10 @@ def _normalized_comparison(case: Mapping[str, Any]) -> dict[str, Any]: raise ValueError(f"unexpected comparator fields for {kind}: {fields}") normalized: dict[str, Any] = {"kind": kind} if "absolute_tolerance" in comparison: - normalized["absolute_tolerance"] = deepcopy(comparison["absolute_tolerance"]) + normalized["absolute_tolerance"] = _normalize_json_value( + comparison["absolute_tolerance"], + field="absolute_tolerance", + ) return normalized @@ -110,7 +154,7 @@ def freeze_validation_plan(cases: list[Mapping[str, Any]]) -> dict[str, Any]: seen.add(case_id) normalized.append({ "id": case_id, - "domain": deepcopy(case.get("domain")), + "domain": _normalized_domain(case.get("domain")), "comparison": _normalized_comparison(case), }) normalized.sort(key=lambda case: case["id"]) @@ -136,6 +180,7 @@ def _verify_validation_plan(plan: Mapping[str, Any]) -> None: if not isinstance(case, Mapping) or set(case) != {"id", "domain", "comparison"}: raise ValueError("invalid validation plan case") case_id = _required_nonempty_string(case.get("id"), "case id") + _normalized_domain(case.get("domain")) if case_id in seen: raise ValueError(f"duplicate validation plan case id: {case_id}") seen.add(case_id) @@ -164,7 +209,10 @@ def freeze_predictions( case_id = _required_nonempty_string(case_id, "prediction case id") if case_id not in allowed_ids: raise ValueError(f"prediction case is absent from frozen validation plan: {case_id}") - normalized[case_id] = deepcopy(value) + normalized[case_id] = _normalize_json_value( + value, + field=f"prediction[{case_id}]", + ) normalized = dict(sorted(normalized.items())) envelope = { "schema": _PREDICTION_SCHEMA, @@ -193,8 +241,9 @@ def verify_commitment(commitment: Mapping[str, Any]) -> None: _required_nonempty_string(commitment["validation_plan_sha256"], "validation_plan_sha256") if not isinstance(commitment["predictions"], Mapping): raise ValueError("predictions must be a mapping") - for case_id in commitment["predictions"]: + for case_id, value in commitment["predictions"].items(): _required_nonempty_string(case_id, "prediction case id") + _normalize_json_value(value, field=f"prediction[{case_id}]") unsigned = { k: commitment[k] for k in ("schema", "version", "source_identity", "validation_plan_sha256", "predictions") @@ -322,13 +371,32 @@ def compare_after_freeze( validation_plan: Mapping[str, Any], ) -> dict[str, Any]: """Reveal independently sourced oracle values only after both commitments.""" - _verify_validation_plan(validation_plan) - verify_commitment(commitment) - if commitment["validation_plan_sha256"] != validation_plan["plan_sha256"]: + plan_snapshot = _normalize_json_value( + validation_plan, + field="validation_plan", + ) + commitment_snapshot = _normalize_json_value( + commitment, + field="commitment", + ) + oracle_snapshot = _normalize_json_value( + oracle, + field="oracle", + ) + if not isinstance(plan_snapshot, dict): + raise ValueError("validation plan must be a JSON object") + if not isinstance(commitment_snapshot, dict): + raise ValueError("prediction commitment must be a JSON object") + if not isinstance(oracle_snapshot, dict): + raise ValueError("oracle must be a JSON object") + + _verify_validation_plan(plan_snapshot) + verify_commitment(commitment_snapshot) + if commitment_snapshot["validation_plan_sha256"] != plan_snapshot["plan_sha256"]: raise ValueError("prediction commitment is bound to a different validation plan") - if oracle.get("schema") != _ORACLE_SCHEMA or oracle.get("version") != _VERSION: + if oracle_snapshot.get("schema") != _ORACLE_SCHEMA or oracle_snapshot.get("version") != _VERSION: raise ValueError("unsupported held-out oracle") - cases = oracle.get("cases") + cases = oracle_snapshot.get("cases") if not isinstance(cases, list): raise ValueError("oracle cases must be a list") @@ -341,11 +409,11 @@ def compare_after_freeze( raise ValueError(f"duplicate oracle case id: {case_id}") oracle_by_id[case_id] = case - plan_by_id = {case["id"]: case for case in validation_plan["cases"]} + plan_by_id = {case["id"]: case for case in plan_snapshot["cases"]} if set(oracle_by_id) != set(plan_by_id): raise ValueError("oracle case inventory does not match frozen validation plan") - predictions = commitment["predictions"] + predictions = commitment_snapshot["predictions"] results: list[dict[str, Any]] = [] for case_id in sorted(plan_by_id): case = oracle_by_id[case_id] @@ -353,14 +421,15 @@ def compare_after_freeze( oracle_rule = _normalized_comparison(case) if _canonical(oracle_rule) != _canonical(plan_case["comparison"]): raise ValueError(f"oracle comparator does not match frozen validation plan: {case_id}") - if _canonical(case.get("domain")) != _canonical(plan_case.get("domain")): + oracle_domain = _normalized_domain(case.get("domain")) + if oracle_domain != plan_case["domain"]: raise ValueError(f"oracle domain does not match frozen validation plan: {case_id}") provenance = case.get("provenance") if not _has_nonempty_provenance_identity(provenance): results.append({ "id": case_id, - "domain": deepcopy(case.get("domain")), + "domain": plan_case["domain"], "status": "UNRESOLVED", "reason": "missing provenance identity", }) @@ -368,7 +437,7 @@ def compare_after_freeze( if "expected" not in case: results.append({ "id": case_id, - "domain": deepcopy(case.get("domain")), + "domain": plan_case["domain"], "status": "UNRESOLVED", "reason": "missing expected value", "provenance": deepcopy(provenance), @@ -377,7 +446,7 @@ def compare_after_freeze( if case_id not in predictions: results.append({ "id": case_id, - "domain": deepcopy(case.get("domain")), + "domain": plan_case["domain"], "status": "UNRESOLVED", "reason": "no frozen prediction", "expected": deepcopy(case["expected"]), @@ -388,7 +457,7 @@ def compare_after_freeze( status = _compare(case["expected"], predictions[case_id], plan_case["comparison"]) results.append({ "id": case_id, - "domain": deepcopy(case.get("domain")), + "domain": plan_case["domain"], "status": status, "predicted": deepcopy(predictions[case_id]), "expected": deepcopy(case["expected"]), @@ -400,9 +469,9 @@ def compare_after_freeze( receipt = { "schema": _RECEIPT_SCHEMA, "version": _VERSION, - "validation_plan_sha256": validation_plan["plan_sha256"], - "prediction_commitment_sha256": commitment["commitment_sha256"], - "oracle_sha256": _digest(oracle), + "validation_plan_sha256": plan_snapshot["plan_sha256"], + "prediction_commitment_sha256": commitment_snapshot["commitment_sha256"], + "oracle_sha256": _digest(oracle_snapshot), "results": results, "counts": counts, } diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index 8c023ee..1f22c00 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -106,6 +106,140 @@ def test_prediction_cannot_escape_frozen_case_inventory(): freeze_predictions({"y": 1}, source_identity="epac@test", validation_plan=plan) +def test_prediction_commitment_rejects_non_json_values_before_hashing(): + plan = freeze_validation_plan([ + {"id": "x", "domain": "fixture", "comparison": {"kind": "exact"}} + ]) + + with pytest.raises(ValueError, match="JSON-shaped"): + freeze_predictions( + {"x": (1,)}, + source_identity="epac@test", + validation_plan=plan, + ) + with pytest.raises(ValueError, match="JSON-shaped"): + freeze_predictions( + {"x": [{"nested": (1,)}]}, + source_identity="epac@test", + validation_plan=plan, + ) + + commitment = freeze_predictions( + {"x": [1]}, + source_identity="epac@test", + validation_plan=plan, + ) + forged = deepcopy(commitment) + forged["predictions"]["x"] = (1,) + unsigned = { + k: forged[k] + for k in ("schema", "version", "source_identity", "validation_plan_sha256", "predictions") + } + # Python's JSON encoder gives tuple/list the same bytes; verification must + # still reject the in-memory tuple representation before accepting the SHA. + assert _digest_envelope(unsigned) == commitment["commitment_sha256"] + forged["commitment_sha256"] = _digest_envelope(unsigned) + with pytest.raises(ValueError, match="JSON-shaped"): + verify_commitment(forged) + + round_tripped = json.loads(json.dumps(commitment)) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": "fixture", + "comparison": {"kind": "exact"}, + "expected": [1], + "provenance": {"authority": "fixture", "locator": "json-stability"}, + }], + } + before = compare_after_freeze(commitment, heldout, validation_plan=plan) + after = compare_after_freeze(round_tripped, heldout, validation_plan=plan) + assert before == after + assert before["counts"] == {"SURVIVED": 1, "FALSIFIED": 0, "UNRESOLVED": 0} + + +def test_domain_is_identifier_shaped_and_revalidated(): + invalid_domains = ( + {"expected": 7}, + ["expected", 7], + 7, + " ", + " padded ", + ) + for domain in invalid_domains: + with pytest.raises(ValueError, match="domain must be null or a nonempty canonical string"): + freeze_validation_plan([ + {"id": "x", "domain": domain, "comparison": {"kind": "exact"}} + ]) + + plan = freeze_validation_plan([ + {"id": "x", "domain": "fixture", "comparison": {"kind": "exact"}} + ]) + forged = deepcopy(plan) + forged["cases"][0]["domain"] = {"expected": 7} + unsigned = {k: forged[k] for k in ("schema", "version", "cases")} + forged["plan_sha256"] = _digest_envelope(unsigned) + with pytest.raises(ValueError, match="domain must be null or a nonempty canonical string"): + freeze_predictions( + {"x": 1}, + source_identity="epac@test", + validation_plan=forged, + ) + + commitment = freeze_predictions( + {"x": 1}, + source_identity="epac@test", + validation_plan=plan, + ) + oracle_with_structured_domain = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": {"expected": 7}, + "comparison": {"kind": "exact"}, + "expected": 1, + "provenance": {"authority": "fixture", "locator": "domain"}, + }], + } + with pytest.raises(ValueError, match="domain must be null or a nonempty canonical string"): + compare_after_freeze( + commitment, + oracle_with_structured_domain, + validation_plan=plan, + ) + + +def test_programmatic_oracle_rejects_non_json_values_before_evidence_hashing(): + plan = freeze_validation_plan([ + {"id": "x", "domain": "fixture", "comparison": {"kind": "exact"}} + ]) + commitment = freeze_predictions( + {"x": [1]}, + source_identity="epac@test", + validation_plan=plan, + ) + oracle_with_tuple = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": "fixture", + "comparison": {"kind": "exact"}, + "expected": (1,), + "provenance": {"authority": "fixture", "locator": "tuple"}, + }], + } + with pytest.raises(ValueError, match="JSON-shaped"): + compare_after_freeze( + commitment, + oracle_with_tuple, + validation_plan=plan, + ) + + def test_tampered_prediction_cannot_be_compared(): plan, commitment = frozen({"element:H:valence": 1}) tampered = deepcopy(commitment) From 7d8620ad9bf455507da0e9b07a4436b0ca430a04 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 29 Sep 2026 12:32:15 -0700 Subject: [PATCH 09/19] Complete held-out contract evidence graph --- epac_heldout_validation.py | 51 +++++- tests/test_heldout_validation.py | 257 ++++++++++++++++++++++++++++--- 2 files changed, 280 insertions(+), 28 deletions(-) diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 8d20c9e..5d74f79 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -42,6 +42,44 @@ # unresolved: external oracle authority selection and custody remain outside EPAC; this module binds supplied evidence but does not establish independent custody by itself # === END MODULE_BUILD === +# === CONTRACTS === +# id: heldout_selection_boundary_frozen_before_predictions +# given: a validation plan and prediction commitment are supplied for held-out comparison +# then: case ids, canonical domains, and comparator rules are bound before predictions; prediction ids outside that verified plan are rejected +# class: evidence +# since: 2026-09-29 +# +# id: heldout_commitment_persistence_and_identity +# given: predictions and source identity are frozen, serialized, reloaded, or externally reconstructed +# then: only persistence-stable JSON-shaped prediction values with a nonempty source identity and matching digest are accepted +# class: evidence +# since: 2026-09-29 +# +# id: heldout_comparison_tri_state_semantics +# given: frozen predictions and held-out expected values are compared under a preregistered comparator +# then: comparable agreement is SURVIVED, comparable disagreement is FALSIFIED, and missing or incomparable evidence is UNRESOLVED without bool/number aliasing or large-integer loss +# class: correctness +# since: 2026-09-29 +# +# id: heldout_oracle_evidence_integrity +# given: held-out oracle cases and provenance are revealed after prediction commitment +# then: oracle ids are unique, values are persistence-stable, provenance identities are nonblank, and malformed evidence cannot be scored as success +# class: evidence +# since: 2026-09-29 +# +# id: heldout_receipt_evidence_binding +# given: a verified plan, verified prediction commitment, and held-out oracle are compared +# then: returned receipt evidence is detached from mutable caller inputs and binds the plan, prediction commitment, oracle digest, results, and counts +# class: evidence +# since: 2026-09-29 +# +# id: heldout_oracle_loading_is_unambiguous +# given: oracle evidence is loaded from a file, source checkout, or installed distribution +# then: duplicate JSON keys are rejected and source checkout data is preferred over an unrelated installed package +# class: correctness +# since: 2026-09-29 +# === END CONTRACTS === + STATUSES = ("SURVIVED", "FALSIFIED", "UNRESOLVED") _PLAN_SCHEMA = "epac.heldout-validation-plan" _PREDICTION_SCHEMA = "epac.heldout-prediction-commitment" @@ -265,10 +303,9 @@ def _json_exact_equal(left: Any, right: Any) -> bool | None: return False if not isinstance(right, (int, float)) or isinstance(right, bool): return False - try: - if not math.isfinite(float(left)) or not math.isfinite(float(right)): - return None - except (OverflowError, TypeError, ValueError): + if isinstance(left, float) and not math.isfinite(left): + return None + if isinstance(right, float) and not math.isfinite(right): return None return left == right if isinstance(left, str) or isinstance(right, str): @@ -414,6 +451,12 @@ def compare_after_freeze( raise ValueError("oracle case inventory does not match frozen validation plan") predictions = commitment_snapshot["predictions"] + unexpected_prediction_ids = sorted(set(predictions) - set(plan_by_id)) + if unexpected_prediction_ids: + raise ValueError( + "prediction commitment includes cases absent from frozen validation plan: " + + ", ".join(unexpected_prediction_ids) + ) results: list[dict[str, Any]] = [] for case_id in sorted(plan_by_id): case = oracle_by_id[case_id] diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index 1f22c00..be61d42 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -20,6 +20,146 @@ ) +# === CHECKS === +# id: check_freeze_is_deterministic_oracle_free_and_plan_bound +# proves: heldout_selection_boundary_frozen_before_predictions, heldout_commitment_persistence_and_identity +# call: self::test_freeze_is_deterministic_oracle_free_and_plan_bound +# mutates: none +# cleanup: none +# +# id: check_plan_rejects_duplicate_ids_and_oracle_fields +# proves: heldout_selection_boundary_frozen_before_predictions +# call: self::test_plan_rejects_duplicate_ids_and_oracle_fields +# mutates: none +# cleanup: none +# +# id: check_plan_rejects_oracle_fields_hidden_in_comparator_rules +# proves: heldout_selection_boundary_frozen_before_predictions +# call: self::test_plan_rejects_oracle_fields_hidden_in_comparator_rules +# mutates: none +# cleanup: none +# +# id: check_prediction_cannot_escape_frozen_case_inventory +# proves: heldout_selection_boundary_frozen_before_predictions +# call: self::test_prediction_cannot_escape_frozen_case_inventory +# mutates: none +# cleanup: none +# +# id: check_prediction_commitment_rejects_non_json_values_before_hashing +# proves: heldout_commitment_persistence_and_identity +# call: self::test_prediction_commitment_rejects_non_json_values_before_hashing +# mutates: none +# cleanup: none +# +# id: check_domain_is_identifier_shaped_and_revalidated +# proves: heldout_selection_boundary_frozen_before_predictions +# call: self::test_domain_is_identifier_shaped_and_revalidated +# mutates: none +# cleanup: none +# +# id: check_programmatic_oracle_rejects_non_json_values_before_evidence_hashing +# proves: heldout_oracle_evidence_integrity +# call: self::test_programmatic_oracle_rejects_non_json_values_before_evidence_hashing +# mutates: none +# cleanup: none +# +# id: check_tampered_prediction_cannot_be_compared +# proves: heldout_commitment_persistence_and_identity +# call: self::test_tampered_prediction_cannot_be_compared +# mutates: none +# cleanup: none +# +# id: check_commitment_revalidates_nonempty_source_identity_even_with_matching_digest +# proves: heldout_commitment_persistence_and_identity +# call: self::test_commitment_revalidates_nonempty_source_identity_even_with_matching_digest +# mutates: none +# cleanup: none +# +# id: check_case_inventory_and_comparator_are_bound_before_comparison +# proves: heldout_selection_boundary_frozen_before_predictions +# call: self::test_case_inventory_and_comparator_are_bound_before_comparison +# mutates: none +# cleanup: none +# +# id: check_external_commitment_rejects_predictions_outside_verified_plan +# proves: heldout_selection_boundary_frozen_before_predictions, heldout_commitment_persistence_and_identity +# call: self::test_external_commitment_rejects_predictions_outside_verified_plan +# mutates: none +# cleanup: none +# +# id: check_exact_comparison_preserves_arbitrary_size_integers +# proves: heldout_comparison_tri_state_semantics +# call: self::test_exact_comparison_preserves_arbitrary_size_integers +# mutates: none +# cleanup: none +# +# id: check_comparison_classifies_only_after_freeze +# proves: heldout_comparison_tri_state_semantics, heldout_receipt_evidence_binding +# call: self::test_comparison_classifies_only_after_freeze +# mutates: none +# cleanup: none +# +# id: check_absent_expected_null_provenance_and_missing_prediction_are_unresolved +# proves: heldout_comparison_tri_state_semantics, heldout_oracle_evidence_integrity +# call: self::test_absent_expected_null_provenance_and_missing_prediction_are_unresolved +# mutates: none +# cleanup: none +# +# id: check_exact_comparison_is_json_type_safe_including_nested_values +# proves: heldout_comparison_tri_state_semantics +# call: self::test_exact_comparison_is_json_type_safe_including_nested_values +# mutates: none +# cleanup: none +# +# id: check_duplicate_oracle_case_ids_are_rejected_before_scoring +# proves: heldout_oracle_evidence_integrity +# call: self::test_duplicate_oracle_case_ids_are_rejected_before_scoring +# mutates: none +# cleanup: none +# +# id: check_nonfinite_numeric_operands_or_tolerance_are_unresolved +# proves: heldout_comparison_tri_state_semantics +# call: self::test_nonfinite_numeric_operands_or_tolerance_are_unresolved +# mutates: none +# cleanup: none +# +# id: check_numeric_tolerance_preserves_large_integer_distinctions +# proves: heldout_comparison_tri_state_semantics +# call: self::test_numeric_tolerance_preserves_large_integer_distinctions +# mutates: none +# cleanup: none +# +# id: check_blank_provenance_identity_is_unresolved +# proves: heldout_oracle_evidence_integrity +# call: self::test_blank_provenance_identity_is_unresolved +# mutates: none +# cleanup: none +# +# id: check_receipt_and_commitment_detach_mutable_evidence_inputs +# proves: heldout_receipt_evidence_binding +# call: self::test_receipt_and_commitment_detach_mutable_evidence_inputs +# mutates: none +# cleanup: none +# +# id: check_load_oracle_rejects_duplicate_json_object_keys +# proves: heldout_oracle_loading_is_unambiguous, heldout_oracle_evidence_integrity +# call: self::test_load_oracle_rejects_duplicate_json_object_keys +# mutates: none +# cleanup: none +# +# id: check_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout +# proves: heldout_oracle_loading_is_unambiguous +# call: self::test_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout +# mutates: none +# cleanup: none +# +# id: check_set_equality_does_not_alias_boolean_and_numeric_values +# proves: heldout_comparison_tri_state_semantics +# call: self::test_set_equality_does_not_alias_boolean_and_numeric_values +# mutates: none +# cleanup: none +# === END CHECKS === + def plan_cases(): return [ {"id": "element:H:valence", "domain": "valence", "comparison": {"kind": "exact"}}, @@ -275,6 +415,73 @@ def test_case_inventory_and_comparator_are_bound_before_comparison(): compare_after_freeze(commitment, changed_rule, validation_plan=plan) +def test_external_commitment_rejects_predictions_outside_verified_plan(): + plan = freeze_validation_plan([ + {"id": "x", "domain": "fixture", "comparison": {"kind": "exact"}} + ]) + commitment = freeze_predictions( + {"x": 1}, + source_identity="epac@test", + validation_plan=plan, + ) + forged = deepcopy(commitment) + forged["predictions"]["outside"] = 99 + unsigned = { + k: forged[k] + for k in ("schema", "version", "source_identity", "validation_plan_sha256", "predictions") + } + forged["commitment_sha256"] = _digest_envelope(unsigned) + verify_commitment(forged) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": "fixture", + "comparison": {"kind": "exact"}, + "expected": 1, + "provenance": {"authority": "fixture", "locator": "membership"}, + }], + } + with pytest.raises(ValueError, match="absent from frozen validation plan"): + compare_after_freeze(forged, heldout, validation_plan=plan) + + +def test_exact_comparison_preserves_arbitrary_size_integers(): + huge = 10 ** 400 + plan = freeze_validation_plan([ + {"id": "same", "domain": "fixture", "comparison": {"kind": "exact"}}, + {"id": "different", "domain": "fixture", "comparison": {"kind": "exact"}}, + ]) + commitment = freeze_predictions( + {"same": huge, "different": huge + 1}, + source_identity="epac@test", + validation_plan=plan, + ) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [ + { + "id": "same", + "domain": "fixture", + "comparison": {"kind": "exact"}, + "expected": huge, + "provenance": {"authority": "fixture", "locator": "huge-same"}, + }, + { + "id": "different", + "domain": "fixture", + "comparison": {"kind": "exact"}, + "expected": huge, + "provenance": {"authority": "fixture", "locator": "huge-different"}, + }, + ], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 1, "FALSIFIED": 1, "UNRESOLVED": 0} + + def test_comparison_classifies_only_after_freeze(): plan, commitment = frozen({ "element:H:valence": 1, @@ -363,35 +570,37 @@ def test_duplicate_oracle_case_ids_are_rejected_before_scoring(): compare_after_freeze(commitment, duplicate, validation_plan=plan) -@pytest.mark.parametrize( - ("expected", "predicted", "tolerance"), - [ +def test_nonfinite_numeric_operands_or_tolerance_are_unresolved(): + cases = [ (1.0, math.inf, 0.1), (math.inf, 1.0, 0.1), (1.0, 1.0, math.inf), (1.0, math.nan, 0.1), (1.0, 1.0, math.nan), - ], -) -def test_nonfinite_numeric_operands_or_tolerance_are_unresolved(expected, predicted, tolerance): - plan = freeze_validation_plan([ - {"id": "x", "domain": "fixture", - "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": tolerance}} - ]) - commitment = freeze_predictions({"x": predicted}, source_identity="epac@test", validation_plan=plan) - heldout = { - "schema": "epac.heldout-chemistry-oracle", - "version": "v1", - "cases": [{ - "id": "x", - "domain": "fixture", - "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": tolerance}, - "expected": expected, - "provenance": {"authority": "fixture", "locator": "x"}, - }], - } - receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) - assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} + ] + for expected, predicted, tolerance in cases: + plan = freeze_validation_plan([ + {"id": "x", "domain": "fixture", + "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": tolerance}} + ]) + commitment = freeze_predictions( + {"x": predicted}, + source_identity="epac@test", + validation_plan=plan, + ) + heldout = { + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "cases": [{ + "id": "x", + "domain": "fixture", + "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": tolerance}, + "expected": expected, + "provenance": {"authority": "fixture", "locator": "x"}, + }], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} def test_numeric_tolerance_preserves_large_integer_distinctions(): From e85fd0f4c4b73aaeea3bf31796cc52da3adb3d34 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 13:01:17 -0700 Subject: [PATCH 10/19] fix: refresh join evidence after package inventory reconciliation Regenerate the receipt and audit from their declared source files. Only source hashes and derived evidence identities change; outcomes, authority, nonclaims, and hmmm remain unchanged. Follows fresh-making. --- data/epac-join-term-v0-receipt.json | 2 +- docs/epac-join-term-v0-audit.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/epac-join-term-v0-receipt.json b/data/epac-join-term-v0-receipt.json index 74230fa..34939d2 100644 --- a/data/epac-join-term-v0-receipt.json +++ b/data/epac-join-term-v0-receipt.json @@ -1 +1 @@ -{"authority":{"epac":{"authority":"schema, constructor, equality, serialization, recovery, and evidence","base_commit":"1e5c999286f12221eff9870d1372203ac7935f2a","base_tree":"ce7781938ff684d826bd91f475b5423bd56b146a","feature_identity":"source-manifest-sha256","repository":"The-Interdependency/epac"},"metapat":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_fixture_path":"src/metapat/fixtures/epac-join-terms-application-v4.json","application_fixture_sha256":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","application_id":"metapat.application.epac_join_terms","application_source_path":"src/metapat/epac_join_terms.py","application_source_sha256":"6cfb4a4109fd43a4f8343b48dd6fd72b74346f4609a65911cbf96188412b6ef0","application_version":"epac-join-terms-application-v4","authority":"semantic spine role license only","authority_transfer":false,"producer_commit":"1cdfb09dd00a451cee30eec2e78624df8c682662","producer_tree":"d946a5a18b0c53fd561dcd9d68fdf36d5dd11638","repository":"The-Interdependency/metapat"},"skill_lib":{"authority":"build and evidence doctrine only","authority_transfer":false,"commit":"516933d98f9de376f4e498f44059043dc0d96470","repository":"The-Interdependency/skill-lib","tree":"a1465ddf1de9519c9abc1a97d058432ba71bb257"},"ucns":{"authority_transfer":false,"consumed":false,"law_modified":false}},"candidate":{"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","joins":[{"join_id":"epac.join:fixture-a:S0-S1","order":1,"provenance":["epac.join-term-v0","authored-near-join:S0->S1"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S0","target_origin_id":"epac.origin:fixture-a:S1"},{"join_id":"epac.join:fixture-a:S1-S2","order":2,"provenance":["epac.join-term-v0","authored-near-join:S1->S2"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S1","target_origin_id":"epac.origin:fixture-a:S2"},{"join_id":"epac.join:fixture-a:S2-S3","order":3,"provenance":["epac.join-term-v0","authored-near-join:S2->S3"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S2","target_origin_id":"epac.origin:fixture-a:S3"},{"join_id":"epac.join:fixture-a:S3-S4","order":4,"provenance":["epac.join-term-v0","authored-near-join:S3->S4"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S3","target_origin_id":"epac.origin:fixture-a:S4"},{"join_id":"epac.join:fixture-a:S4-S5","order":5,"provenance":["epac.join-term-v0","authored-near-join:S4->S5"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S4","target_origin_id":"epac.origin:fixture-a:S5"},{"join_id":"epac.join:fixture-a:S5-S6","order":6,"provenance":["epac.join-term-v0","authored-near-join:S5->S6"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S5","target_origin_id":"epac.origin:fixture-a:S6"}],"legacy_bag":{"excluded_from_tensor":true,"kind":"bag","scale_slot_counts":{"S0":2,"S1":2,"S2":3,"S3":3,"S4":2,"S5":4,"S6":2},"slot_kind_counts":{"hole":6,"leftover":6,"member":6}},"object_kind":"multi-origin-join-tree","origins":[{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0]},"domain_name":"subatomic slot","k":2,"origin_id":"epac.origin:fixture-a:S0","phase":{"denominator":1,"numerator":0},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S0"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":1,"numerator":0}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S0","shape_signature":["0:seed:leftover","1:zero:hole"],"slots":[{"facing":-2,"kind":"leftover","name":"seed","order":0,"participant_id":"epac.leftover:fixture-a:S0:seed","participant_scale":null},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0]},"domain_name":"atomic","k":2,"origin_id":"epac.origin:fixture-a:S1","phase":{"denominator":8,"numerator":1},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S1"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":4},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S1","shape_signature":["0:subatomic:member","1:zero:hole"],"slots":[{"facing":4,"kind":"member","name":"subatomic","order":0,"participant_id":"epac.origin:fixture-a:S0","participant_scale":"S0"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1,0]},"domain_name":"join/arity","k":3,"origin_id":"epac.origin:fixture-a:S2","phase":{"denominator":4,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S2","authored-seating-chart"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":3},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":-3},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S2","shape_signature":["0:core:member","1:guest:leftover","2:vacancy:hole"],"slots":[{"facing":3,"kind":"member","name":"core","order":0,"participant_id":"epac.origin:fixture-a:S1","participant_scale":"S1"},{"facing":-3,"kind":"leftover","name":"guest","order":1,"participant_id":"epac.leftover:fixture-a:S2:guest","participant_scale":null},{"facing":0,"kind":"hole","name":"vacancy","order":2,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0,1]},"domain_name":"embed","k":3,"origin_id":"epac.origin:fixture-a:S3","phase":{"denominator":8,"numerator":3},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S3"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":-5},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":2}],"s5_energy_readout":null,"scale":"S3","shape_signature":["0:join:member","1:zero:hole","2:embed-context:leftover"],"slots":[{"facing":-5,"kind":"member","name":"join","order":0,"participant_id":"epac.origin:fixture-a:S2","participant_scale":"S2"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null},{"facing":2,"kind":"leftover","name":"embed-context","order":2,"participant_id":"epac.leftover:fixture-a:S3:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"electronic state","k":2,"origin_id":"epac.origin:fixture-a:S4","phase":{"denominator":2,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S4"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":2,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S4","shape_signature":["0:embed:member","1:electronic-context:leftover"],"slots":[{"facing":2,"kind":"member","name":"embed","order":0,"participant_id":"epac.origin:fixture-a:S3","participant_scale":"S3"},{"facing":-1,"kind":"leftover","name":"electronic-context","order":1,"participant_id":"epac.leftover:fixture-a:S4:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0,-1,0]},"domain_name":"EPAC energy readout","k":4,"origin_id":"epac.origin:fixture-a:S5","phase":{"denominator":8,"numerator":5},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S5"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":5}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.capacity.v0","value":4},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":7},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[3].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":{"measured_property":"slots[*].kind,k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.occupied-slots-over-k.v0","value":{"denominator":2,"numerator":1}},"scale":"S5","shape_signature":["0:electronic-state:member","1:zero-a:hole","2:occupancy-context:leftover","3:zero-b:hole"],"slots":[{"facing":7,"kind":"member","name":"electronic-state","order":0,"participant_id":"epac.origin:fixture-a:S4","participant_scale":"S4"},{"facing":0,"kind":"hole","name":"zero-a","order":1,"participant_id":null,"participant_scale":null},{"facing":-2,"kind":"leftover","name":"occupancy-context","order":2,"participant_id":"epac.leftover:fixture-a:S5:context","participant_scale":null},{"facing":0,"kind":"hole","name":"zero-b","order":3,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"ensemble","k":2,"origin_id":"epac.origin:fixture-a:S6","phase":{"denominator":4,"numerator":3},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S6"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":1},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S6","shape_signature":["0:energy-readout:member","1:ensemble-context:leftover"],"slots":[{"facing":1,"kind":"member","name":"energy-readout","order":0,"participant_id":"epac.origin:fixture-a:S5","participant_scale":"S5"},{"facing":-1,"kind":"leftover","name":"ensemble-context","order":1,"participant_id":"epac.leftover:fixture-a:S6:context","participant_scale":null}]}],"schema_id":"epac.multi-origin-join-tree","schema_version":"0.2.0","semantic_field_bindings":{"join.join_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"authored join record identity","spine_name":"Thing"},"join.order":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale relation position","spine_name":"Tensor"},"join.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"authored join provenance","spine_name":"Thing"},"join.relation":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale join","spine_name":"Tensor"},"join.source_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"source origin identity","spine_name":"Thing"},"join.target_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"new target origin identity","spine_name":"Thing"},"legacy_bag.excluded_from_tensor":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"tensor-exclusion marker","spine_name":"Thing"},"legacy_bag.kind":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"legacy sidecar kind","spine_name":"Thing"},"legacy_bag.scale_slot_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by scale","spine_name":"Thing"},"legacy_bag.slot_kind_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by kind","spine_name":"Thing"},"origin.arity":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"occupied seating arity","spine_name":"Boundary"},"origin.bearing":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"ordered static slot-facing sign readouts","spine_name":"Scalar"},"origin.domain_name":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC scale name","spine_name":"Simplex"},"origin.k":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"join window k","spine_name":"Boundary"},"origin.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"origin identity","spine_name":"Thing"},"origin.phase":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"EPAC exact phase","spine_name":"State"},"origin.phase_chart":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"visible or lifted phase record","spine_name":"State"},"origin.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"origin provenance","spine_name":"Thing"},"origin.readouts":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"identified phase, capacity, and slot-facing readouts","spine_name":"Scalar"},"origin.s5_energy_readout":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"EPAC S5 occupancy functional","spine_name":"Scalar"},"origin.scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"EPAC scale origin","spine_name":"Simplex"},"origin.shape_signature":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"ordered seating shape","spine_name":"Tensor"},"origin.slots":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"named ordered join slots","spine_name":"Boundary"},"readout.measured_property":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"named source state property","spine_name":"State"},"readout.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"readout source origin identity","spine_name":"Thing"},"readout.rule_id":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC readout rule identity","spine_name":"Thing"},"readout.value":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"computed EPAC readout value","spine_name":"Scalar"},"slot.facing":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"stored slot-facing property","spine_name":"State"},"slot.kind":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"member, hole, or leftover","spine_name":"Boundary"},"slot.name":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot name","spine_name":"Boundary"},"slot.order":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot order","spine_name":"Boundary"},"slot.participant_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"member or leftover identity","spine_name":"Thing"},"slot.participant_scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"member source scale","spine_name":"Simplex"},"tree.joins":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"ordered authored adjacent-scale relations","spine_name":"Tensor"},"tree.legacy_bag":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"non-structural legacy bag sidecar","spine_name":"Thing"},"tree.object_kind":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"multi-origin join-tree kind","spine_name":"Thing"},"tree.origins":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"S0-through-S6 origin tensor","spine_name":"Tensor"}},"semantic_license":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_id":"metapat.application.epac_join_terms","application_version":"epac-join-terms-application-v4","authority":"The-Interdependency/metapat","authority_transfer":false}},"evidence":{"declared_check_count":17,"declared_contract_count":17,"deterministic_regeneration_command":"python tools/generate_join_term_v0.py --check","molecular_shape_standing":"FALSIFIED (preserved; tested separately)","public_recovery_uses_constructor_state":false},"evidence_digest":"215844eda0b1b1f3edf95bf7b360df84deecc97382a88f777c150fa50e359839","evidence_epoch":"2026-09-30","historical_original_authoring_runtime":{"executable":"/usr/bin/python3.12","executable_sha256":"e50d468e8b0adfb05733f5b87b3cff34829c4a8c1aea50c865aa8bdfe4bb150f","implementation":"CPython","version":"3.12.3"},"hmmm":["Authored adjacency and its stored order do not establish state changes or actual occurrence order; Transformation and Time remain unlicensed for this candidate.","Whether a useful operation exists that requires secret EPAC state and cannot be efficiently reproduced from a legitimate public projection remains unestablished; this candidate is wholly public.","Whether exact lineage and seating replay improve an independently preregistered EPAC task remains unmeasured.","Whether any later UCNS correspondence is useful requires a separate license and test without rewriting UCNS law."],"inputs":{"identity_namespace":"fixture-a","input_is_public":true,"phase_chart_rule":"visible on even-numbered scales; lifted on odd-numbered scales","phase_values":[{"denominator":1,"numerator":0},{"denominator":8,"numerator":1},{"denominator":4,"numerator":1},{"denominator":8,"numerator":3},{"denominator":2,"numerator":1},{"denominator":8,"numerator":5},{"denominator":4,"numerator":3}],"private_input_supplied":false,"s2_seating_order":["core","guest","vacancy"]},"nonclaims":["Vector action, resulting state Transformation, or actual Time sequence","chemical or physical truth","molecular geometry or shape prediction","UCNS correspondence, topology, theorem status, or gonol identity","energy coupling, conservation, entropy, force, or field behavior","cryptographic secrecy, confidentiality, public-key separation, or computational hardness","PCEA compatibility","production suitability","release or protocol authority"],"results":{"authored_join_count":6,"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","candidate_json_sha256":"821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd","classification":"PUBLIC_ONLY_RECOVERY_SURVIVED","hole_count":6,"leftover_count":6,"legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","legacy_projection":"slot-count bag","member_count":6,"native_operation":"exact authored origin-lineage and named-seating replay","origin_count":7,"s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"same_bag_alpha_renamed_identity_witness":{"bags_equal":true,"baseline_s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"conclusion":"the legacy bag does not select exact instance identity labels, while structural lineage up to alpha-renaming is unchanged","exact_instance_lineages_equal":false,"join_isomorphic":true,"renamed_candidate_digest":"7e30d6f4942e215462254dec9723a1deeb4e71d680e2cc960f227b02240f420d","renamed_identity_namespace":"fixture-b","renamed_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","renamed_s6_lineage":["epac.origin:fixture-b:S0","epac.origin:fixture-b:S1","epac.origin:fixture-b:S2","epac.origin:fixture-b:S3","epac.origin:fixture-b:S4","epac.origin:fixture-b:S5","epac.origin:fixture-b:S6"]},"same_bag_nonisomorphic_witness":{"bags_equal":true,"baseline_candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","conclusion":"the legacy bag cannot uniquely recover authored seating or the complete join tree","join_isomorphic":false,"reordered_candidate_digest":"f74f9227a79acf84588b11d153bf66f9490d3aa18d8e899b6266c11ca3594633","reordered_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","reordered_s2_seating_order":["guest","core","vacancy"]},"slot_count":18},"runtime_scope":"Deterministic evidence is runtime-independent; current execution identities belong to the exact-head artifact replay receipts, not this historical authoring record.","schema_id":"epac.join-term-v0-evidence","schema_version":"1.0.0","source_files_sha256":{"README.md":"7e28c7c092f7551b11a03c9db7862e27ec88ff6f81167ed3d3f99db26e29a18b","data/metapat-epac-join-terms-application-v4.json":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","docs/PROVENANCE.md":"b410815a47ea9a8229406241616a9dc143ff09eb8da717456c019379efa6d5ee","docs/domain-claims.md":"f6f7f517e127a517a4acccba5527692239700c128b7bf0ecef9603e8c155c911","docs/multi-origin-join-term-v0.md":"6efb441f4421edf84c22556b46e438cc2da4f51536c522d3b6556af7b1c66a39","docs/research-status.md":"1f5194f0dc44521b6dc81d9a9494c72ff385fd879b8eb2b2849824640c5c2d07","docs/work-graph.json":"4edb37ace798511ff4b5f667afd1f0a3852ce78b7335349aaa9662171de0b9e9","docs/work-graphs/epac-join-term-v0.json":"7487441ec7e3ff3203635e245fccba5d293192cef5201c657946a15788166f16","epac_boundary_probe_completeness.py":"61df1624adc2970fe2e7d9d94ee15359decd42ddb7285ac223bd46eb748541ec","epac_join_term.py":"4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af","pyproject.toml":"414cdc71cdb2e0ff859fcb59a359d57a5327e6b105e0ff010a522216dcd3881f","tests/test_boundary_probe_completeness.py":"254590ac58a54979f0f05534cea357e2369577e96304dda0ae337f3114d13ac4","tests/test_epac_join_term.py":"ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8","tools/generate_join_term_v0.py":"1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331"},"source_manifest_sha256":"c3aad9a890e4ea42afcc8118a14c21b8ef10044ec1520a0d32f7daf668fbff0b"} +{"authority":{"epac":{"authority":"schema, constructor, equality, serialization, recovery, and evidence","base_commit":"1e5c999286f12221eff9870d1372203ac7935f2a","base_tree":"ce7781938ff684d826bd91f475b5423bd56b146a","feature_identity":"source-manifest-sha256","repository":"The-Interdependency/epac"},"metapat":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_fixture_path":"src/metapat/fixtures/epac-join-terms-application-v4.json","application_fixture_sha256":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","application_id":"metapat.application.epac_join_terms","application_source_path":"src/metapat/epac_join_terms.py","application_source_sha256":"6cfb4a4109fd43a4f8343b48dd6fd72b74346f4609a65911cbf96188412b6ef0","application_version":"epac-join-terms-application-v4","authority":"semantic spine role license only","authority_transfer":false,"producer_commit":"1cdfb09dd00a451cee30eec2e78624df8c682662","producer_tree":"d946a5a18b0c53fd561dcd9d68fdf36d5dd11638","repository":"The-Interdependency/metapat"},"skill_lib":{"authority":"build and evidence doctrine only","authority_transfer":false,"commit":"516933d98f9de376f4e498f44059043dc0d96470","repository":"The-Interdependency/skill-lib","tree":"a1465ddf1de9519c9abc1a97d058432ba71bb257"},"ucns":{"authority_transfer":false,"consumed":false,"law_modified":false}},"candidate":{"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","joins":[{"join_id":"epac.join:fixture-a:S0-S1","order":1,"provenance":["epac.join-term-v0","authored-near-join:S0->S1"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S0","target_origin_id":"epac.origin:fixture-a:S1"},{"join_id":"epac.join:fixture-a:S1-S2","order":2,"provenance":["epac.join-term-v0","authored-near-join:S1->S2"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S1","target_origin_id":"epac.origin:fixture-a:S2"},{"join_id":"epac.join:fixture-a:S2-S3","order":3,"provenance":["epac.join-term-v0","authored-near-join:S2->S3"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S2","target_origin_id":"epac.origin:fixture-a:S3"},{"join_id":"epac.join:fixture-a:S3-S4","order":4,"provenance":["epac.join-term-v0","authored-near-join:S3->S4"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S3","target_origin_id":"epac.origin:fixture-a:S4"},{"join_id":"epac.join:fixture-a:S4-S5","order":5,"provenance":["epac.join-term-v0","authored-near-join:S4->S5"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S4","target_origin_id":"epac.origin:fixture-a:S5"},{"join_id":"epac.join:fixture-a:S5-S6","order":6,"provenance":["epac.join-term-v0","authored-near-join:S5->S6"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S5","target_origin_id":"epac.origin:fixture-a:S6"}],"legacy_bag":{"excluded_from_tensor":true,"kind":"bag","scale_slot_counts":{"S0":2,"S1":2,"S2":3,"S3":3,"S4":2,"S5":4,"S6":2},"slot_kind_counts":{"hole":6,"leftover":6,"member":6}},"object_kind":"multi-origin-join-tree","origins":[{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0]},"domain_name":"subatomic slot","k":2,"origin_id":"epac.origin:fixture-a:S0","phase":{"denominator":1,"numerator":0},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S0"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":1,"numerator":0}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S0","shape_signature":["0:seed:leftover","1:zero:hole"],"slots":[{"facing":-2,"kind":"leftover","name":"seed","order":0,"participant_id":"epac.leftover:fixture-a:S0:seed","participant_scale":null},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0]},"domain_name":"atomic","k":2,"origin_id":"epac.origin:fixture-a:S1","phase":{"denominator":8,"numerator":1},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S1"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":4},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S1","shape_signature":["0:subatomic:member","1:zero:hole"],"slots":[{"facing":4,"kind":"member","name":"subatomic","order":0,"participant_id":"epac.origin:fixture-a:S0","participant_scale":"S0"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1,0]},"domain_name":"join/arity","k":3,"origin_id":"epac.origin:fixture-a:S2","phase":{"denominator":4,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S2","authored-seating-chart"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":3},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":-3},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S2","shape_signature":["0:core:member","1:guest:leftover","2:vacancy:hole"],"slots":[{"facing":3,"kind":"member","name":"core","order":0,"participant_id":"epac.origin:fixture-a:S1","participant_scale":"S1"},{"facing":-3,"kind":"leftover","name":"guest","order":1,"participant_id":"epac.leftover:fixture-a:S2:guest","participant_scale":null},{"facing":0,"kind":"hole","name":"vacancy","order":2,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0,1]},"domain_name":"embed","k":3,"origin_id":"epac.origin:fixture-a:S3","phase":{"denominator":8,"numerator":3},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S3"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":-5},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":2}],"s5_energy_readout":null,"scale":"S3","shape_signature":["0:join:member","1:zero:hole","2:embed-context:leftover"],"slots":[{"facing":-5,"kind":"member","name":"join","order":0,"participant_id":"epac.origin:fixture-a:S2","participant_scale":"S2"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null},{"facing":2,"kind":"leftover","name":"embed-context","order":2,"participant_id":"epac.leftover:fixture-a:S3:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"electronic state","k":2,"origin_id":"epac.origin:fixture-a:S4","phase":{"denominator":2,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S4"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":2,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S4","shape_signature":["0:embed:member","1:electronic-context:leftover"],"slots":[{"facing":2,"kind":"member","name":"embed","order":0,"participant_id":"epac.origin:fixture-a:S3","participant_scale":"S3"},{"facing":-1,"kind":"leftover","name":"electronic-context","order":1,"participant_id":"epac.leftover:fixture-a:S4:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0,-1,0]},"domain_name":"EPAC energy readout","k":4,"origin_id":"epac.origin:fixture-a:S5","phase":{"denominator":8,"numerator":5},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S5"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":5}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.capacity.v0","value":4},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":7},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[3].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":{"measured_property":"slots[*].kind,k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.occupied-slots-over-k.v0","value":{"denominator":2,"numerator":1}},"scale":"S5","shape_signature":["0:electronic-state:member","1:zero-a:hole","2:occupancy-context:leftover","3:zero-b:hole"],"slots":[{"facing":7,"kind":"member","name":"electronic-state","order":0,"participant_id":"epac.origin:fixture-a:S4","participant_scale":"S4"},{"facing":0,"kind":"hole","name":"zero-a","order":1,"participant_id":null,"participant_scale":null},{"facing":-2,"kind":"leftover","name":"occupancy-context","order":2,"participant_id":"epac.leftover:fixture-a:S5:context","participant_scale":null},{"facing":0,"kind":"hole","name":"zero-b","order":3,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"ensemble","k":2,"origin_id":"epac.origin:fixture-a:S6","phase":{"denominator":4,"numerator":3},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S6"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":1},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S6","shape_signature":["0:energy-readout:member","1:ensemble-context:leftover"],"slots":[{"facing":1,"kind":"member","name":"energy-readout","order":0,"participant_id":"epac.origin:fixture-a:S5","participant_scale":"S5"},{"facing":-1,"kind":"leftover","name":"ensemble-context","order":1,"participant_id":"epac.leftover:fixture-a:S6:context","participant_scale":null}]}],"schema_id":"epac.multi-origin-join-tree","schema_version":"0.2.0","semantic_field_bindings":{"join.join_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"authored join record identity","spine_name":"Thing"},"join.order":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale relation position","spine_name":"Tensor"},"join.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"authored join provenance","spine_name":"Thing"},"join.relation":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale join","spine_name":"Tensor"},"join.source_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"source origin identity","spine_name":"Thing"},"join.target_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"new target origin identity","spine_name":"Thing"},"legacy_bag.excluded_from_tensor":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"tensor-exclusion marker","spine_name":"Thing"},"legacy_bag.kind":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"legacy sidecar kind","spine_name":"Thing"},"legacy_bag.scale_slot_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by scale","spine_name":"Thing"},"legacy_bag.slot_kind_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by kind","spine_name":"Thing"},"origin.arity":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"occupied seating arity","spine_name":"Boundary"},"origin.bearing":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"ordered static slot-facing sign readouts","spine_name":"Scalar"},"origin.domain_name":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC scale name","spine_name":"Simplex"},"origin.k":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"join window k","spine_name":"Boundary"},"origin.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"origin identity","spine_name":"Thing"},"origin.phase":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"EPAC exact phase","spine_name":"State"},"origin.phase_chart":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"visible or lifted phase record","spine_name":"State"},"origin.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"origin provenance","spine_name":"Thing"},"origin.readouts":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"identified phase, capacity, and slot-facing readouts","spine_name":"Scalar"},"origin.s5_energy_readout":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"EPAC S5 occupancy functional","spine_name":"Scalar"},"origin.scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"EPAC scale origin","spine_name":"Simplex"},"origin.shape_signature":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"ordered seating shape","spine_name":"Tensor"},"origin.slots":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"named ordered join slots","spine_name":"Boundary"},"readout.measured_property":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"named source state property","spine_name":"State"},"readout.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"readout source origin identity","spine_name":"Thing"},"readout.rule_id":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC readout rule identity","spine_name":"Thing"},"readout.value":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"computed EPAC readout value","spine_name":"Scalar"},"slot.facing":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"stored slot-facing property","spine_name":"State"},"slot.kind":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"member, hole, or leftover","spine_name":"Boundary"},"slot.name":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot name","spine_name":"Boundary"},"slot.order":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot order","spine_name":"Boundary"},"slot.participant_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"member or leftover identity","spine_name":"Thing"},"slot.participant_scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"member source scale","spine_name":"Simplex"},"tree.joins":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"ordered authored adjacent-scale relations","spine_name":"Tensor"},"tree.legacy_bag":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"non-structural legacy bag sidecar","spine_name":"Thing"},"tree.object_kind":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"multi-origin join-tree kind","spine_name":"Thing"},"tree.origins":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"S0-through-S6 origin tensor","spine_name":"Tensor"}},"semantic_license":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_id":"metapat.application.epac_join_terms","application_version":"epac-join-terms-application-v4","authority":"The-Interdependency/metapat","authority_transfer":false}},"evidence":{"declared_check_count":17,"declared_contract_count":17,"deterministic_regeneration_command":"python tools/generate_join_term_v0.py --check","molecular_shape_standing":"FALSIFIED (preserved; tested separately)","public_recovery_uses_constructor_state":false},"evidence_digest":"e3a5160c642050cc2d4f4bae126b34328b91fe17425fec5431182a7486adab95","evidence_epoch":"2026-09-30","historical_original_authoring_runtime":{"executable":"/usr/bin/python3.12","executable_sha256":"e50d468e8b0adfb05733f5b87b3cff34829c4a8c1aea50c865aa8bdfe4bb150f","implementation":"CPython","version":"3.12.3"},"hmmm":["Authored adjacency and its stored order do not establish state changes or actual occurrence order; Transformation and Time remain unlicensed for this candidate.","Whether a useful operation exists that requires secret EPAC state and cannot be efficiently reproduced from a legitimate public projection remains unestablished; this candidate is wholly public.","Whether exact lineage and seating replay improve an independently preregistered EPAC task remains unmeasured.","Whether any later UCNS correspondence is useful requires a separate license and test without rewriting UCNS law."],"inputs":{"identity_namespace":"fixture-a","input_is_public":true,"phase_chart_rule":"visible on even-numbered scales; lifted on odd-numbered scales","phase_values":[{"denominator":1,"numerator":0},{"denominator":8,"numerator":1},{"denominator":4,"numerator":1},{"denominator":8,"numerator":3},{"denominator":2,"numerator":1},{"denominator":8,"numerator":5},{"denominator":4,"numerator":3}],"private_input_supplied":false,"s2_seating_order":["core","guest","vacancy"]},"nonclaims":["Vector action, resulting state Transformation, or actual Time sequence","chemical or physical truth","molecular geometry or shape prediction","UCNS correspondence, topology, theorem status, or gonol identity","energy coupling, conservation, entropy, force, or field behavior","cryptographic secrecy, confidentiality, public-key separation, or computational hardness","PCEA compatibility","production suitability","release or protocol authority"],"results":{"authored_join_count":6,"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","candidate_json_sha256":"821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd","classification":"PUBLIC_ONLY_RECOVERY_SURVIVED","hole_count":6,"leftover_count":6,"legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","legacy_projection":"slot-count bag","member_count":6,"native_operation":"exact authored origin-lineage and named-seating replay","origin_count":7,"s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"same_bag_alpha_renamed_identity_witness":{"bags_equal":true,"baseline_s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"conclusion":"the legacy bag does not select exact instance identity labels, while structural lineage up to alpha-renaming is unchanged","exact_instance_lineages_equal":false,"join_isomorphic":true,"renamed_candidate_digest":"7e30d6f4942e215462254dec9723a1deeb4e71d680e2cc960f227b02240f420d","renamed_identity_namespace":"fixture-b","renamed_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","renamed_s6_lineage":["epac.origin:fixture-b:S0","epac.origin:fixture-b:S1","epac.origin:fixture-b:S2","epac.origin:fixture-b:S3","epac.origin:fixture-b:S4","epac.origin:fixture-b:S5","epac.origin:fixture-b:S6"]},"same_bag_nonisomorphic_witness":{"bags_equal":true,"baseline_candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","conclusion":"the legacy bag cannot uniquely recover authored seating or the complete join tree","join_isomorphic":false,"reordered_candidate_digest":"f74f9227a79acf84588b11d153bf66f9490d3aa18d8e899b6266c11ca3594633","reordered_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","reordered_s2_seating_order":["guest","core","vacancy"]},"slot_count":18},"runtime_scope":"Deterministic evidence is runtime-independent; current execution identities belong to the exact-head artifact replay receipts, not this historical authoring record.","schema_id":"epac.join-term-v0-evidence","schema_version":"1.0.0","source_files_sha256":{"README.md":"7e28c7c092f7551b11a03c9db7862e27ec88ff6f81167ed3d3f99db26e29a18b","data/metapat-epac-join-terms-application-v4.json":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","docs/PROVENANCE.md":"b410815a47ea9a8229406241616a9dc143ff09eb8da717456c019379efa6d5ee","docs/domain-claims.md":"f6f7f517e127a517a4acccba5527692239700c128b7bf0ecef9603e8c155c911","docs/multi-origin-join-term-v0.md":"6efb441f4421edf84c22556b46e438cc2da4f51536c522d3b6556af7b1c66a39","docs/research-status.md":"1f5194f0dc44521b6dc81d9a9494c72ff385fd879b8eb2b2849824640c5c2d07","docs/work-graph.json":"4edb37ace798511ff4b5f667afd1f0a3852ce78b7335349aaa9662171de0b9e9","docs/work-graphs/epac-join-term-v0.json":"7487441ec7e3ff3203635e245fccba5d293192cef5201c657946a15788166f16","epac_boundary_probe_completeness.py":"1bc8a3e990e99d52b9636eb1f656f3da0d6178c42de9dd86d811d9211e87f014","epac_join_term.py":"4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af","pyproject.toml":"44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd","tests/test_boundary_probe_completeness.py":"c0d0196718e14126a82d669359ae58b207d46b1a20ff5158828c9ca1c7422c8f","tests/test_epac_join_term.py":"ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8","tools/generate_join_term_v0.py":"1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331"},"source_manifest_sha256":"aaa855bba19979641bedf562dfe90cf18884fcec17ab92fd052f3e11691447c5"} diff --git a/docs/epac-join-term-v0-audit.md b/docs/epac-join-term-v0-audit.md index ba3ed21..70bc026 100644 --- a/docs/epac-join-term-v0-audit.md +++ b/docs/epac-join-term-v0-audit.md @@ -7,7 +7,7 @@ The public receipt recovers one complete typed S0-through-S6 join tree and its e ## Exact authority - EPAC base: `1e5c999286f12221eff9870d1372203ac7935f2a` (tree `ce7781938ff684d826bd91f475b5423bd56b146a`) -- EPAC feature source manifest: `c3aad9a890e4ea42afcc8118a14c21b8ef10044ec1520a0d32f7daf668fbff0b` +- EPAC feature source manifest: `aaa855bba19979641bedf562dfe90cf18884fcec17ab92fd052f3e11691447c5` - METAPAT producer: `1cdfb09dd00a451cee30eec2e78624df8c682662` (tree `d946a5a18b0c53fd561dcd9d68fdf36d5dd11638`) - METAPAT application: `metapat.application.epac_join_terms@epac-join-terms-application-v4` - METAPAT application digest: `461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b` @@ -20,7 +20,7 @@ The containing feature commit cannot be embedded in a file inside itself. The ev - Candidate digest: `11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442` - Candidate JSON SHA-256: `821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd` -- Evidence digest: `215844eda0b1b1f3edf95bf7b360df84deecc97382a88f777c150fa50e359839` +- Evidence digest: `e3a5160c642050cc2d4f4bae126b34328b91fe17425fec5431182a7486adab95` - Origins: `7` - Authored joins (no Transformation or Time claim): `6` - Slots: `18` (`6` members, `6` holes, `6` leftovers) @@ -56,10 +56,10 @@ This is information loss. It is not a secrecy, computational-hardness, or confid - `docs/research-status.md`: `1f5194f0dc44521b6dc81d9a9494c72ff385fd879b8eb2b2849824640c5c2d07` - `docs/work-graph.json`: `4edb37ace798511ff4b5f667afd1f0a3852ce78b7335349aaa9662171de0b9e9` - `docs/work-graphs/epac-join-term-v0.json`: `7487441ec7e3ff3203635e245fccba5d293192cef5201c657946a15788166f16` -- `epac_boundary_probe_completeness.py`: `61df1624adc2970fe2e7d9d94ee15359decd42ddb7285ac223bd46eb748541ec` +- `epac_boundary_probe_completeness.py`: `1bc8a3e990e99d52b9636eb1f656f3da0d6178c42de9dd86d811d9211e87f014` - `epac_join_term.py`: `4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af` -- `pyproject.toml`: `414cdc71cdb2e0ff859fcb59a359d57a5327e6b105e0ff010a522216dcd3881f` -- `tests/test_boundary_probe_completeness.py`: `254590ac58a54979f0f05534cea357e2369577e96304dda0ae337f3114d13ac4` +- `pyproject.toml`: `44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd` +- `tests/test_boundary_probe_completeness.py`: `c0d0196718e14126a82d669359ae58b207d46b1a20ff5158828c9ca1c7422c8f` - `tests/test_epac_join_term.py`: `ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8` - `tools/generate_join_term_v0.py`: `1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331` From d6f10ccc0438bda494001b1fe2b053e2ec63024b Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 13:30:08 -0700 Subject: [PATCH 11/19] fix: preserve unresolved provenance and verify persisted receipts Keep hmmm authority/locator identities UNRESOLVED. Add duplicate-safe receipt loading with envelope, digest, row, status/evidence, and count checks; use it in the runnable workflow. Link regression witnesses and refresh the affected join evidence after the public inventory change. Checksums establish internal integrity, not authenticated custody. --- data/epac-join-term-v0-receipt.json | 2 +- docs/epac-join-term-v0-audit.md | 6 +- docs/heldout-chemistry-validation.md | 7 +- epac_heldout_validation.py | 90 +++++++++++++++- tests/test_boundary_probe_completeness.py | 3 +- tests/test_heldout_validation.py | 122 ++++++++++++++++++++++ 6 files changed, 219 insertions(+), 11 deletions(-) diff --git a/data/epac-join-term-v0-receipt.json b/data/epac-join-term-v0-receipt.json index 34939d2..4d50b97 100644 --- a/data/epac-join-term-v0-receipt.json +++ b/data/epac-join-term-v0-receipt.json @@ -1 +1 @@ -{"authority":{"epac":{"authority":"schema, constructor, equality, serialization, recovery, and evidence","base_commit":"1e5c999286f12221eff9870d1372203ac7935f2a","base_tree":"ce7781938ff684d826bd91f475b5423bd56b146a","feature_identity":"source-manifest-sha256","repository":"The-Interdependency/epac"},"metapat":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_fixture_path":"src/metapat/fixtures/epac-join-terms-application-v4.json","application_fixture_sha256":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","application_id":"metapat.application.epac_join_terms","application_source_path":"src/metapat/epac_join_terms.py","application_source_sha256":"6cfb4a4109fd43a4f8343b48dd6fd72b74346f4609a65911cbf96188412b6ef0","application_version":"epac-join-terms-application-v4","authority":"semantic spine role license only","authority_transfer":false,"producer_commit":"1cdfb09dd00a451cee30eec2e78624df8c682662","producer_tree":"d946a5a18b0c53fd561dcd9d68fdf36d5dd11638","repository":"The-Interdependency/metapat"},"skill_lib":{"authority":"build and evidence doctrine only","authority_transfer":false,"commit":"516933d98f9de376f4e498f44059043dc0d96470","repository":"The-Interdependency/skill-lib","tree":"a1465ddf1de9519c9abc1a97d058432ba71bb257"},"ucns":{"authority_transfer":false,"consumed":false,"law_modified":false}},"candidate":{"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","joins":[{"join_id":"epac.join:fixture-a:S0-S1","order":1,"provenance":["epac.join-term-v0","authored-near-join:S0->S1"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S0","target_origin_id":"epac.origin:fixture-a:S1"},{"join_id":"epac.join:fixture-a:S1-S2","order":2,"provenance":["epac.join-term-v0","authored-near-join:S1->S2"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S1","target_origin_id":"epac.origin:fixture-a:S2"},{"join_id":"epac.join:fixture-a:S2-S3","order":3,"provenance":["epac.join-term-v0","authored-near-join:S2->S3"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S2","target_origin_id":"epac.origin:fixture-a:S3"},{"join_id":"epac.join:fixture-a:S3-S4","order":4,"provenance":["epac.join-term-v0","authored-near-join:S3->S4"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S3","target_origin_id":"epac.origin:fixture-a:S4"},{"join_id":"epac.join:fixture-a:S4-S5","order":5,"provenance":["epac.join-term-v0","authored-near-join:S4->S5"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S4","target_origin_id":"epac.origin:fixture-a:S5"},{"join_id":"epac.join:fixture-a:S5-S6","order":6,"provenance":["epac.join-term-v0","authored-near-join:S5->S6"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S5","target_origin_id":"epac.origin:fixture-a:S6"}],"legacy_bag":{"excluded_from_tensor":true,"kind":"bag","scale_slot_counts":{"S0":2,"S1":2,"S2":3,"S3":3,"S4":2,"S5":4,"S6":2},"slot_kind_counts":{"hole":6,"leftover":6,"member":6}},"object_kind":"multi-origin-join-tree","origins":[{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0]},"domain_name":"subatomic slot","k":2,"origin_id":"epac.origin:fixture-a:S0","phase":{"denominator":1,"numerator":0},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S0"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":1,"numerator":0}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S0","shape_signature":["0:seed:leftover","1:zero:hole"],"slots":[{"facing":-2,"kind":"leftover","name":"seed","order":0,"participant_id":"epac.leftover:fixture-a:S0:seed","participant_scale":null},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0]},"domain_name":"atomic","k":2,"origin_id":"epac.origin:fixture-a:S1","phase":{"denominator":8,"numerator":1},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S1"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":4},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S1","shape_signature":["0:subatomic:member","1:zero:hole"],"slots":[{"facing":4,"kind":"member","name":"subatomic","order":0,"participant_id":"epac.origin:fixture-a:S0","participant_scale":"S0"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1,0]},"domain_name":"join/arity","k":3,"origin_id":"epac.origin:fixture-a:S2","phase":{"denominator":4,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S2","authored-seating-chart"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":3},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":-3},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S2","shape_signature":["0:core:member","1:guest:leftover","2:vacancy:hole"],"slots":[{"facing":3,"kind":"member","name":"core","order":0,"participant_id":"epac.origin:fixture-a:S1","participant_scale":"S1"},{"facing":-3,"kind":"leftover","name":"guest","order":1,"participant_id":"epac.leftover:fixture-a:S2:guest","participant_scale":null},{"facing":0,"kind":"hole","name":"vacancy","order":2,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0,1]},"domain_name":"embed","k":3,"origin_id":"epac.origin:fixture-a:S3","phase":{"denominator":8,"numerator":3},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S3"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":-5},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":2}],"s5_energy_readout":null,"scale":"S3","shape_signature":["0:join:member","1:zero:hole","2:embed-context:leftover"],"slots":[{"facing":-5,"kind":"member","name":"join","order":0,"participant_id":"epac.origin:fixture-a:S2","participant_scale":"S2"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null},{"facing":2,"kind":"leftover","name":"embed-context","order":2,"participant_id":"epac.leftover:fixture-a:S3:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"electronic state","k":2,"origin_id":"epac.origin:fixture-a:S4","phase":{"denominator":2,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S4"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":2,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S4","shape_signature":["0:embed:member","1:electronic-context:leftover"],"slots":[{"facing":2,"kind":"member","name":"embed","order":0,"participant_id":"epac.origin:fixture-a:S3","participant_scale":"S3"},{"facing":-1,"kind":"leftover","name":"electronic-context","order":1,"participant_id":"epac.leftover:fixture-a:S4:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0,-1,0]},"domain_name":"EPAC energy readout","k":4,"origin_id":"epac.origin:fixture-a:S5","phase":{"denominator":8,"numerator":5},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S5"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":5}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.capacity.v0","value":4},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":7},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[3].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":{"measured_property":"slots[*].kind,k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.occupied-slots-over-k.v0","value":{"denominator":2,"numerator":1}},"scale":"S5","shape_signature":["0:electronic-state:member","1:zero-a:hole","2:occupancy-context:leftover","3:zero-b:hole"],"slots":[{"facing":7,"kind":"member","name":"electronic-state","order":0,"participant_id":"epac.origin:fixture-a:S4","participant_scale":"S4"},{"facing":0,"kind":"hole","name":"zero-a","order":1,"participant_id":null,"participant_scale":null},{"facing":-2,"kind":"leftover","name":"occupancy-context","order":2,"participant_id":"epac.leftover:fixture-a:S5:context","participant_scale":null},{"facing":0,"kind":"hole","name":"zero-b","order":3,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"ensemble","k":2,"origin_id":"epac.origin:fixture-a:S6","phase":{"denominator":4,"numerator":3},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S6"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":1},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S6","shape_signature":["0:energy-readout:member","1:ensemble-context:leftover"],"slots":[{"facing":1,"kind":"member","name":"energy-readout","order":0,"participant_id":"epac.origin:fixture-a:S5","participant_scale":"S5"},{"facing":-1,"kind":"leftover","name":"ensemble-context","order":1,"participant_id":"epac.leftover:fixture-a:S6:context","participant_scale":null}]}],"schema_id":"epac.multi-origin-join-tree","schema_version":"0.2.0","semantic_field_bindings":{"join.join_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"authored join record identity","spine_name":"Thing"},"join.order":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale relation position","spine_name":"Tensor"},"join.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"authored join provenance","spine_name":"Thing"},"join.relation":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale join","spine_name":"Tensor"},"join.source_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"source origin identity","spine_name":"Thing"},"join.target_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"new target origin identity","spine_name":"Thing"},"legacy_bag.excluded_from_tensor":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"tensor-exclusion marker","spine_name":"Thing"},"legacy_bag.kind":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"legacy sidecar kind","spine_name":"Thing"},"legacy_bag.scale_slot_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by scale","spine_name":"Thing"},"legacy_bag.slot_kind_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by kind","spine_name":"Thing"},"origin.arity":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"occupied seating arity","spine_name":"Boundary"},"origin.bearing":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"ordered static slot-facing sign readouts","spine_name":"Scalar"},"origin.domain_name":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC scale name","spine_name":"Simplex"},"origin.k":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"join window k","spine_name":"Boundary"},"origin.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"origin identity","spine_name":"Thing"},"origin.phase":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"EPAC exact phase","spine_name":"State"},"origin.phase_chart":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"visible or lifted phase record","spine_name":"State"},"origin.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"origin provenance","spine_name":"Thing"},"origin.readouts":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"identified phase, capacity, and slot-facing readouts","spine_name":"Scalar"},"origin.s5_energy_readout":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"EPAC S5 occupancy functional","spine_name":"Scalar"},"origin.scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"EPAC scale origin","spine_name":"Simplex"},"origin.shape_signature":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"ordered seating shape","spine_name":"Tensor"},"origin.slots":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"named ordered join slots","spine_name":"Boundary"},"readout.measured_property":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"named source state property","spine_name":"State"},"readout.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"readout source origin identity","spine_name":"Thing"},"readout.rule_id":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC readout rule identity","spine_name":"Thing"},"readout.value":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"computed EPAC readout value","spine_name":"Scalar"},"slot.facing":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"stored slot-facing property","spine_name":"State"},"slot.kind":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"member, hole, or leftover","spine_name":"Boundary"},"slot.name":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot name","spine_name":"Boundary"},"slot.order":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot order","spine_name":"Boundary"},"slot.participant_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"member or leftover identity","spine_name":"Thing"},"slot.participant_scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"member source scale","spine_name":"Simplex"},"tree.joins":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"ordered authored adjacent-scale relations","spine_name":"Tensor"},"tree.legacy_bag":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"non-structural legacy bag sidecar","spine_name":"Thing"},"tree.object_kind":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"multi-origin join-tree kind","spine_name":"Thing"},"tree.origins":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"S0-through-S6 origin tensor","spine_name":"Tensor"}},"semantic_license":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_id":"metapat.application.epac_join_terms","application_version":"epac-join-terms-application-v4","authority":"The-Interdependency/metapat","authority_transfer":false}},"evidence":{"declared_check_count":17,"declared_contract_count":17,"deterministic_regeneration_command":"python tools/generate_join_term_v0.py --check","molecular_shape_standing":"FALSIFIED (preserved; tested separately)","public_recovery_uses_constructor_state":false},"evidence_digest":"e3a5160c642050cc2d4f4bae126b34328b91fe17425fec5431182a7486adab95","evidence_epoch":"2026-09-30","historical_original_authoring_runtime":{"executable":"/usr/bin/python3.12","executable_sha256":"e50d468e8b0adfb05733f5b87b3cff34829c4a8c1aea50c865aa8bdfe4bb150f","implementation":"CPython","version":"3.12.3"},"hmmm":["Authored adjacency and its stored order do not establish state changes or actual occurrence order; Transformation and Time remain unlicensed for this candidate.","Whether a useful operation exists that requires secret EPAC state and cannot be efficiently reproduced from a legitimate public projection remains unestablished; this candidate is wholly public.","Whether exact lineage and seating replay improve an independently preregistered EPAC task remains unmeasured.","Whether any later UCNS correspondence is useful requires a separate license and test without rewriting UCNS law."],"inputs":{"identity_namespace":"fixture-a","input_is_public":true,"phase_chart_rule":"visible on even-numbered scales; lifted on odd-numbered scales","phase_values":[{"denominator":1,"numerator":0},{"denominator":8,"numerator":1},{"denominator":4,"numerator":1},{"denominator":8,"numerator":3},{"denominator":2,"numerator":1},{"denominator":8,"numerator":5},{"denominator":4,"numerator":3}],"private_input_supplied":false,"s2_seating_order":["core","guest","vacancy"]},"nonclaims":["Vector action, resulting state Transformation, or actual Time sequence","chemical or physical truth","molecular geometry or shape prediction","UCNS correspondence, topology, theorem status, or gonol identity","energy coupling, conservation, entropy, force, or field behavior","cryptographic secrecy, confidentiality, public-key separation, or computational hardness","PCEA compatibility","production suitability","release or protocol authority"],"results":{"authored_join_count":6,"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","candidate_json_sha256":"821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd","classification":"PUBLIC_ONLY_RECOVERY_SURVIVED","hole_count":6,"leftover_count":6,"legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","legacy_projection":"slot-count bag","member_count":6,"native_operation":"exact authored origin-lineage and named-seating replay","origin_count":7,"s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"same_bag_alpha_renamed_identity_witness":{"bags_equal":true,"baseline_s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"conclusion":"the legacy bag does not select exact instance identity labels, while structural lineage up to alpha-renaming is unchanged","exact_instance_lineages_equal":false,"join_isomorphic":true,"renamed_candidate_digest":"7e30d6f4942e215462254dec9723a1deeb4e71d680e2cc960f227b02240f420d","renamed_identity_namespace":"fixture-b","renamed_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","renamed_s6_lineage":["epac.origin:fixture-b:S0","epac.origin:fixture-b:S1","epac.origin:fixture-b:S2","epac.origin:fixture-b:S3","epac.origin:fixture-b:S4","epac.origin:fixture-b:S5","epac.origin:fixture-b:S6"]},"same_bag_nonisomorphic_witness":{"bags_equal":true,"baseline_candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","conclusion":"the legacy bag cannot uniquely recover authored seating or the complete join tree","join_isomorphic":false,"reordered_candidate_digest":"f74f9227a79acf84588b11d153bf66f9490d3aa18d8e899b6266c11ca3594633","reordered_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","reordered_s2_seating_order":["guest","core","vacancy"]},"slot_count":18},"runtime_scope":"Deterministic evidence is runtime-independent; current execution identities belong to the exact-head artifact replay receipts, not this historical authoring record.","schema_id":"epac.join-term-v0-evidence","schema_version":"1.0.0","source_files_sha256":{"README.md":"7e28c7c092f7551b11a03c9db7862e27ec88ff6f81167ed3d3f99db26e29a18b","data/metapat-epac-join-terms-application-v4.json":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","docs/PROVENANCE.md":"b410815a47ea9a8229406241616a9dc143ff09eb8da717456c019379efa6d5ee","docs/domain-claims.md":"f6f7f517e127a517a4acccba5527692239700c128b7bf0ecef9603e8c155c911","docs/multi-origin-join-term-v0.md":"6efb441f4421edf84c22556b46e438cc2da4f51536c522d3b6556af7b1c66a39","docs/research-status.md":"1f5194f0dc44521b6dc81d9a9494c72ff385fd879b8eb2b2849824640c5c2d07","docs/work-graph.json":"4edb37ace798511ff4b5f667afd1f0a3852ce78b7335349aaa9662171de0b9e9","docs/work-graphs/epac-join-term-v0.json":"7487441ec7e3ff3203635e245fccba5d293192cef5201c657946a15788166f16","epac_boundary_probe_completeness.py":"1bc8a3e990e99d52b9636eb1f656f3da0d6178c42de9dd86d811d9211e87f014","epac_join_term.py":"4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af","pyproject.toml":"44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd","tests/test_boundary_probe_completeness.py":"c0d0196718e14126a82d669359ae58b207d46b1a20ff5158828c9ca1c7422c8f","tests/test_epac_join_term.py":"ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8","tools/generate_join_term_v0.py":"1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331"},"source_manifest_sha256":"aaa855bba19979641bedf562dfe90cf18884fcec17ab92fd052f3e11691447c5"} +{"authority":{"epac":{"authority":"schema, constructor, equality, serialization, recovery, and evidence","base_commit":"1e5c999286f12221eff9870d1372203ac7935f2a","base_tree":"ce7781938ff684d826bd91f475b5423bd56b146a","feature_identity":"source-manifest-sha256","repository":"The-Interdependency/epac"},"metapat":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_fixture_path":"src/metapat/fixtures/epac-join-terms-application-v4.json","application_fixture_sha256":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","application_id":"metapat.application.epac_join_terms","application_source_path":"src/metapat/epac_join_terms.py","application_source_sha256":"6cfb4a4109fd43a4f8343b48dd6fd72b74346f4609a65911cbf96188412b6ef0","application_version":"epac-join-terms-application-v4","authority":"semantic spine role license only","authority_transfer":false,"producer_commit":"1cdfb09dd00a451cee30eec2e78624df8c682662","producer_tree":"d946a5a18b0c53fd561dcd9d68fdf36d5dd11638","repository":"The-Interdependency/metapat"},"skill_lib":{"authority":"build and evidence doctrine only","authority_transfer":false,"commit":"516933d98f9de376f4e498f44059043dc0d96470","repository":"The-Interdependency/skill-lib","tree":"a1465ddf1de9519c9abc1a97d058432ba71bb257"},"ucns":{"authority_transfer":false,"consumed":false,"law_modified":false}},"candidate":{"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","joins":[{"join_id":"epac.join:fixture-a:S0-S1","order":1,"provenance":["epac.join-term-v0","authored-near-join:S0->S1"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S0","target_origin_id":"epac.origin:fixture-a:S1"},{"join_id":"epac.join:fixture-a:S1-S2","order":2,"provenance":["epac.join-term-v0","authored-near-join:S1->S2"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S1","target_origin_id":"epac.origin:fixture-a:S2"},{"join_id":"epac.join:fixture-a:S2-S3","order":3,"provenance":["epac.join-term-v0","authored-near-join:S2->S3"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S2","target_origin_id":"epac.origin:fixture-a:S3"},{"join_id":"epac.join:fixture-a:S3-S4","order":4,"provenance":["epac.join-term-v0","authored-near-join:S3->S4"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S3","target_origin_id":"epac.origin:fixture-a:S4"},{"join_id":"epac.join:fixture-a:S4-S5","order":5,"provenance":["epac.join-term-v0","authored-near-join:S4->S5"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S4","target_origin_id":"epac.origin:fixture-a:S5"},{"join_id":"epac.join:fixture-a:S5-S6","order":6,"provenance":["epac.join-term-v0","authored-near-join:S5->S6"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S5","target_origin_id":"epac.origin:fixture-a:S6"}],"legacy_bag":{"excluded_from_tensor":true,"kind":"bag","scale_slot_counts":{"S0":2,"S1":2,"S2":3,"S3":3,"S4":2,"S5":4,"S6":2},"slot_kind_counts":{"hole":6,"leftover":6,"member":6}},"object_kind":"multi-origin-join-tree","origins":[{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0]},"domain_name":"subatomic slot","k":2,"origin_id":"epac.origin:fixture-a:S0","phase":{"denominator":1,"numerator":0},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S0"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":1,"numerator":0}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S0","shape_signature":["0:seed:leftover","1:zero:hole"],"slots":[{"facing":-2,"kind":"leftover","name":"seed","order":0,"participant_id":"epac.leftover:fixture-a:S0:seed","participant_scale":null},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0]},"domain_name":"atomic","k":2,"origin_id":"epac.origin:fixture-a:S1","phase":{"denominator":8,"numerator":1},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S1"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":4},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S1","shape_signature":["0:subatomic:member","1:zero:hole"],"slots":[{"facing":4,"kind":"member","name":"subatomic","order":0,"participant_id":"epac.origin:fixture-a:S0","participant_scale":"S0"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1,0]},"domain_name":"join/arity","k":3,"origin_id":"epac.origin:fixture-a:S2","phase":{"denominator":4,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S2","authored-seating-chart"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":3},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":-3},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S2","shape_signature":["0:core:member","1:guest:leftover","2:vacancy:hole"],"slots":[{"facing":3,"kind":"member","name":"core","order":0,"participant_id":"epac.origin:fixture-a:S1","participant_scale":"S1"},{"facing":-3,"kind":"leftover","name":"guest","order":1,"participant_id":"epac.leftover:fixture-a:S2:guest","participant_scale":null},{"facing":0,"kind":"hole","name":"vacancy","order":2,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0,1]},"domain_name":"embed","k":3,"origin_id":"epac.origin:fixture-a:S3","phase":{"denominator":8,"numerator":3},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S3"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":-5},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":2}],"s5_energy_readout":null,"scale":"S3","shape_signature":["0:join:member","1:zero:hole","2:embed-context:leftover"],"slots":[{"facing":-5,"kind":"member","name":"join","order":0,"participant_id":"epac.origin:fixture-a:S2","participant_scale":"S2"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null},{"facing":2,"kind":"leftover","name":"embed-context","order":2,"participant_id":"epac.leftover:fixture-a:S3:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"electronic state","k":2,"origin_id":"epac.origin:fixture-a:S4","phase":{"denominator":2,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S4"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":2,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S4","shape_signature":["0:embed:member","1:electronic-context:leftover"],"slots":[{"facing":2,"kind":"member","name":"embed","order":0,"participant_id":"epac.origin:fixture-a:S3","participant_scale":"S3"},{"facing":-1,"kind":"leftover","name":"electronic-context","order":1,"participant_id":"epac.leftover:fixture-a:S4:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0,-1,0]},"domain_name":"EPAC energy readout","k":4,"origin_id":"epac.origin:fixture-a:S5","phase":{"denominator":8,"numerator":5},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S5"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":5}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.capacity.v0","value":4},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":7},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[3].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":{"measured_property":"slots[*].kind,k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.occupied-slots-over-k.v0","value":{"denominator":2,"numerator":1}},"scale":"S5","shape_signature":["0:electronic-state:member","1:zero-a:hole","2:occupancy-context:leftover","3:zero-b:hole"],"slots":[{"facing":7,"kind":"member","name":"electronic-state","order":0,"participant_id":"epac.origin:fixture-a:S4","participant_scale":"S4"},{"facing":0,"kind":"hole","name":"zero-a","order":1,"participant_id":null,"participant_scale":null},{"facing":-2,"kind":"leftover","name":"occupancy-context","order":2,"participant_id":"epac.leftover:fixture-a:S5:context","participant_scale":null},{"facing":0,"kind":"hole","name":"zero-b","order":3,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"ensemble","k":2,"origin_id":"epac.origin:fixture-a:S6","phase":{"denominator":4,"numerator":3},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S6"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":1},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S6","shape_signature":["0:energy-readout:member","1:ensemble-context:leftover"],"slots":[{"facing":1,"kind":"member","name":"energy-readout","order":0,"participant_id":"epac.origin:fixture-a:S5","participant_scale":"S5"},{"facing":-1,"kind":"leftover","name":"ensemble-context","order":1,"participant_id":"epac.leftover:fixture-a:S6:context","participant_scale":null}]}],"schema_id":"epac.multi-origin-join-tree","schema_version":"0.2.0","semantic_field_bindings":{"join.join_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"authored join record identity","spine_name":"Thing"},"join.order":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale relation position","spine_name":"Tensor"},"join.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"authored join provenance","spine_name":"Thing"},"join.relation":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale join","spine_name":"Tensor"},"join.source_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"source origin identity","spine_name":"Thing"},"join.target_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"new target origin identity","spine_name":"Thing"},"legacy_bag.excluded_from_tensor":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"tensor-exclusion marker","spine_name":"Thing"},"legacy_bag.kind":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"legacy sidecar kind","spine_name":"Thing"},"legacy_bag.scale_slot_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by scale","spine_name":"Thing"},"legacy_bag.slot_kind_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by kind","spine_name":"Thing"},"origin.arity":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"occupied seating arity","spine_name":"Boundary"},"origin.bearing":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"ordered static slot-facing sign readouts","spine_name":"Scalar"},"origin.domain_name":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC scale name","spine_name":"Simplex"},"origin.k":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"join window k","spine_name":"Boundary"},"origin.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"origin identity","spine_name":"Thing"},"origin.phase":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"EPAC exact phase","spine_name":"State"},"origin.phase_chart":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"visible or lifted phase record","spine_name":"State"},"origin.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"origin provenance","spine_name":"Thing"},"origin.readouts":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"identified phase, capacity, and slot-facing readouts","spine_name":"Scalar"},"origin.s5_energy_readout":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"EPAC S5 occupancy functional","spine_name":"Scalar"},"origin.scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"EPAC scale origin","spine_name":"Simplex"},"origin.shape_signature":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"ordered seating shape","spine_name":"Tensor"},"origin.slots":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"named ordered join slots","spine_name":"Boundary"},"readout.measured_property":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"named source state property","spine_name":"State"},"readout.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"readout source origin identity","spine_name":"Thing"},"readout.rule_id":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC readout rule identity","spine_name":"Thing"},"readout.value":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"computed EPAC readout value","spine_name":"Scalar"},"slot.facing":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"stored slot-facing property","spine_name":"State"},"slot.kind":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"member, hole, or leftover","spine_name":"Boundary"},"slot.name":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot name","spine_name":"Boundary"},"slot.order":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot order","spine_name":"Boundary"},"slot.participant_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"member or leftover identity","spine_name":"Thing"},"slot.participant_scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"member source scale","spine_name":"Simplex"},"tree.joins":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"ordered authored adjacent-scale relations","spine_name":"Tensor"},"tree.legacy_bag":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"non-structural legacy bag sidecar","spine_name":"Thing"},"tree.object_kind":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"multi-origin join-tree kind","spine_name":"Thing"},"tree.origins":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"S0-through-S6 origin tensor","spine_name":"Tensor"}},"semantic_license":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_id":"metapat.application.epac_join_terms","application_version":"epac-join-terms-application-v4","authority":"The-Interdependency/metapat","authority_transfer":false}},"evidence":{"declared_check_count":17,"declared_contract_count":17,"deterministic_regeneration_command":"python tools/generate_join_term_v0.py --check","molecular_shape_standing":"FALSIFIED (preserved; tested separately)","public_recovery_uses_constructor_state":false},"evidence_digest":"0e7188233a25dd675d58a1cf91350d6f7b0674bdcd1c922f131bb04fbf61870f","evidence_epoch":"2026-09-30","historical_original_authoring_runtime":{"executable":"/usr/bin/python3.12","executable_sha256":"e50d468e8b0adfb05733f5b87b3cff34829c4a8c1aea50c865aa8bdfe4bb150f","implementation":"CPython","version":"3.12.3"},"hmmm":["Authored adjacency and its stored order do not establish state changes or actual occurrence order; Transformation and Time remain unlicensed for this candidate.","Whether a useful operation exists that requires secret EPAC state and cannot be efficiently reproduced from a legitimate public projection remains unestablished; this candidate is wholly public.","Whether exact lineage and seating replay improve an independently preregistered EPAC task remains unmeasured.","Whether any later UCNS correspondence is useful requires a separate license and test without rewriting UCNS law."],"inputs":{"identity_namespace":"fixture-a","input_is_public":true,"phase_chart_rule":"visible on even-numbered scales; lifted on odd-numbered scales","phase_values":[{"denominator":1,"numerator":0},{"denominator":8,"numerator":1},{"denominator":4,"numerator":1},{"denominator":8,"numerator":3},{"denominator":2,"numerator":1},{"denominator":8,"numerator":5},{"denominator":4,"numerator":3}],"private_input_supplied":false,"s2_seating_order":["core","guest","vacancy"]},"nonclaims":["Vector action, resulting state Transformation, or actual Time sequence","chemical or physical truth","molecular geometry or shape prediction","UCNS correspondence, topology, theorem status, or gonol identity","energy coupling, conservation, entropy, force, or field behavior","cryptographic secrecy, confidentiality, public-key separation, or computational hardness","PCEA compatibility","production suitability","release or protocol authority"],"results":{"authored_join_count":6,"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","candidate_json_sha256":"821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd","classification":"PUBLIC_ONLY_RECOVERY_SURVIVED","hole_count":6,"leftover_count":6,"legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","legacy_projection":"slot-count bag","member_count":6,"native_operation":"exact authored origin-lineage and named-seating replay","origin_count":7,"s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"same_bag_alpha_renamed_identity_witness":{"bags_equal":true,"baseline_s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"conclusion":"the legacy bag does not select exact instance identity labels, while structural lineage up to alpha-renaming is unchanged","exact_instance_lineages_equal":false,"join_isomorphic":true,"renamed_candidate_digest":"7e30d6f4942e215462254dec9723a1deeb4e71d680e2cc960f227b02240f420d","renamed_identity_namespace":"fixture-b","renamed_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","renamed_s6_lineage":["epac.origin:fixture-b:S0","epac.origin:fixture-b:S1","epac.origin:fixture-b:S2","epac.origin:fixture-b:S3","epac.origin:fixture-b:S4","epac.origin:fixture-b:S5","epac.origin:fixture-b:S6"]},"same_bag_nonisomorphic_witness":{"bags_equal":true,"baseline_candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","conclusion":"the legacy bag cannot uniquely recover authored seating or the complete join tree","join_isomorphic":false,"reordered_candidate_digest":"f74f9227a79acf84588b11d153bf66f9490d3aa18d8e899b6266c11ca3594633","reordered_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","reordered_s2_seating_order":["guest","core","vacancy"]},"slot_count":18},"runtime_scope":"Deterministic evidence is runtime-independent; current execution identities belong to the exact-head artifact replay receipts, not this historical authoring record.","schema_id":"epac.join-term-v0-evidence","schema_version":"1.0.0","source_files_sha256":{"README.md":"7e28c7c092f7551b11a03c9db7862e27ec88ff6f81167ed3d3f99db26e29a18b","data/metapat-epac-join-terms-application-v4.json":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","docs/PROVENANCE.md":"b410815a47ea9a8229406241616a9dc143ff09eb8da717456c019379efa6d5ee","docs/domain-claims.md":"f6f7f517e127a517a4acccba5527692239700c128b7bf0ecef9603e8c155c911","docs/multi-origin-join-term-v0.md":"6efb441f4421edf84c22556b46e438cc2da4f51536c522d3b6556af7b1c66a39","docs/research-status.md":"1f5194f0dc44521b6dc81d9a9494c72ff385fd879b8eb2b2849824640c5c2d07","docs/work-graph.json":"4edb37ace798511ff4b5f667afd1f0a3852ce78b7335349aaa9662171de0b9e9","docs/work-graphs/epac-join-term-v0.json":"7487441ec7e3ff3203635e245fccba5d293192cef5201c657946a15788166f16","epac_boundary_probe_completeness.py":"1bc8a3e990e99d52b9636eb1f656f3da0d6178c42de9dd86d811d9211e87f014","epac_join_term.py":"4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af","pyproject.toml":"44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd","tests/test_boundary_probe_completeness.py":"8c096277431fc59163b362a7e88c5c3dd515f66bab62deed81eff1be2dc4a641","tests/test_epac_join_term.py":"ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8","tools/generate_join_term_v0.py":"1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331"},"source_manifest_sha256":"bf8574518bf2be7d2b6e72fc8590f90b9ec2104415f88a55ba06420128d12834"} diff --git a/docs/epac-join-term-v0-audit.md b/docs/epac-join-term-v0-audit.md index 70bc026..33aa665 100644 --- a/docs/epac-join-term-v0-audit.md +++ b/docs/epac-join-term-v0-audit.md @@ -7,7 +7,7 @@ The public receipt recovers one complete typed S0-through-S6 join tree and its e ## Exact authority - EPAC base: `1e5c999286f12221eff9870d1372203ac7935f2a` (tree `ce7781938ff684d826bd91f475b5423bd56b146a`) -- EPAC feature source manifest: `aaa855bba19979641bedf562dfe90cf18884fcec17ab92fd052f3e11691447c5` +- EPAC feature source manifest: `bf8574518bf2be7d2b6e72fc8590f90b9ec2104415f88a55ba06420128d12834` - METAPAT producer: `1cdfb09dd00a451cee30eec2e78624df8c682662` (tree `d946a5a18b0c53fd561dcd9d68fdf36d5dd11638`) - METAPAT application: `metapat.application.epac_join_terms@epac-join-terms-application-v4` - METAPAT application digest: `461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b` @@ -20,7 +20,7 @@ The containing feature commit cannot be embedded in a file inside itself. The ev - Candidate digest: `11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442` - Candidate JSON SHA-256: `821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd` -- Evidence digest: `e3a5160c642050cc2d4f4bae126b34328b91fe17425fec5431182a7486adab95` +- Evidence digest: `0e7188233a25dd675d58a1cf91350d6f7b0674bdcd1c922f131bb04fbf61870f` - Origins: `7` - Authored joins (no Transformation or Time claim): `6` - Slots: `18` (`6` members, `6` holes, `6` leftovers) @@ -59,7 +59,7 @@ This is information loss. It is not a secrecy, computational-hardness, or confid - `epac_boundary_probe_completeness.py`: `1bc8a3e990e99d52b9636eb1f656f3da0d6178c42de9dd86d811d9211e87f014` - `epac_join_term.py`: `4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af` - `pyproject.toml`: `44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd` -- `tests/test_boundary_probe_completeness.py`: `c0d0196718e14126a82d669359ae58b207d46b1a20ff5158828c9ca1c7422c8f` +- `tests/test_boundary_probe_completeness.py`: `8c096277431fc59163b362a7e88c5c3dd515f66bab62deed81eff1be2dc4a641` - `tests/test_epac_join_term.py`: `ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8` - `tools/generate_join_term_v0.py`: `1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331` diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index 7eee574..557f406 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -42,6 +42,10 @@ Provenance `authority` and `locator` must both be nonempty strings after trimmin In a source checkout, `load_packaged_oracle()` prefers the sibling `data/heldout_chemistry_oracle.json`; in an installed distribution it reads the `epac_data` package resource. Loading a plan or commitment never loads oracle data. +The reserved `hmmm` provenance identity (ignoring surrounding whitespace and case) is unresolved, just like a blank or null identity. It cannot produce a scored success. + +Reload preserved receipts with `load_validation_receipt(path)`. It rejects duplicate keys, invalid envelopes and digest fields, repeated case IDs, inconsistent result statuses or counts, and a mismatched `receipt_sha256`. It also checks scored statuses against the evidence carried in each result. These checks establish internal integrity; an unkeyed checksum does not authenticate a custodian or prevent replacement of the entire evidence chain. Preserve externally trusted digests or independently controlled custody when that stronger claim matters. + ## Runnable end-to-end example This runs from a source checkout or the installed distribution. It persists and reloads both commitments before revealing a synthetic held-out value, exercises packaged-oracle loading, then persists the evidence receipt. @@ -59,6 +63,7 @@ from epac_heldout_validation import ( load_oracle, load_packaged_oracle, load_prediction_commitment, + load_validation_receipt, load_validation_plan, ) @@ -118,7 +123,7 @@ with TemporaryDirectory() as tmp: validation_plan=plan, ) (root / "receipt.json").write_text(json.dumps(receipt), encoding="utf-8") - preserved = json.loads((root / "receipt.json").read_text(encoding="utf-8")) + preserved = load_validation_receipt(root / "receipt.json") assert preserved["counts"] == { "SURVIVED": 1, diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 219373b..a3c48d6 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -10,7 +10,8 @@ oracle and call compare_after_freeze. Reload persisted inputs with load_validation_plan(path) and load_prediction_commitment(path, validation_plan=plan) to reject ambiguous JSON before verification. A complete runnable source/install -example lives in docs/heldout-chemistry-validation.md. +example lives in docs/heldout-chemistry-validation.md. Preserve and reload receipts +with load_validation_receipt(path) to verify their evidence and digest. """ from __future__ import annotations @@ -30,8 +31,8 @@ # module_kind: experiment # summary: comparison-only held-out validation boundary that preregisters case identities and comparator rules before EPAC predictions are frozen and oracle values are revealed # owner: The Interdependency -# public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_validation_plan, load_prediction_commitment, load_oracle, load_packaged_oracle -# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _loads_evidence_json +# public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_validation_plan, load_prediction_commitment, load_validation_receipt, load_oracle, load_packaged_oracle +# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _loads_evidence_json # auth_boundary: none # storage_boundary: read # network_boundary: none @@ -65,7 +66,7 @@ # # id: heldout_oracle_evidence_integrity # given: held-out oracle cases and provenance are revealed after prediction commitment -# then: oracle ids are unique, values are persistence-stable, provenance identities are nonblank, and malformed evidence cannot be scored as success +# then: oracle ids are unique, values are persistence-stable, provenance identities are nonblank and not hmmm, and malformed evidence cannot be scored as success # class: evidence # since: 2026-09-29 # @@ -86,6 +87,12 @@ # then: duplicate JSON keys at every depth and invalid envelopes or digests are rejected; prediction commitments are verified against the supplied frozen plan before being returned # class: evidence # since: 2026-10-04 +# +# id: heldout_persisted_receipts_preserve_evidence +# given: a persisted validation receipt is reloaded +# then: duplicate keys, invalid envelopes or bindings, inconsistent results or counts, and digest mismatches are rejected; valid evidence is preserved without granting custody authentication +# class: evidence +# since: 2026-10-04 # === END CONTRACTS === STATUSES = ("SURVIVED", "FALSIFIED", "UNRESOLVED") @@ -155,7 +162,9 @@ def _has_nonempty_provenance_identity(provenance: Any) -> bool: if not isinstance(provenance, Mapping): return False return all( - isinstance(provenance.get(field), str) and bool(provenance[field].strip()) + isinstance(provenance.get(field), str) + and bool(provenance[field].strip()) + and provenance[field].strip().casefold() != "hmmm" for field in ("authority", "locator") ) @@ -578,6 +587,77 @@ def load_prediction_commitment( return commitment +def _verify_validation_receipt(receipt: Mapping[str, Any]) -> None: + required = { + "schema", "version", "validation_plan_sha256", "prediction_commitment_sha256", + "oracle_sha256", "results", "counts", "receipt_sha256", + } + if set(receipt) != required: + raise ValueError("invalid validation receipt envelope") + if receipt["schema"] != _RECEIPT_SCHEMA or receipt["version"] != _VERSION: + raise ValueError("unsupported validation receipt") + for field in ("validation_plan_sha256", "prediction_commitment_sha256", "oracle_sha256", "receipt_sha256"): + value = receipt[field] + if not isinstance(value, str) or len(value) != 64 or any(c not in "0123456789abcdef" for c in value): + raise ValueError(f"{field} must be a canonical SHA-256 digest") + if not isinstance(receipt["results"], list): + raise ValueError("receipt results must be a list") + counts = receipt["counts"] + if not isinstance(counts, Mapping) or set(counts) != set(STATUSES): + raise ValueError("invalid receipt counts") + if any(type(value) is not int or value < 0 for value in counts.values()): + raise ValueError("receipt counts must be nonnegative integers") + observed = dict.fromkeys(STATUSES, 0) + seen: set[str] = set() + base_fields = {"id", "domain", "status"} + reason_fields = { + "missing provenance identity": base_fields | {"reason"}, + "missing expected value": base_fields | {"reason", "provenance"}, + "no frozen prediction": base_fields | {"reason", "expected", "provenance"}, + } + for result in receipt["results"]: + if not isinstance(result, Mapping) or not base_fields <= result.keys(): + raise ValueError("invalid receipt result") + case_id = _required_nonempty_string(result["id"], "receipt case id") + if case_id in seen: + raise ValueError(f"duplicate receipt case id: {case_id}") + seen.add(case_id) + _normalized_domain(result["domain"]) + status = result["status"] + if not isinstance(status, str) or status not in STATUSES: + raise ValueError("invalid receipt result status") + if "reason" in result: + reason = result["reason"] + if not isinstance(reason, str) or reason not in reason_fields: + raise ValueError("invalid receipt unresolved reason") + if status != "UNRESOLVED" or set(result) != reason_fields[reason]: + raise ValueError("receipt result contradicts its unresolved reason") + else: + if set(result) != base_fields | {"predicted", "expected", "comparison", "provenance"}: + raise ValueError("invalid scored receipt result") + rule = _normalized_comparison(result) + if _canonical(rule) != _canonical(result["comparison"]): + raise ValueError("receipt comparison must be canonical") + if _compare(result["expected"], result["predicted"], rule) != status: + raise ValueError("receipt result status disagrees with preserved evidence") + if "provenance" in result and not _has_nonempty_provenance_identity(result["provenance"]): + raise ValueError("receipt result has unresolved provenance identity") + observed[status] += 1 + if counts != observed: + raise ValueError("receipt counts do not match results") + if _digest({key: receipt[key] for key in required - {"receipt_sha256"}}) != receipt["receipt_sha256"]: + raise ValueError("validation receipt digest mismatch") + + +def load_validation_receipt(path: str | Path) -> dict[str, Any]: + """Reload duplicate-safe, internally verified evidence; this is not authentication.""" + receipt = _loads_evidence_json( + Path(path).read_text(encoding="utf-8"), document="validation receipt" + ) + _verify_validation_receipt(receipt) + return receipt + + def load_oracle(path: str | Path) -> dict[str, Any]: """Comparison-side helper. Construction modules must not call this.""" return _loads_evidence_json(Path(path).read_text(encoding="utf-8"), document="oracle") diff --git a/tests/test_boundary_probe_completeness.py b/tests/test_boundary_probe_completeness.py index fb2434b..486e82e 100644 --- a/tests/test_boundary_probe_completeness.py +++ b/tests/test_boundary_probe_completeness.py @@ -154,6 +154,7 @@ def test_inventory_covers_packaged_execution_modules(self) -> None: "load_packaged_oracle", "load_validation_plan", "load_prediction_commitment", + "load_validation_receipt", }, ) for name in heldout_exports: @@ -167,7 +168,7 @@ def test_inventory_covers_packaged_execution_modules(self) -> None: def test_declared_operations_preserve_unresolved_semantics(self) -> None: inventory = self.report["operation_inventory"] - self.assertEqual(inventory["operation_count"], 172) + self.assertEqual(inventory["operation_count"], 173) self.assertEqual(inventory["boundary_relevant_count"], 58) self.assertEqual(inventory["ambiguous_count"], 67) self.assertEqual(inventory["omitted_boundary_relevant_count"], 14) diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index 4b0e555..6eb71d2 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -21,6 +21,7 @@ load_oracle, load_packaged_oracle, load_prediction_commitment, + load_validation_receipt, load_validation_plan, verify_commitment, ) @@ -165,6 +166,18 @@ # mutates: filesystem # cleanup: tempdir_teardown # +# id: check_receipt_loader_preserves_verified_evidence_and_unresolved_results +# proves: heldout_persisted_receipts_preserve_evidence, heldout_comparison_tri_state_semantics +# call: self::test_receipt_loader_preserves_verified_evidence_and_unresolved_results +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_receipt_loader_rejects_ambiguous_or_tampered_evidence +# proves: heldout_persisted_receipts_preserve_evidence +# call: self::test_receipt_loader_rejects_ambiguous_or_tampered_evidence +# mutates: filesystem +# cleanup: tempdir_teardown +# # id: check_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout # proves: heldout_oracle_loading_is_unambiguous # call: self::test_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout @@ -682,6 +695,13 @@ def test_blank_provenance_identity_is_unresolved(): receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} assert receipt["results"][0]["reason"] == "missing provenance identity" + for field in ("authority", "locator"): + for unknown in ("hmmm", " HMMM ", "\thmmm\n"): + heldout["cases"][0]["provenance"] = {"authority": "fixture", "locator": "x"} + heldout["cases"][0]["provenance"][field] = unknown + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} + assert receipt["results"][0]["reason"] == "missing provenance identity" def test_receipt_and_commitment_detach_mutable_evidence_inputs(): @@ -813,6 +833,108 @@ def test_persisted_commitment_loaders_verify_envelopes_and_plan_binding(): load_prediction_commitment(prediction_path, validation_plan=forged_plan) +def test_receipt_loader_preserves_verified_evidence_and_unresolved_results(): + cases = [ + {"id": name, "comparison": {"kind": "exact"}} + for name in ("same", "different", "nonfinite", "missing_expected", "missing_prediction", "missing_provenance") + ] + [{"id": "unsupported", "comparison": {"kind": "future"}}] + plan = freeze_validation_plan(cases) + commitment = freeze_predictions( + {"same": 1, "different": 2, "nonfinite": math.inf, "missing_expected": None, + "missing_provenance": 1, "unsupported": 1}, + source_identity="epac@test", validation_plan=plan, + ) + heldout = { + "schema": "epac.heldout-chemistry-oracle", "version": "v1", + "cases": [dict(case, expected=1, provenance={"authority": "fixture", "locator": case["id"]}) for case in cases], + } + del heldout["cases"][3]["expected"] + heldout["cases"][5]["provenance"] = {"authority": "hmmm", "locator": "x"} + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 1, "FALSIFIED": 1, "UNRESOLVED": 5} + with TemporaryDirectory() as tmp: + path = Path(tmp) / "receipt.json" + path.write_text(json.dumps(receipt), encoding="utf-8") + preserved = load_validation_receipt(path) + assert json.dumps(preserved, sort_keys=True) == json.dumps(receipt, sort_keys=True) + empty_plan = freeze_validation_plan([]) + empty_commitment = freeze_predictions({}, source_identity="epac@test", validation_plan=empty_plan) + empty = compare_after_freeze(empty_commitment, dict(heldout, cases=[]), validation_plan=empty_plan) + path.write_text(json.dumps(empty), encoding="utf-8") + assert load_validation_receipt(path) == empty + + +def test_receipt_loader_rejects_ambiguous_or_tampered_evidence(): + plan, commitment = frozen({"element:H:valence": 1}) + receipt = compare_after_freeze(commitment, oracle(), validation_plan=plan) + with TemporaryDirectory() as tmp: + path = Path(tmp) / "receipt.json" + encoded = json.dumps(receipt) + attacks = ["{" + json.dumps(key) + ":null," + encoded[1:] for key in receipt] + attacks += [ + encoded.replace('"status": "SURVIVED"', '"status":"FALSIFIED","status": "SURVIVED"'), + encoded.replace('"SURVIVED": 1', '"SURVIVED":0,"SURVIVED": 1'), + encoded.replace('"SURVIVED": 1', '"SURVIVED":1,"SURVIVED": 1'), + ] + for payload in attacks: + assert json.loads(payload) == receipt + path.write_text(payload, encoding="utf-8") + with pytest.raises(ValueError, match="duplicate JSON key"): + load_validation_receipt(path) + + for key in receipt: + forged = deepcopy(receipt) + del forged[key] + path.write_text(json.dumps(forged), encoding="utf-8") + with pytest.raises(ValueError): + load_validation_receipt(path) + + def rejected(forged, *, rehash=False): + if rehash: + forged["receipt_sha256"] = _digest_envelope({ + key: value for key, value in forged.items() if key != "receipt_sha256" + }) + path.write_text(json.dumps(forged), encoding="utf-8") + with pytest.raises(ValueError): + load_validation_receipt(path) + + for value in (None, [], 7, "not an object"): + rejected(value) + # Keep every shape otherwise valid to witness digest verification itself. + forged = deepcopy(receipt) + forged["results"][0]["provenance"]["locator"] = "altered" + rejected(forged) + for field, value in ( + ("schema", "other"), ("version", "v2"), ("oracle_sha256", "g" * 64), + ("results", {}), ("counts", {"SURVIVED": True, "FALSIFIED": 0, "UNRESOLVED": 2}), + ("counts", {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 2}), + ): + forged = deepcopy(receipt) + forged[field] = value + rejected(forged, rehash=True) + for field, value in ( + ("status", "UNREVIEWED"), ("domain", {"expected": 1}), + ("provenance", {"authority": "hmmm", "locator": "x"}), + ("comparison", {"kind": "exact", "expected": 1}), + ("expected", 99), ("unexpected", "oracle"), + ): + forged = deepcopy(receipt) + forged["results"][0][field] = value + rejected(forged, rehash=True) + forged = deepcopy(receipt) + forged["results"].append(deepcopy(forged["results"][0])) + forged["counts"]["SURVIVED"] += 1 + rejected(forged, rehash=True) + # Even consistent counts plus a new digest cannot legitimize a flipped status. + forged = deepcopy(receipt) + forged["results"][0]["status"] = "FALSIFIED" + forged["counts"].update(SURVIVED=0, FALSIFIED=1) + rejected(forged, rehash=True) + forged = deepcopy(receipt) + forged["results"][1]["reason"] = "invented reason" + rejected(forged, rehash=True) + + def test_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout(): source_oracle = ( Path(heldout_validation.__file__).resolve().parent From 4ffbdfd138a34eb4ea76abae6c2eb6fba867182b Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 13:37:48 -0700 Subject: [PATCH 12/19] fix: reject unresolved prediction source identities Reject the reserved hmmm sentinel, including case and whitespace variants, through the shared source-identity validation used by freezing and verification. Exercise comparison and strict reloads with recomputed matching digests; keep unresolved authority out of scored evidence. --- docs/heldout-chemistry-validation.md | 2 ++ epac_heldout_validation.py | 4 +++- tests/test_heldout_validation.py | 35 ++++++++++++++++++++-------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index 557f406..761d89c 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -44,6 +44,8 @@ In a source checkout, `load_packaged_oracle()` prefers the sibling `data/heldout The reserved `hmmm` provenance identity (ignoring surrounding whitespace and case) is unresolved, just like a blank or null identity. It cannot produce a scored success. +The same reserved sentinel is rejected as a prediction `source_identity` during freezing, commitment verification, reloading, and comparison, including envelopes with a recomputed matching digest. Supply a resolved EPAC head or receipt identity before freezing predictions. + Reload preserved receipts with `load_validation_receipt(path)`. It rejects duplicate keys, invalid envelopes and digest fields, repeated case IDs, inconsistent result statuses or counts, and a mismatched `receipt_sha256`. It also checks scored statuses against the evidence carried in each result. These checks establish internal integrity; an unkeyed checksum does not authenticate a custodian or prevent replacement of the entire evidence chain. Preserve externally trusted digests or independently controlled custody when that stronger claim matters. ## Runnable end-to-end example diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index a3c48d6..7f0eb2c 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -54,7 +54,7 @@ # # id: heldout_commitment_persistence_and_identity # given: predictions and source identity are frozen, serialized, reloaded, or externally reconstructed -# then: only persistence-stable JSON-shaped prediction values with a nonempty source identity and matching digest are accepted +# then: only persistence-stable JSON-shaped prediction values with a nonempty resolved source identity and matching digest are accepted; the reserved hmmm source sentinel is rejected # class: evidence # since: 2026-09-29 # @@ -114,6 +114,8 @@ def _digest(value: Any) -> str: def _required_nonempty_string(value: Any, field: str) -> str: if not isinstance(value, str) or not value.strip(): raise ValueError(f"{field} must be a nonempty string") + if field == "source_identity" and value.strip().casefold() == "hmmm": + raise ValueError("source_identity must be resolved, not the hmmm sentinel") return value diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index 6eb71d2..ca2f25c 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -437,16 +437,21 @@ def test_tampered_prediction_cannot_be_compared(): def test_commitment_revalidates_nonempty_source_identity_even_with_matching_digest(): - _, commitment = frozen({"element:H:valence": 1}) - forged = deepcopy(commitment) - forged["source_identity"] = "" - unsigned = { - k: forged[k] - for k in ("schema", "version", "source_identity", "validation_plan_sha256", "predictions") - } - forged["commitment_sha256"] = _digest_envelope(unsigned) - with pytest.raises(ValueError, match="source_identity"): - verify_commitment(forged) + plan, commitment = frozen({"element:H:valence": 1}) + for source_identity in ("", " \t", "hmmm", " HMMM ", "\thMmM\n"): + with pytest.raises(ValueError, match="source_identity"): + freeze_predictions({"element:H:valence": 1}, source_identity=source_identity, validation_plan=plan) + forged = deepcopy(commitment) + forged["source_identity"] = source_identity + unsigned = { + k: forged[k] + for k in ("schema", "version", "source_identity", "validation_plan_sha256", "predictions") + } + forged["commitment_sha256"] = _digest_envelope(unsigned) + with pytest.raises(ValueError, match="source_identity"): + verify_commitment(forged) + with pytest.raises(ValueError, match="source_identity"): + compare_after_freeze(forged, oracle(), validation_plan=plan) def test_case_inventory_and_comparator_are_bound_before_comparison(): @@ -821,6 +826,16 @@ def test_persisted_commitment_loaders_verify_envelopes_and_plan_binding(): with pytest.raises(ValueError, match="absent from frozen validation plan"): load_prediction_commitment(prediction_path, validation_plan=plan) + for source_identity in ("hmmm", " HMMM ", "\thMmM\n"): + forged = deepcopy(commitment) + forged["source_identity"] = source_identity + forged["commitment_sha256"] = _digest_envelope({ + key: value for key, value in forged.items() if key != "commitment_sha256" + }) + prediction_path.write_text(json.dumps(forged), encoding="utf-8") + with pytest.raises(ValueError, match="source_identity"): + load_prediction_commitment(prediction_path, validation_plan=plan) + forged_plan = deepcopy(plan) forged_plan["cases"][0]["comparison"]["expected"] = 7 forged_plan["plan_sha256"] = _digest_envelope({ From ddfef262d90bb96ebee0b43b7447f4c3a069ddd9 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 13:49:34 -0700 Subject: [PATCH 13/19] Reject JSON numeric literals that lose decimal value during evidence reload --- docs/heldout-chemistry-validation.md | 2 + epac_heldout_validation.py | 26 ++++++++++++- tests/test_heldout_validation.py | 57 ++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index 761d89c..cfc57c7 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -46,6 +46,8 @@ The reserved `hmmm` provenance identity (ignoring surrounding whitespace and cas The same reserved sentinel is rejected as a prediction `source_identity` during freezing, commitment verification, reloading, and comparison, including envelopes with a recomputed matching digest. Supply a resolved EPAC head or receipt identity before freezing predictions. +Every loader also requires decimal/exponent JSON numbers to preserve their exact decimal value when parsed and serialized back to JSON. Ordinary round-trip values such as `0.1` and `1.007825` remain supported; precision-losing literals such as `9007199254740993.0`, finite-number overflow, and underflow are rejected before hashing or scoring. Integer tokens retain Python's exact integer handling. This is a decimal-value round-trip check, not a claim that decimal fractions have exact binary encodings. Explicit non-finite Python JSON extensions retain the existing `UNRESOLVED` comparison policy. + Reload preserved receipts with `load_validation_receipt(path)`. It rejects duplicate keys, invalid envelopes and digest fields, repeated case IDs, inconsistent result statuses or counts, and a mismatched `receipt_sha256`. It also checks scored statuses against the evidence carried in each result. These checks establish internal integrity; an unkeyed checksum does not authenticate a custodian or prevent replacement of the entire evidence chain. Preserve externally trusted digests or independently controlled custody when that stronger claim matters. ## Runnable end-to-end example diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 7f0eb2c..e5a2b89 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -17,6 +17,7 @@ from collections.abc import Mapping from copy import deepcopy +from decimal import Decimal from fractions import Fraction import hashlib from importlib.resources import files @@ -32,7 +33,7 @@ # summary: comparison-only held-out validation boundary that preregisters case identities and comparator rules before EPAC predictions are frozen and oracle values are revealed # owner: The Interdependency # public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_validation_plan, load_prediction_commitment, load_validation_receipt, load_oracle, load_packaged_oracle -# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _loads_evidence_json +# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _parse_roundtrip_float, _loads_evidence_json # auth_boundary: none # storage_boundary: read # network_boundary: none @@ -93,6 +94,12 @@ # then: duplicate keys, invalid envelopes or bindings, inconsistent results or counts, and digest mismatches are rejected; valid evidence is preserved without granting custody authentication # class: evidence # since: 2026-10-04 +# +# id: heldout_json_numbers_preserve_decimal_value +# given: a JSON number with a decimal point or exponent is loaded as held-out evidence +# then: its exact decimal value must survive parsing and canonical float serialization; rounding, underflow, and overflow are rejected before commitment or scoring +# class: evidence +# since: 2026-10-04 # === END CONTRACTS === STATUSES = ("SURVIVED", "FALSIFIED", "UNRESOLVED") @@ -562,8 +569,23 @@ def _reject_duplicate_object_pairs(pairs: list[tuple[str, Any]]) -> dict[str, An return value +def _parse_roundtrip_float(token: str) -> float: + """Accept only decimal values preserved by our JSON float representation. + +This protects decimal-value identity across JSON round trips; it does not claim +that ordinary decimals such as 0.1 have exact binary floating-point encodings. +""" + value = float(token) + if not math.isfinite(value) or Decimal(str(value)) != Decimal(token): + raise ValueError("JSON number cannot round-trip without rounding") + return value + + def _loads_evidence_json(payload: str, *, document: str) -> dict[str, Any]: - value = json.loads(payload, object_pairs_hook=_reject_duplicate_object_pairs) + value = json.loads( + payload, object_pairs_hook=_reject_duplicate_object_pairs, + parse_float=_parse_roundtrip_float, + ) if not isinstance(value, dict): raise ValueError(f"{document} document must be a JSON object") return value diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index ca2f25c..27b62e0 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -178,6 +178,12 @@ # mutates: filesystem # cleanup: tempdir_teardown # +# id: check_evidence_loaders_reject_decimal_value_loss +# proves: heldout_json_numbers_preserve_decimal_value +# call: self::test_evidence_loaders_reject_decimal_value_loss +# mutates: filesystem +# cleanup: tempdir_teardown +# # id: check_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout # proves: heldout_oracle_loading_is_unambiguous # call: self::test_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout @@ -950,6 +956,57 @@ def rejected(forged, *, rehash=False): rejected(forged, rehash=True) +def test_evidence_loaders_reject_decimal_value_loss(): + plan = freeze_validation_plan([ + {"id": "x", "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": 0.5}} + ]) + commitment = freeze_predictions({"x": 0.5}, source_identity="epac@test", validation_plan=plan) + heldout = { + "schema": "epac.heldout-chemistry-oracle", "version": "v1", + "cases": [{"id": "x", "comparison": plan["cases"][0]["comparison"], "expected": 0.5, + "provenance": {"authority": "fixture", "locator": "x"}}], + } + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + with TemporaryDirectory() as tmp: + path = Path(tmp) / "evidence.json" + for envelope, loader in ( + (plan, load_validation_plan), + (commitment, lambda path: load_prediction_commitment(path, validation_plan=plan)), + (heldout, load_oracle), + (receipt, load_validation_receipt), + ): + encoded = json.dumps(envelope) + path.write_text(encoded, encoding="utf-8") + assert loader(path) == envelope + for number in ("9007199254740993.0", "0.50000000000000001", "1e-4000", "1e4000"): + payload = encoded.replace("0.5", number) + assert payload != encoded + if number == "0.50000000000000001": + # Default parsing would silently accept the original digests. + assert json.loads(payload) == envelope + path.write_text(payload, encoding="utf-8") + with pytest.raises(ValueError, match="without rounding"): + loader(path) + + exact_plan = freeze_validation_plan([{"id": "x"}]) + exact_commitment = freeze_predictions({"x": 9007199254740992}, source_identity="epac@test", validation_plan=exact_plan) + for literal in ("9007199254740992.0", "9007199254740993.0"): + payload = ('{"schema":"epac.heldout-chemistry-oracle","version":"v1",' + '"cases":[{"id":"x","expected":' + literal + ',' + '"provenance":{"authority":"fixture","locator":"x"}}]}') + path.write_text(payload, encoding="utf-8") + if literal == "9007199254740993.0": + with pytest.raises(ValueError, match="without rounding"): + load_oracle(path) + else: + loaded = load_oracle(path) + assert compare_after_freeze(exact_commitment, loaded, validation_plan=exact_plan)["counts"]["SURVIVED"] == 1 + + for literal in ("0.1", "1.007825", "1e-6", "1.2300", "-0.0"): + path.write_text('{"expected":' + literal + '}', encoding="utf-8") + assert load_oracle(path)["expected"] == float(literal) + + def test_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout(): source_oracle = ( Path(heldout_validation.__file__).resolve().parent From b1053fc3558f0cd76390ce74781f3d4404e2f1de Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 13:58:42 -0700 Subject: [PATCH 14/19] Keep unknown and non-finite evidence unresolved before every comparator --- docs/heldout-chemistry-validation.md | 2 + epac_heldout_validation.py | 22 +++++++++- tests/test_heldout_validation.py | 61 ++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index cfc57c7..423153c 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -46,6 +46,8 @@ The reserved `hmmm` provenance identity (ignoring surrounding whitespace and cas The same reserved sentinel is rejected as a prediction `source_identity` during freezing, commitment verification, reloading, and comparison, including envelopes with a recomputed matching digest. Supply a resolved EPAC head or receipt identity before freezing predictions. +Within prediction or expected evidence, a reserved `hmmm` string (ignoring case and surrounding whitespace) or a non-finite number makes the case `UNRESOLVED` before any comparator runs. This applies recursively to arrays and object keys/values, even when the other operand has a different type, length, or keys. Explicitly unknown evidence cannot establish agreement or disagreement, and the receipt loader enforces the same classification. + Every loader also requires decimal/exponent JSON numbers to preserve their exact decimal value when parsed and serialized back to JSON. Ordinary round-trip values such as `0.1` and `1.007825` remain supported; precision-losing literals such as `9007199254740993.0`, finite-number overflow, and underflow are rejected before hashing or scoring. Integer tokens retain Python's exact integer handling. This is a decimal-value round-trip check, not a claim that decimal fractions have exact binary encodings. Explicit non-finite Python JSON extensions retain the existing `UNRESOLVED` comparison policy. Reload preserved receipts with `load_validation_receipt(path)`. It rejects duplicate keys, invalid envelopes and digest fields, repeated case IDs, inconsistent result statuses or counts, and a mismatched `receipt_sha256`. It also checks scored statuses against the evidence carried in each result. These checks establish internal integrity; an unkeyed checksum does not authenticate a custodian or prevent replacement of the entire evidence chain. Preserve externally trusted digests or independently controlled custody when that stronger claim matters. diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index e5a2b89..2135ca3 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -33,7 +33,7 @@ # summary: comparison-only held-out validation boundary that preregisters case identities and comparator rules before EPAC predictions are frozen and oracle values are revealed # owner: The Interdependency # public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_validation_plan, load_prediction_commitment, load_validation_receipt, load_oracle, load_packaged_oracle -# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _parse_roundtrip_float, _loads_evidence_json +# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _has_unresolved_evidence, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _parse_roundtrip_float, _loads_evidence_json # auth_boundary: none # storage_boundary: read # network_boundary: none @@ -61,7 +61,7 @@ # # id: heldout_comparison_tri_state_semantics # given: frozen predictions and held-out expected values are compared under a preregistered comparator -# then: comparable agreement is SURVIVED, comparable disagreement is FALSIFIED, and missing or incomparable evidence is UNRESOLVED without bool/number aliasing or large-integer loss +# then: comparable agreement is SURVIVED, comparable disagreement is FALSIFIED, and missing or incomparable evidence is UNRESOLVED without bool/number aliasing or large-integer loss; non-finite numbers and reserved hmmm values anywhere in either evidence operand take precedence over all comparisons # class: correctness # since: 2026-09-29 # @@ -338,6 +338,22 @@ def _verify_prediction_plan_binding( ) +def _has_unresolved_evidence(value: Any) -> bool: + """Unknown evidence remains unresolved, including within JSON containers.""" + if isinstance(value, str): + return value.strip().casefold() == "hmmm" + if isinstance(value, float): + return not math.isfinite(value) + if isinstance(value, list): + return any(_has_unresolved_evidence(item) for item in value) + if isinstance(value, Mapping): + return any( + _has_unresolved_evidence(key) or _has_unresolved_evidence(item) + for key, item in value.items() + ) + return False + + def _json_exact_equal(left: Any, right: Any) -> bool | None: """JSON-type-aware equality; None means the values are not comparable JSON.""" if left is None or right is None: @@ -423,6 +439,8 @@ def _numeric_within_tolerance( def _compare(expected: Any, predicted: Any, rule: Mapping[str, Any]) -> str: + if _has_unresolved_evidence(expected) or _has_unresolved_evidence(predicted): + return "UNRESOLVED" kind = rule.get("kind", "exact") if kind == "exact": equal = _json_exact_equal(predicted, expected) diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index 27b62e0..d9cb6b7 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -136,6 +136,18 @@ # mutates: none # cleanup: none # +# id: check_nonfinite_exact_type_mismatches_are_unresolved +# proves: heldout_comparison_tri_state_semantics, heldout_persisted_receipts_preserve_evidence +# call: self::test_nonfinite_exact_type_mismatches_are_unresolved +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_hmmm_evidence_is_unresolved_for_every_comparator +# proves: heldout_comparison_tri_state_semantics, heldout_persisted_receipts_preserve_evidence +# call: self::test_hmmm_evidence_is_unresolved_for_every_comparator +# mutates: filesystem +# cleanup: tempdir_teardown +# # id: check_blank_provenance_identity_is_unresolved # proves: heldout_oracle_evidence_integrity # call: self::test_blank_provenance_identity_is_unresolved @@ -956,6 +968,55 @@ def rejected(forged, *, rehash=False): rejected(forged, rehash=True) +def test_nonfinite_exact_type_mismatches_are_unresolved(): + plan = freeze_validation_plan([{"id": "x"}]) + with TemporaryDirectory() as tmp: + path = Path(tmp) / "receipt.json" + for nonfinite in (math.inf, -math.inf, math.nan): + for unknown in (nonfinite, [nonfinite], {"x": nonfinite}, [{"x": [nonfinite]}]): + for known in (None, True, 1, "unknown", [], {}, [0, 1], {"other": 1}): + for predicted, expected in ((unknown, known), (known, unknown)): + commitment = freeze_predictions({"x": predicted}, source_identity="epac@test", validation_plan=plan) + heldout = {"schema": "epac.heldout-chemistry-oracle", "version": "v1", "cases": [ + {"id": "x", "expected": expected, "provenance": {"authority": "fixture", "locator": "x"}} + ]} + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} + path.write_text(json.dumps(receipt), encoding="utf-8") + assert load_validation_receipt(path)["counts"] == receipt["counts"] + + +def test_hmmm_evidence_is_unresolved_for_every_comparator(): + with TemporaryDirectory() as tmp: + path = Path(tmp) / "receipt.json" + for rule in ({"kind": "exact"}, {"kind": "set-equality"}, + {"kind": "numeric-tolerance", "absolute_tolerance": 0}, {"kind": "unsupported"}): + plan = freeze_validation_plan([{"id": "x", "comparison": rule}]) + for sentinel in ("hmmm", " HMMM ", "\thMmM\n"): + for unknown in (sentinel, [sentinel], {"x": sentinel}, [{"x": [sentinel]}], {sentinel: 1}): + for known in (unknown, None, True, 1, "known", [], {}, [0], {"other": 1}): + for predicted, expected in ((unknown, known), (known, unknown)): + commitment = freeze_predictions({"x": predicted}, source_identity="epac@test", validation_plan=plan) + heldout = {"schema": "epac.heldout-chemistry-oracle", "version": "v1", "cases": [ + {"id": "x", "expected": expected, "comparison": rule, + "provenance": {"authority": "fixture", "locator": "x"}} + ]} + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 0, "FALSIFIED": 0, "UNRESOLVED": 1} + path.write_text(json.dumps(receipt), encoding="utf-8") + assert load_validation_receipt(path)["counts"] == receipt["counts"] + forged = deepcopy(receipt) + forged["results"][0]["status"] = "SURVIVED" + forged["counts"] = {"SURVIVED": 1, "FALSIFIED": 0, "UNRESOLVED": 0} + forged["receipt_sha256"] = heldout_validation._digest({key: value for key, value in forged.items() if key != "receipt_sha256"}) + path.write_text(json.dumps(forged), encoding="utf-8") + with pytest.raises(ValueError, match="status disagrees"): + load_validation_receipt(path) + for value in ("hmmmm", "known hmmm value", None, False, 1, [1], {"known": 1}): + assert heldout_validation._compare(value, value, {"kind": "exact"}) == "SURVIVED" + assert heldout_validation._compare("known", 1, {"kind": "exact"}) == "FALSIFIED" + + def test_evidence_loaders_reject_decimal_value_loss(): plan = freeze_validation_plan([ {"id": "x", "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": 0.5}} From d70a20085a2851e489b26cd6a9aa63717984b867 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 14:03:35 -0700 Subject: [PATCH 15/19] Compare numeric evidence by its committed JSON decimal value --- docs/heldout-chemistry-validation.md | 2 ++ epac_heldout_validation.py | 15 ++++++---- tests/test_heldout_validation.py | 42 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index 423153c..7d9fe4c 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -50,6 +50,8 @@ Within prediction or expected evidence, a reserved `hmmm` string (ignoring case Every loader also requires decimal/exponent JSON numbers to preserve their exact decimal value when parsed and serialized back to JSON. Ordinary round-trip values such as `0.1` and `1.007825` remain supported; precision-losing literals such as `9007199254740993.0`, finite-number overflow, and underflow are rejected before hashing or scoring. Integer tokens retain Python's exact integer handling. This is a decimal-value round-trip check, not a claim that decimal fractions have exact binary encodings. Explicit non-finite Python JSON extensions retain the existing `UNRESOLVED` comparison policy. +All numeric comparison uses exact rational interpretations of the committed JSON decimal values, including nested exact/set equality and tolerance arithmetic. For example, `1e23` means exactly `100000000000000000000000`, not the nearby integer represented by its in-memory binary float. Programmatic floats use their canonical JSON decimal representation, so direct and persisted scoring agree. + Reload preserved receipts with `load_validation_receipt(path)`. It rejects duplicate keys, invalid envelopes and digest fields, repeated case IDs, inconsistent result statuses or counts, and a mismatched `receipt_sha256`. It also checks scored statuses against the evidence carried in each result. These checks establish internal integrity; an unkeyed checksum does not authenticate a custodian or prevent replacement of the entire evidence chain. Preserve externally trusted digests or independently controlled custody when that stronger claim matters. ## Runnable end-to-end example diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 2135ca3..70cd25b 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -33,7 +33,7 @@ # summary: comparison-only held-out validation boundary that preregisters case identities and comparator rules before EPAC predictions are frozen and oracle values are revealed # owner: The Interdependency # public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_validation_plan, load_prediction_commitment, load_validation_receipt, load_oracle, load_packaged_oracle -# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _has_unresolved_evidence, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _parse_roundtrip_float, _loads_evidence_json +# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _has_unresolved_evidence, _json_number_fraction, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _parse_roundtrip_float, _loads_evidence_json # auth_boundary: none # storage_boundary: read # network_boundary: none @@ -97,7 +97,7 @@ # # id: heldout_json_numbers_preserve_decimal_value # given: a JSON number with a decimal point or exponent is loaded as held-out evidence -# then: its exact decimal value must survive parsing and canonical float serialization; rounding, underflow, and overflow are rejected before commitment or scoring +# then: its exact decimal value must survive parsing and canonical float serialization; rounding, underflow, and overflow are rejected before commitment or scoring, and every numeric comparator uses the decimal value committed by that serialization # class: evidence # since: 2026-10-04 # === END CONTRACTS === @@ -354,6 +354,11 @@ def _has_unresolved_evidence(value: Any) -> bool: return False +def _json_number_fraction(value: int | float) -> Fraction: + """Interpret finite numbers by the decimal JSON value bound into the digest.""" + return Fraction(str(value)) if isinstance(value, float) else Fraction(value) + + def _json_exact_equal(left: Any, right: Any) -> bool | None: """JSON-type-aware equality; None means the values are not comparable JSON.""" if left is None or right is None: @@ -371,7 +376,7 @@ def _json_exact_equal(left: Any, right: Any) -> bool | None: return None if isinstance(right, float) and not math.isfinite(right): return None - return left == right + return _json_number_fraction(left) == _json_number_fraction(right) if isinstance(left, str) or isinstance(right, str): return left == right if isinstance(left, str) and isinstance(right, str) else False if isinstance(left, list) or isinstance(right, list): @@ -411,7 +416,7 @@ def _json_token(value: Any) -> tuple[Any, ...] | None: return ("boolean", value) if isinstance(value, (int, float)): finite = _finite_number(value) - return None if finite is None else ("number", value) + return None if finite is None else ("number", _json_number_fraction(finite)) if isinstance(value, str): return ("string", value) if isinstance(value, list): @@ -435,7 +440,7 @@ def _numeric_within_tolerance( right = _finite_number(expected) if tolerance is None or tolerance < 0 or left is None or right is None: return None - return abs(Fraction(left) - Fraction(right)) <= Fraction(tolerance) + return abs(_json_number_fraction(left) - _json_number_fraction(right)) <= _json_number_fraction(tolerance) def _compare(expected: Any, predicted: Any, rule: Mapping[str, Any]) -> str: diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index d9cb6b7..2b0b55e 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -196,6 +196,12 @@ # mutates: filesystem # cleanup: tempdir_teardown # +# id: check_comparators_use_committed_decimal_number_values +# proves: heldout_json_numbers_preserve_decimal_value, heldout_comparison_tri_state_semantics, heldout_persisted_receipts_preserve_evidence +# call: self::test_comparators_use_committed_decimal_number_values +# mutates: filesystem +# cleanup: tempdir_teardown +# # id: check_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout # proves: heldout_oracle_loading_is_unambiguous # call: self::test_packaged_oracle_loader_works_from_checkout_or_install_and_prefers_checkout @@ -1017,6 +1023,42 @@ def test_hmmm_evidence_is_unresolved_for_every_comparator(): assert heldout_validation._compare("known", 1, {"kind": "exact"}) == "FALSIFIED" +def test_comparators_use_committed_decimal_number_values(): + with TemporaryDirectory() as tmp: + path = Path(tmp) / "evidence.json" + for sign in (1, -1): + decimal_integer = sign * 10**23 + binary_integer = int(sign * 1e23) + assert decimal_integer != binary_integer + for rule in ({"kind": "exact"}, {"kind": "set-equality"}, + {"kind": "numeric-tolerance", "absolute_tolerance": 0}): + plan = freeze_validation_plan([{"id": "x", "comparison": rule}]) + for number, status in ((decimal_integer, "SURVIVED"), (binary_integer, "FALSIFIED")): + expected, predicted = sign * 1e23, number + if rule["kind"] == "set-equality": + expected, predicted = [{"nested": [expected]}], [{"nested": [predicted]}] + for expected, predicted in ((expected, predicted), (predicted, expected)): + commitment = freeze_predictions({"x": predicted}, source_identity="epac@test", validation_plan=plan) + heldout = {"schema": "epac.heldout-chemistry-oracle", "version": "v1", "cases": [ + {"id": "x", "expected": expected, "comparison": rule, + "provenance": {"authority": "fixture", "locator": "x"}} + ]} + direct = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert direct["results"][0]["status"] == status + path.write_text(json.dumps(heldout), encoding="utf-8") + loaded_oracle = load_oracle(path) + path.write_text(json.dumps(commitment), encoding="utf-8") + loaded_commitment = load_prediction_commitment(path, validation_plan=plan) + restored = compare_after_freeze(loaded_commitment, loaded_oracle, validation_plan=plan) + assert restored == direct + path.write_text(json.dumps(restored), encoding="utf-8") + assert load_validation_receipt(path) == direct + assert heldout_validation._compare(0.1, 0.4, {"kind": "numeric-tolerance", "absolute_tolerance": 0.3}) == "SURVIVED" + assert heldout_validation._compare(0, 10**23, {"kind": "numeric-tolerance", "absolute_tolerance": 1e23}) == "SURVIVED" + assert heldout_validation._compare(0, 10**23 + 1, {"kind": "numeric-tolerance", "absolute_tolerance": 1e23}) == "FALSIFIED" + assert heldout_validation._compare({"x": [1e23]}, {"x": [int(1e23)]}, {"kind": "exact"}) == "FALSIFIED" + + def test_evidence_loaders_reject_decimal_value_loss(): plan = freeze_validation_plan([ {"id": "x", "comparison": {"kind": "numeric-tolerance", "absolute_tolerance": 0.5}} From 805072b7ff354963a329f6ea3013a0db7378c94f Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 14:12:49 -0700 Subject: [PATCH 16/19] Reject unresolved sentinels in all case and domain identities --- docs/heldout-chemistry-validation.md | 2 ++ epac_heldout_validation.py | 8 ++--- tests/test_heldout_validation.py | 54 ++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index 7d9fe4c..f045d78 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -46,6 +46,8 @@ The reserved `hmmm` provenance identity (ignoring surrounding whitespace and cas The same reserved sentinel is rejected as a prediction `source_identity` during freezing, commitment verification, reloading, and comparison, including envelopes with a recomputed matching digest. Supply a resolved EPAC head or receipt identity before freezing predictions. +Case identifiers and non-null domains must also be resolved identities: the reserved `hmmm` sentinel is rejected in plans, predictions, oracle comparisons, and preserved receipts, including externally reconstructed envelopes with matching digests. An omitted domain remains `null`; it is not replaced by an unknown identity string. + Within prediction or expected evidence, a reserved `hmmm` string (ignoring case and surrounding whitespace) or a non-finite number makes the case `UNRESOLVED` before any comparator runs. This applies recursively to arrays and object keys/values, even when the other operand has a different type, length, or keys. Explicitly unknown evidence cannot establish agreement or disagreement, and the receipt loader enforces the same classification. Every loader also requires decimal/exponent JSON numbers to preserve their exact decimal value when parsed and serialized back to JSON. Ordinary round-trip values such as `0.1` and `1.007825` remain supported; precision-losing literals such as `9007199254740993.0`, finite-number overflow, and underflow are rejected before hashing or scoring. Integer tokens retain Python's exact integer handling. This is a decimal-value round-trip check, not a claim that decimal fractions have exact binary encodings. Explicit non-finite Python JSON extensions retain the existing `UNRESOLVED` comparison policy. diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 70cd25b..4df4263 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -49,7 +49,7 @@ # === CONTRACTS === # id: heldout_selection_boundary_frozen_before_predictions # given: a validation plan and prediction commitment are supplied for held-out comparison -# then: case ids, canonical domains, and comparator rules are bound before predictions; prediction ids outside that verified plan are rejected +# then: resolved case ids, canonical resolved domains, and comparator rules are bound before predictions; reserved hmmm identities and prediction ids outside that verified plan are rejected # class: evidence # since: 2026-09-29 # @@ -121,8 +121,8 @@ def _digest(value: Any) -> str: def _required_nonempty_string(value: Any, field: str) -> str: if not isinstance(value, str) or not value.strip(): raise ValueError(f"{field} must be a nonempty string") - if field == "source_identity" and value.strip().casefold() == "hmmm": - raise ValueError("source_identity must be resolved, not the hmmm sentinel") + if value.strip().casefold() == "hmmm": + raise ValueError(f"{field} must be resolved, not the hmmm sentinel") return value @@ -164,7 +164,7 @@ def _normalized_domain(value: Any) -> str | None: return None if not isinstance(value, str) or not value.strip() or value != value.strip(): raise ValueError("domain must be null or a nonempty canonical string") - return value + return _required_nonempty_string(value, "domain") def _has_nonempty_provenance_identity(provenance: Any) -> bool: diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index 2b0b55e..af30ea6 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -52,6 +52,12 @@ # mutates: none # cleanup: none # +# id: check_case_and_domain_identities_reject_reserved_sentinel +# proves: heldout_selection_boundary_frozen_before_predictions, heldout_commitment_persistence_and_identity, heldout_oracle_evidence_integrity, heldout_persisted_receipts_preserve_evidence +# call: self::test_case_and_domain_identities_reject_reserved_sentinel +# mutates: filesystem +# cleanup: tempdir_teardown +# # id: check_prediction_commitment_rejects_non_json_values_before_hashing # proves: heldout_commitment_persistence_and_identity # call: self::test_prediction_commitment_rejects_non_json_values_before_hashing @@ -1023,6 +1029,54 @@ def test_hmmm_evidence_is_unresolved_for_every_comparator(): assert heldout_validation._compare("known", 1, {"kind": "exact"}) == "FALSIFIED" +def test_case_and_domain_identities_reject_reserved_sentinel(): + plan = freeze_validation_plan([{"id": "x"}]) + commitment = freeze_predictions({"x": 1}, source_identity="epac@test", validation_plan=plan) + heldout = {"schema": "epac.heldout-chemistry-oracle", "version": "v1", "cases": [ + {"id": "x", "expected": 1, "provenance": {"authority": "fixture", "locator": "x"}} + ]} + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + with TemporaryDirectory() as tmp: + path = Path(tmp) / "evidence.json" + for sentinel in ("hmmm", "HMMM", " HmMm ", "\thmmm\n"): + for field in ("id", "domain"): + case = {"id": "x", field: sentinel} + with pytest.raises(ValueError, match="domain|hmmm"): + freeze_validation_plan([case]) + forged_plan = deepcopy(plan) + forged_plan["cases"][0][field] = sentinel + forged_plan["plan_sha256"] = heldout_validation._digest({k: v for k, v in forged_plan.items() if k != "plan_sha256"}) + with pytest.raises(ValueError, match="domain|hmmm"): + freeze_predictions({}, source_identity="epac@test", validation_plan=forged_plan) + path.write_text(json.dumps(forged_plan), encoding="utf-8") + with pytest.raises(ValueError, match="domain|hmmm"): + load_validation_plan(path) + forged_oracle = deepcopy(heldout) + forged_oracle["cases"][0][field] = sentinel + with pytest.raises(ValueError, match="domain|hmmm"): + compare_after_freeze(commitment, forged_oracle, validation_plan=plan) + forged_receipt = deepcopy(receipt) + forged_receipt["results"][0][field] = sentinel + forged_receipt["receipt_sha256"] = heldout_validation._digest({k: v for k, v in forged_receipt.items() if k != "receipt_sha256"}) + path.write_text(json.dumps(forged_receipt), encoding="utf-8") + with pytest.raises(ValueError, match="domain|hmmm"): + load_validation_receipt(path) + with pytest.raises(ValueError, match="hmmm"): + freeze_predictions({sentinel: 1}, source_identity="epac@test", validation_plan=plan) + forged_commitment = deepcopy(commitment) + forged_commitment["predictions"] = {sentinel: 1} + forged_commitment["commitment_sha256"] = heldout_validation._digest({k: v for k, v in forged_commitment.items() if k != "commitment_sha256"}) + with pytest.raises(ValueError, match="hmmm"): + verify_commitment(forged_commitment) + with pytest.raises(ValueError, match="hmmm"): + compare_after_freeze(forged_commitment, heldout, validation_plan=plan) + path.write_text(json.dumps(forged_commitment), encoding="utf-8") + with pytest.raises(ValueError, match="hmmm"): + load_prediction_commitment(path, validation_plan=plan) + assert freeze_validation_plan([{"id": "hmmmm", "domain": "known"}])["cases"][0]["id"] == "hmmmm" + assert plan["cases"][0]["domain"] is None + + def test_comparators_use_committed_decimal_number_values(): with TemporaryDirectory() as tmp: path = Path(tmp) / "evidence.json" From db6fc5c0815593f9ecd03df2840b8e1d5fc831b0 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 14:22:02 -0700 Subject: [PATCH 17/19] Reject unpaired Unicode surrogates before loading or hashing evidence --- docs/heldout-chemistry-validation.md | 2 + epac_heldout_validation.py | 26 +++++++--- tests/test_heldout_validation.py | 76 ++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 7 deletions(-) diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index f045d78..ae171f1 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -54,6 +54,8 @@ Every loader also requires decimal/exponent JSON numbers to preserve their exact All numeric comparison uses exact rational interpretations of the committed JSON decimal values, including nested exact/set equality and tolerance arithmetic. For example, `1e23` means exactly `100000000000000000000000`, not the nearby integer represented by its in-memory binary float. Programmatic floats use their canonical JSON decimal representation, so direct and persisted scoring agree. +Strings and object keys must contain valid Unicode scalar values. Loaders and programmatic normalization reject unpaired surrogates with `ValueError` before hashing or returning loaded evidence. Valid non-ASCII text, including JSON-escaped surrogate pairs that decode to a Unicode scalar, retains the same canonical UTF-8 digest. + Reload preserved receipts with `load_validation_receipt(path)`. It rejects duplicate keys, invalid envelopes and digest fields, repeated case IDs, inconsistent result statuses or counts, and a mismatched `receipt_sha256`. It also checks scored statuses against the evidence carried in each result. These checks establish internal integrity; an unkeyed checksum does not authenticate a custodian or prevent replacement of the entire evidence chain. Preserve externally trusted digests or independently controlled custody when that stronger claim matters. ## Runnable end-to-end example diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 4df4263..131c595 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -33,7 +33,7 @@ # summary: comparison-only held-out validation boundary that preregisters case identities and comparator rules before EPAC predictions are frozen and oracle values are revealed # owner: The Interdependency # public_surface: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_validation_plan, load_prediction_commitment, load_validation_receipt, load_oracle, load_packaged_oracle -# internal_surface: _canonical, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _has_unresolved_evidence, _json_number_fraction, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _parse_roundtrip_float, _loads_evidence_json +# internal_surface: _canonical, _validated_unicode_string, _digest, _required_nonempty_string, _normalize_json_value, _normalized_domain, _has_nonempty_provenance_identity, _normalized_comparison, _verify_validation_plan, _verify_prediction_plan_binding, _verify_validation_receipt, _has_unresolved_evidence, _json_number_fraction, _json_exact_equal, _finite_number, _json_token, _numeric_within_tolerance, _compare, _reject_duplicate_object_pairs, _parse_roundtrip_float, _loads_evidence_json # auth_boundary: none # storage_boundary: read # network_boundary: none @@ -55,7 +55,7 @@ # # id: heldout_commitment_persistence_and_identity # given: predictions and source identity are frozen, serialized, reloaded, or externally reconstructed -# then: only persistence-stable JSON-shaped prediction values with a nonempty resolved source identity and matching digest are accepted; the reserved hmmm source sentinel is rejected +# then: only persistence-stable JSON-shaped prediction values with Unicode scalar strings, a nonempty resolved source identity, and matching digest are accepted; the reserved hmmm source sentinel is rejected # class: evidence # since: 2026-09-29 # @@ -79,7 +79,7 @@ # # id: heldout_oracle_loading_is_unambiguous # given: oracle evidence is loaded from a file, source checkout, or installed distribution -# then: duplicate JSON keys are rejected and source checkout data is preferred over an unrelated installed package +# then: duplicate JSON keys and unpaired Unicode surrogates are rejected before evidence is returned, and source checkout data is preferred over an unrelated installed package # class: correctness # since: 2026-09-29 # @@ -111,7 +111,17 @@ def _canonical(value: Any) -> bytes: - return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8") + payload = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + return _validated_unicode_string(payload, field="held-out evidence").encode("utf-8") + + +def _validated_unicode_string(value: str, *, field: str) -> str: + value = str(value) + try: + value.encode("utf-8") + except UnicodeEncodeError as exc: + raise ValueError(f"{field} must contain Unicode scalar values") from exc + return value def _digest(value: Any) -> str: @@ -123,7 +133,7 @@ def _required_nonempty_string(value: Any, field: str) -> str: raise ValueError(f"{field} must be a nonempty string") if value.strip().casefold() == "hmmm": raise ValueError(f"{field} must be resolved, not the hmmm sentinel") - return value + return _validated_unicode_string(value, field=field) def _normalize_json_value(value: Any, *, field: str) -> Any: @@ -137,7 +147,7 @@ def _normalize_json_value(value: Any, *, field: str) -> Any: if isinstance(value, float): return float(value) if isinstance(value, str): - return str(value) + return _validated_unicode_string(value, field=field) if isinstance(value, list): return [ _normalize_json_value(item, field=f"{field}[]") @@ -148,6 +158,7 @@ def _normalize_json_value(value: Any, *, field: str) -> Any: for key, item in value.items(): if not isinstance(key, str): raise ValueError(f"{field} object keys must be strings") + key = _validated_unicode_string(key, field=f"{field} object key") normalized[key] = _normalize_json_value( item, field=f"{field}.{key}", @@ -185,6 +196,7 @@ def _normalized_comparison(case: Mapping[str, Any]) -> dict[str, Any]: kind = comparison.get("kind", "exact") if not isinstance(kind, str) or not kind.strip() or kind != kind.strip(): raise ValueError("comparison kind must be a nonempty canonical string") + kind = _validated_unicode_string(kind, field="comparison kind") allowed = {"kind", "absolute_tolerance"} if kind == "numeric-tolerance" else {"kind"} extra = set(comparison) - allowed if extra: @@ -611,7 +623,7 @@ def _loads_evidence_json(payload: str, *, document: str) -> dict[str, Any]: ) if not isinstance(value, dict): raise ValueError(f"{document} document must be a JSON object") - return value + return _normalize_json_value(value, field=document) def load_validation_plan(path: str | Path) -> dict[str, Any]: diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py index af30ea6..5c53dfb 100644 --- a/tests/test_heldout_validation.py +++ b/tests/test_heldout_validation.py @@ -58,6 +58,18 @@ # mutates: filesystem # cleanup: tempdir_teardown # +# id: check_unpaired_surrogates_are_rejected_before_hashing_or_loading +# proves: heldout_commitment_persistence_and_identity, heldout_oracle_loading_is_unambiguous +# call: self::test_unpaired_surrogates_are_rejected_before_hashing_or_loading +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_valid_unicode_preserves_persisted_evidence_identity +# proves: heldout_commitment_persistence_and_identity, heldout_oracle_loading_is_unambiguous, heldout_persisted_receipts_preserve_evidence +# call: self::test_valid_unicode_preserves_persisted_evidence_identity +# mutates: filesystem +# cleanup: tempdir_teardown +# # id: check_prediction_commitment_rejects_non_json_values_before_hashing # proves: heldout_commitment_persistence_and_identity # call: self::test_prediction_commitment_rejects_non_json_values_before_hashing @@ -1029,6 +1041,70 @@ def test_hmmm_evidence_is_unresolved_for_every_comparator(): assert heldout_validation._compare("known", 1, {"kind": "exact"}) == "FALSIFIED" +def test_unpaired_surrogates_are_rejected_before_hashing_or_loading(): + plan = freeze_validation_plan([{"id": "x"}]) + commitment = freeze_predictions({"x": 1}, source_identity="epac@test", validation_plan=plan) + heldout = {"schema": "epac.heldout-chemistry-oracle", "version": "v1", "cases": [ + {"id": "x", "expected": 1, "provenance": {"authority": "fixture", "locator": "x"}} + ]} + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + with TemporaryDirectory() as tmp: + path = Path(tmp) / "evidence.json" + for bad in ("\ud800", "\udfff", "a\ud800b"): + for case in ({"id": bad}, {"id": "x", "domain": bad}, {"id": "x", "comparison": {"kind": bad}}): + with pytest.raises(ValueError, match="Unicode scalar"): + freeze_validation_plan([case]) + with pytest.raises(ValueError, match="Unicode scalar"): + freeze_predictions({"x": 1}, source_identity=bad, validation_plan=plan) + for prediction in (bad, [bad], {bad: 1}, {"nested": [bad]}): + with pytest.raises(ValueError, match="Unicode scalar"): + freeze_predictions({"x": prediction}, source_identity="epac@test", validation_plan=plan) + for field in ("expected", "provenance"): + malformed = deepcopy(heldout) + malformed["cases"][0][field] = bad if field == "expected" else {"authority": "fixture", "locator": bad} + with pytest.raises(ValueError, match="Unicode scalar"): + compare_after_freeze(commitment, malformed, validation_plan=plan) + with pytest.raises(ValueError, match="Unicode scalar"): + heldout_validation._digest({"value": bad}) + for envelope, loader in ( + (plan, load_validation_plan), + (commitment, lambda path: load_prediction_commitment(path, validation_plan=plan)), + (heldout, load_oracle), + (receipt, load_validation_receipt), + ): + payload = json.dumps(envelope).replace('"x"', json.dumps(bad)) + path.write_text(payload, encoding="utf-8") + with pytest.raises(ValueError, match="Unicode scalar"): + loader(path) + path.write_text(json.dumps({bad: 1}), encoding="utf-8") + with pytest.raises(ValueError, match="Unicode scalar"): + load_oracle(path) + + +def test_valid_unicode_preserves_persisted_evidence_identity(): + case_id = "element:氢:😀" + plan = freeze_validation_plan([{"id": case_id, "domain": "质量"}]) + prediction = {"λ": ["😀", "氢"]} + commitment = freeze_predictions({case_id: prediction}, source_identity="epac@😀", validation_plan=plan) + heldout = {"schema": "epac.heldout-chemistry-oracle", "version": "v1", "cases": [ + {"id": case_id, "domain": "质量", "expected": prediction, + "provenance": {"authority": "来源", "locator": "😀"}} + ]} + receipt = compare_after_freeze(commitment, heldout, validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 1, "FALSIFIED": 0, "UNRESOLVED": 0} + with TemporaryDirectory() as tmp: + path = Path(tmp) / "evidence.json" + for envelope, loader in ( + (plan, load_validation_plan), + (commitment, lambda path: load_prediction_commitment(path, validation_plan=plan)), + (heldout, load_oracle), + (receipt, load_validation_receipt), + ): + for escaped in (True, False): + path.write_text(json.dumps(envelope, ensure_ascii=escaped), encoding="utf-8") + assert loader(path) == envelope + + def test_case_and_domain_identities_reject_reserved_sentinel(): plan = freeze_validation_plan([{"id": "x"}]) commitment = freeze_predictions({"x": 1}, source_identity="epac@test", validation_plan=plan) From 91a3dcdff8470e05cc130227a7727d1f99b8ac56 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 14:36:56 -0700 Subject: [PATCH 18/19] Declare held-out runtime contracts and preserve unresolved boundary mappings --- data/epac-join-term-v0-receipt.json | 2 +- docs/boundary_probe_completeness.md | 6 +- docs/epac-join-term-v0-audit.md | 8 +-- docs/heldout-chemistry-validation.md | 6 +- epac_boundary_probe_completeness.py | 19 ++++++- epac_heldout_validation.py | 68 ++++++++++++++++++++++- tests/test_boundary_probe_completeness.py | 18 +++++- 7 files changed, 113 insertions(+), 14 deletions(-) diff --git a/data/epac-join-term-v0-receipt.json b/data/epac-join-term-v0-receipt.json index 4d50b97..f20587a 100644 --- a/data/epac-join-term-v0-receipt.json +++ b/data/epac-join-term-v0-receipt.json @@ -1 +1 @@ -{"authority":{"epac":{"authority":"schema, constructor, equality, serialization, recovery, and evidence","base_commit":"1e5c999286f12221eff9870d1372203ac7935f2a","base_tree":"ce7781938ff684d826bd91f475b5423bd56b146a","feature_identity":"source-manifest-sha256","repository":"The-Interdependency/epac"},"metapat":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_fixture_path":"src/metapat/fixtures/epac-join-terms-application-v4.json","application_fixture_sha256":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","application_id":"metapat.application.epac_join_terms","application_source_path":"src/metapat/epac_join_terms.py","application_source_sha256":"6cfb4a4109fd43a4f8343b48dd6fd72b74346f4609a65911cbf96188412b6ef0","application_version":"epac-join-terms-application-v4","authority":"semantic spine role license only","authority_transfer":false,"producer_commit":"1cdfb09dd00a451cee30eec2e78624df8c682662","producer_tree":"d946a5a18b0c53fd561dcd9d68fdf36d5dd11638","repository":"The-Interdependency/metapat"},"skill_lib":{"authority":"build and evidence doctrine only","authority_transfer":false,"commit":"516933d98f9de376f4e498f44059043dc0d96470","repository":"The-Interdependency/skill-lib","tree":"a1465ddf1de9519c9abc1a97d058432ba71bb257"},"ucns":{"authority_transfer":false,"consumed":false,"law_modified":false}},"candidate":{"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","joins":[{"join_id":"epac.join:fixture-a:S0-S1","order":1,"provenance":["epac.join-term-v0","authored-near-join:S0->S1"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S0","target_origin_id":"epac.origin:fixture-a:S1"},{"join_id":"epac.join:fixture-a:S1-S2","order":2,"provenance":["epac.join-term-v0","authored-near-join:S1->S2"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S1","target_origin_id":"epac.origin:fixture-a:S2"},{"join_id":"epac.join:fixture-a:S2-S3","order":3,"provenance":["epac.join-term-v0","authored-near-join:S2->S3"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S2","target_origin_id":"epac.origin:fixture-a:S3"},{"join_id":"epac.join:fixture-a:S3-S4","order":4,"provenance":["epac.join-term-v0","authored-near-join:S3->S4"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S3","target_origin_id":"epac.origin:fixture-a:S4"},{"join_id":"epac.join:fixture-a:S4-S5","order":5,"provenance":["epac.join-term-v0","authored-near-join:S4->S5"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S4","target_origin_id":"epac.origin:fixture-a:S5"},{"join_id":"epac.join:fixture-a:S5-S6","order":6,"provenance":["epac.join-term-v0","authored-near-join:S5->S6"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S5","target_origin_id":"epac.origin:fixture-a:S6"}],"legacy_bag":{"excluded_from_tensor":true,"kind":"bag","scale_slot_counts":{"S0":2,"S1":2,"S2":3,"S3":3,"S4":2,"S5":4,"S6":2},"slot_kind_counts":{"hole":6,"leftover":6,"member":6}},"object_kind":"multi-origin-join-tree","origins":[{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0]},"domain_name":"subatomic slot","k":2,"origin_id":"epac.origin:fixture-a:S0","phase":{"denominator":1,"numerator":0},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S0"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":1,"numerator":0}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S0","shape_signature":["0:seed:leftover","1:zero:hole"],"slots":[{"facing":-2,"kind":"leftover","name":"seed","order":0,"participant_id":"epac.leftover:fixture-a:S0:seed","participant_scale":null},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0]},"domain_name":"atomic","k":2,"origin_id":"epac.origin:fixture-a:S1","phase":{"denominator":8,"numerator":1},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S1"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":4},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S1","shape_signature":["0:subatomic:member","1:zero:hole"],"slots":[{"facing":4,"kind":"member","name":"subatomic","order":0,"participant_id":"epac.origin:fixture-a:S0","participant_scale":"S0"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1,0]},"domain_name":"join/arity","k":3,"origin_id":"epac.origin:fixture-a:S2","phase":{"denominator":4,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S2","authored-seating-chart"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":3},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":-3},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S2","shape_signature":["0:core:member","1:guest:leftover","2:vacancy:hole"],"slots":[{"facing":3,"kind":"member","name":"core","order":0,"participant_id":"epac.origin:fixture-a:S1","participant_scale":"S1"},{"facing":-3,"kind":"leftover","name":"guest","order":1,"participant_id":"epac.leftover:fixture-a:S2:guest","participant_scale":null},{"facing":0,"kind":"hole","name":"vacancy","order":2,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0,1]},"domain_name":"embed","k":3,"origin_id":"epac.origin:fixture-a:S3","phase":{"denominator":8,"numerator":3},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S3"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":-5},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":2}],"s5_energy_readout":null,"scale":"S3","shape_signature":["0:join:member","1:zero:hole","2:embed-context:leftover"],"slots":[{"facing":-5,"kind":"member","name":"join","order":0,"participant_id":"epac.origin:fixture-a:S2","participant_scale":"S2"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null},{"facing":2,"kind":"leftover","name":"embed-context","order":2,"participant_id":"epac.leftover:fixture-a:S3:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"electronic state","k":2,"origin_id":"epac.origin:fixture-a:S4","phase":{"denominator":2,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S4"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":2,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S4","shape_signature":["0:embed:member","1:electronic-context:leftover"],"slots":[{"facing":2,"kind":"member","name":"embed","order":0,"participant_id":"epac.origin:fixture-a:S3","participant_scale":"S3"},{"facing":-1,"kind":"leftover","name":"electronic-context","order":1,"participant_id":"epac.leftover:fixture-a:S4:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0,-1,0]},"domain_name":"EPAC energy readout","k":4,"origin_id":"epac.origin:fixture-a:S5","phase":{"denominator":8,"numerator":5},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S5"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":5}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.capacity.v0","value":4},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":7},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[3].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":{"measured_property":"slots[*].kind,k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.occupied-slots-over-k.v0","value":{"denominator":2,"numerator":1}},"scale":"S5","shape_signature":["0:electronic-state:member","1:zero-a:hole","2:occupancy-context:leftover","3:zero-b:hole"],"slots":[{"facing":7,"kind":"member","name":"electronic-state","order":0,"participant_id":"epac.origin:fixture-a:S4","participant_scale":"S4"},{"facing":0,"kind":"hole","name":"zero-a","order":1,"participant_id":null,"participant_scale":null},{"facing":-2,"kind":"leftover","name":"occupancy-context","order":2,"participant_id":"epac.leftover:fixture-a:S5:context","participant_scale":null},{"facing":0,"kind":"hole","name":"zero-b","order":3,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"ensemble","k":2,"origin_id":"epac.origin:fixture-a:S6","phase":{"denominator":4,"numerator":3},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S6"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":1},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S6","shape_signature":["0:energy-readout:member","1:ensemble-context:leftover"],"slots":[{"facing":1,"kind":"member","name":"energy-readout","order":0,"participant_id":"epac.origin:fixture-a:S5","participant_scale":"S5"},{"facing":-1,"kind":"leftover","name":"ensemble-context","order":1,"participant_id":"epac.leftover:fixture-a:S6:context","participant_scale":null}]}],"schema_id":"epac.multi-origin-join-tree","schema_version":"0.2.0","semantic_field_bindings":{"join.join_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"authored join record identity","spine_name":"Thing"},"join.order":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale relation position","spine_name":"Tensor"},"join.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"authored join provenance","spine_name":"Thing"},"join.relation":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale join","spine_name":"Tensor"},"join.source_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"source origin identity","spine_name":"Thing"},"join.target_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"new target origin identity","spine_name":"Thing"},"legacy_bag.excluded_from_tensor":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"tensor-exclusion marker","spine_name":"Thing"},"legacy_bag.kind":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"legacy sidecar kind","spine_name":"Thing"},"legacy_bag.scale_slot_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by scale","spine_name":"Thing"},"legacy_bag.slot_kind_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by kind","spine_name":"Thing"},"origin.arity":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"occupied seating arity","spine_name":"Boundary"},"origin.bearing":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"ordered static slot-facing sign readouts","spine_name":"Scalar"},"origin.domain_name":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC scale name","spine_name":"Simplex"},"origin.k":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"join window k","spine_name":"Boundary"},"origin.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"origin identity","spine_name":"Thing"},"origin.phase":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"EPAC exact phase","spine_name":"State"},"origin.phase_chart":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"visible or lifted phase record","spine_name":"State"},"origin.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"origin provenance","spine_name":"Thing"},"origin.readouts":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"identified phase, capacity, and slot-facing readouts","spine_name":"Scalar"},"origin.s5_energy_readout":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"EPAC S5 occupancy functional","spine_name":"Scalar"},"origin.scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"EPAC scale origin","spine_name":"Simplex"},"origin.shape_signature":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"ordered seating shape","spine_name":"Tensor"},"origin.slots":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"named ordered join slots","spine_name":"Boundary"},"readout.measured_property":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"named source state property","spine_name":"State"},"readout.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"readout source origin identity","spine_name":"Thing"},"readout.rule_id":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC readout rule identity","spine_name":"Thing"},"readout.value":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"computed EPAC readout value","spine_name":"Scalar"},"slot.facing":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"stored slot-facing property","spine_name":"State"},"slot.kind":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"member, hole, or leftover","spine_name":"Boundary"},"slot.name":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot name","spine_name":"Boundary"},"slot.order":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot order","spine_name":"Boundary"},"slot.participant_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"member or leftover identity","spine_name":"Thing"},"slot.participant_scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"member source scale","spine_name":"Simplex"},"tree.joins":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"ordered authored adjacent-scale relations","spine_name":"Tensor"},"tree.legacy_bag":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"non-structural legacy bag sidecar","spine_name":"Thing"},"tree.object_kind":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"multi-origin join-tree kind","spine_name":"Thing"},"tree.origins":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"S0-through-S6 origin tensor","spine_name":"Tensor"}},"semantic_license":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_id":"metapat.application.epac_join_terms","application_version":"epac-join-terms-application-v4","authority":"The-Interdependency/metapat","authority_transfer":false}},"evidence":{"declared_check_count":17,"declared_contract_count":17,"deterministic_regeneration_command":"python tools/generate_join_term_v0.py --check","molecular_shape_standing":"FALSIFIED (preserved; tested separately)","public_recovery_uses_constructor_state":false},"evidence_digest":"0e7188233a25dd675d58a1cf91350d6f7b0674bdcd1c922f131bb04fbf61870f","evidence_epoch":"2026-09-30","historical_original_authoring_runtime":{"executable":"/usr/bin/python3.12","executable_sha256":"e50d468e8b0adfb05733f5b87b3cff34829c4a8c1aea50c865aa8bdfe4bb150f","implementation":"CPython","version":"3.12.3"},"hmmm":["Authored adjacency and its stored order do not establish state changes or actual occurrence order; Transformation and Time remain unlicensed for this candidate.","Whether a useful operation exists that requires secret EPAC state and cannot be efficiently reproduced from a legitimate public projection remains unestablished; this candidate is wholly public.","Whether exact lineage and seating replay improve an independently preregistered EPAC task remains unmeasured.","Whether any later UCNS correspondence is useful requires a separate license and test without rewriting UCNS law."],"inputs":{"identity_namespace":"fixture-a","input_is_public":true,"phase_chart_rule":"visible on even-numbered scales; lifted on odd-numbered scales","phase_values":[{"denominator":1,"numerator":0},{"denominator":8,"numerator":1},{"denominator":4,"numerator":1},{"denominator":8,"numerator":3},{"denominator":2,"numerator":1},{"denominator":8,"numerator":5},{"denominator":4,"numerator":3}],"private_input_supplied":false,"s2_seating_order":["core","guest","vacancy"]},"nonclaims":["Vector action, resulting state Transformation, or actual Time sequence","chemical or physical truth","molecular geometry or shape prediction","UCNS correspondence, topology, theorem status, or gonol identity","energy coupling, conservation, entropy, force, or field behavior","cryptographic secrecy, confidentiality, public-key separation, or computational hardness","PCEA compatibility","production suitability","release or protocol authority"],"results":{"authored_join_count":6,"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","candidate_json_sha256":"821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd","classification":"PUBLIC_ONLY_RECOVERY_SURVIVED","hole_count":6,"leftover_count":6,"legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","legacy_projection":"slot-count bag","member_count":6,"native_operation":"exact authored origin-lineage and named-seating replay","origin_count":7,"s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"same_bag_alpha_renamed_identity_witness":{"bags_equal":true,"baseline_s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"conclusion":"the legacy bag does not select exact instance identity labels, while structural lineage up to alpha-renaming is unchanged","exact_instance_lineages_equal":false,"join_isomorphic":true,"renamed_candidate_digest":"7e30d6f4942e215462254dec9723a1deeb4e71d680e2cc960f227b02240f420d","renamed_identity_namespace":"fixture-b","renamed_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","renamed_s6_lineage":["epac.origin:fixture-b:S0","epac.origin:fixture-b:S1","epac.origin:fixture-b:S2","epac.origin:fixture-b:S3","epac.origin:fixture-b:S4","epac.origin:fixture-b:S5","epac.origin:fixture-b:S6"]},"same_bag_nonisomorphic_witness":{"bags_equal":true,"baseline_candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","conclusion":"the legacy bag cannot uniquely recover authored seating or the complete join tree","join_isomorphic":false,"reordered_candidate_digest":"f74f9227a79acf84588b11d153bf66f9490d3aa18d8e899b6266c11ca3594633","reordered_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","reordered_s2_seating_order":["guest","core","vacancy"]},"slot_count":18},"runtime_scope":"Deterministic evidence is runtime-independent; current execution identities belong to the exact-head artifact replay receipts, not this historical authoring record.","schema_id":"epac.join-term-v0-evidence","schema_version":"1.0.0","source_files_sha256":{"README.md":"7e28c7c092f7551b11a03c9db7862e27ec88ff6f81167ed3d3f99db26e29a18b","data/metapat-epac-join-terms-application-v4.json":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","docs/PROVENANCE.md":"b410815a47ea9a8229406241616a9dc143ff09eb8da717456c019379efa6d5ee","docs/domain-claims.md":"f6f7f517e127a517a4acccba5527692239700c128b7bf0ecef9603e8c155c911","docs/multi-origin-join-term-v0.md":"6efb441f4421edf84c22556b46e438cc2da4f51536c522d3b6556af7b1c66a39","docs/research-status.md":"1f5194f0dc44521b6dc81d9a9494c72ff385fd879b8eb2b2849824640c5c2d07","docs/work-graph.json":"4edb37ace798511ff4b5f667afd1f0a3852ce78b7335349aaa9662171de0b9e9","docs/work-graphs/epac-join-term-v0.json":"7487441ec7e3ff3203635e245fccba5d293192cef5201c657946a15788166f16","epac_boundary_probe_completeness.py":"1bc8a3e990e99d52b9636eb1f656f3da0d6178c42de9dd86d811d9211e87f014","epac_join_term.py":"4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af","pyproject.toml":"44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd","tests/test_boundary_probe_completeness.py":"8c096277431fc59163b362a7e88c5c3dd515f66bab62deed81eff1be2dc4a641","tests/test_epac_join_term.py":"ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8","tools/generate_join_term_v0.py":"1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331"},"source_manifest_sha256":"bf8574518bf2be7d2b6e72fc8590f90b9ec2104415f88a55ba06420128d12834"} +{"authority":{"epac":{"authority":"schema, constructor, equality, serialization, recovery, and evidence","base_commit":"1e5c999286f12221eff9870d1372203ac7935f2a","base_tree":"ce7781938ff684d826bd91f475b5423bd56b146a","feature_identity":"source-manifest-sha256","repository":"The-Interdependency/epac"},"metapat":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_fixture_path":"src/metapat/fixtures/epac-join-terms-application-v4.json","application_fixture_sha256":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","application_id":"metapat.application.epac_join_terms","application_source_path":"src/metapat/epac_join_terms.py","application_source_sha256":"6cfb4a4109fd43a4f8343b48dd6fd72b74346f4609a65911cbf96188412b6ef0","application_version":"epac-join-terms-application-v4","authority":"semantic spine role license only","authority_transfer":false,"producer_commit":"1cdfb09dd00a451cee30eec2e78624df8c682662","producer_tree":"d946a5a18b0c53fd561dcd9d68fdf36d5dd11638","repository":"The-Interdependency/metapat"},"skill_lib":{"authority":"build and evidence doctrine only","authority_transfer":false,"commit":"516933d98f9de376f4e498f44059043dc0d96470","repository":"The-Interdependency/skill-lib","tree":"a1465ddf1de9519c9abc1a97d058432ba71bb257"},"ucns":{"authority_transfer":false,"consumed":false,"law_modified":false}},"candidate":{"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","joins":[{"join_id":"epac.join:fixture-a:S0-S1","order":1,"provenance":["epac.join-term-v0","authored-near-join:S0->S1"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S0","target_origin_id":"epac.origin:fixture-a:S1"},{"join_id":"epac.join:fixture-a:S1-S2","order":2,"provenance":["epac.join-term-v0","authored-near-join:S1->S2"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S1","target_origin_id":"epac.origin:fixture-a:S2"},{"join_id":"epac.join:fixture-a:S2-S3","order":3,"provenance":["epac.join-term-v0","authored-near-join:S2->S3"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S2","target_origin_id":"epac.origin:fixture-a:S3"},{"join_id":"epac.join:fixture-a:S3-S4","order":4,"provenance":["epac.join-term-v0","authored-near-join:S3->S4"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S3","target_origin_id":"epac.origin:fixture-a:S4"},{"join_id":"epac.join:fixture-a:S4-S5","order":5,"provenance":["epac.join-term-v0","authored-near-join:S4->S5"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S4","target_origin_id":"epac.origin:fixture-a:S5"},{"join_id":"epac.join:fixture-a:S5-S6","order":6,"provenance":["epac.join-term-v0","authored-near-join:S5->S6"],"relation":"epac.join.adjacent-scale","source_origin_id":"epac.origin:fixture-a:S5","target_origin_id":"epac.origin:fixture-a:S6"}],"legacy_bag":{"excluded_from_tensor":true,"kind":"bag","scale_slot_counts":{"S0":2,"S1":2,"S2":3,"S3":3,"S4":2,"S5":4,"S6":2},"slot_kind_counts":{"hole":6,"leftover":6,"member":6}},"object_kind":"multi-origin-join-tree","origins":[{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0]},"domain_name":"subatomic slot","k":2,"origin_id":"epac.origin:fixture-a:S0","phase":{"denominator":1,"numerator":0},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S0"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":1,"numerator":0}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S0","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S0","shape_signature":["0:seed:leftover","1:zero:hole"],"slots":[{"facing":-2,"kind":"leftover","name":"seed","order":0,"participant_id":"epac.leftover:fixture-a:S0:seed","participant_scale":null},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":1,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0]},"domain_name":"atomic","k":2,"origin_id":"epac.origin:fixture-a:S1","phase":{"denominator":8,"numerator":1},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S1"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":4},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S1","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S1","shape_signature":["0:subatomic:member","1:zero:hole"],"slots":[{"facing":4,"kind":"member","name":"subatomic","order":0,"participant_id":"epac.origin:fixture-a:S0","participant_scale":"S0"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1,0]},"domain_name":"join/arity","k":3,"origin_id":"epac.origin:fixture-a:S2","phase":{"denominator":4,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S2","authored-seating-chart"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":3},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":-3},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S2","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":null,"scale":"S2","shape_signature":["0:core:member","1:guest:leftover","2:vacancy:hole"],"slots":[{"facing":3,"kind":"member","name":"core","order":0,"participant_id":"epac.origin:fixture-a:S1","participant_scale":"S1"},{"facing":-3,"kind":"leftover","name":"guest","order":1,"participant_id":"epac.leftover:fixture-a:S2:guest","participant_scale":null},{"facing":0,"kind":"hole","name":"vacancy","order":2,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.bearing.slot-facing-sign.v0","value":[-1,0,1]},"domain_name":"embed","k":3,"origin_id":"epac.origin:fixture-a:S3","phase":{"denominator":8,"numerator":3},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S3"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.capacity.v0","value":3},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":-5},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S3","rule_id":"epac.readout.slot-facing.v0","value":2}],"s5_energy_readout":null,"scale":"S3","shape_signature":["0:join:member","1:zero:hole","2:embed-context:leftover"],"slots":[{"facing":-5,"kind":"member","name":"join","order":0,"participant_id":"epac.origin:fixture-a:S2","participant_scale":"S2"},{"facing":0,"kind":"hole","name":"zero","order":1,"participant_id":null,"participant_scale":null},{"facing":2,"kind":"leftover","name":"embed-context","order":2,"participant_id":"epac.leftover:fixture-a:S3:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"electronic state","k":2,"origin_id":"epac.origin:fixture-a:S4","phase":{"denominator":2,"numerator":1},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S4"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":2,"numerator":1}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":2},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S4","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S4","shape_signature":["0:embed:member","1:electronic-context:leftover"],"slots":[{"facing":2,"kind":"member","name":"embed","order":0,"participant_id":"epac.origin:fixture-a:S3","participant_scale":"S3"},{"facing":-1,"kind":"leftover","name":"electronic-context","order":1,"participant_id":"epac.leftover:fixture-a:S4:context","participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,0,-1,0]},"domain_name":"EPAC energy readout","k":4,"origin_id":"epac.origin:fixture-a:S5","phase":{"denominator":8,"numerator":5},"phase_chart":"lifted","provenance":["epac.join-term-v0","declared-scale:S5"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":8,"numerator":5}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.capacity.v0","value":4},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":7},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0},{"measured_property":"slots[2].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":-2},{"measured_property":"slots[3].facing","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.slot-facing.v0","value":0}],"s5_energy_readout":{"measured_property":"slots[*].kind,k","origin_id":"epac.origin:fixture-a:S5","rule_id":"epac.readout.occupied-slots-over-k.v0","value":{"denominator":2,"numerator":1}},"scale":"S5","shape_signature":["0:electronic-state:member","1:zero-a:hole","2:occupancy-context:leftover","3:zero-b:hole"],"slots":[{"facing":7,"kind":"member","name":"electronic-state","order":0,"participant_id":"epac.origin:fixture-a:S4","participant_scale":"S4"},{"facing":0,"kind":"hole","name":"zero-a","order":1,"participant_id":null,"participant_scale":null},{"facing":-2,"kind":"leftover","name":"occupancy-context","order":2,"participant_id":"epac.leftover:fixture-a:S5:context","participant_scale":null},{"facing":0,"kind":"hole","name":"zero-b","order":3,"participant_id":null,"participant_scale":null}]},{"arity":2,"bearing":{"measured_property":"slots[*].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.bearing.slot-facing-sign.v0","value":[1,-1]},"domain_name":"ensemble","k":2,"origin_id":"epac.origin:fixture-a:S6","phase":{"denominator":4,"numerator":3},"phase_chart":"visible","provenance":["epac.join-term-v0","declared-scale:S6"],"readouts":[{"measured_property":"phase","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.exact-phase.v0","value":{"denominator":4,"numerator":3}},{"measured_property":"k","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.capacity.v0","value":2},{"measured_property":"slots[0].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":1},{"measured_property":"slots[1].facing","origin_id":"epac.origin:fixture-a:S6","rule_id":"epac.readout.slot-facing.v0","value":-1}],"s5_energy_readout":null,"scale":"S6","shape_signature":["0:energy-readout:member","1:ensemble-context:leftover"],"slots":[{"facing":1,"kind":"member","name":"energy-readout","order":0,"participant_id":"epac.origin:fixture-a:S5","participant_scale":"S5"},{"facing":-1,"kind":"leftover","name":"ensemble-context","order":1,"participant_id":"epac.leftover:fixture-a:S6:context","participant_scale":null}]}],"schema_id":"epac.multi-origin-join-tree","schema_version":"0.2.0","semantic_field_bindings":{"join.join_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"authored join record identity","spine_name":"Thing"},"join.order":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale relation position","spine_name":"Tensor"},"join.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"authored join provenance","spine_name":"Thing"},"join.relation":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"authored adjacent-scale join","spine_name":"Tensor"},"join.source_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"source origin identity","spine_name":"Thing"},"join.target_origin_id":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"new target origin identity","spine_name":"Thing"},"legacy_bag.excluded_from_tensor":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"tensor-exclusion marker","spine_name":"Thing"},"legacy_bag.kind":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"legacy sidecar kind","spine_name":"Thing"},"legacy_bag.scale_slot_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by scale","spine_name":"Thing"},"legacy_bag.slot_kind_counts":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"lossy slot-count map by kind","spine_name":"Thing"},"origin.arity":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"occupied seating arity","spine_name":"Boundary"},"origin.bearing":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"ordered static slot-facing sign readouts","spine_name":"Scalar"},"origin.domain_name":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC scale name","spine_name":"Simplex"},"origin.k":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"join window k","spine_name":"Boundary"},"origin.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"origin identity","spine_name":"Thing"},"origin.phase":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"EPAC exact phase","spine_name":"State"},"origin.phase_chart":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"visible or lifted phase record","spine_name":"State"},"origin.provenance":{"application_role":"recursive-origin","catalog_module_id":"metapat.axiom.7.emerge","domain_name":"origin provenance","spine_name":"Thing"},"origin.readouts":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"identified phase, capacity, and slot-facing readouts","spine_name":"Scalar"},"origin.s5_energy_readout":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"EPAC S5 occupancy functional","spine_name":"Scalar"},"origin.scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"EPAC scale origin","spine_name":"Simplex"},"origin.shape_signature":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"ordered seating shape","spine_name":"Tensor"},"origin.slots":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"named ordered join slots","spine_name":"Boundary"},"readout.measured_property":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"named source state property","spine_name":"State"},"readout.origin_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"readout source origin identity","spine_name":"Thing"},"readout.rule_id":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"EPAC readout rule identity","spine_name":"Thing"},"readout.value":{"application_role":"state-metric","catalog_module_id":"metapat.axiom.9.scalar","domain_name":"computed EPAC readout value","spine_name":"Scalar"},"slot.facing":{"application_role":"origin-state","catalog_module_id":"metapat.axiom.3.state","domain_name":"stored slot-facing property","spine_name":"State"},"slot.kind":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"member, hole, or leftover","spine_name":"Boundary"},"slot.name":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot name","spine_name":"Boundary"},"slot.order":{"application_role":"join-boundary","catalog_module_id":"metapat.axiom.2.boundary","domain_name":"slot order","spine_name":"Boundary"},"slot.participant_id":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"member or leftover identity","spine_name":"Thing"},"slot.participant_scale":{"application_role":"scale-origin","catalog_module_id":"metapat.axiom.4.simplex","domain_name":"member source scale","spine_name":"Simplex"},"tree.joins":{"application_role":"authored-near-join","catalog_module_id":"metapat.axiom.6.relate","domain_name":"ordered authored adjacent-scale relations","spine_name":"Tensor"},"tree.legacy_bag":{"application_role":"domain-customs","catalog_module_id":"metapat.axiom.12.domain_qualification","domain_name":"non-structural legacy bag sidecar","spine_name":"Thing"},"tree.object_kind":{"application_role":"stored-join-term","catalog_module_id":"metapat.axiom.1.thing","domain_name":"multi-origin join-tree kind","spine_name":"Thing"},"tree.origins":{"application_role":"multi-origin-tensor","catalog_module_id":"metapat.axiom.5.tensor","domain_name":"S0-through-S6 origin tensor","spine_name":"Tensor"}},"semantic_license":{"application_digest":"461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b","application_id":"metapat.application.epac_join_terms","application_version":"epac-join-terms-application-v4","authority":"The-Interdependency/metapat","authority_transfer":false}},"evidence":{"declared_check_count":17,"declared_contract_count":17,"deterministic_regeneration_command":"python tools/generate_join_term_v0.py --check","molecular_shape_standing":"FALSIFIED (preserved; tested separately)","public_recovery_uses_constructor_state":false},"evidence_digest":"0a11bd666927926ab57fd0f5f7625034cbfa8670f4f1f3dc4c4c6f3905c2d638","evidence_epoch":"2026-09-30","historical_original_authoring_runtime":{"executable":"/usr/bin/python3.12","executable_sha256":"e50d468e8b0adfb05733f5b87b3cff34829c4a8c1aea50c865aa8bdfe4bb150f","implementation":"CPython","version":"3.12.3"},"hmmm":["Authored adjacency and its stored order do not establish state changes or actual occurrence order; Transformation and Time remain unlicensed for this candidate.","Whether a useful operation exists that requires secret EPAC state and cannot be efficiently reproduced from a legitimate public projection remains unestablished; this candidate is wholly public.","Whether exact lineage and seating replay improve an independently preregistered EPAC task remains unmeasured.","Whether any later UCNS correspondence is useful requires a separate license and test without rewriting UCNS law."],"inputs":{"identity_namespace":"fixture-a","input_is_public":true,"phase_chart_rule":"visible on even-numbered scales; lifted on odd-numbered scales","phase_values":[{"denominator":1,"numerator":0},{"denominator":8,"numerator":1},{"denominator":4,"numerator":1},{"denominator":8,"numerator":3},{"denominator":2,"numerator":1},{"denominator":8,"numerator":5},{"denominator":4,"numerator":3}],"private_input_supplied":false,"s2_seating_order":["core","guest","vacancy"]},"nonclaims":["Vector action, resulting state Transformation, or actual Time sequence","chemical or physical truth","molecular geometry or shape prediction","UCNS correspondence, topology, theorem status, or gonol identity","energy coupling, conservation, entropy, force, or field behavior","cryptographic secrecy, confidentiality, public-key separation, or computational hardness","PCEA compatibility","production suitability","release or protocol authority"],"results":{"authored_join_count":6,"candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","candidate_json_sha256":"821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd","classification":"PUBLIC_ONLY_RECOVERY_SURVIVED","hole_count":6,"leftover_count":6,"legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","legacy_projection":"slot-count bag","member_count":6,"native_operation":"exact authored origin-lineage and named-seating replay","origin_count":7,"s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"same_bag_alpha_renamed_identity_witness":{"bags_equal":true,"baseline_s6_lineage":["epac.origin:fixture-a:S0","epac.origin:fixture-a:S1","epac.origin:fixture-a:S2","epac.origin:fixture-a:S3","epac.origin:fixture-a:S4","epac.origin:fixture-a:S5","epac.origin:fixture-a:S6"],"conclusion":"the legacy bag does not select exact instance identity labels, while structural lineage up to alpha-renaming is unchanged","exact_instance_lineages_equal":false,"join_isomorphic":true,"renamed_candidate_digest":"7e30d6f4942e215462254dec9723a1deeb4e71d680e2cc960f227b02240f420d","renamed_identity_namespace":"fixture-b","renamed_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","renamed_s6_lineage":["epac.origin:fixture-b:S0","epac.origin:fixture-b:S1","epac.origin:fixture-b:S2","epac.origin:fixture-b:S3","epac.origin:fixture-b:S4","epac.origin:fixture-b:S5","epac.origin:fixture-b:S6"]},"same_bag_nonisomorphic_witness":{"bags_equal":true,"baseline_candidate_digest":"11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442","conclusion":"the legacy bag cannot uniquely recover authored seating or the complete join tree","join_isomorphic":false,"reordered_candidate_digest":"f74f9227a79acf84588b11d153bf66f9490d3aa18d8e899b6266c11ca3594633","reordered_legacy_bag_sha256":"ae6e48f3245310cefc233eb091a2cb872ac7ab0302296735d3a1b5fe9e2a2f9f","reordered_s2_seating_order":["guest","core","vacancy"]},"slot_count":18},"runtime_scope":"Deterministic evidence is runtime-independent; current execution identities belong to the exact-head artifact replay receipts, not this historical authoring record.","schema_id":"epac.join-term-v0-evidence","schema_version":"1.0.0","source_files_sha256":{"README.md":"7e28c7c092f7551b11a03c9db7862e27ec88ff6f81167ed3d3f99db26e29a18b","data/metapat-epac-join-terms-application-v4.json":"7f3ff74394b48c2162818fad8a373d1b88547b0df73beb8a604376022dcddf07","docs/PROVENANCE.md":"b410815a47ea9a8229406241616a9dc143ff09eb8da717456c019379efa6d5ee","docs/domain-claims.md":"f6f7f517e127a517a4acccba5527692239700c128b7bf0ecef9603e8c155c911","docs/multi-origin-join-term-v0.md":"6efb441f4421edf84c22556b46e438cc2da4f51536c522d3b6556af7b1c66a39","docs/research-status.md":"1f5194f0dc44521b6dc81d9a9494c72ff385fd879b8eb2b2849824640c5c2d07","docs/work-graph.json":"4edb37ace798511ff4b5f667afd1f0a3852ce78b7335349aaa9662171de0b9e9","docs/work-graphs/epac-join-term-v0.json":"7487441ec7e3ff3203635e245fccba5d293192cef5201c657946a15788166f16","epac_boundary_probe_completeness.py":"96e37f87e62c6346f8bd4f888e6151487636853d8c9309f199574b5c60da9600","epac_join_term.py":"4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af","pyproject.toml":"44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd","tests/test_boundary_probe_completeness.py":"8cd71e6f708949ce03a6750ac690a563c7cb236e9145d33e583539f0af46f93d","tests/test_epac_join_term.py":"ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8","tools/generate_join_term_v0.py":"1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331"},"source_manifest_sha256":"ad606b04310d1d04db2bb6b773b3e2d045bb0b10debc90e7bd568b3c2be54e22"} diff --git a/docs/boundary_probe_completeness.md b/docs/boundary_probe_completeness.md index 3647923..1a74237 100644 --- a/docs/boundary_probe_completeness.md +++ b/docs/boundary_probe_completeness.md @@ -35,13 +35,13 @@ construction, evidence, provenance and visualization modules. The inventory cove | item | count | |---|---:| -| declared operations classified | 136 | +| declared operations classified | 173 | | boundary-relevant operations | 58 | | omitted boundary-relevant operations | 14 | | omitted operations that distinguish same-B frozen states | 3 | -| ambiguous operations | 39 | +| ambiguous operations | 76 | -Thirty-nine public callable addresses, including the representation, probe-relativity, minimal-refinement and probe-completeness audits, have unresolved boundary relevance. Unknown names are not classified by substrings or treated as internal/non-boundary. The ledger records these uncertainties explicitly. Existing counterexamples still falsify completeness; unresolved operations do not erase that evidence. +Seventy-six public callable addresses, including the representation, probe-relativity, minimal-refinement, probe-completeness, join-term, and held-out validation surfaces, have unresolved boundary relevance. Each of the nine held-out operations has an explicit unresolved mapping reason: selection rules, prediction envelopes, oracle data, and comparison receipts have not been measured against the frozen 27-state construction or same-B discrimination. Integrity verification does not establish that mapping. Unknown names are not classified by substrings or treated as internal/non-boundary. The ledger records these uncertainties explicitly. Existing counterexamples still falsify completeness; unresolved operations do not erase that evidence. ## Partition Result diff --git a/docs/epac-join-term-v0-audit.md b/docs/epac-join-term-v0-audit.md index 33aa665..4d3b1aa 100644 --- a/docs/epac-join-term-v0-audit.md +++ b/docs/epac-join-term-v0-audit.md @@ -7,7 +7,7 @@ The public receipt recovers one complete typed S0-through-S6 join tree and its e ## Exact authority - EPAC base: `1e5c999286f12221eff9870d1372203ac7935f2a` (tree `ce7781938ff684d826bd91f475b5423bd56b146a`) -- EPAC feature source manifest: `bf8574518bf2be7d2b6e72fc8590f90b9ec2104415f88a55ba06420128d12834` +- EPAC feature source manifest: `ad606b04310d1d04db2bb6b773b3e2d045bb0b10debc90e7bd568b3c2be54e22` - METAPAT producer: `1cdfb09dd00a451cee30eec2e78624df8c682662` (tree `d946a5a18b0c53fd561dcd9d68fdf36d5dd11638`) - METAPAT application: `metapat.application.epac_join_terms@epac-join-terms-application-v4` - METAPAT application digest: `461ef7e059aa65b514017683ba9b57058ee9f582bc63748fabd021cdeb660b4b` @@ -20,7 +20,7 @@ The containing feature commit cannot be embedded in a file inside itself. The ev - Candidate digest: `11504eb61c4952fc3da04a51d3779484194292db2c8a5c3174b3c3ea540e6442` - Candidate JSON SHA-256: `821d35a1e42c8d22423f78fdce8449066af40392d47f3e79e5b30b8f993fcbfd` -- Evidence digest: `0e7188233a25dd675d58a1cf91350d6f7b0674bdcd1c922f131bb04fbf61870f` +- Evidence digest: `0a11bd666927926ab57fd0f5f7625034cbfa8670f4f1f3dc4c4c6f3905c2d638` - Origins: `7` - Authored joins (no Transformation or Time claim): `6` - Slots: `18` (`6` members, `6` holes, `6` leftovers) @@ -56,10 +56,10 @@ This is information loss. It is not a secrecy, computational-hardness, or confid - `docs/research-status.md`: `1f5194f0dc44521b6dc81d9a9494c72ff385fd879b8eb2b2849824640c5c2d07` - `docs/work-graph.json`: `4edb37ace798511ff4b5f667afd1f0a3852ce78b7335349aaa9662171de0b9e9` - `docs/work-graphs/epac-join-term-v0.json`: `7487441ec7e3ff3203635e245fccba5d293192cef5201c657946a15788166f16` -- `epac_boundary_probe_completeness.py`: `1bc8a3e990e99d52b9636eb1f656f3da0d6178c42de9dd86d811d9211e87f014` +- `epac_boundary_probe_completeness.py`: `96e37f87e62c6346f8bd4f888e6151487636853d8c9309f199574b5c60da9600` - `epac_join_term.py`: `4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af` - `pyproject.toml`: `44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd` -- `tests/test_boundary_probe_completeness.py`: `8c096277431fc59163b362a7e88c5c3dd515f66bab62deed81eff1be2dc4a641` +- `tests/test_boundary_probe_completeness.py`: `8cd71e6f708949ce03a6750ac690a563c7cb236e9145d33e583539f0af46f93d` - `tests/test_epac_join_term.py`: `ed060a6ef7903f178e3da6b94890f088de8b37dc7b94e6e41b4ccc15a3b7f5c8` - `tools/generate_join_term_v0.py`: `1836734e0ba692f23f604838ed3146c811528266a341979366eff6b19447e331` diff --git a/docs/heldout-chemistry-validation.md b/docs/heldout-chemistry-validation.md index ae171f1..39acbd8 100644 --- a/docs/heldout-chemistry-validation.md +++ b/docs/heldout-chemistry-validation.md @@ -2,6 +2,10 @@ Purpose: test EPAC against chemistry facts without feeding those facts into EPAC construction. +Runtime effects are limited to reading caller-supplied evidence and the packaged oracle and returning in-memory results. The module writes no files or network messages; callers control persistence as shown below. The source declares its runtime boundary, documentation, capabilities, dependency edges, and existing owner. + +The operation inventory retains all nine public held-out functions as `AMBIGUOUS`, with a separate mapping reason for each operation. Their relation to the frozen 27-state construction and ability to distinguish same-B states have not been measured. Validation integrity does not establish boundary non-relevance or transfer chemistry standing. + ## Boundary The selection boundary is committed before EPAC predictions are produced or revealed: @@ -36,7 +40,7 @@ Validation-plan fields are deliberately narrow so oracle-shaped data cannot cros Prediction and programmatic oracle values must use the JSON container/type model: `null`, booleans, numbers, strings, arrays/lists, and string-keyed objects/mappings. Python-only coercible values such as tuples are rejected before commitment or evidence hashing, including when nested. This keeps an in-memory commitment semantically identical to the same commitment after documented JSON persistence/reload. Non-finite Python floats remain admissible only so the comparator can classify them `UNRESOLVED`; they never produce a numeric success. -Numeric-tolerance comparison keeps integers as integers and uses exact rational arithmetic over accepted finite Python numeric values, so large integer distinctions are not collapsed by an intermediate float conversion. Non-finite operands or tolerances remain `UNRESOLVED`. +Numeric-tolerance comparison keeps integers as integers and uses exact rational arithmetic over accepted finite JSON decimal values, so large integer distinctions are not collapsed by an intermediate float conversion. Non-finite operands or tolerances remain `UNRESOLVED`. Provenance `authority` and `locator` must both be nonempty strings after trimming; blank or null identities are `UNRESOLVED`. All evidence loaders reject duplicate JSON object keys at every nesting level, including identical duplicates and escaped spellings of the same key. Reload plans with `load_validation_plan(path)` and commitments with `load_prediction_commitment(path, validation_plan=plan)`: these verify envelopes and digests, and the latter also verifies the plan binding and prediction-ID membership before returning. Missing predictions remain admissible and score `UNRESOLVED`; extra predictions are rejected. `verify_commitment()` alone checks only the envelope, not plan membership. Raw `json.loads` discards duplicate-key evidence and must not be used to reload externally supplied plans or commitments. diff --git a/epac_boundary_probe_completeness.py b/epac_boundary_probe_completeness.py index c041606..b587ef0 100644 --- a/epac_boundary_probe_completeness.py +++ b/epac_boundary_probe_completeness.py @@ -356,9 +356,22 @@ def _declared_operations() -> tuple[dict[str, str], ...]: return tuple(sorted(operations, key=lambda item: item["operation"])) +_HELDOUT_OPERATION_RELEVANCE = { + "freeze_validation_plan": (AMBIGUOUS, "hmmm: case selection and comparator rules have no measured mapping to the frozen 27-state construction"), + "freeze_predictions": (AMBIGUOUS, "hmmm: externally derived predictions have no measured mapping to the frozen 27-state construction"), + "verify_commitment": (AMBIGUOUS, "hmmm: envelope integrity does not measure the prediction mapping to the frozen 27-state construction"), + "compare_after_freeze": (AMBIGUOUS, "hmmm: comparison results have no measured same-B discrimination on the frozen 27-state construction"), + "load_validation_plan": (AMBIGUOUS, "hmmm: persisted selection rules have no measured mapping to the frozen 27-state construction"), + "load_prediction_commitment": (AMBIGUOUS, "hmmm: reloaded prediction evidence has no measured mapping to the frozen 27-state construction"), + "load_validation_receipt": (AMBIGUOUS, "hmmm: reloaded comparison evidence has no measured mapping to the frozen 27-state construction"), + "load_oracle": (AMBIGUOUS, "hmmm: external reference cases have no measured mapping to the frozen 27-state construction"), + "load_packaged_oracle": (AMBIGUOUS, "hmmm: packaged reference cases have no measured mapping to the frozen 27-state construction"), +} + + def _classify_operation(module: str, name: str) -> str: if module == "epac_heldout_validation": - return INTERNAL_NON_BOUNDARY + return _HELDOUT_OPERATION_RELEVANCE.get(name, (AMBIGUOUS, "hmmm: unclassified held-out operation"))[0] if module == "epac_join_term": # The join-tree candidate is not part of the frozen 27-state boundary # quotient surface. Its possible boundary relevance remains unresolved @@ -683,6 +696,10 @@ def declared_operation_ledger() -> tuple[OperationRecord, ...]: ), } ) + if raw["module"] == "epac_heldout_validation": + records[-1]["boundary_relevance_reason"] = _HELDOUT_OPERATION_RELEVANCE.get( + raw["name"], (AMBIGUOUS, "hmmm: unclassified held-out operation") + )[1] return tuple(records) diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index 131c595..b11e460 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -37,7 +37,7 @@ # auth_boundary: none # storage_boundary: read # network_boundary: none -# user_data_boundary: none +# user_data_boundary: read # admin_only: false # tests: tests.test_heldout_validation # rollout: opt-in research validation surface; callers persist the validation-plan commitment before prediction commitment and reveal independently sourced oracle values only during comparison @@ -46,6 +46,72 @@ # unresolved: external oracle authority selection and custody remain outside EPAC; this module binds supplied evidence but does not establish independent custody by itself # === END MODULE_BUILD === +# === BOUNDARIES === +# id: heldout_validation_runtime_boundary +# summary: reads caller-supplied validation evidence and the packaged oracle; returns detached in-memory evidence without persisting or transmitting it +# auth_boundary: none +# storage_boundary: read +# network_boundary: none +# user_data_boundary: read +# admin_only: false +# pii: possible +# owner: The Interdependency +# since: 2026-10-04 +# === END BOUNDARIES === + +# === DOCS === +# id: heldout_validation_public_guide +# summary: explains preregistration, prediction commitments, strict evidence reloads, comparison semantics, receipt preservation, and external custody limits +# audience: developer +# source: docs/heldout-chemistry-validation.md +# covers: freeze_validation_plan, freeze_predictions, verify_commitment, compare_after_freeze, load_validation_plan, load_prediction_commitment, load_validation_receipt, load_oracle, load_packaged_oracle +# examples: docs/heldout-chemistry-validation.md#runnable-end-to-end-example +# status: current +# owner: The Interdependency +# === END DOCS === + +# === CAPABILITIES === +# id: heldout_preregister_selection +# summary: freezes and reloads oracle-free case identities and comparator rules before predictions +# exposes: freeze_validation_plan, load_validation_plan +# inputs: case identities, domains, comparator rules, persisted plan path +# outputs: verified validation plan +# boundaries: auth:none, storage:read, network:none, user_data:read +# owner: The Interdependency +# +# id: heldout_commit_predictions +# summary: freezes and verifies prediction envelopes bound to a preregistered plan without loading oracle data +# exposes: freeze_predictions, verify_commitment, load_prediction_commitment +# inputs: predictions, source identity, validation plan, persisted commitment path +# outputs: prediction commitment, envelope validation +# boundaries: auth:none, storage:read, network:none, user_data:read +# owner: The Interdependency +# +# id: heldout_preserve_comparison_evidence +# summary: reveals comparison-side oracle evidence after freezing and preserves internally verifiable tri-state receipts +# exposes: compare_after_freeze, load_validation_receipt, load_oracle, load_packaged_oracle +# inputs: verified plan, prediction commitment, oracle, evidence paths +# outputs: oracle data, detached comparison receipt, internally verified receipt +# boundaries: auth:none, storage:read, network:none, user_data:read +# owner: The Interdependency +# === END CAPABILITIES === + +# === DEPENDENCIES === +# id: heldout_validation_dependency_edges +# summary: uses standard-library parsing, exact arithmetic, hashing, and file/package-resource reads; imports no EPAC construction or chemistry reference module +# imports: collections.abc, copy, decimal, fractions, hashlib, importlib.resources, json, math, pathlib, typing +# calls: importlib.resources.files, pathlib.Path.read_text +# class: runtime +# direction: outbound +# owner: The Interdependency +# === END DEPENDENCIES === + +# === OWNERS === +# id: heldout_validation_owner +# owner: The Interdependency +# since: 2026-10-04 +# === END OWNERS === + # === CONTRACTS === # id: heldout_selection_boundary_frozen_before_predictions # given: a validation plan and prediction commitment are supplied for held-out comparison diff --git a/tests/test_boundary_probe_completeness.py b/tests/test_boundary_probe_completeness.py index 486e82e..ad526b5 100644 --- a/tests/test_boundary_probe_completeness.py +++ b/tests/test_boundary_probe_completeness.py @@ -13,6 +13,12 @@ # mutates: none # cleanup: none # +# id: check_boundary_probe_inventory_covers_packaged_execution_modules +# proves: boundary_probe_audit_inventory_covers_declared_operations +# call: self::test_inventory_covers_packaged_execution_modules +# mutates: none +# cleanup: none +# # id: check_boundary_probe_audit_uses_no_new_probe_or_descriptor # proves: boundary_probe_audit_uses_no_new_probe_or_descriptor # call: self::test_audit_adds_only_existing_observables_and_does_not_extend_B @@ -63,7 +69,6 @@ from epac_boundary_probe_completeness import ( AMBIGUOUS, BOUNDARY_OBSERVING, - INTERNAL_NON_BOUNDARY, FALSIFIED, SURVIVED, UNRESOLVED, @@ -160,8 +165,15 @@ def test_inventory_covers_packaged_execution_modules(self) -> None: for name in heldout_exports: self.assertEqual( _installed_audit._classify_operation("epac_heldout_validation", name), - INTERNAL_NON_BOUNDARY, + AMBIGUOUS, ) + row = self._row_by_operation(self.report, "epac_heldout_validation." + name) + self.assertIn("hmmm", row["boundary_relevance_reason"]) + self.assertIn("frozen 27-state construction", row["boundary_relevance_reason"]) + self.assertIsNone(row["currently_probed"]) + self.assertIsNone(row["can_distinguish_same_B_states"]) + self.assertEqual(set(_installed_audit._HELDOUT_OPERATION_RELEVANCE), heldout_exports) + self.assertEqual(_installed_audit._classify_operation("epac_heldout_validation", "future_operation"), AMBIGUOUS) for name in ("AtomicRecord", "ElectronState", "atomic_record", "iter_table"): row = operations["epac_atomic." + name] self.assertEqual(_installed_audit._classify_operation(row["module"], row["name"]), AMBIGUOUS) @@ -170,7 +182,7 @@ def test_declared_operations_preserve_unresolved_semantics(self) -> None: inventory = self.report["operation_inventory"] self.assertEqual(inventory["operation_count"], 173) self.assertEqual(inventory["boundary_relevant_count"], 58) - self.assertEqual(inventory["ambiguous_count"], 67) + self.assertEqual(inventory["ambiguous_count"], 76) self.assertEqual(inventory["omitted_boundary_relevant_count"], 14) for row in self.report["operation_ledger"]: if row["name"] in self.report["unmapped_operation_probes"]: From 500c6e4a97402ae24c76bab8066578a173d370ce Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 4 Oct 2026 14:38:50 -0700 Subject: [PATCH 19/19] Declare the comparison-side packaged oracle resource dependency --- epac_heldout_validation.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/epac_heldout_validation.py b/epac_heldout_validation.py index b11e460..93636ea 100644 --- a/epac_heldout_validation.py +++ b/epac_heldout_validation.py @@ -98,12 +98,20 @@ # === DEPENDENCIES === # id: heldout_validation_dependency_edges -# summary: uses standard-library parsing, exact arithmetic, hashing, and file/package-resource reads; imports no EPAC construction or chemistry reference module +# summary: uses standard-library parsing, exact arithmetic, hashing, and file/package-resource reads without importing EPAC construction # imports: collections.abc, copy, decimal, fractions, hashlib, importlib.resources, json, math, pathlib, typing # calls: importlib.resources.files, pathlib.Path.read_text # class: runtime # direction: outbound # owner: The Interdependency +# +# id: heldout_packaged_oracle_dependency +# summary: the comparison-side packaged loader resolves the epac_data package and reads heldout_chemistry_oracle.json only when checkout data is absent +# imports: epac_data +# calls: importlib.resources.files +# class: runtime +# direction: outbound +# owner: The Interdependency # === END DEPENDENCIES === # === OWNERS ===