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 03152a12..b122af0f 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":"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"} +{"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":"dad1bfaa7fd06945c66102ea388034f52f5edf4a99d02d6312b79ab2053d819c","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":"016cbe658a2bf0b289d1647cad0982b54fa4a9aec7b56934e2439adfb9eb8454","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":"91d56e7b86a6dfc11691b67da97852d7b26678a6cad8932abed3b622783e3dd9"},{"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":"156a7295c56113b1630e082fe561620726c97cec336b85f99169f8840155b899"} diff --git a/crates/labcolors-core/src/appearance.rs b/crates/labcolors-core/src/appearance.rs index 321a3709..7775a42e 100644 --- a/crates/labcolors-core/src/appearance.rs +++ b/crates/labcolors-core/src/appearance.rs @@ -26,6 +26,7 @@ )] use std::collections::BTreeSet; +use std::sync::Arc; use crate::Srgb8; pub(crate) use crate::composition::CompositionProfileV1; @@ -599,6 +600,7 @@ impl AppearanceGraphSpec { .collect(); Ok(CompiledAppearanceGraph { + instance: Arc::new(()), color_inputs, surface_input_ports, opacity_inputs, @@ -796,10 +798,74 @@ pub(crate) struct CompiledOccurrenceSlotV1 { id: OccurrenceId, } +/// Versioned counterfactual declared for one modeled point target. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum PointOccurrenceAbsenceReleaseV1 { + /// Replace the target Occurrence output with its own resolved backdrop, + /// then replay every unchanged downstream occurrence and quantization. + BypassOwnBackdropV1, +} + +/// Compiler-minted terminal root authority. A naked OccurrenceId cannot +/// authorize a final modeled-result claim. +#[derive(Debug, Clone)] +pub(crate) struct CompiledPointPresentationRootV1 { + graph_instance: Arc<()>, + terminal: CompiledOccurrenceSlotV1, +} + +impl CompiledPointPresentationRootV1 { + pub(crate) const fn terminal(&self) -> OccurrenceId { + self.terminal.id + } +} + +/// Unique target-to-root ancestry proven on the compiler cold edge. +#[derive(Debug, Clone)] +pub(crate) struct CompiledPointPresentationPathV1 { + graph_instance: Arc<()>, + target: OccurrenceId, + root: OccurrenceId, + occurrences: Box<[CompiledOccurrenceSlotV1]>, +} + +impl CompiledPointPresentationPathV1 { + pub(crate) const fn target(&self) -> OccurrenceId { + self.target + } + + pub(crate) const fn root(&self) -> OccurrenceId { + self.root + } + + pub(crate) const fn len(&self) -> usize { + self.occurrences.len() + } + + pub(crate) fn belongs_to(&self, graph: &CompiledAppearanceGraph) -> bool { + Arc::ptr_eq(&self.graph_instance, &graph.instance) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum PointPresentationPathErrorV1 { + MissingRoot, + MissingTarget, + RootConsumedDownstream, + TargetOutsideRootAncestry, + IncompatibleRoot, + ResourceExhausted, + InternalInvariant, +} + /// Канонический compiled IR с индексными ссылками: после проверки bindings /// исполнение самих Paint/Surface/Occurrence узлов линейно по их числу. -#[derive(Debug, Clone, PartialEq, Eq)] +/// +/// Graph намеренно не реализует `Clone` и value equality: его instance-token +/// является compiler-owned authority, а не частью сравнимого содержимого. +#[derive(Debug)] pub(crate) struct CompiledAppearanceGraph { + instance: Arc<()>, color_inputs: Vec, surface_input_ports: Vec, opacity_inputs: Vec, @@ -1634,6 +1700,89 @@ impl CompiledAppearanceGraph { Some(CompiledOccurrenceSlotV1 { index, id }) } + /// Mint authority only for an occurrence that is not consumed by another + /// occurrence in this point graph. Intermediate layers therefore cannot + /// be mistaken for final modeled results. + pub(crate) fn compile_point_presentation_root( + &self, + terminal: OccurrenceId, + ) -> Result { + let terminal = self + .bind_occurrence(terminal) + .ok_or(PointPresentationPathErrorV1::MissingRoot)?; + let consumed = self.occurrences.iter().any(|occurrence| { + matches!( + self.surfaces.get(occurrence.against), + Some(CompiledSurfaceSpec::FromOccurrence { + occurrence: source_occurrence, + .. + }) if *source_occurrence == terminal.index + ) + }); + if consumed { + return Err(PointPresentationPathErrorV1::RootConsumedDownstream); + } + Ok(CompiledPointPresentationRootV1 { + graph_instance: Arc::clone(&self.instance), + terminal, + }) + } + + /// Prove one target's membership in the root's unique backdrop ancestry. + pub(crate) fn compile_point_presentation_path( + &self, + target: OccurrenceId, + root: &CompiledPointPresentationRootV1, + ) -> Result { + if !Arc::ptr_eq(&self.instance, &root.graph_instance) { + return Err(PointPresentationPathErrorV1::IncompatibleRoot); + } + let target_slot = self + .bind_occurrence(target) + .ok_or(PointPresentationPathErrorV1::MissingTarget)?; + let root_slot = root.terminal; + let mut reverse = Vec::new(); + reverse + .try_reserve_exact(self.occurrences.len()) + .map_err(|_| PointPresentationPathErrorV1::ResourceExhausted)?; + + // `compile()` rejects `RenderCycle` through canonical functional + // topology. Every hop therefore moves to a unique ancestor and the + // walk terminates within `occurrences.len()` nodes; that same finite + // upper bound makes the exact reservation safe. + let mut current = root_slot.index; + loop { + let spec = self + .occurrences + .get(current) + .ok_or(PointPresentationPathErrorV1::InternalInvariant)?; + reverse.push(CompiledOccurrenceSlotV1 { + index: current, + id: spec.id, + }); + if current == target_slot.index { + break; + } + let surface = self + .surfaces + .get(spec.against) + .ok_or(PointPresentationPathErrorV1::InternalInvariant)?; + current = match surface { + CompiledSurfaceSpec::FromOccurrence { occurrence, .. } => *occurrence, + CompiledSurfaceSpec::Input { .. } => { + return Err(PointPresentationPathErrorV1::TargetOutsideRootAncestry); + } + }; + } + reverse.reverse(); + Ok(CompiledPointPresentationPathV1 { + graph_instance: Arc::clone(&self.instance), + target, + root: root_slot.id, + occurrences: reverse.into_boxed_slice(), + }) + } + /// Canonical client-owned occurrence identities emitted by this program. pub(crate) fn occurrence_ids(&self) -> impl ExactSizeIterator + '_ { self.occurrences.iter().map(|occurrence| occurrence.id) diff --git a/crates/labcolors-core/src/appearance_graph_tests.rs b/crates/labcolors-core/src/appearance_graph_tests.rs index 109e8f4c..f8fba1ac 100644 --- a/crates/labcolors-core/src/appearance_graph_tests.rs +++ b/crates/labcolors-core/src/appearance_graph_tests.rs @@ -13,7 +13,8 @@ use crate::Srgb8; use crate::appearance::{ AdmittedAppearanceBindings, AppearanceBindings, AppearanceGraphSpec, BindingError, ColorInputId, CompileError, CompositionProfileV1, EncodedPointPaintV1, OccurrenceId, - OccurrenceSpec, OpacityInputId, PaintId, PaintSpec, SurfaceId, SurfaceInputPortId, SurfaceSpec, + OccurrenceSpec, OpacityInputId, PaintId, PaintSpec, PointPresentationPathErrorV1, SurfaceId, + SurfaceInputPortId, SurfaceSpec, }; use crate::constraints::Evaluator; @@ -83,6 +84,111 @@ fn bindings(source: [u8; 3], opacity: f64, context: [u8; 3]) -> AppearanceBindin ) } +fn terminal_chain() -> AppearanceGraphSpec { + AppearanceGraphSpec::new( + vec![SOURCE], + vec![CONTEXT], + vec![], + vec![PaintSpec::Solid { + id: SOLID_PAINT, + color: SOURCE, + }], + vec![ + SurfaceSpec::Input { + id: CONTEXT_SURFACE, + port: CONTEXT, + }, + SurfaceSpec::FromOccurrence { + id: DERIVED_SURFACE, + occurrence: FILL_OCCURRENCE, + }, + ], + vec![ + OccurrenceSpec { + id: FILL_OCCURRENCE, + subject: SOLID_PAINT, + against: CONTEXT_SURFACE, + profile: CompositionProfileV1::EncodedSrgb8SourceOverV1, + }, + OccurrenceSpec { + id: OTHER_OCCURRENCE, + subject: SOLID_PAINT, + against: DERIVED_SURFACE, + profile: CompositionProfileV1::EncodedSrgb8SourceOverV1, + }, + ], + ) +} + +#[test] +fn presentation_root_authority_is_minted_only_for_a_terminal_occurrence() { + let graph = terminal_chain().compile().unwrap(); + assert!(matches!( + graph.compile_point_presentation_root(FILL_OCCURRENCE), + Err(PointPresentationPathErrorV1::RootConsumedDownstream) + )); + let root = graph + .compile_point_presentation_root(OTHER_OCCURRENCE) + .unwrap(); + assert_eq!(root.terminal(), OTHER_OCCURRENCE); + let path = graph + .compile_point_presentation_path(FILL_OCCURRENCE, &root) + .unwrap(); + assert_eq!(path.target(), FILL_OCCURRENCE); + assert_eq!(path.root(), OTHER_OCCURRENCE); + assert_eq!(path.len(), 2); + assert!(path.belongs_to(&graph)); +} + +#[test] +fn presentation_root_authority_is_bound_to_its_compiled_graph() { + let first = terminal_chain().compile().unwrap(); + let second = terminal_chain().compile().unwrap(); + let foreign_root = first + .compile_point_presentation_root(OTHER_OCCURRENCE) + .unwrap(); + assert!(matches!( + second.compile_point_presentation_path(FILL_OCCURRENCE, &foreign_root), + Err(PointPresentationPathErrorV1::IncompatibleRoot) + )); +} + +#[test] +fn presentation_path_reports_missing_root_and_target_at_the_graph_boundary() { + let graph = terminal_chain().compile().unwrap(); + let missing = OccurrenceId::new(u32::MAX); + assert!(matches!( + graph.compile_point_presentation_root(missing), + Err(PointPresentationPathErrorV1::MissingRoot) + )); + + let root = graph + .compile_point_presentation_root(OTHER_OCCURRENCE) + .unwrap(); + assert!(matches!( + graph.compile_point_presentation_path(missing, &root), + Err(PointPresentationPathErrorV1::MissingTarget) + )); +} + +#[test] +fn presentation_path_rejects_a_target_outside_the_root_ancestry() { + let graph = slot_component(false).compile().unwrap(); + let root = graph + .compile_point_presentation_root(OTHER_OCCURRENCE) + .unwrap(); + assert!(matches!( + graph.compile_point_presentation_path(FILL_OCCURRENCE, &root), + Err(PointPresentationPathErrorV1::TargetOutsideRootAncestry) + )); +} + +#[test] +fn compiled_appearance_graph_remains_send_and_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} + fn slot_component(reverse_declarations: bool) -> AppearanceGraphSpec { let mut paints = vec![ PaintSpec::Solid { @@ -1185,8 +1291,9 @@ proptest! { fn compile_rejects_duplicate_declarations_with_typed_errors() { assert_eq!( AppearanceGraphSpec::new(vec![SOURCE, SOURCE], vec![], vec![], vec![], vec![], vec![]) - .compile(), - Err(CompileError::DuplicateColorInput { input: SOURCE }) + .compile() + .unwrap_err(), + CompileError::DuplicateColorInput { input: SOURCE } ); assert_eq!( AppearanceGraphSpec::new( @@ -1197,8 +1304,9 @@ fn compile_rejects_duplicate_declarations_with_typed_errors() { vec![], vec![] ) - .compile(), - Err(CompileError::DuplicateOpacityInput { input: OPACITY }) + .compile() + .unwrap_err(), + CompileError::DuplicateOpacityInput { input: OPACITY } ); assert_eq!( AppearanceGraphSpec::new( @@ -1209,8 +1317,9 @@ fn compile_rejects_duplicate_declarations_with_typed_errors() { vec![], vec![] ) - .compile(), - Err(CompileError::DuplicateSurfaceInputPort { input: CONTEXT }) + .compile() + .unwrap_err(), + CompileError::DuplicateSurfaceInputPort { input: CONTEXT } ); assert_eq!( AppearanceGraphSpec::new( @@ -1230,8 +1339,9 @@ fn compile_rejects_duplicate_declarations_with_typed_errors() { vec![], vec![] ) - .compile(), - Err(CompileError::DuplicatePaint { paint: SOLID_PAINT }) + .compile() + .unwrap_err(), + CompileError::DuplicatePaint { paint: SOLID_PAINT } ); assert_eq!( AppearanceGraphSpec::new( @@ -1251,10 +1361,11 @@ fn compile_rejects_duplicate_declarations_with_typed_errors() { ], vec![] ) - .compile(), - Err(CompileError::DuplicateSurface { + .compile() + .unwrap_err(), + CompileError::DuplicateSurface { surface: CONTEXT_SURFACE - }) + } ); assert_eq!( AppearanceGraphSpec::new( @@ -1278,10 +1389,11 @@ fn compile_rejects_duplicate_declarations_with_typed_errors() { }, ] ) - .compile(), - Err(CompileError::DuplicateOccurrence { + .compile() + .unwrap_err(), + CompileError::DuplicateOccurrence { occurrence: FILL_OCCURRENCE - }) + } ); } @@ -1300,11 +1412,11 @@ fn compile_rejects_every_dangling_canonical_edge() { ) .compile(); assert_eq!( - missing_color, - Err(CompileError::MissingPaintColorInput { + missing_color.unwrap_err(), + CompileError::MissingPaintColorInput { paint: SOLID_PAINT, input: SOURCE, - }) + } ); let missing_paint = AppearanceGraphSpec::new( @@ -1321,11 +1433,11 @@ fn compile_rejects_every_dangling_canonical_edge() { ) .compile(); assert_eq!( - missing_paint, - Err(CompileError::MissingPaintSource { + missing_paint.unwrap_err(), + CompileError::MissingPaintSource { paint: FILL_PAINT, source: SOLID_PAINT, - }) + } ); let missing_opacity = AppearanceGraphSpec::new( @@ -1348,11 +1460,11 @@ fn compile_rejects_every_dangling_canonical_edge() { ) .compile(); assert_eq!( - missing_opacity, - Err(CompileError::MissingPaintOpacityInput { + missing_opacity.unwrap_err(), + CompileError::MissingPaintOpacityInput { paint: FILL_PAINT, input: OPACITY, - }) + } ); let missing_surface_input = AppearanceGraphSpec::new( @@ -1368,11 +1480,11 @@ fn compile_rejects_every_dangling_canonical_edge() { ) .compile(); assert_eq!( - missing_surface_input, - Err(CompileError::MissingSurfaceInputPort { + missing_surface_input.unwrap_err(), + CompileError::MissingSurfaceInputPort { surface: CONTEXT_SURFACE, input: CONTEXT, - }) + } ); let missing_surface_occurrence = AppearanceGraphSpec::new( @@ -1388,11 +1500,11 @@ fn compile_rejects_every_dangling_canonical_edge() { ) .compile(); assert_eq!( - missing_surface_occurrence, - Err(CompileError::MissingSurfaceOccurrence { + missing_surface_occurrence.unwrap_err(), + CompileError::MissingSurfaceOccurrence { surface: DERIVED_SURFACE, occurrence: FILL_OCCURRENCE, - }) + } ); let missing_occurrence_paint = AppearanceGraphSpec::new( @@ -1413,11 +1525,11 @@ fn compile_rejects_every_dangling_canonical_edge() { ) .compile(); assert_eq!( - missing_occurrence_paint, - Err(CompileError::MissingOccurrencePaint { + missing_occurrence_paint.unwrap_err(), + CompileError::MissingOccurrencePaint { occurrence: FILL_OCCURRENCE, paint: FILL_PAINT, - }) + } ); let missing_occurrence_backdrop = AppearanceGraphSpec::new( @@ -1438,11 +1550,11 @@ fn compile_rejects_every_dangling_canonical_edge() { ) .compile(); assert_eq!( - missing_occurrence_backdrop, - Err(CompileError::MissingOccurrenceBackdrop { + missing_occurrence_backdrop.unwrap_err(), + CompileError::MissingOccurrenceBackdrop { occurrence: FILL_OCCURRENCE, surface: CONTEXT_SURFACE, - }) + } ); } @@ -1477,10 +1589,10 @@ fn cycle_errors_contain_only_actual_cycle_members() { ) .compile(); assert_eq!( - paint_cycle, - Err(CompileError::PaintCycle { + paint_cycle.unwrap_err(), + CompileError::PaintCycle { paints: vec![cycle_a, cycle_b], - }) + } ); let cycle_surface = SurfaceId::new(10); @@ -1522,11 +1634,11 @@ fn cycle_errors_contain_only_actual_cycle_members() { ) .compile(); assert_eq!( - render_cycle, - Err(CompileError::RenderCycle { + render_cycle.unwrap_err(), + CompileError::RenderCycle { surfaces: vec![cycle_surface], occurrences: vec![cycle_occurrence], - }) + } ); } diff --git a/crates/labcolors-core/src/generic_boundary_tests.rs b/crates/labcolors-core/src/generic_boundary_tests.rs index 7a367c5e..aef181ba 100644 --- a/crates/labcolors-core/src/generic_boundary_tests.rs +++ b/crates/labcolors-core/src/generic_boundary_tests.rs @@ -1027,22 +1027,28 @@ fn program_identity_binds_lcs_releases_only_through_lcs_constraint_content() { "DOMAIN_V1", "PROGRAM_SCHEMA_V1", "compile_program_content_identity_v1", + "ProgramContentIdentityV2", + "ContentIdentityV2", + "DOMAIN_V2", + "PROGRAM_SCHEMA_V2", + "compile_program_content_identity_v2", ] { 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}", + "the V3 content-address cut must not retain legacy identity symbol `{retired}` in {path}", ); } } assert!(!PROGRAM_IDENTITY_SOURCE.contains("labcolors.program-content-identity.v1")); + assert!(!PROGRAM_IDENTITY_SOURCE.contains("labcolors.program-content-identity.v2")); for required in [ - "const DOMAIN_V2: &[u8] = b\"labcolors.program-content-identity.v2\\0\";", - "pub(super) const PROGRAM_SCHEMA_V2: u8 = 2;", + "const DOMAIN_V3: &[u8] = b\"labcolors.program-content-identity.v3\\0\";", + "pub(super) const PROGRAM_SCHEMA_V3: u8 = 3;", ] { assert!( PROGRAM_IDENTITY_SOURCE.contains(required), - "the V2 content-address type must bind its exact domain and schema tag; missing `{required}`", + "the V3 content-address type must bind its exact domain and schema tag; missing `{required}`", ); } diff --git a/crates/labcolors-core/src/program.rs b/crates/labcolors-core/src/program.rs index 04dbd4e9..eabeddc0 100644 --- a/crates/labcolors-core/src/program.rs +++ b/crates/labcolors-core/src/program.rs @@ -35,7 +35,7 @@ //! [`CertificateV1::Verified`] хранит выбранное состояние, все клетки //! доказательства и сертифицированные выходы. [`CertificateV1::Conflict`] //! хранит исчерпывающий конфликт по всем рассмотренным состояниям. -//! [`ContentIdentityV2`] идентифицирует каноническое содержание, но не даёт +//! [`ContentIdentityV3`] идентифицирует каноническое содержание, но не даёт //! полномочий живого [`OwnerV1`]. #![forbid(unreachable_pub)] @@ -68,8 +68,9 @@ use crate::program_session::{ CoreProgramConstraintInvocationV1, CoreProgramDraftErrorV1, CoreProgramDraftV1, CoreProgramEvaluatorErrorV1, CoreProgramEvaluatorsV1, CoreProgramPassEvidenceV1, CoreProgramViolationEvidenceV1, DeclaredJointSelectionV1, JointCandidateStateV1, Occurrence, - OpacityInput, OutputBinding, OutputSlotId, Paint, ProgramCompileError, ProgramConflictV1, - ProgramConstraintCellV1, ProgramConstraintResultV1, ProgramContentIdentityV2, ProgramOutputV1, + OpacityInput, OutputBinding, OutputSlotId, Paint, PointPresentationRootV1, + PointPresentationTargetV1, PresentationRootId, ProgramCompileError, ProgramConflictV1, + ProgramConstraintCellV1, ProgramConstraintResultV1, ProgramContentIdentityV3, ProgramOutputV1, ProgramSessionEvaluationError, ProgramSessionInstantiateError, ProgramSessionPlan, ProgramVerifiedV1, Source, SourceId, Surface, Target, TargetCandidateChoiceV1, TargetCandidateId, TargetCandidateV1 as CoreTargetCandidateV1, TargetId, @@ -183,6 +184,11 @@ authored_id!( OccurrenceIdV1, OccurrenceId ); +authored_id!( + "Идентификатор моделируемого point presentation root.", + PresentationRootIdV1, + PresentationRootId +); authored_id!( "Идентификатор проверяемого ограничения.", ConstraintIdV1, @@ -428,6 +434,22 @@ pub(crate) enum CompileErrorKindV1 { DuplicateSurface, /// Повторно объявлен Occurrence. DuplicateOccurrence, + /// Повторно объявлен presentation root. + DuplicatePresentationRoot, + /// Presentation root ссылается на отсутствующий терминальный Occurrence. + MissingPresentationRootOccurrence, + /// Терминальный Occurrence root всё ещё потребляется downstream. + PresentationRootConsumedDownstream, + /// Presentation root не имеет ни одной проверяемой цели. + UnusedPresentationRoot, + /// Повторно объявлена одна пара presentation root/target. + DuplicatePointPresentationTarget, + /// Presentation target ссылается на отсутствующий root. + MissingPointPresentationRoot, + /// Presentation target ссылается на отсутствующий Occurrence. + MissingPointPresentationOccurrence, + /// Target Occurrence не принадлежит ancestry объявленного root. + PointPresentationOccurrenceOutsideRootAncestry, /// Повторно объявлено ограничение. DuplicateConstraint, /// Повторно объявлен выходной слот. @@ -513,6 +535,8 @@ pub(crate) enum CompileErrorHandleV1 { Surface(SurfaceIdV1), /// Occurrence. Occurrence(OccurrenceIdV1), + /// Моделируемый presentation root. + PresentationRoot(PresentationRootIdV1), /// Ограничение. Constraint(ConstraintIdV1), /// Выходной слот. @@ -531,6 +555,7 @@ impl CompileErrorHandleV1 { Self::SurfaceInputPort(value) => value.value(), Self::Surface(value) => value.value(), Self::Occurrence(value) => value.value(), + Self::PresentationRoot(value) => value.value(), Self::Constraint(value) => value.value(), Self::OutputSlot(value) => value.value(), } @@ -733,6 +758,58 @@ pub(crate) enum CompileErrorV1 { /// Отсутствующая Surface. surface: SurfaceIdV1, }, + /// Повторно объявлен presentation root. + DuplicatePresentationRoot { + /// Повторный root ID. + root: PresentationRootIdV1, + }, + /// Presentation root ссылается на отсутствующий терминальный Occurrence. + MissingPresentationRootOccurrence { + /// Ошибочный root ID. + root: PresentationRootIdV1, + /// Отсутствующий Occurrence, объявленный терминалом root. + occurrence: OccurrenceIdV1, + }, + /// Терминальный Occurrence root всё ещё потребляется downstream. + PresentationRootConsumedDownstream { + /// Ошибочный root ID. + root: PresentationRootIdV1, + /// Occurrence, объявленный терминалом root. + occurrence: OccurrenceIdV1, + }, + /// Root не имеет ни одной проверяемой цели. + UnusedPresentationRoot { + /// Неиспользуемый root ID. + root: PresentationRootIdV1, + }, + /// Повторно объявлена одна пара root/target. + DuplicatePointPresentationTarget { + /// Root повторной пары. + root: PresentationRootIdV1, + /// Target Occurrence повторной пары. + occurrence: OccurrenceIdV1, + }, + /// Target ссылается на отсутствующий root. + MissingPointPresentationRoot { + /// Отсутствующий root ID. + root: PresentationRootIdV1, + }, + /// Target ссылается на отсутствующий Occurrence. + MissingPointPresentationOccurrence { + /// Root ошибочного target. + root: PresentationRootIdV1, + /// Отсутствующий target Occurrence. + occurrence: OccurrenceIdV1, + }, + /// Target не принадлежит ancestry root. + PointPresentationOccurrenceOutsideRootAncestry { + /// Root, относительно которого проверялся target. + root: PresentationRootIdV1, + /// Финальный Occurrence, объявленный терминалом root. + terminal: OccurrenceIdV1, + /// Target Occurrence вне ancestry терминала. + occurrence: OccurrenceIdV1, + }, /// Обнаружен цикл зависимостей Paint. PaintCycle(PaintCycleV1), /// Обнаружен цикл Surface/Occurrence. @@ -869,6 +946,22 @@ impl CompileErrorV1 { Self::DuplicatePaint { .. } => Kind::DuplicatePaint, Self::DuplicateSurface { .. } => Kind::DuplicateSurface, Self::DuplicateOccurrence { .. } => Kind::DuplicateOccurrence, + Self::DuplicatePresentationRoot { .. } => Kind::DuplicatePresentationRoot, + Self::MissingPresentationRootOccurrence { .. } => { + Kind::MissingPresentationRootOccurrence + } + Self::PresentationRootConsumedDownstream { .. } => { + Kind::PresentationRootConsumedDownstream + } + Self::UnusedPresentationRoot { .. } => Kind::UnusedPresentationRoot, + Self::DuplicatePointPresentationTarget { .. } => Kind::DuplicatePointPresentationTarget, + Self::MissingPointPresentationRoot { .. } => Kind::MissingPointPresentationRoot, + Self::MissingPointPresentationOccurrence { .. } => { + Kind::MissingPointPresentationOccurrence + } + Self::PointPresentationOccurrenceOutsideRootAncestry { .. } => { + Kind::PointPresentationOccurrenceOutsideRootAncestry + } Self::MissingPaintTarget { .. } => Kind::MissingPaintTarget, Self::MissingPaintSource { .. } => Kind::MissingPaintSource, Self::MissingPaintOpacityInput { .. } => Kind::MissingPaintOpacityInput, @@ -941,6 +1034,16 @@ impl CompileErrorV1 { | Self::MissingOccurrenceBackdrop { occurrence, .. } => { Some(Handle::Occurrence(*occurrence)) } + Self::DuplicatePresentationRoot { root } + | Self::MissingPresentationRootOccurrence { root, .. } + | Self::PresentationRootConsumedDownstream { root, .. } + | Self::UnusedPresentationRoot { root } + | Self::DuplicatePointPresentationTarget { root, .. } + | Self::MissingPointPresentationRoot { root } + | Self::MissingPointPresentationOccurrence { root, .. } + | Self::PointPresentationOccurrenceOutsideRootAncestry { root, .. } => { + Some(Handle::PresentationRoot(*root)) + } Self::UnassessedOutput { output, .. } | Self::DuplicateOutputSlot { output } | Self::MissingOutputPaint { output, .. } => Some(Handle::OutputSlot(*output)), @@ -977,7 +1080,12 @@ impl CompileErrorV1 { Self::MissingPaintOpacityInput { input, .. } => Some(Handle::OpacityInput(*input)), Self::MissingSurfaceInputPort { input, .. } => Some(Handle::SurfaceInputPort(*input)), Self::MissingSurfaceOccurrence { occurrence, .. } - | Self::MissingConstraintOccurrence { occurrence, .. } => { + | Self::MissingConstraintOccurrence { occurrence, .. } + | Self::MissingPresentationRootOccurrence { occurrence, .. } + | Self::PresentationRootConsumedDownstream { occurrence, .. } + | Self::DuplicatePointPresentationTarget { occurrence, .. } + | Self::MissingPointPresentationOccurrence { occurrence, .. } + | Self::PointPresentationOccurrenceOutsideRootAncestry { occurrence, .. } => { Some(Handle::Occurrence(*occurrence)) } Self::MissingOccurrencePaint { paint, .. } @@ -1000,6 +1108,9 @@ impl CompileErrorV1 { | Self::DuplicatePaint { .. } | Self::DuplicateSurface { .. } | Self::DuplicateOccurrence { .. } + | Self::DuplicatePresentationRoot { .. } + | Self::UnusedPresentationRoot { .. } + | Self::MissingPointPresentationRoot { .. } | Self::PaintCycle(_) | Self::RenderCycle(_) | Self::OpacityOutOfDomain { .. } @@ -1175,6 +1286,35 @@ impl DraftV1 { self } + /// Declares one terminal root of the modeled point graph. + pub(crate) fn push_point_presentation_root( + &mut self, + id: PresentationRootIdV1, + terminal: OccurrenceIdV1, + ) -> &mut Self { + self.inner + .push_point_presentation_root(PointPresentationRootV1::new( + id.into_core(), + terminal.into_core(), + )); + self + } + + /// Declares one occurrence whose final point contribution belongs to the + /// named root under the code-owned versioned absence intervention. + pub(crate) fn push_point_presentation_target( + &mut self, + root: PresentationRootIdV1, + occurrence: OccurrenceIdV1, + ) -> &mut Self { + self.inner + .push_point_presentation_target(PointPresentationTargetV1::new( + root.into_core(), + occurrence.into_core(), + )); + self + } + /// Добавляет обязательное точное сравнение видимого sRGB8 результата. pub(crate) fn push_exact_hard( &mut self, @@ -1314,8 +1454,8 @@ impl OwnerV1 { /// /// Identity доступна до первого update, но не заменяет полномочия этой /// конкретной owner-эпохи. - pub(crate) fn content_identity(&self) -> ContentIdentityV2 { - ContentIdentityV2::from_core(self.compiled.content_identity()) + pub(crate) fn content_identity(&self) -> ContentIdentityV3 { + ContentIdentityV3::from_core(self.compiled.content_identity()) } /// Вычисляет верхние границы клеток для prospective Observed-update. @@ -1343,6 +1483,11 @@ impl OwnerV1 { self.compiled.surface_input_ports().len() } + /// Number of compiler-admitted target/root presentation relations. + pub(crate) fn point_presentation_count(&self) -> usize { + self.compiled.point_presentation_count() + } + /// Канонический порядок входных портов для однократного binding на хосте. pub(crate) fn surface_input_ports( &self, @@ -1363,7 +1508,7 @@ impl OwnerV1 { /// Проецирует операции только для Session этой точной owner-эпохи. /// - /// Равенство [`ContentIdentityV2`] не даёт полномочий. + /// Равенство [`ContentIdentityV3`] не даёт полномочий. pub(crate) fn project<'owner, 'session>( &'owner self, session: &'session SessionV1, @@ -1691,10 +1836,10 @@ impl<'owner, 'session> ProjectionV1<'owner, 'session> { /// Identity не идентифицирует owner-эпоху и не даёт runtime-полномочий. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct ContentIdentityV2([u8; 32]); +pub(crate) struct ContentIdentityV3([u8; 32]); -impl ContentIdentityV2 { - const fn from_core(value: ProgramContentIdentityV2) -> Self { +impl ContentIdentityV3 { + const fn from_core(value: ProgramContentIdentityV3) -> Self { Self(*value.as_bytes()) } @@ -1712,8 +1857,8 @@ pub(crate) struct VerifiedCertificateV1<'a> { impl<'a> VerifiedCertificateV1<'a> { /// Возвращает identity скомпилированного содержания. - pub(crate) const fn content_identity(self) -> ContentIdentityV2 { - ContentIdentityV2::from_core(self.inner.report().content_identity()) + pub(crate) const fn content_identity(self) -> ContentIdentityV3 { + ContentIdentityV3::from_core(self.inner.report().content_identity()) } /// Возвращает точное наблюдение, на котором выдан сертификат. @@ -1758,8 +1903,8 @@ pub(crate) struct ConflictCertificateV1<'a> { impl<'a> ConflictCertificateV1<'a> { /// Возвращает identity скомпилированного содержания. - pub(crate) const fn content_identity(self) -> ContentIdentityV2 { - ContentIdentityV2::from_core(self.inner.report().content_identity()) + pub(crate) const fn content_identity(self) -> ContentIdentityV3 { + ContentIdentityV3::from_core(self.inner.report().content_identity()) } /// Возвращает точное наблюдение, вызвавшее конфликт. @@ -1808,7 +1953,7 @@ impl<'a> CertificateV1<'a> { } /// Возвращает identity скомпилированного содержания. - pub(crate) const fn content_identity(self) -> ContentIdentityV2 { + pub(crate) const fn content_identity(self) -> ContentIdentityV3 { match self { Self::Verified(value) => value.content_identity(), Self::Conflict(value) => value.content_identity(), @@ -2941,6 +3086,54 @@ fn map_program_compile_error(error: ProgramCompileError) -> CompileErrorV1 { occurrence: OccurrenceIdV1::from_core(occurrence), surface: SurfaceIdV1::from_core(surface), }, + ProgramCompileError::DuplicatePresentationRoot { root } => { + CompileErrorV1::DuplicatePresentationRoot { + root: PresentationRootIdV1::from_core(root), + } + } + ProgramCompileError::MissingPresentationRootOccurrence { root, occurrence } => { + CompileErrorV1::MissingPresentationRootOccurrence { + root: PresentationRootIdV1::from_core(root), + occurrence: OccurrenceIdV1::from_core(occurrence), + } + } + ProgramCompileError::PresentationRootConsumedDownstream { root, occurrence } => { + CompileErrorV1::PresentationRootConsumedDownstream { + root: PresentationRootIdV1::from_core(root), + occurrence: OccurrenceIdV1::from_core(occurrence), + } + } + ProgramCompileError::UnusedPresentationRoot { root } => { + CompileErrorV1::UnusedPresentationRoot { + root: PresentationRootIdV1::from_core(root), + } + } + ProgramCompileError::DuplicatePointPresentationTarget { root, occurrence } => { + CompileErrorV1::DuplicatePointPresentationTarget { + root: PresentationRootIdV1::from_core(root), + occurrence: OccurrenceIdV1::from_core(occurrence), + } + } + ProgramCompileError::MissingPointPresentationRoot { root } => { + CompileErrorV1::MissingPointPresentationRoot { + root: PresentationRootIdV1::from_core(root), + } + } + ProgramCompileError::MissingPointPresentationOccurrence { root, occurrence } => { + CompileErrorV1::MissingPointPresentationOccurrence { + root: PresentationRootIdV1::from_core(root), + occurrence: OccurrenceIdV1::from_core(occurrence), + } + } + ProgramCompileError::PointPresentationOccurrenceOutsideRootAncestry { + root, + terminal, + occurrence, + } => CompileErrorV1::PointPresentationOccurrenceOutsideRootAncestry { + root: PresentationRootIdV1::from_core(root), + terminal: OccurrenceIdV1::from_core(terminal), + occurrence: OccurrenceIdV1::from_core(occurrence), + }, ProgramCompileError::PaintCycle { paints } => { CompileErrorV1::PaintCycle(PaintCycleV1 { paints }) } diff --git a/crates/labcolors-core/src/program_api_tests.rs b/crates/labcolors-core/src/program_api_tests.rs index 0c6bbd9f..46394a19 100644 --- a/crates/labcolors-core/src/program_api_tests.rs +++ b/crates/labcolors-core/src/program_api_tests.rs @@ -10,6 +10,7 @@ fn staged_program_api_is_module_qualified_without_transport_prefixes() { let paint = program::PaintIdV1::new(4); let surface = program::SurfaceIdV1::new(5); let occurrence = program::OccurrenceIdV1::new(6); + let root = program::PresentationRootIdV1::new(9); let constraint = program::ConstraintIdV1::new(7); let output = program::OutputSlotIdV1::new(8); let context = @@ -22,10 +23,13 @@ fn staged_program_api_is_module_qualified_without_transport_prefixes() { draft.push_solid_paint(paint, target); draft.push_input_surface(surface, input); draft.push_source_over_occurrence(occurrence, paint, surface, context); + draft.push_point_presentation_root(root, occurrence); + draft.push_point_presentation_target(root, occurrence); draft.push_exact_hard(constraint, occurrence, Srgb8::new([0, 0, 0])); draft.push_output(output, paint); let owner = draft.compile().unwrap(); + assert_eq!(owner.point_presentation_count(), 1); let mut session = owner.instantiate(1).unwrap(); let white = [Srgb8::new([255, 255, 255])]; let scenarios = [program::ScenarioV1::new(1, &white)]; diff --git a/crates/labcolors-core/src/program_boundary_tests.rs b/crates/labcolors-core/src/program_boundary_tests.rs index 2177eda7..2d73743e 100644 --- a/crates/labcolors-core/src/program_boundary_tests.rs +++ b/crates/labcolors-core/src/program_boundary_tests.rs @@ -9,12 +9,12 @@ use crate::Srgb8; use crate::program::{ AppearanceContextErrorKindV1, AppearanceContextFieldV1, AppearanceContextV1, AssessmentV1, CertificateV1, CompileErrorHandleV1, CompileErrorKindV1, CompileErrorV1, ConstraintIdV1, - ContentIdentityV2, DraftErrorV1, DraftV1, EvidenceBoundsErrorV1, InstantiateErrorV1, + ContentIdentityV3, 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, - TargetIdV1, UpdateErrorKindV1, UpdateErrorV1, UpdateV1, VerdictV1, + PhysicalPointV1, PresentationRootIdV1, ProjectionV1, ScenarioV1, SessionV1, SignalV1, + SourceIdV1, StateKindV1, SurfaceIdV1, SurfaceInputPortIdV1, SurroundV1, TargetCandidateIdV1, + TargetCandidateV1, TargetIdV1, UpdateErrorKindV1, UpdateErrorV1, UpdateV1, VerdictV1, }; use crate::wcag22::Wcag22CriterionV1; @@ -182,6 +182,11 @@ fn staged_boundary_uses_only_closed_concrete_types() { assert_eq!(core::mem::size_of::(), 3); } +const NESTED_SOLID_PAINT: PaintIdV1 = PaintIdV1::new(4); +const NESTED_INPUT_SURFACE: SurfaceIdV1 = SurfaceIdV1::new(6); +const NESTED_INNER_OCCURRENCE: OccurrenceIdV1 = OccurrenceIdV1::new(8); +const NESTED_TERMINAL_OCCURRENCE: OccurrenceIdV1 = OccurrenceIdV1::new(9); + fn fixed_nested_draft( opacity_value: f64, target_source: SourceIdV1, @@ -191,12 +196,8 @@ fn fixed_nested_draft( let source = SourceIdV1::new(1); let target = TargetIdV1::new(2); let opacity = OpacityInputIdV1::new(3); - let solid = PaintIdV1::new(4); let translucent = PaintIdV1::new(5); - let input_surface = SurfaceIdV1::new(6); - let nested_surface = SurfaceIdV1::new(7); - let first_occurrence = OccurrenceIdV1::new(8); - let second_occurrence = OccurrenceIdV1::new(9); + let derived_surface = SurfaceIdV1::new(7); let exact = ConstraintIdV1::new(10); let wcag = ConstraintIdV1::new(11); let output = OutputSlotIdV1::new(12); @@ -207,25 +208,184 @@ fn fixed_nested_draft( draft.push_fixed_target(target, target_source); draft.push_surface_input_port(declared_input); draft.push_opacity_input(opacity, opacity_value); - draft.push_solid_paint(solid, target); - draft.push_opacity_paint(translucent, solid, opacity); - draft.push_input_surface(input_surface, used_input); - draft.push_source_over_occurrence(first_occurrence, translucent, input_surface, context); - draft.push_occurrence_surface(nested_surface, first_occurrence); - draft.push_source_over_occurrence(second_occurrence, solid, nested_surface, context); - draft.push_exact_hard(exact, first_occurrence, Srgb8::new([0; 3])); - draft.push_wcag22_report_only(wcag, second_occurrence, Wcag22CriterionV1::Sc143TextDefault); + draft.push_solid_paint(NESTED_SOLID_PAINT, target); + draft.push_opacity_paint(translucent, NESTED_SOLID_PAINT, opacity); + draft.push_input_surface(NESTED_INPUT_SURFACE, used_input); + draft.push_source_over_occurrence( + NESTED_INNER_OCCURRENCE, + translucent, + NESTED_INPUT_SURFACE, + context, + ); + draft.push_occurrence_surface(derived_surface, NESTED_INNER_OCCURRENCE); + draft.push_source_over_occurrence( + NESTED_TERMINAL_OCCURRENCE, + NESTED_SOLID_PAINT, + derived_surface, + context, + ); + draft.push_exact_hard(exact, NESTED_INNER_OCCURRENCE, Srgb8::new([0; 3])); + draft.push_wcag22_report_only( + wcag, + NESTED_TERMINAL_OCCURRENCE, + Wcag22CriterionV1::Sc143TextDefault, + ); draft.push_output(output, translucent); draft } +#[test] +fn program_compiler_admits_only_explicit_terminal_point_presentations() { + let input = SurfaceInputPortIdV1::new(50); + let root = PresentationRootIdV1::new(51); + let mut valid = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + valid.push_point_presentation_root(root, NESTED_TERMINAL_OCCURRENCE); + valid.push_point_presentation_target(root, NESTED_INNER_OCCURRENCE); + let owner = valid.compile().unwrap(); + assert_eq!(owner.point_presentation_count(), 1); + + let mut intermediate = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + intermediate.push_point_presentation_root(root, NESTED_INNER_OCCURRENCE); + intermediate.push_point_presentation_target(root, NESTED_INNER_OCCURRENCE); + assert_eq!( + compile_error(intermediate), + CompileErrorV1::PresentationRootConsumedDownstream { + root, + occurrence: NESTED_INNER_OCCURRENCE, + } + ); +} + +#[test] +fn presentation_compile_failures_preserve_typed_root_and_occurrence_handles() { + let input = SurfaceInputPortIdV1::new(50); + let root = PresentationRootIdV1::new(51); + + let mut missing_root = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + missing_root.push_point_presentation_target(root, NESTED_INNER_OCCURRENCE); + let error = compile_error(missing_root); + assert_eq!( + error.kind(), + CompileErrorKindV1::MissingPointPresentationRoot + ); + assert_eq!( + error.primary_handle(), + Some(CompileErrorHandleV1::PresentationRoot(root)) + ); + + let mut unused_root = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + unused_root.push_point_presentation_root(root, NESTED_TERMINAL_OCCURRENCE); + assert_eq!( + compile_error(unused_root), + CompileErrorV1::UnusedPresentationRoot { root } + ); + + let missing_occurrence = OccurrenceIdV1::new(999); + let mut missing_target = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + missing_target.push_point_presentation_root(root, NESTED_TERMINAL_OCCURRENCE); + missing_target.push_point_presentation_target(root, missing_occurrence); + let error = compile_error(missing_target); + assert_eq!( + error, + CompileErrorV1::MissingPointPresentationOccurrence { + root, + occurrence: missing_occurrence, + } + ); + assert_eq!( + error.related_handle(), + Some(CompileErrorHandleV1::Occurrence(missing_occurrence)) + ); +} + +#[test] +fn malformed_presentation_target_precedes_an_aggregate_unused_root_error() { + let input = SurfaceInputPortIdV1::new(50); + let root = PresentationRootIdV1::new(51); + let orphan = PresentationRootIdV1::new(52); + let missing = OccurrenceIdV1::new(999); + let mut draft = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + draft.push_point_presentation_root(root, NESTED_TERMINAL_OCCURRENCE); + draft.push_point_presentation_root(orphan, NESTED_TERMINAL_OCCURRENCE); + draft.push_point_presentation_target(root, missing); + + assert_eq!( + compile_error(draft), + CompileErrorV1::MissingPointPresentationOccurrence { + root, + occurrence: missing, + } + ); +} + +#[test] +fn presentation_declarations_fail_closed_on_duplicates_and_unrelated_nodes() { + let input = SurfaceInputPortIdV1::new(50); + let root = PresentationRootIdV1::new(51); + let terminal = NESTED_TERMINAL_OCCURRENCE; + let target = NESTED_INNER_OCCURRENCE; + + let mut duplicate_root = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + duplicate_root.push_point_presentation_root(root, terminal); + duplicate_root.push_point_presentation_root(root, terminal); + duplicate_root.push_point_presentation_target(root, target); + assert_eq!( + compile_error(duplicate_root), + CompileErrorV1::DuplicatePresentationRoot { root } + ); + + let mut duplicate_target = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + duplicate_target.push_point_presentation_root(root, terminal); + duplicate_target.push_point_presentation_target(root, target); + duplicate_target.push_point_presentation_target(root, target); + assert_eq!( + compile_error(duplicate_target), + CompileErrorV1::DuplicatePointPresentationTarget { + root, + occurrence: target, + } + ); + + let missing_terminal = OccurrenceIdV1::new(998); + let mut missing_root_occurrence = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + missing_root_occurrence.push_point_presentation_root(root, missing_terminal); + missing_root_occurrence.push_point_presentation_target(root, target); + assert_eq!( + compile_error(missing_root_occurrence), + CompileErrorV1::MissingPresentationRootOccurrence { + root, + occurrence: missing_terminal, + } + ); + + let unrelated = OccurrenceIdV1::new(997); + let context = AppearanceContextV1::try_new(64.0, 0.2, SurroundV1::Dim).unwrap(); + let mut outside_ancestry = fixed_nested_draft(0.5, SourceIdV1::new(1), input, input); + outside_ancestry.push_source_over_occurrence( + unrelated, + NESTED_SOLID_PAINT, + NESTED_INPUT_SURFACE, + context, + ); + outside_ancestry.push_point_presentation_root(root, terminal); + outside_ancestry.push_point_presentation_target(root, unrelated); + assert_eq!( + compile_error(outside_ancestry), + CompileErrorV1::PointPresentationOccurrenceOutsideRootAncestry { + root, + terminal, + occurrence: unrelated, + } + ); +} + fn attach_target_assessment(draft: &mut DraftV1, target: TargetIdV1) { let paint = PaintIdV1::new(770); let occurrence = OccurrenceIdV1::new(771); let constraint = ConstraintIdV1::new(772); let context = AppearanceContextV1::try_new(64.0, 0.2, SurroundV1::Average).unwrap(); draft.push_solid_paint(paint, target); - draft.push_source_over_occurrence(occurrence, paint, SurfaceIdV1::new(6), context); + draft.push_source_over_occurrence(occurrence, paint, NESTED_INPUT_SURFACE, context); draft.push_exact_report_only(constraint, occurrence, Srgb8::new([0; 3])); } @@ -677,7 +837,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: ContentIdentityV2 = owner.content_identity(); + let owner_identity: ContentIdentityV3 = owner.content_identity(); let mut session = owner.instantiate(13).unwrap(); let no_scenarios = []; @@ -924,7 +1084,7 @@ fn declared_and_referenced_surface_inputs_cannot_drift() { assert_eq!(error.kind(), CompileErrorKindV1::MissingSurfaceInputPort); assert_eq!( error.primary_handle(), - Some(CompileErrorHandleV1::Surface(SurfaceIdV1::new(6))) + Some(CompileErrorHandleV1::Surface(NESTED_INPUT_SURFACE)) ); assert_eq!( error.related_handle(), @@ -971,7 +1131,7 @@ fn declared_input_ports_form_an_exact_bijection_with_input_surfaces() { error, CompileErrorV1::DuplicateSurfaceInputBinding { input, - first: SurfaceIdV1::new(6), + first: NESTED_INPUT_SURFACE, duplicate: duplicate_surface, } ); @@ -1072,7 +1232,7 @@ fn dependency_cycles_retain_all_typed_core_members_without_reallocation() { render_cycle.push_occurrence_surface(cyclic_surface, cyclic_occurrence); render_cycle.push_source_over_occurrence( cyclic_occurrence, - PaintIdV1::new(4), + NESTED_SOLID_PAINT, cyclic_surface, context, ); diff --git a/crates/labcolors-core/src/program_identity.rs b/crates/labcolors-core/src/program_identity.rs index 467aa236..8e6b8b8e 100644 --- a/crates/labcolors-core/src/program_identity.rs +++ b/crates/labcolors-core/src/program_identity.rs @@ -7,19 +7,21 @@ use super::*; -const DOMAIN_V2: &[u8] = b"labcolors.program-content-identity.v2\0"; -// Максимальный V2-цвет принадлежит Occurrence: теги вершины, композиции, +const DOMAIN_V3: &[u8] = b"labcolors.program-content-identity.v3\0"; +// Максимальный V3-цвет принадлежит Occurrence: теги вершины, композиции, // контекста и frame, два binary64-параметра наблюдения и surround. Явная // граница устраняет аллокацию на каждую вершину и требует пересмотра при // расширении схемы вместо скрытого runtime-лимита. const COLOR_CAPACITY: usize = 1 + 1 + 1 + 4 + 8 + 8 + 1; mod release_tag { - pub(super) const PROGRAM_SCHEMA_V2: u8 = 2; + pub(super) const PROGRAM_SCHEMA_V3: u8 = 3; 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; + pub(super) const MODELED_POINT_PRESENTATION_V1: u8 = 1; + pub(super) const POINT_ABSENCE_BYPASS_OWN_BACKDROP_V1: u8 = 1; #[cfg(test)] pub(super) const MODELED_LCS_OCCURRENCE_V1: u8 = 1; #[cfg(test)] @@ -62,14 +64,14 @@ mod release_tag { pub(super) const MODELED_LCS_PROBE_FAMILY_V1: u8 = 3; } -/// Устойчивый к коллизиям адрес канонизированного содержимого Program V2. +/// Устойчивый к коллизиям адрес канонизированного содержимого Program V3. /// /// SHA-256 не делает адрес инъективным. Адрес не связывает пространства opaque /// ID и не подтверждает владельца, поколение либо revision. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct ProgramContentIdentityV2([u8; 32]); +pub(crate) struct ProgramContentIdentityV3([u8; 32]); -impl ProgramContentIdentityV2 { +impl ProgramContentIdentityV3 { pub(crate) const fn as_bytes(&self) -> &[u8; 32] { &self.0 } @@ -144,6 +146,8 @@ mod vertex_tag { pub(super) const JOINT_SELECTION: u8 = 17; pub(super) const JOINT_STATE: u8 = 18; pub(super) const JOINT_CHOICE: u8 = 19; + pub(super) const PRESENTATION_ROOT: u8 = 20; + pub(super) const PRESENTATION_TARGET: u8 = 21; } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -166,6 +170,9 @@ enum EdgeRoleV1 { StateChoice = 15, ChoiceTarget = 16, ChoiceCandidate = 17, + PresentationRootTerminal = 18, + PresentationTargetRoot = 19, + PresentationTargetOccurrence = 20, } #[derive(Debug, Clone, Copy)] @@ -326,17 +333,30 @@ fn program_root_color() -> Result { // encoded Paint emission. Derived capability releases bind only the // constraints that execute them. for release in [ - release_tag::PROGRAM_SCHEMA_V2, + release_tag::PROGRAM_SCHEMA_V3, 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_POINT_PRESENTATION_V1, ] { color.push_u8(release)?; } Ok(color) } +fn presentation_target_color( + target: PointPresentationTargetV1, +) -> Result { + let mut color = VertexColorV1::new(vertex_tag::PRESENTATION_TARGET); + color.push_u8(match target.absence_release() { + PointOccurrenceAbsenceReleaseV1::BypassOwnBackdropV1 => { + release_tag::POINT_ABSENCE_BYPASS_OWN_BACKDROP_V1 + } + })?; + Ok(color) +} + fn write_signal(color: &mut VertexColorV1, signal: ColorSignal) -> Result<(), ProgramCompileError> { let profile = match signal.output_profile() { crate::lcs_occurrence::OutputProfileId::Iec61966Srgb8D65V1 => { @@ -537,6 +557,11 @@ where let mut ports = IdIndexV1::new(); let mut surfaces = IdIndexV1::new(); let mut occurrences = IdIndexV1::new(); + let mut presentation_roots = IdIndexV1::new(); + let mut presentation_targets = Vec::new(); + presentation_targets + .try_reserve_exact(program.presentation_targets.len()) + .map_err(|_| ProgramCompileError::ResourceExhausted)?; for source in &program.sources { sources.insert(source.id(), graph.add_member(source_color(*source)?)?)?; @@ -586,6 +611,22 @@ where graph.add_member(occurrence_color(*occurrence)?)?, )?; } + for root in &program.presentation_roots { + presentation_roots.insert( + root.id(), + graph.add_member(VertexColorV1::new(vertex_tag::PRESENTATION_ROOT))?, + )?; + } + // `prepare_program` reaches this private identity step only after + // `compile_point_presentations` has sorted and rejected duplicate + // `(root, occurrence)` pairs. Deduplicating again here would hide an + // authored error and split that admission rule across two authorities. + for target in &program.presentation_targets { + presentation_targets.push(( + *target, + graph.add_member(presentation_target_color(*target)?)?, + )); + } sources.finish()?; targets.finish()?; @@ -595,6 +636,7 @@ where ports.finish()?; surfaces.finish()?; occurrences.finish()?; + presentation_roots.finish()?; for target in &program.targets { let target_vertex = targets.get(target.id())?; @@ -667,6 +709,25 @@ where EdgeRoleV1::OccurrenceBackdropSurface, )?; } + for root in &program.presentation_roots { + graph.add_edge( + presentation_roots.get(root.id())?, + occurrences.get(root.terminal())?, + EdgeRoleV1::PresentationRootTerminal, + )?; + } + for (target, vertex) in presentation_targets { + graph.add_edge( + vertex, + presentation_roots.get(target.root())?, + EdgeRoleV1::PresentationTargetRoot, + )?; + graph.add_edge( + vertex, + occurrences.get(target.occurrence())?, + EdgeRoleV1::PresentationTargetOccurrence, + )?; + } for constraint in &program.constraints.hard { let color = constraint_color( @@ -992,7 +1053,7 @@ fn serialize_leaf( .and_then(|value| value.checked_add(color.as_slice().len())) .ok_or(ProgramCompileError::ResourceExhausted) })?; - let capacity = DOMAIN_V2 + let capacity = DOMAIN_V3 .len() .checked_add(16) .and_then(|value| value.checked_add(color_bytes)) @@ -1002,7 +1063,7 @@ fn serialize_leaf( output .try_reserve_exact(capacity) .map_err(|_| ProgramCompileError::ResourceExhausted)?; - output.extend_from_slice(DOMAIN_V2); + output.extend_from_slice(DOMAIN_V3); push_u64_bytes(&mut output, usize_as_u64(graph.colors.len())?); push_u64_bytes(&mut output, usize_as_u64(graph.edge_count)?); @@ -1336,9 +1397,9 @@ fn canonical_preimage(graph: &CanonicalGraphV1) -> Result, ProgramCompil canonical_search(graph).map(|(preimage, _)| preimage) } -pub(super) fn compile_program_content_identity_v2( +pub(super) fn compile_program_content_identity_v3( program: &Program, -) -> Result +) -> Result where Evaluation: ProgramConstraintEvaluatorSetV1, ProgramConstraintInvocationOf: Copy, @@ -1346,7 +1407,7 @@ where let graph = build_graph(program)?; let preimage = canonical_preimage(&graph)?; let digest = crate::sha256::digest(&preimage); - Ok(ProgramContentIdentityV2(*digest.as_bytes())) + Ok(ProgramContentIdentityV3(*digest.as_bytes())) } #[cfg(test)] diff --git a/crates/labcolors-core/src/program_identity_tests.rs b/crates/labcolors-core/src/program_identity_tests.rs index d420533b..08fb7d3c 100644 --- a/crates/labcolors-core/src/program_identity_tests.rs +++ b/crates/labcolors-core/src/program_identity_tests.rs @@ -9,8 +9,9 @@ use crate::program_session::{ CompositionProfile, ConstraintId, ConstraintInvocation, ConstraintSet, CoreProgramConstraintInvocationV1, CoreProgramEvaluatorsV1, CoreProgramV1, DeclaredJointSelectionV1, JointCandidateStateV1, ObservationGroup, Occurrence, OpacityInput, - OutputBinding, OutputSlotId, Paint, Program, ProgramContentIdentityV2, Source, SourceId, - Surface, Target, TargetCandidateChoiceV1, TargetCandidateId, TargetCandidateV1, TargetId, + OutputBinding, OutputSlotId, Paint, PointPresentationRootV1, PointPresentationTargetV1, + PresentationRootId, Program, ProgramContentIdentityV3, Source, SourceId, Surface, Target, + TargetCandidateChoiceV1, TargetCandidateId, TargetCandidateV1, TargetId, }; use crate::wcag22::Wcag22CriterionV1; @@ -201,7 +202,122 @@ fn fixed_graph_identity_ignores_opaque_names_and_unordered_declaration_order() { } #[test] -fn canonical_v2_digest_is_cross_platform_golden() { +fn presentation_topology_ignores_root_names_and_declaration_order() { + let ids = FixedIds { + sources: [SourceId::new(10), SourceId::new(20)], + targets: [TargetId::new(30), TargetId::new(40)], + paints: [PaintId::new(50), PaintId::new(60)], + ports: [SurfaceInputPortId::new(70), SurfaceInputPortId::new(80)], + surfaces: [SurfaceId::new(90), SurfaceId::new(100)], + occurrences: [OccurrenceId::new(110), OccurrenceId::new(120)], + constraints: [ConstraintId::new(130), ConstraintId::new(140)], + outputs: [OutputSlotId::new(150), OutputSlotId::new(160)], + group: ObservationGroupId::new(170), + }; + let canonical = fixed_program( + ids, + false, + Srgb8::new([0x40, 0x50, 0x60]), + FixedMutation::None, + ) + .with_point_presentations( + vec![ + PointPresentationRootV1::new(PresentationRootId::new(1), ids.occurrences[0]), + PointPresentationRootV1::new(PresentationRootId::new(2), ids.occurrences[1]), + ], + vec![ + PointPresentationTargetV1::new(PresentationRootId::new(1), ids.occurrences[0]), + PointPresentationTargetV1::new(PresentationRootId::new(2), ids.occurrences[1]), + ], + ) + .compile() + .unwrap(); + let renamed = fixed_program( + ids, + true, + Srgb8::new([0x40, 0x50, 0x60]), + FixedMutation::None, + ) + .with_point_presentations( + vec![ + PointPresentationRootV1::new(PresentationRootId::new(900), ids.occurrences[1]), + PointPresentationRootV1::new(PresentationRootId::new(700), ids.occurrences[0]), + ], + vec![ + PointPresentationTargetV1::new(PresentationRootId::new(900), ids.occurrences[1]), + PointPresentationTargetV1::new(PresentationRootId::new(700), ids.occurrences[0]), + ], + ) + .compile() + .unwrap(); + assert_eq!(canonical.content_identity(), renamed.content_identity()); +} + +#[test] +fn presentation_root_terminal_relation_changes_v3_identity() { + let ids = canonical_full_ids(); + let root = PresentationRootId::new(1); + let first = full_program(ids, false, FullMutation::None) + .with_point_presentations( + vec![PointPresentationRootV1::new(root, ids.occurrences[1])], + vec![PointPresentationTargetV1::new(root, ids.occurrences[0])], + ) + .compile() + .unwrap(); + let second = full_program(ids, false, FullMutation::None) + .with_point_presentations( + vec![PointPresentationRootV1::new(root, ids.occurrences[2])], + vec![PointPresentationTargetV1::new(root, ids.occurrences[0])], + ) + .compile() + .unwrap(); + assert_ne!(first.content_identity(), second.content_identity()); +} + +#[test] +fn presentation_target_relation_and_multiplicity_change_v3_identity() { + let ids = canonical_full_ids(); + let root = PresentationRootId::new(1); + let nested_target = full_program(ids, false, FullMutation::None) + .with_point_presentations( + vec![PointPresentationRootV1::new(root, ids.occurrences[1])], + vec![PointPresentationTargetV1::new(root, ids.occurrences[0])], + ) + .compile() + .unwrap(); + let root_target = full_program(ids, false, FullMutation::None) + .with_point_presentations( + vec![PointPresentationRootV1::new(root, ids.occurrences[1])], + vec![PointPresentationTargetV1::new(root, ids.occurrences[1])], + ) + .compile() + .unwrap(); + let duplicated_topology = full_program(ids, false, FullMutation::None) + .with_point_presentations( + vec![ + PointPresentationRootV1::new(root, ids.occurrences[1]), + PointPresentationRootV1::new(PresentationRootId::new(2), ids.occurrences[1]), + ], + vec![ + PointPresentationTargetV1::new(root, ids.occurrences[0]), + PointPresentationTargetV1::new(PresentationRootId::new(2), ids.occurrences[0]), + ], + ) + .compile() + .unwrap(); + + assert_ne!( + nested_target.content_identity(), + root_target.content_identity() + ); + assert_ne!( + nested_target.content_identity(), + duplicated_topology.content_identity() + ); +} + +#[test] +fn canonical_v3_digest_is_cross_platform_golden() { let ids = FixedIds { sources: [SourceId::new(10), SourceId::new(20)], targets: [TargetId::new(30), TargetId::new(40)], @@ -222,12 +338,12 @@ fn canonical_v2_digest_is_cross_platform_golden() { .compile() .unwrap(); - let identity: ProgramContentIdentityV2 = compiled.content_identity(); + let identity: ProgramContentIdentityV3 = compiled.content_identity(); assert_eq!( identity.as_bytes(), &[ - 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, + 211, 179, 135, 69, 69, 109, 110, 86, 153, 187, 96, 120, 151, 187, 168, 80, 219, 230, + 123, 39, 214, 9, 163, 4, 100, 108, 41, 75, 133, 240, 235, 243, ] ); } @@ -521,7 +637,7 @@ fn full_program(ids: FullIds, reverse_unordered: bool, mutation: FullMutation) - } } - Program::new( + let program = Program::new( sources, targets, ObservationGroup::new(ids.group, vec![ids.port]), @@ -533,7 +649,22 @@ fn full_program(ids: FullIds, reverse_unordered: bool, mutation: FullMutation) - outputs, CoreProgramEvaluatorsV1, ) - .with_joint_selection(DeclaredJointSelectionV1::new(states)) + .with_joint_selection(DeclaredJointSelectionV1::new(states)); + + if matches!(mutation, FullMutation::CompleteSchemaGolden) { + program.with_point_presentations( + vec![PointPresentationRootV1::new( + PresentationRootId::new(1_002), + ids.occurrences[1], + )], + vec![PointPresentationTargetV1::new( + PresentationRootId::new(1_002), + ids.occurrences[0], + )], + ) + } else { + program + } } fn canonical_full_ids() -> FullIds { @@ -569,8 +700,8 @@ fn canonical_full_ids() -> FullIds { } #[test] -fn complete_program_schema_v2_digest_is_cross_platform_golden() { - // Вместе с fixed golden этот Program содержит каждый V2 vertex/edge tag, +fn complete_program_schema_v3_digest_is_cross_platform_golden() { + // Вместе с fixed golden этот Program содержит каждый V3 vertex/edge tag, // обе constraint families и оба режима. Случайная смена кодировки требует // явной смены версии, а не тихого перевыпуска прежнего content address. let compiled = full_program( @@ -584,8 +715,8 @@ fn complete_program_schema_v2_digest_is_cross_platform_golden() { assert_eq!( compiled.content_identity().as_bytes(), &[ - 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, + 15, 108, 204, 68, 241, 35, 134, 151, 201, 150, 216, 202, 77, 2, 70, 32, 150, 16, 125, + 38, 103, 99, 132, 86, 227, 246, 128, 42, 234, 33, 53, 173, ] ); } diff --git a/crates/labcolors-core/src/program_session.rs b/crates/labcolors-core/src/program_session.rs index a6cc6dd2..252ec193 100644 --- a/crates/labcolors-core/src/program_session.rs +++ b/crates/labcolors-core/src/program_session.rs @@ -26,8 +26,10 @@ use crate::Srgb8; use crate::appearance::{ AdmittedAppearanceBindings, AppearanceBindings, AppearanceGraphSpec, AppearanceWorkspace, BindingError, ColorInputId, CompileError, CompiledAppearanceGraph, CompiledColorInputSlotV1, - CompiledOccurrenceSlotV1, CompiledPaintSlotV1, EncodedPointPaintV1, OccurrenceId, - OccurrenceSpec, OpacityInputId, PaintId, PaintSpec, SurfaceId, SurfaceInputPortId, SurfaceSpec, + CompiledOccurrenceSlotV1, CompiledPaintSlotV1, CompiledPointPresentationPathV1, + EncodedPointPaintV1, OccurrenceId, OccurrenceSpec, OpacityInputId, PaintId, PaintSpec, + PointOccurrenceAbsenceReleaseV1, PointPresentationPathErrorV1, SurfaceId, SurfaceInputPortId, + SurfaceSpec, }; use crate::composition::CompositionProfileV1; use crate::constraints::{ @@ -55,7 +57,7 @@ use crate::wcag22::Wcag22CriterionV1; #[path = "program_identity.rs"] mod identity; -pub(crate) use identity::ProgramContentIdentityV2; +pub(crate) use identity::ProgramContentIdentityV3; /// Opaque identity of one immutable authored colour source. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -379,6 +381,72 @@ impl OutputSlotId { } } +/// Opaque identity of one modeled point presentation root. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PresentationRootId(u32); + +impl PresentationRootId { + pub const fn new(value: u32) -> Self { + Self(value) + } + + pub const fn value(self) -> u32 { + self.0 + } +} + +/// One declared terminal occurrence of the modeled point graph. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PointPresentationRootV1 { + id: PresentationRootId, + terminal: OccurrenceId, +} + +impl PointPresentationRootV1 { + pub const fn new(id: PresentationRootId, terminal: OccurrenceId) -> Self { + Self { id, terminal } + } + + pub const fn id(self) -> PresentationRootId { + self.id + } + + pub const fn terminal(self) -> OccurrenceId { + self.terminal + } +} + +/// One occurrence whose final point contribution will be evaluated at the +/// declared root under an explicit, versioned absence intervention. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PointPresentationTargetV1 { + root: PresentationRootId, + occurrence: OccurrenceId, + absence_release: PointOccurrenceAbsenceReleaseV1, +} + +impl PointPresentationTargetV1 { + pub const fn new(root: PresentationRootId, occurrence: OccurrenceId) -> Self { + Self { + root, + occurrence, + absence_release: PointOccurrenceAbsenceReleaseV1::BypassOwnBackdropV1, + } + } + + pub const fn root(self) -> PresentationRootId { + self.root + } + + pub const fn occurrence(self) -> OccurrenceId { + self.occurrence + } + + pub const fn absence_release(self) -> PointOccurrenceAbsenceReleaseV1 { + self.absence_release + } +} + /// Type-level marker for a mandatory constraint. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HardModeV1 {} @@ -714,6 +782,8 @@ where paints: Vec, surfaces: Vec, occurrences: Vec, + presentation_roots: Vec, + presentation_targets: Vec, constraints: ConstraintSet>, outputs: Vec, evaluator: Evaluation, @@ -746,6 +816,8 @@ where paints, surfaces, occurrences, + presentation_roots: Vec::new(), + presentation_targets: Vec::new(), constraints, outputs, evaluator, @@ -760,6 +832,16 @@ where self } + pub fn with_point_presentations( + mut self, + roots: Vec, + targets: Vec, + ) -> Self { + self.presentation_roots = roots; + self.presentation_targets = targets; + self + } + pub fn compile(self) -> Result, ProgramCompileError> { prepare_program(self).map(|epoch| CompiledProgram { owner_generation: Rc::new(epoch), @@ -847,6 +929,14 @@ impl CoreProgramDraftV1 { self.program.occurrences.push(occurrence); } + pub(crate) fn push_point_presentation_root(&mut self, root: PointPresentationRootV1) { + self.program.presentation_roots.push(root); + } + + pub(crate) fn push_point_presentation_target(&mut self, target: PointPresentationTargetV1) { + self.program.presentation_targets.push(target); + } + pub(crate) fn push_hard_constraint( &mut self, constraint: ConstraintInvocation, @@ -934,6 +1024,36 @@ pub enum ProgramCompileError { occurrence: OccurrenceId, surface: SurfaceId, }, + DuplicatePresentationRoot { + root: PresentationRootId, + }, + MissingPresentationRootOccurrence { + root: PresentationRootId, + occurrence: OccurrenceId, + }, + PresentationRootConsumedDownstream { + root: PresentationRootId, + occurrence: OccurrenceId, + }, + UnusedPresentationRoot { + root: PresentationRootId, + }, + DuplicatePointPresentationTarget { + root: PresentationRootId, + occurrence: OccurrenceId, + }, + MissingPointPresentationRoot { + root: PresentationRootId, + }, + MissingPointPresentationOccurrence { + root: PresentationRootId, + occurrence: OccurrenceId, + }, + PointPresentationOccurrenceOutsideRootAncestry { + root: PresentationRootId, + terminal: OccurrenceId, + occurrence: OccurrenceId, + }, PaintCycle { paints: Vec, }, @@ -1088,6 +1208,14 @@ struct CompiledObservationGroupV1 { schema: CanonicalObservationSchemaV1, } +struct CompiledPointPresentationV1 { + root: PresentationRootId, + terminal: OccurrenceId, + target: OccurrenceId, + absence_release: PointOccurrenceAbsenceReleaseV1, + path: CompiledPointPresentationPathV1, +} + struct CompiledFiniteTargetV1 { binding: CompiledColorInputSlotV1, candidates: Box<[ColorSignal]>, @@ -1102,7 +1230,7 @@ where Evaluation: ProgramConstraintEvaluatorSetV1, ProgramConstraintInvocationOf: Copy, { - content_identity: ProgramContentIdentityV2, + content_identity: ProgramContentIdentityV3, evaluator: Evaluation, graph: CompiledAppearanceGraph, binding_template: AdmittedAppearanceBindings, @@ -1110,6 +1238,7 @@ where occurrence_contexts: Box<[CompiledOccurrenceContextV1]>, constraints: Box<[CompiledPointConstraint>]>, constraint_phases: CompiledConstraintPhasesV1, + point_presentations: Box<[CompiledPointPresentationV1]>, outputs: Box<[CompiledOutputBinding]>, finite_targets: Box<[CompiledFiniteTargetV1]>, joint_selection: Option, @@ -1143,12 +1272,12 @@ where self.owner_generation.observation_group.id } - /// Контентный адрес Program в границах схемы V2. + /// Контентный адрес Program в границах схемы V3. /// /// Opaque ID и порядок неупорядоченных объявлений исключены; явный joint /// order входит в адрес. Адрес не подтверждает поколение владельца и не /// заменяет revision-bound evidence. - pub fn content_identity(&self) -> ProgramContentIdentityV2 { + pub fn content_identity(&self) -> ProgramContentIdentityV3 { self.owner_generation.content_identity } @@ -1170,6 +1299,31 @@ where .map(|output| (output.output, output.paint_id)) } + pub(crate) fn point_presentation_count(&self) -> usize { + debug_assert!( + self.owner_generation + .point_presentations + .windows(2) + .all(|pair| (pair[0].root, pair[0].target) < (pair[1].root, pair[1].target)) + ); + debug_assert!( + self.owner_generation + .point_presentations + .iter() + .all(|presentation| { + presentation.path.belongs_to(&self.owner_generation.graph) + && presentation.path.root() == presentation.terminal + && presentation.path.target() == presentation.target + && presentation.path.len() != 0 + && matches!( + presentation.absence_release, + PointOccurrenceAbsenceReleaseV1::BypassOwnBackdropV1 + ) + }) + ); + self.owner_generation.point_presentations.len() + } + pub(crate) fn output_count(&self) -> usize { self.owner_generation.outputs.len() } @@ -1325,7 +1479,7 @@ pub struct ProgramReportV1 where Evaluation: ProgramConstraintEvaluatorSetV1, { - content_identity: ProgramContentIdentityV2, + content_identity: ProgramContentIdentityV3, observation: RevisionBoundObservationV1, cells: Vec>, } @@ -1336,7 +1490,7 @@ where { /// Адрес содержимого Program, по которому построен report; это не /// идентификатор поколения и не runtime-authority. - pub const fn content_identity(&self) -> ProgramContentIdentityV2 { + pub const fn content_identity(&self) -> ProgramContentIdentityV3 { self.content_identity } @@ -2223,8 +2377,13 @@ where let constraint_phases = CompiledConstraintPhasesV1::from_authored(&program.constraints); let occurrence_contexts = compact_constraint_contexts(&all_occurrence_contexts, &mut constraints)?; + let point_presentations = compile_point_presentations( + &graph, + &mut program.presentation_roots, + &mut program.presentation_targets, + )?; let outputs = compile_outputs(&graph, &mut program.outputs)?; - let content_identity = identity::compile_program_content_identity_v2(&program)?; + let content_identity = identity::compile_program_content_identity_v3(&program)?; Ok(ProgramEpochV1 { content_identity, evaluator: program.evaluator, @@ -2237,6 +2396,7 @@ where occurrence_contexts, constraints, constraint_phases, + point_presentations, outputs, finite_targets, joint_selection, @@ -2987,6 +3147,111 @@ fn compact_constraint_contexts( Ok(compact.into_boxed_slice()) } +fn compile_point_presentations( + graph: &CompiledAppearanceGraph, + roots: &mut [PointPresentationRootV1], + targets: &mut [PointPresentationTargetV1], +) -> Result, ProgramCompileError> { + roots.sort_unstable_by_key(|root| root.id); + if let Some(root) = roots + .windows(2) + .find(|pair| pair[0].id == pair[1].id) + .map(|pair| pair[0].id) + { + return Err(ProgramCompileError::DuplicatePresentationRoot { root }); + } + + let mut compiled_roots = Vec::new(); + compiled_roots + .try_reserve_exact(roots.len()) + .map_err(|_| ProgramCompileError::ResourceExhausted)?; + for root in roots.iter().copied() { + let compiled = graph + .compile_point_presentation_root(root.terminal) + .map_err(|error| match error { + PointPresentationPathErrorV1::MissingRoot => { + ProgramCompileError::MissingPresentationRootOccurrence { + root: root.id, + occurrence: root.terminal, + } + } + PointPresentationPathErrorV1::RootConsumedDownstream => { + ProgramCompileError::PresentationRootConsumedDownstream { + root: root.id, + occurrence: root.terminal, + } + } + PointPresentationPathErrorV1::ResourceExhausted => { + ProgramCompileError::ResourceExhausted + } + _ => ProgramCompileError::InternalInvariant, + })?; + compiled_roots.push((root.id, compiled)); + } + + targets.sort_unstable_by_key(|target| (target.root, target.occurrence)); + if let Some(duplicate) = targets + .windows(2) + .find(|pair| pair[0] == pair[1]) + .map(|pair| pair[0]) + { + return Err(ProgramCompileError::DuplicatePointPresentationTarget { + root: duplicate.root, + occurrence: duplicate.occurrence, + }); + } + + let mut compiled = Vec::new(); + compiled + .try_reserve_exact(targets.len()) + .map_err(|_| ProgramCompileError::ResourceExhausted)?; + for target in targets.iter().copied() { + let root_index = compiled_roots + .binary_search_by_key(&target.root, |(root, _)| *root) + .map_err(|_| ProgramCompileError::MissingPointPresentationRoot { root: target.root })?; + let compiled_root = &compiled_roots[root_index].1; + let terminal = compiled_root.terminal(); + let path = graph + .compile_point_presentation_path(target.occurrence, compiled_root) + .map_err(|error| match error { + PointPresentationPathErrorV1::MissingTarget => { + ProgramCompileError::MissingPointPresentationOccurrence { + root: target.root, + occurrence: target.occurrence, + } + } + PointPresentationPathErrorV1::TargetOutsideRootAncestry => { + ProgramCompileError::PointPresentationOccurrenceOutsideRootAncestry { + root: target.root, + terminal, + occurrence: target.occurrence, + } + } + PointPresentationPathErrorV1::ResourceExhausted => { + ProgramCompileError::ResourceExhausted + } + _ => ProgramCompileError::InternalInvariant, + })?; + compiled.push(CompiledPointPresentationV1 { + root: target.root, + terminal, + target: target.occurrence, + absence_release: target.absence_release, + path, + }); + } + + for root in roots.iter() { + if targets + .binary_search_by_key(&root.id, |target| target.root) + .is_err() + { + return Err(ProgramCompileError::UnusedPresentationRoot { root: root.id }); + } + } + Ok(compiled.into_boxed_slice()) +} + fn compile_outputs( graph: &CompiledAppearanceGraph, authored: &mut [OutputBinding], diff --git a/scripts/verify_point_support_surplus.py b/scripts/verify_point_support_surplus.py index b356e55c..5ae0bc33 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 = ( - "5bed543bf75209b2660d7e4f2f0f0cb107f9ee4c841f7dad8bd05175f8ad897d" + "016cbe658a2bf0b289d1647cad0982b54fa4a9aec7b56934e2439adfb9eb8454" ) EXPECTED_Q55_PROOF_SHA256 = ( "ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"