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 d8f7a665..efdfdd99 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 @@ -48,11 +48,11 @@ "sha256": "8bf5d5a1c0f00ce245a1ecb18b923aa1631483345962d72b633e466c242a8a1d" }, { - "bytes": 17408, + "bytes": 18517, "license": "MIT", "path": "crates/labcolors-core/src/lib.rs", "role": "module_registration_source", - "sha256": "0a43c29b5dcff9c344443c62e8dddfcc82e516ac7ca6e6708f11354202e9fcd9" + "sha256": "37b3cae3969a85d27863ab8177a056c1736cbc45e75e5bbcfc5aa603a7200cd7" }, { "bytes": 39439, 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 92b1b39f..adfefcad 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 @@ -a27604c3306a3a40818594c173b09e7feda925653eb9658d64c9b9784526c48e receipt-v1.json +959f1b235e4a08004c0632c87678622b4b688ded4fd507cd28f9d41072fd3ac0 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 23659f54..4daba6bc 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":"70bc203cff409eed349ce85878bcef12dd58e29eea292185e178037216bd40b8","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":"e0de826414c860dff2dd348fdaba71285ca1aac067a90fe72d71a58d0cb94e8f","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":"fecab7250ec96171daf3d6fe75c663e45b2356a2b0a8bb6f5ee418c9d62654a1"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"0afc3232dd0c3b5b94596813d0a5e8aef4a2dea74b09dbae05f41e64d4c8ca1b"},{"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":"f18bcafd7f911e84049cf9dae9673d6d6e0ed1fd50a9a7635a0ff597953b7727"},{"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":"0a43c29b5dcff9c344443c62e8dddfcc82e516ac7ca6e6708f11354202e9fcd9"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"b0cfec5c9fe798abd5492260aac3caf87685f7e27f4f9628b01f386ef3f6ac7d"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"6f6a376ff036d3d65960c004e6566e1bca580f19f5bd3cd333a80b0da5b5c242"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"38c65b46a3e392b399092e8c4834d7f31acb2b91eff626d6faa1b1aaa90b52f3"},{"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":"cb9a1632bcd868870bacdc99a29c840c7d3f72864e018e330ccc5425d041d683"} +{"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":"4e664ae3a7084a47fa9e2786813105e2042c253569d3fe9d11562b57d963f0d6","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":"685aa65bf1971f0ec729efdd133b9fa7a052689b02b49e3d17b5af636fdbe2d5","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":"fecab7250ec96171daf3d6fe75c663e45b2356a2b0a8bb6f5ee418c9d62654a1"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"0afc3232dd0c3b5b94596813d0a5e8aef4a2dea74b09dbae05f41e64d4c8ca1b"},{"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":"f18bcafd7f911e84049cf9dae9673d6d6e0ed1fd50a9a7635a0ff597953b7727"},{"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":"37b3cae3969a85d27863ab8177a056c1736cbc45e75e5bbcfc5aa603a7200cd7"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"b0cfec5c9fe798abd5492260aac3caf87685f7e27f4f9628b01f386ef3f6ac7d"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"6f6a376ff036d3d65960c004e6566e1bca580f19f5bd3cd333a80b0da5b5c242"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"38c65b46a3e392b399092e8c4834d7f31acb2b91eff626d6faa1b1aaa90b52f3"},{"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":"8b7731b8a76874baddaf623d0706bce1fe1c26d1c2ac6091863f71090b05438e"} diff --git a/crates/labcolors-core/src/contextual_region_tests.rs b/crates/labcolors-core/src/contextual_region_tests.rs index 0f50b142..1e1ea2b6 100644 --- a/crates/labcolors-core/src/contextual_region_tests.rs +++ b/crates/labcolors-core/src/contextual_region_tests.rs @@ -21,14 +21,14 @@ use crate::lcs_occurrence::{ const FORMULA_SPEC_BYTES: &[u8] = include_bytes!("../contracts/contextual-region-formula-v1.lcir"); const FORMULA_SPEC_DOMAIN: &[u8] = b"labcolors.nominal-exact-real-lift.ascii-ssa.v1\0"; -const POSITIVE_ZERO: u64 = 0x0000_0000_0000_0000; +pub(crate) const POSITIVE_ZERO: u64 = 0x0000_0000_0000_0000; const NEGATIVE_ZERO: u64 = 0x8000_0000_0000_0000; const MIN_SUBNORMAL: u64 = 0x0000_0000_0000_0001; const MIN_NORMAL: u64 = 0x0010_0000_0000_0000; const NEGATIVE_MIN_SUBNORMAL: u64 = 0x8000_0000_0000_0001; -const ONE: u64 = 0x3ff0_0000_0000_0000; +pub(crate) const ONE: u64 = 0x3ff0_0000_0000_0000; const ONE_AND_HALF: u64 = 0x3ff8_0000_0000_0000; -const TWO: u64 = 0x4000_0000_0000_0000; +pub(crate) const TWO: u64 = 0x4000_0000_0000_0000; const TWO_AND_HALF: u64 = 0x4004_0000_0000_0000; const THREE: u64 = 0x4008_0000_0000_0000; const FOUR: u64 = 0x4010_0000_0000_0000; @@ -37,7 +37,7 @@ const NEGATIVE_ONE: u64 = 0xbff0_0000_0000_0000; const MAX_FINITE: u64 = 0x7fef_ffff_ffff_ffff; const NEGATIVE_MAX_FINITE: u64 = 0xffef_ffff_ffff_ffff; -fn context(frame: crate::lcs_occurrence::ColorimetricFrameId) -> AppearanceContextId { +pub(crate) fn context(frame: crate::lcs_occurrence::ColorimetricFrameId) -> AppearanceContextId { context_with(frame, 64.0, 0.2, SurroundProfileId::AverageV1) } @@ -60,7 +60,7 @@ fn knot(tone: u64, center_a: u64, center_b: u64, radius_squared: u64) -> TubeKno TubeKnotBitsV1::new(tone, center_a, center_b, radius_squared) } -fn region_with_centers(centers: [[u64; 2]; 2]) -> PiecewiseLinearCartesianTubeV1 { +pub(crate) fn region_with_centers(centers: [[u64; 2]; 2]) -> PiecewiseLinearCartesianTubeV1 { PiecewiseLinearCartesianTubeV1::try_from_bits( Shape2BitsV1::new(ONE, POSITIVE_ZERO, ONE), &[ @@ -71,7 +71,7 @@ fn region_with_centers(centers: [[u64; 2]; 2]) -> PiecewiseLinearCartesianTubeV1 .unwrap() } -fn pipeline(context: AppearanceContextId) -> ContextualRegionPipelineV1 { +pub(crate) fn pipeline(context: AppearanceContextId) -> ContextualRegionPipelineV1 { pipeline_with_release(context, MODELED_LCS_OCCURRENCE_RELEASE_V1) } diff --git a/crates/labcolors-core/src/family_artifact.rs b/crates/labcolors-core/src/family_artifact.rs index 43418c76..3f32bdf0 100644 --- a/crates/labcolors-core/src/family_artifact.rs +++ b/crates/labcolors-core/src/family_artifact.rs @@ -186,7 +186,9 @@ impl FamilyImageCertificateV2 { /// /// Потребитель сверяет его с `ContextualRegionFamilyProviderV1`, иначе /// доверенная запись говорит лишь «этот артефакт цел», но не «этот - /// артефакт — образ того региона, который я спрашивал». + /// артефакт — образ того региона, который я спрашивал». Сам loader этот + /// адрес не интерпретирует: сверка принадлежит `family_definition_binding` + /// и является единственным её местом. pub(crate) const fn definition_digest(self) -> FamilyDefinitionDigestV2 { self.definition_digest } @@ -280,6 +282,39 @@ impl FamilyImageCertificateV2 { self } + /// Переадресует certificate на другое определение, пересчитывая semantic + /// release и receipt. + /// + /// Когерентность здесь обязательна, а не удобна: guard определения обязан + /// отказывать входу, который `parse_trusted` принимает. Некогерентный + /// certificate доказывал бы отказ на недостижимом значении. + /// Восстанавливает когерентность после правки поля преимиджа. + /// + /// Порядок обязателен: receipt покрывает `semantic_release`, поэтому + /// пересчёт release должен предшествовать. Мутатор, повторивший блок с + /// переставленным порядком, дал бы запись, которую `parse_trusted` + /// отвергает, — и тест доказывал бы отказ на недостижимом входе. Один + /// helper делает эту ошибку непредставимой. + #[cfg(test)] + fn recohere_for_test(mut self) -> Self { + self.semantic_release = semantic_family_release_id_v2( + self.definition_digest, + self.image_digest, + self.member_count, + ); + self.artifact_receipt = artifact_receipt(self); + self + } + + #[cfg(test)] + pub(crate) fn definition_with_coherent_certificate_for_test( + mut self, + definition_digest: FamilyDefinitionDigestV2, + ) -> Self { + self.definition_digest = definition_digest; + self.recohere_for_test() + } + #[cfg(test)] pub(crate) fn semantic_mismatch_with_valid_receipt_for_test(mut self) -> Self { let mut bytes = *self.semantic_release.as_bytes(); @@ -294,13 +329,7 @@ impl FamilyImageCertificateV2 { let mut bytes = *self.image_digest.as_bytes(); bytes[0] ^= 1; self.image_digest = CanonicalFamilyImageDigestV2::from_digest(bytes); - self.semantic_release = semantic_family_release_id_v2( - self.definition_digest, - self.image_digest, - self.member_count, - ); - self.artifact_receipt = artifact_receipt(self); - self + self.recohere_for_test() } #[cfg(test)] @@ -316,13 +345,7 @@ impl FamilyImageCertificateV2 { member_count: u64, ) -> Self { self.member_count = member_count; - self.semantic_release = semantic_family_release_id_v2( - self.definition_digest, - self.image_digest, - self.member_count, - ); - self.artifact_receipt = artifact_receipt(self); - self + self.recohere_for_test() } } diff --git a/crates/labcolors-core/src/family_definition_binding.rs b/crates/labcolors-core/src/family_definition_binding.rs new file mode 100644 index 00000000..bcc46c74 --- /dev/null +++ b/crates/labcolors-core/src/family_definition_binding.rs @@ -0,0 +1,99 @@ +//! Связывание доверенного certificate с адресом спрошенного определения. +//! +//! `contextual_region` отвечает, какой content address имеет спрошенный регион. +//! `family_artifact` отвечает, что байты точно соответствуют доверенному +//! certificate. Ни одна из границ не отвечает на вопрос «а этот certificate +//! вообще про спрошенный регион?»: целый artifact ДРУГОГО региона проходит обе +//! проверки. Модуль замыкает цепь одним сравнением двух адресов. + +#![cfg_attr( + not(test), + expect( + dead_code, + reason = "V5b2b keeps the definition-bound admission private until the public family provider cutover" + ) +)] + +use core::fmt; + +use crate::contextual_region::{ + ContextualRegionFamilyProviderV1, ContextualRegionPipelineV1, PiecewiseLinearCartesianTubeV1, +}; +use crate::family::FamilyDefinitionDigestV2; +use crate::family_artifact::{ + AdmittedFamilyArtifactV2, EncodedFamilyArtifactV2, FamilyArtifactLoadErrorV1, + FamilyArtifactLoaderV1, FamilyImageCertificateV2, +}; + +/// Причина, по которой artifact не допущен как образ спрошенного региона. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum DefinitionBoundFamilyLoadErrorV1 { + /// Certificate доверен и цел, но адресует другое определение. + ForeignDefinition { + asked: FamilyDefinitionDigestV2, + certified: FamilyDefinitionDigestV2, + }, + /// Certificate про спрошенное определение, но transport его не подтвердил. + Artifact(FamilyArtifactLoadErrorV1), +} + +/// Неуспех возвращает те же owned bytes: диагностика, исправление или повтор +/// не требуют refetch и clone. +pub(crate) struct DefinitionBoundFamilyLoadFailureV1 { + cause: DefinitionBoundFamilyLoadErrorV1, + encoded: EncodedFamilyArtifactV2, +} + +impl fmt::Debug for DefinitionBoundFamilyLoadFailureV1 { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("DefinitionBoundFamilyLoadFailureV1") + .field("cause", &self.cause) + .finish_non_exhaustive() + } +} + +impl DefinitionBoundFamilyLoadFailureV1 { + pub(crate) const fn cause(&self) -> DefinitionBoundFamilyLoadErrorV1 { + self.cause + } + + pub(crate) fn into_parts(self) -> (DefinitionBoundFamilyLoadErrorV1, EncodedFamilyArtifactV2) { + (self.cause, self.encoded) + } +} + +/// Единственный вход, допускающий artifact к тому определению, которое +/// потребитель действительно спросил. +pub(crate) struct DefinitionBoundFamilyLoaderV1; + +impl DefinitionBoundFamilyLoaderV1 { + /// Спрошенный регион задаётся `pipeline` и `region`; семейство приходит + /// извне как доверенный `certificate` и его `encoded` bytes. + /// + /// Сравнение адресов идёт по двум записям и завершается до parse envelope, + /// хеширования payload и decode образа: чужое определение стоит одного + /// content address, а не мегабайтов. + pub(crate) fn load( + pipeline: ContextualRegionPipelineV1, + region: &PiecewiseLinearCartesianTubeV1, + certificate: FamilyImageCertificateV2, + encoded: EncodedFamilyArtifactV2, + ) -> Result { + let asked = ContextualRegionFamilyProviderV1::definition_digest(pipeline, region); + let certified = certificate.definition_digest(); + if asked != certified { + return Err(DefinitionBoundFamilyLoadFailureV1 { + cause: DefinitionBoundFamilyLoadErrorV1::ForeignDefinition { asked, certified }, + encoded, + }); + } + FamilyArtifactLoaderV1::load(certificate, encoded).map_err(|failure| { + let (cause, encoded) = failure.into_parts(); + DefinitionBoundFamilyLoadFailureV1 { + cause: DefinitionBoundFamilyLoadErrorV1::Artifact(cause), + encoded, + } + }) + } +} diff --git a/crates/labcolors-core/src/family_definition_binding_tests.rs b/crates/labcolors-core/src/family_definition_binding_tests.rs new file mode 100644 index 00000000..1e334a08 --- /dev/null +++ b/crates/labcolors-core/src/family_definition_binding_tests.rs @@ -0,0 +1,242 @@ +//! Certificate допускается только к тому определению, которое спрошено. + +use core::cell::Cell; + +use crate::Srgb8; +use crate::contextual_region::{ + ContextualRegionFamilyProviderV1, ContextualRegionPipelineV1, PiecewiseLinearCartesianTubeV1, +}; +use crate::contextual_region_tests::{ + ONE, POSITIVE_ZERO, TWO, context, pipeline, region_with_centers, +}; +use crate::family::FamilyDefinitionDigestV2; +use crate::family_artifact::{ + EncodedFamilyArtifactV2, FAMILY_ARTIFACT_PAYLOAD_DIGEST_CALLS, + FAMILY_CERTIFICATE_RECORD_LEN_V2, FamilyArtifactLoadErrorV1, FamilyArtifactLoaderV1, + FamilyImageCertificateV2, encode_raw_bitmap24_family_artifact_v2_for_test, +}; +use crate::family_definition_binding::{ + DefinitionBoundFamilyLoadErrorV1, DefinitionBoundFamilyLoaderV1, +}; +use crate::lcs_occurrence::{ColorSignal, IEC_SRGB_D65_XYZ_FRAME_V1}; + +fn asked_pipeline() -> ContextualRegionPipelineV1 { + pipeline(context(IEC_SRGB_D65_XYZ_FRAME_V1)) +} + +/// Регионы отличаются только центрами узлов: геометрия семейства другая, а +/// весь остальной pipeline тот же. +fn asked_region() -> PiecewiseLinearCartesianTubeV1 { + region_with_centers([[POSITIVE_ZERO; 2]; 2]) +} + +fn other_region() -> PiecewiseLinearCartesianTubeV1 { + region_with_centers([[ONE, TWO], [TWO, ONE]]) +} + +fn members() -> Vec { + [[0, 0, 1], [0, 0, 2], [0, 0, 255]] + .into_iter() + .map(Srgb8::new) + .map(ColorSignal::from_srgb8) + .collect() +} + +/// Внешний registry поставляет доверенную запись сертификата образа именно +/// этого региона и — отдельным каналом — недоверенные bytes артефакта. +/// +/// Запись разбирается тем же и единственным входом, что и у потребителя: +/// `FamilyImageCertificateV2::parse_trusted`. Своего способа получить +/// certificate у guard-а нет, и здесь он тоже не заводится. +fn artifact_of( + region: &PiecewiseLinearCartesianTubeV1, +) -> (FamilyImageCertificateV2, EncodedFamilyArtifactV2) { + let definition = ContextualRegionFamilyProviderV1::definition_digest(asked_pipeline(), region); + let (minted, encoded) = + encode_raw_bitmap24_family_artifact_v2_for_test(definition, &members()).unwrap(); + let bytes = encoded.clone().into_bytes(); + let trusted = + FamilyImageCertificateV2::parse_trusted(&bytes[..FAMILY_CERTIFICATE_RECORD_LEN_V2]) + .unwrap(); + // Запись артефакта и minted certificate — одно значение, а не два + // согласуемых источника: расхождение здесь означало бы, что guard проверяет + // не то, что предъявит потребитель. + assert_eq!(trusted, minted); + (trusted, encoded) +} + +#[test] +fn the_artifact_of_the_asked_region_is_admitted() { + let region = asked_region(); + let (certificate, encoded) = artifact_of(®ion); + + let admitted = + DefinitionBoundFamilyLoaderV1::load(asked_pipeline(), ®ion, certificate, encoded) + .unwrap(); + + assert_eq!(admitted.semantic_release(), certificate.semantic_release()); + assert_eq!(admitted.artifact_receipt(), certificate.artifact_receipt()); + for member in members() { + assert!(admitted.contains(member), "missing member {member:?}"); + } +} + +#[test] +fn an_intact_artifact_of_another_region_is_refused_by_type() { + let asked = asked_region(); + let other = other_region(); + let (certificate, encoded) = artifact_of(&other); + let allocation = encoded.allocation_ptr_for_test(); + + let failure = + DefinitionBoundFamilyLoaderV1::load(asked_pipeline(), &asked, certificate, encoded) + .unwrap_err(); + + assert_eq!( + failure.cause(), + DefinitionBoundFamilyLoadErrorV1::ForeignDefinition { + asked: ContextualRegionFamilyProviderV1::definition_digest(asked_pipeline(), &asked), + certified: ContextualRegionFamilyProviderV1::definition_digest( + asked_pipeline(), + &other, + ), + }, + ); + let (_, returned) = failure.into_parts(); + assert_eq!(returned.allocation_ptr_for_test(), allocation); + // Отвергнут не повреждённый artifact: те же bytes и тот же certificate + // безупречны для transport-границы, они лишь про другое определение. + FamilyArtifactLoaderV1::load(certificate, returned).unwrap(); +} + +#[test] +fn a_foreign_definition_is_refused_before_any_payload_work() { + let asked = asked_region(); + let (foreign_certificate, foreign_encoded) = artifact_of(&other_region()); + let (asked_certificate, asked_encoded) = artifact_of(&asked); + + FAMILY_ARTIFACT_PAYLOAD_DIGEST_CALLS.with(|calls| calls.set(0)); + let failure = DefinitionBoundFamilyLoaderV1::load( + asked_pipeline(), + &asked, + foreign_certificate, + foreign_encoded, + ) + .unwrap_err(); + let refused_cost = FAMILY_ARTIFACT_PAYLOAD_DIGEST_CALLS.with(Cell::get); + + assert!(matches!( + failure.cause(), + DefinitionBoundFamilyLoadErrorV1::ForeignDefinition { .. }, + )); + // Payload digest — первая дорогая работа над 2 MiB; decode образа идёт + // строго после него, поэтому ноль здесь закрывает обе стадии. + assert_eq!( + refused_cost, 0, + "foreign definition must be refused on the record, before payload work", + ); + + // Anti-vacuity: счётчик действительно наблюдает дорогую работу, когда + // адрес совпал. + DefinitionBoundFamilyLoaderV1::load(asked_pipeline(), &asked, asked_certificate, asked_encoded) + .unwrap(); + assert_eq!(FAMILY_ARTIFACT_PAYLOAD_DIGEST_CALLS.with(Cell::get), 1); +} + +/// Адрес определения сравнивается целиком. Сравнение любой собственной части +/// адреса допустило бы чужой образ, совпавший ровно на сравниваемой части, — +/// поэтому расхождение в КАЖДОЙ из 32 позиций обязано отказывать по отдельности. +/// +/// Каждый отвергаемый certificate здесь когерентен и предъявляется как запись, +/// которую `parse_trusted` принимает: отказ доказывается на достижимом входе, а +/// не на значении, которое потребитель не смог бы предъявить. +/// +/// Transport при самом отказе пустой: guard обязан отказать до parse envelope, +/// поэтому байтов, которые можно было бы разобрать, для отказа не требуется. +#[test] +fn every_byte_of_the_definition_address_is_compared() { + let region = asked_region(); + let (certificate, encoded) = artifact_of(®ion); + let asked = ContextualRegionFamilyProviderV1::definition_digest(asked_pipeline(), ®ion); + + // Ширина берётся у самого значения: литерал молча перестал бы покрывать + // все позиции, если бы digest сменил ширину, и тест остался бы зелёным + // при неполном сравнении. + for index in 0..asked.as_bytes().len() { + let mut bytes = *asked.as_bytes(); + bytes[index] ^= 1; + let certified = FamilyDefinitionDigestV2::from_digest(bytes); + let foreign = certificate.definition_with_coherent_certificate_for_test(certified); + + let record = encoded + .clone() + .with_certificate_for_test(foreign) + .into_bytes(); + assert_eq!( + FamilyImageCertificateV2::parse_trusted(&record[..FAMILY_CERTIFICATE_RECORD_LEN_V2]) + .unwrap(), + foreign, + "byte {index}: the refused certificate is not a record the consumer could present", + ); + + let failure = DefinitionBoundFamilyLoaderV1::load( + asked_pipeline(), + ®ion, + foreign, + EncodedFamilyArtifactV2::from_raw_bytes_for_test(Vec::new()), + ) + .unwrap_err(); + + assert_eq!( + failure.cause(), + DefinitionBoundFamilyLoadErrorV1::ForeignDefinition { asked, certified }, + "byte {index} of the definition address is not part of the comparison", + ); + } +} + +/// Отказ печатает только типизированную причину: owned transport не попадает +/// в логи и отчёты вслед за неудачным admission. +#[test] +fn a_refused_binding_reports_only_its_typed_cause() { + let asked = asked_region(); + let (certificate, encoded) = artifact_of(&other_region()); + + let failure = + DefinitionBoundFamilyLoaderV1::load(asked_pipeline(), &asked, certificate, encoded) + .unwrap_err(); + + assert_eq!( + format!("{failure:?}"), + format!( + "DefinitionBoundFamilyLoadFailureV1 {{ cause: {:?}, .. }}", + failure.cause(), + ), + "owning failures must not dump transport bytes", + ); +} + +#[test] +fn the_asked_definition_does_not_weaken_the_transport_contract() { + let region = asked_region(); + let (certificate, mut encoded) = artifact_of(®ion); + encoded.flip_first_payload_bit_for_test(); + let allocation = encoded.allocation_ptr_for_test(); + + let failure = + DefinitionBoundFamilyLoaderV1::load(asked_pipeline(), ®ion, certificate, encoded) + .unwrap_err(); + + assert_eq!( + failure.cause(), + DefinitionBoundFamilyLoadErrorV1::Artifact( + FamilyArtifactLoadErrorV1::PayloadDigestMismatch, + ), + ); + // The type's contract — a failure hands the same owned bytes back, so a + // caller never refetches or clones — was asserted only on the foreign + // definition branch. This branch could have reallocated and nothing + // would have turned red. + let (_, returned) = failure.into_parts(); + assert_eq!(returned.allocation_ptr_for_test(), allocation); +} diff --git a/crates/labcolors-core/src/lib.rs b/crates/labcolors-core/src/lib.rs index 4a39e5fd..5f32c93a 100644 --- a/crates/labcolors-core/src/lib.rs +++ b/crates/labcolors-core/src/lib.rs @@ -19,6 +19,7 @@ pub(crate) mod composition; pub(crate) mod contextual_region; mod family; mod family_artifact; +mod family_definition_binding; pub(crate) mod spaces; pub use srgb8::Srgb8; @@ -197,6 +198,9 @@ mod constraint_tests; #[cfg(test)] mod family_artifact_tests; +#[cfg(test)] +mod family_definition_binding_tests; + #[cfg(test)] mod clean_set_tests; @@ -474,6 +478,14 @@ pub struct NoPrematurePointSupportApi; /// система ушла осознанно. Сегодня такого перечня нет: семейство — данные, /// приходящие извне. /// +/// `family_definition_binding` — часть именно этого механизма, а не исключение +/// из него: он сравнивает адрес спрошенного региона с адресом в доверенной +/// записи и не хранит ни одного имени семейства. Поэтому он закрыт тем же +/// гейтом и по той же причине. Гейт от него не сужается: слой закрыт целиком, +/// а второе условие сдвигает не публикацию, а полноту механизма — первое +/// условие (ядро не аутентифицирует запись) им не затрагивается и остаётся +/// невыполненным. +/// /// ```compile_fail /// use labcolors_core::family_artifact; /// ``` @@ -497,6 +509,14 @@ pub struct NoPrematurePointSupportApi; /// ```compile_fail /// use labcolors_core::contextual_region::ContextualRegionFamilyProviderV1; /// ``` +/// +/// ```compile_fail +/// use labcolors_core::family_definition_binding; +/// ``` +/// +/// ```compile_fail +/// use labcolors_core::family_definition_binding::DefinitionBoundFamilyLoaderV1; +/// ``` #[cfg(doctest)] pub struct NoPrematureFamilyArtifactApi; diff --git a/scripts/verify_point_support_surplus.py b/scripts/verify_point_support_surplus.py index 57707e63..0cbee024 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 = ( - "e0de826414c860dff2dd348fdaba71285ca1aac067a90fe72d71a58d0cb94e8f" + "685aa65bf1971f0ec729efdd133b9fa7a052689b02b49e3d17b5af636fdbe2d5" ) EXPECTED_Q55_PROOF_SHA256 = ( "ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"