diff --git a/crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.json b/crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.json index b8f1928b..91783e52 100644 --- a/crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.json +++ b/crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.json @@ -41,18 +41,18 @@ "sha256": "57f3eb04c91a3562d40523a601d435caf1181dd0a24df0d9162100c13a7c5262" }, { - "bytes": 45142, + "bytes": 7686, "license": "MIT", "path": "crates/labcolors-core/src/joint.rs", "role": "joint_selection_source", - "sha256": "5add7d50fbaacd4849552f81461d6c7c584b985be0f4485112c4b5705dee991a" + "sha256": "8bf5d5a1c0f00ce245a1ecb18b923aa1631483345962d72b633e466c242a8a1d" }, { - "bytes": 13873, + "bytes": 13680, "license": "MIT", "path": "crates/labcolors-core/src/lib.rs", "role": "module_registration_source", - "sha256": "b30300edd3910e3d9da1e56896ce14c3db508b1a6fc5608e01032a5ad95777b1" + "sha256": "db3be029d1ee35471f8c311ae4ec59babd06fcca26f8a1dcd381ef28bc937690" }, { "bytes": 38255, @@ -90,11 +90,11 @@ "sha256": "2c0876c61196f6492d0f250789ded9644bad1de9ff92c9dea2a14fc55e87f18e" }, { - "bytes": 165362, + "bytes": 164302, "license": "MIT", "path": "crates/labcolors-core/src/program.rs", "role": "program_facade_source", - "sha256": "7067a2108a7fe224302ca7e2b6effdff97966d63328ce5e0b93ca0bcd3a9fda3" + "sha256": "a2e3f0cf8fd5445a474c804bbd962d244d9eb42ce2c7ca099ac8c0793de6563e" }, { "bytes": 71867, @@ -104,11 +104,11 @@ "sha256": "b59f1f5fe0c71637471498635240de6a381c6b8f11308424e81c0f3e0c6b12d0" }, { - "bytes": 170258, + "bytes": 174358, "license": "MIT", "path": "crates/labcolors-core/src/program_session.rs", "role": "program_source", - "sha256": "86a035d09bfee36cac9d5761254b21803a41f6f75b2072c1a5df11967ea006de" + "sha256": "13306b6a817c5509eba31550a1358c5a1d6deaae9d4344ff915d98738f8fd12f" }, { "bytes": 21872, @@ -125,11 +125,11 @@ "sha256": "aa6aa7c0b630437f1c1ba8c2ceafb0dadf6551c42331559504076a6cd44e6331" }, { - "bytes": 26928, + "bytes": 26829, "license": "MIT", "path": "crates/labcolors-core/src/session.rs", "role": "session_runtime_source", - "sha256": "c7a1706ad4837adfdfbe58a302d20b613912530ebff260fd877a47210b02ef46" + "sha256": "383b163f9e715e3f5b313a3c29f962968dec3df2414cc8172e2def6b43236a83" }, { "bytes": 34105, diff --git a/crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.sha256 b/crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.sha256 index 18a3f2a8..36d81e31 100644 --- a/crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.sha256 +++ b/crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.sha256 @@ -1 +1 @@ -3b51f8ecf38181d68b619b30ebb1fdd28a17606f5ac2699bf8070497a0515b80 receipt-v1.json +269e85bd887ddc6a09e86d183338ba5b26a9dc506762429eee7a65f981c6d31f receipt-v1.json diff --git a/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json b/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json index 5ae15e3c..472d8a66 100644 --- a/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json +++ b/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json @@ -1 +1 @@ -{"artifact_id":"wcag22-srgb8-luminance-q55-v1","basis_point_proof":{"checks":30,"drop_all_semantics":"zero required surplus; current must still meet the anchor","drop_domain_inclusive":[0,10000],"nonpositive_baseline_semantics":"zero required surplus; current must meet the anchor"},"bound_id":"point-support-reference-surplus-q55-bps-v1","certified_claim":"for every successfully evaluated enabled stability cell, decision is Retained iff current_lower_surplus >= (10000-drop_bps)/10000 * max(baseline_lower_surplus,0); the declared anchor remains a separate hard floor","comparator_proof":{"algorithm":"euclidean-continued-fraction-ordering-v1","dense_denominator_inclusive":[1,31],"dense_numerator_inclusive":[0,31],"dense_small_cases":984064,"invariant":"equal integer parts; reciprocal proper fractions reverse order","largest_fibonacci_index":186,"oracle":"unbounded-integer-cross-product","random_cases":250000,"random_corpus_sha256":"97c4af7b452b31a4ab92645f70c17acb38bf57ca55484e32ad9d7d79d97a333d","random_seed":210583930,"termination":"each nonterminal denominator becomes a strictly smaller remainder","u128_adversarial_cases":190},"declared_operation_law":"q55-lower-reference-distance-explicit-anchor-bps-retention-v1","excluded_claim":"does not certify retention against the unknown exact baseline surplus, renderer equivalence outside encoded-sRGB8 source-over, or a successful result when evaluation fails","integer_replay_envelope":{"assumption":"every Q55 luminance upper <= scale + 3","i128_max":170141183460469231731687303715884105727,"offset_cleared_denominator_max":756604737398243388,"positive_baseline_numerator_max":1188950301625811064,"rational_denominator_max":1513209474796486776,"required_denominator_max":15132094747964867760000,"required_numerator_max":11889503016258110640000,"signed_anchor_abs_coarse_max":5296233161787703716,"u128_max":340282366920938463463374607431768211455,"u64_max":18446744073709551615},"profile_id":"srgb8-q55-retained-reference-surplus-bps-v1","proof_id":"point-support-reference-surplus-integer-v1","proof_payload_sha256":"5452b758c6b38634e13786164109205d002a4ecc51965b823a5a7354e5ea46eb","q55_dependency":{"artifact_id":"wcag22-srgb8-luminance-q55-v1","artifact_sha256":"7ff239d9052b346f3c50da01ca65ca2330892ed1a3ff30e190797fcef6f03604","maximum_luminance_upper":36028797018963971,"outward_interval_width_bound":3,"proof_id":"wcag22-srgb8-full-domain-q55-v1","proof_payload_sha256":"3c639a7c875046c46b56b51ecdd67d5ecaf14a1134490c88a222e7037b63c0f2","proof_sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd","q55_scale":36028797018963968},"reference_and_anchor_proof":{"anchor_identity_checks":75,"orientation_law":"distance-magnitude-symmetric-orientation-reported-separately","overlap_lower_distance":"0/1","separated_endpoint_checks":504},"schema_version":2,"site_id":"point-support-retained-reference-surplus-v1","source_binding_exclusions":["whole-crate compilation or compiler/toolchain attestation","binary, package, FFI, renderer, or browser transport attestation","unrelated Lab Colors modules outside the declared point-support semantic cone"],"source_binding_law":"point-support-rust-whole-file-semantic-cone-v2","source_binding_schema_version":2,"source_binding_scope":"exact bytes of the private point-support Rust semantic cone and its two WCAG include_str inputs; comments and cfg(test) text are intentionally significant","source_closure_sha256":"de9e26c5da5d24cd1a76092c5e5caea02fe69f1f18d367fbf0ea7635bee819a9","source_files":[{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-q55-proof-v1.json","sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"},{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-v1.json","sha256":"b4bb7e5f17a99f2c911fdbe3da23a48b049277b796291094950f14680cc3cc7b"},{"kind":"rust-source","path":"crates/labcolors-core/src/appearance.rs","sha256":"d31c6a7d3b0a4c02532759befaef442e3a99687263a9027771365638b6cc3324"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"195a67327a3bd86d7816b634481389930bf68577bb1202fad14c2ea152df8625"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/exact.rs","sha256":"892576a8621185352583e63dc0a1aacac32e32a8063b6fe24ae16d4ff9dce7cb"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/mod.rs","sha256":"aba84c05a203af12ef2e445334409d9bd385a854c9058f0e30bbf8542addddbc"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/wcag22.rs","sha256":"856093c91159d8b3faab001f2d6524d33d7b16458a5a4e98ea65f8c62ab2694c"},{"kind":"rust-source","path":"crates/labcolors-core/src/hash.rs","sha256":"f97a0fd7d6ad3162f0f1dfb326fccfb7ed40da9a8fa67a5b8a239a1ae2ae49c3"},{"kind":"rust-source","path":"crates/labcolors-core/src/lcs_occurrence.rs","sha256":"78d37406e9bdc37f126b72987c9c92b452c13b3233c0aeb0a75ed25dadb83a68"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"b30300edd3910e3d9da1e56896ce14c3db508b1a6fc5608e01032a5ad95777b1"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"d31f5d5f95fbb2300c90899a26905c3cd2804be7c0e9290999005fd9e2c8f6d6"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"6f6a376ff036d3d65960c004e6566e1bca580f19f5bd3cd333a80b0da5b5c242"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"c7a1706ad4837adfdfbe58a302d20b613912530ebff260fd877a47210b02ef46"},{"kind":"rust-source","path":"crates/labcolors-core/src/srgb8.rs","sha256":"6c95324eb05476f35f75375a9af0b2b4a41b8b2978c46e67d2ce1aea5adde342"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22.rs","sha256":"7ba7864eb7e73789bad6c63c64a4dc2dcc08c2da6921375fb9564fca230c2780"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/kernel.rs","sha256":"c97980c1ca2c7ea9cabff9c8d2fb7282773cca180ae15948391c29c9d6196040"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/q55_data.rs","sha256":"af4d23d6b70c45ce6efa839e7dda4bb0a61f6aae43cb805af6fa9b29e6c3bae2"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22_evidence.rs","sha256":"3c5a75b07254c6071a64700af208a64987d0f0ea9698eadc54a9e74585ce1f72"}],"source_negative_controls":43,"universal_algebraic_certificate":{"basis_point_scale_instantiation":10000,"domain":"integers; Q55 scale Q>0; anchor L>=D>=0; lighter monotonicity L2>=L1>D>=0; darker monotonicity L>D2>=D1>=0; current/baseline denominators b,q>0; basis-point scale B>0 instantiated as 10000; p>0; a>=0; 0<=drop_bps<=B","identities":["three explicit anchor-surplus formulas after denominator clearing","reference distance is monotone increasing in lighter L","reference distance is monotone decreasing in darker D","positive-baseline retained threshold is p*(B-drop)/(q*B)","a/b >= p*(B-drop)/(q*B) iff a*q*B >= p*(B-drop)*b"],"method":"exact-sparse-integer-polynomial-identities-plus-positive-denominator-order-lemma-v1","nonpositive_baseline_case":"max(baseline,0)=0; retained threshold is exactly zero","symbolic_mutation_controls":{"anchor_coefficients_and_denominator":6,"retained_cross_product":5},"wolfram_language_cross_check":{"query":"FullSimplify[{20 g/d - 0 == 20 g/d, 20 g/d - 2 == (20 g - 2 d)/d, 20 g/d - 7/2 == (40 g - 7 d)/(2 d), Equivalent[a/b >= p (s-x)/(q s), a q s >= p (s-x) b], Max[p/q, 0] (s-x)/s == Piecewise[{{0, p <= 0}}, p (s-x)/(q s)]}, Assumptions -> Element[{a,b,p,q,s,x,g,d}, Integers] && a >= 0 && b > 0 && q > 0 && s > 0 && 0 <= x <= s && d > 0 && g >= 0]","query_sha256":"8cdbb9964583030c8b92498961896cb2a98613f1cb31eb7c54acdf8e16beff10","result":"{True, True, True, True, True}","result_sha256":"13a8f2ee8d0fde335a638e46d7cc8a8427b9a1437c77d22cfcf925bb87fa6303"}},"verifier_sha256":"5bb6f2b206d0504d3325ce2cd666a2efafe5f05a6db339549b87c8bf4199543d"} +{"artifact_id":"wcag22-srgb8-luminance-q55-v1","basis_point_proof":{"checks":30,"drop_all_semantics":"zero required surplus; current must still meet the anchor","drop_domain_inclusive":[0,10000],"nonpositive_baseline_semantics":"zero required surplus; current must meet the anchor"},"bound_id":"point-support-reference-surplus-q55-bps-v1","certified_claim":"for every successfully evaluated enabled stability cell, decision is Retained iff current_lower_surplus >= (10000-drop_bps)/10000 * max(baseline_lower_surplus,0); the declared anchor remains a separate hard floor","comparator_proof":{"algorithm":"euclidean-continued-fraction-ordering-v1","dense_denominator_inclusive":[1,31],"dense_numerator_inclusive":[0,31],"dense_small_cases":984064,"invariant":"equal integer parts; reciprocal proper fractions reverse order","largest_fibonacci_index":186,"oracle":"unbounded-integer-cross-product","random_cases":250000,"random_corpus_sha256":"97c4af7b452b31a4ab92645f70c17acb38bf57ca55484e32ad9d7d79d97a333d","random_seed":210583930,"termination":"each nonterminal denominator becomes a strictly smaller remainder","u128_adversarial_cases":190},"declared_operation_law":"q55-lower-reference-distance-explicit-anchor-bps-retention-v1","excluded_claim":"does not certify retention against the unknown exact baseline surplus, renderer equivalence outside encoded-sRGB8 source-over, or a successful result when evaluation fails","integer_replay_envelope":{"assumption":"every Q55 luminance upper <= scale + 3","i128_max":170141183460469231731687303715884105727,"offset_cleared_denominator_max":756604737398243388,"positive_baseline_numerator_max":1188950301625811064,"rational_denominator_max":1513209474796486776,"required_denominator_max":15132094747964867760000,"required_numerator_max":11889503016258110640000,"signed_anchor_abs_coarse_max":5296233161787703716,"u128_max":340282366920938463463374607431768211455,"u64_max":18446744073709551615},"profile_id":"srgb8-q55-retained-reference-surplus-bps-v1","proof_id":"point-support-reference-surplus-integer-v1","proof_payload_sha256":"bcc505425f92927a4587fcd70525cf6cb6d86537e321de87c41e3735dcbd27c1","q55_dependency":{"artifact_id":"wcag22-srgb8-luminance-q55-v1","artifact_sha256":"7ff239d9052b346f3c50da01ca65ca2330892ed1a3ff30e190797fcef6f03604","maximum_luminance_upper":36028797018963971,"outward_interval_width_bound":3,"proof_id":"wcag22-srgb8-full-domain-q55-v1","proof_payload_sha256":"3c639a7c875046c46b56b51ecdd67d5ecaf14a1134490c88a222e7037b63c0f2","proof_sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd","q55_scale":36028797018963968},"reference_and_anchor_proof":{"anchor_identity_checks":75,"orientation_law":"distance-magnitude-symmetric-orientation-reported-separately","overlap_lower_distance":"0/1","separated_endpoint_checks":504},"schema_version":2,"site_id":"point-support-retained-reference-surplus-v1","source_binding_exclusions":["whole-crate compilation or compiler/toolchain attestation","binary, package, FFI, renderer, or browser transport attestation","unrelated Lab Colors modules outside the declared point-support semantic cone"],"source_binding_law":"point-support-rust-whole-file-semantic-cone-v2","source_binding_schema_version":2,"source_binding_scope":"exact bytes of the private point-support Rust semantic cone and its two WCAG include_str inputs; comments and cfg(test) text are intentionally significant","source_closure_sha256":"ad5f8cf281a796533e4784c827193fd5aa723a1161facdbfa6c48308099b4ba0","source_files":[{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-q55-proof-v1.json","sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"},{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-v1.json","sha256":"b4bb7e5f17a99f2c911fdbe3da23a48b049277b796291094950f14680cc3cc7b"},{"kind":"rust-source","path":"crates/labcolors-core/src/appearance.rs","sha256":"d31c6a7d3b0a4c02532759befaef442e3a99687263a9027771365638b6cc3324"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"195a67327a3bd86d7816b634481389930bf68577bb1202fad14c2ea152df8625"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/exact.rs","sha256":"892576a8621185352583e63dc0a1aacac32e32a8063b6fe24ae16d4ff9dce7cb"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/mod.rs","sha256":"aba84c05a203af12ef2e445334409d9bd385a854c9058f0e30bbf8542addddbc"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/wcag22.rs","sha256":"856093c91159d8b3faab001f2d6524d33d7b16458a5a4e98ea65f8c62ab2694c"},{"kind":"rust-source","path":"crates/labcolors-core/src/hash.rs","sha256":"f97a0fd7d6ad3162f0f1dfb326fccfb7ed40da9a8fa67a5b8a239a1ae2ae49c3"},{"kind":"rust-source","path":"crates/labcolors-core/src/lcs_occurrence.rs","sha256":"78d37406e9bdc37f126b72987c9c92b452c13b3233c0aeb0a75ed25dadb83a68"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"db3be029d1ee35471f8c311ae4ec59babd06fcca26f8a1dcd381ef28bc937690"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"d31f5d5f95fbb2300c90899a26905c3cd2804be7c0e9290999005fd9e2c8f6d6"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"6f6a376ff036d3d65960c004e6566e1bca580f19f5bd3cd333a80b0da5b5c242"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"383b163f9e715e3f5b313a3c29f962968dec3df2414cc8172e2def6b43236a83"},{"kind":"rust-source","path":"crates/labcolors-core/src/srgb8.rs","sha256":"6c95324eb05476f35f75375a9af0b2b4a41b8b2978c46e67d2ce1aea5adde342"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22.rs","sha256":"7ba7864eb7e73789bad6c63c64a4dc2dcc08c2da6921375fb9564fca230c2780"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/kernel.rs","sha256":"c97980c1ca2c7ea9cabff9c8d2fb7282773cca180ae15948391c29c9d6196040"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/q55_data.rs","sha256":"af4d23d6b70c45ce6efa839e7dda4bb0a61f6aae43cb805af6fa9b29e6c3bae2"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22_evidence.rs","sha256":"3c5a75b07254c6071a64700af208a64987d0f0ea9698eadc54a9e74585ce1f72"}],"source_negative_controls":43,"universal_algebraic_certificate":{"basis_point_scale_instantiation":10000,"domain":"integers; Q55 scale Q>0; anchor L>=D>=0; lighter monotonicity L2>=L1>D>=0; darker monotonicity L>D2>=D1>=0; current/baseline denominators b,q>0; basis-point scale B>0 instantiated as 10000; p>0; a>=0; 0<=drop_bps<=B","identities":["three explicit anchor-surplus formulas after denominator clearing","reference distance is monotone increasing in lighter L","reference distance is monotone decreasing in darker D","positive-baseline retained threshold is p*(B-drop)/(q*B)","a/b >= p*(B-drop)/(q*B) iff a*q*B >= p*(B-drop)*b"],"method":"exact-sparse-integer-polynomial-identities-plus-positive-denominator-order-lemma-v1","nonpositive_baseline_case":"max(baseline,0)=0; retained threshold is exactly zero","symbolic_mutation_controls":{"anchor_coefficients_and_denominator":6,"retained_cross_product":5},"wolfram_language_cross_check":{"query":"FullSimplify[{20 g/d - 0 == 20 g/d, 20 g/d - 2 == (20 g - 2 d)/d, 20 g/d - 7/2 == (40 g - 7 d)/(2 d), Equivalent[a/b >= p (s-x)/(q s), a q s >= p (s-x) b], Max[p/q, 0] (s-x)/s == Piecewise[{{0, p <= 0}}, p (s-x)/(q s)]}, Assumptions -> Element[{a,b,p,q,s,x,g,d}, Integers] && a >= 0 && b > 0 && q > 0 && s > 0 && 0 <= x <= s && d > 0 && g >= 0]","query_sha256":"8cdbb9964583030c8b92498961896cb2a98613f1cb31eb7c54acdf8e16beff10","result":"{True, True, True, True, True}","result_sha256":"13a8f2ee8d0fde335a638e46d7cc8a8427b9a1437c77d22cfcf925bb87fa6303"}},"verifier_sha256":"3c6fca559708dfdd82ed102b376aa0dcc57cbef6e39f6e21b8e4da81b05ffcb6"} diff --git a/crates/labcolors-core/src/generic_boundary_tests.rs b/crates/labcolors-core/src/generic_boundary_tests.rs index 5a24ea5c..59425d08 100644 --- a/crates/labcolors-core/src/generic_boundary_tests.rs +++ b/crates/labcolors-core/src/generic_boundary_tests.rs @@ -421,6 +421,92 @@ fn finite_target_intent_has_no_dead_source_axis() { } } +#[test] +fn joint_module_contains_only_the_canonical_finite_order_admission() { + for retired in [ + "CandidateOrdinalV1", + "JointPointEvaluatorV1", + "JointCandidateTupleV1", + "JointCandidateSetV1", + "JointObservationV1", + "StaticJointObservationV1", + "PointwiseJointPointProgramV1", + "checked_joint_cardinality", + "PointwiseFullHardReportV1", + "PointwiseHardFeasibilityV1", + "DeclaredTotalOrderV1", + "PointwiseSelectedJointTupleV1", + "PointwiseVerifiedSelectionV1", + ] { + assert!( + !contains_rust_identifier(JOINT_SOURCE, retired), + "the runtime-unused V2a solver must not return through `{retired}`", + ); + } + for canonical in [ + "FiniteDomainOrdinalV1", + "NonEmptyFiniteDomainCardinalitiesV1", + "AdmittedFiniteJointOrderV1", + "FiniteJointOrderAdmissionErrorV1", + "FiniteJointOrderErrorV1", + "admit_finite_joint_order_v1", + ] { + assert!( + contains_rust_identifier(JOINT_SOURCE, canonical), + "joint.rs must retain the sole Program order-admission primitive `{canonical}`", + ); + } + assert!( + !LIB_SOURCE.contains( + "joint-selection internals are used only through the staged Program contract", + ), + "the canonical Program path must not hide a second joint engine behind dead_code", + ); + assert!( + !LIB_SOURCE.contains("mod joint_tests;"), + "tests for the retired solver must not keep its architecture alive", + ); + assert!( + !contains_rust_identifier(JOINT_SOURCE, "EmptyDomain") + && !contains_rust_identifier(PROGRAM_SOURCE, "EmptyDomain"), + "a finite domain is admitted as non-empty before joint-order admission", + ); + assert!( + !JOINT_SOURCE.contains("unreachable!") && !JOINT_SOURCE.contains("panic!"), + "order admission must type internal drift instead of exposing a panic route", + ); + for required in [ + "AdmittedCompiledJointSpaceV1", + "AdmittedCompiledJointStateV1", + "CompiledTargetSelectionV1", + ] { + assert!( + contains_rust_identifier(PROGRAM_SESSION_SOURCE, required), + "the sealed joint space must stay reachable through `{required}`", + ); + } + // Exact snippets intentionally make representation drift loud; a rustfmt + // rewrite must update this anti-regrowth gate in the same reviewed change. + assert!( + PROGRAM_SESSION_SOURCE.contains("Finite(AdmittedCompiledJointSpaceV1)"), + "target selection must carry the admitted space itself", + ); + for retired in [ + "joint_selection: Option", + "finite_targets: Box<[CompiledFiniteTargetV1]>", + "Finite { targets, order }", + ] { + assert!( + !PROGRAM_SESSION_SOURCE.contains(retired), + "the epoch must not resurrect the split joint runtime through `{retired}`", + ); + } + assert!( + !contains_rust_identifier(PROGRAM_SESSION_SOURCE, "CompiledJointSelectionV1"), + "runtime must receive only a sealed joint space derived from its compiled targets", + ); +} + #[test] fn staged_session_is_evidence_only_and_retired_operation_authority_cannot_return() { assert_eq!( diff --git a/crates/labcolors-core/src/joint.rs b/crates/labcolors-core/src/joint.rs index 64689f67..0e22cd86 100644 --- a/crates/labcolors-core/src/joint.rs +++ b/crates/labcolors-core/src/joint.rs @@ -1,29 +1,15 @@ -//! Приватный V2a-срез совместного point-selection. +//! Допуск полного порядка над конечным произведением target-доменов. //! -//! Один code-owned program связывает две Paint-переменные через реальный -//! `lower occurrence -> visible surface -> upper occurrence`. Candidate domain, -//! полный hard-report, declared policy и fresh recheck являются разными типами. -//! Модуль не знает клиентских recipes, role taxonomy или evaluator families и -//! не минтит terminal output certificate. +//! Модуль не исполняет solver и не знает evaluator families. Он лишь проверяет +//! объявленную клиентом политику порядка один раз до runtime и запечатывает +//! канонические ординалы для единственного исполнителя [`crate::program_session`]. -use core::fmt::Debug; - -use crate::Srgb8; -use crate::appearance::{ - EncodedPointPaintV1, ModeledSrgb8PointOccurrence, PaintId, PointOpacityOverSurfaceV1, - ResolvedOccurrence, SurfaceInputPortId, -}; -use crate::constraints::{ - Evaluator, ExactSrgb8IdentityV1, HardClassifier, HardDecision, PointInvocation, - PointMeasurement, VisiblePointPassEvidence, VisiblePointViolationEvidence, - assess_visible_point_hard, -}; -use crate::observation::{RevisionBoundObservationV1, ScenarioId}; +use core::num::NonZeroUsize; /// One canonical candidate ordinal inside a finite target domain. /// /// The ordinal is assigned only after the owning Program has sorted the -/// target's opaque candidate IDs. It is therefore an internal compiled index, +/// target's opaque candidate IDs. It is therefore an internal compiled index, /// never client identity or declaration-order policy. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct FiniteDomainOrdinalV1(usize); @@ -38,10 +24,33 @@ impl FiniteDomainOrdinalV1 { } } -/// A fully admitted total order over the product of finite target -/// domains. Each tuple is stored in canonical target order. The tuple order -/// is authored policy; no target ID, candidate value, or declaration position -/// becomes an implicit tie-break. +/// Non-empty cardinality vector of the finite targets that own one joint +/// product. Requiring the first dimension here prevents a mathematically valid +/// zero-dimensional product from impersonating a Program joint selection. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct NonEmptyFiniteDomainCardinalitiesV1 { + first: NonZeroUsize, + rest: Box<[NonZeroUsize]>, +} + +impl NonEmptyFiniteDomainCardinalitiesV1 { + pub(crate) fn new(first: NonZeroUsize, rest: Box<[NonZeroUsize]>) -> Self { + Self { first, rest } + } + + fn iter(&self) -> impl Iterator + '_ { + std::iter::once(self.first).chain(self.rest.iter().copied()) + } + + fn len(&self) -> usize { + self.rest.len() + 1 + } +} + +/// A fully admitted total order over the product of finite target domains. +/// Each tuple is stored in canonical target order. The tuple order is authored +/// policy; no target ID, candidate value, or declaration position becomes an +/// implicit tie-break. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct AdmittedFiniteJointOrderV1 { first: Box<[FiniteDomainOrdinalV1]>, @@ -63,9 +72,6 @@ impl AdmittedFiniteJointOrderV1 { /// Failure to admit a declared finite joint selection order. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum FiniteJointOrderErrorV1 { - EmptyDomain { - dimension: usize, - }, CardinalityOverflow, EmptyOrder, TupleArity { @@ -87,81 +93,105 @@ pub(crate) enum FiniteJointOrderErrorV1 { expected: usize, actual: usize, }, +} + +/// Admission separates invalid authored policy from an inability to allocate +/// the proof table. Resource pressure is not evidence that client data is +/// malformed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum FiniteJointOrderAdmissionErrorV1 { + Authored(FiniteJointOrderErrorV1), ResourceExhausted, + InternalInvariant, } /// Check and seal an explicit total order over a finite product domain. /// -/// This function deliberately does not synthesize lexicographic policy. A -/// caller must enumerate every tuple exactly once. Checked multiplication and -/// fallible allocation happen before the result can reach runtime. +/// This function deliberately does not synthesize lexicographic policy. A +/// caller must enumerate every tuple exactly once. Non-empty dimensions are +/// admitted by type; checked multiplication and fallible allocation happen +/// before the result can reach runtime. pub(crate) fn admit_finite_joint_order_v1( - domain_lengths: &[usize], + domain_lengths: &NonEmptyFiniteDomainCardinalitiesV1, authored: Vec>, -) -> Result { +) -> Result { + use FiniteJointOrderAdmissionErrorV1 as AdmissionError; + let mut expected = 1usize; - for (dimension, &domain_len) in domain_lengths.iter().enumerate() { - if domain_len == 0 { - return Err(FiniteJointOrderErrorV1::EmptyDomain { dimension }); - } + for domain_len in domain_lengths.iter() { expected = expected - .checked_mul(domain_len) - .ok_or(FiniteJointOrderErrorV1::CardinalityOverflow)?; + .checked_mul(domain_len.get()) + .ok_or(AdmissionError::Authored( + FiniteJointOrderErrorV1::CardinalityOverflow, + ))?; } if authored.is_empty() { - return Err(FiniteJointOrderErrorV1::EmptyOrder); + return Err(AdmissionError::Authored( + FiniteJointOrderErrorV1::EmptyOrder, + )); } if authored.len() != expected { - return Err(FiniteJointOrderErrorV1::IncompleteOrder { - expected, - actual: authored.len(), - }); + return Err(AdmissionError::Authored( + FiniteJointOrderErrorV1::IncompleteOrder { + expected, + actual: authored.len(), + }, + )); } - // The mixed-radix ordinal is a bijection over the admitted product. A + // The mixed-radix ordinal is a bijection over the admitted product. A // fallibly allocated first-seen table makes duplicate admission O(states × // dimensions), rather than comparing every tuple with every earlier tuple. let mut first_seen = Vec::new(); first_seen .try_reserve_exact(expected) - .map_err(|_| FiniteJointOrderErrorV1::ResourceExhausted)?; + .map_err(|_| AdmissionError::ResourceExhausted)?; first_seen.resize(expected, usize::MAX); let mut rest = Vec::new(); rest.try_reserve_exact(authored.len() - 1) - .map_err(|_| FiniteJointOrderErrorV1::ResourceExhausted)?; + .map_err(|_| AdmissionError::ResourceExhausted)?; let mut first_tuple = None; for (tuple_index, tuple) in authored.into_iter().enumerate() { if tuple.len() != domain_lengths.len() { - return Err(FiniteJointOrderErrorV1::TupleArity { - tuple: tuple_index, - expected: domain_lengths.len(), - actual: tuple.len(), - }); + return Err(AdmissionError::Authored( + FiniteJointOrderErrorV1::TupleArity { + tuple: tuple_index, + expected: domain_lengths.len(), + actual: tuple.len(), + }, + )); } let mut mixed_radix_index = 0usize; - for (dimension, (ordinal, &domain_len)) in tuple.iter().zip(domain_lengths).enumerate() { + for (dimension, (ordinal, domain_len)) in + tuple.iter().zip(domain_lengths.iter()).enumerate() + { + let domain_len = domain_len.get(); if ordinal.index() >= domain_len { - return Err(FiniteJointOrderErrorV1::OrdinalOutOfDomain { - tuple: tuple_index, - dimension, - ordinal: ordinal.index(), - domain_len, - }); + return Err(AdmissionError::Authored( + FiniteJointOrderErrorV1::OrdinalOutOfDomain { + tuple: tuple_index, + dimension, + ordinal: ordinal.index(), + domain_len, + }, + )); } - mixed_radix_index = mixed_radix_index + let next_index = mixed_radix_index .checked_mul(domain_len) .and_then(|index| index.checked_add(ordinal.index())) - .ok_or(FiniteJointOrderErrorV1::CardinalityOverflow)?; + .ok_or(AdmissionError::InternalInvariant)?; + mixed_radix_index = next_index; } - let first = first_seen - .get_mut(mixed_radix_index) - .ok_or(FiniteJointOrderErrorV1::CardinalityOverflow)?; + debug_assert!(mixed_radix_index < expected); + let first = &mut first_seen[mixed_radix_index]; if *first != usize::MAX { - return Err(FiniteJointOrderErrorV1::DuplicateTuple { - first: *first, - duplicate: tuple_index, - }); + return Err(AdmissionError::Authored( + FiniteJointOrderErrorV1::DuplicateTuple { + first: *first, + duplicate: tuple_index, + }, + )); } *first = tuple_index; let tuple = tuple.into_boxed_slice(); @@ -173,1163 +203,9 @@ pub(crate) fn admit_finite_joint_order_v1( } Ok(AdmittedFiniteJointOrderV1 { - // Empty input returned above, so the loop always materialises a first - // tuple; keep the typed branch instead of encoding that proof as panic. - first: first_tuple.ok_or(FiniteJointOrderErrorV1::EmptyOrder)?, + // Empty input returned above, so failure here reports compiler drift, + // never malformed authored policy. + first: first_tuple.ok_or(AdmissionError::InternalInvariant)?, rest: rest.into_boxed_slice(), }) } -use crate::session::SessionObservationBindingPermitV1; - -/// Sealed evaluator family, которую joint-program вызывает одинаково для -/// каждого target occurrence. Конкретные Exact/WCAG/readability payload-и -/// остаются в evaluator-модулях и не образуют центральный enum. -pub(crate) trait JointPointEvaluatorV1: Clone + Debug + PartialEq { - /// Joint execution repeats one invocation across the complete physical - /// matrix. Requiring a value type here keeps that repetition allocation-free - /// instead of hiding an arbitrary `Clone` behind the engine's preflight. - type Invocation: Copy + Debug + PartialEq; - type PassEvidence: Clone + Debug + PartialEq; - type ViolationEvidence: Clone + Debug + PartialEq; - type Error: Clone + Debug + PartialEq; - - fn assess( - &self, - occurrence: &ResolvedOccurrence, - invocation: Self::Invocation, - ) -> Result, Self::Error>; -} - -impl JointPointEvaluatorV1 for Evaluation -where - Evaluation: Clone - + Debug - + PartialEq - + Evaluator - + HardClassifier, PointMeasurement>, - PointInvocation: Copy + Debug + PartialEq, - VisiblePointPassEvidence: Clone + Debug + PartialEq, - VisiblePointViolationEvidence: Clone + Debug + PartialEq, - >::Error: Clone + Debug + PartialEq, -{ - type Invocation = PointInvocation; - type PassEvidence = VisiblePointPassEvidence; - type ViolationEvidence = VisiblePointViolationEvidence; - type Error = >::Error; - - fn assess( - &self, - occurrence: &ResolvedOccurrence, - invocation: Self::Invocation, - ) -> Result, Self::Error> { - assess_visible_point_hard(occurrence, self, invocation) - } -} - -/// Canonical identity одного joint candidate. Число не является declaration -/// order, расстоянием или скрытым приоритетом. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct CandidateOrdinalV1(u32); - -impl CandidateOrdinalV1 { - pub(crate) const fn new(raw: u32) -> Self { - Self(raw) - } -} - -/// Две solver-owned Paint-переменные одного code-owned joint program. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct JointCandidateTupleV1 { - ordinal: CandidateOrdinalV1, - lower: EncodedPointPaintV1, - upper: EncodedPointPaintV1, -} - -impl JointCandidateTupleV1 { - pub(crate) const fn new( - ordinal: CandidateOrdinalV1, - lower: EncodedPointPaintV1, - upper: EncodedPointPaintV1, - ) -> Self { - Self { - ordinal, - lower, - upper, - } - } -} - -/// Order-free candidate domain. Policy не участвует в его construction. -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct JointCandidateSetV1 { - candidates: Vec, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum CandidateSetErrorV1 { - Empty, - DuplicateOrdinal(CandidateOrdinalV1), - DuplicatePhysicalTuple { - first: CandidateOrdinalV1, - second: CandidateOrdinalV1, - }, -} - -impl JointCandidateSetV1 { - pub(crate) fn new( - mut candidates: Vec, - ) -> Result { - if candidates.is_empty() { - return Err(CandidateSetErrorV1::Empty); - } - candidates.sort_unstable_by_key(|candidate| candidate.ordinal); - for pair in candidates.windows(2) { - if pair[0].ordinal == pair[1].ordinal { - return Err(CandidateSetErrorV1::DuplicateOrdinal(pair[0].ordinal)); - } - } - // Group equal physical tuples without auxiliary storage. The explicit - // ordinal tie-break makes every group canonical even though the sort is - // unstable. We inspect every duplicate group and retain the same error - // precedence as the former ordinal-order scan: the smallest first - // ordinal, followed by the smallest matching second ordinal. - candidates.sort_unstable_by(|left, right| { - candidate_physical_key(left) - .cmp(&candidate_physical_key(right)) - .then_with(|| left.ordinal.cmp(&right.ordinal)) - }); - let duplicate = candidates - .windows(2) - .filter(|pair| pair[0].lower == pair[1].lower && pair[0].upper == pair[1].upper) - .map(|pair| (pair[0].ordinal, pair[1].ordinal)) - .min(); - if let Some((first, second)) = duplicate { - return Err(CandidateSetErrorV1::DuplicatePhysicalTuple { first, second }); - } - candidates.sort_unstable_by_key(|candidate| candidate.ordinal); - Ok(Self { candidates }) - } - - pub(crate) fn candidates(&self) -> &[JointCandidateTupleV1] { - &self.candidates - } -} - -fn candidate_physical_key( - candidate: &JointCandidateTupleV1, -) -> (PaintId, Srgb8, u64, PaintId, Srgb8, u64) { - ( - candidate.lower.id(), - candidate.lower.source(), - candidate.lower.opacity().bits(), - candidate.upper.id(), - candidate.upper.source(), - candidate.upper.opacity().bits(), - ) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct JointConstraintIdV1(u32); - -impl JointConstraintIdV1 { - pub(crate) const fn new(raw: u32) -> Self { - Self(raw) - } -} - -/// Physical occurrence, к которому относится hard predicate. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum JointVisibleTargetV1 { - Lower, - Upper, -} - -/// Один constraint конкретного evaluator family. Invocation типизирована самим -/// evaluator-ом; family-specific enum в joint engine отсутствует. -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct PointwiseJointHardConstraintV1 -where - Evaluation: JointPointEvaluatorV1, -{ - id: JointConstraintIdV1, - target: JointVisibleTargetV1, - invocation: Evaluation::Invocation, -} - -impl PointwiseJointHardConstraintV1 -where - Evaluation: JointPointEvaluatorV1, -{ - pub(crate) fn new( - id: JointConstraintIdV1, - target: JointVisibleTargetV1, - invocation: Evaluation::Invocation, - ) -> Self { - Self { - id, - target, - invocation, - } - } -} - -impl PointwiseJointHardConstraintV1 { - pub(crate) fn exact( - id: JointConstraintIdV1, - target: JointVisibleTargetV1, - invocation: Srgb8, - ) -> Self { - Self::new(id, target, invocation) - } -} - -/// Identity первой private joint topology. Она не является public Program ID и -/// не кодирует evaluator family. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum JointPointProgramIdentityV1 { - TwoPaintDerivedSurfacePointV1, -} - -/// Наблюдение, пригодное для одного и того же execution/recheck kernel-а. -/// Runtime revision и статическая compiler binding остаются разными типами. -mod observation_seal { - pub(crate) trait Sealed {} -} - -pub(crate) trait JointObservationV1: observation_seal::Sealed + Debug + PartialEq { - fn case_count(&self) -> usize; - fn bind_surface(&self, surface: SurfaceInputPortId) -> Option; - fn value_at_bound(&self, case_index: usize, bound_index: usize) -> Option; - fn provenance(&self, case_index: usize) -> Option<&[ScenarioId]>; -} - -impl observation_seal::Sealed for RevisionBoundObservationV1 {} - -impl JointObservationV1 for RevisionBoundObservationV1 { - fn case_count(&self) -> usize { - self.physical_case_count() - } - - fn bind_surface(&self, surface: SurfaceInputPortId) -> Option { - self.schema().binary_search(&surface).ok() - } - - fn value_at_bound(&self, case_index: usize, bound_index: usize) -> Option { - self.physical_values(case_index)? - .get(bound_index) - .copied() - .map(crate::lcs_occurrence::ColorSignal::srgb8) - } - - fn provenance(&self, case_index: usize) -> Option<&[ScenarioId]> { - RevisionBoundObservationV1::provenance(self, case_index) - } -} - -/// Один статический point case для build/synchronous resolver path. Тип не -/// содержит runtime stream/revision и не изобретает provenance, которой у -/// синхронного вызова нет. -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct StaticJointObservationV1 { - root_surface: SurfaceInputPortId, - root: Srgb8, -} - -impl StaticJointObservationV1 { - pub(crate) const fn one_case(root_surface: SurfaceInputPortId, root: Srgb8) -> Self { - Self { root_surface, root } - } -} - -impl observation_seal::Sealed for StaticJointObservationV1 {} - -impl JointObservationV1 for StaticJointObservationV1 { - fn case_count(&self) -> usize { - 1 - } - - fn bind_surface(&self, surface: SurfaceInputPortId) -> Option { - (surface == self.root_surface).then_some(0) - } - - fn value_at_bound(&self, case_index: usize, bound_index: usize) -> Option { - (case_index == 0 && bound_index == 0).then_some(self.root) - } - - fn provenance(&self, _case_index: usize) -> Option<&[ScenarioId]> { - None - } -} - -/// Две связанные occurrences над одним observed root backdrop. -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct PointwiseJointPointProgramV1 -where - Evaluation: JointPointEvaluatorV1, -{ - evaluator: Evaluation, - root_surface: SurfaceInputPortId, - lower_paint: PaintId, - upper_paint: PaintId, - constraints: Vec>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum JointProgramErrorV1 { - SamePaintIdentity(PaintId), - DuplicateConstraint(JointConstraintIdV1), -} - -impl PointwiseJointPointProgramV1 { - pub(crate) fn new( - root_surface: SurfaceInputPortId, - lower_paint: PaintId, - upper_paint: PaintId, - constraints: Vec>, - ) -> Result { - Self::with_evaluator( - ExactSrgb8IdentityV1, - root_surface, - lower_paint, - upper_paint, - constraints, - ) - } -} - -impl PointwiseJointPointProgramV1 -where - Evaluation: JointPointEvaluatorV1, -{ - pub(crate) fn with_evaluator( - evaluator: Evaluation, - root_surface: SurfaceInputPortId, - lower_paint: PaintId, - upper_paint: PaintId, - mut constraints: Vec>, - ) -> Result { - if lower_paint == upper_paint { - return Err(JointProgramErrorV1::SamePaintIdentity(lower_paint)); - } - constraints.sort_unstable_by_key(|constraint| constraint.id); - for pair in constraints.windows(2) { - if pair[0].id == pair[1].id { - return Err(JointProgramErrorV1::DuplicateConstraint(pair[0].id)); - } - } - Ok(Self { - evaluator, - root_surface, - lower_paint, - upper_paint, - constraints, - }) - } - - const fn identity(&self) -> JointPointProgramIdentityV1 { - JointPointProgramIdentityV1::TwoPaintDerivedSurfacePointV1 - } - - pub(crate) fn evaluate_static( - self, - candidates: JointCandidateSetV1, - observation: StaticJointObservationV1, - ) -> Result< - PointwiseFullHardReportV1, - PointwiseJointReportErrorV1, - > { - self.evaluate_owned(candidates, observation) - } - - pub(crate) fn evaluate_revision_bound( - self, - candidates: JointCandidateSetV1, - observation: RevisionBoundObservationV1, - _permit: SessionObservationBindingPermitV1, - ) -> Result< - PointwiseFullHardReportV1, - PointwiseJointReportErrorV1, - > { - self.evaluate_owned(candidates, observation) - } - - fn evaluate_owned( - self, - candidates: JointCandidateSetV1, - observation: Observation, - ) -> Result< - PointwiseFullHardReportV1, - PointwiseJointReportErrorV1, - > - where - Observation: JointObservationV1, - { - self.validate_candidates(&candidates)?; - let root_binding = self.bind_observation_surface(&observation)?; - let (execution_count, cell_count) = checked_joint_cardinality_raw( - candidates.candidates.len(), - observation.case_count(), - self.constraints.len(), - ) - .map_err(|_| PointwiseJointReportErrorV1::ResourceExhausted)?; - let mut feasible_ordinals = Vec::new(); - feasible_ordinals - .try_reserve_exact(candidates.candidates.len()) - .map_err(|_| PointwiseJointReportErrorV1::ResourceExhausted)?; - feasible_ordinals.extend( - candidates - .candidates - .iter() - .map(|candidate| candidate.ordinal), - ); - let matrices = self.execute( - candidates.candidates(), - &observation, - root_binding, - execution_count, - cell_count, - )?; - retain_feasible_ordinals(&mut feasible_ordinals, &matrices.cells); - Ok(PointwiseFullHardReportV1 { - program_identity: self.identity(), - program: self, - candidates, - observation, - executions: matrices.executions, - cells: matrices.cells, - feasible_ordinals, - }) - } - - fn bind_observation_surface( - &self, - observation: &Observation, - ) -> Result> - where - Observation: JointObservationV1, - { - let Some(root_binding) = observation.bind_surface(self.root_surface) else { - return Err(PointwiseJointReportErrorV1::MissingRootSurface( - self.root_surface, - )); - }; - if (0..observation.case_count()).any(|case_index| { - observation - .value_at_bound(case_index, root_binding) - .is_none() - }) { - return Err(PointwiseJointReportErrorV1::MissingRootSurface( - self.root_surface, - )); - } - Ok(root_binding) - } - - fn validate_candidates( - &self, - candidates: &JointCandidateSetV1, - ) -> Result<(), PointwiseJointReportErrorV1> { - for candidate in candidates.candidates() { - if candidate.lower.id() != self.lower_paint { - return Err(PointwiseJointReportErrorV1::CandidatePaintMismatch { - ordinal: candidate.ordinal, - stage: JointVisibleTargetV1::Lower, - expected: self.lower_paint, - actual: candidate.lower.id(), - }); - } - if candidate.upper.id() != self.upper_paint { - return Err(PointwiseJointReportErrorV1::CandidatePaintMismatch { - ordinal: candidate.ordinal, - stage: JointVisibleTargetV1::Upper, - expected: self.upper_paint, - actual: candidate.upper.id(), - }); - } - } - Ok(()) - } - - fn execute( - &self, - candidates: &[JointCandidateTupleV1], - observation: &Observation, - root_binding: usize, - execution_count: usize, - cell_count: usize, - ) -> Result< - PointwiseJointEvaluationMatricesV1, - PointwiseJointReportErrorV1, - > - where - Observation: JointObservationV1, - { - let mut executions = Vec::new(); - executions - .try_reserve_exact(execution_count) - .map_err(|_| PointwiseJointReportErrorV1::ResourceExhausted)?; - let mut cells = Vec::new(); - cells - .try_reserve_exact(cell_count) - .map_err(|_| PointwiseJointReportErrorV1::ResourceExhausted)?; - - for candidate in candidates { - for case_index in 0..observation.case_count() { - let root = observation.value_at_bound(case_index, root_binding).ok_or( - PointwiseJointReportErrorV1::MissingRootSurface(self.root_surface), - )?; - let lower = PointOpacityOverSurfaceV1::evaluate_admitted( - candidate.lower.source().bytes(), - candidate.lower.opacity(), - root.bytes(), - ); - let upper = PointOpacityOverSurfaceV1::evaluate_admitted( - candidate.upper.source().bytes(), - candidate.upper.opacity(), - lower.visible(), - ); - debug_assert_eq!(upper.certificate().backdrop_rgb(), lower.visible()); - - executions.push(JointExecutionRecordV1 { - ordinal: candidate.ordinal, - case_index, - lower_paint: candidate.lower, - upper_paint: candidate.upper, - lower, - upper, - }); - - for constraint in &self.constraints { - let occurrence = match constraint.target { - JointVisibleTargetV1::Lower => &lower, - JointVisibleTargetV1::Upper => &upper, - }; - // Evaluator `Err` означает отсутствие валидного hard verdict, - // поэтому частичная матрица не называется FullHardReport. - let decision = match self - .evaluator - .assess(occurrence, constraint.invocation) - .map_err(PointwiseJointReportErrorV1::Evaluator)? - { - HardDecision::Pass(evidence) => { - PointwiseJointConstraintDecisionV1::Pass(evidence) - } - HardDecision::Violation(evidence) => { - PointwiseJointConstraintDecisionV1::Violation(evidence) - } - }; - cells.push(PointwiseJointConstraintCellV1 { - ordinal: candidate.ordinal, - constraint: constraint.id, - target: constraint.target, - case_index, - decision, - }); - } - } - } - - debug_assert_eq!(executions.len(), execution_count); - debug_assert_eq!(cells.len(), cell_count); - Ok(PointwiseJointEvaluationMatricesV1 { executions, cells }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum PointwiseJointReportErrorV1 -where - Evaluation: JointPointEvaluatorV1, -{ - MissingRootSurface(SurfaceInputPortId), - CandidatePaintMismatch { - ordinal: CandidateOrdinalV1, - stage: JointVisibleTargetV1, - expected: PaintId, - actual: PaintId, - }, - Evaluator(Evaluation::Error), - ResourceExhausted, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum JointCapacityErrorV1 { - ResourceExhausted, -} - -fn checked_joint_cardinality_raw( - candidates: usize, - cases: usize, - constraints: usize, -) -> Result<(usize, usize), JointCapacityErrorV1> { - let executions = candidates - .checked_mul(cases) - .ok_or(JointCapacityErrorV1::ResourceExhausted)?; - let cells = executions - .checked_mul(constraints) - .ok_or(JointCapacityErrorV1::ResourceExhausted)?; - Ok((executions, cells)) -} - -pub(crate) fn checked_joint_cardinality( - candidates: usize, - cases: usize, - constraints: usize, -) -> Result<(usize, usize), PointwiseJointReportErrorV1> { - checked_joint_cardinality_raw(candidates, cases, constraints) - .map_err(|_| PointwiseJointReportErrorV1::ResourceExhausted) -} - -#[derive(Debug, PartialEq)] -struct PointwiseJointEvaluationMatricesV1 -where - Evaluation: JointPointEvaluatorV1, -{ - executions: Vec, - cells: Vec>, -} - -fn retain_feasible_ordinals( - feasible: &mut Vec, - cells: &[PointwiseJointConstraintCellV1], -) where - Evaluation: JointPointEvaluatorV1, -{ - // Both vectors are candidate-major and ordinal-canonical. One cursor keeps - // classification O(candidates + cells), including the empty-constraint case. - let mut cell_index = 0; - feasible.retain(|ordinal| { - let mut passes = true; - while let Some(cell) = cells - .get(cell_index) - .filter(|cell| cell.ordinal == *ordinal) - { - passes &= cell.decision.is_pass(); - cell_index += 1; - } - passes - }); - debug_assert_eq!(cell_index, cells.len()); -} - -/// Один execution record существует независимо от наличия constraint на lower. -/// Поэтому связь derived surface доказана даже при пустом constraint set. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct JointExecutionRecordV1 { - ordinal: CandidateOrdinalV1, - case_index: usize, - lower_paint: EncodedPointPaintV1, - upper_paint: EncodedPointPaintV1, - lower: ResolvedOccurrence, - upper: ResolvedOccurrence, -} - -impl JointExecutionRecordV1 { - pub(crate) const fn ordinal(&self) -> CandidateOrdinalV1 { - self.ordinal - } - - pub(crate) const fn case_index(&self) -> usize { - self.case_index - } - - pub(crate) const fn lower_paint(&self) -> EncodedPointPaintV1 { - self.lower_paint - } - - pub(crate) const fn upper_paint(&self) -> EncodedPointPaintV1 { - self.upper_paint - } - - pub(crate) fn lower_visible(&self) -> Srgb8 { - Srgb8::new(self.lower.visible()) - } - - pub(crate) fn upper_visible(&self) -> Srgb8 { - Srgb8::new(self.upper.visible()) - } - - pub(crate) fn derived_surface_is_exact(&self) -> bool { - self.upper.certificate().backdrop_rgb() == self.lower.visible() - } -} - -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum PointwiseJointConstraintDecisionV1 -where - Evaluation: JointPointEvaluatorV1, -{ - Pass(Evaluation::PassEvidence), - Violation(Evaluation::ViolationEvidence), -} - -impl PointwiseJointConstraintDecisionV1 -where - Evaluation: JointPointEvaluatorV1, -{ - pub(crate) const fn is_pass(&self) -> bool { - matches!(self, Self::Pass(_)) - } -} - -impl PointwiseJointConstraintDecisionV1 { - pub(crate) fn actual(&self) -> Srgb8 { - match self { - Self::Pass(evidence) => evidence.actual(), - Self::Violation(evidence) => evidence.actual(), - } - } - - pub(crate) fn target(&self) -> Srgb8 { - match self { - Self::Pass(evidence) => evidence.target(), - Self::Violation(evidence) => evidence.target(), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct PointwiseJointConstraintCellV1 -where - Evaluation: JointPointEvaluatorV1, -{ - ordinal: CandidateOrdinalV1, - constraint: JointConstraintIdV1, - target: JointVisibleTargetV1, - case_index: usize, - decision: PointwiseJointConstraintDecisionV1, -} - -impl PointwiseJointConstraintCellV1 -where - Evaluation: JointPointEvaluatorV1, -{ - pub(crate) const fn ordinal(&self) -> CandidateOrdinalV1 { - self.ordinal - } - - pub(crate) const fn constraint(&self) -> JointConstraintIdV1 { - self.constraint - } - - pub(crate) const fn target_kind(&self) -> JointVisibleTargetV1 { - self.target - } - - pub(crate) const fn case_index(&self) -> usize { - self.case_index - } - - pub(crate) const fn decision(&self) -> &PointwiseJointConstraintDecisionV1 { - &self.decision - } -} - -/// Полная матрица candidate x constraint x unique physical case плюс отдельная -/// joint execution matrix candidate x case. Report не знает selection policy. -#[derive(Debug, PartialEq)] -pub(crate) struct PointwiseFullHardReportV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - program_identity: JointPointProgramIdentityV1, - program: PointwiseJointPointProgramV1, - candidates: JointCandidateSetV1, - observation: Observation, - executions: Vec, - cells: Vec>, - feasible_ordinals: Vec, -} - -impl PointwiseFullHardReportV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - pub(crate) const fn program_identity(&self) -> JointPointProgramIdentityV1 { - self.program_identity - } - - pub(crate) fn candidate_set(&self) -> &JointCandidateSetV1 { - &self.candidates - } - - pub(crate) fn executions(&self) -> &[JointExecutionRecordV1] { - &self.executions - } - - pub(crate) fn cells(&self) -> &[PointwiseJointConstraintCellV1] { - &self.cells - } - - pub(crate) const fn observation(&self) -> &Observation { - &self.observation - } - - pub(crate) fn provenance(&self, case_index: usize) -> Option<&[ScenarioId]> { - self.observation.provenance(case_index) - } - - pub(crate) fn classify(self) -> PointwiseHardFeasibilityV1 { - if self.feasible_ordinals.is_empty() { - PointwiseHardFeasibilityV1::Infeasible(self) - } else { - PointwiseHardFeasibilityV1::NonEmpty(PointwiseNonEmptyFeasibleJointTuplesV1 { - report: self, - }) - } - } -} - -#[derive(Debug, PartialEq)] -pub(crate) enum PointwiseHardFeasibilityV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - Infeasible(PointwiseFullHardReportV1), - NonEmpty(PointwiseNonEmptyFeasibleJointTuplesV1), -} - -#[derive(Debug, PartialEq)] -pub(crate) struct PointwiseNonEmptyFeasibleJointTuplesV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - report: PointwiseFullHardReportV1, -} - -impl PointwiseNonEmptyFeasibleJointTuplesV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - pub(crate) fn feasible(&self) -> &[CandidateOrdinalV1] { - &self.report.feasible_ordinals - } - - pub(crate) fn candidate_set(&self) -> &JointCandidateSetV1 { - self.report.candidate_set() - } - - #[expect( - clippy::result_large_err, - reason = "ownership-preserving rejection keeps the expensive report retryable without heap allocation or recomputation" - )] - pub(crate) fn select( - self, - policy: DeclaredTotalOrderV1, - ) -> Result< - PointwiseSelectedJointTupleV1, - PointwiseSelectionFailureV1, - > { - if !policy.is_bound_to(self.report.candidate_set()) { - return Err(PointwiseSelectionFailureV1 { - feasible: self, - policy, - reason: SelectionPolicyErrorV1::CandidateDomainMismatch, - }); - } - // Canonical policy entries and feasible ordinals are both sorted by - // ordinal. One merge scan finds the feasible entry with minimum - // client-declared rank without C×log(F) lookup or auxiliary storage. - let mut feasible_index = 0; - let mut selected: Option<(usize, usize)> = None; - for (candidate_index, entry) in policy.domain.iter().enumerate() { - if self.report.feasible_ordinals.get(feasible_index) == Some(&entry.ordinal) { - if selected.is_none_or(|(rank, _)| entry.rank < rank) { - selected = Some((entry.rank, candidate_index)); - } - feasible_index += 1; - } - } - let Some((_, candidate_index)) = - selected.filter(|_| feasible_index == self.report.feasible_ordinals.len()) - else { - return Err(PointwiseSelectionFailureV1 { - feasible: self, - policy, - reason: SelectionPolicyErrorV1::InternalInvariant, - }); - }; - let Some(candidate) = self - .report - .candidates - .candidates() - .get(candidate_index) - .copied() - else { - return Err(PointwiseSelectionFailureV1 { - feasible: self, - policy, - reason: SelectionPolicyErrorV1::InternalInvariant, - }); - }; - Ok(PointwiseSelectedJointTupleV1 { - report: self.report, - policy, - candidate, - }) - } -} - -/// Recoverable selection rejection. A foreign/malformed policy cannot destroy -/// the expensive full report: the caller can replace only the policy and retry -/// without recomposition or evaluator execution. -#[derive(Debug, PartialEq)] -pub(crate) struct PointwiseSelectionFailureV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - feasible: PointwiseNonEmptyFeasibleJointTuplesV1, - policy: DeclaredTotalOrderV1, - reason: SelectionPolicyErrorV1, -} - -impl PointwiseSelectionFailureV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - pub(crate) const fn feasible( - &self, - ) -> &PointwiseNonEmptyFeasibleJointTuplesV1 { - &self.feasible - } - - pub(crate) const fn policy(&self) -> &DeclaredTotalOrderV1 { - &self.policy - } - - pub(crate) const fn reason(&self) -> SelectionPolicyErrorV1 { - self.reason - } - - pub(crate) fn into_parts( - self, - ) -> ( - PointwiseNonEmptyFeasibleJointTuplesV1, - DeclaredTotalOrderV1, - SelectionPolicyErrorV1, - ) { - (self.feasible, self.policy, self.reason) - } -} - -/// Полный client-declared tie-break. Он не участвует в measurement/report. -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct DeclaredTotalOrderV1 { - order: Vec, - domain: Vec, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -struct DeclaredPolicyDomainEntryV1 { - ordinal: CandidateOrdinalV1, - rank: usize, -} - -impl DeclaredTotalOrderV1 { - pub(crate) fn new( - candidates: &JointCandidateSetV1, - order: Vec, - ) -> Result { - if order.len() != candidates.candidates.len() { - return Err(SelectionPolicyErrorV1::NotATotalOrder); - } - let mut domain = Vec::new(); - domain - .try_reserve_exact(candidates.candidates.len()) - .map_err(|_| SelectionPolicyErrorV1::ResourceExhausted)?; - domain.extend( - order - .iter() - .copied() - .enumerate() - .map(|(rank, ordinal)| DeclaredPolicyDomainEntryV1 { ordinal, rank }), - ); - domain.sort_unstable_by_key(|entry| entry.ordinal); - if let Some(pair) = domain - .windows(2) - .find(|pair| pair[0].ordinal == pair[1].ordinal) - { - return Err(SelectionPolicyErrorV1::DuplicateOrdinal(pair[0].ordinal)); - } - if domain.iter().map(|entry| entry.ordinal).ne(candidates - .candidates - .iter() - .map(|candidate| candidate.ordinal)) - { - return Err(SelectionPolicyErrorV1::NotATotalOrder); - } - Ok(Self { order, domain }) - } - - pub(crate) fn order(&self) -> &[CandidateOrdinalV1] { - &self.order - } - - pub(crate) fn into_order(self) -> Vec { - self.order - } - - fn is_bound_to(&self, candidates: &JointCandidateSetV1) -> bool { - self.domain.iter().map(|entry| entry.ordinal).eq(candidates - .candidates - .iter() - .map(|candidate| candidate.ordinal)) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum SelectionPolicyErrorV1 { - ResourceExhausted, - DuplicateOrdinal(CandidateOrdinalV1), - NotATotalOrder, - CandidateDomainMismatch, - InternalInvariant, -} - -#[derive(Debug, PartialEq)] -pub(crate) struct PointwiseSelectedJointTupleV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - report: PointwiseFullHardReportV1, - policy: DeclaredTotalOrderV1, - candidate: JointCandidateTupleV1, -} - -impl PointwiseSelectedJointTupleV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - pub(crate) const fn ordinal(&self) -> CandidateOrdinalV1 { - self.candidate.ordinal - } - - pub(crate) fn recheck( - self, - ) -> Result< - PointwiseVerifiedSelectionV1, - PointwiseSelectedRecheckErrorV1, - > { - // A revision-bound report can enter this consuming chain only through - // `evaluate_revision_bound` with a Session-minted linear permit. Static - // reports have a different concrete observation type. - let root_binding = self - .report - .program - .bind_observation_surface(&self.report.observation) - .map_err(|_| PointwiseSelectedRecheckErrorV1::InvariantDrift)?; - let cases = self.report.observation.case_count(); - let (execution_count, cell_count) = - checked_joint_cardinality_raw(1, cases, self.report.program.constraints.len()) - .map_err(|_| PointwiseSelectedRecheckErrorV1::ResourceExhausted)?; - let matrices = self - .report - .program - .execute( - core::slice::from_ref(&self.candidate), - &self.report.observation, - root_binding, - execution_count, - cell_count, - ) - .map_err(|error| match error { - PointwiseJointReportErrorV1::ResourceExhausted => { - PointwiseSelectedRecheckErrorV1::ResourceExhausted - } - PointwiseJointReportErrorV1::Evaluator(error) => { - PointwiseSelectedRecheckErrorV1::Evaluator(error) - } - PointwiseJointReportErrorV1::MissingRootSurface(_) - | PointwiseJointReportErrorV1::CandidatePaintMismatch { .. } => { - PointwiseSelectedRecheckErrorV1::InvariantDrift - } - })?; - if let Some(violation_index) = matrices - .cells - .iter() - .position(|cell| !cell.decision.is_pass()) - { - return Err(PointwiseSelectedRecheckErrorV1::Violation { - evidence: PointwiseFreshJointRecheckV1 { - executions: matrices.executions, - cells: matrices.cells, - }, - violation_index, - }); - } - Ok(PointwiseVerifiedSelectionV1 { - selected: self, - recheck: PointwiseFreshJointRecheckV1 { - executions: matrices.executions, - cells: matrices.cells, - }, - }) - } -} - -#[derive(Debug, PartialEq)] -pub(crate) enum PointwiseSelectedRecheckErrorV1 -where - Evaluation: JointPointEvaluatorV1, -{ - ResourceExhausted, - InvariantDrift, - Evaluator(Evaluation::Error), - Violation { - evidence: PointwiseFreshJointRecheckV1, - violation_index: usize, - }, -} - -#[derive(Debug, PartialEq)] -pub(crate) struct PointwiseFreshJointRecheckV1 -where - Evaluation: JointPointEvaluatorV1, -{ - executions: Vec, - cells: Vec>, -} - -#[derive(Debug, PartialEq)] -pub(crate) struct PointwiseVerifiedSelectionV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - selected: PointwiseSelectedJointTupleV1, - recheck: PointwiseFreshJointRecheckV1, -} - -impl PointwiseVerifiedSelectionV1 -where - Evaluation: JointPointEvaluatorV1, - Observation: JointObservationV1, -{ - pub(crate) const fn ordinal(&self) -> CandidateOrdinalV1 { - self.selected.candidate.ordinal - } - - pub(crate) const fn report(&self) -> &PointwiseFullHardReportV1 { - &self.selected.report - } - - pub(crate) fn policy(&self) -> &[CandidateOrdinalV1] { - &self.selected.policy.order - } - - pub(crate) fn fresh_executions(&self) -> &[JointExecutionRecordV1] { - &self.recheck.executions - } - - pub(crate) fn fresh_cells(&self) -> &[PointwiseJointConstraintCellV1] { - &self.recheck.cells - } -} diff --git a/crates/labcolors-core/src/joint_tests.rs b/crates/labcolors-core/src/joint_tests.rs deleted file mode 100644 index 52cbd969..00000000 --- a/crates/labcolors-core/src/joint_tests.rs +++ /dev/null @@ -1,885 +0,0 @@ -use crate::Srgb8; -use crate::appearance::{ - EncodedPointPaintV1, EncodedPointPaintValueV1, PaintId, SurfaceInputPortId, -}; -use crate::composition::AdmittedOpacityV1; -use crate::constraints::{ExactSrgb8IdentityV1, HardDecision, Wcag22Srgb8V1}; -use crate::joint::{ - CandidateOrdinalV1, CandidateSetErrorV1, DeclaredTotalOrderV1, JointCandidateSetV1, - JointCandidateTupleV1, JointConstraintIdV1, JointPointEvaluatorV1, JointPointProgramIdentityV1, - JointProgramErrorV1, JointVisibleTargetV1, PointwiseHardFeasibilityV1, - PointwiseJointConstraintDecisionV1, PointwiseJointHardConstraintV1, - PointwiseJointPointProgramV1, PointwiseJointReportErrorV1, PointwiseSelectedRecheckErrorV1, - SelectionPolicyErrorV1, checked_joint_cardinality, -}; -use crate::lcs_occurrence::ColorSignal; -use crate::observation::{ - ObservationArenaPoolV1, ObservationHeadViewV1, ObservationOwnerV1, ObservationPayloadInput, - ObservationStreamId, ObservationUpdateInput, ObservedScenarioSetInput, - PreparedObservationUpdateV1, Revision, RevisionBoundObservationV1, ScenarioId, ScenarioInput, - SurfaceInputBinding, canonicalize_observation_schema, prepare_observation, -}; -use crate::session::SessionObservationBindingPermitV1; -use crate::wcag22::Wcag22CriterionV1; -use std::cell::Cell; -use std::rc::Rc; - -const ROOT: SurfaceInputPortId = SurfaceInputPortId::new(7); -const LOWER: PaintId = PaintId::new(11); -const UPPER: PaintId = PaintId::new(12); -const STREAM: ObservationStreamId = ObservationStreamId::new(3); - -fn session_permit() -> SessionObservationBindingPermitV1 { - SessionObservationBindingPermitV1::for_test() -} - -struct EmptyObservationOwner; - -impl ObservationOwnerV1 for EmptyObservationOwner { - fn observation_head(&self) -> ObservationHeadViewV1<'_> { - ObservationHeadViewV1::Empty - } -} - -fn paint(id: PaintId, bytes: [u8; 3], opacity: f64) -> EncodedPointPaintV1 { - EncodedPointPaintV1::from_value( - id, - EncodedPointPaintValueV1::from_admitted( - Srgb8::new(bytes), - AdmittedOpacityV1::new(opacity).unwrap(), - ), - ) -} - -fn candidate(ordinal: u32, lower: ([u8; 3], f64), upper: ([u8; 3], f64)) -> JointCandidateTupleV1 { - JointCandidateTupleV1::new( - CandidateOrdinalV1::new(ordinal), - paint(LOWER, lower.0, lower.1), - paint(UPPER, upper.0, upper.1), - ) -} - -fn candidates(values: Vec) -> JointCandidateSetV1 { - JointCandidateSetV1::new(values).unwrap() -} - -fn program( - constraints: Vec>, -) -> PointwiseJointPointProgramV1 { - PointwiseJointPointProgramV1::new(ROOT, LOWER, UPPER, constraints).unwrap() -} - -fn exact_upper(id: u32, target: [u8; 3]) -> PointwiseJointHardConstraintV1 { - PointwiseJointHardConstraintV1::exact( - JointConstraintIdV1::new(id), - JointVisibleTargetV1::Upper, - Srgb8::new(target), - ) -} - -fn exact_lower(id: u32, target: [u8; 3]) -> PointwiseJointHardConstraintV1 { - PointwiseJointHardConstraintV1::exact( - JointConstraintIdV1::new(id), - JointVisibleTargetV1::Lower, - Srgb8::new(target), - ) -} - -fn observation(revision: u64, cases: Vec<(u32, [u8; 3])>) -> RevisionBoundObservationV1 { - let mut owner = EmptyObservationOwner; - let schema = canonicalize_observation_schema(vec![ROOT]).unwrap(); - let mut arenas = ObservationArenaPoolV1::new(&schema); - let prepared = prepare_observation( - &mut owner, - &mut arenas, - STREAM, - &schema, - ObservationUpdateInput { - stream: STREAM, - revision: Revision::new(revision), - payload: ObservationPayloadInput::Scenarios(ObservedScenarioSetInput { - scenarios: cases - .into_iter() - .map(|(id, value)| ScenarioInput { - id: ScenarioId::new(id), - bindings: vec![SurfaceInputBinding { - port: ROOT, - value: ColorSignal::from_srgb8(Srgb8::new(value)), - }], - }) - .collect(), - }), - }, - ) - .unwrap(); - let PreparedObservationUpdateV1::Observed(prepared) = prepared else { - panic!("fresh observed update must prepare an observation"); - }; - let (_owner, observation) = prepared.into_parts(); - observation -} - -fn observation_with_unrelated_surface( - revision: u64, - unrelated: [u8; 3], - root: [u8; 3], -) -> RevisionBoundObservationV1 { - let unrelated_surface = SurfaceInputPortId::new(ROOT.value() - 1); - let mut owner = EmptyObservationOwner; - let schema = canonicalize_observation_schema(vec![ROOT, unrelated_surface]).unwrap(); - let mut arenas = ObservationArenaPoolV1::new(&schema); - let prepared = prepare_observation( - &mut owner, - &mut arenas, - STREAM, - &schema, - ObservationUpdateInput { - stream: STREAM, - revision: Revision::new(revision), - payload: ObservationPayloadInput::Scenarios(ObservedScenarioSetInput { - scenarios: vec![ScenarioInput { - id: ScenarioId::new(17), - bindings: vec![ - SurfaceInputBinding { - port: ROOT, - value: ColorSignal::from_srgb8(Srgb8::new(root)), - }, - SurfaceInputBinding { - port: unrelated_surface, - value: ColorSignal::from_srgb8(Srgb8::new(unrelated)), - }, - ], - }], - }), - }, - ) - .unwrap(); - let PreparedObservationUpdateV1::Observed(prepared) = prepared else { - panic!("fresh observed update must prepare an observation"); - }; - let (_owner, observation) = prepared.into_parts(); - observation -} - -#[test] -fn linked_candidate_is_selected_only_after_upper_sees_lower_visible_surface() { - let observed = observation(1, vec![(1, [0; 3])]); - let domain = candidates(vec![ - candidate(0, ([0; 3], 1.0), ([255; 3], 0.5)), - candidate(1, ([128; 3], 1.0), ([255; 3], 0.5)), - ]); - let report = program(vec![exact_upper(1, [192; 3])]) - .evaluate_revision_bound(domain, observed, session_permit()) - .unwrap(); - - assert_eq!( - report.program_identity(), - JointPointProgramIdentityV1::TwoPaintDerivedSurfacePointV1 - ); - assert_eq!(report.executions().len(), 2); - assert!( - report - .executions() - .iter() - .all(|execution| execution.derived_surface_is_exact()) - ); - assert_eq!(report.executions()[0].ordinal(), CandidateOrdinalV1::new(0)); - assert_eq!(report.executions()[0].case_index(), 0); - assert_eq!(report.executions()[0].lower_paint().id(), LOWER); - assert_eq!(report.executions()[0].upper_paint().id(), UPPER); - assert_eq!(report.executions()[0].lower_visible(), Srgb8::new([0; 3])); - assert_eq!(report.executions()[0].upper_visible(), Srgb8::new([128; 3])); - assert_eq!(report.executions()[1].upper_visible(), Srgb8::new([192; 3])); - assert_eq!(report.cells()[0].ordinal(), CandidateOrdinalV1::new(0)); - assert_eq!(report.cells()[0].constraint(), JointConstraintIdV1::new(1)); - assert_eq!(report.cells()[0].target_kind(), JointVisibleTargetV1::Upper); - assert_eq!(report.cells()[0].case_index(), 0); - assert_eq!(report.cells()[0].decision().target(), Srgb8::new([192; 3])); - assert!(matches!( - report.cells()[0].decision(), - PointwiseJointConstraintDecisionV1::Violation(_) - )); - assert!(matches!( - report.cells()[1].decision(), - PointwiseJointConstraintDecisionV1::Pass(_) - )); - - let PointwiseHardFeasibilityV1::NonEmpty(feasible) = report.classify() else { - panic!("second joint tuple must be feasible"); - }; - assert_eq!(feasible.feasible(), &[CandidateOrdinalV1::new(1)]); - let policy = DeclaredTotalOrderV1::new( - feasible.candidate_set(), - vec![CandidateOrdinalV1::new(0), CandidateOrdinalV1::new(1)], - ) - .unwrap(); - let selected = feasible.select(policy).unwrap(); - assert_eq!(selected.ordinal(), CandidateOrdinalV1::new(1)); - let verified = selected.recheck().unwrap(); - assert_eq!(verified.ordinal(), CandidateOrdinalV1::new(1)); - assert_eq!(verified.fresh_executions().len(), 1); - assert_eq!(verified.fresh_cells().len(), 1); -} - -#[test] -fn every_unique_physical_case_must_pass_without_worst_or_average_reduction() { - let observed = observation(2, vec![(1, [0; 3]), (2, [255; 3])]); - let domain = candidates(vec![candidate(0, ([0; 3], 0.5), ([255; 3], 0.5))]); - let report = program(vec![exact_upper(1, [128; 3])]) - .evaluate_revision_bound(domain, observed, session_permit()) - .unwrap(); - - assert_eq!(report.executions().len(), 2); - assert_eq!(report.cells().len(), 2); - assert_eq!(report.cells()[0].decision().actual(), Srgb8::new([128; 3])); - assert_eq!(report.cells()[1].decision().actual(), Srgb8::new([192; 3])); - let PointwiseHardFeasibilityV1::Infeasible(report) = report.classify() else { - panic!("one violated case must exclude the whole tuple"); - }; - assert_eq!( - report - .cells() - .iter() - .filter(|cell| !cell.decision().is_pass()) - .count(), - 1 - ); -} - -#[test] -fn full_report_does_not_short_circuit_after_first_violation() { - let observed = observation(3, vec![(1, [0; 3])]); - let domain = candidates(vec![candidate(0, ([0; 3], 1.0), ([255; 3], 0.5))]); - crate::composition::reset_source_over_evaluation_count(); - let report = program(vec![ - exact_upper(1, [0; 3]), - exact_upper(2, [128; 3]), - exact_lower(3, [0; 3]), - ]) - .evaluate_revision_bound(domain, observed, session_permit()) - .unwrap(); - - assert_eq!(crate::composition::source_over_evaluation_count(), 2); - assert_eq!(report.executions().len(), 1); - assert_eq!(report.cells().len(), 3); - assert_eq!( - report - .cells() - .iter() - .filter(|cell| cell.decision().is_pass()) - .count(), - 2 - ); - assert_eq!( - report - .cells() - .iter() - .filter(|cell| !cell.decision().is_pass()) - .count(), - 1 - ); -} - -#[test] -fn candidate_and_constraint_declaration_permutations_are_canonical() { - let observed = observation(4, vec![(1, [0; 3])]); - let first = program(vec![exact_upper(9, [255; 3]), exact_lower(4, [0; 3])]) - .evaluate_revision_bound( - candidates(vec![ - candidate(8, ([255; 3], 0.0), ([255; 3], 1.0)), - candidate(2, ([0; 3], 1.0), ([255; 3], 1.0)), - ]), - observation(4, vec![(1, [0; 3])]), - session_permit(), - ) - .unwrap(); - let second = program(vec![exact_lower(4, [0; 3]), exact_upper(9, [255; 3])]) - .evaluate_revision_bound( - candidates(vec![ - candidate(2, ([0; 3], 1.0), ([255; 3], 1.0)), - candidate(8, ([255; 3], 0.0), ([255; 3], 1.0)), - ]), - observed, - session_permit(), - ) - .unwrap(); - - assert_eq!(first, second); -} - -#[test] -fn scenario_declaration_permutation_is_canonical() { - let first = program(vec![exact_upper(1, [255; 3])]) - .evaluate_revision_bound( - candidates(vec![candidate(0, ([17; 3], 0.5), ([255; 3], 1.0))]), - observation(5, vec![(2, [255; 3]), (1, [0; 3])]), - session_permit(), - ) - .unwrap(); - let second = program(vec![exact_upper(1, [255; 3])]) - .evaluate_revision_bound( - candidates(vec![candidate(0, ([17; 3], 0.5), ([255; 3], 1.0))]), - observation(5, vec![(1, [0; 3]), (2, [255; 3])]), - session_permit(), - ) - .unwrap(); - - assert_eq!(first, second); -} - -#[test] -fn revision_bound_root_uses_its_schema_ordinal_and_retains_case_provenance() { - let report = program(vec![exact_lower(1, [128; 3])]) - .evaluate_revision_bound( - candidates(vec![candidate(0, ([0; 3], 0.5), ([255; 3], 1.0))]), - observation_with_unrelated_surface(6, [0; 3], [255; 3]), - session_permit(), - ) - .unwrap(); - - assert_eq!(report.executions().len(), 1); - assert_eq!(report.executions()[0].lower_visible(), Srgb8::new([128; 3])); - assert_eq!(report.provenance(0), Some(&[ScenarioId::new(17)][..])); - assert!(matches!( - report.cells()[0].decision(), - PointwiseJointConstraintDecisionV1::Pass(_) - )); -} - -#[test] -fn static_joint_evaluation_is_explicitly_lifecycle_free() { - let report = program(vec![exact_upper(1, [255; 3])]) - .evaluate_static( - candidates(vec![candidate(0, ([0; 3], 1.0), ([255; 3], 1.0))]), - crate::joint::StaticJointObservationV1::one_case(ROOT, Srgb8::new([0; 3])), - ) - .unwrap(); - - assert_eq!(report.executions().len(), 1); - assert_eq!(report.provenance(0), None); -} - -#[test] -fn static_joint_key_mismatch_fails_before_compositing() { - crate::composition::reset_source_over_evaluation_count(); - let result = program(vec![exact_upper(1, [255; 3])]).evaluate_static( - candidates(vec![candidate(0, ([0; 3], 1.0), ([255; 3], 1.0))]), - crate::joint::StaticJointObservationV1::one_case( - SurfaceInputPortId::new(8), - Srgb8::new([0; 3]), - ), - ); - - assert!(matches!( - result, - Err(PointwiseJointReportErrorV1::MissingRootSurface(ROOT)) - )); - assert_eq!(crate::composition::source_over_evaluation_count(), 0); -} - -#[test] -fn duplicate_provenance_does_not_repeat_physical_execution() { - let observed = observation(6, vec![(9, [1, 2, 3]), (3, [1, 2, 3])]); - let report = program(vec![exact_upper(1, [255; 3])]) - .evaluate_revision_bound( - candidates(vec![candidate(0, ([0; 3], 1.0), ([255; 3], 1.0))]), - observed, - session_permit(), - ) - .unwrap(); - - assert_eq!(report.executions().len(), 1); - assert_eq!(report.cells().len(), 1); - assert_eq!( - report.provenance(0).unwrap(), - &[ScenarioId::new(3), ScenarioId::new(9)] - ); -} - -#[test] -fn declared_policy_is_separate_from_report_and_is_the_only_tie_break() { - let make_report = || { - program(vec![exact_upper(1, [42; 3])]) - .evaluate_revision_bound( - candidates(vec![ - candidate(7, ([1; 3], 1.0), ([42; 3], 1.0)), - candidate(4, ([250; 3], 1.0), ([42; 3], 1.0)), - ]), - observation(7, vec![(1, [0; 3])]), - session_permit(), - ) - .unwrap() - }; - - let PointwiseHardFeasibilityV1::NonEmpty(first) = make_report().classify() else { - panic!("both tuples must pass"); - }; - let first_policy = DeclaredTotalOrderV1::new( - first.candidate_set(), - vec![CandidateOrdinalV1::new(7), CandidateOrdinalV1::new(4)], - ) - .unwrap(); - let PointwiseHardFeasibilityV1::NonEmpty(second) = make_report().classify() else { - panic!("both tuples must pass"); - }; - let second_policy = DeclaredTotalOrderV1::new( - second.candidate_set(), - vec![CandidateOrdinalV1::new(4), CandidateOrdinalV1::new(7)], - ) - .unwrap(); - assert_eq!( - first.select(first_policy).unwrap().ordinal(), - CandidateOrdinalV1::new(7) - ); - assert_eq!( - second.select(second_policy).unwrap().ordinal(), - CandidateOrdinalV1::new(4) - ); -} - -#[test] -fn foreign_disjoint_and_partially_overlapping_policy_domains_are_typed_errors() { - let make_actual = || { - let report = program(vec![]) - .evaluate_revision_bound( - candidates(vec![ - candidate(1, ([11; 3], 1.0), ([111; 3], 1.0)), - candidate(2, ([22; 3], 1.0), ([122; 3], 1.0)), - ]), - observation(70, vec![(1, [0; 3])]), - session_permit(), - ) - .unwrap(); - let PointwiseHardFeasibilityV1::NonEmpty(feasible) = report.classify() else { - panic!("an unconstrained nonempty domain must be feasible"); - }; - feasible - }; - - let disjoint_domain = candidates(vec![ - candidate(10, ([10; 3], 1.0), ([210; 3], 1.0)), - candidate(11, ([11; 3], 1.0), ([211; 3], 1.0)), - ]); - let disjoint_policy = DeclaredTotalOrderV1::new( - &disjoint_domain, - vec![CandidateOrdinalV1::new(10), CandidateOrdinalV1::new(11)], - ) - .unwrap(); - let disjoint_feasible = make_actual(); - crate::composition::reset_source_over_evaluation_count(); - let (disjoint_failure, disjoint_allocations) = - crate::test_support::measured_allocations(|| { - match disjoint_feasible.select(disjoint_policy) { - Ok(_) => panic!("a disjoint policy domain must be rejected"), - Err(failure) => failure, - } - }); - assert_eq!( - disjoint_failure.reason(), - SelectionPolicyErrorV1::CandidateDomainMismatch - ); - assert_eq!( - disjoint_failure.feasible().feasible(), - &[CandidateOrdinalV1::new(1), CandidateOrdinalV1::new(2)] - ); - assert_eq!( - disjoint_failure.policy().order(), - &[CandidateOrdinalV1::new(10), CandidateOrdinalV1::new(11)] - ); - assert_eq!(disjoint_allocations, 0); - assert_eq!(crate::composition::source_over_evaluation_count(), 0); - - // Ordinal 1 overlaps and is first in the foreign order. The old selector - // silently accepted it; exact-domain validation must reject the policy. - let partial_domain = candidates(vec![ - candidate(1, ([31; 3], 1.0), ([131; 3], 1.0)), - candidate(3, ([33; 3], 1.0), ([133; 3], 1.0)), - ]); - let partial_policy = DeclaredTotalOrderV1::new( - &partial_domain, - vec![CandidateOrdinalV1::new(1), CandidateOrdinalV1::new(3)], - ) - .unwrap(); - let partial_feasible = make_actual(); - crate::composition::reset_source_over_evaluation_count(); - let (partial_failure, partial_allocations) = crate::test_support::measured_allocations(|| { - match partial_feasible.select(partial_policy) { - Ok(_) => panic!("a partially overlapping policy domain must be rejected"), - Err(failure) => failure, - } - }); - assert_eq!( - partial_failure.reason(), - SelectionPolicyErrorV1::CandidateDomainMismatch - ); - assert_eq!(partial_allocations, 0); - assert_eq!(crate::composition::source_over_evaluation_count(), 0); - - // Recover the expensive report and the caller's original Vec allocation, - // repair only the order, then retry without re-running physical evaluation. - let (recovered_feasible, rejected_policy, reason) = partial_failure.into_parts(); - assert_eq!(reason, SelectionPolicyErrorV1::CandidateDomainMismatch); - let order_backing = rejected_policy.order().as_ptr(); - let mut corrected_order = rejected_policy.into_order(); - corrected_order[1] = CandidateOrdinalV1::new(2); - corrected_order.swap(0, 1); - assert_eq!(corrected_order.as_ptr(), order_backing); - let corrected_policy = - DeclaredTotalOrderV1::new(recovered_feasible.candidate_set(), corrected_order).unwrap(); - assert_eq!(corrected_policy.order().as_ptr(), order_backing); - let selected = recovered_feasible.select(corrected_policy).unwrap(); - assert_eq!(selected.ordinal(), CandidateOrdinalV1::new(2)); - assert_eq!(crate::composition::source_over_evaluation_count(), 0); -} - -#[test] -fn policy_is_reusable_for_the_same_ordinal_domain_without_owning_candidate_physics() { - let make_actual = || { - let report = program(vec![]) - .evaluate_revision_bound( - candidates(vec![ - candidate(1, ([11; 3], 1.0), ([111; 3], 1.0)), - candidate(2, ([22; 3], 1.0), ([222; 3], 1.0)), - ]), - observation(71, vec![(1, [0; 3])]), - session_permit(), - ) - .unwrap(); - let PointwiseHardFeasibilityV1::NonEmpty(feasible) = report.classify() else { - panic!("an unconstrained nonempty domain must be feasible"); - }; - feasible - }; - - let different_physics = candidates(vec![ - candidate(2, ([202; 3], 0.5), ([72; 3], 1.0)), - candidate(1, ([201; 3], 0.5), ([71; 3], 1.0)), - ]); - let reusable_policy = DeclaredTotalOrderV1::new( - &different_physics, - vec![CandidateOrdinalV1::new(2), CandidateOrdinalV1::new(1)], - ) - .unwrap(); - let verified = make_actual() - .select(reusable_policy) - .unwrap() - .recheck() - .unwrap(); - assert_eq!(verified.ordinal(), CandidateOrdinalV1::new(2)); - assert_eq!( - verified.fresh_executions()[0].lower_visible(), - Srgb8::new([22; 3]) - ); - assert_eq!( - verified.fresh_executions()[0].upper_visible(), - Srgb8::new([222; 3]) - ); - - let independently_identical = candidates(vec![ - candidate(2, ([22; 3], 1.0), ([222; 3], 1.0)), - candidate(1, ([11; 3], 1.0), ([111; 3], 1.0)), - ]); - let identical_policy = DeclaredTotalOrderV1::new( - &independently_identical, - vec![CandidateOrdinalV1::new(1), CandidateOrdinalV1::new(2)], - ) - .unwrap(); - assert_eq!( - make_actual().select(identical_policy).unwrap().ordinal(), - CandidateOrdinalV1::new(1) - ); -} - -#[test] -fn fresh_recheck_executes_the_selected_joint_program_again_on_the_same_revision() { - let observed = observation(8, vec![(1, [0; 3]), (2, [255; 3])]); - let report = program(vec![exact_upper(1, [17; 3])]) - .evaluate_revision_bound( - candidates(vec![candidate(0, ([9; 3], 1.0), ([17; 3], 1.0))]), - observed, - session_permit(), - ) - .unwrap(); - crate::composition::reset_source_over_evaluation_count(); - let PointwiseHardFeasibilityV1::NonEmpty(feasible) = report.classify() else { - panic!("opaque upper must pass on both roots"); - }; - let policy = - DeclaredTotalOrderV1::new(feasible.candidate_set(), vec![CandidateOrdinalV1::new(0)]) - .unwrap(); - let selected = feasible.select(policy).unwrap(); - let verified = selected.recheck().unwrap(); - - assert_eq!(crate::composition::source_over_evaluation_count(), 4); - assert_eq!(verified.report().observation().revision(), Revision::new(8)); - assert_eq!(verified.fresh_executions().len(), 2); - assert_eq!(verified.fresh_cells().len(), 2); - assert_eq!(verified.policy(), &[CandidateOrdinalV1::new(0)]); -} - -#[test] -fn empty_hard_constraint_set_is_non_vacuously_feasible() { - let observed = observation(10, vec![(1, [13, 17, 19])]); - let domain = candidates(vec![candidate(0, ([20, 30, 40], 0.5), ([50, 60, 70], 1.0))]); - let report = program(vec![]) - .evaluate_revision_bound(domain, observed, session_permit()) - .unwrap(); - - assert_eq!(report.executions().len(), 1); - assert!(report.cells().is_empty()); - let PointwiseHardFeasibilityV1::NonEmpty(feasible) = report.classify() else { - panic!("a tuple with no hard violations must be feasible"); - }; - assert_eq!(feasible.feasible(), &[CandidateOrdinalV1::new(0)]); - let policy = - DeclaredTotalOrderV1::new(feasible.candidate_set(), vec![CandidateOrdinalV1::new(0)]) - .unwrap(); - let verified = feasible.select(policy).unwrap().recheck().unwrap(); - assert_eq!(verified.fresh_executions().len(), 1); - assert!(verified.fresh_cells().is_empty()); -} - -#[test] -fn generic_wcag_evaluator_can_constrain_the_derived_lower_occurrence() { - let observed = observation(11, vec![(1, [255; 3])]); - let domain = candidates(vec![ - candidate(1, ([255; 3], 1.0), ([0; 3], 1.0)), - candidate(2, ([0; 3], 1.0), ([0; 3], 1.0)), - ]); - let constraint = PointwiseJointHardConstraintV1::new( - JointConstraintIdV1::new(1), - JointVisibleTargetV1::Lower, - Wcag22CriterionV1::Sc1411UiComponentOrState, - ); - let program = PointwiseJointPointProgramV1::with_evaluator( - Wcag22Srgb8V1, - ROOT, - LOWER, - UPPER, - vec![constraint], - ) - .unwrap(); - let report = program - .evaluate_revision_bound(domain, observed, session_permit()) - .unwrap(); - - assert_eq!(report.cells().len(), 2); - assert!(!report.cells()[0].decision().is_pass()); - assert!(report.cells()[1].decision().is_pass()); - let PointwiseHardFeasibilityV1::NonEmpty(feasible) = report.classify() else { - panic!("black lower occurrence must be WCAG-feasible over white"); - }; - assert_eq!(feasible.feasible(), &[CandidateOrdinalV1::new(2)]); - let policy = DeclaredTotalOrderV1::new( - feasible.candidate_set(), - vec![CandidateOrdinalV1::new(1), CandidateOrdinalV1::new(2)], - ) - .unwrap(); - let verified = feasible.select(policy).unwrap().recheck().unwrap(); - assert_eq!(verified.ordinal(), CandidateOrdinalV1::new(2)); - assert_eq!(verified.fresh_cells().len(), 1); - assert!(verified.fresh_cells()[0].decision().is_pass()); -} - -#[derive(Clone, Debug)] -struct FailOnCallEvaluatorV1 { - calls: Rc>, - fail_on: u32, -} - -impl PartialEq for FailOnCallEvaluatorV1 { - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.calls, &other.calls) && self.fail_on == other.fail_on - } -} - -impl JointPointEvaluatorV1 for FailOnCallEvaluatorV1 { - type Invocation = (); - type PassEvidence = (); - type ViolationEvidence = (); - type Error = &'static str; - - fn assess( - &self, - _occurrence: &crate::appearance::ResolvedOccurrence, - _invocation: Self::Invocation, - ) -> Result, Self::Error> { - let call = self.calls.get(); - self.calls.set(call + 1); - if call == self.fail_on { - Err("evaluator-fault") - } else { - Ok(HardDecision::Pass(())) - } - } -} - -fn fallible_program( - evaluator: FailOnCallEvaluatorV1, -) -> PointwiseJointPointProgramV1 { - PointwiseJointPointProgramV1::with_evaluator( - evaluator, - ROOT, - LOWER, - UPPER, - vec![PointwiseJointHardConstraintV1::new( - JointConstraintIdV1::new(1), - JointVisibleTargetV1::Upper, - (), - )], - ) - .unwrap() -} - -#[test] -fn evaluator_error_invalidates_the_full_report_and_fresh_recheck() { - let domain = || candidates(vec![candidate(0, ([0; 3], 1.0), ([255; 3], 1.0))]); - let observed = || observation(12, vec![(1, [0; 3])]); - - let immediate = fallible_program(FailOnCallEvaluatorV1 { - calls: Rc::new(Cell::new(0)), - fail_on: 0, - }); - assert!(matches!( - immediate.evaluate_revision_bound(domain(), observed(), session_permit()), - Err(PointwiseJointReportErrorV1::Evaluator("evaluator-fault")) - )); - - let delayed = fallible_program(FailOnCallEvaluatorV1 { - calls: Rc::new(Cell::new(0)), - fail_on: 1, - }); - let report = delayed - .evaluate_revision_bound(domain(), observed(), session_permit()) - .unwrap(); - let PointwiseHardFeasibilityV1::NonEmpty(feasible) = report.classify() else { - panic!("first evaluation must pass"); - }; - let policy = - DeclaredTotalOrderV1::new(feasible.candidate_set(), vec![CandidateOrdinalV1::new(0)]) - .unwrap(); - assert!(matches!( - feasible.select(policy).unwrap().recheck(), - Err(PointwiseSelectedRecheckErrorV1::Evaluator( - "evaluator-fault" - )) - )); -} - -#[test] -fn invalid_domains_and_policies_fail_before_compositing() { - assert_eq!( - JointCandidateSetV1::new(vec![]), - Err(CandidateSetErrorV1::Empty) - ); - assert_eq!( - JointCandidateSetV1::new(vec![ - candidate(1, ([0; 3], 1.0), ([0; 3], 1.0)), - candidate(1, ([1; 3], 1.0), ([1; 3], 1.0)), - ]), - Err(CandidateSetErrorV1::DuplicateOrdinal( - CandidateOrdinalV1::new(1) - )) - ); - assert_eq!( - JointCandidateSetV1::new(vec![ - candidate(1, ([0; 3], 1.0), ([0; 3], 1.0)), - candidate(2, ([0; 3], 1.0), ([0; 3], 1.0)), - ]), - Err(CandidateSetErrorV1::DuplicatePhysicalTuple { - first: CandidateOrdinalV1::new(1), - second: CandidateOrdinalV1::new(2), - }) - ); - assert_eq!( - PointwiseJointPointProgramV1::new(ROOT, LOWER, LOWER, vec![exact_upper(1, [0; 3])]), - Err(JointProgramErrorV1::SamePaintIdentity(LOWER)) - ); - let observed = observation(9, vec![(1, [0; 3])]); - let wrong = JointCandidateSetV1::new(vec![JointCandidateTupleV1::new( - CandidateOrdinalV1::new(0), - paint(PaintId::new(999), [0; 3], 1.0), - paint(UPPER, [0; 3], 1.0), - )]) - .unwrap(); - crate::composition::reset_source_over_evaluation_count(); - assert!(matches!( - program(vec![exact_upper(1, [0; 3])]).evaluate_revision_bound( - wrong, - observed, - session_permit() - ), - Err(PointwiseJointReportErrorV1::CandidatePaintMismatch { - stage: JointVisibleTargetV1::Lower, - .. - }) - )); - assert_eq!(crate::composition::source_over_evaluation_count(), 0); - - let domain = candidates(vec![candidate(0, ([0; 3], 1.0), ([0; 3], 1.0))]); - assert_eq!( - DeclaredTotalOrderV1::new(&domain, vec![]), - Err(SelectionPolicyErrorV1::NotATotalOrder) - ); - assert_eq!( - DeclaredTotalOrderV1::new( - &domain, - vec![CandidateOrdinalV1::new(0), CandidateOrdinalV1::new(0)], - ), - Err(SelectionPolicyErrorV1::NotATotalOrder) - ); -} - -#[test] -fn duplicate_physical_detection_preserves_canonical_ordinal_precedence() { - let first_order = vec![ - candidate(4, ([0; 3], 1.0), ([10; 3], 1.0)), - candidate(9, ([250; 3], 1.0), ([240; 3], 1.0)), - candidate(3, ([0; 3], 1.0), ([10; 3], 1.0)), - candidate(1, ([250; 3], 1.0), ([240; 3], 1.0)), - ]; - let reverse_order = first_order.iter().rev().copied().collect(); - let expected = Err(CandidateSetErrorV1::DuplicatePhysicalTuple { - first: CandidateOrdinalV1::new(1), - second: CandidateOrdinalV1::new(9), - }); - assert_eq!(JointCandidateSetV1::new(first_order), expected); - assert_eq!(JointCandidateSetV1::new(reverse_order), expected); - - assert_eq!( - JointCandidateSetV1::new(vec![ - candidate(7, ([70; 3], 0.5), ([170; 3], 1.0)), - candidate(2, ([70; 3], 0.5), ([170; 3], 1.0)), - candidate(5, ([70; 3], 0.5), ([170; 3], 1.0)), - ]), - Err(CandidateSetErrorV1::DuplicatePhysicalTuple { - first: CandidateOrdinalV1::new(2), - second: CandidateOrdinalV1::new(5), - }) - ); -} - -#[test] -fn large_candidate_domain_remains_ordinal_canonical() { - const COUNT: u32 = 4_096; - let make = |ordinal: u32| { - let bytes = [(ordinal >> 8) as u8, ordinal as u8, 17]; - candidate(ordinal, (bytes, 1.0), ([255, 0, 19], 1.0)) - }; - let input = (0..COUNT).rev().map(make).collect(); - let expected: Vec<_> = (0..COUNT).map(make).collect(); - let domain = JointCandidateSetV1::new(input).unwrap(); - assert_eq!(domain.candidates(), expected); -} - -#[test] -fn cardinality_overflow_is_rejected_by_preflight() { - assert_eq!( - checked_joint_cardinality(usize::MAX, 2, 1), - Err(PointwiseJointReportErrorV1::ResourceExhausted) - ); - assert_eq!( - checked_joint_cardinality(usize::MAX / 2 + 1, 2, 2), - Err(PointwiseJointReportErrorV1::ResourceExhausted) - ); -} diff --git a/crates/labcolors-core/src/lib.rs b/crates/labcolors-core/src/lib.rs index 6f64d18c..db954eb0 100644 --- a/crates/labcolors-core/src/lib.rs +++ b/crates/labcolors-core/src/lib.rs @@ -158,18 +158,8 @@ pub(crate) mod session; #[cfg(test)] mod session_tests; -#[cfg_attr( - not(test), - expect( - dead_code, - reason = "joint-selection internals are used only through the staged Program contract" - ) -)] pub(crate) mod joint; -#[cfg(test)] -mod joint_tests; - #[cfg(test)] mod constraint_tests; diff --git a/crates/labcolors-core/src/program.rs b/crates/labcolors-core/src/program.rs index 663dbf56..d3e2250f 100644 --- a/crates/labcolors-core/src/program.rs +++ b/crates/labcolors-core/src/program.rs @@ -673,11 +673,6 @@ impl RenderCycleV1 { /// Точная причина отказа явно объявленного конечного совместного порядка. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum JointOrderErrorV1 { - /// Одно измерение не содержит кандидатов. - EmptyDomain { - /// Индекс пустого измерения. - dimension: usize, - }, /// Декартова мощность измерений не представима в `usize`. CardinalityOverflow, /// Явный порядок не содержит состояний. @@ -716,8 +711,6 @@ pub(crate) enum JointOrderErrorV1 { /// Фактическое число состояний. actual: usize, }, - /// Для проверки порядка недостаточно ресурсов. - ResourceExhausted, } /// Атомарная и полная ошибка компиляции объявленной программы. @@ -2976,9 +2969,6 @@ impl UpdateErrorV1 { fn map_joint_order_error(error: FiniteJointOrderErrorV1) -> JointOrderErrorV1 { match error { - FiniteJointOrderErrorV1::EmptyDomain { dimension } => { - JointOrderErrorV1::EmptyDomain { dimension } - } FiniteJointOrderErrorV1::CardinalityOverflow => JointOrderErrorV1::CardinalityOverflow, FiniteJointOrderErrorV1::EmptyOrder => JointOrderErrorV1::EmptyOrder, FiniteJointOrderErrorV1::TupleArity { @@ -3010,7 +3000,6 @@ fn map_joint_order_error(error: FiniteJointOrderErrorV1) -> JointOrderErrorV1 { FiniteJointOrderErrorV1::IncompleteOrder { expected, actual } => { JointOrderErrorV1::IncompleteOrder { expected, actual } } - FiniteJointOrderErrorV1::ResourceExhausted => JointOrderErrorV1::ResourceExhausted, } } @@ -3823,21 +3812,3 @@ mod update_error_projection_tests { ); } } - -#[cfg(test)] -mod compile_error_projection_tests { - use super::*; - - #[test] - fn nested_joint_resource_exhaustion_keeps_its_exact_reason_and_site_kind() { - let error = map_program_compile_error(ProgramCompileError::InvalidJointOrder( - FiniteJointOrderErrorV1::ResourceExhausted, - )); - - assert_eq!(error.kind(), CompileErrorKindV1::InvalidJointOrder); - assert_eq!( - error, - CompileErrorV1::InvalidJointOrder(JointOrderErrorV1::ResourceExhausted) - ); - } -} diff --git a/crates/labcolors-core/src/program_joint_integration_tests.rs b/crates/labcolors-core/src/program_joint_integration_tests.rs index 2ea38074..530e0d46 100644 --- a/crates/labcolors-core/src/program_joint_integration_tests.rs +++ b/crates/labcolors-core/src/program_joint_integration_tests.rs @@ -1,3 +1,5 @@ +use core::num::NonZeroUsize; + use crate::Srgb8; use crate::appearance::{ EncodedPointPaintValueV1, OccurrenceId, OpacityInputId, PaintId, SurfaceId, SurfaceInputPortId, @@ -10,7 +12,10 @@ use crate::constraints::{ ProgramVisiblePointBindingV1, ProgramVisiblePointPassEvidence, ProgramVisiblePointViolationEvidence, Wcag22Srgb8V1, assess_program_point_hard, }; -use crate::joint::FiniteJointOrderErrorV1; +use crate::joint::{ + FiniteDomainOrdinalV1, FiniteJointOrderAdmissionErrorV1, FiniteJointOrderErrorV1, + NonEmptyFiniteDomainCardinalitiesV1, admit_finite_joint_order_v1, +}; use crate::lcs_occurrence::{ AdaptingLuminanceCdM2, AppearanceContextId, AppearanceContextSchemaReleaseId, BackgroundLuminanceRatio, ColorSignal, IEC_SRGB_D65_XYZ_FRAME_V1, SurroundProfileId, @@ -296,6 +301,70 @@ impl ProgramConstraintEvaluatorSetV1 for FinalViolationDiagnosticErrorEvaluatorS } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum InjectedEvaluatorFailureV1 { + CandidateSearch, + FreshRecheck, +} + +#[derive(Debug, Clone)] +struct InjectedEvaluatorFailureSetV1 { + failure: InjectedEvaluatorFailureV1, + calls: std::rc::Rc>, +} + +impl InjectedEvaluatorFailureSetV1 { + fn new(failure: InjectedEvaluatorFailureV1) -> Self { + Self { + failure, + calls: std::rc::Rc::new(std::cell::Cell::new(0)), + } + } + + fn call_count(&self) -> usize { + self.calls.get() + } +} + +impl ProgramConstraintEvaluatorSetV1 for InjectedEvaluatorFailureSetV1 { + type Invocation = Srgb8; + type PassEvidence = DiagnosticPoisonPassV1; + type ViolationEvidence = DiagnosticPoisonViolationV1; + type Error = InjectedEvaluatorFailureV1; + + fn assess( + &self, + point: ProgramPointOccurrenceV1, + _invocation: Self::Invocation, + ) -> Result< + HardDecision, + ProgramPointAssessmentErrorV1, + > { + let call = self.calls.get(); + self.calls.set(call + 1); + let must_fail = match self.failure { + InjectedEvaluatorFailureV1::CandidateSearch => call == 0, + InjectedEvaluatorFailureV1::FreshRecheck => call == 1, + }; + if must_fail { + return Err(ProgramPointAssessmentErrorV1::Evaluator(self.failure)); + } + Ok(HardDecision::Pass(DiagnosticPoisonPassV1(point.binding()))) + } + + fn pass_binding(evidence: &Self::PassEvidence) -> ProgramVisiblePointBindingV1 { + evidence.0 + } + + fn violation_binding(evidence: &Self::ViolationEvidence) -> ProgramVisiblePointBindingV1 { + evidence.0 + } + + fn constraint_content(&self, invocation: Self::Invocation) -> ProgramConstraintContentV1 { + ExactSrgb8IdentityV1.program_constraint_content_v1(invocation) + } +} + #[derive(Debug)] struct PanicOnceEvaluatorControlV1 { armed: std::cell::Cell, @@ -584,6 +653,88 @@ fn nested_two_target_program( ])) } +fn nested_alpha_exact_program() -> Program { + Program::new( + vec![ + Source::new(SOURCE, signal(0)), + Source::new(UPPER_SOURCE, signal(0)), + ], + vec![ + Target::finite( + TARGET, + finite_domain(vec![ + candidate_with_opacity(FIRST, 0x00, 0.5), + candidate_with_opacity(SECOND, 0x00, 1.0), + ]), + ), + Target::finite( + UPPER_TARGET, + finite_domain(vec![ + candidate_with_opacity(UPPER_FIRST, 0xFF, 0.5), + candidate_with_opacity(UPPER_SECOND, 0x80, 1.0), + ]), + ), + ], + ObservationGroup::new(GROUP, vec![SURFACE_PORT]), + vec![], + vec![ + Paint::Solid { + id: PAINT, + target: TARGET, + }, + Paint::Solid { + id: UPPER_PAINT, + target: UPPER_TARGET, + }, + ], + vec![ + Surface::Input { + id: BACKDROP, + input: SURFACE_PORT, + }, + Surface::FromOccurrence { + id: DERIVED_SURFACE, + occurrence: OCCURRENCE, + }, + ], + vec![ + Occurrence::new( + OCCURRENCE, + PAINT, + BACKDROP, + CompositionProfile::EncodedSrgb8SourceOverV1, + appearance_context(), + ), + Occurrence::new( + UPPER_OCCURRENCE, + UPPER_PAINT, + DERIVED_SURFACE, + CompositionProfile::EncodedSrgb8SourceOverV1, + appearance_context(), + ), + ], + ConstraintSet::new( + vec![ConstraintInvocation::hard( + ConstraintId::new(1), + UPPER_OCCURRENCE, + Srgb8::new([0x80; 3]), + )], + vec![], + ), + vec![ + OutputBinding::new(OUTPUT, PAINT), + OutputBinding::new(UPPER_OUTPUT, UPPER_PAINT), + ], + ExactSrgb8IdentityV1, + ) + .with_joint_selection(DeclaredJointSelectionV1::new(vec![ + paired_state(FIRST, UPPER_FIRST, false), + paired_state(SECOND, UPPER_FIRST, false), + paired_state(FIRST, UPPER_SECOND, false), + paired_state(SECOND, UPPER_SECOND, false), + ])) +} + #[derive(Clone, Copy)] struct AlphaRenamedJointIds { lower_source: SourceId, @@ -1199,6 +1350,40 @@ fn nested_two_target_selection_ignores_target_and_choice_declaration_order() { ); } +#[test] +fn exact_joint_oracle_covers_nested_alpha_and_every_observed_backdrop() { + // Independent source-over arithmetic for the first two authored states: + // half-white over (half-black over black) is 128, while the same stack over + // white is 192. Making the lower black opaque produces 128 over both + // backdrops, so the second state is the first globally feasible state. + let half_over = |source: u16, backdrop: u16| (source + backdrop).div_ceil(2); + assert_eq!(half_over(0xFF, half_over(0x00, 0x00)), 0x80); + assert_eq!(half_over(0xFF, half_over(0x00, 0xFF)), 0xC0); + assert_eq!(half_over(0xFF, 0x00), 0x80); + + let compiled = nested_alpha_exact_program().compile().unwrap(); + let mut session = compiled.instantiate(STREAM).unwrap(); + let SessionState::Ready { current } = session.commit(update_cases(1, &[0x00, 0xFF])).unwrap() + else { + panic!("the second declared state must be the first exact match over the full ScenarioSet"); + }; + + assert_eq!(current.selected_state_index(), Some(1)); + assert_eq!(current.report().cells().len(), 2); + assert!( + current + .report() + .cells() + .iter() + .all(|cell| cell.candidate_state_index() == 1 && !cell.result().is_violation()) + ); + let outputs = current.outputs(); + assert_eq!(outputs[0].source_signal(), signal(0x00)); + assert_eq!(outputs[0].paint().opacity_bits(), 1.0_f64.to_bits()); + assert_eq!(outputs[1].source_signal(), signal(0xFF)); + assert_eq!(outputs[1].paint().opacity_bits(), 0.5_f64.to_bits()); +} + #[test] fn bijective_source_target_and_candidate_renaming_preserves_joint_evidence() { let canonical_ids = AlphaRenamedJointIds { @@ -1346,6 +1531,57 @@ fn rejected_state_runs_once_and_selected_state_runs_fresh_recheck_twice() { ); } +#[test] +fn evaluator_error_aborts_both_candidate_search_and_fresh_recheck() { + let constraint = ConstraintId::new(1); + for failure in [ + InjectedEvaluatorFailureV1::CandidateSearch, + InjectedEvaluatorFailureV1::FreshRecheck, + ] { + let evaluator = InjectedEvaluatorFailureSetV1::new(failure); + let probe = evaluator.clone(); + let compiled = point_program( + signal(0), + target(vec![candidate(FIRST, 0xFF)]), + vec![ConstraintInvocation::hard( + constraint, + OCCURRENCE, + Srgb8::new([0xFF; 3]), + )], + vec![], + evaluator, + ) + .with_joint_selection(DeclaredJointSelectionV1::new(vec![state(FIRST)])) + .compile() + .unwrap(); + let mut session = compiled.instantiate(STREAM).unwrap(); + + let error = match session.commit(update(1, 0x00)) { + Ok(_) => panic!("an evaluator failure must invalidate the whole prospective update"), + Err(error) => error, + }; + assert_eq!( + error, + SessionUpdateError::Plan(ProgramSessionEvaluationError::Evaluator { + case_index: 0, + constraint, + occurrence: OCCURRENCE, + context: appearance_context(), + source: failure, + }) + ); + assert_eq!( + probe.call_count(), + match failure { + InjectedEvaluatorFailureV1::CandidateSearch => 1, + InjectedEvaluatorFailureV1::FreshRecheck => 2, + }, + "one hard cell is assessed once in candidate search and only a passing candidate reaches fresh recheck", + ); + assert!(matches!(session.state(), SessionState::Waiting)); + } +} + #[test] fn report_evaluator_error_cannot_poison_candidate_search_or_change_selection() { let report_invocation = Wcag22CriterionV1::Sc143TextDefault; @@ -2282,3 +2518,97 @@ fn duplicate_joint_tuple_is_rejected_before_runtime() { }) ); } + +#[test] +fn finite_joint_order_admission_is_total_over_typed_nonempty_domains() { + let ordinal = |index| FiniteDomainOrdinalV1::new(index); + let nonzero = |value| NonZeroUsize::new(value).unwrap(); + let domains = |first, rest: Vec| { + NonEmptyFiniteDomainCardinalitiesV1::new(nonzero(first), rest.into_boxed_slice()) + }; + + let admitted = admit_finite_joint_order_v1( + &domains(2, vec![]), + vec![vec![ordinal(1)], vec![ordinal(0)]], + ) + .unwrap(); + assert_eq!( + admitted + .tuples() + .map(|tuple| tuple.iter().map(|item| item.index()).collect::>()) + .collect::>(), + vec![vec![1], vec![0]], + ); + + let admitted = admit_finite_joint_order_v1( + &domains(2, vec![nonzero(2)]), + vec![ + vec![ordinal(1), ordinal(0)], + vec![ordinal(0), ordinal(1)], + vec![ordinal(1), ordinal(1)], + vec![ordinal(0), ordinal(0)], + ], + ) + .unwrap(); + assert_eq!(admitted.state_count(), 4); + assert_eq!( + admitted + .tuples() + .map(|tuple| tuple.iter().map(|item| item.index()).collect::>()) + .collect::>(), + vec![vec![1, 0], vec![0, 1], vec![1, 1], vec![0, 0]], + ); + + for (domain_lengths, authored, expected) in [ + ( + domains(1, vec![]), + vec![], + FiniteJointOrderErrorV1::EmptyOrder, + ), + ( + domains(2, vec![]), + vec![vec![ordinal(0)]], + FiniteJointOrderErrorV1::IncompleteOrder { + expected: 2, + actual: 1, + }, + ), + ( + domains(1, vec![]), + vec![vec![]], + FiniteJointOrderErrorV1::TupleArity { + tuple: 0, + expected: 1, + actual: 0, + }, + ), + ( + domains(1, vec![]), + vec![vec![ordinal(1)]], + FiniteJointOrderErrorV1::OrdinalOutOfDomain { + tuple: 0, + dimension: 0, + ordinal: 1, + domain_len: 1, + }, + ), + ( + domains(2, vec![]), + vec![vec![ordinal(0)], vec![ordinal(0)]], + FiniteJointOrderErrorV1::DuplicateTuple { + first: 0, + duplicate: 1, + }, + ), + ( + domains(usize::MAX, vec![nonzero(2)]), + vec![vec![ordinal(0), ordinal(0)]], + FiniteJointOrderErrorV1::CardinalityOverflow, + ), + ] { + assert_eq!( + admit_finite_joint_order_v1(&domain_lengths, authored), + Err(FiniteJointOrderAdmissionErrorV1::Authored(expected)), + ); + } +} diff --git a/crates/labcolors-core/src/program_session.rs b/crates/labcolors-core/src/program_session.rs index 9b3f6ed4..216d77af 100644 --- a/crates/labcolors-core/src/program_session.rs +++ b/crates/labcolors-core/src/program_session.rs @@ -50,8 +50,8 @@ use crate::constraints::{ ProgramVisiblePointViolationEvidence, Wcag22Srgb8V1, assess_program_point_hard, }; use crate::joint::{ - AdmittedFiniteJointOrderV1, FiniteDomainOrdinalV1, FiniteJointOrderErrorV1, - admit_finite_joint_order_v1, + AdmittedFiniteJointOrderV1, FiniteDomainOrdinalV1, FiniteJointOrderAdmissionErrorV1, + FiniteJointOrderErrorV1, NonEmptyFiniteDomainCardinalitiesV1, admit_finite_joint_order_v1, }; use crate::lcs_occurrence::{AppearanceContextId, ColorSignal}; use crate::observation::{ @@ -1488,8 +1488,130 @@ struct CompiledFiniteTargetV1 { candidates: Box<[EncodedPointPaintValueV1]>, } -struct CompiledJointSelectionV1 { - order: AdmittedFiniteJointOrderV1, +struct CompiledFiniteTargetsV1 { + first: CompiledFiniteTargetV1, + rest: Box<[CompiledFiniteTargetV1]>, +} + +impl CompiledFiniteTargetsV1 { + fn iter(&self) -> impl Iterator { + std::iter::once(&self.first).chain(self.rest.iter()) + } + + fn len(&self) -> usize { + self.rest.len() + 1 + } +} + +mod admitted_compiled_joint_space { + use super::*; + + pub(super) enum AdmissionErrorV1 { + Authored(FiniteJointOrderErrorV1), + ResourceExhausted, + InternalInvariant, + } + + pub(super) struct AdmittedCompiledJointSpaceV1 { + targets: CompiledFiniteTargetsV1, + order: AdmittedFiniteJointOrderV1, + } + + pub(super) struct AdmittedCompiledJointStateV1<'space> { + index: usize, + targets: &'space CompiledFiniteTargetsV1, + tuple: &'space [FiniteDomainOrdinalV1], + } + + impl AdmittedCompiledJointSpaceV1 { + pub(super) fn admit( + targets: CompiledFiniteTargetsV1, + authored: Vec>, + ) -> Result { + let remaining_target_count = targets.len() - 1; + let mut target_dimensions = targets.iter(); + let first = target_dimensions + .next() + .and_then(|target| NonZeroUsize::new(target.candidates.len())) + .ok_or(AdmissionErrorV1::InternalInvariant)?; + let mut rest = Vec::new(); + rest.try_reserve_exact(remaining_target_count) + .map_err(|_| AdmissionErrorV1::ResourceExhausted)?; + for target in target_dimensions { + rest.push( + NonZeroUsize::new(target.candidates.len()) + .ok_or(AdmissionErrorV1::InternalInvariant)?, + ); + } + let cardinalities = + NonEmptyFiniteDomainCardinalitiesV1::new(first, rest.into_boxed_slice()); + let order = match admit_finite_joint_order_v1(&cardinalities, authored) { + Ok(order) => order, + Err(FiniteJointOrderAdmissionErrorV1::Authored(error)) => { + return Err(AdmissionErrorV1::Authored(error)); + } + Err(FiniteJointOrderAdmissionErrorV1::ResourceExhausted) => { + return Err(AdmissionErrorV1::ResourceExhausted); + } + Err(FiniteJointOrderAdmissionErrorV1::InternalInvariant) => { + return Err(AdmissionErrorV1::InternalInvariant); + } + }; + Ok(Self { targets, order }) + } + + pub(super) fn state_count(&self) -> usize { + self.order.state_count() + } + + pub(super) fn states(&self) -> impl Iterator> { + self.order + .tuples() + .enumerate() + .map(|(index, tuple)| AdmittedCompiledJointStateV1 { + index, + targets: &self.targets, + tuple, + }) + } + } + + impl AdmittedCompiledJointStateV1<'_> { + pub(super) fn index(&self) -> usize { + self.index + } + + pub(super) fn assignments( + &self, + ) -> impl Iterator { + self.targets + .iter() + .zip(self.tuple) + .map(|(target, ordinal)| { + let candidate = target.candidates[ordinal.index()]; + (target, candidate) + }) + } + } +} + +use admitted_compiled_joint_space::{ + AdmissionErrorV1 as CompiledJointSpaceAdmissionErrorV1, AdmittedCompiledJointSpaceV1, + AdmittedCompiledJointStateV1, +}; + +enum CompiledTargetSelectionV1 { + FixedOnly, + Finite(AdmittedCompiledJointSpaceV1), +} + +impl CompiledTargetSelectionV1 { + fn state_count(&self) -> usize { + match self { + Self::FixedOnly => 1, + Self::Finite(space) => space.state_count(), + } + } } struct ProgramEpochV1 @@ -1507,8 +1629,7 @@ where constraint_phases: CompiledConstraintPhasesV1, point_presentations: CompiledPointPresentationsV1, outputs: Box<[CompiledOutputBinding]>, - finite_targets: Box<[CompiledFiniteTargetV1]>, - joint_selection: Option, + target_selection: CompiledTargetSelectionV1, } /// Strong pin одной точной compiled generation Program. Транзакция получает @@ -2384,11 +2505,7 @@ where Evaluation: ProgramConstraintEvaluatorSetV1, ProgramConstraintInvocationOf: Copy, { - let state_count = epoch - .joint_selection - .as_ref() - .map(|selection| selection.order.state_count()) - .unwrap_or(1); + let state_count = epoch.target_selection.state_count(); let can_conflict = epoch .constraint_phases .contains(ProgramEvaluationPhaseV1::Hard); @@ -2454,13 +2571,7 @@ where Evaluation: ProgramConstraintEvaluatorSetV1, ProgramConstraintInvocationOf: Copy, { - let state_count = epoch - .joint_selection - .as_ref() - .map(|selection| selection.order.state_count()) - // Without joint selection the epoch has one fixed configuration, so - // the exhaustive-cell multiplier remains the multiplicative identity. - .unwrap_or(1); + let state_count = epoch.target_selection.state_count(); let can_conflict = epoch .constraint_phases .contains(ProgramEvaluationPhaseV1::Hard); @@ -3025,13 +3136,25 @@ where Evaluation: ProgramConstraintEvaluatorSetV1, ProgramConstraintInvocationOf: Copy, { - let Some(selection) = &epoch.joint_selection else { - return collect_program_candidate_into(runtime, epoch, observation, None, 1, arena, counts); + let space = match &epoch.target_selection { + CompiledTargetSelectionV1::FixedOnly => { + return collect_program_candidate_into( + runtime, + epoch, + observation, + None, + 1, + arena, + counts, + ); + } + CompiledTargetSelectionV1::Finite(space) => space, }; - let state_count = selection.order.state_count(); - for (state_index, tuple) in selection.order.tuples().enumerate() { - apply_joint_candidate::(runtime, &epoch.finite_targets, tuple)?; + let state_count = space.state_count(); + for state in space.states() { + let state_index = state.index(); + apply_joint_candidate::(runtime, &state)?; if !scan_program_candidate( runtime, epoch, @@ -3042,7 +3165,7 @@ where )? { // A selected tuple is never certified from its allocation-free // search pass. Re-apply and collect fresh terminal evidence. - apply_joint_candidate::(runtime, &epoch.finite_targets, tuple)?; + apply_joint_candidate::(runtime, &state)?; match collect_program_candidate_into( runtime, epoch, @@ -3090,8 +3213,9 @@ where return Err(ProgramSessionEvaluationError::InternalInvariant); } - for (state_index, tuple) in selection.order.tuples().enumerate() { - apply_joint_candidate::(runtime, &epoch.finite_targets, tuple)?; + for state in space.states() { + let state_index = state.index(); + apply_joint_candidate::(runtime, &state)?; if !scan_program_candidate( runtime, epoch, @@ -3115,8 +3239,9 @@ where .constraint_phases .contains(ProgramEvaluationPhaseV1::ReportOnly) { - for (state_index, tuple) in selection.order.tuples().enumerate() { - apply_joint_candidate::(runtime, &epoch.finite_targets, tuple)?; + for state in space.states() { + let state_index = state.index(); + apply_joint_candidate::(runtime, &state)?; if scan_program_candidate( runtime, epoch, @@ -3156,24 +3281,16 @@ where fn apply_joint_candidate( runtime: &mut ProgramEvaluationRuntimeV1<'_>, - targets: &[CompiledFiniteTargetV1], - tuple: &[FiniteDomainOrdinalV1], + state: &AdmittedCompiledJointStateV1<'_>, ) -> Result<(), ProgramSessionEvaluationError>> where Evaluation: ProgramConstraintEvaluatorSetV1, ProgramConstraintInvocationOf: Copy, { - if targets.len() != tuple.len() { - return Err(ProgramSessionEvaluationError::InternalInvariant); - } - for (target, ordinal) in targets.iter().zip(tuple) { - let candidate = target - .candidates - .get(ordinal.index()) - .ok_or(ProgramSessionEvaluationError::InternalInvariant)?; + for (target, candidate) in state.assignments() { runtime .bindings - .overwrite_paint_input_at(target.binding, *candidate) + .overwrite_paint_input_at(target.binding, candidate) .map_err(map_program_execution_binding_error)?; } Ok(()) @@ -3719,7 +3836,7 @@ where let observation_schema = canonicalize_observation_schema(surface_input_ports) .map_err(map_observation_schema_compile_error)?; - let (finite_targets, joint_selection) = compile_targets( + let target_selection = compile_targets( &graph, &mut program.targets, program.joint_selection.as_mut(), @@ -3756,8 +3873,7 @@ where constraint_phases, point_presentations, outputs, - finite_targets, - joint_selection, + target_selection, }) } @@ -4162,17 +4278,11 @@ fn compile_targets( graph: &CompiledAppearanceGraph, authored_targets: &mut [Target], authored_selection: Option<&mut DeclaredJointSelectionV1>, -) -> Result< - ( - Box<[CompiledFiniteTargetV1]>, - Option, - ), - ProgramCompileError, -> { +) -> Result { struct CanonicalFiniteTargetV1<'a> { id: TargetId, binding: CompiledPaintInputSlotV1, - candidates: &'a [TargetCandidateV1], + domain: &'a FinitePaintDomainV1, } let mut compiled = Vec::new(); @@ -4226,13 +4336,13 @@ fn compile_targets( compiled.push(CanonicalFiniteTargetV1 { id: target.id, binding, - candidates, + domain, }); } if compiled.is_empty() { return match authored_selection { - None => Ok((Box::new([]), None)), + None => Ok(CompiledTargetSelectionV1::FixedOnly), Some(_) => Err(ProgramCompileError::JointSelectionWithoutTargets), }; } @@ -4284,7 +4394,8 @@ fn compile_targets( })?; let choice = authored_state.choices[choice_index]; let candidate_index = target - .candidates + .domain + .candidates() .binary_search_by_key(&choice.candidate, |candidate| candidate.id) .map_err(|_| ProgramCompileError::JointStateUnknownCandidate { state: state_index, @@ -4296,32 +4407,54 @@ fn compile_targets( authored_tuples.push(tuple); } - let mut domain_lengths = Vec::new(); - domain_lengths - .try_reserve_exact(compiled.len()) - .map_err(|_| ProgramCompileError::ResourceExhausted)?; - domain_lengths.extend(compiled.iter().map(|target| target.candidates.len())); - let order = admit_finite_joint_order_v1(&domain_lengths, authored_tuples) - .map_err(ProgramCompileError::InvalidJointOrder)?; - let mut runtime_targets = Vec::new(); - runtime_targets - .try_reserve_exact(compiled.len()) - .map_err(|_| ProgramCompileError::ResourceExhausted)?; - for target in compiled { + let lower_target = |target: CanonicalFiniteTargetV1<'_>| { let mut candidates = Vec::new(); candidates - .try_reserve_exact(target.candidates.len()) + .try_reserve_exact(target.domain.candidates().len()) .map_err(|_| ProgramCompileError::ResourceExhausted)?; - candidates.extend(target.candidates.iter().map(|candidate| candidate.value())); - runtime_targets.push(CompiledFiniteTargetV1 { + candidates.extend( + target + .domain + .candidates() + .iter() + .map(|candidate| candidate.value()), + ); + Ok(CompiledFiniteTargetV1 { binding: target.binding, candidates: candidates.into_boxed_slice(), - }); + }) + }; + let mut compiled = compiled.into_iter(); + let first = lower_target( + compiled + .next() + .ok_or(ProgramCompileError::InternalInvariant)?, + )?; + let mut rest = Vec::new(); + rest.try_reserve_exact(compiled.len()) + .map_err(|_| ProgramCompileError::ResourceExhausted)?; + for target in compiled { + rest.push(lower_target(target)?); } - Ok(( - runtime_targets.into_boxed_slice(), - Some(CompiledJointSelectionV1 { order }), - )) + let targets = CompiledFiniteTargetsV1 { + first, + rest: rest.into_boxed_slice(), + }; + let space = + AdmittedCompiledJointSpaceV1::admit(targets, authored_tuples).map_err( + |error| match error { + CompiledJointSpaceAdmissionErrorV1::Authored(error) => { + ProgramCompileError::InvalidJointOrder(error) + } + CompiledJointSpaceAdmissionErrorV1::ResourceExhausted => { + ProgramCompileError::ResourceExhausted + } + CompiledJointSpaceAdmissionErrorV1::InternalInvariant => { + ProgramCompileError::InternalInvariant + } + }, + )?; + Ok(CompiledTargetSelectionV1::Finite(space)) } fn compile_occurrence_contexts( diff --git a/crates/labcolors-core/src/session.rs b/crates/labcolors-core/src/session.rs index a6335064..61b25a5f 100644 --- a/crates/labcolors-core/src/session.rs +++ b/crates/labcolors-core/src/session.rs @@ -34,11 +34,6 @@ impl SessionObservationBindingPermitV1 { const fn mint() -> Self { Self { _private: () } } - - #[cfg(test)] - pub(crate) const fn for_test() -> Self { - Self { _private: () } - } } /// Complete result of evaluating one admitted observation. diff --git a/scripts/verify_point_support_surplus.py b/scripts/verify_point_support_surplus.py index 16f023be..3ac2b39a 100755 --- a/scripts/verify_point_support_surplus.py +++ b/scripts/verify_point_support_surplus.py @@ -58,7 +58,7 @@ SOURCE_BINDING_LAW = "point-support-rust-whole-file-semantic-cone-v2" SOURCE_BINDING_DOMAIN = b"labcolors.point-support.rust-whole-file-semantic-cone.v2" EXPECTED_SOURCE_CAPSULE_SHA256 = ( - "de9e26c5da5d24cd1a76092c5e5caea02fe69f1f18d367fbf0ea7635bee819a9" + "ad5f8cf281a796533e4784c827193fd5aa723a1161facdbfa6c48308099b4ba0" ) EXPECTED_Q55_PROOF_SHA256 = ( "ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"