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 be59a9a2..03152a12 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":"6b873ccc50542bb9fe1b442a962226efc648940218457236e13bb6210c1426c9","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":"23c9de8a10733a3eae24cbc6c7361279048fd99f316c6251a166a9c93d8da0b6","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":"09be54900efe29ffdac8705efd0d6d613055c90d634446ca4b228f51a63997d0"},{"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":"51d7835a52b9eb4ab4888aef401ab145b1764eb2054ff7e856828fefc770a132"},{"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":"6f202ad7425a235b9d18caba0c817fc33a2b8e042050a34f5ddff3fd09efc53d"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"6b0c493d78b4cb91f6d34ed94ee93aaaf94ad95818c66d2e8301ee2fe2e3f6f5"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"8c9838107077775c51d80638ba0b59f9672d14347ca404dfd4c63e2fb62d1c45"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"6f6a376ff036d3d65960c004e6566e1bca580f19f5bd3cd333a80b0da5b5c242"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"4f77643206077c080e5e9b182e896145bfb69bf3db8aa4c1ac7d4c5360ea8504"},{"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":"5781357323f601029adb13be71de232fb0293b3d7d883ccffc5499c265a639de"} +{"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":"0c037f3c8b340df0d0c35ecf56f33427c1a0668a3e4bc0b3edf31f316d32f9b9","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":"5bed543bf75209b2660d7e4f2f0f0cb107f9ee4c841f7dad8bd05175f8ad897d","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":"09be54900efe29ffdac8705efd0d6d613055c90d634446ca4b228f51a63997d0"},{"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":"6b0c493d78b4cb91f6d34ed94ee93aaaf94ad95818c66d2e8301ee2fe2e3f6f5"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"8c9838107077775c51d80638ba0b59f9672d14347ca404dfd4c63e2fb62d1c45"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"6f6a376ff036d3d65960c004e6566e1bca580f19f5bd3cd333a80b0da5b5c242"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"4f77643206077c080e5e9b182e896145bfb69bf3db8aa4c1ac7d4c5360ea8504"},{"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":"de09ddbd1a1064850a2aad69861d9262af66c1c4307b936dfe774b2f803a9cd7"} diff --git a/crates/labcolors-core/src/constraints/mod.rs b/crates/labcolors-core/src/constraints/mod.rs index 6ae5a1c3..6853ed78 100644 --- a/crates/labcolors-core/src/constraints/mod.rs +++ b/crates/labcolors-core/src/constraints/mod.rs @@ -1,14 +1,18 @@ //! Приватная typed-связка physical measurement, hard-classifier и evidence. //! -//! Evaluator только измеряет modeled occurrence. Hard verdict появляется один +//! Evaluator только измеряет свой typed target. Hard verdict появляется один //! раз в sealed classifier-е, после чего source-specific binder атомарно //! связывает результат с physical occurrence и metadata реально вызванного -//! evaluator-а. +//! evaluator-а. Derived LCS target отделён от encoded point capability типом. use crate::Srgb8; use crate::appearance::{ModeledSrgb8PointOccurrence, ResolvedOccurrence, VisiblePointBindingV1}; -use crate::lcs_occurrence::ModeledLcsOccurrenceV1; +use crate::lcs_occurrence::{ + AppearanceContextId, ColorSignal, ColorimetricTransformReleaseId, + ModeledLcsOccurrenceFormationErrorV1, ModeledLcsOccurrenceReleaseId, ModeledLcsOccurrenceV1, +}; use crate::wcag22::{Wcag22CriterionV1, Wcag22ProfileIdV1}; +use std::cell::OnceCell; mod exact; pub(crate) use exact::{ @@ -79,6 +83,16 @@ pub(crate) struct MutantExactPassV1; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct MutantExactViolationV1; +/// Test-only LCS-aware evaluator used to prove capability sharing and atomic +/// provenance before the first production LCS constraint family is admitted. +#[cfg(test)] +#[derive(Debug, Clone, Copy, Default)] +pub(crate) struct LcsProbeProgramEvaluatorV1; + +#[cfg(test)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct LcsProbePassV1; + /// Seals недоступны внешним crate-ам: новые evaluator/classifier families /// добавляются только вместе с code-owned physical adapter-ом. mod private { @@ -98,6 +112,12 @@ impl private::EvaluatorSealed for FinalRecheckMutantProgramEvaluatorV1 {} #[cfg(test)] impl private::HardClassifierSealed for FinalRecheckMutantProgramEvaluatorV1 {} +#[cfg(test)] +impl private::EvaluatorSealed for LcsProbeProgramEvaluatorV1 {} + +#[cfg(test)] +impl private::HardClassifierSealed for LcsProbeProgramEvaluatorV1 {} + pub(crate) trait Evaluator: private::EvaluatorSealed { type Invocation; type Identity; @@ -216,27 +236,117 @@ pub(crate) type PointViolation = , >>::Violation; -/// Program-only target: the exact encoded point used by physical evaluators -/// and the context-bound LCS occurrence derived from that same visible signal. -/// Neither half is optional, and construction remains inside the sole Program -/// execution path. +/// Program-only encoded point. Appearance context and every derived view stay +/// outside this capability, so an encoded evaluator cannot acquire either +/// dependency through its target type. #[derive(Debug, Clone, Copy)] pub(crate) struct ProgramPointTargetV1 { encoded: ModeledSrgb8PointOccurrence, - modeled_lcs: ModeledLcsOccurrenceV1, } impl ProgramPointTargetV1 { - pub(crate) const fn new( - encoded: ModeledSrgb8PointOccurrence, - modeled_lcs: ModeledLcsOccurrenceV1, + pub(crate) const fn new(encoded: ModeledSrgb8PointOccurrence) -> Self { + Self { encoded } + } + + pub(crate) const fn encoded(self) -> ModeledSrgb8PointOccurrence { + self.encoded + } +} + +/// Canonical physical Program occurrence. Target and evidence views are +/// projected from this single value, so evaluator input and final source-over +/// certificate cannot name different occurrences. +#[derive(Debug, Clone, Copy)] +pub(crate) struct ProgramPointOccurrenceV1 { + encoded: ModeledSrgb8PointOccurrence, + physical: VisiblePointBindingV1, + context: AppearanceContextId, +} + +impl ProgramPointOccurrenceV1 { + pub(crate) fn from_resolved(source: &ResolvedOccurrence, context: AppearanceContextId) -> Self { + Self { + encoded: source.modeled_srgb8_point(), + physical: source.visible_point_binding(), + context, + } + } + + pub(crate) const fn target(self) -> ProgramPointTargetV1 { + ProgramPointTargetV1::new(self.encoded) + } + + pub(crate) const fn binding(self) -> ProgramVisiblePointBindingV1 { + ProgramVisiblePointBindingV1 { + physical: self.physical, + context: self.context, + } + } +} + +/// Exact executable dependencies of an LCS-aware Program constraint. The same +/// value is retained by evaluator evidence and compile-time content identity. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +pub(crate) struct ProgramLcsDependencyReleaseV1 { + modeled_lcs_release: ModeledLcsOccurrenceReleaseId, + transform_release: ColorimetricTransformReleaseId, +} + +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +impl ProgramLcsDependencyReleaseV1 { + pub(crate) const fn current() -> Self { + Self { + modeled_lcs_release: crate::lcs_occurrence::MODELED_LCS_OCCURRENCE_RELEASE_V1, + transform_release: crate::lcs_occurrence::ADMITTED_SRGB8_TRISTIMULUS_BINDING_V1 + .transform_release(), + } + } + + pub(crate) const fn modeled_lcs_release(self) -> ModeledLcsOccurrenceReleaseId { + self.modeled_lcs_release + } + + pub(crate) const fn transform_release(self) -> ColorimetricTransformReleaseId { + self.transform_release + } + + #[cfg(test)] + pub(crate) const fn with_modeled_lcs_release_for_test( + self, + modeled_lcs_release: ModeledLcsOccurrenceReleaseId, ) -> Self { Self { - encoded, - modeled_lcs, + modeled_lcs_release, + ..self } } +} +/// LCS-aware target is a distinct capability, never an optional field on the +/// encoded target. Only the occurrence-scoped adapter can construct it. +#[derive(Debug, Clone, Copy)] +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +pub(crate) struct ProgramLcsPointTargetV1 { + encoded: ModeledSrgb8PointOccurrence, + modeled_lcs: ModeledLcsOccurrenceV1, +} + +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +impl ProgramLcsPointTargetV1 { pub(crate) const fn encoded(self) -> ModeledSrgb8PointOccurrence { self.encoded } @@ -246,6 +356,94 @@ impl ProgramPointTargetV1 { } } +/// One lazy derived LCS capability over a canonical physical occurrence. +/// Success and failure are memoized so repeated LCS-aware constraints cannot +/// repeat the sRGB8 -> XYZ derivation inside one evaluation scope. +#[derive(Debug)] +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +pub(crate) struct ProgramLcsPointAdapterV1 { + point: ProgramPointOccurrenceV1, + modeled_lcs: OnceCell>, +} + +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +impl ProgramLcsPointAdapterV1 { + pub(crate) const fn new(point: ProgramPointOccurrenceV1) -> Self { + Self { + point, + modeled_lcs: OnceCell::new(), + } + } + + fn modeled_lcs(&self) -> Result { + *self.modeled_lcs.get_or_init(|| { + ModeledLcsOccurrenceV1::from_signal_in_context( + ColorSignal::from_srgb8(Srgb8::new(self.point.encoded.visible())), + self.point.context, + ) + }) + } +} + +#[cfg(test)] +impl Evaluator for LcsProbeProgramEvaluatorV1 { + type Invocation = u8; + type Identity = (); + type Release = ProgramLcsDependencyReleaseV1; + type Capability = (); + type Measurement = ModeledLcsOccurrenceV1; + type Error = core::convert::Infallible; + + fn identity(&self) -> Self::Identity {} + + fn release(&self) -> Self::Release { + ProgramLcsDependencyReleaseV1::current() + } + + fn capability(&self) -> Self::Capability {} + + fn evaluate( + &self, + target: &ProgramLcsPointTargetV1, + _invocation: &Self::Invocation, + ) -> Result { + debug_assert_eq!( + target.modeled_lcs().signal().srgb8(), + Srgb8::new(target.encoded().visible()) + ); + Ok(target.modeled_lcs()) + } +} + +#[cfg(test)] +impl HardClassifier for LcsProbeProgramEvaluatorV1 { + type Pass = LcsProbePassV1; + type Violation = core::convert::Infallible; + + fn classify( + &self, + _invocation: &u8, + _measurement: &ModeledLcsOccurrenceV1, + ) -> HardDecision { + HardDecision::Pass(LcsProbePassV1) + } +} + +#[cfg(test)] +impl LcsProbeProgramEvaluatorV1 { + pub(crate) fn program_constraint_content_v1(self) -> ProgramConstraintContentV1 { + ProgramConstraintContentV1::ModeledLcsProbe { + release: >::release(&self), + } + } +} + pub(crate) type ProgramPointInvocation = >::Invocation; pub(crate) type ProgramPointMeasurement = @@ -299,6 +497,10 @@ pub(crate) enum ProgramConstraintContentV1 { criterion: Wcag22CriterionV1, }, #[cfg(test)] + ModeledLcsProbe { + release: ProgramLcsDependencyReleaseV1, + }, + #[cfg(test)] FinalRecheckMutantExactSrgb8 { expected: Srgb8 }, } @@ -457,12 +659,11 @@ impl HardClassifier for FinalRecheckMutantProgramEvaluatorV1 { } /// Program evidence binds the physical source-over certificate and the exact -/// modeled LCS provenance/context used by the evaluator in one non-forgeable -/// value. +/// declared appearance context. A derived LCS view is a separate capability. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct ProgramVisiblePointBindingV1 { physical: VisiblePointBindingV1, - modeled_lcs: ModeledLcsOccurrenceV1, + context: AppearanceContextId, } impl ProgramVisiblePointBindingV1 { @@ -470,6 +671,31 @@ impl ProgramVisiblePointBindingV1 { self.physical } + pub(crate) const fn context(self) -> AppearanceContextId { + self.context + } +} + +/// Evidence binding available only to LCS-aware evaluators. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +pub(crate) struct ProgramLcsVisiblePointBindingV1 { + physical: ProgramVisiblePointBindingV1, + modeled_lcs: ModeledLcsOccurrenceV1, +} + +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +impl ProgramLcsVisiblePointBindingV1 { + pub(crate) const fn physical(self) -> ProgramVisiblePointBindingV1 { + self.physical + } + pub(crate) const fn modeled_lcs(self) -> ModeledLcsOccurrenceV1 { self.modeled_lcs } @@ -494,24 +720,117 @@ pub(crate) type ProgramVisiblePointViolationEvidence = BoundProgramP >; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct ProgramPointBindingMismatchV1 { - physical: Srgb8, - modeled: Srgb8, +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +pub(crate) enum ProgramLcsPointAssessmentErrorV1 { + Formation(ModeledLcsOccurrenceFormationErrorV1), + Evaluator(EvaluatorError), } -impl ProgramPointBindingMismatchV1 { - pub(crate) const fn physical(self) -> Srgb8 { - self.physical - } +pub(crate) type ProgramLcsPointInvocation = + >::Invocation; +pub(crate) type ProgramLcsPointMeasurement = + >::Measurement; +pub(crate) type ProgramLcsPointPass = , + ProgramLcsPointMeasurement, +>>::Pass; +pub(crate) type ProgramLcsPointViolation = , + ProgramLcsPointMeasurement, +>>::Violation; - pub(crate) const fn modeled(self) -> Srgb8 { - self.modeled - } +type BoundProgramLcsPointMeasurement = BoundEvidence< + ProgramLcsVisiblePointBindingV1, + >::Identity, + >::Release, + >::Capability, + >::Invocation, + Measurement, +>; + +pub(crate) type ProgramLcsVisiblePointPassEvidence = BoundProgramLcsPointMeasurement< + Evaluation, + ClassifiedMeasurement, ProgramLcsPointPass>, +>; +pub(crate) type ProgramLcsVisiblePointViolationEvidence = + BoundProgramLcsPointMeasurement< + Evaluation, + ClassifiedMeasurement< + ProgramLcsPointMeasurement, + ProgramLcsPointViolation, + >, + >; +pub(crate) type ProgramLcsPointAssessmentResultV1 = Result< + HardDecision< + ProgramLcsVisiblePointPassEvidence, + ProgramLcsVisiblePointViolationEvidence, + >, + ProgramLcsPointAssessmentErrorV1<>::Error>, +>; + +/// The only LCS-aware Program binder. The target and its physical evidence are +/// minted together from one occurrence-scoped adapter after one memoized +/// derivation, so callers cannot pair equal bytes from different occurrences. +#[allow( + dead_code, + reason = "the typed LCS capability precedes its first admitted Program evaluator" +)] +pub(crate) fn assess_program_lcs_point_hard( + adapter: &ProgramLcsPointAdapterV1, + evaluator: &Evaluation, + invocation: ProgramLcsPointInvocation, +) -> ProgramLcsPointAssessmentResultV1 +where + Evaluation: Evaluator + + HardClassifier< + ProgramLcsPointInvocation, + ProgramLcsPointMeasurement, + >, +{ + let modeled_lcs = adapter + .modeled_lcs() + .map_err(ProgramLcsPointAssessmentErrorV1::Formation)?; + let target = ProgramLcsPointTargetV1 { + encoded: adapter.point.encoded, + modeled_lcs, + }; + let binding = ProgramLcsVisiblePointBindingV1 { + physical: adapter.point.binding(), + modeled_lcs, + }; + let measurement = evaluator + .evaluate(&target, &invocation) + .map_err(ProgramLcsPointAssessmentErrorV1::Evaluator)?; + let classification = evaluator.classify(&invocation, &measurement); + let identity = evaluator.identity(); + let release = evaluator.release(); + let capability = evaluator.capability(); + + Ok(match classification { + HardDecision::Pass(payload) => HardDecision::Pass(BoundEvidence { + binding, + identity, + release, + capability, + invocation, + measurement: ClassifiedMeasurement::new(measurement, payload), + }), + HardDecision::Violation(payload) => HardDecision::Violation(BoundEvidence { + binding, + identity, + release, + capability, + invocation, + measurement: ClassifiedMeasurement::new(measurement, payload), + }), + }) } #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum ProgramPointAssessmentErrorV1 { - Binding(ProgramPointBindingMismatchV1), Evaluator(EvaluatorError), } @@ -524,8 +843,7 @@ pub(crate) type ProgramPointAssessmentResultV1 = Result< >; pub(crate) fn assess_program_point_hard( - source: &ResolvedOccurrence, - modeled_lcs: ModeledLcsOccurrenceV1, + point: ProgramPointOccurrenceV1, evaluator: &Evaluation, invocation: ProgramPointInvocation, ) -> ProgramPointAssessmentResultV1 @@ -533,18 +851,8 @@ where Evaluation: Evaluator + HardClassifier, ProgramPointMeasurement>, { - let physical = Srgb8::new(source.visible()); - let modeled = modeled_lcs.signal().srgb8(); - if physical != modeled { - return Err(ProgramPointAssessmentErrorV1::Binding( - ProgramPointBindingMismatchV1 { physical, modeled }, - )); - } - let target = ProgramPointTargetV1::new(source.modeled_srgb8_point(), modeled_lcs); - let binding = ProgramVisiblePointBindingV1 { - physical: source.visible_point_binding(), - modeled_lcs: target.modeled_lcs(), - }; + let target = point.target(); + let binding = point.binding(); let measurement = >::evaluate(evaluator, &target, &invocation) .map_err(ProgramPointAssessmentErrorV1::Evaluator)?; diff --git a/crates/labcolors-core/src/generic_boundary_tests.rs b/crates/labcolors-core/src/generic_boundary_tests.rs index e40cfc45..7a367c5e 100644 --- a/crates/labcolors-core/src/generic_boundary_tests.rs +++ b/crates/labcolors-core/src/generic_boundary_tests.rs @@ -786,23 +786,46 @@ fn shared_observation_ssot_has_one_backing_without_lifecycle_or_adapter_facades( } #[test] -fn program_session_owns_context_bound_lcs_evidence_and_one_session_scratch_cache() { +fn program_session_keeps_physical_evidence_separate_from_lazy_lcs_capability() { for required in [ - "ProgramPointTargetV1", - "ModeledLcsOccurrenceV1", + "ProgramPointOccurrenceV1::from_resolved(source, binding.context)", "AppearanceContextId", "ProgramVisiblePointPassEvidence", "ProgramVisiblePointViolationEvidence", - "ModeledLcsOccurrenceV1::from_signal_in_context(", "source.visible() != source.certificate().output_rgb()", - "binding.context,", "assess_program_point_hard(", ] { assert!( PROGRAM_SESSION_SOURCE.contains(required), - "Program must retain physical-source and context-bound LCS evidence; missing `{required}`", + "Program must retain physical-source evidence and declared context; missing `{required}`", ); } + for forbidden in [ + "ModeledLcsOccurrenceV1::from_signal_in_context(", + "modeled_occurrences: Vec>", + "ProgramPointAssessmentErrorV1::Binding", + "ProgramSessionEvaluationError::ModeledOccurrence", + ] { + assert!( + !PROGRAM_SESSION_SOURCE.contains(forbidden), + "encoded Program execution must not restore eager LCS path `{forbidden}`", + ); + } + + let encoded_target = normalized_source_scope( + CONSTRAINTS_SOURCE, + "pub(crate) struct ProgramPointTargetV1 {", + "impl ProgramPointTargetV1", + ); + assert!( + encoded_target.contains("encoded: ModeledSrgb8PointOccurrence,"), + "encoded target must retain the physical sRGB8 point", + ); + assert!( + !encoded_target.contains("ModeledLcsOccurrenceV1") + && !encoded_target.contains("AppearanceContextId"), + "encoded evaluator target must expose neither a derived LCS view nor appearance context", + ); let program_evidence_binding = normalized_source_scope( CONSTRAINTS_SOURCE, @@ -811,11 +834,32 @@ fn program_session_owns_context_bound_lcs_evidence_and_one_session_scratch_cache ); for required in [ "physical: VisiblePointBindingV1,", - "modeled_lcs: ModeledLcsOccurrenceV1,", + "context: AppearanceContextId,", ] { assert!( program_evidence_binding.contains(required), - "Program evidence must bind source-over and LCS context in one value; missing `{required}`", + "base Program evidence must bind source-over and declared context; missing `{required}`", + ); + } + assert!( + !program_evidence_binding.contains("ModeledLcsOccurrenceV1"), + "base Program evidence must not smuggle a derived LCS view", + ); + let projected_binding = source_scope( + PROGRAM_SOURCE, + "impl<'a> PointBindingV1<'a> {", + "/// Закрытое семейство точной физической композиции.", + ); + assert!(projected_binding.contains("appearance_context(self)")); + for forbidden in [ + "modeled(self)", + "ModeledPointV1", + ".modeled_lcs()", + "xyz(self)", + ] { + assert!( + !projected_binding.contains(forbidden), + "base projected binding must not restore derived view `{forbidden}`", ); } @@ -824,16 +868,8 @@ fn program_session_owns_context_bound_lcs_evidence_and_one_session_scratch_cache "impl ProgramConstraintResultV1", "/// One canonical `physical case × constraint` report cell.", ); - for required in [ - "fn binding(&self) -> ProgramVisiblePointBindingV1", - "fn modeled_lcs_occurrence(&self) -> ModeledLcsOccurrenceV1", - "self.binding().modeled_lcs()", - ] { - assert!( - result.contains(required), - "Program result must project modeled LCS from its evidence SSOT; missing `{required}`", - ); - } + assert!(result.contains("fn binding(&self) -> ProgramVisiblePointBindingV1")); + assert!(!result.contains("modeled_lcs")); let cell = source_scope( PROGRAM_SESSION_SOURCE, "pub struct ProgramConstraintCellV1", @@ -888,19 +924,7 @@ fn program_session_owns_context_bound_lcs_evidence_and_one_session_scratch_cache 1, "CompiledProgram must be the one strong owner of its generation", ); - assert_eq!( - plan.matches("modeled_occurrences: Vec>,") - .count(), - 1, - "each Program Session must own exactly one reusable modeled-occurrence scratch cache", - ); - assert_eq!( - PROGRAM_SESSION_SOURCE - .matches("modeled_occurrences: Vec>,") - .count(), - 1, - "the modeled-occurrence scratch cache must not be duplicated outside the Session plan", - ); + assert!(!plan.contains("ModeledLcsOccurrenceV1")); let preparation = normalized_source_scope( PROGRAM_SESSION_SOURCE, @@ -925,7 +949,7 @@ fn program_session_owns_context_bound_lcs_evidence_and_one_session_scratch_cache for required in [ "targets.sort_unstable(); targets.dedup();", ".binary_search_by_key(&constraint.target_id, |binding| binding.occurrence)", - "constraint.modeled_occurrence_index = index;", + "constraint.occurrence_context_index = index;", ] { assert!( compaction.contains(required), @@ -942,16 +966,128 @@ fn program_session_owns_context_bound_lcs_evidence_and_one_session_scratch_cache !hot_evaluation.contains("binary_search"), "hot Program evaluation must consume compile-time direct indices without searching", ); + assert!(hot_evaluation.contains(".get(constraint.occurrence_context_index)")); + for required in [ - "plan.modeled_occurrences.fill(None);", - ".get(constraint.modeled_occurrence_index)", - ".get_mut(constraint.modeled_occurrence_index)", + "pub(crate) struct ProgramLcsPointAdapterV1", + "OnceCell>", + "pub(crate) struct ProgramLcsPointTargetV1", + "pub(crate) struct ProgramLcsVisiblePointBindingV1", + "assess_program_lcs_point_hard", ] { assert!( - hot_evaluation.contains(required), - "hot Program evaluation must reuse the compact direct-index cache; missing `{required}`", + CONSTRAINTS_SOURCE.contains(required), + "the explicit lazy LCS capability is incomplete; missing `{required}`", ); } + let lcs_adapter = source_scope( + CONSTRAINTS_SOURCE, + "pub(crate) struct ProgramLcsPointAdapterV1 {", + "#[cfg(test)]\nimpl Evaluator", + ); + assert!( + !lcs_adapter.contains("Option"), + "LCS capability must be a typed adapter, not an optional field state", + ); + let lcs_target_impl = normalized_source_scope( + CONSTRAINTS_SOURCE, + "impl ProgramLcsPointTargetV1 {", + "/// One lazy derived LCS capability", + ); + let lcs_binding_impl = normalized_source_scope( + CONSTRAINTS_SOURCE, + "impl ProgramLcsVisiblePointBindingV1 {", + "type BoundProgramPointMeasurement", + ); + for (surface, source) in [ + ("LCS target", lcs_target_impl.as_str()), + ("LCS evidence", lcs_binding_impl.as_str()), + ] { + assert!( + !source.contains("fn bind("), + "{surface} must have no independent bind constructor", + ); + } + assert!( + !CONSTRAINTS_SOURCE.contains("verify_program_lcs_point_binding"), + "byte/context equality cannot certify physical occurrence identity", + ); +} + +#[test] +fn program_identity_binds_lcs_releases_only_through_lcs_constraint_content() { + let identity_sources = [ + ("program_identity.rs", PROGRAM_IDENTITY_SOURCE), + ("program_session.rs", PROGRAM_SESSION_SOURCE), + ("program.rs", PROGRAM_SOURCE), + ]; + for retired in [ + "ProgramContentIdentityV1", + "ContentIdentityV1", + "DOMAIN_V1", + "PROGRAM_SCHEMA_V1", + "compile_program_content_identity_v1", + ] { + for (path, source) in identity_sources { + assert!( + !contains_rust_identifier(source, retired), + "the V2 content-address cut must not retain legacy identity symbol `{retired}` in {path}", + ); + } + } + assert!(!PROGRAM_IDENTITY_SOURCE.contains("labcolors.program-content-identity.v1")); + for required in [ + "const DOMAIN_V2: &[u8] = b\"labcolors.program-content-identity.v2\\0\";", + "pub(super) const PROGRAM_SCHEMA_V2: u8 = 2;", + ] { + assert!( + PROGRAM_IDENTITY_SOURCE.contains(required), + "the V2 content-address type must bind its exact domain and schema tag; missing `{required}`", + ); + } + + let root = normalized_source_scope( + PROGRAM_IDENTITY_SOURCE, + "fn program_root_color()", + "fn write_signal(", + ); + assert!( + !root.contains("MODELED_LCS_OCCURRENCE_V1"), + "an encoded-only Program must not inherit the modeled-LCS release", + ); + + let signal = normalized_source_scope( + PROGRAM_IDENTITY_SOURCE, + "fn write_signal(", + "fn source_color(", + ); + assert!( + !signal.contains("transform_release") + && !signal.contains("IEC_SRGB8_TO_XYZ_D65_TRANSFORM_V1"), + "encoded signal identity must not inherit an unused sRGB-to-XYZ transform", + ); + + let content = normalized_source_scope( + CONSTRAINTS_SOURCE, + "pub(crate) enum ProgramConstraintContentV1 {", + "/// Внутрикрейтное описание generic test seam", + ); + assert!(content.contains("ModeledLcs")); + assert!(content.contains("release: ProgramLcsDependencyReleaseV1")); + let dependency_release = normalized_source_scope( + CONSTRAINTS_SOURCE, + "pub(crate) struct ProgramLcsDependencyReleaseV1 {", + "impl ProgramLcsDependencyReleaseV1", + ); + assert!(dependency_release.contains("modeled_lcs_release: ModeledLcsOccurrenceReleaseId")); + assert!(dependency_release.contains("transform_release: ColorimetricTransformReleaseId")); + let constraint_identity = normalized_source_scope( + PROGRAM_IDENTITY_SOURCE, + "fn constraint_color(", + "fn build_graph<", + ); + assert!(constraint_identity.contains("release.modeled_lcs_release()")); + assert!(constraint_identity.contains("release.transform_release()")); } #[test] @@ -1099,6 +1235,17 @@ fn existing_encoded_evaluators_delegate_program_targets_without_parallel_formula !implementation.contains(".modeled_lcs()"), "{path} encoded evaluator must not silently grow a contextual LCS formula", ); + for forbidden in [ + "ProgramLcsPointTargetV1", + "ProgramLcsPointAdapterV1", + "ModeledLcsOccurrenceV1", + "ColorSignal", + ] { + assert!( + !contains_rust_identifier(source, forbidden), + "{path} encoded evaluator must not acquire LCS capability `{forbidden}`", + ); + } } } diff --git a/crates/labcolors-core/src/lcs_occurrence.rs b/crates/labcolors-core/src/lcs_occurrence.rs index fb7f08c5..81b845b4 100644 --- a/crates/labcolors-core/src/lcs_occurrence.rs +++ b/crates/labcolors-core/src/lcs_occurrence.rs @@ -616,6 +616,17 @@ pub enum ModeledLcsOccurrenceFormationErrorV1 { }, } +/// Formula and binding release for one modeled LCS occurrence. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ModeledLcsOccurrenceReleaseId { + V1, + #[cfg(test)] + MutationSentinelV1, +} + +pub(crate) const MODELED_LCS_OCCURRENCE_RELEASE_V1: ModeledLcsOccurrenceReleaseId = + ModeledLcsOccurrenceReleaseId::V1; + /// One replayable modeled signal derivation bound to exactly one immutable /// appearance context. /// diff --git a/crates/labcolors-core/src/program.rs b/crates/labcolors-core/src/program.rs index 592d6530..04dbd4e9 100644 --- a/crates/labcolors-core/src/program.rs +++ b/crates/labcolors-core/src/program.rs @@ -35,7 +35,7 @@ //! [`CertificateV1::Verified`] хранит выбранное состояние, все клетки //! доказательства и сертифицированные выходы. [`CertificateV1::Conflict`] //! хранит исчерпывающий конфликт по всем рассмотренным состояниям. -//! [`ContentIdentityV1`] идентифицирует каноническое содержание, но не даёт +//! [`ContentIdentityV2`] идентифицирует каноническое содержание, но не даёт //! полномочий живого [`OwnerV1`]. #![forbid(unreachable_pub)] @@ -53,14 +53,10 @@ use crate::constraints::{ }; use crate::joint::FiniteJointOrderErrorV1; use crate::lcs_occurrence::{ - AdaptingLuminanceCdM2, AdmittedSrgb8TristimulusBindingV1, AppearanceContextDomainErrorV1, + AdaptingLuminanceCdM2, AppearanceContextDomainErrorV1, AppearanceContextFieldV1 as CoreAppearanceContextFieldV1, AppearanceContextId, AppearanceContextSchemaReleaseId, BackgroundLuminanceRatio, ColorSignal, ColorSignalViewV1, - ColorimetricFrameId, ColorimetricFrameReleaseId, IEC_SRGB_D65_XYZ_FRAME_V1, - ModeledLcsOccurrenceFormationErrorV1, NumericDomainError, ObserverProfileId, - OccurrenceFormationError, ReferenceWhiteId, SurroundProfileId, - TristimulusComponentV1 as CoreTristimulusComponentV1, TristimulusDomainErrorV1, - TristimulusSample, TristimulusScale, + IEC_SRGB_D65_XYZ_FRAME_V1, NumericDomainError, SurroundProfileId, }; use crate::numerics::NumericalDecisionEvidenceV1; use crate::observation::{ @@ -73,7 +69,7 @@ use crate::program_session::{ CoreProgramEvaluatorErrorV1, CoreProgramEvaluatorsV1, CoreProgramPassEvidenceV1, CoreProgramViolationEvidenceV1, DeclaredJointSelectionV1, JointCandidateStateV1, Occurrence, OpacityInput, OutputBinding, OutputSlotId, Paint, ProgramCompileError, ProgramConflictV1, - ProgramConstraintCellV1, ProgramConstraintResultV1, ProgramContentIdentityV1, ProgramOutputV1, + ProgramConstraintCellV1, ProgramConstraintResultV1, ProgramContentIdentityV2, ProgramOutputV1, ProgramSessionEvaluationError, ProgramSessionInstantiateError, ProgramSessionPlan, ProgramVerifiedV1, Source, SourceId, Surface, Target, TargetCandidateChoiceV1, TargetCandidateId, TargetCandidateV1 as CoreTargetCandidateV1, TargetId, @@ -1318,8 +1314,8 @@ impl OwnerV1 { /// /// Identity доступна до первого update, но не заменяет полномочия этой /// конкретной owner-эпохи. - pub(crate) fn content_identity(&self) -> ContentIdentityV1 { - ContentIdentityV1::from_core(self.compiled.content_identity()) + pub(crate) fn content_identity(&self) -> ContentIdentityV2 { + ContentIdentityV2::from_core(self.compiled.content_identity()) } /// Вычисляет верхние границы клеток для prospective Observed-update. @@ -1367,7 +1363,7 @@ impl OwnerV1 { /// Проецирует операции только для Session этой точной owner-эпохи. /// - /// Равенство [`ContentIdentityV1`] не даёт полномочий. + /// Равенство [`ContentIdentityV2`] не даёт полномочий. pub(crate) fn project<'owner, 'session>( &'owner self, session: &'session SessionV1, @@ -1695,10 +1691,10 @@ impl<'owner, 'session> ProjectionV1<'owner, 'session> { /// Identity не идентифицирует owner-эпоху и не даёт runtime-полномочий. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct ContentIdentityV1([u8; 32]); +pub(crate) struct ContentIdentityV2([u8; 32]); -impl ContentIdentityV1 { - const fn from_core(value: ProgramContentIdentityV1) -> Self { +impl ContentIdentityV2 { + const fn from_core(value: ProgramContentIdentityV2) -> Self { Self(*value.as_bytes()) } @@ -1716,8 +1712,8 @@ pub(crate) struct VerifiedCertificateV1<'a> { impl<'a> VerifiedCertificateV1<'a> { /// Возвращает identity скомпилированного содержания. - pub(crate) const fn content_identity(self) -> ContentIdentityV1 { - ContentIdentityV1::from_core(self.inner.report().content_identity()) + pub(crate) const fn content_identity(self) -> ContentIdentityV2 { + ContentIdentityV2::from_core(self.inner.report().content_identity()) } /// Возвращает точное наблюдение, на котором выдан сертификат. @@ -1762,8 +1758,8 @@ pub(crate) struct ConflictCertificateV1<'a> { impl<'a> ConflictCertificateV1<'a> { /// Возвращает identity скомпилированного содержания. - pub(crate) const fn content_identity(self) -> ContentIdentityV1 { - ContentIdentityV1::from_core(self.inner.report().content_identity()) + pub(crate) const fn content_identity(self) -> ContentIdentityV2 { + ContentIdentityV2::from_core(self.inner.report().content_identity()) } /// Возвращает точное наблюдение, вызвавшее конфликт. @@ -1812,7 +1808,7 @@ impl<'a> CertificateV1<'a> { } /// Возвращает identity скомпилированного содержания. - pub(crate) const fn content_identity(self) -> ContentIdentityV1 { + pub(crate) const fn content_identity(self) -> ContentIdentityV2 { match self { Self::Verified(value) => value.content_identity(), Self::Conflict(value) => value.content_identity(), @@ -2055,7 +2051,7 @@ impl<'a> AssessmentV1<'a> { } } - /// Возвращает общую физическую и моделированную привязку точки. + /// Возвращает физическую occurrence-привязку и объявленный appearance context. pub(crate) fn binding(self) -> PointBindingV1<'a> { match self { Self::ExactSrgb8(value) => value.binding(), @@ -2079,7 +2075,7 @@ enum ExactSrgb8EvidenceRefV1<'a> { Violation(&'a CoreExactViolationEvidenceV1), } -/// Evidence точного sRGB8 сравнения с физикой и моделированным контекстом. +/// Evidence точного sRGB8 сравнения с физической occurrence и объявленным context. #[derive(Clone, Copy)] pub(crate) struct ExactSrgb8EvidenceV1<'a> { inner: ExactSrgb8EvidenceRefV1<'a>, @@ -2102,7 +2098,7 @@ impl<'a> ExactSrgb8EvidenceV1<'a> { } } - /// Возвращает физическую и моделированную привязку точки. + /// Возвращает физическую occurrence-привязку и объявленный appearance context. pub(crate) fn binding(self) -> PointBindingV1<'a> { let value = match self.inner { ExactSrgb8EvidenceRefV1::Pass(value) => value.binding(), @@ -2118,7 +2114,7 @@ enum Wcag22Srgb8EvidenceRefV1<'a> { Violation(&'a CoreWcag22ViolationEvidenceV1), } -/// WCAG 2.2 evidence вместе с физикой и моделированным контекстом. +/// WCAG 2.2 evidence с физической occurrence и объявленным appearance context. #[derive(Clone, Copy)] pub(crate) struct Wcag22Srgb8EvidenceV1<'a> { inner: Wcag22Srgb8EvidenceRefV1<'a>, @@ -2175,7 +2171,7 @@ impl<'a> Wcag22Srgb8EvidenceV1<'a> { } } - /// Возвращает физическую и моделированную привязку точки. + /// Возвращает физическую occurrence-привязку и объявленный appearance context. pub(crate) fn binding(self) -> PointBindingV1<'a> { let value = match self.inner { Wcag22Srgb8EvidenceRefV1::Pass(value) => value.binding(), @@ -2185,7 +2181,7 @@ impl<'a> Wcag22Srgb8EvidenceV1<'a> { } } -/// Общая привязка физической композиции и моделированного tristimulus/context. +/// Общая привязка физической композиции и объявленного appearance context. #[derive(Clone, Copy)] pub(crate) struct PointBindingV1<'a> { inner: &'a ProgramVisiblePointBindingV1, @@ -2203,15 +2199,9 @@ impl<'a> PointBindingV1<'a> { } } - /// Возвращает закрытый тип допущенного моделированного сигнала. - pub(crate) const fn modeled(self) -> ModeledPointV1<'a> { - match self.inner.modeled_lcs().provenance().binding() { - AdmittedSrgb8TristimulusBindingV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1V1 => { - ModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1( - ModeledTristimulusV1 { inner: self.inner }, - ) - } - } + /// Возвращает точный объявленный контекст без неявного построения LCS-view. + pub(crate) const fn appearance_context(self) -> AppearanceContextV1 { + AppearanceContextV1(self.inner.context()) } } @@ -2265,31 +2255,6 @@ impl EncodedSrgb8SourceOverV1<'_> { } } -/// Закрытое семейство provenance моделированного tristimulus. -#[derive(Clone, Copy)] -pub(crate) enum ModeledPointV1<'a> { - /// IEC sRGB8 → CIE 1931 2° XYZ D65 с относительным `Y=1`. - Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(ModeledTristimulusV1<'a>), -} - -/// Допущенный моделированный tristimulus и его контекст восприятия. -#[derive(Clone, Copy)] -pub(crate) struct ModeledTristimulusV1<'a> { - inner: &'a ProgramVisiblePointBindingV1, -} - -impl ModeledTristimulusV1<'_> { - /// Возвращает относительные координаты CIE XYZ. - pub(crate) fn xyz(self) -> [f64; 3] { - self.inner.modeled_lcs().derivation().sample().xyz() - } - - /// Возвращает явный контекст, использованный при моделировании. - pub(crate) const fn appearance_context(self) -> AppearanceContextV1 { - AppearanceContextV1(self.inner.modeled_lcs().occurrence().context()) - } -} - /// Один Core-сертифицированный выходной Paint. #[derive(Clone, Copy)] pub(crate) struct CertifiedOutputV1<'a> { @@ -2646,95 +2611,6 @@ pub(crate) enum ObservationBindingFailureV1 { }, } -/// Зарегистрированная identity XYZ-frame в диагностике закрытого Core. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ColorimetricFrameV1 { - /// CIE 1931 2°, IEC 61966-2-1 D65, относительная шкала `Y=1`, XYZ v1. - Iec61966Srgb8D65XyzRelativeY1V1, - /// Зарезервированный frame hostile-теста, недостижимый в production. - #[cfg(test)] - MutationSentinelV1, -} - -/// Компонент XYZ в точной диагностике. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum TristimulusComponentV1 { - /// Компонент X. - X, - /// Компонент Y. - Y, - /// Компонент Z. - Z, -} - -/// Точная конечная XYZ-точка и её зарегистрированный frame. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct TristimulusSampleV1 { - /// Биты IEEE-754 сохраняют точный диагностический payload и отделяют - /// равенство записи от семантики сравнения floating-point. - xyz_bits: [u64; 3], - frame: ColorimetricFrameV1, -} - -impl TristimulusSampleV1 { - fn from_core(sample: TristimulusSample) -> Self { - Self { - xyz_bits: sample.xyz().map(f64::to_bits), - frame: map_colorimetric_frame(sample.frame()), - } - } - - /// Возвращает точные конечные XYZ-компоненты. - pub(crate) fn xyz(self) -> [f64; 3] { - self.xyz_bits.map(f64::from_bits) - } - - /// Возвращает зарегистрированный frame точки. - pub(crate) const fn frame(self) -> ColorimetricFrameV1 { - self.frame - } -} - -/// Точная причина, по которой Core не сформировал modeled LCS occurrence. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ModeledOccurrenceFailureV1 { - /// Детерминированное преобразование получило недопустимую XYZ-компоненту. - Tristimulus { - /// Ошибочная компонента. - component: TristimulusComponentV1, - /// Точная числовая причина. - reason: NumericDomainErrorV1, - }, - /// Stimulus и appearance context принадлежат разным frame. - FrameMismatch { - /// Frame modeled stimulus. - stimulus: ColorimetricFrameV1, - /// Frame appearance context. - context: ColorimetricFrameV1, - }, - /// Повторное вычисление provenance получило недопустимую XYZ-компоненту. - ProvenanceReplayFailed { - /// Ошибочная компонента. - component: TristimulusComponentV1, - /// Точная числовая причина. - reason: NumericDomainErrorV1, - }, - /// Записанная modeled-точка не совпала с повторным вычислением provenance. - RecordedSampleDoesNotReplay { - /// Записанная точка. - recorded: TristimulusSampleV1, - /// Повторно вычисленная точка. - replayed: TristimulusSampleV1, - }, - /// Точка occurrence не совпала с modeled provenance. - OccurrenceSampleMismatch { - /// Точка occurrence. - occurrence: TristimulusSampleV1, - /// Точка modeled provenance. - modeled: TristimulusSampleV1, - }, -} - /// Точное недопустимое protocol-состояние зарегистрированного evaluator-а. #[derive(Debug, Clone, PartialEq, Eq)] #[expect( @@ -2776,10 +2652,6 @@ pub(crate) enum UpdateInvariantV1 { EvidenceBinding, /// Applicable evaluator вернул недопустимое protocol-состояние. EvaluatorProtocol, - /// Physical и modeled точки одного evaluator-вызова разошлись. - PhysicalModeledBinding, - /// Зарегистрированный сигнал не сформировал свой LCS occurrence. - ModeledOccurrenceFormation, /// Один выбранный state дал разные выходы в физических сценариях. OutputCaseInvariance, /// Детерминированная финальная перепроверка разошлась с поиском. @@ -2816,32 +2688,6 @@ pub(crate) enum UpdateInvariantFailureV1 { /// Недопустимое protocol-состояние. source: EvaluatorProtocolFailureV1, }, - /// Physical и modeled точки одного evaluator-вызова разошлись. - PhysicalModeledBinding { - /// Индекс физического сценария. - case_index: usize, - /// Непрозрачный ID проверяемого ограничения. - constraint: ConstraintIdV1, - /// Непрозрачный ID физического occurrence. - occurrence: OccurrenceIdV1, - /// Точный appearance context evaluator-вызова. - context: AppearanceContextV1, - /// Физическая encoded sRGB8-точка. - physical: Srgb8, - /// Modeled encoded sRGB8-точка. - modeled: Srgb8, - }, - /// Зарегистрированный сигнал не сформировал свой LCS occurrence. - ModeledOccurrenceFormation { - /// Индекс физического сценария. - case_index: usize, - /// Непрозрачный ID occurrence. - occurrence: OccurrenceIdV1, - /// Intended appearance context формирования. - context: AppearanceContextV1, - /// Точная причина отказа формирования. - source: ModeledOccurrenceFailureV1, - }, /// Один выбранный state дал разные выходы в физических сценариях. OutputCaseInvariance { /// Непрозрачный ID выходного слота. @@ -2876,10 +2722,6 @@ impl UpdateInvariantFailureV1 { Self::ObservationBinding { .. } => UpdateInvariantV1::ObservationBinding, Self::EvidenceBinding => UpdateInvariantV1::EvidenceBinding, Self::EvaluatorProtocol { .. } => UpdateInvariantV1::EvaluatorProtocol, - Self::PhysicalModeledBinding { .. } => UpdateInvariantV1::PhysicalModeledBinding, - Self::ModeledOccurrenceFormation { .. } => { - UpdateInvariantV1::ModeledOccurrenceFormation - } Self::OutputCaseInvariance { .. } => UpdateInvariantV1::OutputCaseInvariance, Self::SelectionRecheck { .. } => UpdateInvariantV1::SelectionRecheck, Self::ProgramEvaluation => UpdateInvariantV1::ProgramEvaluation, @@ -3212,93 +3054,6 @@ fn map_program_compile_error(error: ProgramCompileError) -> CompileErrorV1 { } } -const fn map_colorimetric_frame(frame: ColorimetricFrameId) -> ColorimetricFrameV1 { - match ( - frame.observer(), - frame.reference_white(), - frame.scale(), - frame.release(), - ) { - ( - ObserverProfileId::Cie1931TwoDegreeV1, - ReferenceWhiteId::Iec61966D65ChromaticityV1, - TristimulusScale::RelativeY1, - ColorimetricFrameReleaseId::XyzV1, - ) => ColorimetricFrameV1::Iec61966Srgb8D65XyzRelativeY1V1, - #[cfg(test)] - ( - ObserverProfileId::Cie1931TwoDegreeV1, - ReferenceWhiteId::Iec61966D65ChromaticityV1, - TristimulusScale::RelativeY1, - ColorimetricFrameReleaseId::MutationSentinelV1, - ) => ColorimetricFrameV1::MutationSentinelV1, - } -} - -const fn map_numeric_domain_error(error: NumericDomainError) -> NumericDomainErrorV1 { - match error { - NumericDomainError::NonFinite => NumericDomainErrorV1::NonFinite, - NumericDomainError::Negative => NumericDomainErrorV1::Negative, - NumericDomainError::NotPositive => NumericDomainErrorV1::NotPositive, - NumericDomainError::AboveOne => NumericDomainErrorV1::AboveOne, - NumericDomainError::HueOutOfRange => NumericDomainErrorV1::HueOutOfRange, - } -} - -const fn map_tristimulus_component( - component: CoreTristimulusComponentV1, -) -> TristimulusComponentV1 { - match component { - CoreTristimulusComponentV1::X => TristimulusComponentV1::X, - CoreTristimulusComponentV1::Y => TristimulusComponentV1::Y, - CoreTristimulusComponentV1::Z => TristimulusComponentV1::Z, - } -} - -const fn map_tristimulus_domain_error( - error: TristimulusDomainErrorV1, -) -> (TristimulusComponentV1, NumericDomainErrorV1) { - ( - map_tristimulus_component(error.component()), - map_numeric_domain_error(error.reason()), - ) -} - -fn map_modeled_occurrence_error( - error: ModeledLcsOccurrenceFormationErrorV1, -) -> ModeledOccurrenceFailureV1 { - match error { - ModeledLcsOccurrenceFormationErrorV1::Tristimulus(source) => { - let (component, reason) = map_tristimulus_domain_error(source); - ModeledOccurrenceFailureV1::Tristimulus { component, reason } - } - ModeledLcsOccurrenceFormationErrorV1::Formation( - OccurrenceFormationError::FrameMismatch { stimulus, context }, - ) => ModeledOccurrenceFailureV1::FrameMismatch { - stimulus: map_colorimetric_frame(stimulus), - context: map_colorimetric_frame(context), - }, - ModeledLcsOccurrenceFormationErrorV1::ProvenanceReplayFailed(source) => { - let (component, reason) = map_tristimulus_domain_error(source); - ModeledOccurrenceFailureV1::ProvenanceReplayFailed { component, reason } - } - ModeledLcsOccurrenceFormationErrorV1::RecordedSampleDoesNotReplay { - recorded, - replayed, - } => ModeledOccurrenceFailureV1::RecordedSampleDoesNotReplay { - recorded: TristimulusSampleV1::from_core(recorded), - replayed: TristimulusSampleV1::from_core(replayed), - }, - ModeledLcsOccurrenceFormationErrorV1::OccurrenceSampleMismatch { - occurrence, - modeled, - } => ModeledOccurrenceFailureV1::OccurrenceSampleMismatch { - occurrence: TristimulusSampleV1::from_core(occurrence), - modeled: TristimulusSampleV1::from_core(modeled), - }, - } -} - fn map_observation_schema_mismatch( error: ObservationSchemaMismatchV1, ) -> ObservationBindingFailureV1 { @@ -3444,36 +3199,6 @@ fn map_plan_error(error: CoreProgramPlanErrorV1) -> UpdateErrorV1 { }, }, }, - ProgramSessionEvaluationError::ProgramTargetBinding { - case_index, - constraint, - occurrence, - context, - physical, - modeled, - } => UpdateErrorV1::InternalInvariant { - source: UpdateInvariantFailureV1::PhysicalModeledBinding { - case_index, - constraint: ConstraintIdV1::from_core(constraint), - occurrence: OccurrenceIdV1::from_core(occurrence), - context: AppearanceContextV1::from_core(context), - physical, - modeled, - }, - }, - ProgramSessionEvaluationError::ModeledOccurrence { - case_index, - occurrence, - context, - source, - } => UpdateErrorV1::InternalInvariant { - source: UpdateInvariantFailureV1::ModeledOccurrenceFormation { - case_index, - occurrence: OccurrenceIdV1::from_core(occurrence), - context: AppearanceContextV1::from_core(context), - source: map_modeled_occurrence_error(source), - }, - }, ProgramSessionEvaluationError::OutputVariesAcrossCases { output, first_case, @@ -3759,85 +3484,8 @@ mod update_error_projection_tests { } } - #[test] - fn every_modeled_occurrence_failure_has_an_isomorphic_boundary_witness() { - use crate::lcs_occurrence::{ - MUTATION_SENTINEL_XYZ_FRAME_V1, ModeledLcsOccurrenceFormationErrorV1, - OccurrenceFormationError, TristimulusSample, - }; - - let domain = TristimulusSample::try_from_xyz_for_test( - [f64::NAN, 0.2, 0.3], - IEC_SRGB_D65_XYZ_FRAME_V1, - ) - .unwrap_err(); - let recorded = - TristimulusSample::try_from_xyz_for_test([0.1, 0.2, 0.3], IEC_SRGB_D65_XYZ_FRAME_V1) - .unwrap(); - let replayed = - TristimulusSample::try_from_xyz_for_test([0.1, 0.2, 0.4], IEC_SRGB_D65_XYZ_FRAME_V1) - .unwrap(); - - let cases = [ - ( - ModeledLcsOccurrenceFormationErrorV1::Tristimulus(domain), - ModeledOccurrenceFailureV1::Tristimulus { - component: TristimulusComponentV1::X, - reason: NumericDomainErrorV1::NonFinite, - }, - ), - ( - ModeledLcsOccurrenceFormationErrorV1::Formation( - OccurrenceFormationError::FrameMismatch { - stimulus: IEC_SRGB_D65_XYZ_FRAME_V1, - context: MUTATION_SENTINEL_XYZ_FRAME_V1, - }, - ), - ModeledOccurrenceFailureV1::FrameMismatch { - stimulus: ColorimetricFrameV1::Iec61966Srgb8D65XyzRelativeY1V1, - context: ColorimetricFrameV1::MutationSentinelV1, - }, - ), - ( - ModeledLcsOccurrenceFormationErrorV1::ProvenanceReplayFailed(domain), - ModeledOccurrenceFailureV1::ProvenanceReplayFailed { - component: TristimulusComponentV1::X, - reason: NumericDomainErrorV1::NonFinite, - }, - ), - ( - ModeledLcsOccurrenceFormationErrorV1::RecordedSampleDoesNotReplay { - recorded, - replayed, - }, - ModeledOccurrenceFailureV1::RecordedSampleDoesNotReplay { - recorded: TristimulusSampleV1::from_core(recorded), - replayed: TristimulusSampleV1::from_core(replayed), - }, - ), - ( - ModeledLcsOccurrenceFormationErrorV1::OccurrenceSampleMismatch { - occurrence: recorded, - modeled: replayed, - }, - ModeledOccurrenceFailureV1::OccurrenceSampleMismatch { - occurrence: TristimulusSampleV1::from_core(recorded), - modeled: TristimulusSampleV1::from_core(replayed), - }, - ), - ]; - - for (source, expected) in cases { - assert_eq!(map_modeled_occurrence_error(source), expected); - } - } - #[test] fn every_unreachable_core_failure_keeps_its_subject_and_witness_facts() { - use crate::lcs_occurrence::{ - MUTATION_SENTINEL_XYZ_FRAME_V1, ModeledLcsOccurrenceFormationErrorV1, - OccurrenceFormationError, - }; use crate::observation::ObservationSchemaMismatchV1; let assert_invariant = |error: UpdateErrorV1, source: UpdateInvariantFailureV1| { @@ -3863,12 +3511,6 @@ mod update_error_projection_tests { UpdateInvariantFailureV1::EvaluatorProtocol { .. } => { UpdateInvariantV1::EvaluatorProtocol } - UpdateInvariantFailureV1::PhysicalModeledBinding { .. } => { - UpdateInvariantV1::PhysicalModeledBinding - } - UpdateInvariantFailureV1::ModeledOccurrenceFormation { .. } => { - UpdateInvariantV1::ModeledOccurrenceFormation - } UpdateInvariantFailureV1::OutputCaseInvariance { .. } => { UpdateInvariantV1::OutputCaseInvariance } @@ -3910,46 +3552,6 @@ mod update_error_projection_tests { }, }, ); - assert_invariant( - map_plan_error(ProgramSessionEvaluationError::ProgramTargetBinding { - case_index: 2, - constraint: ConstraintId::new(3), - occurrence: OccurrenceId::new(4), - context: context().0, - physical: Srgb8::new([1, 2, 3]), - modeled: Srgb8::new([3, 2, 1]), - }), - UpdateInvariantFailureV1::PhysicalModeledBinding { - case_index: 2, - constraint: ConstraintIdV1::new(3), - occurrence: OccurrenceIdV1::new(4), - context: context(), - physical: Srgb8::new([1, 2, 3]), - modeled: Srgb8::new([3, 2, 1]), - }, - ); - assert_invariant( - map_plan_error(ProgramSessionEvaluationError::ModeledOccurrence { - case_index: 2, - occurrence: OccurrenceId::new(4), - context: context().0, - source: ModeledLcsOccurrenceFormationErrorV1::Formation( - OccurrenceFormationError::FrameMismatch { - stimulus: IEC_SRGB_D65_XYZ_FRAME_V1, - context: MUTATION_SENTINEL_XYZ_FRAME_V1, - }, - ), - }), - UpdateInvariantFailureV1::ModeledOccurrenceFormation { - case_index: 2, - occurrence: OccurrenceIdV1::new(4), - context: context(), - source: ModeledOccurrenceFailureV1::FrameMismatch { - stimulus: ColorimetricFrameV1::Iec61966Srgb8D65XyzRelativeY1V1, - context: ColorimetricFrameV1::MutationSentinelV1, - }, - }, - ); assert_invariant( map_plan_error(ProgramSessionEvaluationError::OutputVariesAcrossCases { output: OutputSlotId::new(5), diff --git a/crates/labcolors-core/src/program_boundary_tests.rs b/crates/labcolors-core/src/program_boundary_tests.rs index 0f133d37..2177eda7 100644 --- a/crates/labcolors-core/src/program_boundary_tests.rs +++ b/crates/labcolors-core/src/program_boundary_tests.rs @@ -9,8 +9,8 @@ use crate::Srgb8; use crate::program::{ AppearanceContextErrorKindV1, AppearanceContextFieldV1, AppearanceContextV1, AssessmentV1, CertificateV1, CompileErrorHandleV1, CompileErrorKindV1, CompileErrorV1, ConstraintIdV1, - DraftErrorV1, DraftV1, EvidenceBoundsErrorV1, InstantiateErrorV1, JointChoiceV1, - JointOrderErrorV1, JointStateV1, ModeledPointV1, NumericDomainErrorV1, ObservationHeadV1, + ContentIdentityV2, DraftErrorV1, DraftV1, EvidenceBoundsErrorV1, InstantiateErrorV1, + JointChoiceV1, JointOrderErrorV1, JointStateV1, NumericDomainErrorV1, ObservationHeadV1, OccurrenceIdV1, OpacityInputIdV1, OperationV1, OutputSlotIdV1, OwnerV1, PaintIdV1, PhysicalPointV1, ProjectionV1, ScenarioV1, SessionV1, SignalV1, SourceIdV1, StateKindV1, SurfaceIdV1, SurfaceInputPortIdV1, SurroundV1, TargetCandidateIdV1, TargetCandidateV1, @@ -114,10 +114,7 @@ fn assert_projection_is_owner_bound(projection: ProjectionV1<'_, '_>) { let _ = physical.opacity(); let _: Srgb8 = physical.backdrop(); let _: Srgb8 = physical.visible(); - let ModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(modeled) = - binding.modeled(); - let _: [f64; 3] = modeled.xyz(); - let context = modeled.appearance_context(); + let context = binding.appearance_context(); let _ = context.adapting_luminance_cd_m2(); let _ = context.background_luminance_ratio_yb_yw(); let _ = context.surround(); @@ -680,7 +677,7 @@ fn owner_and_update_errors_preserve_content_and_input_identity() { let owner = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input) .compile() .unwrap(); - let owner_identity = owner.content_identity(); + let owner_identity: ContentIdentityV2 = owner.content_identity(); let mut session = owner.instantiate(13).unwrap(); let no_scenarios = []; diff --git a/crates/labcolors-core/src/program_identity.rs b/crates/labcolors-core/src/program_identity.rs index 0c29d56c..467aa236 100644 --- a/crates/labcolors-core/src/program_identity.rs +++ b/crates/labcolors-core/src/program_identity.rs @@ -7,22 +7,26 @@ use super::*; -const DOMAIN_V1: &[u8] = b"labcolors.program-content-identity.v1\0"; -// Максимальный V1-цвет принадлежит Occurrence: теги вершины, композиции, +const DOMAIN_V2: &[u8] = b"labcolors.program-content-identity.v2\0"; +// Максимальный V2-цвет принадлежит Occurrence: теги вершины, композиции, // контекста и frame, два binary64-параметра наблюдения и surround. Явная // граница устраняет аллокацию на каждую вершину и требует пересмотра при // расширении схемы вместо скрытого runtime-лимита. const COLOR_CAPACITY: usize = 1 + 1 + 1 + 4 + 8 + 8 + 1; mod release_tag { - pub(super) const PROGRAM_SCHEMA_V1: u8 = 1; + pub(super) const PROGRAM_SCHEMA_V2: u8 = 2; pub(super) const DECLARED_TOTAL_ORDER_V1: u8 = 1; pub(super) const FRESH_FULL_RECHECK_V1: u8 = 1; pub(super) const ATOMIC_OBSERVATION_GROUP_V1: u8 = 1; pub(super) const ENCODED_PAINT_EMISSION_V1: u8 = 1; + #[cfg(test)] pub(super) const MODELED_LCS_OCCURRENCE_V1: u8 = 1; + #[cfg(test)] + pub(super) const MODELED_LCS_OCCURRENCE_MUTATION_SENTINEL_V1: u8 = 2; pub(super) const IEC_SRGB8_D65_OUTPUT_PROFILE_V1: u8 = 1; + #[cfg(test)] pub(super) const IEC_SRGB8_TO_XYZ_D65_TRANSFORM_V1: u8 = 1; pub(super) const CIE1931_TWO_DEGREE_OBSERVER_V1: u8 = 1; pub(super) const IEC61966_D65_REFERENCE_WHITE_V1: u8 = 1; @@ -54,16 +58,18 @@ mod release_tag { pub(super) const WCAG22_SC_1_4_3_TEXT_LARGE_SCALE: u8 = 2; pub(super) const WCAG22_SC_1_4_11_UI_COMPONENT_OR_STATE: u8 = 3; pub(super) const WCAG22_SC_1_4_11_GRAPHICAL_OBJECT: u8 = 4; + #[cfg(test)] + pub(super) const MODELED_LCS_PROBE_FAMILY_V1: u8 = 3; } -/// Устойчивый к коллизиям адрес канонизированного содержимого Program V1. +/// Устойчивый к коллизиям адрес канонизированного содержимого Program V2. /// /// SHA-256 не делает адрес инъективным. Адрес не связывает пространства opaque /// ID и не подтверждает владельца, поколение либо revision. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct ProgramContentIdentityV1([u8; 32]); +pub(crate) struct ProgramContentIdentityV2([u8; 32]); -impl ProgramContentIdentityV1 { +impl ProgramContentIdentityV2 { pub(crate) const fn as_bytes(&self) -> &[u8; 32] { &self.0 } @@ -316,15 +322,15 @@ where fn program_root_color() -> Result { let mut color = VertexColorV1::new(vertex_tag::PROGRAM); // Эти теги связывают адрес с версиями исполняемых законов: схемой Program, - // total-order selection, финальной перепроверкой, атомарным наблюдением, - // encoded Paint emission и формированием modeled LCS. + // total-order selection, финальной перепроверкой, атомарным наблюдением и + // encoded Paint emission. Derived capability releases bind only the + // constraints that execute them. for release in [ - release_tag::PROGRAM_SCHEMA_V1, + release_tag::PROGRAM_SCHEMA_V2, release_tag::DECLARED_TOTAL_ORDER_V1, release_tag::FRESH_FULL_RECHECK_V1, release_tag::ATOMIC_OBSERVATION_GROUP_V1, release_tag::ENCODED_PAINT_EMISSION_V1, - release_tag::MODELED_LCS_OCCURRENCE_V1, ] { color.push_u8(release)?; } @@ -338,13 +344,6 @@ fn write_signal(color: &mut VertexColorV1, signal: ColorSignal) -> Result<(), Pr } }; color.push_u8(profile)?; - color.push_u8(match crate::lcs_occurrence::ADMITTED_SRGB8_TRISTIMULUS_BINDING_V1 - .transform_release() - { - crate::lcs_occurrence::ColorimetricTransformReleaseId::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1V1 => { - release_tag::IEC_SRGB8_TO_XYZ_D65_TRANSFORM_V1 - } - })?; color.push_srgb8(signal.srgb8()) } @@ -495,6 +494,23 @@ fn constraint_color( color.push_u8(wcag_criterion_tag(criterion))?; } #[cfg(test)] + ProgramConstraintContentV1::ModeledLcsProbe { release } => { + color.push_u8(release_tag::MODELED_LCS_PROBE_FAMILY_V1)?; + color.push_u8(match release.modeled_lcs_release() { + crate::lcs_occurrence::ModeledLcsOccurrenceReleaseId::V1 => { + release_tag::MODELED_LCS_OCCURRENCE_V1 + } + crate::lcs_occurrence::ModeledLcsOccurrenceReleaseId::MutationSentinelV1 => { + release_tag::MODELED_LCS_OCCURRENCE_MUTATION_SENTINEL_V1 + } + })?; + color.push_u8(match release.transform_release() { + crate::lcs_occurrence::ColorimetricTransformReleaseId::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1V1 => { + release_tag::IEC_SRGB8_TO_XYZ_D65_TRANSFORM_V1 + } + })?; + } + #[cfg(test)] ProgramConstraintContentV1::FinalRecheckMutantExactSrgb8 { expected } => { for tag in [0xFE_u8, 1, 1, 1] { color.push_u8(tag)?; @@ -976,7 +992,7 @@ fn serialize_leaf( .and_then(|value| value.checked_add(color.as_slice().len())) .ok_or(ProgramCompileError::ResourceExhausted) })?; - let capacity = DOMAIN_V1 + let capacity = DOMAIN_V2 .len() .checked_add(16) .and_then(|value| value.checked_add(color_bytes)) @@ -986,7 +1002,7 @@ fn serialize_leaf( output .try_reserve_exact(capacity) .map_err(|_| ProgramCompileError::ResourceExhausted)?; - output.extend_from_slice(DOMAIN_V1); + output.extend_from_slice(DOMAIN_V2); push_u64_bytes(&mut output, usize_as_u64(graph.colors.len())?); push_u64_bytes(&mut output, usize_as_u64(graph.edge_count)?); @@ -1320,9 +1336,9 @@ fn canonical_preimage(graph: &CanonicalGraphV1) -> Result, ProgramCompil canonical_search(graph).map(|(preimage, _)| preimage) } -pub(super) fn compile_program_content_identity_v1( +pub(super) fn compile_program_content_identity_v2( program: &Program, -) -> Result +) -> Result where Evaluation: ProgramConstraintEvaluatorSetV1, ProgramConstraintInvocationOf: Copy, @@ -1330,7 +1346,7 @@ where let graph = build_graph(program)?; let preimage = canonical_preimage(&graph)?; let digest = crate::sha256::digest(&preimage); - Ok(ProgramContentIdentityV1(*digest.as_bytes())) + Ok(ProgramContentIdentityV2(*digest.as_bytes())) } #[cfg(test)] @@ -1400,6 +1416,48 @@ mod tests { } } + #[test] + fn modeled_lcs_release_mutation_affects_only_lcs_aware_constraint_content() { + fn mutate_modeled_lcs_release( + content: ProgramConstraintContentV1, + ) -> ProgramConstraintContentV1 { + match content { + ProgramConstraintContentV1::ModeledLcsProbe { release } => { + ProgramConstraintContentV1::ModeledLcsProbe { + release: release.with_modeled_lcs_release_for_test( + crate::lcs_occurrence::ModeledLcsOccurrenceReleaseId::MutationSentinelV1, + ), + } + } + encoded_only => encoded_only, + } + } + + let exact = crate::constraints::ExactSrgb8IdentityV1 + .program_constraint_content_v1(Srgb8::new([0x12, 0x34, 0x56])); + let wcag = crate::constraints::Wcag22Srgb8V1 + .program_constraint_content_v1(Wcag22CriterionV1::Sc143TextDefault); + for encoded_only in [exact, wcag] { + assert_eq!( + constraint_color(vertex_tag::CONSTRAINT_HARD, encoded_only).unwrap(), + constraint_color( + vertex_tag::CONSTRAINT_HARD, + mutate_modeled_lcs_release(encoded_only), + ) + .unwrap(), + "an encoded-only descriptor has no LCS release capability to mutate", + ); + } + + let lcs = crate::constraints::LcsProbeProgramEvaluatorV1.program_constraint_content_v1(); + assert_ne!( + constraint_color(vertex_tag::CONSTRAINT_HARD, lcs).unwrap(), + constraint_color(vertex_tag::CONSTRAINT_HARD, mutate_modeled_lcs_release(lcs),) + .unwrap(), + "an LCS-aware descriptor must bind the modeled occurrence release", + ); + } + fn context_color(context: AppearanceContextId) -> VertexColorV1 { let mut color = VertexColorV1::new(vertex_tag::OCCURRENCE); write_context(&mut color, context).unwrap(); diff --git a/crates/labcolors-core/src/program_identity_tests.rs b/crates/labcolors-core/src/program_identity_tests.rs index 218c0b5e..d420533b 100644 --- a/crates/labcolors-core/src/program_identity_tests.rs +++ b/crates/labcolors-core/src/program_identity_tests.rs @@ -9,8 +9,8 @@ use crate::program_session::{ CompositionProfile, ConstraintId, ConstraintInvocation, ConstraintSet, CoreProgramConstraintInvocationV1, CoreProgramEvaluatorsV1, CoreProgramV1, DeclaredJointSelectionV1, JointCandidateStateV1, ObservationGroup, Occurrence, OpacityInput, - OutputBinding, OutputSlotId, Paint, Program, Source, SourceId, Surface, Target, - TargetCandidateChoiceV1, TargetCandidateId, TargetCandidateV1, TargetId, + OutputBinding, OutputSlotId, Paint, Program, ProgramContentIdentityV2, Source, SourceId, + Surface, Target, TargetCandidateChoiceV1, TargetCandidateId, TargetCandidateV1, TargetId, }; use crate::wcag22::Wcag22CriterionV1; @@ -201,7 +201,7 @@ fn fixed_graph_identity_ignores_opaque_names_and_unordered_declaration_order() { } #[test] -fn canonical_v1_digest_is_cross_platform_golden() { +fn canonical_v2_digest_is_cross_platform_golden() { let ids = FixedIds { sources: [SourceId::new(10), SourceId::new(20)], targets: [TargetId::new(30), TargetId::new(40)], @@ -222,11 +222,12 @@ fn canonical_v1_digest_is_cross_platform_golden() { .compile() .unwrap(); + let identity: ProgramContentIdentityV2 = compiled.content_identity(); assert_eq!( - compiled.content_identity().as_bytes(), + identity.as_bytes(), &[ - 168, 248, 71, 93, 18, 147, 245, 229, 235, 83, 237, 210, 202, 114, 6, 19, 16, 224, 85, - 63, 190, 86, 219, 66, 99, 226, 22, 200, 208, 224, 149, 196, + 105, 3, 194, 140, 229, 146, 207, 108, 6, 103, 170, 89, 223, 123, 17, 99, 144, 255, 27, + 240, 129, 52, 2, 255, 197, 97, 146, 190, 217, 50, 138, 120, ] ); } @@ -568,8 +569,8 @@ fn canonical_full_ids() -> FullIds { } #[test] -fn complete_program_schema_v1_digest_is_cross_platform_golden() { - // Вместе с fixed golden этот Program содержит каждый V1 vertex/edge tag, +fn complete_program_schema_v2_digest_is_cross_platform_golden() { + // Вместе с fixed golden этот Program содержит каждый V2 vertex/edge tag, // обе constraint families и оба режима. Случайная смена кодировки требует // явной смены версии, а не тихого перевыпуска прежнего content address. let compiled = full_program( @@ -583,8 +584,8 @@ fn complete_program_schema_v1_digest_is_cross_platform_golden() { assert_eq!( compiled.content_identity().as_bytes(), &[ - 31, 240, 88, 38, 57, 68, 24, 218, 176, 123, 232, 154, 83, 136, 136, 238, 75, 62, 8, - 163, 188, 120, 229, 152, 163, 217, 101, 60, 245, 191, 167, 219, + 47, 106, 231, 159, 6, 154, 100, 143, 78, 142, 99, 175, 114, 42, 229, 41, 69, 234, 79, + 180, 60, 17, 100, 195, 213, 202, 188, 234, 16, 229, 67, 159, ] ); } diff --git a/crates/labcolors-core/src/program_joint_integration_tests.rs b/crates/labcolors-core/src/program_joint_integration_tests.rs index 88afd5a1..a35da7d7 100644 --- a/crates/labcolors-core/src/program_joint_integration_tests.rs +++ b/crates/labcolors-core/src/program_joint_integration_tests.rs @@ -799,7 +799,6 @@ fn bijective_source_target_and_candidate_renaming_preserves_joint_evidence() { .cells() .iter() .map(|cell| { - let modeled = cell.modeled_lcs_occurrence(); ( cell.candidate_state_index(), cell.case_index(), @@ -807,8 +806,7 @@ fn bijective_source_target_and_candidate_renaming_preserves_joint_evidence() { cell.target(), cell.is_hard(), cell.result().is_violation(), - modeled, - modeled.signal(), + cell.appearance_context(), ) }) .collect::>(); diff --git a/crates/labcolors-core/src/program_lcs_integration_tests.rs b/crates/labcolors-core/src/program_lcs_integration_tests.rs index cb09ebee..2e333b12 100644 --- a/crates/labcolors-core/src/program_lcs_integration_tests.rs +++ b/crates/labcolors-core/src/program_lcs_integration_tests.rs @@ -3,13 +3,14 @@ use crate::appearance::{ OccurrenceId, OpacityInputId, PaintId, PointOpacityOverSurfaceV1, SurfaceId, SurfaceInputPortId, }; use crate::constraints::{ - ExactSrgb8IdentityV1, ProgramPointAssessmentErrorV1, ProgramVisiblePointBindingV1, - Wcag22Srgb8V1, assess_program_point_hard, + ExactSrgb8IdentityV1, HardDecision, LcsProbeProgramEvaluatorV1, ProgramLcsDependencyReleaseV1, + ProgramLcsPointAdapterV1, ProgramLcsPointAssessmentErrorV1, ProgramPointOccurrenceV1, + ProgramVisiblePointBindingV1, Wcag22Srgb8V1, assess_program_lcs_point_hard, }; use crate::lcs_occurrence::{ - AdaptingLuminanceCdM2, AppearanceContextId, AppearanceContextSchemaReleaseId, AppearanceState, - BackgroundLuminanceRatio, ColorSignal, HueState, IEC_SRGB_D65_XYZ_FRAME_V1, - MODELED_TRISTIMULUS_DERIVATION_CALLS, ModeledLcsOccurrenceV1, SurroundProfileId, + AdaptingLuminanceCdM2, AppearanceContextId, AppearanceContextSchemaReleaseId, + BackgroundLuminanceRatio, ColorSignal, IEC_SRGB_D65_XYZ_FRAME_V1, + MODELED_TRISTIMULUS_DERIVATION_CALLS, MUTATION_SENTINEL_XYZ_FRAME_V1, SurroundProfileId, }; use crate::observation::{ ObservationGroupId, ObservationPayloadInput, ObservationStreamId, ObservationUpdateInput, @@ -272,19 +273,20 @@ fn compiled_duplicate_constraint_program() -> CompiledProgram mismatch, - ProgramPointAssessmentErrorV1::Evaluator(unreachable) => match unreachable {}, + let first = assess_program_lcs_point_hard(&adapter, &LcsProbeProgramEvaluatorV1, 1) + .expect_err("the incompatible frame must fail"); + let second = assess_program_lcs_point_hard(&adapter, &LcsProbeProgramEvaluatorV1, 2) + .expect_err("the memoized incompatible frame must fail identically"); + + assert_eq!(first, second); + assert!(matches!( + first, + ProgramLcsPointAssessmentErrorV1::Formation(_) + )); + assert_eq!( + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.get()), + 1, + "one adapter must cache a failed derivation as well as a successful one", + ); +} + +#[test] +fn equal_bytes_and_context_cannot_mix_provenance_between_physical_occurrences() { + let context = context(SurroundProfileId::AverageV1); + let translucent = PointOpacityOverSurfaceV1::evaluate([0; 3], 0.5, [0xFF; 3]).unwrap(); + let solid = PointOpacityOverSurfaceV1::evaluate([0x80; 3], 1.0, [0; 3]).unwrap(); + assert_eq!(translucent.visible(), solid.visible()); + assert_ne!( + translucent.visible_point_binding(), + solid.visible_point_binding() + ); + let translucent_point = ProgramPointOccurrenceV1::from_resolved(&translucent, context); + let solid_point = ProgramPointOccurrenceV1::from_resolved(&solid, context); + let translucent_adapter = ProgramLcsPointAdapterV1::new(translucent_point); + let solid_adapter = ProgramLcsPointAdapterV1::new(solid_point); + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(0)); + + let translucent_evidence = + assess_program_lcs_point_hard(&translucent_adapter, &LcsProbeProgramEvaluatorV1, 1) + .unwrap(); + let solid_evidence = + assess_program_lcs_point_hard(&solid_adapter, &LcsProbeProgramEvaluatorV1, 1).unwrap(); + let HardDecision::Pass(translucent_evidence) = translucent_evidence else { + panic!("the LCS probe has no violation branch"); + }; + let HardDecision::Pass(solid_evidence) = solid_evidence else { + panic!("the LCS probe has no violation branch"); }; - assert_eq!(mismatch.physical(), Srgb8::new([0; 3])); - assert_eq!(mismatch.modeled(), Srgb8::new([0xFF; 3])); + + assert_eq!( + translucent_evidence.binding().physical(), + translucent_point.binding() + ); + assert_eq!(solid_evidence.binding().physical(), solid_point.binding()); + assert_ne!( + translucent_evidence.binding().physical(), + solid_evidence.binding().physical() + ); + assert_eq!( + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.get()), + 2, + "byte equality must not collapse distinct physical occurrence scopes", + ); } #[test] -fn program_derives_lcs_once_per_target_and_case_without_eager_cam16() { +fn exact_report_only_program_does_not_derive_lcs() { let compiled = compiled_duplicate_constraint_program(); let mut session = compiled.instantiate(STREAM_A).unwrap(); MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(0)); @@ -521,8 +589,8 @@ fn program_derives_lcs_once_per_target_and_case_without_eager_cam16() { }; assert_eq!(current.report().cells().len(), 4); assert_eq!( - derivations, 2, - "two constraints sharing one target must reuse one LCS derivation in each physical case", + derivations, 0, + "encoded-only constraints must not pay for or depend on a derived LCS occurrence", ); assert_eq!( cam16_forwards, 0, @@ -531,7 +599,54 @@ fn program_derives_lcs_once_per_target_and_case_without_eager_cam16() { } #[test] -fn declaration_permutations_preserve_canonical_lcs_cells_and_output_signals() { +fn wcag_program_does_not_derive_lcs() { + let compiled = compiled_wcag_program(1.0); + let mut session = compiled.instantiate(STREAM_A).unwrap(); + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(0)); + let state = session.update(observed_white(STREAM_A)).unwrap(); + assert!(matches!(state, SessionState::Ready { .. })); + assert_eq!( + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.get()), + 0, + "WCAG must consume the final encoded pair without constructing LCS", + ); +} + +#[test] +fn encoded_only_program_is_not_rejected_by_an_lcs_incompatible_declared_context() { + let incompatible = AppearanceContextId::from_inputs( + AppearanceContextSchemaReleaseId::Ciecam16ViewingInputsV1, + MUTATION_SENTINEL_XYZ_FRAME_V1, + AdaptingLuminanceCdM2::try_new(64.0).unwrap(), + BackgroundLuminanceRatio::try_new(0.2).unwrap(), + SurroundProfileId::AverageV1, + ); + let compiled = compiled_program( + &[(AVERAGE_OCCURRENCE, AVERAGE_CONSTRAINT, incompatible)], + Srgb8::new([0x80; 3]), + 0.5, + false, + ); + let mut session = compiled.instantiate(STREAM_A).unwrap(); + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(0)); + + let state = session.update(observed_white(STREAM_A)).unwrap(); + let SessionState::Ready { current } = state else { + panic!("an encoded evaluator must not inherit an unrelated LCS frame requirement"); + }; + let [cell] = current.report().cells() else { + panic!("the exact constraint must still emit one evidence cell"); + }; + assert_eq!(cell.appearance_context(), incompatible); + assert_eq!( + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.get()), + 0, + "an LCS-incompatible declared context must not be derived by an encoded-only constraint", + ); +} + +#[test] +fn declaration_permutations_preserve_canonical_context_cells_and_output_signals() { let declarations = [ ( AVERAGE_OCCURRENCE, @@ -563,7 +678,7 @@ fn declaration_permutations_preserve_canonical_lcs_cells_and_output_signals() { cell.case_index(), cell.constraint(), cell.target(), - cell.modeled_lcs_occurrence(), + cell.appearance_context(), cell.result().is_violation(), ) }; diff --git a/crates/labcolors-core/src/program_mixed_evaluator_tests.rs b/crates/labcolors-core/src/program_mixed_evaluator_tests.rs index 1085f0a7..59f08ce2 100644 --- a/crates/labcolors-core/src/program_mixed_evaluator_tests.rs +++ b/crates/labcolors-core/src/program_mixed_evaluator_tests.rs @@ -17,9 +17,9 @@ use crate::observation::{ }; use crate::program::{ AccessErrorV1, AssessmentV1, CertificateV1, ConflictCellV1, ConstraintModeV1, - ExactSrgb8EvidenceV1, ModeledPointV1, ObservationHeadV1, ObservationV1, OperationV1, - OutputSlotIdV1, OwnerV1, PhysicalPointV1, ProjectionV1, ScenarioV1, SignalV1, StateKindV1, - SurroundV1, UpdateErrorKindV1, UpdateV1, VerdictV1, VerifiedCellV1, Wcag22Srgb8EvidenceV1, + ExactSrgb8EvidenceV1, ObservationHeadV1, ObservationV1, OperationV1, OutputSlotIdV1, OwnerV1, + PhysicalPointV1, ProjectionV1, ScenarioV1, SignalV1, StateKindV1, SurroundV1, + UpdateErrorKindV1, UpdateV1, VerdictV1, VerifiedCellV1, Wcag22Srgb8EvidenceV1, }; use crate::program_session::{ CORE_PROGRAM_ASSESSMENT_CALLS, CompiledCoreProgramV1, CompositionProfile, ConstraintId, @@ -284,18 +284,8 @@ fn assert_public_binding_matches_core( Srgb8::new(core_occurrence.output_rgb()) ); - let ModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(public_modeled) = - public.binding().modeled(); - assert_eq!( - public_modeled.xyz().map(f64::to_bits), - core.modeled_lcs() - .derivation() - .sample() - .xyz() - .map(f64::to_bits) - ); - let public_context = public_modeled.appearance_context(); - let core_context = core.modeled_lcs().occurrence().context(); + let public_context = public.binding().appearance_context(); + let core_context = core.context(); assert_eq!( public_context.adapting_luminance_cd_m2().to_bits(), core_context.adapting_luminance_cd_m2().to_bits() @@ -549,12 +539,7 @@ fn consume_public_assessment(assessment: AssessmentV1<'_>, probe: &mut Projectio probe.mix_srgb8(physical.backdrop()); probe.mix_srgb8(physical.visible()); - let ModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(modeled) = - assessment.binding().modeled(); - for coordinate in modeled.xyz() { - probe.mix(coordinate.to_bits()); - } - let context = modeled.appearance_context(); + let context = assessment.binding().appearance_context(); probe.mix(context.adapting_luminance_cd_m2().to_bits()); probe.mix(context.background_luminance_ratio_yb_yw().to_bits()); probe.mix(match context.surround() { @@ -709,6 +694,7 @@ fn assert_unknown_head( #[test] fn one_program_retains_typed_exact_and_wcag22_outcomes() { + let declared_context = context(); let program: CoreProgramV1 = Program::new( vec![Source::new(SOURCE, signal([0; 3]))], vec![Target::fixed(TARGET, SOURCE)], @@ -727,7 +713,7 @@ fn one_program_retains_typed_exact_and_wcag22_outcomes() { PAINT, SURFACE, CompositionProfile::EncodedSrgb8SourceOverV1, - context(), + declared_context, )], ConstraintSet::new( vec![ @@ -773,10 +759,7 @@ fn one_program_retains_typed_exact_and_wcag22_outcomes() { evidence.capability(), &ExactIdentityCapabilityV1::FinalOccurrenceSrgb8IdentityV1, ); - assert_eq!( - evidence.binding().modeled_lcs(), - exact.modeled_lcs_occurrence(), - ); + assert_eq!(evidence.binding().context(), declared_context); let ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::Wcag22Srgb8(evidence)) = wcag.result() @@ -784,10 +767,7 @@ fn one_program_retains_typed_exact_and_wcag22_outcomes() { panic!("the second cell must retain WCAG22-specific pass evidence"); }; assert_eq!(evidence.release(), &wcag22_profile_v1().profile_id); - assert_eq!( - evidence.binding().modeled_lcs(), - wcag.modeled_lcs_occurrence(), - ); + assert_eq!(evidence.binding().context(), declared_context); assert_ne!( core::any::type_name_of_val(evidence.identity()), core::any::type_name_of_val(exact.result()), @@ -1118,7 +1098,10 @@ fn committed_projection_is_zero_alloc_and_repeats_no_composite_transform_or_eval let ready_derivations = MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get); let ready_assessments = CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get); assert!(ready_compositions > 0); - assert!(ready_derivations > 0); + assert_eq!( + ready_derivations, 0, + "the encoded-only evaluator set must not derive an LCS view", + ); assert!(ready_assessments > 0); let (ready_probe, ready_allocations) = crate::test_support::measured_allocations(|| { consume_public_projection(std::hint::black_box(owner.project(&session).unwrap())) diff --git a/crates/labcolors-core/src/program_session.rs b/crates/labcolors-core/src/program_session.rs index de47e97f..ef691a26 100644 --- a/crates/labcolors-core/src/program_session.rs +++ b/crates/labcolors-core/src/program_session.rs @@ -6,9 +6,10 @@ //! occurrences, and outputs bind opaque slots back to Paints. The compiled //! result owns only admitted, canonical topology; runtime observation, //! lifecycle and terminal emission belong to the sole revision-bound Session. -//! Output transport remains encoded, while every assessed visible occurrence -//! carries deterministic, context-bound modeled LCS provenance. Neither claim -//! is renderer observation or human-subject evidence. +//! Output transport and encoded-only assessments retain exact physical +//! occurrence evidence plus the declared appearance context. A modeled LCS +//! occurrence is derived only through its separate typed capability; neither +//! claim is renderer observation or human-subject evidence. use std::marker::PhantomData; use std::rc::{Rc, Weak}; @@ -24,17 +25,15 @@ use crate::composition::CompositionProfileV1; use crate::constraints::{ Evaluator, ExactSrgb8IdentityV1, HardDecision, ProgramConstraintContentV1, ProgramPointAssessmentErrorV1, ProgramPointEvaluatorContentV1, ProgramPointEvaluatorV1, - ProgramPointInvocation, ProgramPointTargetV1, ProgramVisiblePointBindingV1, - ProgramVisiblePointPassEvidence, ProgramVisiblePointViolationEvidence, Wcag22Srgb8V1, - assess_program_point_hard, + ProgramPointInvocation, ProgramPointOccurrenceV1, ProgramPointTargetV1, + ProgramVisiblePointBindingV1, ProgramVisiblePointPassEvidence, + ProgramVisiblePointViolationEvidence, Wcag22Srgb8V1, assess_program_point_hard, }; use crate::joint::{ AdmittedFiniteJointOrderV1, FiniteDomainOrdinalV1, FiniteJointOrderErrorV1, admit_finite_joint_order_v1, }; -use crate::lcs_occurrence::{ - AppearanceContextId, ColorSignal, ModeledLcsOccurrenceFormationErrorV1, ModeledLcsOccurrenceV1, -}; +use crate::lcs_occurrence::{AppearanceContextId, ColorSignal}; use crate::observation::{ CanonicalObservationSchemaV1, ObservationError, ObservationGroupId, ObservationSchemaMismatchV1, ObservationStreamId, RevisionBoundObservationV1, @@ -48,7 +47,7 @@ use crate::wcag22::Wcag22CriterionV1; #[path = "program_identity.rs"] mod identity; -pub(crate) use identity::ProgramContentIdentityV1; +pub(crate) use identity::ProgramContentIdentityV2; /// Opaque identity of one immutable authored colour source. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -480,8 +479,7 @@ pub(crate) trait ProgramConstraintEvaluatorSetV1: Sized { fn assess( &self, - source: &crate::appearance::ResolvedOccurrence, - modeled_lcs: ModeledLcsOccurrenceV1, + point: ProgramPointOccurrenceV1, invocation: Self::Invocation, ) -> ProgramConstraintAssessmentResultV1; @@ -504,11 +502,10 @@ where fn assess( &self, - source: &crate::appearance::ResolvedOccurrence, - modeled_lcs: ModeledLcsOccurrenceV1, + point: ProgramPointOccurrenceV1, invocation: Self::Invocation, ) -> ProgramConstraintAssessmentResultV1 { - assess_program_point_hard(source, modeled_lcs, self, invocation) + assess_program_point_hard(point, self, invocation) } fn pass_binding(evidence: &Self::PassEvidence) -> ProgramVisiblePointBindingV1 { @@ -528,16 +525,17 @@ where thread_local! { /// Counts the concrete production evaluator dispatch itself, so certificate /// projection cannot accidentally recompute a verdict while still reusing - /// the stored physical and modeled witnesses. + /// the stored physical witness and declared context. pub(crate) static CORE_PROGRAM_ASSESSMENT_CALLS: core::cell::Cell = const { core::cell::Cell::new(0) }; } /// Generates the code-owned heterogeneous evaluator set as parallel closed /// unions. Each evidence variant retains the concrete evaluator's physical + -/// LCS binding, identity, release, capability, invocation, measurement, and -/// classifier payload. Adding a family therefore requires a Core code change -/// in this single declaration, not a client-extensible semantic registry. +/// declared-context binding, identity, release, capability, invocation, +/// measurement, and classifier payload. Adding a family therefore requires a +/// Core code change in this single declaration, not a client-extensible +/// semantic registry. macro_rules! define_core_program_evaluators_v1 { ($( $variant:ident { @@ -577,8 +575,7 @@ macro_rules! define_core_program_evaluators_v1 { fn assess( &self, - source: &crate::appearance::ResolvedOccurrence, - modeled_lcs: ModeledLcsOccurrenceV1, + point: ProgramPointOccurrenceV1, invocation: Self::Invocation, ) -> ProgramConstraintAssessmentResultV1 { #[cfg(test)] @@ -587,8 +584,7 @@ macro_rules! define_core_program_evaluators_v1 { $(CoreProgramConstraintInvocationV1::$variant(invocation) => { let evaluator: $evaluator = $evaluator_value; match assess_program_point_hard( - source, - modeled_lcs, + point, &evaluator, invocation, ) { @@ -598,9 +594,6 @@ macro_rules! define_core_program_evaluators_v1 { Ok(HardDecision::Violation(evidence)) => Ok(HardDecision::Violation( CoreProgramViolationEvidenceV1::$variant(evidence), )), - Err(ProgramPointAssessmentErrorV1::Binding(source)) => { - Err(ProgramPointAssessmentErrorV1::Binding(source)) - } Err(ProgramPointAssessmentErrorV1::Evaluator(source)) => Err( ProgramPointAssessmentErrorV1::Evaluator( CoreProgramEvaluatorErrorV1::$variant(source), @@ -1024,7 +1017,7 @@ struct CompiledPointConstraint { id: ConstraintId, target_id: OccurrenceId, target: CompiledOccurrenceSlotV1, - modeled_occurrence_index: usize, + occurrence_context_index: usize, mode: CompiledConstraintModeV1, invocation: Invocation, } @@ -1062,7 +1055,7 @@ where Evaluation: ProgramConstraintEvaluatorSetV1, ProgramConstraintInvocationOf: Copy, { - content_identity: ProgramContentIdentityV1, + content_identity: ProgramContentIdentityV2, evaluator: Evaluation, graph: CompiledAppearanceGraph, binding_template: AdmittedAppearanceBindings, @@ -1102,12 +1095,12 @@ where self.owner_generation.observation_group.id } - /// Контентный адрес Program в границах схемы V1. + /// Контентный адрес Program в границах схемы V2. /// /// Opaque ID и порядок неупорядоченных объявлений исключены; явный joint /// order входит в адрес. Адрес не подтверждает поколение владельца и не /// заменяет revision-bound evidence. - pub fn content_identity(&self) -> ProgramContentIdentityV1 { + pub fn content_identity(&self) -> ProgramContentIdentityV2 { self.owner_generation.content_identity } @@ -1181,18 +1174,12 @@ where .graph .new_workspace() .map_err(map_session_instantiate_error)?; - let mut modeled_occurrences = Vec::new(); - modeled_occurrences - .try_reserve_exact(self.owner_generation.occurrence_contexts.len()) - .map_err(|_| ProgramSessionInstantiateError::ResourceExhausted)?; - modeled_occurrences.resize(self.owner_generation.occurrence_contexts.len(), None); Ok(Session::new( stream, ProgramSessionPlan { owner_generation: Rc::downgrade(&self.owner_generation), bindings, workspace, - modeled_occurrences, }, )) } @@ -1235,10 +1222,6 @@ where Self::Violation(evidence) => Evaluation::violation_binding(evidence), } } - - fn modeled_lcs_occurrence(&self) -> ModeledLcsOccurrenceV1 { - self.binding().modeled_lcs() - } } /// One canonical `physical case × constraint` report cell. @@ -1274,8 +1257,8 @@ where self.target } - pub fn modeled_lcs_occurrence(&self) -> ModeledLcsOccurrenceV1 { - self.result.modeled_lcs_occurrence() + pub fn appearance_context(&self) -> AppearanceContextId { + self.result.binding().context() } pub const fn is_hard(&self) -> bool { @@ -1294,7 +1277,7 @@ pub struct ProgramReportV1 where Evaluation: ProgramConstraintEvaluatorSetV1, { - content_identity: ProgramContentIdentityV1, + content_identity: ProgramContentIdentityV2, observation: RevisionBoundObservationV1, cells: Vec>, } @@ -1305,7 +1288,7 @@ where { /// Адрес содержимого Program, по которому построен report; это не /// идентификатор поколения и не runtime-authority. - pub const fn content_identity(&self) -> ProgramContentIdentityV1 { + pub const fn content_identity(&self) -> ProgramContentIdentityV2 { self.content_identity } @@ -1431,20 +1414,6 @@ pub enum ProgramSessionEvaluationError { context: AppearanceContextId, source: EvaluationError, }, - ProgramTargetBinding { - case_index: usize, - constraint: ConstraintId, - occurrence: OccurrenceId, - context: AppearanceContextId, - physical: Srgb8, - modeled: Srgb8, - }, - ModeledOccurrence { - case_index: usize, - occurrence: OccurrenceId, - context: AppearanceContextId, - source: ModeledLcsOccurrenceFormationErrorV1, - }, OutputVariesAcrossCases { output: OutputSlotId, first_case: usize, @@ -1671,7 +1640,6 @@ where owner_generation: Weak>, bindings: AdmittedAppearanceBindings, workspace: AppearanceWorkspace, - modeled_occurrences: Vec>, } impl session_private::PlanSealed for ProgramSessionPlan @@ -1930,7 +1898,6 @@ where .graph .evaluate_admitted_into(&plan.bindings, &mut plan.workspace) .map_err(map_program_execution_binding_error)?; - plan.modeled_occurrences.fill(None); for constraint in epoch.constraints.iter() { let source = evaluation @@ -1939,71 +1906,26 @@ where if source.visible() != source.certificate().output_rgb() { return Err(ProgramSessionEvaluationError::InternalInvariant); } - let modeled_lcs_occurrence = match plan - .modeled_occurrences - .get(constraint.modeled_occurrence_index) - .copied() - .flatten() - { - Some(modeled) => modeled, - None => { - let binding = epoch - .occurrence_contexts - .get(constraint.modeled_occurrence_index) - .ok_or(ProgramSessionEvaluationError::InternalInvariant)?; - if binding.occurrence != constraint.target_id - || binding.target != constraint.target - { - return Err(ProgramSessionEvaluationError::InternalInvariant); - } - let modeled = ModeledLcsOccurrenceV1::from_signal_in_context( - ColorSignal::from_srgb8(Srgb8::new(source.visible())), - binding.context, - ) - .map_err(|source| { - ProgramSessionEvaluationError::ModeledOccurrence { + let binding = epoch + .occurrence_contexts + .get(constraint.occurrence_context_index) + .ok_or(ProgramSessionEvaluationError::InternalInvariant)?; + if binding.occurrence != constraint.target_id || binding.target != constraint.target { + return Err(ProgramSessionEvaluationError::InternalInvariant); + } + let point = ProgramPointOccurrenceV1::from_resolved(source, binding.context); + let decision = Evaluation::assess(&epoch.evaluator, point, constraint.invocation) + .map_err(|error| match error { + ProgramPointAssessmentErrorV1::Evaluator(source) => { + ProgramSessionEvaluationError::Evaluator { case_index, + constraint: constraint.id, occurrence: constraint.target_id, context: binding.context, source, } - })?; - let slot = plan - .modeled_occurrences - .get_mut(constraint.modeled_occurrence_index) - .ok_or(ProgramSessionEvaluationError::InternalInvariant)?; - *slot = Some(modeled); - modeled - } - }; - let decision = Evaluation::assess( - &epoch.evaluator, - source, - modeled_lcs_occurrence, - constraint.invocation, - ) - .map_err(|error| match error { - ProgramPointAssessmentErrorV1::Binding(source) => { - debug_assert_ne!(source.physical(), source.modeled()); - ProgramSessionEvaluationError::ProgramTargetBinding { - case_index, - constraint: constraint.id, - occurrence: constraint.target_id, - context: modeled_lcs_occurrence.occurrence().context(), - physical: source.physical(), - modeled: source.modeled(), } - } - ProgramPointAssessmentErrorV1::Evaluator(source) => { - ProgramSessionEvaluationError::Evaluator { - case_index, - constraint: constraint.id, - occurrence: constraint.target_id, - context: modeled_lcs_occurrence.occurrence().context(), - source, - } - } - })?; + })?; let result = match decision { HardDecision::Pass(evidence) => ProgramConstraintResultV1::Pass(evidence), HardDecision::Violation(evidence) => { @@ -2014,7 +1936,7 @@ where } }; debug_assert_eq!(result.binding().physical(), source.visible_point_binding()); - debug_assert_eq!(result.binding().modeled_lcs(), modeled_lcs_occurrence); + debug_assert_eq!(result.binding().context(), binding.context); if let Some(cells) = cells.as_deref_mut() { cells.push(ProgramConstraintCellV1 { candidate_state_index, @@ -2180,7 +2102,7 @@ where let occurrence_contexts = compact_constraint_contexts(&all_occurrence_contexts, &mut constraints)?; let outputs = compile_outputs(&graph, &mut program.outputs)?; - let content_identity = identity::compile_program_content_identity_v1(&program)?; + let content_identity = identity::compile_program_content_identity_v2(&program)?; Ok(ProgramEpochV1 { content_identity, evaluator: program.evaluator, @@ -2889,17 +2811,17 @@ where let target = graph .bind_occurrence(constraint.target) .ok_or(ProgramCompileError::InternalInvariant)?; - let modeled_occurrence_index = occurrence_contexts + let occurrence_context_index = occurrence_contexts .binary_search_by_key(&constraint.target, |binding| binding.occurrence) .map_err(|_| ProgramCompileError::InternalInvariant)?; - if occurrence_contexts[modeled_occurrence_index].target != target { + if occurrence_contexts[occurrence_context_index].target != target { return Err(ProgramCompileError::InternalInvariant); } compiled.push(CompiledPointConstraint { id: constraint.id, target_id: constraint.target, target, - modeled_occurrence_index, + occurrence_context_index, mode: constraint.mode, invocation: constraint.invocation, }); @@ -2937,7 +2859,7 @@ fn compact_constraint_contexts( if compact[index].target != constraint.target { return Err(ProgramCompileError::InternalInvariant); } - constraint.modeled_occurrence_index = index; + constraint.occurrence_context_index = index; } Ok(compact.into_boxed_slice()) } diff --git a/scripts/verify_point_support_surplus.py b/scripts/verify_point_support_surplus.py index ff727c5b..b356e55c 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 = ( - "23c9de8a10733a3eae24cbc6c7361279048fd99f316c6251a166a9c93d8da0b6" + "5bed543bf75209b2660d7e4f2f0f0cb107f9ee4c841f7dad8bd05175f8ad897d" ) EXPECTED_Q55_PROOF_SHA256 = ( "ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"