diff --git a/data/epac-join-term-v0-receipt.json b/data/epac-join-term-v0-receipt.json index 74230fa..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":"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":"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/data/heldout_chemistry_oracle.json b/data/heldout_chemistry_oracle.json new file mode 100644 index 0000000..a990e6e --- /dev/null +++ b/data/heldout_chemistry_oracle.json @@ -0,0 +1,22 @@ +{ + "schema": "epac.heldout-chemistry-oracle", + "version": "v1", + "policy": { + "role": "comparison-only", + "construction_access": "forbidden", + "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" + ], + "cases": [] +} 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 ba3ed21..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: `c3aad9a890e4ea42afcc8118a14c21b8ef10044ec1520a0d32f7daf668fbff0b` +- 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: `215844eda0b1b1f3edf95bf7b360df84deecc97382a88f777c150fa50e359839` +- 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`: `61df1624adc2970fe2e7d9d94ee15359decd42ddb7285ac223bd46eb748541ec` +- `epac_boundary_probe_completeness.py`: `96e37f87e62c6346f8bd4f888e6151487636853d8c9309f199574b5c60da9600` - `epac_join_term.py`: `4697da5c76d5dfc77aedc5c84c71b7175180550824b6a1a2c1bf23a8548839af` -- `pyproject.toml`: `414cdc71cdb2e0ff859fcb59a359d57a5327e6b105e0ff010a522216dcd3881f` -- `tests/test_boundary_probe_completeness.py`: `254590ac58a54979f0f05534cea357e2369577e96304dda0ae337f3114d13ac4` +- `pyproject.toml`: `44936ff75383ab25d3e295c6574b1759709fef7a072773d4c5774dd00243bcdd` +- `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 new file mode 100644 index 0000000..39acbd8 --- /dev/null +++ b/docs/heldout-chemistry-validation.md @@ -0,0 +1,167 @@ +# Held-out chemistry validation + +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: + +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 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. + +## Comparator and evidence input rules + +Validation-plan fields are deliberately narrow so oracle-shaped data cannot cross into the prediction side: + +- `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`; a supplied tolerance must be a number or `null`, so nested oracle fields, strings, and booleans cannot cross through that slot; +- 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 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. + +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. + +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. + +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 + +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, + load_prediction_commitment, + load_validation_receipt, + load_validation_plan, +) + +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 = load_validation_plan(root / "plan.json") + + # 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 = load_prediction_commitment( + root / "prediction.json", validation_plan=plan + ) + + # 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 = load_validation_receipt(root / "receipt.json") + + 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 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 30cba2b..b587ef0 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,7 +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 _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 @@ -680,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 new file mode 100644 index 0000000..93636ea --- /dev/null +++ b/epac_heldout_validation.py @@ -0,0 +1,805 @@ +"""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. +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. 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. Preserve and reload receipts +with load_validation_receipt(path) to verify their evidence and digest. +""" +from __future__ import annotations + +from collections.abc import Mapping +from copy import deepcopy +from decimal import Decimal +from fractions import Fraction +import hashlib +from importlib.resources import files +import json +import math +from pathlib import Path +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_validation_plan, load_prediction_commitment, load_validation_receipt, load_oracle, load_packaged_oracle +# 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 +# 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 +# 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 === + +# === 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 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 === +# 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 +# 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 +# +# 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 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 +# +# 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; non-finite numbers and reserved hmmm values anywhere in either evidence operand take precedence over all comparisons +# 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 not hmmm, 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 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 +# +# id: heldout_persisted_commitments_are_unambiguous +# given: a persisted validation plan or prediction commitment is loaded from a file +# 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 +# +# 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, and every numeric comparator uses the decimal value committed by that serialization +# class: evidence +# since: 2026-10-04 +# === END CONTRACTS === + +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: + 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: + return hashlib.sha256(_canonical(value)).hexdigest() + + +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 value.strip().casefold() == "hmmm": + raise ValueError(f"{field} must be resolved, not the hmmm sentinel") + return _validated_unicode_string(value, field=field) + + +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 _validated_unicode_string(value, field=field) + 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") + key = _validated_unicode_string(key, field=f"{field} object key") + 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 _required_nonempty_string(value, "domain") + + +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()) + and provenance[field].strip().casefold() != "hmmm" + 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") + 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: + 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: + tolerance = comparison["absolute_tolerance"] + if tolerance is not None and ( + isinstance(tolerance, bool) or not isinstance(tolerance, (int, float)) + ): + raise ValueError("absolute_tolerance must be a number or null") + normalized["absolute_tolerance"] = _normalize_json_value( + tolerance, + field="absolute_tolerance", + ) + return normalized + + +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": _normalized_domain(case.get("domain")), + "comparison": _normalized_comparison(case), + }) + normalized.sort(key=lambda case: case["id"]) + envelope = { + "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") + _normalized_domain(case.get("domain")) + if case_id in seen: + raise ValueError(f"duplicate validation plan case id: {case_id}") + seen.add(case_id) + 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") + + +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] = _normalize_json_value( + value, + field=f"prediction[{case_id}]", + ) + 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: + """Verify this envelope; plan membership is checked at load/comparison time.""" + required = { + "schema", + "version", + "source_identity", + "validation_plan_sha256", + "predictions", + "commitment_sha256", + } + if set(commitment) != required: + raise ValueError("invalid prediction commitment envelope") + if commitment["schema"] != _PREDICTION_SCHEMA or commitment["version"] != _VERSION: + raise ValueError("unsupported prediction commitment") + _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, 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") + } + if _digest(unsigned) != commitment["commitment_sha256"]: + raise ValueError("prediction commitment digest mismatch") + + +def _verify_prediction_plan_binding( + commitment: Mapping[str, Any], validation_plan: Mapping[str, Any] +) -> None: + _verify_validation_plan(validation_plan) + verify_commitment(commitment) + if commitment["validation_plan_sha256"] != validation_plan["plan_sha256"]: + raise ValueError("prediction commitment is bound to a different validation plan") + allowed_ids = {case["id"] for case in validation_plan["cases"]} + unexpected_ids = sorted(set(commitment["predictions"]) - allowed_ids) + if unexpected_ids: + raise ValueError( + "prediction commitment includes cases absent from frozen validation plan: " + + ", ".join(unexpected_ids) + ) + + +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_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: + 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 + if isinstance(left, float) and not math.isfinite(left): + return None + if isinstance(right, float) and not math.isfinite(right): + return None + 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): + 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) -> int | float | None: + if isinstance(value, bool) or not isinstance(value, (int, float)): + return None + if isinstance(value, float) and not math.isfinite(value): + return None + return value + + +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", _json_number_fraction(finite)) + 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 _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(_json_number_fraction(left) - _json_number_fraction(right)) <= _json_number_fraction(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) + if equal is None: + return "UNRESOLVED" + return "SURVIVED" if equal else "FALSIFIED" + if kind == "set-equality": + if not isinstance(predicted, list) or not isinstance(expected, list): + return "UNRESOLVED" + 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" + return "SURVIVED" if predicted_tokens == expected_tokens else "FALSIFIED" + if kind == "numeric-tolerance": + within = _numeric_within_tolerance( + expected, + predicted, + rule.get("absolute_tolerance"), + ) + if within is None: + return "UNRESOLVED" + return "SURVIVED" if within else "FALSIFIED" + return "UNRESOLVED" + + +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.""" + 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_prediction_plan_binding(commitment_snapshot, plan_snapshot) + if oracle_snapshot.get("schema") != _ORACLE_SCHEMA or oracle_snapshot.get("version") != _VERSION: + raise ValueError("unsupported held-out oracle") + cases = oracle_snapshot.get("cases") + if not isinstance(cases, list): + raise ValueError("oracle cases must be a list") + + oracle_by_id: dict[str, Mapping[str, Any]] = {} + for case in cases: + 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 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_snapshot["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}") + 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": plan_case["domain"], + "status": "UNRESOLVED", + "reason": "missing provenance identity", + }) + continue + if "expected" not in case: + results.append({ + "id": case_id, + "domain": plan_case["domain"], + "status": "UNRESOLVED", + "reason": "missing expected value", + "provenance": deepcopy(provenance), + }) + continue + if case_id not in predictions: + results.append({ + "id": case_id, + "domain": plan_case["domain"], + "status": "UNRESOLVED", + "reason": "no frozen prediction", + "expected": deepcopy(case["expected"]), + "provenance": deepcopy(provenance), + }) + continue + + status = _compare(case["expected"], predictions[case_id], plan_case["comparison"]) + results.append({ + "id": case_id, + "domain": plan_case["domain"], + "status": status, + "predicted": deepcopy(predictions[case_id]), + "expected": deepcopy(case["expected"]), + "comparison": deepcopy(plan_case["comparison"]), + "provenance": deepcopy(provenance), + }) + + counts = {status: sum(result["status"] == status for result in results) for status in STATUSES} + receipt = { + "schema": _RECEIPT_SCHEMA, + "version": _VERSION, + "validation_plan_sha256": plan_snapshot["plan_sha256"], + "prediction_commitment_sha256": commitment_snapshot["commitment_sha256"], + "oracle_sha256": _digest(oracle_snapshot), + "results": results, + "counts": counts, + } + 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 held-out evidence: {key}") + value[key] = item + 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, + parse_float=_parse_roundtrip_float, + ) + if not isinstance(value, dict): + raise ValueError(f"{document} document must be a JSON object") + return _normalize_json_value(value, field=document) + + +def load_validation_plan(path: str | Path) -> dict[str, Any]: + """Reload an unambiguous, verified plan before deriving predictions.""" + plan = _loads_evidence_json( + Path(path).read_text(encoding="utf-8"), document="validation plan" + ) + _verify_validation_plan(plan) + return plan + + +def load_prediction_commitment( + path: str | Path, *, validation_plan: Mapping[str, Any] +) -> dict[str, Any]: + """Reload unambiguous predictions bound to a verified plan, without an oracle.""" + commitment = _loads_evidence_json( + Path(path).read_text(encoding="utf-8"), document="prediction commitment" + ) + _verify_prediction_plan_binding(commitment, validation_plan) + 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") + + +def load_packaged_oracle() -> dict[str, Any]: + """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_evidence_json(resource.read_text(encoding="utf-8"), document="oracle") diff --git a/pyproject.toml b/pyproject.toml index 16b5933..9cb086e 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_join_term", "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_join_term", "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"} diff --git a/tests/test_boundary_probe_completeness.py b/tests/test_boundary_probe_completeness.py index 30eaa1c..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 @@ -137,15 +143,46 @@ 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", + "load_validation_plan", + "load_prediction_commitment", + "load_validation_receipt", + }, + ) + for name in heldout_exports: + self.assertEqual( + _installed_audit._classify_operation("epac_heldout_validation", name), + 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) def test_declared_operations_preserve_unresolved_semantics(self) -> None: inventory = self.report["operation_inventory"] - self.assertEqual(inventory["operation_count"], 164) + 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"]: diff --git a/tests/test_heldout_validation.py b/tests/test_heldout_validation.py new file mode 100644 index 0000000..5c53dfb --- /dev/null +++ b/tests/test_heldout_validation.py @@ -0,0 +1,1283 @@ +"""Usage: python -m pytest -q tests/test_heldout_validation.py. + +Synthetic evidence exercises validation integrity without assigning chemistry standing. +""" +from __future__ import annotations + +from copy import deepcopy +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, + load_prediction_commitment, + load_validation_receipt, + load_validation_plan, + verify_commitment, +) + + +# === 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_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_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 +# 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_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 +# 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: filesystem +# cleanup: tempdir_teardown +# +# id: check_persisted_commitment_loaders_reject_duplicate_keys_at_every_depth +# proves: heldout_persisted_commitments_are_unambiguous +# call: self::test_persisted_commitment_loaders_reject_duplicate_keys_at_every_depth +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_persisted_commitment_loaders_verify_envelopes_and_plan_binding +# proves: heldout_persisted_commitments_are_unambiguous, heldout_commitment_persistence_and_identity, heldout_selection_boundary_frozen_before_predictions +# call: self::test_persisted_commitment_loaders_verify_envelopes_and_plan_binding +# 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_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_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 +# 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"}}, + {"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(): + 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 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_plan_rejects_oracle_fields_hidden_in_comparator_rules(): + invalid_rules = [ + {"kind": "exact", "expected": 7}, + {"kind": "set-equality", "expected": [7]}, + {"kind": "numeric-tolerance", "absolute_tolerance": 0, "expected": 7}, + {"kind": "future-comparator", "provenance": {"authority": "oracle"}}, + {"kind": "exact", "absolute_tolerance": 7}, + ] + invalid_rules.extend( + {"kind": "numeric-tolerance", "absolute_tolerance": value} + for value in ({"expected": 7}, [{"provenance": "oracle"}], "expected=7", True) + ) + for comparison in invalid_rules: + error = "unexpected comparator fields|absolute_tolerance must be" + with pytest.raises(ValueError, match=error): + freeze_validation_plan([{"id": "x", "comparison": comparison}]) + + plan = freeze_validation_plan([{"id": "x"}]) + forged = deepcopy(plan) + forged["cases"][0]["comparison"] = comparison + unsigned = {k: forged[k] for k in ("schema", "version", "cases")} + forged["plan_sha256"] = _digest_envelope(unsigned) + with pytest.raises(ValueError, match=error): + freeze_predictions({"x": 1}, source_identity="epac@test", validation_plan=forged) + + commitment = freeze_predictions({"x": 1}, source_identity="epac@test", validation_plan=plan) + heldout = { + "schema": "epac.heldout-chemistry-oracle", "version": "v1", + "cases": [{"id": "x", "comparison": comparison, "expected": 1, + "provenance": {"authority": "fixture", "locator": "x"}}], + } + with pytest.raises(ValueError, match=error): + compare_after_freeze(commitment, heldout, validation_plan=plan) + + +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_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) + tampered["predictions"]["element:H:valence"] = 2 + with pytest.raises(ValueError, match="digest mismatch"): + compare_after_freeze(tampered, oracle(), validation_plan=plan) + + +def test_commitment_revalidates_nonempty_source_identity_even_with_matching_digest(): + 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(): + 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_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, + "element:O:oxidation": [-2, 2], + "isotope:H-1:mass": 1.0078251, + }) + receipt = compare_after_freeze(commitment, oracle(), validation_plan=plan) + assert receipt["counts"] == {"SURVIVED": 2, "FALSIFIED": 1, "UNRESOLVED": 0} + 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_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) + + +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), + ] + 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(): + 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" + 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(): + 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_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_persisted_commitment_loaders_reject_duplicate_keys_at_every_depth(): + plan = freeze_validation_plan([{"id": "x"}]) + commitment = freeze_predictions( + {"x": {"nested": [1]}}, source_identity="epac@test", 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)), + ): + encoded = json.dumps(envelope) + # Every root field, including cases/predictions and their digests: + # last-key-wins would restore the valid envelope in each attack. + attacks = [ + "{" + json.dumps(key) + ":null," + encoded[1:] + for key in envelope + ] + if envelope is plan: + attacks += [ + encoded.replace('"id": "x"', '"id":"other","id": "x"'), + encoded.replace('"kind": "exact"', '"kind":"other","kind": "exact"'), + encoded.replace('"kind": "exact"', '"k\\u0069nd":"other","kind": "exact"'), + ] + else: + attacks += [ + encoded.replace('"x": {', '"x":null,"x": {'), + encoded.replace('"nested": [1]', '"nested":[99],"nested": [1]'), + ] + for payload in attacks: + assert json.loads(payload) == envelope + path.write_text(payload, encoding="utf-8") + with pytest.raises(ValueError, match="duplicate JSON key"): + loader(path) + + +def test_persisted_commitment_loaders_verify_envelopes_and_plan_binding(): + plan, commitment = frozen({"element:H:valence": 1}) + with TemporaryDirectory() as tmp: + plan_path = Path(tmp) / "plan.json" + prediction_path = Path(tmp) / "predictions.json" + plan_path.write_text(json.dumps(plan), encoding="utf-8") + prediction_path.write_text(json.dumps(commitment), encoding="utf-8") + reloaded_plan = load_validation_plan(plan_path) + reloaded_commitment = load_prediction_commitment( + prediction_path, validation_plan=reloaded_plan + ) + assert reloaded_plan == plan + assert reloaded_commitment == commitment + before = compare_after_freeze(commitment, oracle(), validation_plan=plan) + after = compare_after_freeze(reloaded_commitment, oracle(), validation_plan=reloaded_plan) + assert before == after + assert after["counts"] == {"SURVIVED": 1, "FALSIFIED": 0, "UNRESOLVED": 2} + + for envelope, loader in ( + (plan, load_validation_plan), + (commitment, lambda path: load_prediction_commitment(path, validation_plan=plan)), + ): + path = Path(tmp) / "invalid.json" + for value in (None, [], 7, "not an object", {}): + path.write_text(json.dumps(value), encoding="utf-8") + with pytest.raises(ValueError): + loader(path) + for key in envelope: + forged = deepcopy(envelope) + forged[key] = "tampered" + path.write_text(json.dumps(forged), encoding="utf-8") + with pytest.raises(ValueError): + loader(path) + + other_plan = freeze_validation_plan([{"id": "other"}]) + with pytest.raises(ValueError, match="different validation plan"): + load_prediction_commitment(prediction_path, validation_plan=other_plan) + + forged = deepcopy(commitment) + forged["predictions"]["outside"] = 99 + 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="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({ + key: value for key, value in forged_plan.items() if key != "plan_sha256" + }) + plan_path.write_text(json.dumps(forged_plan), encoding="utf-8") + with pytest.raises(ValueError, match="unexpected comparator fields"): + load_validation_plan(plan_path) + with pytest.raises(ValueError, match="unexpected comparator fields"): + 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_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_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) + 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" + 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}} + ]) + 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 + / "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"] == [] + + +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}