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 13d9b054..1ecfc3fc 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":"2e7c31a9b66fa310e0a7e33291bc167283157034703c47d86d7e22b5323acee6","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":"669326bce56a2901f7fbbd8b4c23f26f8b33daceb1471b81c98763940b41d3e4","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":"e73b9c0b8c3a4112cb53987753d5a6b5f639774b0b872afaea9836e6647a2f7d"},{"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":"b3edb3764119c0b4fd50f52b62cbc07fa81c4bfdf1246b91f20eb3d8d4eebd31"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"887f139e6750cc99cd751b3c7e47276971293f74091130028a1746fa29ebb104"},{"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":"e93845aacc62968c9a21a1c7b8ef7222434b64c2100e3a177b3288051d03507d"} +{"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":"563b58088ed413b0a65c2c7d4282792559a756b9f9a51e03774d20afd8259b0a","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":"371a19f9da8337a13fb9a474c0f0395b35117822fd2d9293c6228b1f01e97ca4","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":"28b21948812801582fc2c59e304df050fc131e3bfd0970fe34b3118c667f2885"},{"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":"e480c973e04de69c364ffb913a508e7cca82b8ec1760bc4a50634c2c5e8afc2a"} diff --git a/crates/labcolors-core/src/constraints/mod.rs b/crates/labcolors-core/src/constraints/mod.rs index fb887337..6ae5a1c3 100644 --- a/crates/labcolors-core/src/constraints/mod.rs +++ b/crates/labcolors-core/src/constraints/mod.rs @@ -21,14 +21,14 @@ pub(crate) use exact::ExactIdentityPassV1; mod wcag22; -pub(crate) use wcag22::{Wcag22Srgb8CapabilityV1, Wcag22Srgb8EvaluatorIdentityV1, Wcag22Srgb8V1}; - -#[cfg(test)] pub(crate) use wcag22::{ - ApplicableWcag22EvaluationErrorV1, ApplicableWcag22MeasurementV1, Wcag22PassV1, - Wcag22ViolationV1, + ApplicableWcag22EvaluationErrorV1, Wcag22Srgb8CapabilityV1, Wcag22Srgb8EvaluatorIdentityV1, + Wcag22Srgb8V1, }; +#[cfg(test)] +pub(crate) use wcag22::{ApplicableWcag22MeasurementV1, Wcag22PassV1, Wcag22ViolationV1}; + /// Test-only probe around the production Program WCAG evaluator. It records /// each physical visible signal without changing measurement or /// classification, allowing execution-count assertions at the Program diff --git a/crates/labcolors-core/src/generic_boundary_tests.rs b/crates/labcolors-core/src/generic_boundary_tests.rs index 6c717b5a..e180305e 100644 --- a/crates/labcolors-core/src/generic_boundary_tests.rs +++ b/crates/labcolors-core/src/generic_boundary_tests.rs @@ -9,7 +9,7 @@ const LIB_SOURCE: &str = include_str!("lib.rs"); const LCS_OCCURRENCE_SOURCE: &str = include_str!("lcs_occurrence.rs"); const OBSERVATION_SOURCE: &str = include_str!("observation.rs"); const OUTPUT_PROJECTION_SOURCE: &str = include_str!("output_projection.rs"); -const PACKAGE_BRIDGE_SOURCE: &str = include_str!("package_bridge.rs"); +const PROGRAM_SOURCE: &str = include_str!("program.rs"); const POINT_SUPPORT_SOURCE: &str = include_str!("point_support.rs"); const PROGRAM_IDENTITY_SOURCE: &str = include_str!("program_identity.rs"); const PROGRAM_SESSION_SOURCE: &str = include_str!("program_session.rs"); @@ -69,6 +69,48 @@ fn contains_rust_identifier(source: &str, identifier: &str) -> bool { }) } +fn assert_only_in_compile_fail(source: &str, needle: &str) { + let mut in_compile_fail = false; + let mut occurrences = 0; + + for (line_index, line) in source.lines().enumerate() { + let doc = line.trim_start().strip_prefix("///").map(str::trim_start); + match doc { + Some("```compile_fail") => { + assert!( + !in_compile_fail, + "nested compile_fail fence before line {}", + line_index + 1, + ); + in_compile_fail = true; + continue; + } + Some("```") if in_compile_fail => { + in_compile_fail = false; + continue; + } + _ => {} + } + + let line_occurrences = line.matches(needle).count(); + if line_occurrences == 0 { + continue; + } + assert!( + in_compile_fail, + "`{needle}` escaped its negative compile_fail sentinel at line {}", + line_index + 1, + ); + occurrences += line_occurrences; + } + + assert!(!in_compile_fail, "unclosed compile_fail fence"); + assert!( + occurrences > 0, + "`{needle}` must remain covered by a negative API sentinel", + ); +} + fn production_rust_sources() -> Vec<(String, String)> { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); let mut pending = vec![root.clone()]; @@ -116,15 +158,81 @@ fn generic_physical_and_transport_modules_contain_no_client_or_legacy_vocabulary } #[test] -fn package_authoring_is_one_thin_concrete_core_draft_without_a_second_graph() { +fn stacked_program_module_is_visible_module_qualified_and_transport_neutral() { + let normalized_lib = LIB_SOURCE.split_whitespace().collect::>().join(" "); + assert_eq!( + LIB_SOURCE + .lines() + .filter(|line| line.trim() == "pub mod program;") + .count(), + 1, + "the crate root must expose exactly one file-backed Program module", + ); + assert!( + normalized_lib.contains("#[deny(missing_docs)] pub mod program;"), + "the public Program reference must stay complete by construction", + ); + assert!( + !normalized_lib.contains("#[doc(hidden)] pub mod program;"), + "the reviewed Program API must remain visible in rustdoc", + ); + + for introducer in ["pub use ", "pub type "] { + let mut remaining = LIB_SOURCE; + while let Some(start) = remaining.find(introducer) { + let statement = &remaining[start + ..start + + remaining[start..] + .find(';') + .expect("root public declaration must terminate") + + 1]; + assert!( + !contains_rust_identifier(statement, "program"), + "Program types must stay module-qualified; found root alias `{statement}`", + ); + remaining = &remaining[start + statement.len()..]; + } + } + + let source_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); + assert!( + source_root.join("program.rs").is_file(), + "the public Program implementation must remain file-backed", + ); + assert!( + !source_root.join("package_bridge.rs").exists(), + "the superseded transport-named module must not return", + ); + assert!( + !PROGRAM_SOURCE.contains("PackageProgram") + && !contains_rust_identifier(PROGRAM_SOURCE, "package_bridge"), + "the public Program source must not retain transport-era vocabulary", + ); + + for (path, source) in production_rust_sources() { + if path == "lib.rs" { + continue; + } + assert!( + !source.contains("PackageProgram") + && !contains_rust_identifier(&source, "package_bridge"), + "{path} must not retain the superseded public path or prefix", + ); + } + assert_only_in_compile_fail(LIB_SOURCE, "PackageProgram"); + assert_only_in_compile_fail(LIB_SOURCE, "package_bridge"); +} + +#[test] +fn public_program_draft_wraps_the_single_canonical_core_graph() { assert_eq!( normalized_source_scope( - PACKAGE_BRIDGE_SOURCE, - "pub struct PackageProgramDraftV1 {", - "/// Draft mutation rejected", + PROGRAM_SOURCE, + "pub struct DraftV1 {", + "/// Ошибка изменения Draft до компиляции.", ), - "pub struct PackageProgramDraftV1 { inner: CoreProgramDraftV1, }", - "the package seam must forward actual IR nodes into the sole Core draft", + "pub struct DraftV1 { inner: CoreProgramDraftV1, }", + "the public seam must forward actual IR nodes into the sole Core draft", ); assert_eq!( normalized_source_scope( @@ -150,41 +258,37 @@ fn package_authoring_is_one_thin_concrete_core_draft_without_a_second_graph() { "HashMap", "dyn Program", "OutputProfileId", - "PackageProgramObservationGroupIdV1", - "PackageProgramOpacityIdV1", - "PackageProgramSurfaceInputIdV1", + "ObservationGroupIdV1", + "OpacityIdV1", + "SurfaceInputIdV1", "pub fn push_opacity(", "pub fn push_surface_input(", "pub fn surface_input_slots(", ] { assert!( - !PACKAGE_BRIDGE_SOURCE.contains(forbidden), - "the concrete package lowerer must not acquire `{forbidden}`", + !PROGRAM_SOURCE.contains(forbidden), + "the concrete public lowerer must not acquire `{forbidden}`", ); } } #[test] -fn package_session_keeps_evidence_but_owner_alone_grants_updates_and_operations() { +fn public_session_keeps_evidence_but_owner_alone_grants_updates_and_operations() { assert_eq!( - normalized_source_scope( - PACKAGE_BRIDGE_SOURCE, - "pub struct PackageProgramSessionV1 {", - "impl PackageProgramSessionV1", - ), + normalized_source_scope(PROGRAM_SOURCE, "pub struct SessionV1 {", "impl SessionV1",), concat!( - "pub struct PackageProgramSessionV1 { ", + "pub struct SessionV1 { ", "scenario_order_scratch: Vec, ", "session: CoreProgramSessionV1, ", "}", ), - "the package Session must not duplicate owner schema, outputs, stream, or lifecycle state", + "the public Session must not duplicate owner schema, outputs, stream, or lifecycle state", ); let session_api = source_scope( - PACKAGE_BRIDGE_SOURCE, - "impl PackageProgramSessionV1 {", - "struct PackageProgramScenarioSourceV1", + PROGRAM_SOURCE, + "impl SessionV1 {", + "struct ScenarioSourceV1", ); assert_eq!( session_api.matches("pub fn evidence(").count(), @@ -210,9 +314,9 @@ fn package_session_keeps_evidence_but_owner_alone_grants_updates_and_operations( } let evidence_api = source_scope( - PACKAGE_BRIDGE_SOURCE, - "impl<'a> PackageProgramEvidenceViewV1<'a> {", - "struct PackageProgramBorrowScopeV1<'owner, 'session>", + PROGRAM_SOURCE, + "impl<'a> EvidenceViewV1<'a> {", + "struct BorrowScopeV1<'owner, 'session>", ); for forbidden in [ "pub fn revision(", @@ -227,9 +331,9 @@ fn package_session_keeps_evidence_but_owner_alone_grants_updates_and_operations( } let owner_api = source_scope( - PACKAGE_BRIDGE_SOURCE, - "impl PackageProgramOwnerV1 {", - "pub struct PackageProgramScenarioV1<'a> {", + PROGRAM_SOURCE, + "impl OwnerV1 {", + "pub struct ScenarioV1<'a> {", ); for required in [ "pub fn project<'owner, 'session>(", @@ -256,44 +360,50 @@ fn package_session_keeps_evidence_but_owner_alone_grants_updates_and_operations( "owner mismatch must be rejected before admission, allocation, or evaluation", ); - let public_access_errors = source_scope( - PACKAGE_BRIDGE_SOURCE, - "pub enum PackageProgramAccessErrorV1 {", - "impl PackageProgramOwnerV1", - ); + let public_access_errors = + source_scope(PROGRAM_SOURCE, "pub enum AccessErrorV1 {", "impl OwnerV1"); assert!( public_access_errors.contains("OwnerMismatch,") && !public_access_errors.contains("OwnerExpired"), "operation projection must distinguish foreign ownership, not expose internal expiry", ); let public_update_errors = source_scope( - PACKAGE_BRIDGE_SOURCE, - "pub enum PackageProgramUpdateErrorKindV1 {", - "pub struct PackageProgramUpdateErrorV1 {", + PROGRAM_SOURCE, + "pub enum UpdateErrorKindV1 {", + "pub enum UpdateErrorV1 {", ); assert!( public_update_errors.contains("OwnerMismatch,") && !public_update_errors.contains("OwnerExpired"), "owner expiry is an internal invariant after a matching owner borrow", ); + assert!( + PROGRAM_SOURCE.contains("pub enum UpdateErrorV1 {") + && !PROGRAM_SOURCE.contains("pub struct UpdateErrorV1 {") + && PROGRAM_SOURCE + .contains("fn map_observation_error(error: ObservationError) -> UpdateErrorV1",) + && PROGRAM_SOURCE + .contains("fn map_plan_error(error: CoreProgramPlanErrorV1) -> UpdateErrorV1",), + "update errors must retain payloads in the authoritative enum before kind projection", + ); for (payload, end) in [ ( - "pub struct PackageProgramSetV1<'owner, 'session> {", - "impl<'session> PackageProgramSetV1<'_, 'session>", + "pub struct SetV1<'owner, 'session> {", + "impl<'session> SetV1<'_, 'session>", ), ( - "pub struct PackageProgramRemoveV1<'owner, 'session> {", - "impl PackageProgramRemoveV1<'_, '_>", + "pub struct RemoveV1<'owner, 'session> {", + "impl RemoveV1<'_, '_>", ), ( - "pub struct PackageProgramHoldV1<'owner, 'session> {", - "impl<'session> PackageProgramHoldV1<'_, 'session>", + "pub struct HoldV1<'owner, 'session> {", + "impl<'session> HoldV1<'_, 'session>", ), ] { assert!( - source_scope(PACKAGE_BRIDGE_SOURCE, payload, end) - .contains("_scope: PackageProgramBorrowScopeV1<'owner, 'session>,"), + source_scope(PROGRAM_SOURCE, payload, end) + .contains("_scope: BorrowScopeV1<'owner, 'session>,"), "{payload} must retain both owner and immutable Session borrows", ); } diff --git a/crates/labcolors-core/src/lib.rs b/crates/labcolors-core/src/lib.rs index fa185ec4..c1d72f4b 100644 --- a/crates/labcolors-core/src/lib.rs +++ b/crates/labcolors-core/src/lib.rs @@ -24,7 +24,7 @@ pub mod ladder; pub mod lcs; #[expect( dead_code, - reason = "private F0 colour-identity foundation precedes the terminal public hard cut" + reason = "F0 colour-identity internals are exposed only through typed Program evidence" )] pub(crate) mod lcs_occurrence; pub(crate) mod lpc; @@ -33,24 +33,24 @@ pub mod neutral; pub mod numerical_plan; #[expect( dead_code, - reason = "private F0 output-projection release firewall precedes the atomic public hard cut" + reason = "the output-profile firewall is intentionally internal to registered profiles" )] pub(crate) mod output_projection; -#[doc(hidden)] -pub mod package_bridge; #[cfg_attr( not(test), expect( dead_code, - reason = "private C8d full-support recheck is production-compiled before its package bridge exists" + reason = "the full-support recheck remains a private verified engine" ) )] pub(crate) mod point_support; +#[deny(missing_docs)] +pub mod program; #[cfg_attr( not(test), expect( dead_code, - reason = "private Program compiler/lowering precedes its direct sole-Session bridge" + reason = "generic Program machinery is exposed only through the concrete public module" ) )] pub(crate) mod program_session; @@ -58,7 +58,7 @@ pub(crate) mod program_session; not(test), expect( dead_code, - reason = "private F0 release registry precedes the atomic public hard cut" + reason = "release-registry internals are projected only through typed public evidence" ) )] pub(crate) mod release_registry; @@ -116,7 +116,7 @@ mod generic_boundary_tests; not(test), expect( dead_code, - reason = "private F2 raw admission is production-compiled before its package bridge exists" + reason = "raw observation ownership is exposed only through the public Program session" ) )] pub(crate) mod observation; @@ -131,7 +131,7 @@ mod point_support_tests; not(test), expect( dead_code, - reason = "private F2 Session is production-compiled before C8c package integration exists" + reason = "the generic Session engine is exposed only through the public Program owner" ) )] pub(crate) mod session; @@ -143,7 +143,7 @@ mod session_tests; not(test), expect( dead_code, - reason = "private V2a joint selection is production-compiled before a public Program exists" + reason = "joint-selection internals are exposed only through the public Program contract" ) )] pub(crate) mod joint; @@ -359,8 +359,73 @@ pub struct NoHybridLpcSurfaceMetric; #[cfg(doctest)] pub struct NoPrematureScalarLpcApi; -/// C8d recheck and F2 observation remain one private Session-owned protocol; -/// they do not create a second public authoring root before C7c. +/// Публичный Program API живёт только в одноимённом модуле и не сохраняет +/// транспортный префикс, старый путь или корневые реэкспорты. +/// +/// ``` +/// use labcolors_core::{Srgb8, program}; +/// +/// let source = program::SourceIdV1::new(1); +/// let target = program::TargetIdV1::new(2); +/// let input = program::SurfaceInputPortIdV1::new(3); +/// let paint = program::PaintIdV1::new(4); +/// let surface = program::SurfaceIdV1::new(5); +/// let occurrence = program::OccurrenceIdV1::new(6); +/// let constraint = program::ConstraintIdV1::new(7); +/// let output = program::OutputSlotIdV1::new(8); +/// let context = program::AppearanceContextV1::try_new( +/// 64.0, +/// 0.2, +/// program::SurroundV1::Average, +/// ).unwrap(); +/// let mut draft = program::DraftV1::new(); +/// +/// draft.push_source(source, Srgb8::new([0, 0, 0])); +/// draft.push_fixed_target(target, source); +/// draft.push_surface_input_port(input); +/// draft.push_solid_paint(paint, target); +/// draft.push_input_surface(surface, input); +/// draft.push_source_over_occurrence(occurrence, paint, surface, context); +/// draft.push_exact_hard(constraint, occurrence, Srgb8::new([0, 0, 0])); +/// draft.push_output(output, paint); +/// +/// let owner = draft.compile().unwrap(); +/// let mut session = owner.instantiate(1).unwrap(); +/// let white = [Srgb8::new([255, 255, 255])]; +/// let scenarios = [program::ScenarioV1::new(1, &white)]; +/// let projection = owner.update( +/// &mut session, +/// program::UpdateV1::Observed { +/// revision: 1, +/// scenarios: &scenarios, +/// }, +/// ).unwrap(); +/// assert!(matches!( +/// projection.operations().next(), +/// Some(program::OperationV1::Set(_)), +/// )); +/// ``` +/// +/// ```compile_fail +/// use labcolors_core::package_bridge; +/// ``` +/// +/// ```compile_fail +/// use labcolors_core::package_bridge::PackageProgramDraftV1; +/// ``` +/// +/// ```compile_fail +/// use labcolors_core::program::PackageProgramDraftV1; +/// ``` +/// +/// ```compile_fail +/// use labcolors_core::DraftV1; +/// ``` +#[cfg(doctest)] +pub struct ProgramApiBoundary; + +/// C8d recheck и F2 observation остаются деталями одной приватной Session; +/// они не могут стать дополнительными public authoring/runtime roots. /// /// ```compile_fail /// use labcolors_core::point_support::CompiledPointSupportRecheckV1; diff --git a/crates/labcolors-core/src/observation.rs b/crates/labcolors-core/src/observation.rs index 7dc0ae56..686d4c5f 100644 --- a/crates/labcolors-core/src/observation.rs +++ b/crates/labcolors-core/src/observation.rs @@ -165,6 +165,22 @@ impl ObservationSchemaMismatchV1 { actual, } } + + pub(crate) const fn into_parts( + self, + ) -> ( + usize, + usize, + Option, + Option, + ) { + ( + self.case_index, + self.binding_index, + self.expected, + self.actual, + ) + } } /// Canonical nonempty correlated set. Values and provenance each use one flat diff --git a/crates/labcolors-core/src/package_bridge.rs b/crates/labcolors-core/src/package_bridge.rs deleted file mode 100644 index 42330136..00000000 --- a/crates/labcolors-core/src/package_bridge.rs +++ /dev/null @@ -1,2723 +0,0 @@ -//! Sole concrete package authoring and runtime seam for a Core Program. -//! -//! The cold path appends typed physical declarations directly to the real Core -//! Program IR and compiles it with the code-owned evaluator union. The hot path -//! supplies schema-ordered physical scenarios and receives a borrowed, -//! allocation-free projection of Core-owned state and evidence. Neither path -//! exposes evaluator traits, generic Session plans, client vocabulary, -//! transport words, strings, or lifecycle generations. - -use core::iter::FusedIterator; -use core::marker::PhantomData; -use core::slice; - -use crate::Srgb8; -use crate::appearance::{OccurrenceId, OpacityInputId, PaintId, SurfaceId, SurfaceInputPortId}; -use crate::composition::CompositionProfileV1; -use crate::constraints::{ - ExactSrgb8IdentityV1, ProgramVisiblePointBindingV1, ProgramVisiblePointPassEvidence, - ProgramVisiblePointViolationEvidence, Wcag22Srgb8V1, -}; -use crate::joint::FiniteJointOrderErrorV1; -use crate::lcs_occurrence::{ - AdaptingLuminanceCdM2, AdmittedSrgb8TristimulusBindingV1, AppearanceContextDomainErrorV1, - AppearanceContextFieldV1, AppearanceContextId, AppearanceContextSchemaReleaseId, - BackgroundLuminanceRatio, ColorSignal, ColorSignalViewV1, IEC_SRGB_D65_XYZ_FRAME_V1, - NumericDomainError, SurroundProfileId, -}; -use crate::numerics::NumericalDecisionEvidenceV1; -use crate::observation::{ - ObservationError, ObservationHeadViewV1, ObservationStreamId, Revision, ScenarioId, - SchemaOrderedScenarioSourceV1, UnknownReasonId, -}; -use crate::program_session::{ - CompiledCoreProgramV1, CompositionProfile, ConstraintId, ConstraintInvocation, - CoreProgramConstraintInvocationV1, CoreProgramDraftErrorV1, CoreProgramDraftV1, - CoreProgramEvaluatorErrorV1, CoreProgramEvaluatorsV1, CoreProgramPassEvidenceV1, - CoreProgramViolationEvidenceV1, DeclaredJointSelectionV1, JointCandidateStateV1, Occurrence, - OpacityInput, OutputBinding, OutputSlotId, Paint, ProgramCompileError, ProgramConflictV1, - ProgramConstraintCellV1, ProgramConstraintResultV1, ProgramContentIdentityV1, ProgramOutputV1, - ProgramSessionEvaluationError, ProgramSessionInstantiateError, ProgramSessionPlan, - ProgramVerifiedV1, Source, SourceId, Surface, Target, TargetCandidateChoiceV1, - TargetCandidateId, TargetCandidateV1, TargetId, -}; -use crate::session::{Session, SessionState, SessionUpdateError}; -use crate::wcag22::{Wcag22CriterionV1, Wcag22LuminanceBoundsQ55V1, Wcag22ProfileIdV1}; - -type CoreVerifiedV1 = ProgramVerifiedV1; -type CoreConflictV1 = ProgramConflictV1; -type CoreProgramPlanV1 = ProgramSessionPlan; -type CoreProgramSessionV1 = Session; -type CoreProgramStateV1 = SessionState; -type CoreProgramPlanErrorV1 = ProgramSessionEvaluationError; -type CoreProgramConstraintCellV1 = ProgramConstraintCellV1; -type CoreExactPassEvidenceV1 = ProgramVisiblePointPassEvidence; -type CoreExactViolationEvidenceV1 = ProgramVisiblePointViolationEvidence; -type CoreWcag22PassEvidenceV1 = ProgramVisiblePointPassEvidence; -type CoreWcag22ViolationEvidenceV1 = ProgramVisiblePointViolationEvidence; - -macro_rules! package_program_id { - ($name:ident, $core:ty) => { - #[repr(transparent)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - #[must_use] - pub struct $name($core); - - impl $name { - pub const fn new(value: u32) -> Self { - Self(<$core>::new(value)) - } - - pub const fn value(self) -> u32 { - self.0.value() - } - - const fn from_core(value: $core) -> Self { - Self(value) - } - - const fn into_core(self) -> $core { - self.0 - } - } - - impl core::hash::Hash for $name { - fn hash(&self, state: &mut H) { - core::hash::Hash::hash(&self.value(), state); - } - } - }; -} - -macro_rules! package_program_projected_id { - ($name:ident, $core:ty) => { - #[repr(transparent)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - #[must_use] - pub struct $name($core); - - impl $name { - const fn from_core(value: $core) -> Self { - Self(value) - } - - pub const fn value(self) -> u32 { - self.0.value() - } - } - - impl core::hash::Hash for $name { - fn hash(&self, state: &mut H) { - core::hash::Hash::hash(&self.value(), state); - } - } - }; -} - -package_program_id!(PackageProgramSourceIdV1, SourceId); -package_program_id!(PackageProgramTargetIdV1, TargetId); -package_program_id!(PackageProgramTargetCandidateIdV1, TargetCandidateId); -package_program_id!(PackageProgramOpacityInputIdV1, OpacityInputId); -package_program_id!(PackageProgramPaintIdV1, PaintId); -package_program_id!(PackageProgramSurfaceInputPortIdV1, SurfaceInputPortId); -package_program_id!(PackageProgramSurfaceIdV1, SurfaceId); -package_program_id!(PackageProgramOccurrenceIdV1, OccurrenceId); -package_program_id!(PackageProgramConstraintIdV1, ConstraintId); -package_program_id!(PackageProgramOutputSlotIdV1, OutputSlotId); -package_program_projected_id!(PackageProgramStreamIdV1, ObservationStreamId); -package_program_projected_id!(PackageProgramScenarioIdV1, ScenarioId); - -/// One finite candidate, stored as the actual Core target-candidate IR node. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PackageProgramTargetCandidateV1(TargetCandidateV1); - -impl PackageProgramTargetCandidateV1 { - pub const fn new(id: PackageProgramTargetCandidateIdV1, source: Srgb8) -> Self { - Self(TargetCandidateV1::from_srgb8(id.into_core(), source)) - } -} - -/// One typed target/candidate choice stored as the actual Core joint IR node. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PackageProgramJointChoiceV1(TargetCandidateChoiceV1); - -impl PackageProgramJointChoiceV1 { - pub const fn new( - target: PackageProgramTargetIdV1, - candidate: PackageProgramTargetCandidateIdV1, - ) -> Self { - Self(TargetCandidateChoiceV1::new( - target.into_core(), - candidate.into_core(), - )) - } -} - -/// One complete explicit state in the finite joint order. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PackageProgramJointStateV1(JointCandidateStateV1); - -impl PackageProgramJointStateV1 { - pub fn new(choices: Vec) -> Self { - Self(JointCandidateStateV1::new( - choices.into_iter().map(|choice| choice.0).collect(), - )) - } -} - -/// Registered surround input for the current CIECAM16 context release. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramSurroundV1 { - Average, - Dim, - Dark, -} - -impl PackageProgramSurroundV1 { - const fn into_core(self) -> SurroundProfileId { - match self { - Self::Average => SurroundProfileId::AverageV1, - Self::Dim => SurroundProfileId::DimV1, - Self::Dark => SurroundProfileId::DarkV1, - } - } -} - -/// Exact semantic input field rejected while forming an appearance context. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramAppearanceContextFieldV1 { - AdaptingLuminanceCdM2, - BackgroundLuminanceRatioYbYw, -} - -/// Exact numeric reason rejected while forming an appearance context. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramNumericDomainErrorV1 { - NonFinite, - Negative, - NotPositive, - AboveOne, -} - -/// Closed appearance-context admission failure. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramAppearanceContextErrorKindV1 { - Domain, - InternalInvariant, -} - -/// Closed appearance-context admission failure. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PackageProgramAppearanceContextErrorV1 { - kind: PackageProgramAppearanceContextErrorKindV1, - field: Option, - reason: Option, -} - -impl PackageProgramAppearanceContextErrorV1 { - pub const fn kind(self) -> PackageProgramAppearanceContextErrorKindV1 { - self.kind - } - - pub const fn field(self) -> Option { - self.field - } - - pub const fn reason(self) -> Option { - self.reason - } - - fn from_core(error: AppearanceContextDomainErrorV1) -> Self { - let field = match error.field() { - AppearanceContextFieldV1::AdaptingLuminanceCdM2 => { - PackageProgramAppearanceContextFieldV1::AdaptingLuminanceCdM2 - } - AppearanceContextFieldV1::BackgroundLuminanceRatio => { - PackageProgramAppearanceContextFieldV1::BackgroundLuminanceRatioYbYw - } - }; - let reason = match error.reason() { - NumericDomainError::NonFinite => Some(PackageProgramNumericDomainErrorV1::NonFinite), - NumericDomainError::Negative => Some(PackageProgramNumericDomainErrorV1::Negative), - NumericDomainError::NotPositive => { - Some(PackageProgramNumericDomainErrorV1::NotPositive) - } - NumericDomainError::AboveOne => Some(PackageProgramNumericDomainErrorV1::AboveOne), - NumericDomainError::HueOutOfRange => None, - }; - match reason { - Some(reason) => Self { - kind: PackageProgramAppearanceContextErrorKindV1::Domain, - field: Some(field), - reason: Some(reason), - }, - None => Self { - kind: PackageProgramAppearanceContextErrorKindV1::InternalInvariant, - field: None, - reason: None, - }, - } - } -} - -/// Immutable admitted appearance context stored as the actual Core value. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PackageProgramAppearanceContextV1(AppearanceContextId); - -impl PackageProgramAppearanceContextV1 { - /// Admit the explicit CIECAM16 viewing inputs for encoded sRGB8/D65. - /// `background_luminance_ratio_yb_yw` is the dimensionless ratio `Y_b/Y_w` - /// and must be finite in `(0, 1]`; it is not an absolute luminance. - pub fn try_new( - adapting_luminance_cd_m2: f64, - background_luminance_ratio_yb_yw: f64, - surround: PackageProgramSurroundV1, - ) -> Result { - let adapting_luminance_cd_m2 = AdaptingLuminanceCdM2::try_new(adapting_luminance_cd_m2) - .map_err(PackageProgramAppearanceContextErrorV1::from_core)?; - let background_luminance_ratio = - BackgroundLuminanceRatio::try_new(background_luminance_ratio_yb_yw) - .map_err(PackageProgramAppearanceContextErrorV1::from_core)?; - Ok(Self(AppearanceContextId::from_inputs( - AppearanceContextSchemaReleaseId::Ciecam16ViewingInputsV1, - IEC_SRGB_D65_XYZ_FRAME_V1, - adapting_luminance_cd_m2, - background_luminance_ratio, - surround.into_core(), - ))) - } - - pub fn adapting_luminance_cd_m2(self) -> f64 { - self.0.adapting_luminance_cd_m2() - } - - pub fn background_luminance_ratio_yb_yw(self) -> f64 { - self.0.background_luminance_ratio() - } - - pub const fn surround(self) -> PackageProgramSurroundV1 { - match self.0.surround_profile() { - SurroundProfileId::AverageV1 => PackageProgramSurroundV1::Average, - SurroundProfileId::DimV1 => PackageProgramSurroundV1::Dim, - SurroundProfileId::DarkV1 => PackageProgramSurroundV1::Dark, - } - } -} - -/// Closed compile classification; the generic Core error never escapes. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramCompileErrorKindV1 { - DuplicateSource, - DuplicateTarget, - DuplicateTargetCandidate, - DuplicateTargetCandidateSignal, - DuplicateOpacityInput, - DuplicateSurfaceInputPort, - UnusedSurfaceInputPort, - DuplicateSurfaceInputBinding, - DuplicatePaint, - DuplicateSurface, - DuplicateOccurrence, - DuplicateConstraint, - DuplicateOutputSlot, - MissingTargetSource, - MissingPaintTarget, - MissingPaintSource, - MissingPaintOpacityInput, - MissingSurfaceInputPort, - MissingSurfaceOccurrence, - MissingOccurrencePaint, - MissingOccurrenceBackdrop, - MissingConstraintOccurrence, - MissingOutputPaint, - PaintCycle, - RenderCycle, - OpacityOutOfDomain, - EmptyTargetDomain, - UnconstrainedTarget, - DisconnectedFiniteTargets, - UnassessedOutput, - MissingJointSelection, - JointSelectionWithoutTargets, - JointStateDuplicateTarget, - JointStateMissingTarget, - JointStateUnknownTarget, - JointStateUnknownCandidate, - InvalidJointOrder, - EmptySurfaceInputPortSet, - EmptyOccurrenceSet, - EmptyConstraintSet, - EmptyOutputSet, - ResourceExhausted, - InternalInvariant, -} - -/// Typed offending identity when one error has a single attributable handle. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramCompileErrorHandleV1 { - Source(PackageProgramSourceIdV1), - Target(PackageProgramTargetIdV1), - TargetCandidate(PackageProgramTargetCandidateIdV1), - OpacityInput(PackageProgramOpacityInputIdV1), - Paint(PackageProgramPaintIdV1), - SurfaceInputPort(PackageProgramSurfaceInputPortIdV1), - Surface(PackageProgramSurfaceIdV1), - Occurrence(PackageProgramOccurrenceIdV1), - Constraint(PackageProgramConstraintIdV1), - OutputSlot(PackageProgramOutputSlotIdV1), -} - -impl PackageProgramCompileErrorHandleV1 { - pub const fn value(self) -> u32 { - match self { - Self::Source(value) => value.value(), - Self::Target(value) => value.value(), - Self::TargetCandidate(value) => value.value(), - Self::OpacityInput(value) => value.value(), - Self::Paint(value) => value.value(), - Self::SurfaceInputPort(value) => value.value(), - Self::Surface(value) => value.value(), - Self::Occurrence(value) => value.value(), - Self::Constraint(value) => value.value(), - Self::OutputSlot(value) => value.value(), - } - } -} - -/// Exact owned members of one paint dependency cycle. -/// -/// The wrapper takes ownership of Core's existing allocation. Projecting a -/// compile failure therefore cannot introduce a second infallible allocation. -#[derive(Debug, PartialEq, Eq)] -pub struct PackageProgramPaintCycleV1 { - paints: Vec, -} - -impl PackageProgramPaintCycleV1 { - pub fn paints(&self) -> impl ExactSizeIterator + '_ { - self.paints - .iter() - .copied() - .map(PackageProgramPaintIdV1::from_core) - } -} - -/// Exact owned members of one render dependency cycle. -/// -/// Surface and occurrence identities remain separate physical namespaces. -#[derive(Debug, PartialEq, Eq)] -pub struct PackageProgramRenderCycleV1 { - surfaces: Vec, - occurrences: Vec, -} - -impl PackageProgramRenderCycleV1 { - pub fn surfaces(&self) -> impl ExactSizeIterator + '_ { - self.surfaces - .iter() - .copied() - .map(PackageProgramSurfaceIdV1::from_core) - } - - pub fn occurrences(&self) -> impl ExactSizeIterator + '_ { - self.occurrences - .iter() - .copied() - .map(PackageProgramOccurrenceIdV1::from_core) - } -} - -/// Exact closed reason why an explicit finite joint order was rejected. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramJointOrderErrorV1 { - EmptyDomain { - dimension: usize, - }, - CardinalityOverflow, - EmptyOrder, - TupleArity { - state: usize, - expected: usize, - actual: usize, - }, - OrdinalOutOfDomain { - state: usize, - dimension: usize, - ordinal: usize, - domain_len: usize, - }, - DuplicateTuple { - first_state: usize, - duplicate_state: usize, - }, - IncompleteOrder { - expected: usize, - actual: usize, - }, - ResourceExhausted, -} - -/// Atomic, lossless authored-program compile failure. -/// -/// This public enum is authoritative. `kind`, `primary_handle`, and -/// `related_handle` are convenience projections only; they never replace the -/// complete typed payload carried by the matching variant. -#[derive(Debug, PartialEq, Eq)] -pub enum PackageProgramCompileErrorV1 { - DuplicateSource { - source: PackageProgramSourceIdV1, - }, - DuplicateTarget { - target: PackageProgramTargetIdV1, - }, - MissingTargetSource { - target: PackageProgramTargetIdV1, - source: PackageProgramSourceIdV1, - }, - DuplicateOpacityInput { - input: PackageProgramOpacityInputIdV1, - }, - DuplicateSurfaceInputPort { - input: PackageProgramSurfaceInputPortIdV1, - }, - UnusedSurfaceInputPort { - input: PackageProgramSurfaceInputPortIdV1, - }, - DuplicateSurfaceInputBinding { - input: PackageProgramSurfaceInputPortIdV1, - first: PackageProgramSurfaceIdV1, - duplicate: PackageProgramSurfaceIdV1, - }, - DuplicatePaint { - paint: PackageProgramPaintIdV1, - }, - DuplicateSurface { - surface: PackageProgramSurfaceIdV1, - }, - DuplicateOccurrence { - occurrence: PackageProgramOccurrenceIdV1, - }, - MissingPaintTarget { - paint: PackageProgramPaintIdV1, - target: PackageProgramTargetIdV1, - }, - MissingPaintSource { - paint: PackageProgramPaintIdV1, - source: PackageProgramPaintIdV1, - }, - MissingPaintOpacityInput { - paint: PackageProgramPaintIdV1, - input: PackageProgramOpacityInputIdV1, - }, - MissingSurfaceInputPort { - surface: PackageProgramSurfaceIdV1, - input: PackageProgramSurfaceInputPortIdV1, - }, - MissingSurfaceOccurrence { - surface: PackageProgramSurfaceIdV1, - occurrence: PackageProgramOccurrenceIdV1, - }, - MissingOccurrencePaint { - occurrence: PackageProgramOccurrenceIdV1, - paint: PackageProgramPaintIdV1, - }, - MissingOccurrenceBackdrop { - occurrence: PackageProgramOccurrenceIdV1, - surface: PackageProgramSurfaceIdV1, - }, - PaintCycle(PackageProgramPaintCycleV1), - RenderCycle(PackageProgramRenderCycleV1), - OpacityOutOfDomain { - input: PackageProgramOpacityInputIdV1, - }, - EmptyTargetDomain { - target: PackageProgramTargetIdV1, - }, - DuplicateTargetCandidate { - target: PackageProgramTargetIdV1, - candidate: PackageProgramTargetCandidateIdV1, - }, - DuplicateTargetCandidateSignal { - target: PackageProgramTargetIdV1, - first: PackageProgramTargetCandidateIdV1, - duplicate: PackageProgramTargetCandidateIdV1, - encoded_srgb8: Srgb8, - }, - UnconstrainedTarget { - target: PackageProgramTargetIdV1, - }, - DisconnectedFiniteTargets, - UnassessedOutput { - output: PackageProgramOutputSlotIdV1, - paint: PackageProgramPaintIdV1, - }, - MissingJointSelection, - JointSelectionWithoutTargets, - JointStateDuplicateTarget { - state: usize, - target: PackageProgramTargetIdV1, - }, - JointStateMissingTarget { - state: usize, - target: PackageProgramTargetIdV1, - }, - JointStateUnknownTarget { - state: usize, - target: PackageProgramTargetIdV1, - }, - JointStateUnknownCandidate { - state: usize, - target: PackageProgramTargetIdV1, - candidate: PackageProgramTargetCandidateIdV1, - }, - InvalidJointOrder(PackageProgramJointOrderErrorV1), - /// The package contract has one code-owned atomic observation group; - /// authored input ports are its complete, canonical membership. - EmptySurfaceInputPortSet, - EmptyOccurrenceSet, - EmptyConstraintSet, - EmptyOutputSet, - DuplicateConstraint { - constraint: PackageProgramConstraintIdV1, - }, - MissingConstraintOccurrence { - constraint: PackageProgramConstraintIdV1, - occurrence: PackageProgramOccurrenceIdV1, - }, - DuplicateOutputSlot { - output: PackageProgramOutputSlotIdV1, - }, - MissingOutputPaint { - output: PackageProgramOutputSlotIdV1, - paint: PackageProgramPaintIdV1, - }, - ResourceExhausted, - InternalInvariant, -} - -impl PackageProgramCompileErrorV1 { - pub const fn kind(&self) -> PackageProgramCompileErrorKindV1 { - use PackageProgramCompileErrorKindV1 as Kind; - - match self { - Self::DuplicateSource { .. } => Kind::DuplicateSource, - Self::DuplicateTarget { .. } => Kind::DuplicateTarget, - Self::MissingTargetSource { .. } => Kind::MissingTargetSource, - Self::DuplicateOpacityInput { .. } => Kind::DuplicateOpacityInput, - Self::DuplicateSurfaceInputPort { .. } => Kind::DuplicateSurfaceInputPort, - Self::UnusedSurfaceInputPort { .. } => Kind::UnusedSurfaceInputPort, - Self::DuplicateSurfaceInputBinding { .. } => Kind::DuplicateSurfaceInputBinding, - Self::DuplicatePaint { .. } => Kind::DuplicatePaint, - Self::DuplicateSurface { .. } => Kind::DuplicateSurface, - Self::DuplicateOccurrence { .. } => Kind::DuplicateOccurrence, - Self::MissingPaintTarget { .. } => Kind::MissingPaintTarget, - Self::MissingPaintSource { .. } => Kind::MissingPaintSource, - Self::MissingPaintOpacityInput { .. } => Kind::MissingPaintOpacityInput, - Self::MissingSurfaceInputPort { .. } => Kind::MissingSurfaceInputPort, - Self::MissingSurfaceOccurrence { .. } => Kind::MissingSurfaceOccurrence, - Self::MissingOccurrencePaint { .. } => Kind::MissingOccurrencePaint, - Self::MissingOccurrenceBackdrop { .. } => Kind::MissingOccurrenceBackdrop, - Self::PaintCycle(_) => Kind::PaintCycle, - Self::RenderCycle(_) => Kind::RenderCycle, - Self::OpacityOutOfDomain { .. } => Kind::OpacityOutOfDomain, - Self::EmptyTargetDomain { .. } => Kind::EmptyTargetDomain, - Self::DuplicateTargetCandidate { .. } => Kind::DuplicateTargetCandidate, - Self::DuplicateTargetCandidateSignal { .. } => Kind::DuplicateTargetCandidateSignal, - Self::UnconstrainedTarget { .. } => Kind::UnconstrainedTarget, - Self::DisconnectedFiniteTargets => Kind::DisconnectedFiniteTargets, - Self::UnassessedOutput { .. } => Kind::UnassessedOutput, - Self::MissingJointSelection => Kind::MissingJointSelection, - Self::JointSelectionWithoutTargets => Kind::JointSelectionWithoutTargets, - Self::JointStateDuplicateTarget { .. } => Kind::JointStateDuplicateTarget, - Self::JointStateMissingTarget { .. } => Kind::JointStateMissingTarget, - Self::JointStateUnknownTarget { .. } => Kind::JointStateUnknownTarget, - Self::JointStateUnknownCandidate { .. } => Kind::JointStateUnknownCandidate, - Self::InvalidJointOrder(_) => Kind::InvalidJointOrder, - Self::EmptySurfaceInputPortSet => Kind::EmptySurfaceInputPortSet, - Self::EmptyOccurrenceSet => Kind::EmptyOccurrenceSet, - Self::EmptyConstraintSet => Kind::EmptyConstraintSet, - Self::EmptyOutputSet => Kind::EmptyOutputSet, - Self::DuplicateConstraint { .. } => Kind::DuplicateConstraint, - Self::MissingConstraintOccurrence { .. } => Kind::MissingConstraintOccurrence, - Self::DuplicateOutputSlot { .. } => Kind::DuplicateOutputSlot, - Self::MissingOutputPaint { .. } => Kind::MissingOutputPaint, - Self::ResourceExhausted => Kind::ResourceExhausted, - Self::InternalInvariant => Kind::InternalInvariant, - } - } - - pub const fn primary_handle(&self) -> Option { - use PackageProgramCompileErrorHandleV1 as Handle; - - match self { - Self::DuplicateSource { source } => Some(Handle::Source(*source)), - Self::DuplicateTarget { target } - | Self::EmptyTargetDomain { target } - | Self::UnconstrainedTarget { target } - | Self::JointStateDuplicateTarget { target, .. } - | Self::JointStateMissingTarget { target, .. } - | Self::JointStateUnknownTarget { target, .. } - | Self::JointStateUnknownCandidate { target, .. } => Some(Handle::Target(*target)), - Self::MissingTargetSource { target, .. } - | Self::DuplicateTargetCandidate { target, .. } - | Self::DuplicateTargetCandidateSignal { target, .. } => Some(Handle::Target(*target)), - Self::DuplicateOpacityInput { input } | Self::OpacityOutOfDomain { input } => { - Some(Handle::OpacityInput(*input)) - } - Self::DuplicateSurfaceInputPort { input } - | Self::UnusedSurfaceInputPort { input } - | Self::DuplicateSurfaceInputBinding { input, .. } => { - Some(Handle::SurfaceInputPort(*input)) - } - Self::DuplicatePaint { paint } - | Self::MissingPaintTarget { paint, .. } - | Self::MissingPaintSource { paint, .. } - | Self::MissingPaintOpacityInput { paint, .. } => Some(Handle::Paint(*paint)), - Self::DuplicateSurface { surface } - | Self::MissingSurfaceInputPort { surface, .. } - | Self::MissingSurfaceOccurrence { surface, .. } => Some(Handle::Surface(*surface)), - Self::DuplicateOccurrence { occurrence } - | Self::MissingOccurrencePaint { occurrence, .. } - | Self::MissingOccurrenceBackdrop { occurrence, .. } => { - Some(Handle::Occurrence(*occurrence)) - } - Self::UnassessedOutput { output, .. } - | Self::DuplicateOutputSlot { output } - | Self::MissingOutputPaint { output, .. } => Some(Handle::OutputSlot(*output)), - Self::DuplicateConstraint { constraint } - | Self::MissingConstraintOccurrence { constraint, .. } => { - Some(Handle::Constraint(*constraint)) - } - Self::PaintCycle(_) - | Self::RenderCycle(_) - | Self::DisconnectedFiniteTargets - | Self::MissingJointSelection - | Self::JointSelectionWithoutTargets - | Self::InvalidJointOrder(_) - | Self::EmptySurfaceInputPortSet - | Self::EmptyOccurrenceSet - | Self::EmptyConstraintSet - | Self::EmptyOutputSet - | Self::ResourceExhausted - | Self::InternalInvariant => None, - } - } - - pub const fn related_handle(&self) -> Option { - use PackageProgramCompileErrorHandleV1 as Handle; - - match self { - Self::MissingTargetSource { source, .. } => Some(Handle::Source(*source)), - Self::DuplicateSurfaceInputBinding { duplicate, .. } => { - Some(Handle::Surface(*duplicate)) - } - Self::MissingPaintTarget { target, .. } => Some(Handle::Target(*target)), - Self::MissingPaintSource { source, .. } => Some(Handle::Paint(*source)), - Self::MissingPaintOpacityInput { input, .. } => Some(Handle::OpacityInput(*input)), - Self::MissingSurfaceInputPort { input, .. } => Some(Handle::SurfaceInputPort(*input)), - Self::MissingSurfaceOccurrence { occurrence, .. } - | Self::MissingConstraintOccurrence { occurrence, .. } => { - Some(Handle::Occurrence(*occurrence)) - } - Self::MissingOccurrencePaint { paint, .. } - | Self::UnassessedOutput { paint, .. } - | Self::MissingOutputPaint { paint, .. } => Some(Handle::Paint(*paint)), - Self::MissingOccurrenceBackdrop { surface, .. } => Some(Handle::Surface(*surface)), - Self::DuplicateTargetCandidate { candidate, .. } - | Self::DuplicateTargetCandidateSignal { - duplicate: candidate, - .. - } - | Self::JointStateUnknownCandidate { candidate, .. } => { - Some(Handle::TargetCandidate(*candidate)) - } - Self::DuplicateSource { .. } - | Self::DuplicateTarget { .. } - | Self::DuplicateOpacityInput { .. } - | Self::DuplicateSurfaceInputPort { .. } - | Self::UnusedSurfaceInputPort { .. } - | Self::DuplicatePaint { .. } - | Self::DuplicateSurface { .. } - | Self::DuplicateOccurrence { .. } - | Self::PaintCycle(_) - | Self::RenderCycle(_) - | Self::OpacityOutOfDomain { .. } - | Self::EmptyTargetDomain { .. } - | Self::UnconstrainedTarget { .. } - | Self::DisconnectedFiniteTargets - | Self::MissingJointSelection - | Self::JointSelectionWithoutTargets - | Self::JointStateDuplicateTarget { .. } - | Self::JointStateMissingTarget { .. } - | Self::JointStateUnknownTarget { .. } - | Self::InvalidJointOrder(_) - | Self::EmptySurfaceInputPortSet - | Self::EmptyOccurrenceSet - | Self::EmptyConstraintSet - | Self::EmptyOutputSet - | Self::DuplicateConstraint { .. } - | Self::DuplicateOutputSlot { .. } - | Self::ResourceExhausted - | Self::InternalInvariant => None, - } - } -} - -/// Concrete cold-path builder over the actual Core Program IR. -#[must_use] -pub struct PackageProgramDraftV1 { - inner: CoreProgramDraftV1, -} - -/// Draft mutation rejected before Core compilation. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramDraftErrorV1 { - JointSelectionAlreadyDeclared, -} - -impl PackageProgramDraftV1 { - pub fn new() -> Self { - Self { - inner: CoreProgramDraftV1::new(), - } - } - - pub fn push_source(&mut self, id: PackageProgramSourceIdV1, source: Srgb8) -> &mut Self { - self.inner - .push_source(Source::new(id.into_core(), ColorSignal::from_srgb8(source))); - self - } - - pub fn push_fixed_target( - &mut self, - id: PackageProgramTargetIdV1, - source: PackageProgramSourceIdV1, - ) -> &mut Self { - self.inner - .push_target(Target::fixed(id.into_core(), source.into_core())); - self - } - - pub fn push_finite_target( - &mut self, - id: PackageProgramTargetIdV1, - source: PackageProgramSourceIdV1, - candidates: Vec, - ) -> &mut Self { - self.inner.push_target(Target::finite( - id.into_core(), - source.into_core(), - candidates - .into_iter() - .map(|candidate| candidate.0) - .collect(), - )); - self - } - - pub fn set_joint_selection( - &mut self, - states: Vec, - ) -> Result<&mut Self, PackageProgramDraftErrorV1> { - self.inner - .set_joint_selection(DeclaredJointSelectionV1::new( - states.into_iter().map(|state| state.0).collect(), - )) - .map_err(|error| match error { - CoreProgramDraftErrorV1::JointSelectionAlreadyDeclared => { - PackageProgramDraftErrorV1::JointSelectionAlreadyDeclared - } - })?; - Ok(self) - } - - pub fn push_surface_input_port( - &mut self, - input: PackageProgramSurfaceInputPortIdV1, - ) -> &mut Self { - self.inner.push_surface_input_port(input.into_core()); - self - } - - pub fn push_opacity_input( - &mut self, - id: PackageProgramOpacityInputIdV1, - value: f64, - ) -> &mut Self { - self.inner - .push_opacity_input(OpacityInput::new(id.into_core(), value)); - self - } - - pub fn push_solid_paint( - &mut self, - id: PackageProgramPaintIdV1, - target: PackageProgramTargetIdV1, - ) -> &mut Self { - self.inner.push_paint(Paint::Solid { - id: id.into_core(), - target: target.into_core(), - }); - self - } - - pub fn push_opacity_paint( - &mut self, - id: PackageProgramPaintIdV1, - source: PackageProgramPaintIdV1, - opacity: PackageProgramOpacityInputIdV1, - ) -> &mut Self { - self.inner.push_paint(Paint::Opacity { - id: id.into_core(), - source: source.into_core(), - opacity: opacity.into_core(), - }); - self - } - - pub fn push_input_surface( - &mut self, - id: PackageProgramSurfaceIdV1, - input: PackageProgramSurfaceInputPortIdV1, - ) -> &mut Self { - self.inner.push_surface(Surface::Input { - id: id.into_core(), - input: input.into_core(), - }); - self - } - - pub fn push_occurrence_surface( - &mut self, - id: PackageProgramSurfaceIdV1, - occurrence: PackageProgramOccurrenceIdV1, - ) -> &mut Self { - self.inner.push_surface(Surface::FromOccurrence { - id: id.into_core(), - occurrence: occurrence.into_core(), - }); - self - } - - pub fn push_source_over_occurrence( - &mut self, - id: PackageProgramOccurrenceIdV1, - subject: PackageProgramPaintIdV1, - against: PackageProgramSurfaceIdV1, - context: PackageProgramAppearanceContextV1, - ) -> &mut Self { - self.inner.push_occurrence(Occurrence::new( - id.into_core(), - subject.into_core(), - against.into_core(), - CompositionProfile::EncodedSrgb8SourceOverV1, - context.0, - )); - self - } - - pub fn push_exact_hard( - &mut self, - id: PackageProgramConstraintIdV1, - occurrence: PackageProgramOccurrenceIdV1, - expected: Srgb8, - ) -> &mut Self { - self.inner.push_hard_constraint(ConstraintInvocation::hard( - id.into_core(), - occurrence.into_core(), - CoreProgramConstraintInvocationV1::ExactSrgb8(expected), - )); - self - } - - pub fn push_exact_report_only( - &mut self, - id: PackageProgramConstraintIdV1, - occurrence: PackageProgramOccurrenceIdV1, - expected: Srgb8, - ) -> &mut Self { - self.inner - .push_report_constraint(ConstraintInvocation::report_only( - id.into_core(), - occurrence.into_core(), - CoreProgramConstraintInvocationV1::ExactSrgb8(expected), - )); - self - } - - pub fn push_wcag22_hard( - &mut self, - id: PackageProgramConstraintIdV1, - occurrence: PackageProgramOccurrenceIdV1, - criterion: Wcag22CriterionV1, - ) -> &mut Self { - self.inner.push_hard_constraint(ConstraintInvocation::hard( - id.into_core(), - occurrence.into_core(), - CoreProgramConstraintInvocationV1::Wcag22Srgb8(criterion), - )); - self - } - - pub fn push_wcag22_report_only( - &mut self, - id: PackageProgramConstraintIdV1, - occurrence: PackageProgramOccurrenceIdV1, - criterion: Wcag22CriterionV1, - ) -> &mut Self { - self.inner - .push_report_constraint(ConstraintInvocation::report_only( - id.into_core(), - occurrence.into_core(), - CoreProgramConstraintInvocationV1::Wcag22Srgb8(criterion), - )); - self - } - - pub fn push_output( - &mut self, - output: PackageProgramOutputSlotIdV1, - paint: PackageProgramPaintIdV1, - ) -> &mut Self { - self.inner - .push_output(OutputBinding::new(output.into_core(), paint.into_core())); - self - } - - pub fn compile(self) -> Result { - let compiled = self.inner.compile().map_err(map_program_compile_error)?; - Ok(PackageProgramOwnerV1::from_compiled(compiled)) - } -} - -impl Default for PackageProgramDraftV1 { - fn default() -> Self { - Self::new() - } -} - -/// Opaque strong owner of one exact compiled Core Program. -/// -/// Sessions instantiated from this owner are independently mutable only -/// through this exact allocation. Dropping it revokes updates and operation -/// projections; Session-owned historical evidence remains readable. -pub struct PackageProgramOwnerV1 { - compiled: CompiledCoreProgramV1, -} - -/// A Session belongs to a different immutable owner allocation. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramAccessErrorV1 { - OwnerMismatch, -} - -impl PackageProgramOwnerV1 { - /// Internal handoff from the canonical concrete Core draft compiler. - pub(crate) const fn from_compiled(compiled: CompiledCoreProgramV1) -> Self { - Self { compiled } - } - - /// Number of schema-ordered surface values required in every scenario. - pub fn surface_input_port_count(&self) -> usize { - self.compiled.surface_input_ports().len() - } - - /// Canonically ordered authored input handles for one-time host binding. - pub fn surface_input_ports( - &self, - ) -> impl ExactSizeIterator + '_ { - self.compiled - .surface_input_ports() - .iter() - .copied() - .map(PackageProgramSurfaceInputPortIdV1::from_core) - } - - /// Canonically ordered opaque output slots owned by this Program. - pub fn output_slots(&self) -> impl ExactSizeIterator + '_ { - self.compiled - .outputs() - .map(|(slot, _paint)| PackageProgramOutputSlotIdV1::from_core(slot)) - } - - /// Borrow one operation projection only for the exact Session generation - /// instantiated by this owner. Equivalent content is not authority. - pub fn project<'owner, 'session>( - &'owner self, - session: &'session PackageProgramSessionV1, - ) -> Result, PackageProgramAccessErrorV1> { - if !self.compiled.owns_session(&session.session) { - return Err(PackageProgramAccessErrorV1::OwnerMismatch); - } - Ok(PackageProgramProjectionV1 { - evidence: session.evidence(), - owner: self, - scope: PackageProgramBorrowScopeV1::new(self, session), - }) - } - - /// Admit and commit one update only when owner and Session are the exact - /// same generation, then borrow the resulting immutable snapshot. - pub fn update<'owner, 'session>( - &'owner self, - session: &'session mut PackageProgramSessionV1, - update: PackageProgramUpdateV1<'_>, - ) -> Result, PackageProgramUpdateErrorV1> { - if !self.compiled.owns_session(&session.session) { - return Err(PackageProgramUpdateErrorV1::new( - PackageProgramUpdateErrorKindV1::OwnerMismatch, - )); - } - session.apply_update(update)?; - Ok(PackageProgramProjectionV1 { - evidence: session.evidence(), - owner: self, - scope: PackageProgramBorrowScopeV1::new(self, session), - }) - } - - /// Instantiate one stream-affine Session without exposing a generation. - pub fn instantiate( - &self, - stream_id: u32, - ) -> Result { - let stream = ObservationStreamId::new(stream_id); - let session = self - .compiled - .instantiate(stream) - .map_err(PackageProgramInstantiateErrorV1::from_core)?; - Ok(PackageProgramSessionV1 { - scenario_order_scratch: Vec::new(), - session, - }) - } -} - -/// One borrowed physical scenario in the compiled schema order. -/// -/// A scenario ID is opaque provenance. `values` contains exactly one encoded -/// sRGB8 value per compiled surface input; ports are intentionally absent from -/// the hot package boundary. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PackageProgramScenarioV1<'a> { - scenario_id: u32, - values: &'a [Srgb8], -} - -impl<'a> PackageProgramScenarioV1<'a> { - /// Construct one simultaneous physical tuple in compiled schema order. - pub const fn new(scenario_id: u32, values: &'a [Srgb8]) -> Self { - Self { - scenario_id, - values, - } - } -} - -/// One revision-bound package update. Stream ownership stays in the Session. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramUpdateV1<'a> { - /// Correlated, schema-ordered physical scenarios. - Observed { - revision: u64, - scenarios: &'a [PackageProgramScenarioV1<'a>], - }, - /// Explicitly unavailable observation; no background is invented. - Unknown { revision: u64, reason_id: u32 }, -} - -/// Concrete opaque owner of one mutable Core Program Session. -pub struct PackageProgramSessionV1 { - scenario_order_scratch: Vec, - session: CoreProgramSessionV1, -} - -impl PackageProgramSessionV1 { - /// Historical evidence remains readable after owner expiry. It never - /// grants an operation projection. - pub fn evidence(&self) -> PackageProgramEvidenceViewV1<'_> { - PackageProgramEvidenceViewV1 { - session: &self.session, - } - } - - fn apply_update( - &mut self, - update: PackageProgramUpdateV1<'_>, - ) -> Result<(), PackageProgramUpdateErrorV1> { - match update { - PackageProgramUpdateV1::Observed { - revision, - scenarios, - } => { - let source = PackageProgramScenarioSourceV1(scenarios); - self.session - .update_schema_ordered( - Revision::new(revision), - &source, - &mut self.scenario_order_scratch, - ) - .map_err(map_session_update_error)?; - } - PackageProgramUpdateV1::Unknown { - revision, - reason_id, - } => { - self.session - .update_unknown(Revision::new(revision), UnknownReasonId::new(reason_id)) - .map_err(map_session_update_error)?; - } - } - Ok(()) - } -} - -struct PackageProgramScenarioSourceV1<'a>(&'a [PackageProgramScenarioV1<'a>]); - -impl SchemaOrderedScenarioSourceV1 for PackageProgramScenarioSourceV1<'_> { - fn scenario_count(&self) -> usize { - self.0.len() - } - - fn scenario_id(&self, scenario_index: usize) -> ScenarioId { - ScenarioId::new(self.0[scenario_index].scenario_id) - } - - fn value_count(&self, scenario_index: usize) -> usize { - self.0[scenario_index].values.len() - } - - fn value(&self, scenario_index: usize, binding_index: usize) -> Srgb8 { - self.0[scenario_index].values[binding_index] - } -} - -/// Closed package-visible lifecycle classification. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramStateKindV1 { - Waiting, - Ready, - Stale, - Failed, -} - -/// Closed raw observation head, independent of evaluator lifecycle. -/// -/// A non-empty head retains stream provenance for detached correlation, never -/// as operation authority. `Empty` carries none because no observation exists. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramObservationHeadV1 { - Empty, - Unknown { - stream: PackageProgramStreamIdV1, - revision: u64, - reason_id: u32, - }, - Observed { - stream: PackageProgramStreamIdV1, - revision: u64, - }, -} - -/// Borrowed historical evidence owned solely by one Session. -#[derive(Clone, Copy)] -pub struct PackageProgramEvidenceViewV1<'a> { - session: &'a CoreProgramSessionV1, -} - -impl<'a> PackageProgramEvidenceViewV1<'a> { - const fn state(self) -> &'a CoreProgramStateV1 { - self.session.state() - } - - pub const fn kind(self) -> PackageProgramStateKindV1 { - match self.state() { - SessionState::Waiting => PackageProgramStateKindV1::Waiting, - SessionState::Ready { .. } => PackageProgramStateKindV1::Ready, - SessionState::Stale { .. } => PackageProgramStateKindV1::Stale, - SessionState::Failed { .. } => PackageProgramStateKindV1::Failed, - } - } - - pub fn observation_head(self) -> PackageProgramObservationHeadV1 { - match self.session.raw_head() { - ObservationHeadViewV1::Empty => PackageProgramObservationHeadV1::Empty, - ObservationHeadViewV1::Unknown(unknown) => PackageProgramObservationHeadV1::Unknown { - stream: PackageProgramStreamIdV1::from_core(unknown.stream()), - revision: unknown.revision().value(), - reason_id: unknown.reason().value(), - }, - ObservationHeadViewV1::Observed(observation) => { - PackageProgramObservationHeadV1::Observed { - stream: PackageProgramStreamIdV1::from_core(observation.stream()), - revision: observation.revision().value(), - } - } - } - } - - /// Failed-state cause ordinal inside [`Self::certificates`]. - pub const fn cause_certificate_index(self) -> Option { - match self.state() { - SessionState::Failed { .. } => Some(0), - SessionState::Waiting | SessionState::Ready { .. } | SessionState::Stale { .. } => None, - } - } - - /// Core-owned certificates in canonical same-call ordinal order. - pub fn certificates( - self, - ) -> impl ExactSizeIterator> + FusedIterator + 'a { - let (first, second) = match self.state() { - SessionState::Waiting => (None, None), - SessionState::Ready { current } | SessionState::Stale { previous: current } => { - (Some(PackageProgramCertificateV1::verified(current)), None) - } - SessionState::Failed { cause, previous } => ( - Some(PackageProgramCertificateV1::conflict(cause)), - previous.as_ref().map(PackageProgramCertificateV1::verified), - ), - }; - PackageProgramCertificatesV1::new(first, second) - } -} - -/// Zero-sized lifetime marker tying an operation projection to one exact live -/// owner and one immutable Session snapshot. -/// -/// This constrains borrowed Rust values; it is not a sink commit capability. -#[derive(Clone, Copy)] -struct PackageProgramBorrowScopeV1<'owner, 'session> { - _scope: PhantomData<( - &'owner PackageProgramOwnerV1, - &'session PackageProgramSessionV1, - )>, -} - -impl<'owner, 'session> PackageProgramBorrowScopeV1<'owner, 'session> { - const fn new( - _owner: &'owner PackageProgramOwnerV1, - _session: &'session PackageProgramSessionV1, - ) -> Self { - Self { - _scope: PhantomData, - } - } -} - -/// Owner-and-snapshot-validated projection. Historical evidence is available -/// separately through [`PackageProgramSessionV1::evidence`]. -#[derive(Clone, Copy)] -pub struct PackageProgramProjectionV1<'owner, 'session> { - evidence: PackageProgramEvidenceViewV1<'session>, - owner: &'owner PackageProgramOwnerV1, - scope: PackageProgramBorrowScopeV1<'owner, 'session>, -} - -impl<'owner, 'session> PackageProgramProjectionV1<'owner, 'session> { - pub const fn evidence(self) -> PackageProgramEvidenceViewV1<'session> { - self.evidence - } - - /// Total canonical output projection for this lifecycle state. - pub fn operations( - self, - ) -> impl ExactSizeIterator> + FusedIterator - { - let inner = match self.evidence.state() { - SessionState::Waiting => PackageProgramOperationSourceV1::Empty, - SessionState::Ready { current } => { - debug_assert_eq!(current.outputs().len(), self.owner.compiled.output_count()); - debug_assert!( - current - .outputs() - .iter() - .enumerate() - .all(|(index, output)| self.owner.compiled.output_slot_at(index) - == Some(output.output())) - ); - PackageProgramOperationSourceV1::Set { - outputs: current.outputs().iter(), - certificate: PackageProgramVerifiedCertificateV1 { inner: current }, - scope: self.scope, - } - } - SessionState::Stale { previous } => PackageProgramOperationSourceV1::Hold { - outputs: previous.outputs().iter(), - certificate: PackageProgramVerifiedCertificateV1 { inner: previous }, - scope: self.scope, - }, - SessionState::Failed { - previous: Some(previous), - .. - } => PackageProgramOperationSourceV1::Hold { - outputs: previous.outputs().iter(), - certificate: PackageProgramVerifiedCertificateV1 { inner: previous }, - scope: self.scope, - }, - SessionState::Failed { previous: None, .. } => { - PackageProgramOperationSourceV1::Remove { - slots: PackageProgramOwnerOutputSlotsV1::new(&self.owner.compiled), - scope: self.scope, - } - } - }; - PackageProgramOperationsV1 { inner } - } -} - -/// Collision-resistant address of the canonical physical Program content. -/// It deliberately does not identify an owner epoch or runtime authority. -#[repr(transparent)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PackageProgramContentIdentityV1([u8; 32]); - -impl PackageProgramContentIdentityV1 { - const fn from_core(value: ProgramContentIdentityV1) -> Self { - Self(*value.as_bytes()) - } - - pub const fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } -} - -/// All hard cells passed over the complete admitted physical support. -#[derive(Clone, Copy)] -pub struct PackageProgramVerifiedCertificateV1<'a> { - inner: &'a CoreVerifiedV1, -} - -impl<'a> PackageProgramVerifiedCertificateV1<'a> { - pub const fn content_identity(self) -> PackageProgramContentIdentityV1 { - PackageProgramContentIdentityV1::from_core(self.inner.report().content_identity()) - } - - pub const fn observation(self) -> PackageProgramObservationV1<'a> { - PackageProgramObservationV1 { - inner: self.inner.report().observation(), - } - } - - pub const fn selected_state_index(self) -> Option { - self.inner.selected_state_index() - } - - pub fn cells( - self, - ) -> impl ExactSizeIterator> + FusedIterator + 'a { - self.inner - .report() - .cells() - .iter() - .map(PackageProgramVerifiedCellV1::from_core) - } - - pub fn outputs( - self, - ) -> impl ExactSizeIterator> + FusedIterator + 'a - { - self.inner - .outputs() - .iter() - .map(PackageProgramCertifiedOutputV1::from_core) - } -} - -/// Exhaustive proof that every declared candidate state violates a hard cell. -#[derive(Clone, Copy)] -pub struct PackageProgramConflictCertificateV1<'a> { - inner: &'a CoreConflictV1, -} - -impl<'a> PackageProgramConflictCertificateV1<'a> { - pub const fn content_identity(self) -> PackageProgramContentIdentityV1 { - PackageProgramContentIdentityV1::from_core(self.inner.report().content_identity()) - } - - pub const fn observation(self) -> PackageProgramObservationV1<'a> { - PackageProgramObservationV1 { - inner: self.inner.report().observation(), - } - } - - pub const fn considered_state_count(self) -> usize { - self.inner.considered_state_count() - } - - pub fn cells( - self, - ) -> impl ExactSizeIterator> + FusedIterator + 'a { - self.inner - .report() - .cells() - .iter() - .map(PackageProgramConflictCellV1::from_core) - } -} - -/// Closed borrowed projection of one exact Core-owned certificate. -/// -/// A certificate borrows only Session-owned history, so it can outlive the -/// owner that authorized the projection from which it was read. -/// -/// ```no_run -/// use labcolors_core::package_bridge::{ -/// PackageProgramCertificateV1, PackageProgramOwnerV1, PackageProgramSessionV1, -/// }; -/// -/// fn retain_evidence<'session>( -/// owner: PackageProgramOwnerV1, -/// session: &'session PackageProgramSessionV1, -/// ) -> PackageProgramCertificateV1<'session> { -/// owner -/// .project(session) -/// .unwrap() -/// .evidence() -/// .certificates() -/// .next() -/// .unwrap() -/// } -/// ``` -#[derive(Clone, Copy)] -pub enum PackageProgramCertificateV1<'a> { - Verified(PackageProgramVerifiedCertificateV1<'a>), - Conflict(PackageProgramConflictCertificateV1<'a>), -} - -impl<'a> PackageProgramCertificateV1<'a> { - const fn verified(value: &'a CoreVerifiedV1) -> Self { - Self::Verified(PackageProgramVerifiedCertificateV1 { inner: value }) - } - - const fn conflict(value: &'a CoreConflictV1) -> Self { - Self::Conflict(PackageProgramConflictCertificateV1 { inner: value }) - } - - pub const fn content_identity(self) -> PackageProgramContentIdentityV1 { - match self { - Self::Verified(value) => value.content_identity(), - Self::Conflict(value) => value.content_identity(), - } - } - - pub const fn observation(self) -> PackageProgramObservationV1<'a> { - match self { - Self::Verified(value) => value.observation(), - Self::Conflict(value) => value.observation(), - } - } - - #[cfg(test)] - pub(crate) fn observation_backing_ptr_for_test(self) -> *const () { - self.observation().inner.backing_ptr_for_test() - } -} - -/// The exact revision-bound observation retained by a certificate. -#[derive(Clone, Copy)] -pub struct PackageProgramObservationV1<'a> { - inner: &'a crate::observation::RevisionBoundObservationV1, -} - -impl<'a> PackageProgramObservationV1<'a> { - pub const fn stream(self) -> PackageProgramStreamIdV1 { - PackageProgramStreamIdV1::from_core(self.inner.stream()) - } - - pub const fn revision(self) -> u64 { - self.inner.revision().value() - } - - /// Canonical schema shared by every physical case. Position `i` is the - /// identity of position `i` in each [`PackageProgramPhysicalCaseV1::values`] - /// iterator; the two exact-size iterators always have equal length. - pub fn surface_input_ports( - self, - ) -> impl ExactSizeIterator + FusedIterator + 'a - { - self.inner - .schema() - .iter() - .copied() - .map(PackageProgramSurfaceInputPortIdV1::from_core) - } - - /// Canonical unique physical value vectors. Each case is schema-ordered by - /// [`Self::surface_input_ports`]; scenario IDs remain in `provenance()`. - pub fn physical_cases( - self, - ) -> impl ExactSizeIterator> + FusedIterator + 'a { - (0..self.inner.physical_case_count()).map(move |index| PackageProgramPhysicalCaseV1 { - observation: self.inner, - index, - }) - } -} - -/// Closed encoded signal family retained in a physical observation case. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramSignalV1 { - Iec61966Srgb8D65(Srgb8), -} - -/// One canonical physical observation case and its complete provenance set. -#[derive(Clone, Copy)] -pub struct PackageProgramPhysicalCaseV1<'a> { - observation: &'a crate::observation::RevisionBoundObservationV1, - index: usize, -} - -impl<'a> PackageProgramPhysicalCaseV1<'a> { - pub fn values( - self, - ) -> impl ExactSizeIterator + FusedIterator + 'a { - self.observation - .physical_values(self.index) - .expect("package case originates from the same observation") - .iter() - .copied() - .map(|signal| match signal.view() { - ColorSignalViewV1::Iec61966Srgb8D65(value) => { - PackageProgramSignalV1::Iec61966Srgb8D65(value) - } - }) - } - - pub fn provenance( - self, - ) -> impl ExactSizeIterator + FusedIterator + 'a { - self.observation - .provenance(self.index) - .expect("package case originates from the same observation") - .iter() - .copied() - .map(PackageProgramScenarioIdV1::from_core) - } -} - -/// Whether one constraint cell gates selection or is retained for reporting. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramConstraintModeV1 { - Hard, - ReportOnly, -} - -/// One selected/fixed case × constraint cell; state is owned by its certificate. -#[derive(Clone, Copy)] -pub struct PackageProgramVerifiedCellV1<'a> { - inner: &'a CoreProgramConstraintCellV1, -} - -impl<'a> PackageProgramVerifiedCellV1<'a> { - const fn from_core(inner: &'a CoreProgramConstraintCellV1) -> Self { - Self { inner } - } - - pub const fn case_index(self) -> usize { - self.inner.case_index() - } - - pub const fn constraint(self) -> PackageProgramConstraintIdV1 { - PackageProgramConstraintIdV1::from_core(self.inner.constraint()) - } - - pub const fn occurrence(self) -> PackageProgramOccurrenceIdV1 { - PackageProgramOccurrenceIdV1::from_core(self.inner.target()) - } - - pub const fn mode(self) -> PackageProgramConstraintModeV1 { - project_constraint_mode(self.inner) - } - - pub fn assessment(self) -> PackageProgramAssessmentV1<'a> { - project_assessment(self.inner) - } -} - -/// One exhaustive candidate-state × case × constraint conflict cell. -#[derive(Clone, Copy)] -pub struct PackageProgramConflictCellV1<'a> { - inner: &'a CoreProgramConstraintCellV1, -} - -impl<'a> PackageProgramConflictCellV1<'a> { - const fn from_core(inner: &'a CoreProgramConstraintCellV1) -> Self { - Self { inner } - } - - pub const fn state_index(self) -> usize { - self.inner.candidate_state_index() - } - - pub const fn case_index(self) -> usize { - self.inner.case_index() - } - - pub const fn constraint(self) -> PackageProgramConstraintIdV1 { - PackageProgramConstraintIdV1::from_core(self.inner.constraint()) - } - - pub const fn occurrence(self) -> PackageProgramOccurrenceIdV1 { - PackageProgramOccurrenceIdV1::from_core(self.inner.target()) - } - - pub const fn mode(self) -> PackageProgramConstraintModeV1 { - project_constraint_mode(self.inner) - } - - pub fn assessment(self) -> PackageProgramAssessmentV1<'a> { - project_assessment(self.inner) - } -} - -const fn project_constraint_mode( - cell: &CoreProgramConstraintCellV1, -) -> PackageProgramConstraintModeV1 { - if cell.is_hard() { - PackageProgramConstraintModeV1::Hard - } else { - PackageProgramConstraintModeV1::ReportOnly - } -} - -fn project_assessment(cell: &CoreProgramConstraintCellV1) -> PackageProgramAssessmentV1<'_> { - match cell.result() { - ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::ExactSrgb8(evidence)) => { - PackageProgramAssessmentV1::ExactSrgb8(PackageProgramExactSrgb8EvidenceV1 { - inner: PackageProgramExactSrgb8EvidenceRefV1::Pass(evidence), - }) - } - ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::ExactSrgb8( - evidence, - )) => PackageProgramAssessmentV1::ExactSrgb8(PackageProgramExactSrgb8EvidenceV1 { - inner: PackageProgramExactSrgb8EvidenceRefV1::Violation(evidence), - }), - ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::Wcag22Srgb8(evidence)) => { - PackageProgramAssessmentV1::Wcag22Srgb8(PackageProgramWcag22Srgb8EvidenceV1 { - inner: PackageProgramWcag22Srgb8EvidenceRefV1::Pass(evidence), - }) - } - ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::Wcag22Srgb8( - evidence, - )) => PackageProgramAssessmentV1::Wcag22Srgb8(PackageProgramWcag22Srgb8EvidenceV1 { - inner: PackageProgramWcag22Srgb8EvidenceRefV1::Violation(evidence), - }), - } -} - -/// Stored evaluator family. Its sealed witness retains the incompatible verdict. -#[derive(Clone, Copy)] -pub enum PackageProgramAssessmentV1<'a> { - ExactSrgb8(PackageProgramExactSrgb8EvidenceV1<'a>), - Wcag22Srgb8(PackageProgramWcag22Srgb8EvidenceV1<'a>), -} - -impl<'a> PackageProgramAssessmentV1<'a> { - pub const fn verdict(self) -> PackageProgramVerdictV1 { - match self { - Self::ExactSrgb8(value) => value.verdict(), - Self::Wcag22Srgb8(value) => value.verdict(), - } - } - - pub fn binding(self) -> PackageProgramPointBindingV1<'a> { - match self { - Self::ExactSrgb8(value) => value.binding(), - Self::Wcag22Srgb8(value) => value.binding(), - } - } -} - -/// Incompatible stored classifier outcomes. Clients cannot construct evidence. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramVerdictV1 { - Pass, - Violation, -} - -#[derive(Clone, Copy)] -enum PackageProgramExactSrgb8EvidenceRefV1<'a> { - Pass(&'a CoreExactPassEvidenceV1), - Violation(&'a CoreExactViolationEvidenceV1), -} - -/// Exact-sRGB8 expected value plus retained physical composition and modeled -/// tristimulus/context. -#[derive(Clone, Copy)] -pub struct PackageProgramExactSrgb8EvidenceV1<'a> { - inner: PackageProgramExactSrgb8EvidenceRefV1<'a>, -} - -impl<'a> PackageProgramExactSrgb8EvidenceV1<'a> { - pub const fn verdict(self) -> PackageProgramVerdictV1 { - match self.inner { - PackageProgramExactSrgb8EvidenceRefV1::Pass(_) => PackageProgramVerdictV1::Pass, - PackageProgramExactSrgb8EvidenceRefV1::Violation(_) => { - PackageProgramVerdictV1::Violation - } - } - } - - pub fn expected(self) -> Srgb8 { - match self.inner { - PackageProgramExactSrgb8EvidenceRefV1::Pass(value) => value.target(), - PackageProgramExactSrgb8EvidenceRefV1::Violation(value) => value.target(), - } - } - - pub fn binding(self) -> PackageProgramPointBindingV1<'a> { - let value = match self.inner { - PackageProgramExactSrgb8EvidenceRefV1::Pass(value) => value.binding(), - PackageProgramExactSrgb8EvidenceRefV1::Violation(value) => value.binding(), - }; - PackageProgramPointBindingV1 { inner: value } - } -} - -#[derive(Clone, Copy)] -enum PackageProgramWcag22Srgb8EvidenceRefV1<'a> { - Pass(&'a CoreWcag22PassEvidenceV1), - Violation(&'a CoreWcag22ViolationEvidenceV1), -} - -/// WCAG 2.2 profile, criterion, luminance evidence, physical composition and -/// modeled tristimulus/context retained by the Core report. -#[derive(Clone, Copy)] -pub struct PackageProgramWcag22Srgb8EvidenceV1<'a> { - inner: PackageProgramWcag22Srgb8EvidenceRefV1<'a>, -} - -impl<'a> PackageProgramWcag22Srgb8EvidenceV1<'a> { - pub const fn verdict(self) -> PackageProgramVerdictV1 { - match self.inner { - PackageProgramWcag22Srgb8EvidenceRefV1::Pass(_) => PackageProgramVerdictV1::Pass, - PackageProgramWcag22Srgb8EvidenceRefV1::Violation(_) => { - PackageProgramVerdictV1::Violation - } - } - } - - pub fn profile_id(self) -> Wcag22ProfileIdV1 { - match self.inner { - PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { - value.measurement().value().profile_id() - } - PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { - value.measurement().value().profile_id() - } - } - } - - pub fn criterion(self) -> Wcag22CriterionV1 { - match self.inner { - PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { - value.measurement().value().criterion() - } - PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { - value.measurement().value().criterion() - } - } - } - - pub fn foreground_luminance(self) -> Wcag22LuminanceBoundsQ55V1 { - let measurement = match self.inner { - PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { - value.measurement().value().measurement() - } - PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { - value.measurement().value().measurement() - } - }; - measurement.foreground_luminance - } - - pub fn background_luminance(self) -> Wcag22LuminanceBoundsQ55V1 { - let measurement = match self.inner { - PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { - value.measurement().value().measurement() - } - PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { - value.measurement().value().measurement() - } - }; - measurement.background_luminance - } - - pub fn numerical_evidence(self) -> &'a NumericalDecisionEvidenceV1 { - match self.inner { - PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { - value.measurement().value().evidence() - } - PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { - value.measurement().value().evidence() - } - } - } - - pub fn binding(self) -> PackageProgramPointBindingV1<'a> { - let value = match self.inner { - PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => value.binding(), - PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => value.binding(), - }; - PackageProgramPointBindingV1 { inner: value } - } -} - -/// Retained physical composition and modeled tristimulus/context shared by an -/// evaluator witness. -#[derive(Clone, Copy)] -pub struct PackageProgramPointBindingV1<'a> { - inner: &'a ProgramVisiblePointBindingV1, -} - -impl<'a> PackageProgramPointBindingV1<'a> { - pub const fn physical(self) -> PackageProgramPhysicalPointV1<'a> { - match self.inner.physical().occurrence().profile() { - CompositionProfileV1::EncodedSrgb8SourceOverV1 => { - PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver( - PackageProgramEncodedSrgb8SourceOverV1 { inner: self.inner }, - ) - } - } - } - - pub const fn modeled(self) -> PackageProgramModeledPointV1<'a> { - match self.inner.modeled_lcs().provenance().binding() { - AdmittedSrgb8TristimulusBindingV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1V1 => { - PackageProgramModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1( - PackageProgramModeledTristimulusV1 { inner: self.inner }, - ) - } - } - } -} - -/// Closed exact physical-composition family. -#[derive(Clone, Copy)] -pub enum PackageProgramPhysicalPointV1<'a> { - EncodedSrgb8SourceOver(PackageProgramEncodedSrgb8SourceOverV1<'a>), -} - -#[derive(Clone, Copy)] -pub struct PackageProgramEncodedSrgb8SourceOverV1<'a> { - inner: &'a ProgramVisiblePointBindingV1, -} - -impl PackageProgramEncodedSrgb8SourceOverV1<'_> { - pub const fn subject_paint(self) -> PackageProgramPaintIdV1 { - PackageProgramPaintIdV1::from_core(self.inner.physical().program_occurrence().subject()) - } - - pub const fn backdrop_surface(self) -> PackageProgramSurfaceIdV1 { - PackageProgramSurfaceIdV1::from_core( - self.inner - .physical() - .program_occurrence() - .backdrop_surface(), - ) - } - - pub const fn subject(self) -> Srgb8 { - Srgb8::new(self.inner.physical().occurrence().subject_rgb()) - } - - pub const fn opacity(self) -> f64 { - f64::from_bits(self.inner.physical().occurrence().subject_opacity_bits()) - } - - pub const fn backdrop(self) -> Srgb8 { - Srgb8::new(self.inner.physical().occurrence().backdrop_rgb()) - } - - pub const fn visible(self) -> Srgb8 { - Srgb8::new(self.inner.physical().occurrence().output_rgb()) - } -} - -/// Closed modeled-tristimulus provenance family. -#[derive(Clone, Copy)] -pub enum PackageProgramModeledPointV1<'a> { - Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(PackageProgramModeledTristimulusV1<'a>), -} - -#[derive(Clone, Copy)] -pub struct PackageProgramModeledTristimulusV1<'a> { - inner: &'a ProgramVisiblePointBindingV1, -} - -impl PackageProgramModeledTristimulusV1<'_> { - pub fn xyz(self) -> [f64; 3] { - self.inner.modeled_lcs().derivation().sample().xyz() - } - - pub const fn appearance_context(self) -> PackageProgramAppearanceContextV1 { - PackageProgramAppearanceContextV1(self.inner.modeled_lcs().occurrence().context()) - } -} - -/// One Core-certified output Paint. No output exists for Conflict. -#[derive(Clone, Copy)] -pub struct PackageProgramCertifiedOutputV1<'a> { - inner: &'a ProgramOutputV1, -} - -impl<'a> PackageProgramCertifiedOutputV1<'a> { - const fn from_core(inner: &'a ProgramOutputV1) -> Self { - Self { inner } - } - - pub const fn output_slot(self) -> PackageProgramOutputSlotIdV1 { - PackageProgramOutputSlotIdV1::from_core((*self.inner).output()) - } - - pub const fn paint(self) -> PackageProgramPaintIdV1 { - PackageProgramPaintIdV1::from_core((*self.inner).paint().id()) - } - - pub const fn source(self) -> Srgb8 { - (*self.inner).paint().source() - } - - pub const fn opacity(self) -> f64 { - (*self.inner).paint().opacity().value() - } -} - -/// A Set operation is structurally tied to the exact Verified certificate. -#[derive(Clone, Copy)] -pub struct PackageProgramSetV1<'owner, 'session> { - output: &'session ProgramOutputV1, - certificate: PackageProgramVerifiedCertificateV1<'session>, - _scope: PackageProgramBorrowScopeV1<'owner, 'session>, -} - -impl<'session> PackageProgramSetV1<'_, 'session> { - pub const fn output_slot(self) -> PackageProgramOutputSlotIdV1 { - PackageProgramOutputSlotIdV1::from_core((*self.output).output()) - } - - pub const fn source(self) -> Srgb8 { - (*self.output).paint().source() - } - - pub const fn opacity(self) -> f64 { - (*self.output).paint().opacity().value() - } - - pub const fn certificate(self) -> PackageProgramVerifiedCertificateV1<'session> { - self.certificate - } -} - -#[derive(Clone, Copy)] -pub struct PackageProgramRemoveV1<'owner, 'session> { - output_slot: PackageProgramOutputSlotIdV1, - _scope: PackageProgramBorrowScopeV1<'owner, 'session>, -} - -impl PackageProgramRemoveV1<'_, '_> { - pub const fn output_slot(self) -> PackageProgramOutputSlotIdV1 { - self.output_slot - } -} - -/// A Hold operation is structurally tied to the retained Verified certificate. -#[derive(Clone, Copy)] -pub struct PackageProgramHoldV1<'owner, 'session> { - output: &'session ProgramOutputV1, - certificate: PackageProgramVerifiedCertificateV1<'session>, - _scope: PackageProgramBorrowScopeV1<'owner, 'session>, -} - -impl<'session> PackageProgramHoldV1<'_, 'session> { - pub const fn output_slot(self) -> PackageProgramOutputSlotIdV1 { - PackageProgramOutputSlotIdV1::from_core((*self.output).output()) - } - - pub const fn certificate(self) -> PackageProgramVerifiedCertificateV1<'session> { - self.certificate - } -} - -/// Closed total operation union over opaque output slots. -/// -/// Every payload borrows both the exact owner and immutable Session snapshot; -/// destructuring a `Remove` cannot erase that scope. -/// Copied slot/source/opacity values are data only: a runtime adapter must -/// recheck its live owner, Session and revision immediately before one atomic -/// sink commit. -/// -/// ```compile_fail,E0515 -/// use labcolors_core::package_bridge::{ -/// PackageProgramOperationV1, PackageProgramOwnerV1, PackageProgramRemoveV1, -/// PackageProgramSessionV1, -/// }; -/// -/// fn escape_remove<'session>( -/// owner: PackageProgramOwnerV1, -/// session: &'session PackageProgramSessionV1, -/// ) -> PackageProgramRemoveV1<'session, 'session> { -/// match owner.project(session).unwrap().operations().next().unwrap() { -/// PackageProgramOperationV1::Remove(remove) => remove, -/// _ => panic!("fixture supplies Remove"), -/// } -/// } -/// ``` -/// -/// ```compile_fail,E0515 -/// use labcolors_core::package_bridge::{ -/// PackageProgramOperationV1, PackageProgramOwnerV1, PackageProgramRemoveV1, -/// }; -/// -/// fn escape_local_session<'owner>( -/// owner: &'owner PackageProgramOwnerV1, -/// ) -> PackageProgramRemoveV1<'owner, 'owner> { -/// let session = owner.instantiate(1).unwrap(); -/// match owner.project(&session).unwrap().operations().next().unwrap() { -/// PackageProgramOperationV1::Remove(remove) => remove, -/// _ => panic!("fixture supplies Remove"), -/// } -/// } -/// ``` -/// -/// ```compile_fail,E0515 -/// use labcolors_core::package_bridge::{ -/// PackageProgramOperationV1, PackageProgramOwnerV1, PackageProgramSessionV1, -/// PackageProgramSetV1, -/// }; -/// -/// fn escape_set<'session>( -/// owner: PackageProgramOwnerV1, -/// session: &'session PackageProgramSessionV1, -/// ) -> PackageProgramSetV1<'session, 'session> { -/// match owner.project(session).unwrap().operations().next().unwrap() { -/// PackageProgramOperationV1::Set(set) => set, -/// _ => panic!("fixture supplies Set"), -/// } -/// } -/// ``` -/// -/// ```compile_fail,E0515 -/// use labcolors_core::package_bridge::{ -/// PackageProgramHoldV1, PackageProgramOperationV1, PackageProgramOwnerV1, -/// PackageProgramSessionV1, -/// }; -/// -/// fn escape_hold<'session>( -/// owner: PackageProgramOwnerV1, -/// session: &'session PackageProgramSessionV1, -/// ) -> PackageProgramHoldV1<'session, 'session> { -/// match owner.project(session).unwrap().operations().next().unwrap() { -/// PackageProgramOperationV1::Hold(hold) => hold, -/// _ => panic!("fixture supplies Hold"), -/// } -/// } -/// ``` -/// -/// ```compile_fail,E0502 -/// use labcolors_core::package_bridge::{ -/// PackageProgramOperationV1, PackageProgramOwnerV1, PackageProgramSessionV1, -/// PackageProgramUpdateV1, -/// }; -/// -/// fn remove_blocks_session_mutation( -/// owner: &PackageProgramOwnerV1, -/// session: &mut PackageProgramSessionV1, -/// ) { -/// let remove = match owner.project(session).unwrap().operations().next().unwrap() { -/// PackageProgramOperationV1::Remove(remove) => remove, -/// _ => return, -/// }; -/// let _second = owner.update( -/// session, -/// PackageProgramUpdateV1::Unknown { -/// revision: 2, -/// reason_id: 7, -/// }, -/// ); -/// let _slot = remove.output_slot(); -/// } -/// ``` -#[derive(Clone, Copy)] -pub enum PackageProgramOperationV1<'owner, 'session> { - Set(PackageProgramSetV1<'owner, 'session>), - Remove(PackageProgramRemoveV1<'owner, 'session>), - Hold(PackageProgramHoldV1<'owner, 'session>), -} - -struct PackageProgramCertificatesV1<'a> { - values: [Option>; 2], - index: usize, - len: usize, -} - -impl<'a> PackageProgramCertificatesV1<'a> { - fn new( - first: Option>, - second: Option>, - ) -> Self { - let len = usize::from(first.is_some()) + usize::from(second.is_some()); - debug_assert!(first.is_some() || second.is_none()); - Self { - values: [first, second], - index: 0, - len, - } - } -} - -impl<'a> Iterator for PackageProgramCertificatesV1<'a> { - type Item = PackageProgramCertificateV1<'a>; - - fn next(&mut self) -> Option { - if self.index == self.len { - return None; - } - let value = self.values[self.index]; - self.index += 1; - value - } - - fn size_hint(&self) -> (usize, Option) { - let remaining = self.len - self.index; - (remaining, Some(remaining)) - } -} - -impl ExactSizeIterator for PackageProgramCertificatesV1<'_> {} -impl FusedIterator for PackageProgramCertificatesV1<'_> {} - -struct PackageProgramOwnerOutputSlotsV1<'owner> { - compiled: &'owner CompiledCoreProgramV1, - index: usize, - len: usize, -} - -impl<'owner> PackageProgramOwnerOutputSlotsV1<'owner> { - fn new(compiled: &'owner CompiledCoreProgramV1) -> Self { - Self { - compiled, - index: 0, - len: compiled.output_count(), - } - } -} - -impl Iterator for PackageProgramOwnerOutputSlotsV1<'_> { - type Item = PackageProgramOutputSlotIdV1; - - fn next(&mut self) -> Option { - if self.index == self.len { - return None; - } - let output = self.compiled.output_slot_at(self.index)?; - self.index += 1; - Some(PackageProgramOutputSlotIdV1::from_core(output)) - } - - fn size_hint(&self) -> (usize, Option) { - let remaining = self.len - self.index; - (remaining, Some(remaining)) - } -} - -impl ExactSizeIterator for PackageProgramOwnerOutputSlotsV1<'_> {} -impl FusedIterator for PackageProgramOwnerOutputSlotsV1<'_> {} - -enum PackageProgramOperationSourceV1<'owner, 'session> { - Empty, - Set { - outputs: slice::Iter<'session, ProgramOutputV1>, - certificate: PackageProgramVerifiedCertificateV1<'session>, - scope: PackageProgramBorrowScopeV1<'owner, 'session>, - }, - Hold { - outputs: slice::Iter<'session, ProgramOutputV1>, - certificate: PackageProgramVerifiedCertificateV1<'session>, - scope: PackageProgramBorrowScopeV1<'owner, 'session>, - }, - Remove { - slots: PackageProgramOwnerOutputSlotsV1<'owner>, - scope: PackageProgramBorrowScopeV1<'owner, 'session>, - }, -} - -struct PackageProgramOperationsV1<'owner, 'session> { - inner: PackageProgramOperationSourceV1<'owner, 'session>, -} - -impl<'owner, 'session> Iterator for PackageProgramOperationsV1<'owner, 'session> { - type Item = PackageProgramOperationV1<'owner, 'session>; - - fn next(&mut self) -> Option { - match &mut self.inner { - PackageProgramOperationSourceV1::Empty => None, - PackageProgramOperationSourceV1::Set { - outputs, - certificate, - scope, - } => { - let output = outputs.next()?; - Some(PackageProgramOperationV1::Set(PackageProgramSetV1 { - output, - certificate: *certificate, - _scope: *scope, - })) - } - PackageProgramOperationSourceV1::Hold { - outputs, - certificate, - scope, - } => Some(PackageProgramOperationV1::Hold(PackageProgramHoldV1 { - output: outputs.next()?, - certificate: *certificate, - _scope: *scope, - })), - PackageProgramOperationSourceV1::Remove { slots, scope } => { - Some(PackageProgramOperationV1::Remove(PackageProgramRemoveV1 { - output_slot: slots.next()?, - _scope: *scope, - })) - } - } - } - - fn size_hint(&self) -> (usize, Option) { - let remaining = match &self.inner { - PackageProgramOperationSourceV1::Empty => 0, - PackageProgramOperationSourceV1::Set { outputs, .. } => outputs.len(), - PackageProgramOperationSourceV1::Hold { outputs, .. } => outputs.len(), - PackageProgramOperationSourceV1::Remove { slots, .. } => slots.len(), - }; - (remaining, Some(remaining)) - } -} - -impl ExactSizeIterator for PackageProgramOperationsV1<'_, '_> {} -impl FusedIterator for PackageProgramOperationsV1<'_, '_> {} - -/// Closed package error classifications for Session construction. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramInstantiateErrorKindV1 { - ResourceExhausted, - InternalInvariant, -} - -/// Opaque Session construction failure. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PackageProgramInstantiateErrorV1 { - kind: PackageProgramInstantiateErrorKindV1, -} - -impl PackageProgramInstantiateErrorV1 { - const fn new(kind: PackageProgramInstantiateErrorKindV1) -> Self { - Self { kind } - } - - fn from_core(error: ProgramSessionInstantiateError) -> Self { - let kind = match error { - ProgramSessionInstantiateError::ResourceExhausted => { - PackageProgramInstantiateErrorKindV1::ResourceExhausted - } - ProgramSessionInstantiateError::InternalInvariant => { - PackageProgramInstantiateErrorKindV1::InternalInvariant - } - }; - Self::new(kind) - } - - pub const fn kind(self) -> PackageProgramInstantiateErrorKindV1 { - self.kind - } -} - -impl From for PackageProgramInstantiateErrorV1 { - fn from(kind: PackageProgramInstantiateErrorKindV1) -> Self { - Self::new(kind) - } -} - -/// Closed package error classifications for one atomic update. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramUpdateErrorKindV1 { - OwnerMismatch, - InvalidObservation, - RevisionOutOfOrder, - RevisionConflict, - ResourceExhausted, - EvaluationFailed, - InternalInvariant, -} - -/// Opaque update failure. Core state is unchanged for every returned error. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PackageProgramUpdateErrorV1 { - kind: PackageProgramUpdateErrorKindV1, -} - -impl PackageProgramUpdateErrorV1 { - const fn new(kind: PackageProgramUpdateErrorKindV1) -> Self { - Self { kind } - } - - pub const fn kind(self) -> PackageProgramUpdateErrorKindV1 { - self.kind - } -} - -fn map_joint_order_error(error: FiniteJointOrderErrorV1) -> PackageProgramJointOrderErrorV1 { - match error { - FiniteJointOrderErrorV1::EmptyDomain { dimension } => { - PackageProgramJointOrderErrorV1::EmptyDomain { dimension } - } - FiniteJointOrderErrorV1::CardinalityOverflow => { - PackageProgramJointOrderErrorV1::CardinalityOverflow - } - FiniteJointOrderErrorV1::EmptyOrder => PackageProgramJointOrderErrorV1::EmptyOrder, - FiniteJointOrderErrorV1::TupleArity { - tuple, - expected, - actual, - } => PackageProgramJointOrderErrorV1::TupleArity { - state: tuple, - expected, - actual, - }, - FiniteJointOrderErrorV1::OrdinalOutOfDomain { - tuple, - dimension, - ordinal, - domain_len, - } => PackageProgramJointOrderErrorV1::OrdinalOutOfDomain { - state: tuple, - dimension, - ordinal, - domain_len, - }, - FiniteJointOrderErrorV1::DuplicateTuple { first, duplicate } => { - PackageProgramJointOrderErrorV1::DuplicateTuple { - first_state: first, - duplicate_state: duplicate, - } - } - FiniteJointOrderErrorV1::IncompleteOrder { expected, actual } => { - PackageProgramJointOrderErrorV1::IncompleteOrder { expected, actual } - } - FiniteJointOrderErrorV1::ResourceExhausted => { - PackageProgramJointOrderErrorV1::ResourceExhausted - } - } -} - -fn map_program_compile_error(error: ProgramCompileError) -> PackageProgramCompileErrorV1 { - match error { - ProgramCompileError::DuplicateSource { source } => { - PackageProgramCompileErrorV1::DuplicateSource { - source: PackageProgramSourceIdV1::from_core(source), - } - } - ProgramCompileError::DuplicateTarget { target } => { - PackageProgramCompileErrorV1::DuplicateTarget { - target: PackageProgramTargetIdV1::from_core(target), - } - } - ProgramCompileError::MissingTargetSource { target, source } => { - PackageProgramCompileErrorV1::MissingTargetSource { - target: PackageProgramTargetIdV1::from_core(target), - source: PackageProgramSourceIdV1::from_core(source), - } - } - ProgramCompileError::DuplicateOpacityInput { input } => { - PackageProgramCompileErrorV1::DuplicateOpacityInput { - input: PackageProgramOpacityInputIdV1::from_core(input), - } - } - ProgramCompileError::DuplicateSurfaceInputPort { input } => { - PackageProgramCompileErrorV1::DuplicateSurfaceInputPort { - input: PackageProgramSurfaceInputPortIdV1::from_core(input), - } - } - ProgramCompileError::UnusedSurfaceInputPort { input } => { - PackageProgramCompileErrorV1::UnusedSurfaceInputPort { - input: PackageProgramSurfaceInputPortIdV1::from_core(input), - } - } - ProgramCompileError::DuplicateSurfaceInputBinding { - input, - first, - duplicate, - } => PackageProgramCompileErrorV1::DuplicateSurfaceInputBinding { - input: PackageProgramSurfaceInputPortIdV1::from_core(input), - first: PackageProgramSurfaceIdV1::from_core(first), - duplicate: PackageProgramSurfaceIdV1::from_core(duplicate), - }, - ProgramCompileError::DuplicatePaint { paint } => { - PackageProgramCompileErrorV1::DuplicatePaint { - paint: PackageProgramPaintIdV1::from_core(paint), - } - } - ProgramCompileError::DuplicateSurface { surface } => { - PackageProgramCompileErrorV1::DuplicateSurface { - surface: PackageProgramSurfaceIdV1::from_core(surface), - } - } - ProgramCompileError::DuplicateOccurrence { occurrence } => { - PackageProgramCompileErrorV1::DuplicateOccurrence { - occurrence: PackageProgramOccurrenceIdV1::from_core(occurrence), - } - } - ProgramCompileError::MissingPaintTarget { paint, target } => { - PackageProgramCompileErrorV1::MissingPaintTarget { - paint: PackageProgramPaintIdV1::from_core(paint), - target: PackageProgramTargetIdV1::from_core(target), - } - } - ProgramCompileError::MissingPaintSource { paint, source } => { - PackageProgramCompileErrorV1::MissingPaintSource { - paint: PackageProgramPaintIdV1::from_core(paint), - source: PackageProgramPaintIdV1::from_core(source), - } - } - ProgramCompileError::MissingPaintOpacityInput { paint, input } => { - PackageProgramCompileErrorV1::MissingPaintOpacityInput { - paint: PackageProgramPaintIdV1::from_core(paint), - input: PackageProgramOpacityInputIdV1::from_core(input), - } - } - ProgramCompileError::MissingSurfaceInputPort { surface, input } => { - PackageProgramCompileErrorV1::MissingSurfaceInputPort { - surface: PackageProgramSurfaceIdV1::from_core(surface), - input: PackageProgramSurfaceInputPortIdV1::from_core(input), - } - } - ProgramCompileError::MissingSurfaceOccurrence { - surface, - occurrence, - } => PackageProgramCompileErrorV1::MissingSurfaceOccurrence { - surface: PackageProgramSurfaceIdV1::from_core(surface), - occurrence: PackageProgramOccurrenceIdV1::from_core(occurrence), - }, - ProgramCompileError::MissingOccurrencePaint { occurrence, paint } => { - PackageProgramCompileErrorV1::MissingOccurrencePaint { - occurrence: PackageProgramOccurrenceIdV1::from_core(occurrence), - paint: PackageProgramPaintIdV1::from_core(paint), - } - } - ProgramCompileError::MissingOccurrenceBackdrop { - occurrence, - surface, - } => PackageProgramCompileErrorV1::MissingOccurrenceBackdrop { - occurrence: PackageProgramOccurrenceIdV1::from_core(occurrence), - surface: PackageProgramSurfaceIdV1::from_core(surface), - }, - ProgramCompileError::PaintCycle { paints } => { - PackageProgramCompileErrorV1::PaintCycle(PackageProgramPaintCycleV1 { paints }) - } - ProgramCompileError::RenderCycle { - surfaces, - occurrences, - } => PackageProgramCompileErrorV1::RenderCycle(PackageProgramRenderCycleV1 { - surfaces, - occurrences, - }), - ProgramCompileError::OpacityOutOfDomain { input } => { - PackageProgramCompileErrorV1::OpacityOutOfDomain { - input: PackageProgramOpacityInputIdV1::from_core(input), - } - } - ProgramCompileError::EmptyTargetDomain { target } => { - PackageProgramCompileErrorV1::EmptyTargetDomain { - target: PackageProgramTargetIdV1::from_core(target), - } - } - ProgramCompileError::DuplicateTargetCandidate { target, candidate } => { - PackageProgramCompileErrorV1::DuplicateTargetCandidate { - target: PackageProgramTargetIdV1::from_core(target), - candidate: PackageProgramTargetCandidateIdV1::from_core(candidate), - } - } - ProgramCompileError::DuplicateTargetCandidateSignal { - target, - first, - duplicate, - signal, - } => PackageProgramCompileErrorV1::DuplicateTargetCandidateSignal { - target: PackageProgramTargetIdV1::from_core(target), - first: PackageProgramTargetCandidateIdV1::from_core(first), - duplicate: PackageProgramTargetCandidateIdV1::from_core(duplicate), - encoded_srgb8: signal.srgb8(), - }, - ProgramCompileError::UnconstrainedTarget { target } => { - PackageProgramCompileErrorV1::UnconstrainedTarget { - target: PackageProgramTargetIdV1::from_core(target), - } - } - ProgramCompileError::DisconnectedFiniteTargets => { - PackageProgramCompileErrorV1::DisconnectedFiniteTargets - } - ProgramCompileError::UnassessedOutput { output, paint } => { - PackageProgramCompileErrorV1::UnassessedOutput { - output: PackageProgramOutputSlotIdV1::from_core(output), - paint: PackageProgramPaintIdV1::from_core(paint), - } - } - ProgramCompileError::MissingJointSelection => { - PackageProgramCompileErrorV1::MissingJointSelection - } - ProgramCompileError::JointSelectionWithoutTargets => { - PackageProgramCompileErrorV1::JointSelectionWithoutTargets - } - ProgramCompileError::JointStateDuplicateTarget { state, target } => { - PackageProgramCompileErrorV1::JointStateDuplicateTarget { - state, - target: PackageProgramTargetIdV1::from_core(target), - } - } - ProgramCompileError::JointStateMissingTarget { state, target } => { - PackageProgramCompileErrorV1::JointStateMissingTarget { - state, - target: PackageProgramTargetIdV1::from_core(target), - } - } - ProgramCompileError::JointStateUnknownTarget { state, target } => { - PackageProgramCompileErrorV1::JointStateUnknownTarget { - state, - target: PackageProgramTargetIdV1::from_core(target), - } - } - ProgramCompileError::JointStateUnknownCandidate { - state, - target, - candidate, - } => PackageProgramCompileErrorV1::JointStateUnknownCandidate { - state, - target: PackageProgramTargetIdV1::from_core(target), - candidate: PackageProgramTargetCandidateIdV1::from_core(candidate), - }, - ProgramCompileError::InvalidJointOrder(error) => { - PackageProgramCompileErrorV1::InvalidJointOrder(map_joint_order_error(error)) - } - ProgramCompileError::EmptyObservationGroup { .. } => { - PackageProgramCompileErrorV1::EmptySurfaceInputPortSet - } - ProgramCompileError::EmptyOccurrenceSet => PackageProgramCompileErrorV1::EmptyOccurrenceSet, - ProgramCompileError::EmptyConstraintSet => PackageProgramCompileErrorV1::EmptyConstraintSet, - ProgramCompileError::EmptyOutputSet => PackageProgramCompileErrorV1::EmptyOutputSet, - ProgramCompileError::DuplicateConstraint { constraint } => { - PackageProgramCompileErrorV1::DuplicateConstraint { - constraint: PackageProgramConstraintIdV1::from_core(constraint), - } - } - ProgramCompileError::MissingConstraintOccurrence { - constraint, - occurrence, - } => PackageProgramCompileErrorV1::MissingConstraintOccurrence { - constraint: PackageProgramConstraintIdV1::from_core(constraint), - occurrence: PackageProgramOccurrenceIdV1::from_core(occurrence), - }, - ProgramCompileError::DuplicateOutputSlot { output } => { - PackageProgramCompileErrorV1::DuplicateOutputSlot { - output: PackageProgramOutputSlotIdV1::from_core(output), - } - } - ProgramCompileError::MissingOutputPaint { output, paint } => { - PackageProgramCompileErrorV1::MissingOutputPaint { - output: PackageProgramOutputSlotIdV1::from_core(output), - paint: PackageProgramPaintIdV1::from_core(paint), - } - } - ProgramCompileError::ResourceExhausted => PackageProgramCompileErrorV1::ResourceExhausted, - ProgramCompileError::InternalInvariant => PackageProgramCompileErrorV1::InternalInvariant, - } -} -fn map_session_update_error( - error: SessionUpdateError, -) -> PackageProgramUpdateErrorV1 { - let kind = match error { - // A borrowed matching owner keeps the exact Rc generation alive for - // the whole transaction; expiry here is therefore an internal breach. - SessionUpdateError::OwnerExpired => PackageProgramUpdateErrorKindV1::InternalInvariant, - SessionUpdateError::Observation(error) => map_observation_error(error), - SessionUpdateError::Plan(error) => map_plan_error(error), - SessionUpdateError::EvidenceBindingInvariant => { - PackageProgramUpdateErrorKindV1::InternalInvariant - } - }; - PackageProgramUpdateErrorV1::new(kind) -} - -fn map_observation_error(error: ObservationError) -> PackageProgramUpdateErrorKindV1 { - match error { - ObservationError::EmptyScenarioSet - | ObservationError::DuplicateScenarioId { .. } - | ObservationError::SchemaOrderedValueCountMismatch { .. } => { - PackageProgramUpdateErrorKindV1::InvalidObservation - } - ObservationError::RevisionOutOfOrder { .. } => { - PackageProgramUpdateErrorKindV1::RevisionOutOfOrder - } - ObservationError::RevisionConflict { .. } => { - PackageProgramUpdateErrorKindV1::RevisionConflict - } - ObservationError::ResourceExhausted => PackageProgramUpdateErrorKindV1::ResourceExhausted, - ObservationError::EmptyCompiledSurfaceInputSchema - | ObservationError::DuplicateCompiledSurfaceInputPort { .. } - | ObservationError::StreamMismatch { .. } - | ObservationError::DuplicateSurfaceInputBinding { .. } - | ObservationError::MissingSurfaceInputBinding { .. } - | ObservationError::UnexpectedSurfaceInputBinding { .. } => { - PackageProgramUpdateErrorKindV1::InternalInvariant - } - } -} - -fn map_plan_error(error: CoreProgramPlanErrorV1) -> PackageProgramUpdateErrorKindV1 { - match error { - ProgramSessionEvaluationError::ResourceExhausted => { - PackageProgramUpdateErrorKindV1::ResourceExhausted - } - ProgramSessionEvaluationError::Evaluator { .. } => { - PackageProgramUpdateErrorKindV1::EvaluationFailed - } - ProgramSessionEvaluationError::ObservationSchemaMismatch(_) - | ProgramSessionEvaluationError::ProgramTargetBinding { .. } - | ProgramSessionEvaluationError::ModeledOccurrence { .. } - | ProgramSessionEvaluationError::OutputVariesAcrossCases { .. } - | ProgramSessionEvaluationError::FinalRecheckViolation { .. } - | ProgramSessionEvaluationError::InternalInvariant => { - PackageProgramUpdateErrorKindV1::InternalInvariant - } - } -} - -#[cfg(test)] -mod compile_error_projection_tests { - use super::*; - - #[test] - fn operation_scope_is_a_zero_sized_borrow_marker() { - assert_eq!( - core::mem::size_of::>(), - 0 - ); - } - - #[test] - fn nested_joint_resource_exhaustion_keeps_its_exact_reason_and_site_kind() { - let error = map_program_compile_error(ProgramCompileError::InvalidJointOrder( - FiniteJointOrderErrorV1::ResourceExhausted, - )); - - assert_eq!( - error.kind(), - PackageProgramCompileErrorKindV1::InvalidJointOrder - ); - assert_eq!( - error, - PackageProgramCompileErrorV1::InvalidJointOrder( - PackageProgramJointOrderErrorV1::ResourceExhausted - ) - ); - } -} diff --git a/crates/labcolors-core/src/program.rs b/crates/labcolors-core/src/program.rs new file mode 100644 index 00000000..976c045f --- /dev/null +++ b/crates/labcolors-core/src/program.rs @@ -0,0 +1,4086 @@ +//! Публичный декларативный контракт компиляции и исполнения цветовой программы. +//! +//! Клиент один раз описывает физический граф через [`DraftV1`]: исходные +//! сигналы, решаемые цели, Paint, Surface, их [`OccurrenceIdV1`], ограничения +//! и выходы. Идентификаторы непрозрачны: Core не выводит из них семантику. +//! [`DraftV1::compile`] проверяет граф целиком и возвращает [`OwnerV1`] — +//! единственного владельца конкретной скомпилированной эпохи. +//! +//! [`OwnerV1::instantiate`] создаёт потоковую [`SessionV1`]. На горячем пути +//! [`OwnerV1::update`] принимает физические сценарии в каноническом порядке +//! входов и атомарно возвращает [`ProjectionV1`] без повторного решения в +//! адаптере. Исторические доказательства принадлежат Session, но только тот же +//! Owner разрешает обновления и операции. +//! +//! Состояния проецируются однозначно: +//! +//! | Состояние | Операции | +//! |---|---| +//! | `Waiting` | нет | +//! | `Ready` | `Set` для каждого выхода | +//! | `Stale` | `Hold` последнего доказанного результата | +//! | `Failed` с прошлым результатом | `Hold` | +//! | `Failed` без прошлого результата | `Remove` | +//! +//! [`CertificateV1::Verified`] хранит выбранное состояние, все клетки +//! доказательства и сертифицированные выходы. [`CertificateV1::Conflict`] +//! хранит исчерпывающий конфликт по всем рассмотренным состояниям. +//! [`ContentIdentityV1`] идентифицирует каноническое содержание, но не даёт +//! полномочий живого [`OwnerV1`]. + +use core::iter::FusedIterator; +use core::marker::PhantomData; +use core::slice; + +use crate::Srgb8; +use crate::appearance::{OccurrenceId, OpacityInputId, PaintId, SurfaceId, SurfaceInputPortId}; +use crate::composition::CompositionProfileV1; +use crate::constraints::{ + ApplicableWcag22EvaluationErrorV1, ExactSrgb8IdentityV1, ProgramVisiblePointBindingV1, + ProgramVisiblePointPassEvidence, ProgramVisiblePointViolationEvidence, Wcag22Srgb8V1, +}; +use crate::joint::FiniteJointOrderErrorV1; +use crate::lcs_occurrence::{ + AdaptingLuminanceCdM2, AdmittedSrgb8TristimulusBindingV1, 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, +}; +use crate::numerics::NumericalDecisionEvidenceV1; +use crate::observation::{ + ObservationError, ObservationHeadViewV1, ObservationSchemaMismatchV1, ObservationStreamId, + Revision, ScenarioId, SchemaOrderedScenarioSourceV1, UnknownReasonId, +}; +use crate::program_session::{ + CompiledCoreProgramV1, CompositionProfile, ConstraintId, ConstraintInvocation, + CoreProgramConstraintInvocationV1, CoreProgramDraftErrorV1, CoreProgramDraftV1, + CoreProgramEvaluatorErrorV1, CoreProgramEvaluatorsV1, CoreProgramPassEvidenceV1, + CoreProgramViolationEvidenceV1, DeclaredJointSelectionV1, JointCandidateStateV1, Occurrence, + OpacityInput, OutputBinding, OutputSlotId, Paint, ProgramCompileError, ProgramConflictV1, + ProgramConstraintCellV1, ProgramConstraintResultV1, ProgramContentIdentityV1, ProgramOutputV1, + ProgramSessionEvaluationError, ProgramSessionInstantiateError, ProgramSessionPlan, + ProgramVerifiedV1, Source, SourceId, Surface, Target, TargetCandidateChoiceV1, + TargetCandidateId, TargetCandidateV1 as CoreTargetCandidateV1, TargetId, +}; +use crate::session::{Session, SessionState, SessionUpdateError}; +use crate::wcag22::{ + Wcag22ClientDeclaredNotApplicableV1, Wcag22CriterionV1, Wcag22EvaluationErrorV1, + Wcag22LuminanceBoundsQ55V1, Wcag22ProfileIdV1, wcag22_profile_v1, +}; + +type CoreVerifiedV1 = ProgramVerifiedV1; +type CoreConflictV1 = ProgramConflictV1; +type CoreProgramPlanV1 = ProgramSessionPlan; +type CoreProgramSessionV1 = Session; +type CoreProgramStateV1 = SessionState; +type CoreProgramPlanErrorV1 = ProgramSessionEvaluationError; +type CoreProgramConstraintCellV1 = ProgramConstraintCellV1; +type CoreExactPassEvidenceV1 = ProgramVisiblePointPassEvidence; +type CoreExactViolationEvidenceV1 = ProgramVisiblePointViolationEvidence; +type CoreWcag22PassEvidenceV1 = ProgramVisiblePointPassEvidence; +type CoreWcag22ViolationEvidenceV1 = ProgramVisiblePointViolationEvidence; + +macro_rules! authored_id { + ($doc:literal, $name:ident, $core:ty) => { + #[doc = $doc] + #[repr(transparent)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[must_use] + pub struct $name($core); + + impl $name { + /// Создаёт непрозрачный идентификатор из клиентского числового ключа. + pub const fn new(value: u32) -> Self { + Self(<$core>::new(value)) + } + + /// Возвращает исходный клиентский числовой ключ. + pub const fn value(self) -> u32 { + self.0.value() + } + + const fn from_core(value: $core) -> Self { + Self(value) + } + + const fn into_core(self) -> $core { + self.0 + } + } + + impl core::hash::Hash for $name { + fn hash(&self, state: &mut H) { + core::hash::Hash::hash(&self.value(), state); + } + } + }; +} + +macro_rules! projected_id { + ($doc:literal, $name:ident, $core:ty) => { + #[doc = $doc] + #[repr(transparent)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[must_use] + pub struct $name($core); + + impl $name { + const fn from_core(value: $core) -> Self { + Self(value) + } + + /// Возвращает числовой ключ сохранённой provenance. + pub const fn value(self) -> u32 { + self.0.value() + } + } + + impl core::hash::Hash for $name { + fn hash(&self, state: &mut H) { + core::hash::Hash::hash(&self.value(), state); + } + } + }; +} + +authored_id!( + "Идентификатор объявленного исходного сигнала.", + SourceIdV1, + SourceId +); +authored_id!("Идентификатор решаемой цели.", TargetIdV1, TargetId); +authored_id!( + "Идентификатор конечного кандидата одной цели.", + TargetCandidateIdV1, + TargetCandidateId +); +authored_id!( + "Идентификатор объявленного входа прозрачности.", + OpacityInputIdV1, + OpacityInputId +); +authored_id!("Идентификатор узла Paint.", PaintIdV1, PaintId); +authored_id!( + "Идентификатор входного порта динамической поверхности.", + SurfaceInputPortIdV1, + SurfaceInputPortId +); +authored_id!("Идентификатор узла Surface.", SurfaceIdV1, SurfaceId); +authored_id!( + "Идентификатор физического наложения Paint на Surface.", + OccurrenceIdV1, + OccurrenceId +); +authored_id!( + "Идентификатор проверяемого ограничения.", + ConstraintIdV1, + ConstraintId +); +authored_id!( + "Идентификатор клиентского выходного слота.", + OutputSlotIdV1, + OutputSlotId +); +projected_id!( + "Идентификатор потока, сохранённый в наблюдении.", + StreamIdV1, + ObservationStreamId +); +projected_id!( + "Идентификатор сценария, сохранённый как provenance.", + ScenarioIdV1, + ScenarioId +); + +/// Один физический кандидат конечной цели. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TargetCandidateV1(CoreTargetCandidateV1); + +impl TargetCandidateV1 { + /// Связывает непрозрачный ID кандидата с конкретным encoded sRGB8 сигналом. + pub const fn new(id: TargetCandidateIdV1, source: Srgb8) -> Self { + Self(CoreTargetCandidateV1::from_srgb8(id.into_core(), source)) + } +} + +/// Выбор одного кандидата для одной цели в совместном состоянии. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct JointChoiceV1(TargetCandidateChoiceV1); + +impl JointChoiceV1 { + /// Создаёт типизированную пару `цель → кандидат`. + pub const fn new(target: TargetIdV1, candidate: TargetCandidateIdV1) -> Self { + Self(TargetCandidateChoiceV1::new( + target.into_core(), + candidate.into_core(), + )) + } +} + +/// Полное явно объявленное состояние всех конечных целей. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct JointStateV1(JointCandidateStateV1); + +impl JointStateV1 { + /// Создаёт состояние из одного выбора для каждой конечной цели. + pub fn new(choices: Vec) -> Self { + Self(JointCandidateStateV1::new( + choices.into_iter().map(|choice| choice.0).collect(), + )) + } +} + +/// Зарегистрированный режим окружения CIECAM16. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SurroundV1 { + /// Среднее освещение окружения. + Average, + /// Приглушённое освещение окружения. + Dim, + /// Тёмное окружение. + Dark, +} + +impl SurroundV1 { + const fn into_core(self) -> SurroundProfileId { + match self { + Self::Average => SurroundProfileId::AverageV1, + Self::Dim => SurroundProfileId::DimV1, + Self::Dark => SurroundProfileId::DarkV1, + } + } +} + +/// Поле входного контекста восприятия, не прошедшее admission. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AppearanceContextFieldV1 { + /// Адаптирующая яркость в кд/м². + AdaptingLuminanceCdM2, + /// Безразмерное отношение фоновой яркости `Y_b/Y_w`. + BackgroundLuminanceRatioYbYw, +} + +/// Числовая причина отказа при формировании контекста восприятия. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NumericDomainErrorV1 { + /// Значение не является конечным числом. + NonFinite, + /// Значение отрицательно. + Negative, + /// Значение должно быть строго положительным. + NotPositive, + /// Значение превышает единицу. + AboveOne, + /// Угол оттенка находится вне полуинтервала `[0°, 360°)`. + HueOutOfRange, +} + +/// Закрытая классификация отказа admission контекста восприятия. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AppearanceContextErrorKindV1 { + /// Клиентское значение находится вне объявленного домена. + Domain, + /// Нарушен внутренний инвариант закрытого преобразования. + InternalInvariant, +} + +/// Типизированный отказ admission контекста восприятия. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AppearanceContextErrorV1 { + kind: AppearanceContextErrorKindV1, + field: Option, + reason: Option, +} + +impl AppearanceContextErrorV1 { + /// Возвращает класс отказа. + pub const fn kind(self) -> AppearanceContextErrorKindV1 { + self.kind + } + + /// Возвращает отвергнутое поле, когда Core смог его локализовать. + pub const fn field(self) -> Option { + self.field + } + + /// Возвращает точную числовую причину, если отказ относится к входному домену. + pub const fn reason(self) -> Option { + self.reason + } + + fn from_core(error: AppearanceContextDomainErrorV1) -> Self { + let field = match error.field() { + CoreAppearanceContextFieldV1::AdaptingLuminanceCdM2 => { + AppearanceContextFieldV1::AdaptingLuminanceCdM2 + } + CoreAppearanceContextFieldV1::BackgroundLuminanceRatio => { + AppearanceContextFieldV1::BackgroundLuminanceRatioYbYw + } + }; + let reason = match error.reason() { + NumericDomainError::NonFinite => Some(NumericDomainErrorV1::NonFinite), + NumericDomainError::Negative => Some(NumericDomainErrorV1::Negative), + NumericDomainError::NotPositive => Some(NumericDomainErrorV1::NotPositive), + NumericDomainError::AboveOne => Some(NumericDomainErrorV1::AboveOne), + NumericDomainError::HueOutOfRange => None, + }; + match reason { + Some(reason) => Self { + kind: AppearanceContextErrorKindV1::Domain, + field: Some(field), + reason: Some(reason), + }, + None => Self { + kind: AppearanceContextErrorKindV1::InternalInvariant, + field: Some(field), + reason: None, + }, + } + } +} + +/// Неизменяемый допущенный контекст восприятия. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AppearanceContextV1(AppearanceContextId); + +impl AppearanceContextV1 { + const fn from_core(context: AppearanceContextId) -> Self { + Self(context) + } + + /// Допускает явные входы CIECAM16 для encoded sRGB8/D65. + /// + /// `background_luminance_ratio_yb_yw` — безразмерное `Y_b/Y_w` в `(0, 1]`, + /// а не абсолютная яркость. + pub fn try_new( + adapting_luminance_cd_m2: f64, + background_luminance_ratio_yb_yw: f64, + surround: SurroundV1, + ) -> Result { + let adapting_luminance_cd_m2 = AdaptingLuminanceCdM2::try_new(adapting_luminance_cd_m2) + .map_err(AppearanceContextErrorV1::from_core)?; + let background_luminance_ratio = + BackgroundLuminanceRatio::try_new(background_luminance_ratio_yb_yw) + .map_err(AppearanceContextErrorV1::from_core)?; + Ok(Self(AppearanceContextId::from_inputs( + AppearanceContextSchemaReleaseId::Ciecam16ViewingInputsV1, + IEC_SRGB_D65_XYZ_FRAME_V1, + adapting_luminance_cd_m2, + background_luminance_ratio, + surround.into_core(), + ))) + } + + /// Возвращает допущенную адаптирующую яркость в кд/м². + pub fn adapting_luminance_cd_m2(self) -> f64 { + self.0.adapting_luminance_cd_m2() + } + + /// Возвращает допущенное безразмерное отношение `Y_b/Y_w`. + pub fn background_luminance_ratio_yb_yw(self) -> f64 { + self.0.background_luminance_ratio() + } + + /// Возвращает зарегистрированный режим окружения. + pub const fn surround(self) -> SurroundV1 { + match self.0.surround_profile() { + SurroundProfileId::AverageV1 => SurroundV1::Average, + SurroundProfileId::DimV1 => SurroundV1::Dim, + SurroundProfileId::DarkV1 => SurroundV1::Dark, + } + } +} + +/// Закрытая классификация ошибки компиляции. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CompileErrorKindV1 { + /// Повторно объявлен исходный сигнал. + DuplicateSource, + /// Повторно объявлена цель. + DuplicateTarget, + /// В одной цели повторно объявлен ID кандидата. + DuplicateTargetCandidate, + /// Два кандидата одной цели задают одинаковый сигнал. + DuplicateTargetCandidateSignal, + /// Повторно объявлен вход прозрачности. + DuplicateOpacityInput, + /// Повторно объявлен входной порт поверхности. + DuplicateSurfaceInputPort, + /// Объявленный входной порт поверхности не используется. + UnusedSurfaceInputPort, + /// Один входной порт привязан к нескольким Surface. + DuplicateSurfaceInputBinding, + /// Повторно объявлен Paint. + DuplicatePaint, + /// Повторно объявлен Surface. + DuplicateSurface, + /// Повторно объявлен Occurrence. + DuplicateOccurrence, + /// Повторно объявлено ограничение. + DuplicateConstraint, + /// Повторно объявлен выходной слот. + DuplicateOutputSlot, + /// Цель ссылается на отсутствующий исходный сигнал. + MissingTargetSource, + /// Paint ссылается на отсутствующую цель. + MissingPaintTarget, + /// Paint ссылается на отсутствующий Paint. + MissingPaintSource, + /// Paint ссылается на отсутствующий вход прозрачности. + MissingPaintOpacityInput, + /// Surface ссылается на отсутствующий входной порт. + MissingSurfaceInputPort, + /// Surface ссылается на отсутствующий Occurrence. + MissingSurfaceOccurrence, + /// Occurrence ссылается на отсутствующий Paint. + MissingOccurrencePaint, + /// Occurrence ссылается на отсутствующий backdrop Surface. + MissingOccurrenceBackdrop, + /// Ограничение ссылается на отсутствующий Occurrence. + MissingConstraintOccurrence, + /// Выход ссылается на отсутствующий Paint. + MissingOutputPaint, + /// Граф Paint содержит цикл. + PaintCycle, + /// Граф рендера содержит цикл Surface/Occurrence. + RenderCycle, + /// Значение прозрачности находится вне `[0, 1]` или не конечно. + OpacityOutOfDomain, + /// Конечная цель не содержит кандидатов. + EmptyTargetDomain, + /// Конечная цель не участвует ни в одном ограничении. + UnconstrainedTarget, + /// Конечные цели образуют несвязанные компоненты. + DisconnectedFiniteTargets, + /// Выходной Paint не покрыт ни одним ограничением. + UnassessedOutput, + /// Для конечных целей не объявлен совместный порядок. + MissingJointSelection, + /// Совместный порядок объявлен без конечных целей. + JointSelectionWithoutTargets, + /// Состояние повторяет одну цель. + JointStateDuplicateTarget, + /// В состоянии отсутствует конечная цель. + JointStateMissingTarget, + /// Состояние ссылается на неизвестную цель. + JointStateUnknownTarget, + /// Состояние ссылается на неизвестного кандидата. + JointStateUnknownCandidate, + /// Совместный порядок не является полным конечным порядком. + InvalidJointOrder, + /// Не объявлено ни одного динамического входа поверхности. + EmptySurfaceInputPortSet, + /// Не объявлено ни одного физического Occurrence. + EmptyOccurrenceSet, + /// Не объявлено ни одного ограничения. + EmptyConstraintSet, + /// Не объявлено ни одного выхода. + EmptyOutputSet, + /// Для компиляции недостаточно памяти или адресного пространства. + ResourceExhausted, + /// Нарушен внутренний инвариант закрытого компилятора. + InternalInvariant, +} + +/// Типизированный ID узла, к которому относится ошибка компиляции. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CompileErrorHandleV1 { + /// Исходный сигнал. + Source(SourceIdV1), + /// Цель. + Target(TargetIdV1), + /// Кандидат цели. + TargetCandidate(TargetCandidateIdV1), + /// Вход прозрачности. + OpacityInput(OpacityInputIdV1), + /// Paint. + Paint(PaintIdV1), + /// Входной порт Surface. + SurfaceInputPort(SurfaceInputPortIdV1), + /// Surface. + Surface(SurfaceIdV1), + /// Occurrence. + Occurrence(OccurrenceIdV1), + /// Ограничение. + Constraint(ConstraintIdV1), + /// Выходной слот. + OutputSlot(OutputSlotIdV1), +} + +impl CompileErrorHandleV1 { + /// Возвращает клиентский числовой ключ независимо от пространства ID. + pub const fn value(self) -> u32 { + match self { + Self::Source(value) => value.value(), + Self::Target(value) => value.value(), + Self::TargetCandidate(value) => value.value(), + Self::OpacityInput(value) => value.value(), + Self::Paint(value) => value.value(), + Self::SurfaceInputPort(value) => value.value(), + Self::Surface(value) => value.value(), + Self::Occurrence(value) => value.value(), + Self::Constraint(value) => value.value(), + Self::OutputSlot(value) => value.value(), + } + } +} + +/// Точные участники одного цикла зависимостей Paint. +#[derive(Debug, PartialEq, Eq)] +pub struct PaintCycleV1 { + paints: Vec, +} + +impl PaintCycleV1 { + /// Возвращает участников цикла в каноническом порядке диагностики. + pub fn paints(&self) -> impl ExactSizeIterator + '_ { + self.paints.iter().copied().map(PaintIdV1::from_core) + } +} + +/// Точные участники одного цикла рендера. +#[derive(Debug, PartialEq, Eq)] +pub struct RenderCycleV1 { + surfaces: Vec, + occurrences: Vec, +} + +impl RenderCycleV1 { + /// Возвращает Surface-участников цикла. + pub fn surfaces(&self) -> impl ExactSizeIterator + '_ { + self.surfaces.iter().copied().map(SurfaceIdV1::from_core) + } + + /// Возвращает Occurrence-участников цикла. + pub fn occurrences(&self) -> impl ExactSizeIterator + '_ { + self.occurrences + .iter() + .copied() + .map(OccurrenceIdV1::from_core) + } +} + +/// Точная причина отказа явно объявленного конечного совместного порядка. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum JointOrderErrorV1 { + /// Одно измерение не содержит кандидатов. + EmptyDomain { + /// Индекс пустого измерения. + dimension: usize, + }, + /// Декартова мощность измерений не представима в `usize`. + CardinalityOverflow, + /// Явный порядок не содержит состояний. + EmptyOrder, + /// Состояние содержит неверное число измерений. + TupleArity { + /// Индекс состояния. + state: usize, + /// Требуемое число измерений. + expected: usize, + /// Фактическое число измерений. + actual: usize, + }, + /// Ординал кандидата выходит за домен измерения. + OrdinalOutOfDomain { + /// Индекс состояния. + state: usize, + /// Индекс измерения. + dimension: usize, + /// Некорректный ординал. + ordinal: usize, + /// Мощность домена измерения. + domain_len: usize, + }, + /// Два состояния задают один и тот же кортеж. + DuplicateTuple { + /// Индекс первого состояния. + first_state: usize, + /// Индекс повторного состояния. + duplicate_state: usize, + }, + /// Порядок не покрывает полный декартов домен. + IncompleteOrder { + /// Требуемое число состояний. + expected: usize, + /// Фактическое число состояний. + actual: usize, + }, + /// Для проверки порядка недостаточно ресурсов. + ResourceExhausted, +} + +/// Атомарная и полная ошибка компиляции объявленной программы. +/// +/// Enum авторитетен; [`Self::kind`], [`Self::primary_handle`] и +/// [`Self::related_handle`] — только удобные проекции полного payload. +#[derive(Debug, PartialEq, Eq)] +pub enum CompileErrorV1 { + /// Повторно объявлен исходный сигнал. + DuplicateSource { + /// Повторный ID. + source: SourceIdV1, + }, + /// Повторно объявлена цель. + DuplicateTarget { + /// Повторный ID. + target: TargetIdV1, + }, + /// Цель ссылается на отсутствующий исходный сигнал. + MissingTargetSource { + /// Ошибочная цель. + target: TargetIdV1, + /// Отсутствующий исходный сигнал. + source: SourceIdV1, + }, + /// Повторно объявлен вход прозрачности. + DuplicateOpacityInput { + /// Повторный ID. + input: OpacityInputIdV1, + }, + /// Повторно объявлен входной порт поверхности. + DuplicateSurfaceInputPort { + /// Повторный ID. + input: SurfaceInputPortIdV1, + }, + /// Объявленный входной порт не используется. + UnusedSurfaceInputPort { + /// Неиспользуемый ID. + input: SurfaceInputPortIdV1, + }, + /// Один входной порт привязан к двум Surface. + DuplicateSurfaceInputBinding { + /// Повторно привязанный порт. + input: SurfaceInputPortIdV1, + /// Первая Surface. + first: SurfaceIdV1, + /// Повторная Surface. + duplicate: SurfaceIdV1, + }, + /// Повторно объявлен Paint. + DuplicatePaint { + /// Повторный ID. + paint: PaintIdV1, + }, + /// Повторно объявлен Surface. + DuplicateSurface { + /// Повторный ID. + surface: SurfaceIdV1, + }, + /// Повторно объявлен Occurrence. + DuplicateOccurrence { + /// Повторный ID. + occurrence: OccurrenceIdV1, + }, + /// Paint ссылается на отсутствующую цель. + MissingPaintTarget { + /// Ошибочный Paint. + paint: PaintIdV1, + /// Отсутствующая цель. + target: TargetIdV1, + }, + /// Paint ссылается на отсутствующий исходный Paint. + MissingPaintSource { + /// Ошибочный Paint. + paint: PaintIdV1, + /// Отсутствующий исходный Paint. + source: PaintIdV1, + }, + /// Paint ссылается на отсутствующий вход прозрачности. + MissingPaintOpacityInput { + /// Ошибочный Paint. + paint: PaintIdV1, + /// Отсутствующий вход. + input: OpacityInputIdV1, + }, + /// Surface ссылается на отсутствующий входной порт. + MissingSurfaceInputPort { + /// Ошибочная Surface. + surface: SurfaceIdV1, + /// Отсутствующий порт. + input: SurfaceInputPortIdV1, + }, + /// Surface ссылается на отсутствующий Occurrence. + MissingSurfaceOccurrence { + /// Ошибочная Surface. + surface: SurfaceIdV1, + /// Отсутствующий Occurrence. + occurrence: OccurrenceIdV1, + }, + /// Occurrence ссылается на отсутствующий Paint. + MissingOccurrencePaint { + /// Ошибочный Occurrence. + occurrence: OccurrenceIdV1, + /// Отсутствующий Paint. + paint: PaintIdV1, + }, + /// Occurrence ссылается на отсутствующий backdrop Surface. + MissingOccurrenceBackdrop { + /// Ошибочный Occurrence. + occurrence: OccurrenceIdV1, + /// Отсутствующая Surface. + surface: SurfaceIdV1, + }, + /// Обнаружен цикл зависимостей Paint. + PaintCycle(PaintCycleV1), + /// Обнаружен цикл Surface/Occurrence. + RenderCycle(RenderCycleV1), + /// Вход прозрачности не является конечным числом в `[0, 1]`. + OpacityOutOfDomain { + /// Ошибочный вход. + input: OpacityInputIdV1, + }, + /// Конечная цель не содержит кандидатов. + EmptyTargetDomain { + /// Пустая цель. + target: TargetIdV1, + }, + /// В одной цели повторно объявлен ID кандидата. + DuplicateTargetCandidate { + /// Цель кандидата. + target: TargetIdV1, + /// Повторный кандидат. + candidate: TargetCandidateIdV1, + }, + /// Два кандидата одной цели имеют одинаковый физический сигнал. + DuplicateTargetCandidateSignal { + /// Цель кандидатов. + target: TargetIdV1, + /// Первый кандидат. + first: TargetCandidateIdV1, + /// Повторный кандидат. + duplicate: TargetCandidateIdV1, + /// Совпавший encoded sRGB8 сигнал. + encoded_srgb8: Srgb8, + }, + /// Конечная цель не участвует ни в одном ограничении. + UnconstrainedTarget { + /// Неограниченная цель. + target: TargetIdV1, + }, + /// Конечные цели образуют несвязанные компоненты. + DisconnectedFiniteTargets, + /// Выходной Paint не покрыт ни одним ограничением. + UnassessedOutput { + /// Непроверенный выход. + output: OutputSlotIdV1, + /// Его Paint. + paint: PaintIdV1, + }, + /// Для конечных целей не объявлен совместный порядок. + MissingJointSelection, + /// Совместный порядок объявлен без конечных целей. + JointSelectionWithoutTargets, + /// Состояние повторяет одну цель. + JointStateDuplicateTarget { + /// Индекс состояния. + state: usize, + /// Повторная цель. + target: TargetIdV1, + }, + /// В состоянии отсутствует конечная цель. + JointStateMissingTarget { + /// Индекс состояния. + state: usize, + /// Отсутствующая цель. + target: TargetIdV1, + }, + /// Состояние ссылается на неизвестную цель. + JointStateUnknownTarget { + /// Индекс состояния. + state: usize, + /// Неизвестная цель. + target: TargetIdV1, + }, + /// Состояние ссылается на неизвестного кандидата. + JointStateUnknownCandidate { + /// Индекс состояния. + state: usize, + /// Цель кандидата. + target: TargetIdV1, + /// Неизвестный кандидат. + candidate: TargetCandidateIdV1, + }, + /// Явный совместный порядок не является полным конечным порядком. + InvalidJointOrder(JointOrderErrorV1), + /// Не объявлено ни одного динамического входа поверхности. + EmptySurfaceInputPortSet, + /// Не объявлено ни одного физического Occurrence. + EmptyOccurrenceSet, + /// Не объявлено ни одного ограничения. + EmptyConstraintSet, + /// Не объявлено ни одного выхода. + EmptyOutputSet, + /// Повторно объявлено ограничение. + DuplicateConstraint { + /// Повторный ID. + constraint: ConstraintIdV1, + }, + /// Ограничение ссылается на отсутствующий Occurrence. + MissingConstraintOccurrence { + /// Ошибочное ограничение. + constraint: ConstraintIdV1, + /// Отсутствующий Occurrence. + occurrence: OccurrenceIdV1, + }, + /// Повторно объявлен выходной слот. + DuplicateOutputSlot { + /// Повторный ID. + output: OutputSlotIdV1, + }, + /// Выход ссылается на отсутствующий Paint. + MissingOutputPaint { + /// Ошибочный выход. + output: OutputSlotIdV1, + /// Отсутствующий Paint. + paint: PaintIdV1, + }, + /// Для компиляции недостаточно ресурсов. + ResourceExhausted, + /// Нарушен внутренний инвариант закрытого компилятора. + InternalInvariant, +} + +impl CompileErrorV1 { + /// Возвращает стабильный класс ошибки без потери полного payload. + pub const fn kind(&self) -> CompileErrorKindV1 { + use CompileErrorKindV1 as Kind; + + match self { + Self::DuplicateSource { .. } => Kind::DuplicateSource, + Self::DuplicateTarget { .. } => Kind::DuplicateTarget, + Self::MissingTargetSource { .. } => Kind::MissingTargetSource, + Self::DuplicateOpacityInput { .. } => Kind::DuplicateOpacityInput, + Self::DuplicateSurfaceInputPort { .. } => Kind::DuplicateSurfaceInputPort, + Self::UnusedSurfaceInputPort { .. } => Kind::UnusedSurfaceInputPort, + Self::DuplicateSurfaceInputBinding { .. } => Kind::DuplicateSurfaceInputBinding, + Self::DuplicatePaint { .. } => Kind::DuplicatePaint, + Self::DuplicateSurface { .. } => Kind::DuplicateSurface, + Self::DuplicateOccurrence { .. } => Kind::DuplicateOccurrence, + Self::MissingPaintTarget { .. } => Kind::MissingPaintTarget, + Self::MissingPaintSource { .. } => Kind::MissingPaintSource, + Self::MissingPaintOpacityInput { .. } => Kind::MissingPaintOpacityInput, + Self::MissingSurfaceInputPort { .. } => Kind::MissingSurfaceInputPort, + Self::MissingSurfaceOccurrence { .. } => Kind::MissingSurfaceOccurrence, + Self::MissingOccurrencePaint { .. } => Kind::MissingOccurrencePaint, + Self::MissingOccurrenceBackdrop { .. } => Kind::MissingOccurrenceBackdrop, + Self::PaintCycle(_) => Kind::PaintCycle, + Self::RenderCycle(_) => Kind::RenderCycle, + Self::OpacityOutOfDomain { .. } => Kind::OpacityOutOfDomain, + Self::EmptyTargetDomain { .. } => Kind::EmptyTargetDomain, + Self::DuplicateTargetCandidate { .. } => Kind::DuplicateTargetCandidate, + Self::DuplicateTargetCandidateSignal { .. } => Kind::DuplicateTargetCandidateSignal, + Self::UnconstrainedTarget { .. } => Kind::UnconstrainedTarget, + Self::DisconnectedFiniteTargets => Kind::DisconnectedFiniteTargets, + Self::UnassessedOutput { .. } => Kind::UnassessedOutput, + Self::MissingJointSelection => Kind::MissingJointSelection, + Self::JointSelectionWithoutTargets => Kind::JointSelectionWithoutTargets, + Self::JointStateDuplicateTarget { .. } => Kind::JointStateDuplicateTarget, + Self::JointStateMissingTarget { .. } => Kind::JointStateMissingTarget, + Self::JointStateUnknownTarget { .. } => Kind::JointStateUnknownTarget, + Self::JointStateUnknownCandidate { .. } => Kind::JointStateUnknownCandidate, + Self::InvalidJointOrder(_) => Kind::InvalidJointOrder, + Self::EmptySurfaceInputPortSet => Kind::EmptySurfaceInputPortSet, + Self::EmptyOccurrenceSet => Kind::EmptyOccurrenceSet, + Self::EmptyConstraintSet => Kind::EmptyConstraintSet, + Self::EmptyOutputSet => Kind::EmptyOutputSet, + Self::DuplicateConstraint { .. } => Kind::DuplicateConstraint, + Self::MissingConstraintOccurrence { .. } => Kind::MissingConstraintOccurrence, + Self::DuplicateOutputSlot { .. } => Kind::DuplicateOutputSlot, + Self::MissingOutputPaint { .. } => Kind::MissingOutputPaint, + Self::ResourceExhausted => Kind::ResourceExhausted, + Self::InternalInvariant => Kind::InternalInvariant, + } + } + + /// Возвращает основной типизированный ID, если ошибка локализуема одним узлом. + pub const fn primary_handle(&self) -> Option { + use CompileErrorHandleV1 as Handle; + + match self { + Self::DuplicateSource { source } => Some(Handle::Source(*source)), + Self::DuplicateTarget { target } + | Self::EmptyTargetDomain { target } + | Self::UnconstrainedTarget { target } + | Self::JointStateDuplicateTarget { target, .. } + | Self::JointStateMissingTarget { target, .. } + | Self::JointStateUnknownTarget { target, .. } + | Self::JointStateUnknownCandidate { target, .. } + | Self::MissingTargetSource { target, .. } + | Self::DuplicateTargetCandidate { target, .. } + | Self::DuplicateTargetCandidateSignal { target, .. } => Some(Handle::Target(*target)), + Self::DuplicateOpacityInput { input } | Self::OpacityOutOfDomain { input } => { + Some(Handle::OpacityInput(*input)) + } + Self::DuplicateSurfaceInputPort { input } + | Self::UnusedSurfaceInputPort { input } + | Self::DuplicateSurfaceInputBinding { input, .. } => { + Some(Handle::SurfaceInputPort(*input)) + } + Self::DuplicatePaint { paint } + | Self::MissingPaintTarget { paint, .. } + | Self::MissingPaintSource { paint, .. } + | Self::MissingPaintOpacityInput { paint, .. } => Some(Handle::Paint(*paint)), + Self::DuplicateSurface { surface } + | Self::MissingSurfaceInputPort { surface, .. } + | Self::MissingSurfaceOccurrence { surface, .. } => Some(Handle::Surface(*surface)), + Self::DuplicateOccurrence { occurrence } + | Self::MissingOccurrencePaint { occurrence, .. } + | Self::MissingOccurrenceBackdrop { occurrence, .. } => { + Some(Handle::Occurrence(*occurrence)) + } + Self::UnassessedOutput { output, .. } + | Self::DuplicateOutputSlot { output } + | Self::MissingOutputPaint { output, .. } => Some(Handle::OutputSlot(*output)), + Self::DuplicateConstraint { constraint } + | Self::MissingConstraintOccurrence { constraint, .. } => { + Some(Handle::Constraint(*constraint)) + } + Self::PaintCycle(_) + | Self::RenderCycle(_) + | Self::DisconnectedFiniteTargets + | Self::MissingJointSelection + | Self::JointSelectionWithoutTargets + | Self::InvalidJointOrder(_) + | Self::EmptySurfaceInputPortSet + | Self::EmptyOccurrenceSet + | Self::EmptyConstraintSet + | Self::EmptyOutputSet + | Self::ResourceExhausted + | Self::InternalInvariant => None, + } + } + + /// Возвращает связанный типизированный ID для ошибки отношения двух узлов. + pub const fn related_handle(&self) -> Option { + use CompileErrorHandleV1 as Handle; + + match self { + Self::MissingTargetSource { source, .. } => Some(Handle::Source(*source)), + Self::DuplicateSurfaceInputBinding { duplicate, .. } => { + Some(Handle::Surface(*duplicate)) + } + Self::MissingPaintTarget { target, .. } => Some(Handle::Target(*target)), + Self::MissingPaintSource { source, .. } => Some(Handle::Paint(*source)), + Self::MissingPaintOpacityInput { input, .. } => Some(Handle::OpacityInput(*input)), + Self::MissingSurfaceInputPort { input, .. } => Some(Handle::SurfaceInputPort(*input)), + Self::MissingSurfaceOccurrence { occurrence, .. } + | Self::MissingConstraintOccurrence { occurrence, .. } => { + Some(Handle::Occurrence(*occurrence)) + } + Self::MissingOccurrencePaint { paint, .. } + | Self::UnassessedOutput { paint, .. } + | Self::MissingOutputPaint { paint, .. } => Some(Handle::Paint(*paint)), + Self::MissingOccurrenceBackdrop { surface, .. } => Some(Handle::Surface(*surface)), + Self::DuplicateTargetCandidate { candidate, .. } + | Self::DuplicateTargetCandidateSignal { + duplicate: candidate, + .. + } + | Self::JointStateUnknownCandidate { candidate, .. } => { + Some(Handle::TargetCandidate(*candidate)) + } + Self::DuplicateSource { .. } + | Self::DuplicateTarget { .. } + | Self::DuplicateOpacityInput { .. } + | Self::DuplicateSurfaceInputPort { .. } + | Self::UnusedSurfaceInputPort { .. } + | Self::DuplicatePaint { .. } + | Self::DuplicateSurface { .. } + | Self::DuplicateOccurrence { .. } + | Self::PaintCycle(_) + | Self::RenderCycle(_) + | Self::OpacityOutOfDomain { .. } + | Self::EmptyTargetDomain { .. } + | Self::UnconstrainedTarget { .. } + | Self::DisconnectedFiniteTargets + | Self::MissingJointSelection + | Self::JointSelectionWithoutTargets + | Self::JointStateDuplicateTarget { .. } + | Self::JointStateMissingTarget { .. } + | Self::JointStateUnknownTarget { .. } + | Self::InvalidJointOrder(_) + | Self::EmptySurfaceInputPortSet + | Self::EmptyOccurrenceSet + | Self::EmptyConstraintSet + | Self::EmptyOutputSet + | Self::DuplicateConstraint { .. } + | Self::DuplicateOutputSlot { .. } + | Self::ResourceExhausted + | Self::InternalInvariant => None, + } + } +} + +/// Холодный декларативный builder канонической Program IR. +#[must_use] +pub struct DraftV1 { + inner: CoreProgramDraftV1, +} + +/// Ошибка изменения Draft до компиляции. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DraftErrorV1 { + /// Совместный порядок уже объявлен и не может быть молча заменён. + JointSelectionAlreadyDeclared, +} + +impl DraftV1 { + /// Создаёт пустой Draft. + pub fn new() -> Self { + Self { + inner: CoreProgramDraftV1::new(), + } + } + + /// Объявляет неизменяемый исходный encoded sRGB8 сигнал. + pub fn push_source(&mut self, id: SourceIdV1, source: Srgb8) -> &mut Self { + self.inner + .push_source(Source::new(id.into_core(), ColorSignal::from_srgb8(source))); + self + } + + /// Объявляет цель, физически равную исходному сигналу. + pub fn push_fixed_target(&mut self, id: TargetIdV1, source: SourceIdV1) -> &mut Self { + self.inner + .push_target(Target::fixed(id.into_core(), source.into_core())); + self + } + + /// Объявляет решаемую цель с конечным набором физических кандидатов. + pub fn push_finite_target( + &mut self, + id: TargetIdV1, + source: SourceIdV1, + candidates: Vec, + ) -> &mut Self { + self.inner.push_target(Target::finite( + id.into_core(), + source.into_core(), + candidates + .into_iter() + .map(|candidate| candidate.0) + .collect(), + )); + self + } + + /// Один раз задаёт полный порядок совместных состояний конечных целей. + pub fn set_joint_selection( + &mut self, + states: Vec, + ) -> Result<&mut Self, DraftErrorV1> { + self.inner + .set_joint_selection(DeclaredJointSelectionV1::new( + states.into_iter().map(|state| state.0).collect(), + )) + .map_err(|error| match error { + CoreProgramDraftErrorV1::JointSelectionAlreadyDeclared => { + DraftErrorV1::JointSelectionAlreadyDeclared + } + })?; + Ok(self) + } + + /// Объявляет один динамический вход поверхности. + pub fn push_surface_input_port(&mut self, input: SurfaceInputPortIdV1) -> &mut Self { + self.inner.push_surface_input_port(input.into_core()); + self + } + + /// Объявляет числовой вход прозрачности; домен проверяется при компиляции. + pub fn push_opacity_input(&mut self, id: OpacityInputIdV1, value: f64) -> &mut Self { + self.inner + .push_opacity_input(OpacityInput::new(id.into_core(), value)); + self + } + + /// Объявляет непрозрачный Paint, связанный с целью. + pub fn push_solid_paint(&mut self, id: PaintIdV1, target: TargetIdV1) -> &mut Self { + self.inner.push_paint(Paint::Solid { + id: id.into_core(), + target: target.into_core(), + }); + self + } + + /// Объявляет Paint как прозрачную версию другого Paint. + pub fn push_opacity_paint( + &mut self, + id: PaintIdV1, + source: PaintIdV1, + opacity: OpacityInputIdV1, + ) -> &mut Self { + self.inner.push_paint(Paint::Opacity { + id: id.into_core(), + source: source.into_core(), + opacity: opacity.into_core(), + }); + self + } + + /// Объявляет Surface, значение которой поступает из runtime-сценария. + pub fn push_input_surface( + &mut self, + id: SurfaceIdV1, + input: SurfaceInputPortIdV1, + ) -> &mut Self { + self.inner.push_surface(Surface::Input { + id: id.into_core(), + input: input.into_core(), + }); + self + } + + /// Объявляет Surface как видимый результат другого Occurrence. + pub fn push_occurrence_surface( + &mut self, + id: SurfaceIdV1, + occurrence: OccurrenceIdV1, + ) -> &mut Self { + self.inner.push_surface(Surface::FromOccurrence { + id: id.into_core(), + occurrence: occurrence.into_core(), + }); + self + } + + /// Объявляет encoded-sRGB8 source-over Occurrence в явном контексте. + pub fn push_source_over_occurrence( + &mut self, + id: OccurrenceIdV1, + subject: PaintIdV1, + against: SurfaceIdV1, + context: AppearanceContextV1, + ) -> &mut Self { + self.inner.push_occurrence(Occurrence::new( + id.into_core(), + subject.into_core(), + against.into_core(), + CompositionProfile::EncodedSrgb8SourceOverV1, + context.0, + )); + self + } + + /// Добавляет обязательное точное сравнение видимого sRGB8 результата. + pub fn push_exact_hard( + &mut self, + id: ConstraintIdV1, + occurrence: OccurrenceIdV1, + expected: Srgb8, + ) -> &mut Self { + self.inner.push_hard_constraint(ConstraintInvocation::hard( + id.into_core(), + occurrence.into_core(), + CoreProgramConstraintInvocationV1::ExactSrgb8(expected), + )); + self + } + + /// Добавляет диагностическое точное сравнение, не влияющее на выбор. + pub fn push_exact_report_only( + &mut self, + id: ConstraintIdV1, + occurrence: OccurrenceIdV1, + expected: Srgb8, + ) -> &mut Self { + self.inner + .push_report_constraint(ConstraintInvocation::report_only( + id.into_core(), + occurrence.into_core(), + CoreProgramConstraintInvocationV1::ExactSrgb8(expected), + )); + self + } + + /// Добавляет обязательный критерий WCAG 2.2 для видимого результата. + pub fn push_wcag22_hard( + &mut self, + id: ConstraintIdV1, + occurrence: OccurrenceIdV1, + criterion: Wcag22CriterionV1, + ) -> &mut Self { + self.inner.push_hard_constraint(ConstraintInvocation::hard( + id.into_core(), + occurrence.into_core(), + CoreProgramConstraintInvocationV1::Wcag22Srgb8(criterion), + )); + self + } + + /// Добавляет диагностический критерий WCAG 2.2, не влияющий на выбор. + pub fn push_wcag22_report_only( + &mut self, + id: ConstraintIdV1, + occurrence: OccurrenceIdV1, + criterion: Wcag22CriterionV1, + ) -> &mut Self { + self.inner + .push_report_constraint(ConstraintInvocation::report_only( + id.into_core(), + occurrence.into_core(), + CoreProgramConstraintInvocationV1::Wcag22Srgb8(criterion), + )); + self + } + + /// Связывает клиентский выходной слот с итоговым Paint. + pub fn push_output(&mut self, output: OutputSlotIdV1, paint: PaintIdV1) -> &mut Self { + self.inner + .push_output(OutputBinding::new(output.into_core(), paint.into_core())); + self + } + + /// Атомарно проверяет и компилирует весь граф. + pub fn compile(self) -> Result { + let compiled = self.inner.compile().map_err(map_program_compile_error)?; + Ok(OwnerV1::from_compiled(compiled)) + } +} + +impl Default for DraftV1 { + fn default() -> Self { + Self::new() + } +} + +/// Непрозрачный сильный владелец одной точной скомпилированной Program. +/// +/// Созданные им Session изменяются только через эту же аллокацию. Уничтожение +/// Owner отзывает обновления и операции, но исторические evidence остаются в +/// Session. +pub struct OwnerV1 { + compiled: CompiledCoreProgramV1, +} + +/// Отказ доступа из-за несовпадения точной owner-эпохи. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AccessErrorV1 { + /// Session была создана другой аллокацией Owner. + OwnerMismatch, +} + +impl OwnerV1 { + /// Внутренняя передача из канонического компилятора. + pub(crate) const fn from_compiled(compiled: CompiledCoreProgramV1) -> Self { + Self { compiled } + } + + /// Возвращает каноническую identity скомпилированного содержания. + /// + /// Identity доступна до первого update, но не заменяет полномочия этой + /// конкретной owner-эпохи. + pub fn content_identity(&self) -> ContentIdentityV1 { + ContentIdentityV1::from_core(self.compiled.content_identity()) + } + + /// Число значений Surface в каждом schema-ordered сценарии. + pub fn surface_input_port_count(&self) -> usize { + self.compiled.surface_input_ports().len() + } + + /// Канонический порядок входных портов для однократного binding на хосте. + pub fn surface_input_ports(&self) -> impl ExactSizeIterator + '_ { + self.compiled + .surface_input_ports() + .iter() + .copied() + .map(SurfaceInputPortIdV1::from_core) + } + + /// Канонический порядок непрозрачных выходных слотов. + pub fn output_slots(&self) -> impl ExactSizeIterator + '_ { + self.compiled + .outputs() + .map(|(slot, _paint)| OutputSlotIdV1::from_core(slot)) + } + + /// Проецирует операции только для Session этой точной owner-эпохи. + /// + /// Равенство [`ContentIdentityV1`] не даёт полномочий. + pub fn project<'owner, 'session>( + &'owner self, + session: &'session SessionV1, + ) -> Result, AccessErrorV1> { + if !self.compiled.owns_session(&session.session) { + return Err(AccessErrorV1::OwnerMismatch); + } + Ok(ProjectionV1 { + evidence: session.evidence(), + owner: self, + scope: BorrowScopeV1::new(self, session), + }) + } + + /// Атомарно допускает update и возвращает его неизменяемую проекцию. + /// + /// Несовпадение Owner проверяется до admission, аллокаций и вычисления. + pub fn update<'owner, 'session>( + &'owner self, + session: &'session mut SessionV1, + update: UpdateV1<'_>, + ) -> Result, UpdateErrorV1> { + if !self.compiled.owns_session(&session.session) { + return Err(UpdateErrorV1::OwnerMismatch); + } + session.apply_update(update)?; + Ok(ProjectionV1 { + evidence: session.evidence(), + owner: self, + scope: BorrowScopeV1::new(self, session), + }) + } + + /// Создаёт Session, привязанную к одному непрозрачному stream ID. + pub fn instantiate(&self, stream_id: u32) -> Result { + let stream = ObservationStreamId::new(stream_id); + let session = self + .compiled + .instantiate(stream) + .map_err(InstantiateErrorV1::from_core)?; + Ok(SessionV1 { + scenario_order_scratch: Vec::new(), + session, + }) + } +} + +/// Один заимствованный физический сценарий в скомпилированном schema order. +/// +/// ID сценария — непрозрачная provenance. `values` содержит ровно один encoded +/// sRGB8 на каждый [`OwnerV1::surface_input_ports`] в том же порядке. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ScenarioV1<'a> { + scenario_id: u32, + values: &'a [Srgb8], +} + +impl<'a> ScenarioV1<'a> { + /// Создаёт один одновременный физический кортеж. + pub const fn new(scenario_id: u32, values: &'a [Srgb8]) -> Self { + Self { + scenario_id, + values, + } + } +} + +/// Одно revision-bound обновление; stream принадлежит Session. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpdateV1<'a> { + /// Согласованные физические сценарии в порядке скомпилированной схемы. + Observed { + /// Монотонная ревизия входного наблюдения. + revision: u64, + /// Одновременные сценарии физического контекста. + scenarios: &'a [ScenarioV1<'a>], + }, + /// Наблюдение явно недоступно; Core не изобретает фон. + Unknown { + /// Монотонная ревизия входного наблюдения. + revision: u64, + /// Непрозрачная клиентская причина недоступности. + reason_id: u32, + }, +} + +/// Непрозрачная изменяемая Session одной Program и одного stream. +pub struct SessionV1 { + scenario_order_scratch: Vec, + session: CoreProgramSessionV1, +} + +impl SessionV1 { + /// Возвращает исторические evidence без права на операции. + pub fn evidence(&self) -> EvidenceViewV1<'_> { + EvidenceViewV1 { + session: &self.session, + } + } + + fn apply_update(&mut self, update: UpdateV1<'_>) -> Result<(), UpdateErrorV1> { + match update { + UpdateV1::Observed { + revision, + scenarios, + } => { + let source = ScenarioSourceV1(scenarios); + self.session + .update_schema_ordered( + Revision::new(revision), + &source, + &mut self.scenario_order_scratch, + ) + .map_err(map_session_update_error)?; + } + UpdateV1::Unknown { + revision, + reason_id, + } => { + self.session + .update_unknown(Revision::new(revision), UnknownReasonId::new(reason_id)) + .map_err(map_session_update_error)?; + } + } + Ok(()) + } +} + +struct ScenarioSourceV1<'a>(&'a [ScenarioV1<'a>]); + +impl SchemaOrderedScenarioSourceV1 for ScenarioSourceV1<'_> { + fn scenario_count(&self) -> usize { + self.0.len() + } + + fn scenario_id(&self, scenario_index: usize) -> ScenarioId { + ScenarioId::new(self.0[scenario_index].scenario_id) + } + + fn value_count(&self, scenario_index: usize) -> usize { + self.0[scenario_index].values.len() + } + + fn value(&self, scenario_index: usize, binding_index: usize) -> Srgb8 { + self.0[scenario_index].values[binding_index] + } +} + +/// Закрытая классификация lifecycle Session. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StateKindV1 { + /// Допущенного вычислимого наблюдения ещё нет. + Waiting, + /// Текущая ревизия сертифицирована. + Ready, + /// Новое наблюдение недоступно, сохранён прошлый сертификат. + Stale, + /// Текущая ревизия имеет исчерпывающий конфликт. + Failed, +} + +/// Текущая сырая голова наблюдений независимо от evaluator lifecycle. +/// +/// Непустая голова хранит stream provenance, но не полномочия на операции. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ObservationHeadV1 { + /// Наблюдений ещё не было. + Empty, + /// Наблюдение явно недоступно. + Unknown { + /// Stream наблюдения. + stream: StreamIdV1, + /// Ревизия наблюдения. + revision: u64, + /// Непрозрачная причина недоступности. + reason_id: u32, + }, + /// Принят физический набор сценариев. + Observed { + /// Stream наблюдения. + stream: StreamIdV1, + /// Ревизия наблюдения. + revision: u64, + }, +} + +/// Заимствованное историческое evidence, принадлежащее Session. +#[derive(Clone, Copy)] +pub struct EvidenceViewV1<'a> { + session: &'a CoreProgramSessionV1, +} + +impl<'a> EvidenceViewV1<'a> { + const fn state(self) -> &'a CoreProgramStateV1 { + self.session.state() + } + + /// Возвращает lifecycle-класс текущего состояния. + pub const fn kind(self) -> StateKindV1 { + match self.state() { + SessionState::Waiting => StateKindV1::Waiting, + SessionState::Ready { .. } => StateKindV1::Ready, + SessionState::Stale { .. } => StateKindV1::Stale, + SessionState::Failed { .. } => StateKindV1::Failed, + } + } + + /// Возвращает сырую голову наблюдений вместе с provenance. + pub fn observation_head(self) -> ObservationHeadV1 { + match self.session.raw_head() { + ObservationHeadViewV1::Empty => ObservationHeadV1::Empty, + ObservationHeadViewV1::Unknown(unknown) => ObservationHeadV1::Unknown { + stream: StreamIdV1::from_core(unknown.stream()), + revision: unknown.revision().value(), + reason_id: unknown.reason().value(), + }, + ObservationHeadViewV1::Observed(observation) => ObservationHeadV1::Observed { + stream: StreamIdV1::from_core(observation.stream()), + revision: observation.revision().value(), + }, + } + } + + /// Индекс cause-сертификата в [`Self::certificates`] для `Failed`. + pub const fn cause_certificate_index(self) -> Option { + match self.state() { + SessionState::Failed { .. } => Some(0), + SessionState::Waiting | SessionState::Ready { .. } | SessionState::Stale { .. } => None, + } + } + + /// Сертификаты в каноническом порядке одного снимка. + pub fn certificates( + self, + ) -> impl ExactSizeIterator> + FusedIterator + 'a { + let (first, second) = match self.state() { + SessionState::Waiting => (None, None), + SessionState::Ready { current } | SessionState::Stale { previous: current } => { + (Some(CertificateV1::verified(current)), None) + } + SessionState::Failed { cause, previous } => ( + Some(CertificateV1::conflict(cause)), + previous.as_ref().map(CertificateV1::verified), + ), + }; + CertificatesV1::new(first, second) + } +} + +/// Нулевой lifetime-маркер точной пары Owner и неизменяемого снимка Session. +#[derive(Clone, Copy)] +struct BorrowScopeV1<'owner, 'session> { + _scope: PhantomData<(&'owner OwnerV1, &'session SessionV1)>, +} + +impl<'owner, 'session> BorrowScopeV1<'owner, 'session> { + const fn new(_owner: &'owner OwnerV1, _session: &'session SessionV1) -> Self { + Self { + _scope: PhantomData, + } + } +} + +/// Проверенная Owner-and-snapshot проекция evidence и операций. +#[derive(Clone, Copy)] +pub struct ProjectionV1<'owner, 'session> { + evidence: EvidenceViewV1<'session>, + owner: &'owner OwnerV1, + scope: BorrowScopeV1<'owner, 'session>, +} + +impl<'owner, 'session> ProjectionV1<'owner, 'session> { + /// Возвращает историческое evidence этого снимка. + pub const fn evidence(self) -> EvidenceViewV1<'session> { + self.evidence + } + + /// Возвращает полную каноническую последовательность операций состояния. + pub fn operations( + self, + ) -> impl ExactSizeIterator> + FusedIterator { + let inner = match self.evidence.state() { + SessionState::Waiting => OperationSourceV1::Empty, + SessionState::Ready { current } => { + debug_assert_eq!(current.outputs().len(), self.owner.compiled.output_count()); + debug_assert!( + current + .outputs() + .iter() + .enumerate() + .all(|(index, output)| self.owner.compiled.output_slot_at(index) + == Some(output.output())) + ); + OperationSourceV1::Set { + outputs: current.outputs().iter(), + certificate: VerifiedCertificateV1 { inner: current }, + scope: self.scope, + } + } + SessionState::Stale { previous } => OperationSourceV1::Hold { + outputs: previous.outputs().iter(), + certificate: VerifiedCertificateV1 { inner: previous }, + scope: self.scope, + }, + SessionState::Failed { + previous: Some(previous), + .. + } => OperationSourceV1::Hold { + outputs: previous.outputs().iter(), + certificate: VerifiedCertificateV1 { inner: previous }, + scope: self.scope, + }, + SessionState::Failed { previous: None, .. } => OperationSourceV1::Remove { + slots: OwnerOutputSlotsV1::new(&self.owner.compiled), + scope: self.scope, + }, + }; + OperationsV1 { inner } + } +} + +/// Collision-resistant адрес канонического физического содержания Program. +/// +/// Identity не идентифицирует owner-эпоху и не даёт runtime-полномочий. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ContentIdentityV1([u8; 32]); + +impl ContentIdentityV1 { + const fn from_core(value: ProgramContentIdentityV1) -> Self { + Self(*value.as_bytes()) + } + + /// Возвращает 256-битное каноническое представление identity. + pub const fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +/// Доказательство прохождения всех hard-клеток на полном physical support. +#[derive(Clone, Copy)] +pub struct VerifiedCertificateV1<'a> { + inner: &'a CoreVerifiedV1, +} + +impl<'a> VerifiedCertificateV1<'a> { + /// Возвращает identity скомпилированного содержания. + pub const fn content_identity(self) -> ContentIdentityV1 { + ContentIdentityV1::from_core(self.inner.report().content_identity()) + } + + /// Возвращает точное наблюдение, на котором выдан сертификат. + pub const fn observation(self) -> ObservationV1<'a> { + ObservationV1 { + inner: self.inner.report().observation(), + } + } + + /// Возвращает индекс выбранного состояния или `None` для fixed Program. + pub const fn selected_state_index(self) -> Option { + self.inner.selected_state_index() + } + + /// Возвращает все `case × constraint` клетки выбранного состояния. + pub fn cells(self) -> impl ExactSizeIterator> + FusedIterator + 'a { + self.inner + .report() + .cells() + .iter() + .map(VerifiedCellV1::from_core) + } + + /// Возвращает все сертифицированные выходы в каноническом порядке. + pub fn outputs( + self, + ) -> impl ExactSizeIterator> + FusedIterator + 'a { + self.inner + .outputs() + .iter() + .map(CertifiedOutputV1::from_core) + } +} + +/// Исчерпывающее доказательство, что каждое состояние нарушает hard-клетку. +#[derive(Clone, Copy)] +pub struct ConflictCertificateV1<'a> { + inner: &'a CoreConflictV1, +} + +impl<'a> ConflictCertificateV1<'a> { + /// Возвращает identity скомпилированного содержания. + pub const fn content_identity(self) -> ContentIdentityV1 { + ContentIdentityV1::from_core(self.inner.report().content_identity()) + } + + /// Возвращает точное наблюдение, вызвавшее конфликт. + pub const fn observation(self) -> ObservationV1<'a> { + ObservationV1 { + inner: self.inner.report().observation(), + } + } + + /// Возвращает число исчерпывающе рассмотренных состояний. + pub const fn considered_state_count(self) -> usize { + self.inner.considered_state_count() + } + + /// Возвращает все `state × case × constraint` клетки конфликта. + pub fn cells(self) -> impl ExactSizeIterator> + FusedIterator + 'a { + self.inner + .report() + .cells() + .iter() + .map(ConflictCellV1::from_core) + } +} + +/// Закрытая заимствованная проекция одного Core-owned сертификата. +/// +/// Сертификат заимствует только историю Session и может пережить Owner, +/// разрешивший исходную проекцию. +/// +/// ```no_run +/// use labcolors_core::program::{ +/// CertificateV1, OwnerV1, SessionV1, +/// }; +/// +/// fn retain_evidence<'session>( +/// owner: OwnerV1, +/// session: &'session SessionV1, +/// ) -> CertificateV1<'session> { +/// owner +/// .project(session) +/// .unwrap() +/// .evidence() +/// .certificates() +/// .next() +/// .unwrap() +/// } +/// ``` +#[derive(Clone, Copy)] +pub enum CertificateV1<'a> { + /// Все hard-клетки полного support прошли. + Verified(VerifiedCertificateV1<'a>), + /// Каждое рассмотренное состояние нарушает хотя бы одну hard-клетку. + Conflict(ConflictCertificateV1<'a>), +} + +impl<'a> CertificateV1<'a> { + const fn verified(value: &'a CoreVerifiedV1) -> Self { + Self::Verified(VerifiedCertificateV1 { inner: value }) + } + + const fn conflict(value: &'a CoreConflictV1) -> Self { + Self::Conflict(ConflictCertificateV1 { inner: value }) + } + + /// Возвращает identity скомпилированного содержания. + pub const fn content_identity(self) -> ContentIdentityV1 { + match self { + Self::Verified(value) => value.content_identity(), + Self::Conflict(value) => value.content_identity(), + } + } + + /// Возвращает точное revision-bound наблюдение сертификата. + pub const fn observation(self) -> ObservationV1<'a> { + match self { + Self::Verified(value) => value.observation(), + Self::Conflict(value) => value.observation(), + } + } + + #[cfg(test)] + pub(crate) fn observation_backing_ptr_for_test(self) -> *const () { + self.observation().inner.backing_ptr_for_test() + } +} + +/// Точное revision-bound наблюдение, сохранённое сертификатом. +#[derive(Clone, Copy)] +pub struct ObservationV1<'a> { + inner: &'a crate::observation::RevisionBoundObservationV1, +} + +impl<'a> ObservationV1<'a> { + /// Возвращает stream provenance наблюдения. + pub const fn stream(self) -> StreamIdV1 { + StreamIdV1::from_core(self.inner.stream()) + } + + /// Возвращает ревизию наблюдения. + pub const fn revision(self) -> u64 { + self.inner.revision().value() + } + + /// Возвращает каноническую schema, общую для всех физических cases. + /// + /// Позиция `i` соответствует позиции `i` в [`PhysicalCaseV1::values`]. + pub fn surface_input_ports( + self, + ) -> impl ExactSizeIterator + FusedIterator + 'a { + self.inner + .schema() + .iter() + .copied() + .map(SurfaceInputPortIdV1::from_core) + } + + /// Возвращает канонические уникальные физические cases. + /// + /// Дубликаты значений схлопываются, а их ID сохраняются в provenance. + pub fn physical_cases( + self, + ) -> impl ExactSizeIterator> + FusedIterator + 'a { + (0..self.inner.physical_case_count()).map(move |index| PhysicalCaseV1 { + observation: self.inner, + index, + }) + } +} + +/// Закрытое семейство сигналов физического case. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SignalV1 { + /// Encoded sRGB8 в IEC 61966-2-1 с белой точкой D65. + Iec61966Srgb8D65(Srgb8), +} + +/// Один канонический физический case и его полная provenance. +#[derive(Clone, Copy)] +pub struct PhysicalCaseV1<'a> { + observation: &'a crate::observation::RevisionBoundObservationV1, + index: usize, +} + +impl<'a> PhysicalCaseV1<'a> { + /// Возвращает значения case в каноническом schema order. + pub fn values(self) -> impl ExactSizeIterator + FusedIterator + 'a { + self.observation + .physical_values(self.index) + .expect("physical case originates from the same observation") + .iter() + .copied() + .map(|signal| match signal.view() { + ColorSignalViewV1::Iec61966Srgb8D65(value) => SignalV1::Iec61966Srgb8D65(value), + }) + } + + /// Возвращает все scenario ID, схлопнутые в этот физический case. + pub fn provenance(self) -> impl ExactSizeIterator + FusedIterator + 'a { + self.observation + .provenance(self.index) + .expect("physical case originates from the same observation") + .iter() + .copied() + .map(ScenarioIdV1::from_core) + } +} + +/// Роль одной constraint-клетки в выборе. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConstraintModeV1 { + /// Нарушение запрещает состояние. + Hard, + /// Результат сохраняется, но не влияет на выбор. + ReportOnly, +} + +/// Одна клетка `case × constraint` выбранного или fixed состояния. +#[derive(Clone, Copy)] +pub struct VerifiedCellV1<'a> { + inner: &'a CoreProgramConstraintCellV1, +} + +impl<'a> VerifiedCellV1<'a> { + const fn from_core(inner: &'a CoreProgramConstraintCellV1) -> Self { + Self { inner } + } + + /// Возвращает индекс физического case. + pub const fn case_index(self) -> usize { + self.inner.case_index() + } + + /// Возвращает ID ограничения. + pub const fn constraint(self) -> ConstraintIdV1 { + ConstraintIdV1::from_core(self.inner.constraint()) + } + + /// Возвращает ID проверенного Occurrence. + pub const fn occurrence(self) -> OccurrenceIdV1 { + OccurrenceIdV1::from_core(self.inner.target()) + } + + /// Возвращает роль ограничения в выборе. + pub const fn mode(self) -> ConstraintModeV1 { + project_constraint_mode(self.inner) + } + + /// Возвращает типизированное сохранённое evidence. + pub fn assessment(self) -> AssessmentV1<'a> { + project_assessment(self.inner) + } +} + +/// Одна исчерпывающая клетка `state × case × constraint` конфликта. +#[derive(Clone, Copy)] +pub struct ConflictCellV1<'a> { + inner: &'a CoreProgramConstraintCellV1, +} + +impl<'a> ConflictCellV1<'a> { + const fn from_core(inner: &'a CoreProgramConstraintCellV1) -> Self { + Self { inner } + } + + /// Возвращает индекс рассмотренного состояния. + pub const fn state_index(self) -> usize { + self.inner.candidate_state_index() + } + + /// Возвращает индекс физического case. + pub const fn case_index(self) -> usize { + self.inner.case_index() + } + + /// Возвращает ID ограничения. + pub const fn constraint(self) -> ConstraintIdV1 { + ConstraintIdV1::from_core(self.inner.constraint()) + } + + /// Возвращает ID проверенного Occurrence. + pub const fn occurrence(self) -> OccurrenceIdV1 { + OccurrenceIdV1::from_core(self.inner.target()) + } + + /// Возвращает роль ограничения в выборе. + pub const fn mode(self) -> ConstraintModeV1 { + project_constraint_mode(self.inner) + } + + /// Возвращает типизированное сохранённое evidence. + pub fn assessment(self) -> AssessmentV1<'a> { + project_assessment(self.inner) + } +} + +const fn project_constraint_mode(cell: &CoreProgramConstraintCellV1) -> ConstraintModeV1 { + if cell.is_hard() { + ConstraintModeV1::Hard + } else { + ConstraintModeV1::ReportOnly + } +} + +fn project_assessment(cell: &CoreProgramConstraintCellV1) -> AssessmentV1<'_> { + match cell.result() { + ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::ExactSrgb8(evidence)) => { + AssessmentV1::ExactSrgb8(ExactSrgb8EvidenceV1 { + inner: ExactSrgb8EvidenceRefV1::Pass(evidence), + }) + } + ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::ExactSrgb8( + evidence, + )) => AssessmentV1::ExactSrgb8(ExactSrgb8EvidenceV1 { + inner: ExactSrgb8EvidenceRefV1::Violation(evidence), + }), + ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::Wcag22Srgb8(evidence)) => { + AssessmentV1::Wcag22Srgb8(Wcag22Srgb8EvidenceV1 { + inner: Wcag22Srgb8EvidenceRefV1::Pass(evidence), + }) + } + ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::Wcag22Srgb8( + evidence, + )) => AssessmentV1::Wcag22Srgb8(Wcag22Srgb8EvidenceV1 { + inner: Wcag22Srgb8EvidenceRefV1::Violation(evidence), + }), + } +} + +/// Закрытое семейство сохранённого evaluator evidence. +#[derive(Clone, Copy)] +pub enum AssessmentV1<'a> { + /// Evidence точного сравнения encoded sRGB8. + ExactSrgb8(ExactSrgb8EvidenceV1<'a>), + /// Evidence применимого критерия WCAG 2.2. + Wcag22Srgb8(Wcag22Srgb8EvidenceV1<'a>), +} + +impl<'a> AssessmentV1<'a> { + /// Возвращает несовместимый с противоположным исход классификатора. + pub const fn verdict(self) -> VerdictV1 { + match self { + Self::ExactSrgb8(value) => value.verdict(), + Self::Wcag22Srgb8(value) => value.verdict(), + } + } + + /// Возвращает общую физическую и моделированную привязку точки. + pub fn binding(self) -> PointBindingV1<'a> { + match self { + Self::ExactSrgb8(value) => value.binding(), + Self::Wcag22Srgb8(value) => value.binding(), + } + } +} + +/// Несовместимые сохранённые исходы классификатора. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VerdictV1 { + /// Критерий доказан. + Pass, + /// Критерий доказанно нарушен. + Violation, +} + +#[derive(Clone, Copy)] +enum ExactSrgb8EvidenceRefV1<'a> { + Pass(&'a CoreExactPassEvidenceV1), + Violation(&'a CoreExactViolationEvidenceV1), +} + +/// Evidence точного sRGB8 сравнения с физикой и моделированным контекстом. +#[derive(Clone, Copy)] +pub struct ExactSrgb8EvidenceV1<'a> { + inner: ExactSrgb8EvidenceRefV1<'a>, +} + +impl<'a> ExactSrgb8EvidenceV1<'a> { + /// Возвращает сохранённый исход классификатора. + pub const fn verdict(self) -> VerdictV1 { + match self.inner { + ExactSrgb8EvidenceRefV1::Pass(_) => VerdictV1::Pass, + ExactSrgb8EvidenceRefV1::Violation(_) => VerdictV1::Violation, + } + } + + /// Возвращает ожидаемый encoded sRGB8 результат. + pub fn expected(self) -> Srgb8 { + match self.inner { + ExactSrgb8EvidenceRefV1::Pass(value) => value.target(), + ExactSrgb8EvidenceRefV1::Violation(value) => value.target(), + } + } + + /// Возвращает физическую и моделированную привязку точки. + pub fn binding(self) -> PointBindingV1<'a> { + let value = match self.inner { + ExactSrgb8EvidenceRefV1::Pass(value) => value.binding(), + ExactSrgb8EvidenceRefV1::Violation(value) => value.binding(), + }; + PointBindingV1 { inner: value } + } +} + +#[derive(Clone, Copy)] +enum Wcag22Srgb8EvidenceRefV1<'a> { + Pass(&'a CoreWcag22PassEvidenceV1), + Violation(&'a CoreWcag22ViolationEvidenceV1), +} + +/// WCAG 2.2 evidence вместе с физикой и моделированным контекстом. +#[derive(Clone, Copy)] +pub struct Wcag22Srgb8EvidenceV1<'a> { + inner: Wcag22Srgb8EvidenceRefV1<'a>, +} + +impl<'a> Wcag22Srgb8EvidenceV1<'a> { + /// Возвращает сохранённый исход классификатора. + pub const fn verdict(self) -> VerdictV1 { + match self.inner { + Wcag22Srgb8EvidenceRefV1::Pass(_) => VerdictV1::Pass, + Wcag22Srgb8EvidenceRefV1::Violation(_) => VerdictV1::Violation, + } + } + + /// Возвращает версию применённого WCAG 2.2 профиля. + pub fn profile_id(self) -> Wcag22ProfileIdV1 { + match self.inner { + Wcag22Srgb8EvidenceRefV1::Pass(value) => value.measurement().value().profile_id(), + Wcag22Srgb8EvidenceRefV1::Violation(value) => value.measurement().value().profile_id(), + } + } + + /// Возвращает применённый критерий. + pub fn criterion(self) -> Wcag22CriterionV1 { + match self.inner { + Wcag22Srgb8EvidenceRefV1::Pass(value) => value.measurement().value().criterion(), + Wcag22Srgb8EvidenceRefV1::Violation(value) => value.measurement().value().criterion(), + } + } + + /// Возвращает сертифицированные границы яркости foreground. + pub fn foreground_luminance(self) -> Wcag22LuminanceBoundsQ55V1 { + let measurement = match self.inner { + Wcag22Srgb8EvidenceRefV1::Pass(value) => value.measurement().value().measurement(), + Wcag22Srgb8EvidenceRefV1::Violation(value) => value.measurement().value().measurement(), + }; + measurement.foreground_luminance + } + + /// Возвращает сертифицированные границы яркости background. + pub fn background_luminance(self) -> Wcag22LuminanceBoundsQ55V1 { + let measurement = match self.inner { + Wcag22Srgb8EvidenceRefV1::Pass(value) => value.measurement().value().measurement(), + Wcag22Srgb8EvidenceRefV1::Violation(value) => value.measurement().value().measurement(), + }; + measurement.background_luminance + } + + /// Возвращает числовое доказательство устойчивости решения. + pub fn numerical_evidence(self) -> &'a NumericalDecisionEvidenceV1 { + match self.inner { + Wcag22Srgb8EvidenceRefV1::Pass(value) => value.measurement().value().evidence(), + Wcag22Srgb8EvidenceRefV1::Violation(value) => value.measurement().value().evidence(), + } + } + + /// Возвращает физическую и моделированную привязку точки. + pub fn binding(self) -> PointBindingV1<'a> { + let value = match self.inner { + Wcag22Srgb8EvidenceRefV1::Pass(value) => value.binding(), + Wcag22Srgb8EvidenceRefV1::Violation(value) => value.binding(), + }; + PointBindingV1 { inner: value } + } +} + +/// Общая привязка физической композиции и моделированного tristimulus/context. +#[derive(Clone, Copy)] +pub struct PointBindingV1<'a> { + inner: &'a ProgramVisiblePointBindingV1, +} + +impl<'a> PointBindingV1<'a> { + /// Возвращает закрытый тип точной физической композиции. + pub const fn physical(self) -> PhysicalPointV1<'a> { + match self.inner.physical().occurrence().profile() { + CompositionProfileV1::EncodedSrgb8SourceOverV1 => { + PhysicalPointV1::EncodedSrgb8SourceOver(EncodedSrgb8SourceOverV1 { + inner: self.inner, + }) + } + } + } + + /// Возвращает закрытый тип допущенного моделированного сигнала. + pub const fn modeled(self) -> ModeledPointV1<'a> { + match self.inner.modeled_lcs().provenance().binding() { + AdmittedSrgb8TristimulusBindingV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1V1 => { + ModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1( + ModeledTristimulusV1 { inner: self.inner }, + ) + } + } + } +} + +/// Закрытое семейство точной физической композиции. +#[derive(Clone, Copy)] +pub enum PhysicalPointV1<'a> { + /// Encoded-sRGB8 source-over композиция. + EncodedSrgb8SourceOver(EncodedSrgb8SourceOverV1<'a>), +} + +/// Точная привязка одного encoded-sRGB8 source-over Occurrence. +#[derive(Clone, Copy)] +pub struct EncodedSrgb8SourceOverV1<'a> { + inner: &'a ProgramVisiblePointBindingV1, +} + +impl EncodedSrgb8SourceOverV1<'_> { + /// Возвращает ID накладываемого Paint. + pub const fn subject_paint(self) -> PaintIdV1 { + PaintIdV1::from_core(self.inner.physical().program_occurrence().subject()) + } + + /// Возвращает ID backdrop Surface. + pub const fn backdrop_surface(self) -> SurfaceIdV1 { + SurfaceIdV1::from_core( + self.inner + .physical() + .program_occurrence() + .backdrop_surface(), + ) + } + + /// Возвращает исходный encoded sRGB8 subject до композиции. + pub const fn subject(self) -> Srgb8 { + Srgb8::new(self.inner.physical().occurrence().subject_rgb()) + } + + /// Возвращает точную прозрачность subject в `[0, 1]`. + pub const fn opacity(self) -> f64 { + f64::from_bits(self.inner.physical().occurrence().subject_opacity_bits()) + } + + /// Возвращает observed encoded sRGB8 backdrop. + pub const fn backdrop(self) -> Srgb8 { + Srgb8::new(self.inner.physical().occurrence().backdrop_rgb()) + } + + /// Возвращает видимый encoded sRGB8 результат композиции. + pub const fn visible(self) -> Srgb8 { + Srgb8::new(self.inner.physical().occurrence().output_rgb()) + } +} + +/// Закрытое семейство provenance моделированного tristimulus. +#[derive(Clone, Copy)] +pub enum ModeledPointV1<'a> { + /// IEC sRGB8 → CIE 1931 2° XYZ D65 с относительным `Y=1`. + Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(ModeledTristimulusV1<'a>), +} + +/// Допущенный моделированный tristimulus и его контекст восприятия. +#[derive(Clone, Copy)] +pub struct ModeledTristimulusV1<'a> { + inner: &'a ProgramVisiblePointBindingV1, +} + +impl ModeledTristimulusV1<'_> { + /// Возвращает относительные координаты CIE XYZ. + pub fn xyz(self) -> [f64; 3] { + self.inner.modeled_lcs().derivation().sample().xyz() + } + + /// Возвращает явный контекст, использованный при моделировании. + pub const fn appearance_context(self) -> AppearanceContextV1 { + AppearanceContextV1(self.inner.modeled_lcs().occurrence().context()) + } +} + +/// Один Core-сертифицированный выходной Paint. +#[derive(Clone, Copy)] +pub struct CertifiedOutputV1<'a> { + inner: &'a ProgramOutputV1, +} + +impl<'a> CertifiedOutputV1<'a> { + const fn from_core(inner: &'a ProgramOutputV1) -> Self { + Self { inner } + } + + /// Возвращает клиентский выходной слот. + pub const fn output_slot(self) -> OutputSlotIdV1 { + OutputSlotIdV1::from_core((*self.inner).output()) + } + + /// Возвращает ID сертифицированного Paint. + pub const fn paint(self) -> PaintIdV1 { + PaintIdV1::from_core((*self.inner).paint().id()) + } + + /// Возвращает исходный encoded sRGB8 сигнал Paint. + pub const fn source(self) -> Srgb8 { + (*self.inner).paint().source() + } + + /// Возвращает сертифицированную прозрачность Paint. + pub const fn opacity(self) -> f64 { + (*self.inner).paint().opacity().value() + } +} + +/// Операция установки, структурно связанная с точным Verified-сертификатом. +#[derive(Clone, Copy)] +pub struct SetV1<'owner, 'session> { + output: &'session ProgramOutputV1, + certificate: VerifiedCertificateV1<'session>, + _scope: BorrowScopeV1<'owner, 'session>, +} + +impl<'session> SetV1<'_, 'session> { + /// Возвращает изменяемый клиентский выходной слот. + pub const fn output_slot(self) -> OutputSlotIdV1 { + OutputSlotIdV1::from_core((*self.output).output()) + } + + /// Возвращает исходный encoded sRGB8 сигнал результата. + pub const fn source(self) -> Srgb8 { + (*self.output).paint().source() + } + + /// Возвращает прозрачность результата. + pub const fn opacity(self) -> f64 { + (*self.output).paint().opacity().value() + } + + /// Возвращает сертификат, разрешивший эту операцию. + pub const fn certificate(self) -> VerifiedCertificateV1<'session> { + self.certificate + } +} + +/// Операция удаления результата без допустимого предыдущего значения. +#[derive(Clone, Copy)] +pub struct RemoveV1<'owner, 'session> { + output_slot: OutputSlotIdV1, + _scope: BorrowScopeV1<'owner, 'session>, +} + +impl RemoveV1<'_, '_> { + /// Возвращает удаляемый клиентский выходной слот. + pub const fn output_slot(self) -> OutputSlotIdV1 { + self.output_slot + } +} + +/// Операция удержания прошлого результата с его Verified-сертификатом. +#[derive(Clone, Copy)] +pub struct HoldV1<'owner, 'session> { + output: &'session ProgramOutputV1, + certificate: VerifiedCertificateV1<'session>, + _scope: BorrowScopeV1<'owner, 'session>, +} + +impl<'session> HoldV1<'_, 'session> { + /// Возвращает удерживаемый клиентский выходной слот. + pub const fn output_slot(self) -> OutputSlotIdV1 { + OutputSlotIdV1::from_core((*self.output).output()) + } + + /// Возвращает сертификат удерживаемого результата. + pub const fn certificate(self) -> VerifiedCertificateV1<'session> { + self.certificate + } +} + +/// Полное закрытое множество операций над непрозрачными выходными слотами. +/// +/// Каждый payload заимствует точные Owner и снимок Session. Скопированные +/// slot/source/opacity — только данные: runtime обязан перепроверить живую +/// пару непосредственно перед одним атомарным sink commit. +/// +/// ```compile_fail,E0515 +/// use labcolors_core::program::{ +/// OperationV1, OwnerV1, RemoveV1, +/// SessionV1, +/// }; +/// +/// fn escape_remove<'session>( +/// owner: OwnerV1, +/// session: &'session SessionV1, +/// ) -> RemoveV1<'session, 'session> { +/// match owner.project(session).unwrap().operations().next().unwrap() { +/// OperationV1::Remove(remove) => remove, +/// _ => panic!("fixture supplies Remove"), +/// } +/// } +/// ``` +/// +/// ```compile_fail,E0515 +/// use labcolors_core::program::{ +/// OperationV1, OwnerV1, RemoveV1, +/// }; +/// +/// fn escape_local_session<'owner>( +/// owner: &'owner OwnerV1, +/// ) -> RemoveV1<'owner, 'owner> { +/// let session = owner.instantiate(1).unwrap(); +/// match owner.project(&session).unwrap().operations().next().unwrap() { +/// OperationV1::Remove(remove) => remove, +/// _ => panic!("fixture supplies Remove"), +/// } +/// } +/// ``` +/// +/// ```compile_fail,E0515 +/// use labcolors_core::program::{ +/// OperationV1, OwnerV1, SessionV1, +/// SetV1, +/// }; +/// +/// fn escape_set<'session>( +/// owner: OwnerV1, +/// session: &'session SessionV1, +/// ) -> SetV1<'session, 'session> { +/// match owner.project(session).unwrap().operations().next().unwrap() { +/// OperationV1::Set(set) => set, +/// _ => panic!("fixture supplies Set"), +/// } +/// } +/// ``` +/// +/// ```compile_fail,E0515 +/// use labcolors_core::program::{ +/// HoldV1, OperationV1, OwnerV1, +/// SessionV1, +/// }; +/// +/// fn escape_hold<'session>( +/// owner: OwnerV1, +/// session: &'session SessionV1, +/// ) -> HoldV1<'session, 'session> { +/// match owner.project(session).unwrap().operations().next().unwrap() { +/// OperationV1::Hold(hold) => hold, +/// _ => panic!("fixture supplies Hold"), +/// } +/// } +/// ``` +/// +/// ```compile_fail,E0502 +/// use labcolors_core::program::{ +/// OperationV1, OwnerV1, SessionV1, +/// UpdateV1, +/// }; +/// +/// fn remove_blocks_session_mutation( +/// owner: &OwnerV1, +/// session: &mut SessionV1, +/// ) { +/// let remove = match owner.project(session).unwrap().operations().next().unwrap() { +/// OperationV1::Remove(remove) => remove, +/// _ => return, +/// }; +/// let _second = owner.update( +/// session, +/// UpdateV1::Unknown { +/// revision: 2, +/// reason_id: 7, +/// }, +/// ); +/// let _slot = remove.output_slot(); +/// } +/// ``` +#[derive(Clone, Copy)] +pub enum OperationV1<'owner, 'session> { + /// Установить сертифицированный результат. + Set(SetV1<'owner, 'session>), + /// Удалить результат, когда допустимого прошлого значения нет. + Remove(RemoveV1<'owner, 'session>), + /// Удержать последний сертифицированный результат. + Hold(HoldV1<'owner, 'session>), +} + +struct CertificatesV1<'a> { + values: [Option>; 2], + index: usize, +} + +impl<'a> CertificatesV1<'a> { + fn new(first: Option>, second: Option>) -> Self { + Self { + values: [first, second], + index: 0, + } + } +} + +impl<'a> Iterator for CertificatesV1<'a> { + type Item = CertificateV1<'a>; + + fn next(&mut self) -> Option { + while self.index < self.values.len() { + let value = self.values[self.index]; + self.index += 1; + if value.is_some() { + return value; + } + } + None + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.values[self.index..] + .iter() + .filter(|value| value.is_some()) + .count(); + (remaining, Some(remaining)) + } +} + +impl ExactSizeIterator for CertificatesV1<'_> {} +impl FusedIterator for CertificatesV1<'_> {} + +struct OwnerOutputSlotsV1<'owner> { + compiled: &'owner CompiledCoreProgramV1, + index: usize, + len: usize, +} + +impl<'owner> OwnerOutputSlotsV1<'owner> { + fn new(compiled: &'owner CompiledCoreProgramV1) -> Self { + Self { + compiled, + index: 0, + len: compiled.output_count(), + } + } +} + +impl Iterator for OwnerOutputSlotsV1<'_> { + type Item = OutputSlotIdV1; + + fn next(&mut self) -> Option { + if self.index == self.len { + return None; + } + let output = self.compiled.output_slot_at(self.index)?; + self.index += 1; + Some(OutputSlotIdV1::from_core(output)) + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.len - self.index; + (remaining, Some(remaining)) + } +} + +impl ExactSizeIterator for OwnerOutputSlotsV1<'_> {} +impl FusedIterator for OwnerOutputSlotsV1<'_> {} + +enum OperationSourceV1<'owner, 'session> { + Empty, + Set { + outputs: slice::Iter<'session, ProgramOutputV1>, + certificate: VerifiedCertificateV1<'session>, + scope: BorrowScopeV1<'owner, 'session>, + }, + Hold { + outputs: slice::Iter<'session, ProgramOutputV1>, + certificate: VerifiedCertificateV1<'session>, + scope: BorrowScopeV1<'owner, 'session>, + }, + Remove { + slots: OwnerOutputSlotsV1<'owner>, + scope: BorrowScopeV1<'owner, 'session>, + }, +} + +struct OperationsV1<'owner, 'session> { + inner: OperationSourceV1<'owner, 'session>, +} + +impl<'owner, 'session> Iterator for OperationsV1<'owner, 'session> { + type Item = OperationV1<'owner, 'session>; + + fn next(&mut self) -> Option { + match &mut self.inner { + OperationSourceV1::Empty => None, + OperationSourceV1::Set { + outputs, + certificate, + scope, + } => { + let output = outputs.next()?; + Some(OperationV1::Set(SetV1 { + output, + certificate: *certificate, + _scope: *scope, + })) + } + OperationSourceV1::Hold { + outputs, + certificate, + scope, + } => Some(OperationV1::Hold(HoldV1 { + output: outputs.next()?, + certificate: *certificate, + _scope: *scope, + })), + OperationSourceV1::Remove { slots, scope } => Some(OperationV1::Remove(RemoveV1 { + output_slot: slots.next()?, + _scope: *scope, + })), + } + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = match &self.inner { + OperationSourceV1::Empty => 0, + OperationSourceV1::Set { outputs, .. } => outputs.len(), + OperationSourceV1::Hold { outputs, .. } => outputs.len(), + OperationSourceV1::Remove { slots, .. } => slots.len(), + }; + (remaining, Some(remaining)) + } +} + +impl ExactSizeIterator for OperationsV1<'_, '_> {} +impl FusedIterator for OperationsV1<'_, '_> {} + +/// Закрытая классификация ошибки создания Session. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InstantiateErrorKindV1 { + /// Для создания Session недостаточно ресурсов. + ResourceExhausted, + /// Нарушен внутренний инвариант скомпилированной Program. + InternalInvariant, +} + +/// Непрозрачная ошибка создания Session. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InstantiateErrorV1 { + kind: InstantiateErrorKindV1, +} + +impl InstantiateErrorV1 { + const fn new(kind: InstantiateErrorKindV1) -> Self { + Self { kind } + } + + fn from_core(error: ProgramSessionInstantiateError) -> Self { + let kind = match error { + ProgramSessionInstantiateError::ResourceExhausted => { + InstantiateErrorKindV1::ResourceExhausted + } + ProgramSessionInstantiateError::InternalInvariant => { + InstantiateErrorKindV1::InternalInvariant + } + }; + Self::new(kind) + } + + /// Возвращает стабильный класс ошибки. + pub const fn kind(self) -> InstantiateErrorKindV1 { + self.kind + } +} + +impl From for InstantiateErrorV1 { + fn from(kind: InstantiateErrorKindV1) -> Self { + Self::new(kind) + } +} + +/// Закрытая классификация ошибки одного атомарного update. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpdateErrorKindV1 { + /// Session принадлежит другой точной owner-эпохе. + OwnerMismatch, + /// Наблюдение нарушает скомпилированную schema. + InvalidObservation, + /// Ревизия старше уже принятой. + RevisionOutOfOrder, + /// Та же ревизия содержит другой payload. + RevisionConflict, + /// Для admission или вычисления недостаточно ресурсов. + ResourceExhausted, + /// Зарегистрированный evaluator не смог выполнить оценку. + EvaluationFailed, + /// Нарушен внутренний инвариант. + InternalInvariant, +} + +/// Фаза update, в которой закончился ограниченный ресурс. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpdatePhaseV1 { + /// Admission и канонизация физического наблюдения. + ObservationAdmission, + /// Вычисление, поиск и финальная перепроверка Program. + ProgramEvaluation, +} + +/// Точный отказ зарегистрированного evaluator-а. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EvaluatorFailureV1 { + /// Отказ зарегистрированного WCAG 2.2 evaluator-а. + Wcag22Srgb8 { + /// Версия evaluator-а и его численного доказательства. + profile_id: Wcag22ProfileIdV1, + /// Точная исходная ошибка WCAG 2.2 без строковой переклассификации. + source: Wcag22EvaluationErrorV1, + }, +} + +/// Точная причина расхождения observation со скомпилированным binding. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ObservationBindingFailureV1 { + /// Скомпилированная schema не содержит ни одного входного порта. + EmptyCompiledSurfaceInputSchema, + /// Один входной порт повторён в скомпилированной schema. + DuplicateCompiledSurfaceInputPort { + /// Повторённый непрозрачный ID порта. + input: SurfaceInputPortIdV1, + }, + /// Update относится к другому observation stream. + StreamMismatch { + /// Stream, принадлежащий Session. + expected: StreamIdV1, + /// Stream отвергнутого update. + actual: StreamIdV1, + }, + /// Один входной порт повторён внутри физического сценария. + DuplicateSurfaceInputBinding { + /// Provenance ошибочного сценария. + scenario: ScenarioIdV1, + /// Повторённый входной порт. + input: SurfaceInputPortIdV1, + }, + /// В физическом сценарии отсутствует обязательный входной порт. + MissingSurfaceInputBinding { + /// Provenance ошибочного сценария. + scenario: ScenarioIdV1, + /// Отсутствующий входной порт. + input: SurfaceInputPortIdV1, + }, + /// Физический сценарий содержит неизвестный входной порт. + UnexpectedSurfaceInputBinding { + /// Provenance ошибочного сценария. + scenario: ScenarioIdV1, + /// Неизвестный входной порт. + input: SurfaceInputPortIdV1, + }, + /// Канонический порядок портов разошёлся со скомпилированной schema. + SchemaMismatch { + /// Индекс физического сценария. + case_index: usize, + /// Первый несовпавший индекс binding. + binding_index: usize, + /// Ожидаемый порт либо конец скомпилированной schema. + expected: Option, + /// Фактический порт либо конец observation schema. + actual: Option, + }, +} + +/// Зарегистрированная identity XYZ-frame в диагностике закрытого Core. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub 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 enum TristimulusComponentV1 { + /// Компонент X. + X, + /// Компонент Y. + Y, + /// Компонент Z. + Z, +} + +/// Точная конечная XYZ-точка и её зарегистрированный frame. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub 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 fn xyz(self) -> [f64; 3] { + self.xyz_bits.map(f64::from_bits) + } + + /// Возвращает зарегистрированный frame точки. + pub const fn frame(self) -> ColorimetricFrameV1 { + self.frame + } +} + +/// Точная причина, по которой Core не сформировал modeled LCS occurrence. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub 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)] +pub enum EvaluatorProtocolFailureV1 { + /// WCAG evaluator вернул kernel-ошибку, недостижимую для typed Program. + Wcag22Kernel { + /// Версия evaluator-а и его численного доказательства. + profile_id: Wcag22ProfileIdV1, + /// Точная исходная kernel-ошибка. + source: Wcag22EvaluationErrorV1, + }, + /// Hard-вызов получил только клиентскую декларацию неприменимости. + Wcag22ReportOnly { + /// Версия evaluator-а и его численного доказательства. + profile_id: Wcag22ProfileIdV1, + /// Точная клиентская декларация. + declaration: Wcag22ClientDeclaredNotApplicableV1, + }, + /// Evaluator проверил другой критерий. + Wcag22CriterionMismatch { + /// Запрошенный критерий. + requested: Wcag22CriterionV1, + /// Фактически проверенный критерий. + evaluated: Wcag22CriterionV1, + }, +} + +/// Машиночитаемая identity нарушенного внутреннего контракта. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpdateInvariantV1 { + /// Заимствованный matching Owner не удержал свою эпоху живой. + OwnerAuthority, + /// Каноническая observation schema разошлась со скомпилированным binding. + ObservationBinding, + /// Сохранённое evidence не принадлежит допускаемому observation. + EvidenceBinding, + /// Applicable evaluator вернул недопустимое protocol-состояние. + EvaluatorProtocol, + /// Physical и modeled точки одного evaluator-вызова разошлись. + PhysicalModeledBinding, + /// Зарегистрированный сигнал не сформировал свой LCS occurrence. + ModeledOccurrenceFormation, + /// Один выбранный state дал разные выходы в физических сценариях. + OutputCaseInvariance, + /// Детерминированная финальная перепроверка разошлась с поиском. + SelectionRecheck, + /// Закрытая программа нарушила собственную структуру исполнения. + ProgramEvaluation, +} + +/// Нарушенный внутренний контракт с точными subject и witness-фактами. +/// +/// Эти варианты недостижимы через типизированный public input. Payload нужен +/// для детерминированной диагностики и не превращает breach в цветовой verdict. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum UpdateInvariantFailureV1 { + /// Заимствованный matching Owner не удержал свою эпоху живой. + OwnerAuthority, + /// Каноническая observation schema разошлась со скомпилированным binding. + ObservationBinding { + /// Точное расхождение schema или stream. + source: ObservationBindingFailureV1, + }, + /// Сохранённое evidence не принадлежит допускаемому observation. + EvidenceBinding, + /// Applicable evaluator вернул недопустимое protocol-состояние. + EvaluatorProtocol { + /// Индекс физического сценария. + case_index: usize, + /// Непрозрачный ID проверяемого ограничения. + constraint: ConstraintIdV1, + /// Непрозрачный ID физического occurrence. + occurrence: OccurrenceIdV1, + /// Точный appearance context evaluator-вызова. + context: AppearanceContextV1, + /// Недопустимое 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 выходного слота. + output: OutputSlotIdV1, + /// Первый сценарий, задавший ожидаемое значение. + first_case: usize, + /// Сценарий с отличающимся значением. + actual_case: usize, + }, + /// Детерминированная финальная перепроверка разошлась с поиском. + SelectionRecheck { + /// Индекс выбранного joint state. + state_index: usize, + /// Индекс физического сценария. + case_index: usize, + /// Непрозрачный ID ограничения. + constraint: ConstraintIdV1, + /// Непрозрачный ID occurrence. + occurrence: OccurrenceIdV1, + /// Число hard-нарушений на финальной перепроверке. + hard_violation_count: usize, + }, + /// Закрытая программа нарушила собственную структуру исполнения. + ProgramEvaluation, +} + +impl UpdateInvariantFailureV1 { + /// Возвращает стабильную identity нарушенного контракта. + pub const fn contract(&self) -> UpdateInvariantV1 { + match self { + Self::OwnerAuthority => UpdateInvariantV1::OwnerAuthority, + 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, + } + } +} + +/// Точная ошибка одного атомарного update. +/// +/// Любой variant оставляет observation head и lifecycle-состояние Core +/// неизменными. [`UpdateErrorKindV1`] — только удобная производная проекция: +/// авторитетные IDs и факты отказа находятся в этом enum. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum UpdateErrorV1 { + /// Session создана другой точной owner-эпохой. + OwnerMismatch, + /// Наблюдение не содержит ни одного физического сценария. + EmptyScenarioSet, + /// Один scenario ID повторён внутри наблюдения. + DuplicateScenarioId { + /// Повторённая непрозрачная provenance. + scenario: ScenarioIdV1, + }, + /// Число значений сценария не равно скомпилированной schema. + ScenarioValueCountMismatch { + /// Непрозрачная provenance ошибочного сценария. + scenario: ScenarioIdV1, + /// Число входов в скомпилированной schema. + expected: usize, + /// Фактическое число переданных значений. + actual: usize, + }, + /// Входная ревизия старше уже принятой. + RevisionOutOfOrder { + /// Текущая принятая ревизия. + current: u64, + /// Отвергнутая входная ревизия. + incoming: u64, + }, + /// Та же ревизия содержит другой payload. + RevisionConflict { + /// Ревизия с неоднозначным содержанием. + revision: u64, + }, + /// Ограниченный ресурс закончился до commit. + ResourceExhausted { + /// Фаза, которой не хватило ресурса. + phase: UpdatePhaseV1, + }, + /// Зарегистрированный evaluator не смог выполнить оценку. + EvaluationFailed { + /// Индекс физического сценария в каноническом наблюдении. + case_index: usize, + /// Непрозрачный ID проверяемого ограничения. + constraint: ConstraintIdV1, + /// Непрозрачный ID физического occurrence. + occurrence: OccurrenceIdV1, + /// Точный допущенный appearance context evaluator-вызова. + context: AppearanceContextV1, + /// Точная причина отказа и identity evaluator-а. + source: EvaluatorFailureV1, + }, + /// Нарушен внутренний контракт закрытого Core. + InternalInvariant { + /// Точный факт нарушения; identity выводится через [`UpdateInvariantFailureV1::contract`]. + source: UpdateInvariantFailureV1, + }, +} + +impl UpdateErrorV1 { + /// Возвращает стабильный класс ошибки без потери её payload. + pub const fn kind(&self) -> UpdateErrorKindV1 { + match self { + Self::OwnerMismatch => UpdateErrorKindV1::OwnerMismatch, + Self::EmptyScenarioSet + | Self::DuplicateScenarioId { .. } + | Self::ScenarioValueCountMismatch { .. } => UpdateErrorKindV1::InvalidObservation, + Self::RevisionOutOfOrder { .. } => UpdateErrorKindV1::RevisionOutOfOrder, + Self::RevisionConflict { .. } => UpdateErrorKindV1::RevisionConflict, + Self::ResourceExhausted { .. } => UpdateErrorKindV1::ResourceExhausted, + Self::EvaluationFailed { .. } => UpdateErrorKindV1::EvaluationFailed, + Self::InternalInvariant { .. } => UpdateErrorKindV1::InternalInvariant, + } + } +} + +fn map_joint_order_error(error: FiniteJointOrderErrorV1) -> JointOrderErrorV1 { + match error { + FiniteJointOrderErrorV1::EmptyDomain { dimension } => { + JointOrderErrorV1::EmptyDomain { dimension } + } + FiniteJointOrderErrorV1::CardinalityOverflow => JointOrderErrorV1::CardinalityOverflow, + FiniteJointOrderErrorV1::EmptyOrder => JointOrderErrorV1::EmptyOrder, + FiniteJointOrderErrorV1::TupleArity { + tuple, + expected, + actual, + } => JointOrderErrorV1::TupleArity { + state: tuple, + expected, + actual, + }, + FiniteJointOrderErrorV1::OrdinalOutOfDomain { + tuple, + dimension, + ordinal, + domain_len, + } => JointOrderErrorV1::OrdinalOutOfDomain { + state: tuple, + dimension, + ordinal, + domain_len, + }, + FiniteJointOrderErrorV1::DuplicateTuple { first, duplicate } => { + JointOrderErrorV1::DuplicateTuple { + first_state: first, + duplicate_state: duplicate, + } + } + FiniteJointOrderErrorV1::IncompleteOrder { expected, actual } => { + JointOrderErrorV1::IncompleteOrder { expected, actual } + } + FiniteJointOrderErrorV1::ResourceExhausted => JointOrderErrorV1::ResourceExhausted, + } +} + +fn map_program_compile_error(error: ProgramCompileError) -> CompileErrorV1 { + match error { + ProgramCompileError::DuplicateSource { source } => CompileErrorV1::DuplicateSource { + source: SourceIdV1::from_core(source), + }, + ProgramCompileError::DuplicateTarget { target } => CompileErrorV1::DuplicateTarget { + target: TargetIdV1::from_core(target), + }, + ProgramCompileError::MissingTargetSource { target, source } => { + CompileErrorV1::MissingTargetSource { + target: TargetIdV1::from_core(target), + source: SourceIdV1::from_core(source), + } + } + ProgramCompileError::DuplicateOpacityInput { input } => { + CompileErrorV1::DuplicateOpacityInput { + input: OpacityInputIdV1::from_core(input), + } + } + ProgramCompileError::DuplicateSurfaceInputPort { input } => { + CompileErrorV1::DuplicateSurfaceInputPort { + input: SurfaceInputPortIdV1::from_core(input), + } + } + ProgramCompileError::UnusedSurfaceInputPort { input } => { + CompileErrorV1::UnusedSurfaceInputPort { + input: SurfaceInputPortIdV1::from_core(input), + } + } + ProgramCompileError::DuplicateSurfaceInputBinding { + input, + first, + duplicate, + } => CompileErrorV1::DuplicateSurfaceInputBinding { + input: SurfaceInputPortIdV1::from_core(input), + first: SurfaceIdV1::from_core(first), + duplicate: SurfaceIdV1::from_core(duplicate), + }, + ProgramCompileError::DuplicatePaint { paint } => CompileErrorV1::DuplicatePaint { + paint: PaintIdV1::from_core(paint), + }, + ProgramCompileError::DuplicateSurface { surface } => CompileErrorV1::DuplicateSurface { + surface: SurfaceIdV1::from_core(surface), + }, + ProgramCompileError::DuplicateOccurrence { occurrence } => { + CompileErrorV1::DuplicateOccurrence { + occurrence: OccurrenceIdV1::from_core(occurrence), + } + } + ProgramCompileError::MissingPaintTarget { paint, target } => { + CompileErrorV1::MissingPaintTarget { + paint: PaintIdV1::from_core(paint), + target: TargetIdV1::from_core(target), + } + } + ProgramCompileError::MissingPaintSource { paint, source } => { + CompileErrorV1::MissingPaintSource { + paint: PaintIdV1::from_core(paint), + source: PaintIdV1::from_core(source), + } + } + ProgramCompileError::MissingPaintOpacityInput { paint, input } => { + CompileErrorV1::MissingPaintOpacityInput { + paint: PaintIdV1::from_core(paint), + input: OpacityInputIdV1::from_core(input), + } + } + ProgramCompileError::MissingSurfaceInputPort { surface, input } => { + CompileErrorV1::MissingSurfaceInputPort { + surface: SurfaceIdV1::from_core(surface), + input: SurfaceInputPortIdV1::from_core(input), + } + } + ProgramCompileError::MissingSurfaceOccurrence { + surface, + occurrence, + } => CompileErrorV1::MissingSurfaceOccurrence { + surface: SurfaceIdV1::from_core(surface), + occurrence: OccurrenceIdV1::from_core(occurrence), + }, + ProgramCompileError::MissingOccurrencePaint { occurrence, paint } => { + CompileErrorV1::MissingOccurrencePaint { + occurrence: OccurrenceIdV1::from_core(occurrence), + paint: PaintIdV1::from_core(paint), + } + } + ProgramCompileError::MissingOccurrenceBackdrop { + occurrence, + surface, + } => CompileErrorV1::MissingOccurrenceBackdrop { + occurrence: OccurrenceIdV1::from_core(occurrence), + surface: SurfaceIdV1::from_core(surface), + }, + ProgramCompileError::PaintCycle { paints } => { + CompileErrorV1::PaintCycle(PaintCycleV1 { paints }) + } + ProgramCompileError::RenderCycle { + surfaces, + occurrences, + } => CompileErrorV1::RenderCycle(RenderCycleV1 { + surfaces, + occurrences, + }), + ProgramCompileError::OpacityOutOfDomain { input } => CompileErrorV1::OpacityOutOfDomain { + input: OpacityInputIdV1::from_core(input), + }, + ProgramCompileError::EmptyTargetDomain { target } => CompileErrorV1::EmptyTargetDomain { + target: TargetIdV1::from_core(target), + }, + ProgramCompileError::DuplicateTargetCandidate { target, candidate } => { + CompileErrorV1::DuplicateTargetCandidate { + target: TargetIdV1::from_core(target), + candidate: TargetCandidateIdV1::from_core(candidate), + } + } + ProgramCompileError::DuplicateTargetCandidateSignal { + target, + first, + duplicate, + signal, + } => CompileErrorV1::DuplicateTargetCandidateSignal { + target: TargetIdV1::from_core(target), + first: TargetCandidateIdV1::from_core(first), + duplicate: TargetCandidateIdV1::from_core(duplicate), + encoded_srgb8: signal.srgb8(), + }, + ProgramCompileError::UnconstrainedTarget { target } => { + CompileErrorV1::UnconstrainedTarget { + target: TargetIdV1::from_core(target), + } + } + ProgramCompileError::DisconnectedFiniteTargets => CompileErrorV1::DisconnectedFiniteTargets, + ProgramCompileError::UnassessedOutput { output, paint } => { + CompileErrorV1::UnassessedOutput { + output: OutputSlotIdV1::from_core(output), + paint: PaintIdV1::from_core(paint), + } + } + ProgramCompileError::MissingJointSelection => CompileErrorV1::MissingJointSelection, + ProgramCompileError::JointSelectionWithoutTargets => { + CompileErrorV1::JointSelectionWithoutTargets + } + ProgramCompileError::JointStateDuplicateTarget { state, target } => { + CompileErrorV1::JointStateDuplicateTarget { + state, + target: TargetIdV1::from_core(target), + } + } + ProgramCompileError::JointStateMissingTarget { state, target } => { + CompileErrorV1::JointStateMissingTarget { + state, + target: TargetIdV1::from_core(target), + } + } + ProgramCompileError::JointStateUnknownTarget { state, target } => { + CompileErrorV1::JointStateUnknownTarget { + state, + target: TargetIdV1::from_core(target), + } + } + ProgramCompileError::JointStateUnknownCandidate { + state, + target, + candidate, + } => CompileErrorV1::JointStateUnknownCandidate { + state, + target: TargetIdV1::from_core(target), + candidate: TargetCandidateIdV1::from_core(candidate), + }, + ProgramCompileError::InvalidJointOrder(error) => { + CompileErrorV1::InvalidJointOrder(map_joint_order_error(error)) + } + ProgramCompileError::EmptyObservationGroup { .. } => { + CompileErrorV1::EmptySurfaceInputPortSet + } + ProgramCompileError::EmptyOccurrenceSet => CompileErrorV1::EmptyOccurrenceSet, + ProgramCompileError::EmptyConstraintSet => CompileErrorV1::EmptyConstraintSet, + ProgramCompileError::EmptyOutputSet => CompileErrorV1::EmptyOutputSet, + ProgramCompileError::DuplicateConstraint { constraint } => { + CompileErrorV1::DuplicateConstraint { + constraint: ConstraintIdV1::from_core(constraint), + } + } + ProgramCompileError::MissingConstraintOccurrence { + constraint, + occurrence, + } => CompileErrorV1::MissingConstraintOccurrence { + constraint: ConstraintIdV1::from_core(constraint), + occurrence: OccurrenceIdV1::from_core(occurrence), + }, + ProgramCompileError::DuplicateOutputSlot { output } => { + CompileErrorV1::DuplicateOutputSlot { + output: OutputSlotIdV1::from_core(output), + } + } + ProgramCompileError::MissingOutputPaint { output, paint } => { + CompileErrorV1::MissingOutputPaint { + output: OutputSlotIdV1::from_core(output), + paint: PaintIdV1::from_core(paint), + } + } + ProgramCompileError::ResourceExhausted => CompileErrorV1::ResourceExhausted, + ProgramCompileError::InternalInvariant => CompileErrorV1::InternalInvariant, + } +} + +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 { + let (case_index, binding_index, expected, actual) = error.into_parts(); + ObservationBindingFailureV1::SchemaMismatch { + case_index, + binding_index, + expected: expected.map(SurfaceInputPortIdV1::from_core), + actual: actual.map(SurfaceInputPortIdV1::from_core), + } +} + +fn map_session_update_error(error: SessionUpdateError) -> UpdateErrorV1 { + match error { + // A borrowed matching owner keeps the exact Rc generation alive for + // the whole transaction; expiry here is therefore an internal breach. + SessionUpdateError::OwnerExpired => UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::OwnerAuthority, + }, + SessionUpdateError::Observation(error) => map_observation_error(error), + SessionUpdateError::Plan(error) => map_plan_error(error), + SessionUpdateError::EvidenceBindingInvariant => UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::EvidenceBinding, + }, + } +} + +fn map_observation_error(error: ObservationError) -> UpdateErrorV1 { + match error { + ObservationError::EmptyCompiledSurfaceInputSchema => UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ObservationBinding { + source: ObservationBindingFailureV1::EmptyCompiledSurfaceInputSchema, + }, + }, + ObservationError::DuplicateCompiledSurfaceInputPort { input } => { + UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ObservationBinding { + source: ObservationBindingFailureV1::DuplicateCompiledSurfaceInputPort { + input: SurfaceInputPortIdV1::from_core(input), + }, + }, + } + } + ObservationError::StreamMismatch { expected, actual } => UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ObservationBinding { + source: ObservationBindingFailureV1::StreamMismatch { + expected: StreamIdV1::from_core(expected), + actual: StreamIdV1::from_core(actual), + }, + }, + }, + ObservationError::EmptyScenarioSet => UpdateErrorV1::EmptyScenarioSet, + ObservationError::DuplicateScenarioId { scenario } => UpdateErrorV1::DuplicateScenarioId { + scenario: ScenarioIdV1::from_core(scenario), + }, + ObservationError::SchemaOrderedValueCountMismatch { + scenario, + expected, + actual, + } => UpdateErrorV1::ScenarioValueCountMismatch { + scenario: ScenarioIdV1::from_core(scenario), + expected, + actual, + }, + ObservationError::DuplicateSurfaceInputBinding { scenario, input } => { + UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ObservationBinding { + source: ObservationBindingFailureV1::DuplicateSurfaceInputBinding { + scenario: ScenarioIdV1::from_core(scenario), + input: SurfaceInputPortIdV1::from_core(input), + }, + }, + } + } + ObservationError::MissingSurfaceInputBinding { scenario, input } => { + UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ObservationBinding { + source: ObservationBindingFailureV1::MissingSurfaceInputBinding { + scenario: ScenarioIdV1::from_core(scenario), + input: SurfaceInputPortIdV1::from_core(input), + }, + }, + } + } + ObservationError::UnexpectedSurfaceInputBinding { scenario, input } => { + UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ObservationBinding { + source: ObservationBindingFailureV1::UnexpectedSurfaceInputBinding { + scenario: ScenarioIdV1::from_core(scenario), + input: SurfaceInputPortIdV1::from_core(input), + }, + }, + } + } + ObservationError::RevisionOutOfOrder { current, incoming } => { + UpdateErrorV1::RevisionOutOfOrder { + current: current.value(), + incoming: incoming.value(), + } + } + ObservationError::RevisionConflict { revision } => UpdateErrorV1::RevisionConflict { + revision: revision.value(), + }, + ObservationError::ResourceExhausted => UpdateErrorV1::ResourceExhausted { + phase: UpdatePhaseV1::ObservationAdmission, + }, + } +} + +fn map_plan_error(error: CoreProgramPlanErrorV1) -> UpdateErrorV1 { + match error { + ProgramSessionEvaluationError::ObservationSchemaMismatch(source) => { + UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ObservationBinding { + source: map_observation_schema_mismatch(source), + }, + } + } + ProgramSessionEvaluationError::ResourceExhausted => UpdateErrorV1::ResourceExhausted { + phase: UpdatePhaseV1::ProgramEvaluation, + }, + ProgramSessionEvaluationError::Evaluator { + case_index, + constraint, + occurrence, + context, + source, + } => match map_evaluator_error(source) { + Ok(source) => UpdateErrorV1::EvaluationFailed { + case_index, + constraint: ConstraintIdV1::from_core(constraint), + occurrence: OccurrenceIdV1::from_core(occurrence), + context: AppearanceContextV1::from_core(context), + source, + }, + Err(source) => UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::EvaluatorProtocol { + case_index, + constraint: ConstraintIdV1::from_core(constraint), + occurrence: OccurrenceIdV1::from_core(occurrence), + context: AppearanceContextV1::from_core(context), + source, + }, + }, + }, + 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, + actual_case, + } => UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::OutputCaseInvariance { + output: OutputSlotIdV1::from_core(output), + first_case, + actual_case, + }, + }, + ProgramSessionEvaluationError::FinalRecheckViolation { + state_index, + case_index, + constraint, + target, + hard_violation_count, + } => UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::SelectionRecheck { + state_index, + case_index, + constraint: ConstraintIdV1::from_core(constraint), + occurrence: OccurrenceIdV1::from_core(target), + hard_violation_count, + }, + }, + ProgramSessionEvaluationError::InternalInvariant => UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ProgramEvaluation, + }, + } +} + +fn map_evaluator_error( + error: CoreProgramEvaluatorErrorV1, +) -> Result { + match error { + CoreProgramEvaluatorErrorV1::ExactSrgb8(source) => match source {}, + CoreProgramEvaluatorErrorV1::Wcag22Srgb8(source) => { + let profile_id = wcag22_profile_v1().profile_id; + match source { + ApplicableWcag22EvaluationErrorV1::Kernel( + source @ (Wcag22EvaluationErrorV1::ArtifactInvariantViolation { .. } + | Wcag22EvaluationErrorV1::EvidenceRegistryMismatch(_)), + ) => Ok(EvaluatorFailureV1::Wcag22Srgb8 { profile_id, source }), + ApplicableWcag22EvaluationErrorV1::Kernel(source) => { + Err(EvaluatorProtocolFailureV1::Wcag22Kernel { profile_id, source }) + } + ApplicableWcag22EvaluationErrorV1::ReportOnly { + profile_id, + declaration, + } => Err(EvaluatorProtocolFailureV1::Wcag22ReportOnly { + profile_id, + declaration, + }), + ApplicableWcag22EvaluationErrorV1::CriterionMismatch { + requested, + evaluated, + } => Err(EvaluatorProtocolFailureV1::Wcag22CriterionMismatch { + requested, + evaluated, + }), + } + } + } +} + +#[cfg(test)] +mod update_error_projection_tests { + use super::*; + use crate::wcag22::Wcag22ClientDeclaredNotApplicableV1; + + fn context() -> AppearanceContextV1 { + AppearanceContextV1::try_new(64.0, 0.2, SurroundV1::Average).unwrap() + } + + fn map_wcag_error(source: ApplicableWcag22EvaluationErrorV1) -> UpdateErrorV1 { + let context = context(); + map_plan_error(ProgramSessionEvaluationError::Evaluator { + case_index: 7, + constraint: ConstraintId::new(11), + occurrence: OccurrenceId::new(13), + context: context.0, + source: CoreProgramEvaluatorErrorV1::Wcag22Srgb8(source), + }) + } + + #[test] + fn resource_exhaustion_retains_its_exact_update_phase() { + let observation = map_observation_error(ObservationError::ResourceExhausted); + assert_eq!(observation.kind(), UpdateErrorKindV1::ResourceExhausted); + assert_eq!( + observation, + UpdateErrorV1::ResourceExhausted { + phase: UpdatePhaseV1::ObservationAdmission, + } + ); + let evaluation = map_plan_error(ProgramSessionEvaluationError::ResourceExhausted); + assert_eq!(evaluation.kind(), UpdateErrorKindV1::ResourceExhausted); + assert_eq!( + evaluation, + UpdateErrorV1::ResourceExhausted { + phase: UpdatePhaseV1::ProgramEvaluation, + } + ); + } + + #[test] + fn evaluator_artifact_failure_retains_every_actionable_fact() { + let error = map_wcag_error(ApplicableWcag22EvaluationErrorV1::Kernel( + Wcag22EvaluationErrorV1::ArtifactInvariantViolation { + criterion: Wcag22CriterionV1::Sc143TextLargeScale, + foreground: [1, 2, 3], + background: [4, 5, 6], + }, + )); + assert_eq!(error.kind(), UpdateErrorKindV1::EvaluationFailed); + assert_eq!( + error, + UpdateErrorV1::EvaluationFailed { + case_index: 7, + constraint: ConstraintIdV1::new(11), + occurrence: OccurrenceIdV1::new(13), + context: context(), + source: EvaluatorFailureV1::Wcag22Srgb8 { + profile_id: wcag22_profile_v1().profile_id, + source: Wcag22EvaluationErrorV1::ArtifactInvariantViolation { + criterion: Wcag22CriterionV1::Sc143TextLargeScale, + foreground: [1, 2, 3], + background: [4, 5, 6], + }, + }, + } + ); + + let error = map_wcag_error(ApplicableWcag22EvaluationErrorV1::Kernel( + Wcag22EvaluationErrorV1::EvidenceRegistryMismatch("registry-vs-proof".into()), + )); + assert_eq!(error.kind(), UpdateErrorKindV1::EvaluationFailed); + let UpdateErrorV1::EvaluationFailed { + source: + EvaluatorFailureV1::Wcag22Srgb8 { + profile_id, + source: Wcag22EvaluationErrorV1::EvidenceRegistryMismatch(message), + }, + .. + } = error + else { + panic!("registry mismatch must remain an evaluator failure"); + }; + assert_eq!(profile_id, wcag22_profile_v1().profile_id); + assert_eq!(message, "registry-vs-proof"); + } + + #[test] + fn impossible_wcag_protocol_states_are_not_misreported_as_colour_failures() { + let profile_id = wcag22_profile_v1().profile_id; + let declaration = Wcag22ClientDeclaredNotApplicableV1::try_new("client-scope").unwrap(); + let cases = [ + ( + ApplicableWcag22EvaluationErrorV1::Kernel(Wcag22EvaluationErrorV1::InvalidSrgb8 { + field: "foreground", + reason: "typed Program cannot create this".into(), + }), + EvaluatorProtocolFailureV1::Wcag22Kernel { + profile_id, + source: Wcag22EvaluationErrorV1::InvalidSrgb8 { + field: "foreground", + reason: "typed Program cannot create this".into(), + }, + }, + ), + ( + ApplicableWcag22EvaluationErrorV1::Kernel( + Wcag22EvaluationErrorV1::EmptyNotApplicableReason, + ), + EvaluatorProtocolFailureV1::Wcag22Kernel { + profile_id, + source: Wcag22EvaluationErrorV1::EmptyNotApplicableReason, + }, + ), + ( + ApplicableWcag22EvaluationErrorV1::ReportOnly { + profile_id, + declaration: declaration.clone(), + }, + EvaluatorProtocolFailureV1::Wcag22ReportOnly { + profile_id, + declaration, + }, + ), + ( + ApplicableWcag22EvaluationErrorV1::CriterionMismatch { + requested: Wcag22CriterionV1::Sc143TextDefault, + evaluated: Wcag22CriterionV1::Sc1411UiComponentOrState, + }, + EvaluatorProtocolFailureV1::Wcag22CriterionMismatch { + requested: Wcag22CriterionV1::Sc143TextDefault, + evaluated: Wcag22CriterionV1::Sc1411UiComponentOrState, + }, + ), + ]; + for (source, expected) in cases { + let error = map_wcag_error(source); + assert_eq!(error.kind(), UpdateErrorKindV1::InternalInvariant); + let expected = UpdateInvariantFailureV1::EvaluatorProtocol { + case_index: 7, + constraint: ConstraintIdV1::new(11), + occurrence: OccurrenceIdV1::new(13), + context: context(), + source: expected, + }; + assert_eq!(expected.contract(), UpdateInvariantV1::EvaluatorProtocol); + assert_eq!(error, UpdateErrorV1::InternalInvariant { source: expected }); + } + } + + #[test] + fn every_observation_binding_failure_retains_its_exact_payload() { + let cases = [ + ( + ObservationError::EmptyCompiledSurfaceInputSchema, + ObservationBindingFailureV1::EmptyCompiledSurfaceInputSchema, + ), + ( + ObservationError::DuplicateCompiledSurfaceInputPort { + input: SurfaceInputPortId::new(3), + }, + ObservationBindingFailureV1::DuplicateCompiledSurfaceInputPort { + input: SurfaceInputPortIdV1::new(3), + }, + ), + ( + ObservationError::StreamMismatch { + expected: ObservationStreamId::new(5), + actual: ObservationStreamId::new(7), + }, + ObservationBindingFailureV1::StreamMismatch { + expected: StreamIdV1::from_core(ObservationStreamId::new(5)), + actual: StreamIdV1::from_core(ObservationStreamId::new(7)), + }, + ), + ( + ObservationError::DuplicateSurfaceInputBinding { + scenario: ScenarioId::new(11), + input: SurfaceInputPortId::new(13), + }, + ObservationBindingFailureV1::DuplicateSurfaceInputBinding { + scenario: ScenarioIdV1::from_core(ScenarioId::new(11)), + input: SurfaceInputPortIdV1::new(13), + }, + ), + ( + ObservationError::MissingSurfaceInputBinding { + scenario: ScenarioId::new(17), + input: SurfaceInputPortId::new(19), + }, + ObservationBindingFailureV1::MissingSurfaceInputBinding { + scenario: ScenarioIdV1::from_core(ScenarioId::new(17)), + input: SurfaceInputPortIdV1::new(19), + }, + ), + ( + ObservationError::UnexpectedSurfaceInputBinding { + scenario: ScenarioId::new(23), + input: SurfaceInputPortId::new(29), + }, + ObservationBindingFailureV1::UnexpectedSurfaceInputBinding { + scenario: ScenarioIdV1::from_core(ScenarioId::new(23)), + input: SurfaceInputPortIdV1::new(29), + }, + ), + ]; + + for (source, expected) in cases { + let error = map_observation_error(source); + assert_eq!(error.kind(), UpdateErrorKindV1::InternalInvariant); + assert_eq!( + error, + UpdateErrorV1::InternalInvariant { + source: UpdateInvariantFailureV1::ObservationBinding { source: expected }, + } + ); + } + } + + #[test] + fn every_modeled_occurrence_failure_has_an_isomorphic_public_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| { + assert_eq!(error.kind(), UpdateErrorKindV1::InternalInvariant); + assert_eq!( + error, + UpdateErrorV1::InternalInvariant { + source: source.clone(), + } + ); + assert_eq!( + source.contract(), + match source { + UpdateInvariantFailureV1::OwnerAuthority => { + UpdateInvariantV1::OwnerAuthority + } + UpdateInvariantFailureV1::ObservationBinding { .. } => { + UpdateInvariantV1::ObservationBinding + } + UpdateInvariantFailureV1::EvidenceBinding => { + UpdateInvariantV1::EvidenceBinding + } + UpdateInvariantFailureV1::EvaluatorProtocol { .. } => { + UpdateInvariantV1::EvaluatorProtocol + } + UpdateInvariantFailureV1::PhysicalModeledBinding { .. } => { + UpdateInvariantV1::PhysicalModeledBinding + } + UpdateInvariantFailureV1::ModeledOccurrenceFormation { .. } => { + UpdateInvariantV1::ModeledOccurrenceFormation + } + UpdateInvariantFailureV1::OutputCaseInvariance { .. } => { + UpdateInvariantV1::OutputCaseInvariance + } + UpdateInvariantFailureV1::SelectionRecheck { .. } => { + UpdateInvariantV1::SelectionRecheck + } + UpdateInvariantFailureV1::ProgramEvaluation => { + UpdateInvariantV1::ProgramEvaluation + } + } + ); + }; + + assert_invariant( + map_session_update_error(SessionUpdateError::OwnerExpired), + UpdateInvariantFailureV1::OwnerAuthority, + ); + assert_invariant( + map_session_update_error(SessionUpdateError::EvidenceBindingInvariant), + UpdateInvariantFailureV1::EvidenceBinding, + ); + assert_invariant( + map_observation_error(ObservationError::EmptyCompiledSurfaceInputSchema), + UpdateInvariantFailureV1::ObservationBinding { + source: ObservationBindingFailureV1::EmptyCompiledSurfaceInputSchema, + }, + ); + + assert_invariant( + map_plan_error(ProgramSessionEvaluationError::ObservationSchemaMismatch( + ObservationSchemaMismatchV1::new(2, 3, None, None), + )), + UpdateInvariantFailureV1::ObservationBinding { + source: ObservationBindingFailureV1::SchemaMismatch { + case_index: 2, + binding_index: 3, + expected: None, + actual: None, + }, + }, + ); + 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), + first_case: 1, + actual_case: 2, + }), + UpdateInvariantFailureV1::OutputCaseInvariance { + output: OutputSlotIdV1::new(5), + first_case: 1, + actual_case: 2, + }, + ); + assert_invariant( + map_plan_error(ProgramSessionEvaluationError::FinalRecheckViolation { + state_index: 1, + case_index: 2, + constraint: ConstraintId::new(3), + target: OccurrenceId::new(4), + hard_violation_count: 1, + }), + UpdateInvariantFailureV1::SelectionRecheck { + state_index: 1, + case_index: 2, + constraint: ConstraintIdV1::new(3), + occurrence: OccurrenceIdV1::new(4), + hard_violation_count: 1, + }, + ); + assert_invariant( + map_plan_error(ProgramSessionEvaluationError::InternalInvariant), + UpdateInvariantFailureV1::ProgramEvaluation, + ); + } +} + +#[cfg(test)] +mod operation_scope_tests { + use super::*; + + #[test] + fn operation_scope_is_a_zero_sized_borrow_marker() { + assert_eq!(core::mem::size_of::>(), 0); + } +} + +#[cfg(test)] +mod compile_error_projection_tests { + use super::*; + + #[test] + fn nested_joint_resource_exhaustion_keeps_its_exact_reason_and_site_kind() { + let error = map_program_compile_error(ProgramCompileError::InvalidJointOrder( + FiniteJointOrderErrorV1::ResourceExhausted, + )); + + assert_eq!(error.kind(), CompileErrorKindV1::InvalidJointOrder); + assert_eq!( + error, + CompileErrorV1::InvalidJointOrder(JointOrderErrorV1::ResourceExhausted) + ); + } +} diff --git a/crates/labcolors-core/src/program_mixed_evaluator_tests.rs b/crates/labcolors-core/src/program_mixed_evaluator_tests.rs index cd131e49..999d2ac1 100644 --- a/crates/labcolors-core/src/program_mixed_evaluator_tests.rs +++ b/crates/labcolors-core/src/program_mixed_evaluator_tests.rs @@ -3,8 +3,8 @@ use core::iter::FusedIterator; use crate::Srgb8; use crate::appearance::{OccurrenceId, OpacityInputId, PaintId, SurfaceId, SurfaceInputPortId}; use crate::constraints::{ - ExactConstraintIdentityV1, ExactIdentityCapabilityV1, ExactIdentityReleaseV1, - ProgramVisiblePointBindingV1, + ApplicableWcag22MeasurementV1, ExactConstraintIdentityV1, ExactIdentityCapabilityV1, + ExactIdentityReleaseV1, ProgramVisiblePointBindingV1, }; use crate::lcs_occurrence::{ AdaptingLuminanceCdM2, AppearanceContextId, AppearanceContextSchemaReleaseId, @@ -15,14 +15,11 @@ use crate::observation::{ ObservationGroupId, ObservationPayloadInput, ObservationStreamId, ObservationUpdateInput, ObservedScenarioSetInput, Revision, ScenarioId, ScenarioInput, SurfaceInputBinding, }; -use crate::package_bridge::{ - PackageProgramAccessErrorV1, PackageProgramAssessmentV1, PackageProgramCertificateV1, - PackageProgramConflictCellV1, PackageProgramModeledPointV1, PackageProgramObservationHeadV1, - PackageProgramObservationV1, PackageProgramOperationV1, PackageProgramOutputSlotIdV1, - PackageProgramOwnerV1, PackageProgramPhysicalPointV1, PackageProgramProjectionV1, - PackageProgramScenarioV1, PackageProgramSignalV1, PackageProgramStateKindV1, - PackageProgramSurroundV1, PackageProgramUpdateErrorKindV1, PackageProgramUpdateV1, - PackageProgramVerdictV1, PackageProgramVerifiedCellV1, +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, }; use crate::program_session::{ CORE_PROGRAM_ASSESSMENT_CALLS, CompiledCoreProgramV1, CompositionProfile, ConstraintId, @@ -199,14 +196,14 @@ fn fixed_translucent_program() -> CompiledCoreProgramV1 { .unwrap() } -fn assert_package_observation_matches_core( - package: PackageProgramObservationV1<'_>, +fn assert_public_observation_matches_core( + public: ObservationV1<'_>, core: &crate::observation::RevisionBoundObservationV1, ) { - assert_eq!(package.stream().value(), core.stream().value()); - assert_eq!(package.revision(), core.revision().value()); + assert_eq!(public.stream().value(), core.stream().value()); + assert_eq!(public.revision(), core.revision().value()); assert_eq!( - package + public .surface_input_ports() .map(|port| port.value()) .collect::>(), @@ -216,13 +213,13 @@ fn assert_package_observation_matches_core( .collect::>(), ); - let package_cases = package + let public_cases = public .physical_cases() .map(|case| { let values = case .values() .map(|value| match value { - PackageProgramSignalV1::Iec61966Srgb8D65(value) => value, + SignalV1::Iec61966Srgb8D65(value) => value, }) .collect::>(); let provenance = case @@ -249,11 +246,11 @@ fn assert_package_observation_matches_core( (values, provenance) }) .collect::>(); - assert_eq!(package_cases, core_cases); + assert_eq!(public_cases, core_cases); } -fn assert_package_binding_matches_core( - package: PackageProgramAssessmentV1<'_>, +fn assert_public_binding_matches_core( + public: AssessmentV1<'_>, core: &ProgramVisiblePointBindingV1, expected_occurrence: OccurrenceId, ) -> (Srgb8, Srgb8) { @@ -261,185 +258,189 @@ fn assert_package_binding_matches_core( let core_occurrence = core_physical.occurrence(); let core_program_occurrence = core_physical.program_occurrence(); assert_eq!(core_program_occurrence.occurrence(), expected_occurrence); - let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(package_physical) = - package.binding().physical(); + let PhysicalPointV1::EncodedSrgb8SourceOver(public_physical) = public.binding().physical(); assert_eq!( - package_physical.subject_paint().value(), + public_physical.subject_paint().value(), core_program_occurrence.subject().value() ); assert_eq!( - package_physical.backdrop_surface().value(), + public_physical.backdrop_surface().value(), core_program_occurrence.backdrop_surface().value() ); assert_eq!( - package_physical.subject(), + public_physical.subject(), Srgb8::new(core_occurrence.subject_rgb()) ); assert_eq!( - package_physical.opacity().to_bits(), + public_physical.opacity().to_bits(), core_occurrence.subject_opacity_bits() ); assert_eq!( - package_physical.backdrop(), + public_physical.backdrop(), Srgb8::new(core_occurrence.backdrop_rgb()) ); assert_eq!( - package_physical.visible(), + public_physical.visible(), Srgb8::new(core_occurrence.output_rgb()) ); - let PackageProgramModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1( - package_modeled, - ) = package.binding().modeled(); + let ModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(public_modeled) = + public.binding().modeled(); assert_eq!( - package_modeled.xyz().map(f64::to_bits), + public_modeled.xyz().map(f64::to_bits), core.modeled_lcs() .derivation() .sample() .xyz() .map(f64::to_bits) ); - let package_context = package_modeled.appearance_context(); + let public_context = public_modeled.appearance_context(); let core_context = core.modeled_lcs().occurrence().context(); assert_eq!( - package_context.adapting_luminance_cd_m2().to_bits(), + public_context.adapting_luminance_cd_m2().to_bits(), core_context.adapting_luminance_cd_m2().to_bits() ); assert_eq!( - package_context.background_luminance_ratio_yb_yw().to_bits(), + public_context.background_luminance_ratio_yb_yw().to_bits(), core_context.background_luminance_ratio().to_bits() ); let core_surround = match core_context.surround_profile() { - SurroundProfileId::AverageV1 => PackageProgramSurroundV1::Average, - SurroundProfileId::DimV1 => PackageProgramSurroundV1::Dim, - SurroundProfileId::DarkV1 => PackageProgramSurroundV1::Dark, + SurroundProfileId::AverageV1 => SurroundV1::Average, + SurroundProfileId::DimV1 => SurroundV1::Dim, + SurroundProfileId::DarkV1 => SurroundV1::Dark, }; - assert_eq!(package_context.surround(), core_surround); + assert_eq!(public_context.surround(), core_surround); - (package_physical.visible(), package_physical.backdrop()) + (public_physical.visible(), public_physical.backdrop()) } -fn assert_package_assessment_matches_core( - package: PackageProgramAssessmentV1<'_>, +fn assert_public_assessment_matches_core( + public: AssessmentV1<'_>, core: &ProgramConstraintResultV1, expected_occurrence: OccurrenceId, ) { - match (package, core) { + fn assert_exact_matches( + public: ExactSrgb8EvidenceV1<'_>, + verdict: VerdictV1, + expected: Srgb8, + actual: Srgb8, + binding: &ProgramVisiblePointBindingV1, + expected_occurrence: OccurrenceId, + ) { + assert_eq!(public.verdict(), verdict); + assert_eq!(public.expected(), expected); + let (visible, _) = assert_public_binding_matches_core( + AssessmentV1::ExactSrgb8(public), + binding, + expected_occurrence, + ); + assert_eq!(visible, actual); + } + + fn assert_wcag_matches( + public: Wcag22Srgb8EvidenceV1<'_>, + verdict: VerdictV1, + measurement: &ApplicableWcag22MeasurementV1, + binding: &ProgramVisiblePointBindingV1, + expected_occurrence: OccurrenceId, + ) { + assert_eq!(public.verdict(), verdict); + assert_eq!(public.profile_id(), measurement.profile_id()); + assert_eq!(public.criterion(), measurement.criterion()); + assert_eq!( + public.foreground_luminance(), + measurement.measurement().foreground_luminance + ); + assert_eq!( + public.background_luminance(), + measurement.measurement().background_luminance + ); + assert_eq!(public.numerical_evidence(), measurement.evidence()); + let (visible, backdrop) = assert_public_binding_matches_core( + AssessmentV1::Wcag22Srgb8(public), + binding, + expected_occurrence, + ); + assert_eq!(visible, Srgb8::new(measurement.measurement().foreground)); + assert_eq!(backdrop, Srgb8::new(measurement.measurement().background)); + } + + match (public, core) { ( - PackageProgramAssessmentV1::ExactSrgb8(package), + AssessmentV1::ExactSrgb8(public), ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::ExactSrgb8(core)), - ) => { - assert_eq!(package.verdict(), PackageProgramVerdictV1::Pass); - assert_eq!(package.expected(), core.target()); - let (visible, _) = assert_package_binding_matches_core( - PackageProgramAssessmentV1::ExactSrgb8(package), - core.binding(), - expected_occurrence, - ); - assert_eq!(visible, core.actual()); - } + ) => assert_exact_matches( + public, + VerdictV1::Pass, + core.target(), + core.actual(), + core.binding(), + expected_occurrence, + ), ( - PackageProgramAssessmentV1::ExactSrgb8(package), + AssessmentV1::ExactSrgb8(public), ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::ExactSrgb8(core)), - ) => { - assert_eq!(package.verdict(), PackageProgramVerdictV1::Violation); - assert_eq!(package.expected(), core.target()); - let (visible, _) = assert_package_binding_matches_core( - PackageProgramAssessmentV1::ExactSrgb8(package), - core.binding(), - expected_occurrence, - ); - assert_eq!(visible, core.actual()); - } + ) => assert_exact_matches( + public, + VerdictV1::Violation, + core.target(), + core.actual(), + core.binding(), + expected_occurrence, + ), ( - PackageProgramAssessmentV1::Wcag22Srgb8(package), + AssessmentV1::Wcag22Srgb8(public), ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::Wcag22Srgb8(core)), - ) => { - assert_eq!(package.verdict(), PackageProgramVerdictV1::Pass); - let measurement = core.measurement().value(); - assert_eq!(package.profile_id(), measurement.profile_id()); - assert_eq!(package.criterion(), measurement.criterion()); - assert_eq!( - package.foreground_luminance(), - measurement.measurement().foreground_luminance - ); - assert_eq!( - package.background_luminance(), - measurement.measurement().background_luminance - ); - assert_eq!(package.numerical_evidence(), measurement.evidence()); - let (visible, backdrop) = assert_package_binding_matches_core( - PackageProgramAssessmentV1::Wcag22Srgb8(package), - core.binding(), - expected_occurrence, - ); - assert_eq!(visible, Srgb8::new(measurement.measurement().foreground)); - assert_eq!(backdrop, Srgb8::new(measurement.measurement().background)); - } + ) => assert_wcag_matches( + public, + VerdictV1::Pass, + core.measurement().value(), + core.binding(), + expected_occurrence, + ), ( - PackageProgramAssessmentV1::Wcag22Srgb8(package), + AssessmentV1::Wcag22Srgb8(public), ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::Wcag22Srgb8(core)), - ) => { - assert_eq!(package.verdict(), PackageProgramVerdictV1::Violation); - let measurement = core.measurement().value(); - assert_eq!(package.profile_id(), measurement.profile_id()); - assert_eq!(package.criterion(), measurement.criterion()); - assert_eq!( - package.foreground_luminance(), - measurement.measurement().foreground_luminance - ); - assert_eq!( - package.background_luminance(), - measurement.measurement().background_luminance - ); - assert_eq!(package.numerical_evidence(), measurement.evidence()); - let (visible, backdrop) = assert_package_binding_matches_core( - PackageProgramAssessmentV1::Wcag22Srgb8(package), - core.binding(), - expected_occurrence, - ); - assert_eq!(visible, Srgb8::new(measurement.measurement().foreground)); - assert_eq!(backdrop, Srgb8::new(measurement.measurement().background)); - } - _ => panic!("package assessment family or verdict drifted from Core"), + ) => assert_wcag_matches( + public, + VerdictV1::Violation, + core.measurement().value(), + core.binding(), + expected_occurrence, + ), + _ => panic!("public assessment family or verdict drifted from Core"), } } fn assert_verified_cell_matches_core( - package: PackageProgramVerifiedCellV1<'_>, + public: VerifiedCellV1<'_>, core: &ProgramConstraintCellV1, selected_state_index: usize, ) { assert_eq!(core.candidate_state_index(), selected_state_index); - assert_eq!(package.case_index(), core.case_index()); - assert_eq!(package.constraint().value(), core.constraint().value()); - assert_eq!(package.occurrence().value(), core.target().value()); + assert_eq!(public.case_index(), core.case_index()); + assert_eq!(public.constraint().value(), core.constraint().value()); + assert_eq!(public.occurrence().value(), core.target().value()); assert_eq!( - matches!( - package.mode(), - crate::package_bridge::PackageProgramConstraintModeV1::Hard - ), + matches!(public.mode(), ConstraintModeV1::Hard), core.is_hard() ); - assert_package_assessment_matches_core(package.assessment(), core.result(), core.target()); + assert_public_assessment_matches_core(public.assessment(), core.result(), core.target()); } fn assert_conflict_cell_matches_core( - package: PackageProgramConflictCellV1<'_>, + public: ConflictCellV1<'_>, core: &ProgramConstraintCellV1, ) { - assert_eq!(package.state_index(), core.candidate_state_index()); - assert_eq!(package.case_index(), core.case_index()); - assert_eq!(package.constraint().value(), core.constraint().value()); - assert_eq!(package.occurrence().value(), core.target().value()); + assert_eq!(public.state_index(), core.candidate_state_index()); + assert_eq!(public.case_index(), core.case_index()); + assert_eq!(public.constraint().value(), core.constraint().value()); + assert_eq!(public.occurrence().value(), core.target().value()); assert_eq!( - matches!( - package.mode(), - crate::package_bridge::PackageProgramConstraintModeV1::Hard - ), + matches!(public.mode(), ConstraintModeV1::Hard), core.is_hard() ); - assert_package_assessment_matches_core(package.assessment(), core.result(), core.target()); + assert_public_assessment_matches_core(public.assessment(), core.result(), core.target()); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -518,20 +519,17 @@ fn consume_exact_fused( probe.iterator_laws_hold &= iterator.next().is_none(); } -fn consume_package_assessment( - assessment: PackageProgramAssessmentV1<'_>, - probe: &mut ProjectionProbe, -) { +fn consume_public_assessment(assessment: AssessmentV1<'_>, probe: &mut ProjectionProbe) { probe.mix(match assessment.verdict() { - PackageProgramVerdictV1::Pass => 1, - PackageProgramVerdictV1::Violation => 2, + VerdictV1::Pass => 1, + VerdictV1::Violation => 2, }); match assessment { - PackageProgramAssessmentV1::ExactSrgb8(evidence) => { + AssessmentV1::ExactSrgb8(evidence) => { probe.exact_assessments += 1; probe.mix_srgb8(evidence.expected()); } - PackageProgramAssessmentV1::Wcag22Srgb8(evidence) => { + AssessmentV1::Wcag22Srgb8(evidence) => { probe.wcag_assessments += 1; probe.mix_bytes(evidence.profile_id().key().as_bytes()); probe.mix_bytes(evidence.criterion().key().as_bytes()); @@ -543,8 +541,7 @@ fn consume_package_assessment( } } - let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(physical) = - assessment.binding().physical(); + let PhysicalPointV1::EncodedSrgb8SourceOver(physical) = assessment.binding().physical(); probe.mix(u64::from(physical.subject_paint().value())); probe.mix(u64::from(physical.backdrop_surface().value())); probe.mix_srgb8(physical.subject()); @@ -552,7 +549,7 @@ fn consume_package_assessment( probe.mix_srgb8(physical.backdrop()); probe.mix_srgb8(physical.visible()); - let PackageProgramModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(modeled) = + let ModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(modeled) = assessment.binding().modeled(); for coordinate in modeled.xyz() { probe.mix(coordinate.to_bits()); @@ -561,24 +558,24 @@ fn consume_package_assessment( probe.mix(context.adapting_luminance_cd_m2().to_bits()); probe.mix(context.background_luminance_ratio_yb_yw().to_bits()); probe.mix(match context.surround() { - PackageProgramSurroundV1::Average => 1, - PackageProgramSurroundV1::Dim => 2, - PackageProgramSurroundV1::Dark => 3, + SurroundV1::Average => 1, + SurroundV1::Dim => 2, + SurroundV1::Dark => 3, }); } -fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> ProjectionProbe { +fn consume_public_projection(projection: ProjectionV1<'_, '_>) -> ProjectionProbe { let view = projection.evidence(); let mut probe = ProjectionProbe::new(); probe.mix(match view.kind() { - PackageProgramStateKindV1::Waiting => 1, - PackageProgramStateKindV1::Ready => 2, - PackageProgramStateKindV1::Failed => 3, - PackageProgramStateKindV1::Stale => 4, + StateKindV1::Waiting => 1, + StateKindV1::Ready => 2, + StateKindV1::Failed => 3, + StateKindV1::Stale => 4, }); match view.observation_head() { - PackageProgramObservationHeadV1::Empty => probe.mix(0), - PackageProgramObservationHeadV1::Unknown { + ObservationHeadV1::Empty => probe.mix(0), + ObservationHeadV1::Unknown { stream, revision, reason_id, @@ -588,7 +585,7 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> probe.mix(revision); probe.mix(u64::from(reason_id)); } - PackageProgramObservationHeadV1::Observed { stream, revision } => { + ObservationHeadV1::Observed { stream, revision } => { probe.mix(2); probe.mix(u64::from(stream.value())); probe.mix(revision); @@ -612,7 +609,7 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> probe.cases += 1; consume_exact_fused(case.values(), probe, |value, probe| { probe.values += 1; - let PackageProgramSignalV1::Iec61966Srgb8D65(value) = value; + let SignalV1::Iec61966Srgb8D65(value) = value; probe.mix_srgb8(value); }); consume_exact_fused(case.provenance(), probe, |scenario, probe| { @@ -621,7 +618,7 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> }); }); match certificate { - PackageProgramCertificateV1::Verified(verified) => { + CertificateV1::Verified(verified) => { probe.mix( verified .selected_state_index() @@ -633,10 +630,10 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> probe.mix(u64::from(cell.constraint().value())); probe.mix(u64::from(cell.occurrence().value())); probe.mix(match cell.mode() { - crate::package_bridge::PackageProgramConstraintModeV1::Hard => 1, - crate::package_bridge::PackageProgramConstraintModeV1::ReportOnly => 2, + ConstraintModeV1::Hard => 1, + ConstraintModeV1::ReportOnly => 2, }); - consume_package_assessment(cell.assessment(), probe); + consume_public_assessment(cell.assessment(), probe); }); consume_exact_fused(verified.outputs(), probe, |output, probe| { probe.outputs += 1; @@ -646,7 +643,7 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> probe.mix(output.opacity().to_bits()); }); } - PackageProgramCertificateV1::Conflict(conflict) => { + CertificateV1::Conflict(conflict) => { probe.mix(conflict.considered_state_count() as u64); consume_exact_fused(conflict.cells(), probe, |cell, probe| { probe.cells += 1; @@ -655,10 +652,10 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> probe.mix(u64::from(cell.constraint().value())); probe.mix(u64::from(cell.occurrence().value())); probe.mix(match cell.mode() { - crate::package_bridge::PackageProgramConstraintModeV1::Hard => 1, - crate::package_bridge::PackageProgramConstraintModeV1::ReportOnly => 2, + ConstraintModeV1::Hard => 1, + ConstraintModeV1::ReportOnly => 2, }); - consume_package_assessment(cell.assessment(), probe); + consume_public_assessment(cell.assessment(), probe); }); } } @@ -666,7 +663,7 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> consume_exact_fused(projection.operations(), &mut probe, |operation, probe| { probe.operations += 1; match operation { - PackageProgramOperationV1::Set(set) => { + OperationV1::Set(set) => { probe.mix(1); probe.mix(u64::from(set.output_slot().value())); probe.mix_srgb8(set.source()); @@ -674,11 +671,11 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> probe.mix_bytes(set.certificate().content_identity().as_bytes()); probe.mix(set.certificate().observation().revision()); } - PackageProgramOperationV1::Remove(remove) => { + OperationV1::Remove(remove) => { probe.mix(2); probe.mix(u64::from(remove.output_slot().value())); } - PackageProgramOperationV1::Hold(hold) => { + OperationV1::Hold(hold) => { probe.mix(3); probe.mix(u64::from(hold.output_slot().value())); probe.mix_bytes(hold.certificate().content_identity().as_bytes()); @@ -689,12 +686,8 @@ fn consume_package_projection(projection: PackageProgramProjectionV1<'_, '_>) -> std::hint::black_box(probe) } -fn assert_observed_head( - head: PackageProgramObservationHeadV1, - expected_stream: u32, - expected_revision: u64, -) { - let PackageProgramObservationHeadV1::Observed { stream, revision } = head else { +fn assert_observed_head(head: ObservationHeadV1, expected_stream: u32, expected_revision: u64) { + let ObservationHeadV1::Observed { stream, revision } = head else { panic!("raw evidence must remain a closed Observed payload"); }; assert_eq!(stream.value(), expected_stream); @@ -702,12 +695,12 @@ fn assert_observed_head( } fn assert_unknown_head( - head: PackageProgramObservationHeadV1, + head: ObservationHeadV1, expected_stream: u32, expected_revision: u64, expected_reason: u32, ) { - let PackageProgramObservationHeadV1::Unknown { + let ObservationHeadV1::Unknown { stream, revision, reason_id, @@ -808,40 +801,37 @@ fn one_program_retains_typed_exact_and_wcag22_outcomes() { } #[test] -fn fixed_package_certificate_retains_none_selection_and_nonunit_output_opacity() { - let owner = PackageProgramOwnerV1::from_compiled(fixed_translucent_program()); +fn fixed_public_certificate_retains_none_selection_and_nonunit_output_opacity() { + let owner = OwnerV1::from_compiled(fixed_translucent_program()); let mut session = owner.instantiate(STREAM.value()).unwrap(); let white = [Srgb8::new([0xFF; 3])]; - let scenarios = [PackageProgramScenarioV1::new(1, &white)]; + let scenarios = [ScenarioV1::new(1, &white)]; let projection = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &scenarios, }, ) .unwrap(); - let Some(PackageProgramCertificateV1::Verified(certificate)) = - projection.evidence().certificates().next() + let Some(CertificateV1::Verified(certificate)) = projection.evidence().certificates().next() else { panic!("the exact translucent midpoint must be verified"); }; assert_eq!(certificate.selected_state_index(), None); - let PackageProgramAssessmentV1::ExactSrgb8(assessment) = - certificate.cells().next().unwrap().assessment() + let AssessmentV1::ExactSrgb8(assessment) = certificate.cells().next().unwrap().assessment() else { panic!("the fixed Program has one Exact certificate cell"); }; - let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(physical) = - assessment.binding().physical(); + let PhysicalPointV1::EncodedSrgb8SourceOver(physical) = assessment.binding().physical(); assert_eq!(physical.opacity().to_bits(), 0.5_f64.to_bits()); assert_eq!(physical.visible(), Srgb8::new([0x80; 3])); assert_eq!( certificate.outputs().next().unwrap().opacity().to_bits(), physical.opacity().to_bits() ); - let Some(PackageProgramOperationV1::Set(set)) = projection.operations().next() else { + let Some(OperationV1::Set(set)) = projection.operations().next() else { panic!("Verified must emit one Set"); }; assert_eq!(set.opacity().to_bits(), physical.opacity().to_bits()); @@ -926,13 +916,12 @@ fn mixed_family_conflict_is_exhaustive_and_keeps_report_only_non_gating() { } #[test] -fn package_projection_preserves_every_exposed_ready_and_conflict_field_against_core() { +fn public_projection_preserves_every_exposed_ready_and_conflict_field_against_core() { let ready_core_owner = finite_program([[0x80; 3], [0; 3]]); let ready_identity = ready_core_owner.content_identity(); - let ready_package_owner = - PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); + let ready_public_owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); let mut ready_core_session = ready_core_owner.instantiate(STREAM).unwrap(); - let mut ready_package_session = ready_package_owner.instantiate(STREAM.value()).unwrap(); + let mut ready_public_session = ready_public_owner.instantiate(STREAM.value()).unwrap(); let ready_backdrops = [[0xFF; 3], [0xFF; 3], [0x80; 3]]; let SessionState::Ready { current: core_verified, @@ -945,71 +934,70 @@ fn package_projection_preserves_every_exposed_ready_and_conflict_field_against_c let ready_white = [Srgb8::new([0xFF; 3])]; let ready_gray = [Srgb8::new([0x80; 3])]; let ready_scenarios = [ - PackageProgramScenarioV1::new(1, &ready_white), - PackageProgramScenarioV1::new(2, &ready_white), - PackageProgramScenarioV1::new(3, &ready_gray), + ScenarioV1::new(1, &ready_white), + ScenarioV1::new(2, &ready_white), + ScenarioV1::new(3, &ready_gray), ]; - let package_ready = ready_package_owner + let public_ready = ready_public_owner .update( - &mut ready_package_session, - PackageProgramUpdateV1::Observed { + &mut ready_public_session, + UpdateV1::Observed { revision: 1, scenarios: &ready_scenarios, }, ) .unwrap(); - let mut package_certificates = package_ready.evidence().certificates(); - assert_eq!(package_certificates.len(), 1); - let Some(PackageProgramCertificateV1::Verified(package_verified)) = package_certificates.next() - else { + let mut public_certificates = public_ready.evidence().certificates(); + assert_eq!(public_certificates.len(), 1); + let Some(CertificateV1::Verified(public_verified)) = public_certificates.next() else { panic!("Ready must retain exactly one Verified certificate"); }; - assert!(package_certificates.next().is_none()); - assert!(package_certificates.next().is_none()); + assert!(public_certificates.next().is_none()); + assert!(public_certificates.next().is_none()); assert_eq!( - package_verified.content_identity().as_bytes(), + public_verified.content_identity().as_bytes(), ready_identity.as_bytes() ); - assert_package_observation_matches_core( - package_verified.observation(), + assert_public_observation_matches_core( + public_verified.observation(), core_verified.report().observation(), ); assert_eq!( - package_verified.selected_state_index(), + public_verified.selected_state_index(), core_verified.selected_state_index() ); let selected_state_index = core_verified.selected_state_index().unwrap(); - let mut package_cells = package_verified.cells(); + let mut public_cells = public_verified.cells(); let mut core_cells = core_verified.report().cells().iter(); - assert_eq!(package_cells.len(), core_cells.len()); - while let (Some(package), Some(core)) = (package_cells.next(), core_cells.next()) { - assert_verified_cell_matches_core(package, core, selected_state_index); - assert_eq!(package_cells.len(), core_cells.len()); + assert_eq!(public_cells.len(), core_cells.len()); + while let (Some(public), Some(core)) = (public_cells.next(), core_cells.next()) { + assert_verified_cell_matches_core(public, core, selected_state_index); + assert_eq!(public_cells.len(), core_cells.len()); } - assert!(package_cells.next().is_none()); - assert!(package_cells.next().is_none()); + assert!(public_cells.next().is_none()); + assert!(public_cells.next().is_none()); assert!(core_cells.next().is_none()); - let mut package_outputs = package_verified.outputs(); + let mut public_outputs = public_verified.outputs(); let mut core_outputs = core_verified.outputs().iter(); - assert_eq!(package_outputs.len(), core_outputs.len()); - while let (Some(package), Some(core)) = (package_outputs.next(), core_outputs.next()) { - assert_eq!(package.output_slot().value(), core.output().value()); - assert_eq!(package.paint().value(), core.paint().id().value()); - assert_eq!(package.source(), core.paint().source()); + assert_eq!(public_outputs.len(), core_outputs.len()); + while let (Some(public), Some(core)) = (public_outputs.next(), core_outputs.next()) { + assert_eq!(public.output_slot().value(), core.output().value()); + assert_eq!(public.paint().value(), core.paint().id().value()); + assert_eq!(public.source(), core.paint().source()); assert_eq!( - package.opacity().to_bits(), + public.opacity().to_bits(), core.paint().opacity().value().to_bits() ); - assert_eq!(package_outputs.len(), core_outputs.len()); + assert_eq!(public_outputs.len(), core_outputs.len()); } - assert!(package_outputs.next().is_none()); - assert!(package_outputs.next().is_none()); + assert!(public_outputs.next().is_none()); + assert!(public_outputs.next().is_none()); assert!(core_outputs.next().is_none()); - let mut ready_operations = package_ready.operations(); + let mut ready_operations = public_ready.operations(); assert_eq!(ready_operations.len(), core_verified.outputs().len()); for core_output in core_verified.outputs() { - let Some(PackageProgramOperationV1::Set(set)) = ready_operations.next() else { + let Some(OperationV1::Set(set)) = ready_operations.next() else { panic!("every certified output must become exactly one Set"); }; assert_eq!(set.output_slot().value(), core_output.output().value()); @@ -1020,11 +1008,11 @@ fn package_projection_preserves_every_exposed_ready_and_conflict_field_against_c ); assert_eq!( set.certificate().content_identity(), - package_verified.content_identity() + public_verified.content_identity() ); assert_eq!( set.certificate().observation().revision(), - package_verified.observation().revision() + public_verified.observation().revision() ); } assert!(ready_operations.next().is_none()); @@ -1032,10 +1020,9 @@ fn package_projection_preserves_every_exposed_ready_and_conflict_field_against_c let conflict_core_owner = finite_program([[0; 3], [0xFF; 3]]); let conflict_identity = conflict_core_owner.content_identity(); - let conflict_package_owner = - PackageProgramOwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); + let conflict_public_owner = OwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); let mut conflict_core_session = conflict_core_owner.instantiate(STREAM).unwrap(); - let mut conflict_package_session = conflict_package_owner.instantiate(STREAM.value()).unwrap(); + let mut conflict_public_session = conflict_public_owner.instantiate(STREAM.value()).unwrap(); let conflict_backdrops = [[0xFF; 3], [0; 3]]; let SessionState::Failed { cause: core_conflict, @@ -1049,36 +1036,35 @@ fn package_projection_preserves_every_exposed_ready_and_conflict_field_against_c let conflict_white = [Srgb8::new([0xFF; 3])]; let conflict_black = [Srgb8::new([0; 3])]; let conflict_scenarios = [ - PackageProgramScenarioV1::new(1, &conflict_white), - PackageProgramScenarioV1::new(2, &conflict_black), + ScenarioV1::new(1, &conflict_white), + ScenarioV1::new(2, &conflict_black), ]; - let package_failed = conflict_package_owner + let public_failed = conflict_public_owner .update( - &mut conflict_package_session, - PackageProgramUpdateV1::Observed { + &mut conflict_public_session, + UpdateV1::Observed { revision: 1, scenarios: &conflict_scenarios, }, ) .unwrap(); - let mut package_certificates = package_failed.evidence().certificates(); - assert_eq!(package_certificates.len(), 1); - let Some(PackageProgramCertificateV1::Conflict(package_conflict)) = package_certificates.next() - else { + let mut public_certificates = public_failed.evidence().certificates(); + assert_eq!(public_certificates.len(), 1); + let Some(CertificateV1::Conflict(public_conflict)) = public_certificates.next() else { panic!("Failed without previous state must retain one Conflict certificate"); }; - assert!(package_certificates.next().is_none()); - assert!(package_certificates.next().is_none()); + assert!(public_certificates.next().is_none()); + assert!(public_certificates.next().is_none()); assert_eq!( - package_conflict.content_identity().as_bytes(), + public_conflict.content_identity().as_bytes(), conflict_identity.as_bytes() ); - assert_package_observation_matches_core( - package_conflict.observation(), + assert_public_observation_matches_core( + public_conflict.observation(), core_conflict.report().observation(), ); assert_eq!( - package_conflict.considered_state_count(), + public_conflict.considered_state_count(), core_conflict.considered_state_count() ); let core_passes = core_conflict @@ -1089,21 +1075,21 @@ fn package_projection_preserves_every_exposed_ready_and_conflict_field_against_c .count(); assert!(core_passes > 0); assert!(core_passes < core_conflict.report().cells().len()); - let mut package_cells = package_conflict.cells(); + let mut public_cells = public_conflict.cells(); let mut core_cells = core_conflict.report().cells().iter(); - assert_eq!(package_cells.len(), core_cells.len()); - while let (Some(package), Some(core)) = (package_cells.next(), core_cells.next()) { - assert_conflict_cell_matches_core(package, core); - assert_eq!(package_cells.len(), core_cells.len()); + assert_eq!(public_cells.len(), core_cells.len()); + while let (Some(public), Some(core)) = (public_cells.next(), core_cells.next()) { + assert_conflict_cell_matches_core(public, core); + assert_eq!(public_cells.len(), core_cells.len()); } - assert!(package_cells.next().is_none()); - assert!(package_cells.next().is_none()); + assert!(public_cells.next().is_none()); + assert!(public_cells.next().is_none()); assert!(core_cells.next().is_none()); - let mut failed_operations = package_failed.operations(); + let mut failed_operations = public_failed.operations(); assert_eq!(failed_operations.len(), 1); assert!(matches!( failed_operations.next(), - Some(PackageProgramOperationV1::Remove(_)) + Some(OperationV1::Remove(_)) )); assert!(failed_operations.next().is_none()); assert!(failed_operations.next().is_none()); @@ -1115,21 +1101,20 @@ fn committed_projection_is_zero_alloc_and_repeats_no_composite_transform_or_eval MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(0)); CORE_PROGRAM_ASSESSMENT_CALLS.with(|calls| calls.set(0)); - let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); + let owner = OwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); let mut session = owner.instantiate(STREAM.value()).unwrap(); let white = [Srgb8::new([0xFF; 3])]; - let white_only = [PackageProgramScenarioV1::new(1, &white)]; + let white_only = [ScenarioV1::new(1, &white)]; owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &white_only, }, ) .unwrap(); - let Some(PackageProgramCertificateV1::Verified(ready_certificate)) = - session.evidence().certificates().next() + let Some(CertificateV1::Verified(ready_certificate)) = session.evidence().certificates().next() else { panic!("black must be selected for the white-only physical support"); }; @@ -1142,7 +1127,7 @@ fn committed_projection_is_zero_alloc_and_repeats_no_composite_transform_or_eval assert!(ready_derivations > 0); assert!(ready_assessments > 0); let (ready_probe, ready_allocations) = crate::test_support::measured_allocations(|| { - consume_package_projection(std::hint::black_box(owner.project(&session).unwrap())) + consume_public_projection(std::hint::black_box(owner.project(&session).unwrap())) }); assert_eq!(ready_allocations, 0); assert!(ready_probe.iterator_laws_hold); @@ -1172,7 +1157,7 @@ fn committed_projection_is_zero_alloc_and_repeats_no_composite_transform_or_eval owner .update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 2, reason_id: 7, }, @@ -1182,7 +1167,7 @@ fn committed_projection_is_zero_alloc_and_repeats_no_composite_transform_or_eval let stale_derivations = MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get); let stale_assessments = CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get); let (stale_probe, stale_allocations) = crate::test_support::measured_allocations(|| { - consume_package_projection(std::hint::black_box(owner.project(&session).unwrap())) + consume_public_projection(std::hint::black_box(owner.project(&session).unwrap())) }); assert_eq!(stale_allocations, 0); assert!(stale_probe.iterator_laws_hold); @@ -1205,14 +1190,11 @@ fn committed_projection_is_zero_alloc_and_repeats_no_composite_transform_or_eval ); let black = [Srgb8::new([0; 3])]; - let opposing_backdrops = [ - PackageProgramScenarioV1::new(1, &white), - PackageProgramScenarioV1::new(2, &black), - ]; + let opposing_backdrops = [ScenarioV1::new(1, &white), ScenarioV1::new(2, &black)]; owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 3, scenarios: &opposing_backdrops, }, @@ -1222,7 +1204,7 @@ fn committed_projection_is_zero_alloc_and_repeats_no_composite_transform_or_eval let failed_derivations = MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get); let failed_assessments = CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get); let (failed_probe, failed_allocations) = crate::test_support::measured_allocations(|| { - consume_package_projection(std::hint::black_box(owner.project(&session).unwrap())) + consume_public_projection(std::hint::black_box(owner.project(&session).unwrap())) }); assert_eq!(failed_allocations, 0); assert!(failed_probe.iterator_laws_hold); @@ -1269,11 +1251,11 @@ fn observation_projection_is_invariant_under_every_scenario_permutation_and_keep let core_owner = finite_program([[0x80; 3], [0; 3]]); let content_identity = core_owner.content_identity(); - let package_owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); + let public_owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); let mut core_session = core_owner.instantiate(STREAM).unwrap(); - let mut package_session = package_owner.instantiate(STREAM.value()).unwrap(); - let package_values = BACKDROPS.map(|value| [Srgb8::new(value)]); - let mut first_package_backing = None; + let mut public_session = public_owner.instantiate(STREAM.value()).unwrap(); + let public_values = BACKDROPS.map(|value| [Srgb8::new(value)]); + let mut first_public_backing = None; let mut evaluation_counts_after_first = None; for permutation in PERMUTATIONS { @@ -1299,39 +1281,39 @@ fn observation_projection_is_invariant_under_every_scenario_permutation_and_keep else { panic!("black must pass both deduplicated physical cases"); }; - let package_scenarios = permutation - .map(|index| PackageProgramScenarioV1::new(IDS[index], &package_values[index])); - let package_state = package_owner + let public_scenarios = + permutation.map(|index| ScenarioV1::new(IDS[index], &public_values[index])); + let public_state = public_owner .update( - &mut package_session, - PackageProgramUpdateV1::Observed { + &mut public_session, + UpdateV1::Observed { revision: 1, - scenarios: &package_scenarios, + scenarios: &public_scenarios, }, ) .unwrap(); - let Some(PackageProgramCertificateV1::Verified(package_verified)) = - package_state.evidence().certificates().next() + let Some(CertificateV1::Verified(public_verified)) = + public_state.evidence().certificates().next() else { panic!("the canonical observation must keep one Verified certificate"); }; assert_eq!( - package_verified.content_identity().as_bytes(), + public_verified.content_identity().as_bytes(), content_identity.as_bytes() ); - assert_package_observation_matches_core( - package_verified.observation(), + assert_public_observation_matches_core( + public_verified.observation(), core_verified.report().observation(), ); - let projected_cases = package_verified + let projected_cases = public_verified .observation() .physical_cases() .map(|case| { let values = case .values() .map(|value| match value { - PackageProgramSignalV1::Iec61966Srgb8D65(value) => value, + SignalV1::Iec61966Srgb8D65(value) => value, }) .collect::>(); let provenance = case @@ -1349,11 +1331,10 @@ fn observation_projection_is_invariant_under_every_scenario_permutation_and_keep ] ); - let backing = PackageProgramCertificateV1::Verified(package_verified) - .observation_backing_ptr_for_test(); - match first_package_backing { + let backing = CertificateV1::Verified(public_verified).observation_backing_ptr_for_test(); + match first_public_backing { None => { - first_package_backing = Some(backing); + first_public_backing = Some(backing); evaluation_counts_after_first = Some(( crate::composition::source_over_evaluation_count(), MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get), @@ -1376,59 +1357,47 @@ fn observation_projection_is_invariant_under_every_scenario_permutation_and_keep } #[test] -fn concrete_package_bridge_projects_total_ready_and_stale_operations() { - let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); +fn concrete_program_projects_total_ready_and_stale_operations() { + let owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); assert_eq!(owner.surface_input_port_count(), 1); assert_eq!( owner.output_slots().collect::>(), - [PackageProgramOutputSlotIdV1::new(OUTPUT.value())] + [OutputSlotIdV1::new(OUTPUT.value())] ); let mut session = owner.instantiate(11).unwrap(); let initial = session.evidence(); - assert_eq!(initial.kind(), PackageProgramStateKindV1::Waiting); - assert_eq!( - initial.observation_head(), - PackageProgramObservationHeadV1::Empty - ); + assert_eq!(initial.kind(), StateKindV1::Waiting); + assert_eq!(initial.observation_head(), ObservationHeadV1::Empty); assert_eq!(initial.certificates().len(), 0); assert_eq!(owner.project(&session).unwrap().operations().len(), 0); let white = [Srgb8::new([0xFF; 3])]; let gray = [Srgb8::new([0x80; 3])]; - let scenarios = [ - PackageProgramScenarioV1::new(2, &gray), - PackageProgramScenarioV1::new(1, &white), - ]; + let scenarios = [ScenarioV1::new(2, &gray), ScenarioV1::new(1, &white)]; let ready = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &scenarios, }, ) .unwrap(); let ready_evidence = ready.evidence(); - assert_eq!(ready_evidence.kind(), PackageProgramStateKindV1::Ready); + assert_eq!(ready_evidence.kind(), StateKindV1::Ready); assert_observed_head(ready_evidence.observation_head(), STREAM.value(), 1); assert_eq!(ready_evidence.cause_certificate_index(), None); let certificates = ready_evidence.certificates().collect::>(); assert_eq!(certificates.len(), 1); - assert!(matches!( - certificates[0], - PackageProgramCertificateV1::Verified(_) - )); + assert!(matches!(certificates[0], CertificateV1::Verified(_))); assert_eq!(certificates[0].observation().revision(), 1); let ready_backing = certificates[0].observation_backing_ptr_for_test(); let mut operations = ready.operations(); - let Some(PackageProgramOperationV1::Set(set)) = operations.next() else { + let Some(OperationV1::Set(set)) = operations.next() else { panic!("Ready must emit one Set operation"); }; - assert_eq!( - set.output_slot(), - PackageProgramOutputSlotIdV1::new(OUTPUT.value()) - ); + assert_eq!(set.output_slot(), OutputSlotIdV1::new(OUTPUT.value())); assert_eq!(set.source(), Srgb8::new([0; 3])); assert_eq!(set.opacity(), 1.0); assert_eq!( @@ -1440,21 +1409,18 @@ fn concrete_package_bridge_projects_total_ready_and_stale_operations() { certificates[0].content_identity() ); assert_eq!( - PackageProgramCertificateV1::Verified(set.certificate()).observation_backing_ptr_for_test(), + CertificateV1::Verified(set.certificate()).observation_backing_ptr_for_test(), ready_backing ); assert!(operations.next().is_none()); drop(operations); drop(certificates); - let reordered = [ - PackageProgramScenarioV1::new(1, &white), - PackageProgramScenarioV1::new(2, &gray), - ]; + let reordered = [ScenarioV1::new(1, &white), ScenarioV1::new(2, &gray)]; let replay = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &reordered, }, @@ -1467,10 +1433,10 @@ fn concrete_package_bridge_projects_total_ready_and_stale_operations() { "scenario permutation at the same revision must be exact idempotence" ); - let changed_same_revision = [PackageProgramScenarioV1::new(1, &white)]; + let changed_same_revision = [ScenarioV1::new(1, &white)]; let error = match owner.update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &changed_same_revision, }, @@ -1478,40 +1444,31 @@ fn concrete_package_bridge_projects_total_ready_and_stale_operations() { Ok(_) => panic!("changed payload at the same revision must be rejected"), Err(error) => error, }; - assert_eq!( - error.kind(), - PackageProgramUpdateErrorKindV1::RevisionConflict - ); - assert_eq!(session.evidence().kind(), PackageProgramStateKindV1::Ready); + assert_eq!(error.kind(), UpdateErrorKindV1::RevisionConflict); + assert_eq!(session.evidence().kind(), StateKindV1::Ready); assert_observed_head(session.evidence().observation_head(), STREAM.value(), 1); let stale = owner .update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 2, reason_id: 7, }, ) .unwrap(); let stale_evidence = stale.evidence(); - assert_eq!(stale_evidence.kind(), PackageProgramStateKindV1::Stale); + assert_eq!(stale_evidence.kind(), StateKindV1::Stale); assert_unknown_head(stale_evidence.observation_head(), STREAM.value(), 2, 7); let certificates = stale_evidence.certificates().collect::>(); assert_eq!(certificates.len(), 1); - assert!(matches!( - certificates[0], - PackageProgramCertificateV1::Verified(_) - )); + assert!(matches!(certificates[0], CertificateV1::Verified(_))); assert_eq!(certificates[0].observation().revision(), 1); let mut operations = stale.operations(); - let Some(PackageProgramOperationV1::Hold(hold)) = operations.next() else { + let Some(OperationV1::Hold(hold)) = operations.next() else { panic!("Stale must emit one Hold operation"); }; - assert_eq!( - hold.output_slot(), - PackageProgramOutputSlotIdV1::new(OUTPUT.value()) - ); + assert_eq!(hold.output_slot(), OutputSlotIdV1::new(OUTPUT.value())); assert_eq!( hold.certificate().observation().revision(), certificates[0].observation().revision() @@ -1521,56 +1478,49 @@ fn concrete_package_bridge_projects_total_ready_and_stale_operations() { certificates[0].content_identity() ); assert_eq!( - PackageProgramCertificateV1::Verified(hold.certificate()) - .observation_backing_ptr_for_test(), + CertificateV1::Verified(hold.certificate()).observation_backing_ptr_for_test(), ready_backing ); assert!(operations.next().is_none()); } #[test] -fn concrete_package_bridge_distinguishes_failed_remove_from_failed_hold() { +fn concrete_program_distinguishes_failed_remove_from_failed_hold() { let white = [Srgb8::new([0xFF; 3])]; let black = [Srgb8::new([0; 3])]; - let white_only = [PackageProgramScenarioV1::new(1, &white)]; + let white_only = [ScenarioV1::new(1, &white)]; - let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0xFF; 3]])); + let owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0xFF; 3]])); let mut session = owner.instantiate(11).unwrap(); let failed = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &white_only, }, ) .unwrap(); let failed_evidence = failed.evidence(); - assert_eq!(failed_evidence.kind(), PackageProgramStateKindV1::Failed); + assert_eq!(failed_evidence.kind(), StateKindV1::Failed); assert_eq!(failed_evidence.cause_certificate_index(), Some(0)); let certificates = failed_evidence.certificates().collect::>(); assert_eq!(certificates.len(), 1); - assert!(matches!( - certificates[0], - PackageProgramCertificateV1::Conflict(_) - )); + assert!(matches!(certificates[0], CertificateV1::Conflict(_))); assert_eq!(certificates[0].observation().revision(), 1); let mut operations = failed.operations(); - let Some(PackageProgramOperationV1::Remove(remove)) = operations.next() else { + let Some(OperationV1::Remove(remove)) = operations.next() else { panic!("Failed without previous evidence must emit one Remove operation"); }; - assert_eq!( - remove.output_slot(), - PackageProgramOutputSlotIdV1::new(OUTPUT.value()) - ); + assert_eq!(remove.output_slot(), OutputSlotIdV1::new(OUTPUT.value())); assert!(operations.next().is_none()); - let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); + let owner = OwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); let mut session = owner.instantiate(12).unwrap(); let previous = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &white_only, }, @@ -1582,31 +1532,28 @@ fn concrete_package_bridge_distinguishes_failed_remove_from_failed_hold() { .next() .unwrap() .observation_backing_ptr_for_test(); - let both = [ - PackageProgramScenarioV1::new(1, &white), - PackageProgramScenarioV1::new(2, &black), - ]; + let both = [ScenarioV1::new(1, &white), ScenarioV1::new(2, &black)]; let failed = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 2, scenarios: &both, }, ) .unwrap(); let failed_evidence = failed.evidence(); - assert_eq!(failed_evidence.kind(), PackageProgramStateKindV1::Failed); + assert_eq!(failed_evidence.kind(), StateKindV1::Failed); assert_eq!(failed_evidence.cause_certificate_index(), Some(0)); let certificates = failed_evidence.certificates().collect::>(); assert_eq!( certificates .iter() .map(|certificate| match certificate { - PackageProgramCertificateV1::Verified(value) => { + CertificateV1::Verified(value) => { ("verified", value.observation().revision()) } - PackageProgramCertificateV1::Conflict(value) => { + CertificateV1::Conflict(value) => { ("conflict", value.observation().revision()) } }) @@ -1614,73 +1561,60 @@ fn concrete_package_bridge_distinguishes_failed_remove_from_failed_hold() { [("conflict", 2), ("verified", 1)] ); let mut operations = failed.operations(); - let Some(PackageProgramOperationV1::Hold(hold)) = operations.next() else { + let Some(OperationV1::Hold(hold)) = operations.next() else { panic!("Failed with previous evidence must emit one Hold operation"); }; - assert_eq!( - hold.output_slot(), - PackageProgramOutputSlotIdV1::new(OUTPUT.value()) - ); + assert_eq!(hold.output_slot(), OutputSlotIdV1::new(OUTPUT.value())); assert_eq!(hold.certificate().observation().revision(), 1); assert_eq!( hold.certificate().content_identity(), certificates[1].content_identity() ); assert_eq!( - PackageProgramCertificateV1::Verified(hold.certificate()) - .observation_backing_ptr_for_test(), + CertificateV1::Verified(hold.certificate()).observation_backing_ptr_for_test(), previous_backing ); assert!(operations.next().is_none()); } #[test] -fn concrete_package_bridge_rejects_transport_shape_before_core_admission() { - let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); +fn concrete_program_rejects_transport_shape_before_core_admission() { + let owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); let mut session = owner.instantiate(11).unwrap(); let empty_values = []; - let malformed = [PackageProgramScenarioV1::new(1, &empty_values)]; + let malformed = [ScenarioV1::new(1, &empty_values)]; let error = match owner.update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &malformed, }, ) { - Ok(_) => panic!("schema-short package input must fail before Core admission"), + Ok(_) => panic!("schema-short public input must fail before Core admission"), Err(error) => error, }; - assert_eq!( - error.kind(), - PackageProgramUpdateErrorKindV1::InvalidObservation - ); - assert_eq!( - session.evidence().kind(), - PackageProgramStateKindV1::Waiting - ); + assert_eq!(error.kind(), UpdateErrorKindV1::InvalidObservation); + assert_eq!(session.evidence().kind(), StateKindV1::Waiting); assert_eq!( session.evidence().observation_head(), - PackageProgramObservationHeadV1::Empty + ObservationHeadV1::Empty ); let white = [Srgb8::new([0xFF; 3])]; - let valid = [PackageProgramScenarioV1::new(1, &white)]; + let valid = [ScenarioV1::new(1, &white)]; owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 2, scenarios: &valid, }, ) .unwrap(); - let duplicate = [ - PackageProgramScenarioV1::new(7, &white), - PackageProgramScenarioV1::new(7, &white), - ]; + let duplicate = [ScenarioV1::new(7, &white), ScenarioV1::new(7, &white)]; let error = match owner.update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &duplicate, }, @@ -1688,11 +1622,8 @@ fn concrete_package_bridge_rejects_transport_shape_before_core_admission() { Ok(_) => panic!("duplicate scenario IDs must precede revision admission"), Err(error) => error, }; - assert_eq!( - error.kind(), - PackageProgramUpdateErrorKindV1::InvalidObservation - ); - assert_eq!(session.evidence().kind(), PackageProgramStateKindV1::Ready); + assert_eq!(error.kind(), UpdateErrorKindV1::InvalidObservation); + assert_eq!(session.evidence().kind(), StateKindV1::Ready); assert_observed_head(session.evidence().observation_head(), 11, 2); } @@ -1705,16 +1636,16 @@ fn same_content_foreign_owner_is_rejected_before_admission_without_work() { let compiled_a = finite_program([[0x80; 3], [0; 3]]); let compiled_b = finite_program([[0x80; 3], [0; 3]]); assert_eq!(compiled_a.content_identity(), compiled_b.content_identity()); - let owner_a = PackageProgramOwnerV1::from_compiled(compiled_a); - let owner_b = PackageProgramOwnerV1::from_compiled(compiled_b); + let owner_a = OwnerV1::from_compiled(compiled_a); + let owner_b = OwnerV1::from_compiled(compiled_b); let mut session = owner_a.instantiate(STREAM.value()).unwrap(); let white = [Srgb8::new([0xFF; 3])]; - let observed = [PackageProgramScenarioV1::new(1, &white)]; + let observed = [ScenarioV1::new(1, &white)]; owner_a .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &observed, }, @@ -1736,24 +1667,24 @@ fn same_content_foreign_owner_is_rejected_before_admission_without_work() { let (project_mismatch, project_allocations) = crate::test_support::measured_allocations(|| { matches!( owner_b.project(std::hint::black_box(&session)), - Err(PackageProgramAccessErrorV1::OwnerMismatch) + Err(AccessErrorV1::OwnerMismatch) ) }); assert!(project_mismatch); assert_eq!(project_allocations, 0); let empty = []; - let malformed = [PackageProgramScenarioV1::new(2, &empty)]; + let malformed = [ScenarioV1::new(2, &empty)]; let (update_mismatch, update_allocations) = crate::test_support::measured_allocations(|| { matches!( owner_b.update( std::hint::black_box(&mut session), - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 2, scenarios: &malformed, }, ), - Err(error) if error.kind() == PackageProgramUpdateErrorKindV1::OwnerMismatch + Err(error) if error.kind() == UpdateErrorKindV1::OwnerMismatch ) }); assert!(update_mismatch); @@ -1768,7 +1699,7 @@ fn same_content_foreign_owner_is_rejected_before_admission_without_work() { ); let after = session.evidence(); - assert_eq!(after.kind(), PackageProgramStateKindV1::Ready); + assert_eq!(after.kind(), StateKindV1::Ready); assert_observed_head(after.observation_head(), STREAM.value(), 1); assert_eq!( after @@ -1783,25 +1714,25 @@ fn same_content_foreign_owner_is_rejected_before_admission_without_work() { #[test] fn raw_observation_head_preserves_unknown_reason_independently_of_lifecycle() { - let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); + let owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); let mut session = owner.instantiate(STREAM.value()).unwrap(); assert_eq!( session.evidence().observation_head(), - PackageProgramObservationHeadV1::Empty + ObservationHeadV1::Empty ); owner .update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 1, reason_id: u32::MAX, }, ) .unwrap(); let unknown = session.evidence(); - assert_eq!(unknown.kind(), PackageProgramStateKindV1::Waiting); - let PackageProgramObservationHeadV1::Unknown { + assert_eq!(unknown.kind(), StateKindV1::Waiting); + let ObservationHeadV1::Unknown { stream, revision, reason_id, @@ -1817,7 +1748,7 @@ fn raw_observation_head_preserves_unknown_reason_independently_of_lifecycle() { owner .update( &mut other_session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 1, reason_id: u32::MAX, }, @@ -1831,7 +1762,7 @@ fn raw_observation_head_preserves_unknown_reason_independently_of_lifecycle() { let conflicting = match owner.update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 1, reason_id: 0, }, @@ -1839,10 +1770,7 @@ fn raw_observation_head_preserves_unknown_reason_independently_of_lifecycle() { Ok(_) => panic!("same revision with another reason must conflict"), Err(error) => error, }; - assert_eq!( - conflicting.kind(), - PackageProgramUpdateErrorKindV1::RevisionConflict - ); + assert_eq!(conflicting.kind(), UpdateErrorKindV1::RevisionConflict); assert_unknown_head( session.evidence().observation_head(), STREAM.value(), @@ -1854,12 +1782,12 @@ fn raw_observation_head_preserves_unknown_reason_independently_of_lifecycle() { #[test] fn copied_raw_heads_outlive_owner_and_session_without_losing_provenance() { let (unknown, observed) = { - let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); + let owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); let mut session = owner.instantiate(u32::MAX).unwrap(); owner .update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 1, reason_id: u32::MAX, }, @@ -1868,11 +1796,11 @@ fn copied_raw_heads_outlive_owner_and_session_without_losing_provenance() { let unknown = session.evidence().observation_head(); let white = [Srgb8::new([0xFF; 3])]; - let scenarios = [PackageProgramScenarioV1::new(1, &white)]; + let scenarios = [ScenarioV1::new(1, &white)]; owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: u64::MAX, scenarios: &scenarios, }, @@ -1891,15 +1819,15 @@ fn expired_owner_preserves_historical_evidence_but_equivalent_owner_has_no_autho let compiled_a = finite_program([[0x80; 3], [0; 3]]); let compiled_b = finite_program([[0x80; 3], [0; 3]]); assert_eq!(compiled_a.content_identity(), compiled_b.content_identity()); - let owner_a = PackageProgramOwnerV1::from_compiled(compiled_a); - let owner_b = PackageProgramOwnerV1::from_compiled(compiled_b); + let owner_a = OwnerV1::from_compiled(compiled_a); + let owner_b = OwnerV1::from_compiled(compiled_b); let mut session = owner_a.instantiate(STREAM.value()).unwrap(); let white = [Srgb8::new([0xFF; 3])]; - let observed = [PackageProgramScenarioV1::new(1, &white)]; + let observed = [ScenarioV1::new(1, &white)]; owner_a .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &observed, }, @@ -1918,11 +1846,11 @@ fn expired_owner_preserves_historical_evidence_but_equivalent_owner_has_no_autho assert!(matches!( owner_b.project(&session), - Err(PackageProgramAccessErrorV1::OwnerMismatch) + Err(AccessErrorV1::OwnerMismatch) )); let mismatch = match owner_b.update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 2, reason_id: 9, }, @@ -1930,10 +1858,7 @@ fn expired_owner_preserves_historical_evidence_but_equivalent_owner_has_no_autho Ok(_) => panic!("equivalent recompile must not revive another owner generation"), Err(error) => error, }; - assert_eq!( - mismatch.kind(), - PackageProgramUpdateErrorKindV1::OwnerMismatch - ); + assert_eq!(mismatch.kind(), UpdateErrorKindV1::OwnerMismatch); assert_eq!( session .evidence() @@ -1947,20 +1872,17 @@ fn expired_owner_preserves_historical_evidence_but_equivalent_owner_has_no_autho #[test] fn raw_head_and_evaluator_lifecycle_form_the_exact_reachable_product() { - let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); + let owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); let mut session = owner.instantiate(STREAM.value()).unwrap(); let white = [Srgb8::new([0xFF; 3])]; let black = [Srgb8::new([0; 3])]; - let white_only = [PackageProgramScenarioV1::new(1, &white)]; - let opposing = [ - PackageProgramScenarioV1::new(1, &white), - PackageProgramScenarioV1::new(2, &black), - ]; + let white_only = [ScenarioV1::new(1, &white)]; + let opposing = [ScenarioV1::new(1, &white), ScenarioV1::new(2, &black)]; owner .update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 1, reason_id: 3, }, @@ -1969,27 +1891,27 @@ fn raw_head_and_evaluator_lifecycle_form_the_exact_reachable_product() { owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 2, scenarios: &white_only, }, ) .unwrap(); let ready = session.evidence(); - assert_eq!(ready.kind(), PackageProgramStateKindV1::Ready); + assert_eq!(ready.kind(), StateKindV1::Ready); assert_observed_head(ready.observation_head(), STREAM.value(), 2); owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 3, scenarios: &opposing, }, ) .unwrap(); let failed = session.evidence(); - assert_eq!(failed.kind(), PackageProgramStateKindV1::Failed); + assert_eq!(failed.kind(), StateKindV1::Failed); assert_observed_head(failed.observation_head(), STREAM.value(), 3); assert_eq!( failed @@ -2002,14 +1924,14 @@ fn raw_head_and_evaluator_lifecycle_form_the_exact_reachable_product() { owner .update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 4, reason_id: 17, }, ) .unwrap(); let stale = session.evidence(); - assert_eq!(stale.kind(), PackageProgramStateKindV1::Stale); + assert_eq!(stale.kind(), StateKindV1::Stale); assert_unknown_head(stale.observation_head(), STREAM.value(), 4, 17); assert_eq!( stale @@ -2019,40 +1941,36 @@ fn raw_head_and_evaluator_lifecycle_form_the_exact_reachable_product() { [2] ); - let rejecting_owner = - PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0xFF; 3]])); + let rejecting_owner = OwnerV1::from_compiled(finite_program([[0x80; 3], [0xFF; 3]])); let mut rejecting_session = rejecting_owner.instantiate(STREAM.value()).unwrap(); rejecting_owner .update( &mut rejecting_session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &white_only, }, ) .unwrap(); - assert_eq!( - rejecting_session.evidence().kind(), - PackageProgramStateKindV1::Failed - ); + assert_eq!(rejecting_session.evidence().kind(), StateKindV1::Failed); rejecting_owner .update( &mut rejecting_session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 2, reason_id: 23, }, ) .unwrap(); let waiting = rejecting_session.evidence(); - assert_eq!(waiting.kind(), PackageProgramStateKindV1::Waiting); + assert_eq!(waiting.kind(), StateKindV1::Waiting); assert_unknown_head(waiting.observation_head(), STREAM.value(), 2, 23); assert_eq!(waiting.certificates().len(), 0); } #[test] fn failed_without_previous_removes_every_output_in_canonical_exact_order() { - let owner = PackageProgramOwnerV1::from_compiled(finite_program_with_outputs( + let owner = OwnerV1::from_compiled(finite_program_with_outputs( [[0x80; 3], [0xFF; 3]], vec![ OutputBinding::new(SECOND_OUTPUT, PAINT), @@ -2061,38 +1979,35 @@ fn failed_without_previous_removes_every_output_in_canonical_exact_order() { )); let mut session = owner.instantiate(STREAM.value()).unwrap(); let white = [Srgb8::new([0xFF; 3])]; - let scenarios = [PackageProgramScenarioV1::new(1, &white)]; + let scenarios = [ScenarioV1::new(1, &white)]; let failed = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &scenarios, }, ) .unwrap(); - assert_eq!(failed.evidence().kind(), PackageProgramStateKindV1::Failed); + assert_eq!(failed.evidence().kind(), StateKindV1::Failed); assert_eq!(failed.evidence().certificates().len(), 1); let mut operations = failed.operations(); assert_eq!(operations.len(), 2); assert_eq!(operations.size_hint(), (2, Some(2))); - let Some(PackageProgramOperationV1::Remove(first)) = operations.next() else { + let Some(OperationV1::Remove(first)) = operations.next() else { panic!("Failed without previous evidence must remove the first output"); }; - assert_eq!( - first.output_slot(), - PackageProgramOutputSlotIdV1::new(OUTPUT.value()) - ); + assert_eq!(first.output_slot(), OutputSlotIdV1::new(OUTPUT.value())); assert_eq!(operations.len(), 1); assert_eq!(operations.size_hint(), (1, Some(1))); - let Some(PackageProgramOperationV1::Remove(second)) = operations.next() else { + let Some(OperationV1::Remove(second)) = operations.next() else { panic!("Failed without previous evidence must remove the second output"); }; assert_eq!( second.output_slot(), - PackageProgramOutputSlotIdV1::new(SECOND_OUTPUT.value()) + OutputSlotIdV1::new(SECOND_OUTPUT.value()) ); assert_eq!(operations.len(), 0); assert_eq!(operations.size_hint(), (0, Some(0))); @@ -2102,7 +2017,7 @@ fn failed_without_previous_removes_every_output_in_canonical_exact_order() { #[test] fn ready_and_stale_project_every_output_in_the_same_canonical_order() { - let owner = PackageProgramOwnerV1::from_compiled(finite_program_with_outputs( + let owner = OwnerV1::from_compiled(finite_program_with_outputs( [[0x80; 3], [0; 3]], vec![ OutputBinding::new(SECOND_OUTPUT, PAINT), @@ -2111,13 +2026,13 @@ fn ready_and_stale_project_every_output_in_the_same_canonical_order() { )); let mut session = owner.instantiate(STREAM.value()).unwrap(); let white = [Srgb8::new([0xFF; 3])]; - let scenarios = [PackageProgramScenarioV1::new(1, &white)]; + let scenarios = [ScenarioV1::new(1, &white)]; { let ready = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &scenarios, }, @@ -2126,7 +2041,7 @@ fn ready_and_stale_project_every_output_in_the_same_canonical_order() { let mut operations = ready.operations(); assert_eq!(operations.len(), 2); for expected in [OUTPUT, SECOND_OUTPUT] { - let Some(PackageProgramOperationV1::Set(set)) = operations.next() else { + let Some(OperationV1::Set(set)) = operations.next() else { panic!("Ready must set every compiled output"); }; assert_eq!(set.output_slot().value(), expected.value()); @@ -2138,7 +2053,7 @@ fn ready_and_stale_project_every_output_in_the_same_canonical_order() { let stale = owner .update( &mut session, - PackageProgramUpdateV1::Unknown { + UpdateV1::Unknown { revision: 2, reason_id: 7, }, @@ -2147,7 +2062,7 @@ fn ready_and_stale_project_every_output_in_the_same_canonical_order() { let mut operations = stale.operations(); assert_eq!(operations.len(), 2); for expected in [OUTPUT, SECOND_OUTPUT] { - let Some(PackageProgramOperationV1::Hold(hold)) = operations.next() else { + let Some(OperationV1::Hold(hold)) = operations.next() else { panic!("Stale must hold every previously verified output"); }; assert_eq!(hold.output_slot().value(), expected.value()); diff --git a/crates/labcolors-core/src/program_session.rs b/crates/labcolors-core/src/program_session.rs index 5c49e46b..83d151c3 100644 --- a/crates/labcolors-core/src/program_session.rs +++ b/crates/labcolors-core/src/program_session.rs @@ -1416,17 +1416,22 @@ pub enum ProgramSessionEvaluationError { Evaluator { case_index: usize, constraint: ConstraintId, + occurrence: OccurrenceId, + context: AppearanceContextId, source: EvaluationError, }, ProgramTargetBinding { case_index: usize, constraint: ConstraintId, + occurrence: OccurrenceId, + context: AppearanceContextId, physical: Srgb8, modeled: Srgb8, }, ModeledOccurrence { case_index: usize, - target: OccurrenceId, + occurrence: OccurrenceId, + context: AppearanceContextId, source: ModeledLcsOccurrenceFormationErrorV1, }, OutputVariesAcrossCases { @@ -1923,7 +1928,8 @@ where .map_err(|source| { ProgramSessionEvaluationError::ModeledOccurrence { case_index, - target: constraint.target_id, + occurrence: constraint.target_id, + context: binding.context, source, } })?; @@ -1947,6 +1953,8 @@ where ProgramSessionEvaluationError::ProgramTargetBinding { case_index, constraint: constraint.id, + occurrence: constraint.target_id, + context: modeled_lcs_occurrence.occurrence().context(), physical: source.physical(), modeled: source.modeled(), } @@ -1955,6 +1963,8 @@ where ProgramSessionEvaluationError::Evaluator { case_index, constraint: constraint.id, + occurrence: constraint.target_id, + context: modeled_lcs_occurrence.occurrence().context(), source, } } diff --git a/crates/labcolors-core/tests/program_boundary.rs b/crates/labcolors-core/tests/program_boundary.rs index cd0a2fc6..d3588da4 100644 --- a/crates/labcolors-core/tests/program_boundary.rs +++ b/crates/labcolors-core/tests/program_boundary.rs @@ -7,22 +7,15 @@ use core::iter::FusedIterator; use labcolors_core::Srgb8; -use labcolors_core::package_bridge::{ - PackageProgramAppearanceContextErrorKindV1, PackageProgramAppearanceContextFieldV1, - PackageProgramAppearanceContextV1, PackageProgramAssessmentV1, PackageProgramCertificateV1, - PackageProgramCompileErrorHandleV1, PackageProgramCompileErrorKindV1, - PackageProgramCompileErrorV1, PackageProgramConstraintIdV1, PackageProgramDraftErrorV1, - PackageProgramDraftV1, PackageProgramInstantiateErrorV1, PackageProgramJointChoiceV1, - PackageProgramJointOrderErrorV1, PackageProgramJointStateV1, PackageProgramModeledPointV1, - PackageProgramNumericDomainErrorV1, PackageProgramObservationHeadV1, - PackageProgramOccurrenceIdV1, PackageProgramOpacityInputIdV1, PackageProgramOperationV1, - PackageProgramOutputSlotIdV1, PackageProgramOwnerV1, PackageProgramPaintIdV1, - PackageProgramPhysicalPointV1, PackageProgramProjectionV1, PackageProgramScenarioV1, - PackageProgramSessionV1, PackageProgramSignalV1, PackageProgramSourceIdV1, - PackageProgramStateKindV1, PackageProgramSurfaceIdV1, PackageProgramSurfaceInputPortIdV1, - PackageProgramSurroundV1, PackageProgramTargetCandidateIdV1, PackageProgramTargetCandidateV1, - PackageProgramTargetIdV1, PackageProgramUpdateErrorKindV1, PackageProgramUpdateV1, - PackageProgramVerdictV1, +use labcolors_core::program::{ + AppearanceContextErrorKindV1, AppearanceContextFieldV1, AppearanceContextV1, AssessmentV1, + CertificateV1, CompileErrorHandleV1, CompileErrorKindV1, CompileErrorV1, ConstraintIdV1, + DraftErrorV1, DraftV1, InstantiateErrorV1, JointChoiceV1, JointOrderErrorV1, JointStateV1, + ModeledPointV1, 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, }; use labcolors_core::wcag22::Wcag22CriterionV1; @@ -30,7 +23,7 @@ fn exact_size(iterator: I) -> I { iterator } -fn compile_error(draft: PackageProgramDraftV1) -> PackageProgramCompileErrorV1 { +fn compile_error(draft: DraftV1) -> CompileErrorV1 { match draft.compile() { Ok(_) => panic!("the invalid authored program must not compile"), Err(error) => error, @@ -39,12 +32,12 @@ fn compile_error(draft: PackageProgramDraftV1) -> PackageProgramCompileErrorV1 { #[allow(dead_code)] fn wasm_can_use_only_the_concrete_owner_and_session( - owner: &PackageProgramOwnerV1, - session: &mut PackageProgramSessionV1, - scenarios: &[PackageProgramScenarioV1<'_>], -) -> Result<(), PackageProgramInstantiateErrorV1> { + owner: &OwnerV1, + session: &mut SessionV1, + scenarios: &[ScenarioV1<'_>], +) -> Result<(), InstantiateErrorV1> { let _independent_session = owner.instantiate(0xA11CE)?; - let update = PackageProgramUpdateV1::Observed { + let update = UpdateV1::Observed { revision: 1, scenarios, }; @@ -55,12 +48,12 @@ fn wasm_can_use_only_the_concrete_owner_and_session( Ok(()) } -fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, '_>) { +fn assert_projection_is_owner_bound(projection: ProjectionV1<'_, '_>) { let view = projection.evidence(); - let _kind: PackageProgramStateKindV1 = view.kind(); + let _kind: StateKindV1 = view.kind(); match view.observation_head() { - PackageProgramObservationHeadV1::Empty => {} - PackageProgramObservationHeadV1::Unknown { + ObservationHeadV1::Empty => {} + ObservationHeadV1::Unknown { stream, revision, reason_id, @@ -69,7 +62,7 @@ fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, ' let _: u64 = revision; let _: u32 = reason_id; } - PackageProgramObservationHeadV1::Observed { stream, revision } => { + ObservationHeadV1::Observed { stream, revision } => { let _ = stream.value(); let _: u64 = revision; } @@ -82,11 +75,11 @@ fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, ' let _stream_id = observation.stream().value(); let _revision = observation.revision(); for port in exact_size(observation.surface_input_ports()) { - let _: PackageProgramSurfaceInputPortIdV1 = port; + let _: SurfaceInputPortIdV1 = port; } for case in exact_size(observation.physical_cases()) { for value in exact_size(case.values()) { - let PackageProgramSignalV1::Iec61966Srgb8D65(value) = value; + let SignalV1::Iec61966Srgb8D65(value) = value; let _: Srgb8 = value; } for scenario in exact_size(case.provenance()) { @@ -101,12 +94,12 @@ fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, ' let _ = cell.occurrence().value(); let _ = cell.mode(); let assessment = cell.assessment(); - let _: PackageProgramVerdictV1 = assessment.verdict(); + let _: VerdictV1 = assessment.verdict(); match assessment { - PackageProgramAssessmentV1::ExactSrgb8(evidence) => { + AssessmentV1::ExactSrgb8(evidence) => { let _: Srgb8 = evidence.expected(); } - PackageProgramAssessmentV1::Wcag22Srgb8(evidence) => { + AssessmentV1::Wcag22Srgb8(evidence) => { let _ = evidence.profile_id(); let _ = evidence.criterion(); let _ = evidence.foreground_luminance(); @@ -115,17 +108,15 @@ fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, ' } } let binding = assessment.binding(); - let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(physical) = - binding.physical(); + let PhysicalPointV1::EncodedSrgb8SourceOver(physical) = binding.physical(); let _ = physical.subject_paint().value(); let _ = physical.backdrop_surface().value(); let _: Srgb8 = physical.subject(); let _ = physical.opacity(); let _: Srgb8 = physical.backdrop(); let _: Srgb8 = physical.visible(); - let PackageProgramModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1( - modeled, - ) = binding.modeled(); + let ModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(modeled) = + binding.modeled(); let _: [f64; 3] = modeled.xyz(); let context = modeled.appearance_context(); let _ = context.adapting_luminance_cd_m2(); @@ -134,7 +125,7 @@ fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, ' }}; } match certificate { - PackageProgramCertificateV1::Verified(verified) => { + CertificateV1::Verified(verified) => { let _ = verified.selected_state_index(); for cell in exact_size(verified.cells()) { inspect_cell!(cell); @@ -146,7 +137,7 @@ fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, ' let _ = output.opacity(); } } - PackageProgramCertificateV1::Conflict(conflict) => { + CertificateV1::Conflict(conflict) => { let _ = conflict.considered_state_count(); for cell in exact_size(conflict.cells()) { let _ = cell.state_index(); @@ -157,17 +148,17 @@ fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, ' } for operation in exact_size(projection.operations()) { match operation { - PackageProgramOperationV1::Set(set) => { - let _: PackageProgramOutputSlotIdV1 = set.output_slot(); + OperationV1::Set(set) => { + let _: OutputSlotIdV1 = set.output_slot(); let _: Srgb8 = set.source(); assert!(set.opacity().is_finite() && (0.0..=1.0).contains(&set.opacity())); let _ = set.certificate().content_identity(); } - PackageProgramOperationV1::Remove(remove) => { - let _: PackageProgramOutputSlotIdV1 = remove.output_slot(); + OperationV1::Remove(remove) => { + let _: OutputSlotIdV1 = remove.output_slot(); } - PackageProgramOperationV1::Hold(hold) => { - let _: PackageProgramOutputSlotIdV1 = hold.output_slot(); + OperationV1::Hold(hold) => { + let _: OutputSlotIdV1 = hold.output_slot(); let _ = hold.certificate().content_identity(); } } @@ -177,10 +168,10 @@ fn assert_projection_is_owner_bound(projection: PackageProgramProjectionV1<'_, ' #[allow(dead_code)] fn unknown_is_revision_bound_without_a_stream_or_generation_field( - owner: &PackageProgramOwnerV1, - session: &mut PackageProgramSessionV1, + owner: &OwnerV1, + session: &mut SessionV1, ) { - let update = PackageProgramUpdateV1::Unknown { + let update = UpdateV1::Unknown { revision: 2, reason_id: 7, }; @@ -188,10 +179,8 @@ fn unknown_is_revision_bound_without_a_stream_or_generation_field( } #[allow(dead_code)] -fn owner_mismatch_is_a_closed_package_error( - error: labcolors_core::package_bridge::PackageProgramUpdateErrorV1, -) { - assert_eq!(error.kind(), PackageProgramUpdateErrorKindV1::OwnerMismatch); +fn owner_mismatch_is_a_closed_public_error(error: UpdateErrorV1) { + assert_eq!(error.kind(), UpdateErrorKindV1::OwnerMismatch); } #[test] @@ -203,27 +192,25 @@ fn external_boundary_uses_only_closed_concrete_types() { fn fixed_nested_draft( opacity_value: f64, - target_source: PackageProgramSourceIdV1, - declared_input: PackageProgramSurfaceInputPortIdV1, - used_input: PackageProgramSurfaceInputPortIdV1, -) -> PackageProgramDraftV1 { - let source = PackageProgramSourceIdV1::new(1); - let target = PackageProgramTargetIdV1::new(2); - let opacity = PackageProgramOpacityInputIdV1::new(3); - let solid = PackageProgramPaintIdV1::new(4); - let translucent = PackageProgramPaintIdV1::new(5); - let input_surface = PackageProgramSurfaceIdV1::new(6); - let nested_surface = PackageProgramSurfaceIdV1::new(7); - let first_occurrence = PackageProgramOccurrenceIdV1::new(8); - let second_occurrence = PackageProgramOccurrenceIdV1::new(9); - let exact = PackageProgramConstraintIdV1::new(10); - let wcag = PackageProgramConstraintIdV1::new(11); - let output = PackageProgramOutputSlotIdV1::new(12); - let context = - PackageProgramAppearanceContextV1::try_new(64.0, 0.2, PackageProgramSurroundV1::Dim) - .unwrap(); - - let mut draft = PackageProgramDraftV1::new(); + target_source: SourceIdV1, + declared_input: SurfaceInputPortIdV1, + used_input: SurfaceInputPortIdV1, +) -> DraftV1 { + 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 exact = ConstraintIdV1::new(10); + let wcag = ConstraintIdV1::new(11); + let output = OutputSlotIdV1::new(12); + let context = AppearanceContextV1::try_new(64.0, 0.2, SurroundV1::Dim).unwrap(); + + let mut draft = DraftV1::new(); draft.push_source(source, Srgb8::new([0; 3])); draft.push_fixed_target(target, target_source); draft.push_surface_input_port(declared_input); @@ -240,58 +227,49 @@ fn fixed_nested_draft( draft } -fn attach_target_assessment(draft: &mut PackageProgramDraftV1, target: PackageProgramTargetIdV1) { - let paint = PackageProgramPaintIdV1::new(770); - let occurrence = PackageProgramOccurrenceIdV1::new(771); - let constraint = PackageProgramConstraintIdV1::new(772); - let context = - PackageProgramAppearanceContextV1::try_new(64.0, 0.2, PackageProgramSurroundV1::Average) - .unwrap(); +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, - PackageProgramSurfaceIdV1::new(6), - context, - ); + draft.push_source_over_occurrence(occurrence, paint, SurfaceIdV1::new(6), context); draft.push_exact_report_only(constraint, occurrence, Srgb8::new([0; 3])); } #[test] fn external_authoring_lowers_the_actual_closed_program_and_returns_canonical_input_ports() { - let source = PackageProgramSourceIdV1::new(91); - let target = PackageProgramTargetIdV1::new(72); - let gray = PackageProgramTargetCandidateIdV1::new(8); - let black = PackageProgramTargetCandidateIdV1::new(3); - let paint = PackageProgramPaintIdV1::new(54); - let high_input = PackageProgramSurfaceInputPortIdV1::new(900); - let low_input = PackageProgramSurfaceInputPortIdV1::new(2); - let high_surface = PackageProgramSurfaceIdV1::new(401); - let low_surface = PackageProgramSurfaceIdV1::new(400); - let high_occurrence = PackageProgramOccurrenceIdV1::new(301); - let low_occurrence = PackageProgramOccurrenceIdV1::new(300); - let exact = PackageProgramConstraintIdV1::new(201); - let high_wcag = PackageProgramConstraintIdV1::new(203); - let low_wcag = PackageProgramConstraintIdV1::new(202); - let output = PackageProgramOutputSlotIdV1::new(101); - let context = - PackageProgramAppearanceContextV1::try_new(64.0, 0.2, PackageProgramSurroundV1::Average) - .unwrap(); - - let mut draft = PackageProgramDraftV1::new(); + let source = SourceIdV1::new(91); + let target = TargetIdV1::new(72); + let gray = TargetCandidateIdV1::new(8); + let black = TargetCandidateIdV1::new(3); + let paint = PaintIdV1::new(54); + let high_input = SurfaceInputPortIdV1::new(900); + let low_input = SurfaceInputPortIdV1::new(2); + let high_surface = SurfaceIdV1::new(401); + let low_surface = SurfaceIdV1::new(400); + let high_occurrence = OccurrenceIdV1::new(301); + let low_occurrence = OccurrenceIdV1::new(300); + let exact = ConstraintIdV1::new(201); + let high_wcag = ConstraintIdV1::new(203); + let low_wcag = ConstraintIdV1::new(202); + let output = OutputSlotIdV1::new(101); + let context = AppearanceContextV1::try_new(64.0, 0.2, SurroundV1::Average).unwrap(); + + let mut draft = DraftV1::new(); draft.push_source(source, Srgb8::new([0x80; 3])); draft.push_finite_target( target, source, vec![ - PackageProgramTargetCandidateV1::new(gray, Srgb8::new([0x80; 3])), - PackageProgramTargetCandidateV1::new(black, Srgb8::new([0; 3])), + TargetCandidateV1::new(gray, Srgb8::new([0x80; 3])), + TargetCandidateV1::new(black, Srgb8::new([0; 3])), ], ); draft .set_joint_selection(vec![ - PackageProgramJointStateV1::new(vec![PackageProgramJointChoiceV1::new(target, gray)]), - PackageProgramJointStateV1::new(vec![PackageProgramJointChoiceV1::new(target, black)]), + JointStateV1::new(vec![JointChoiceV1::new(target, gray)]), + JointStateV1::new(vec![JointChoiceV1::new(target, black)]), ]) .unwrap(); // Deliberately reverse numeric order. Core, not the caller, owns the hot @@ -323,19 +301,19 @@ fn external_authoring_lowers_the_actual_closed_program_and_returns_canonical_inp ); let mut session = owner.instantiate(44).unwrap(); let white = [Srgb8::new([0xFF; 3]), Srgb8::new([0xFF; 3])]; - let scenarios = [PackageProgramScenarioV1::new(7, &white)]; + let scenarios = [ScenarioV1::new(7, &white)]; let ready = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &scenarios, }, ) .unwrap(); - assert_eq!(ready.evidence().kind(), PackageProgramStateKindV1::Ready); + assert_eq!(ready.evidence().kind(), StateKindV1::Ready); let mut operations = ready.operations(); - let Some(PackageProgramOperationV1::Set(set)) = operations.next() else { + let Some(OperationV1::Set(set)) = operations.next() else { panic!("Ready must emit one Set operation"); }; assert_eq!(set.output_slot(), output); @@ -347,8 +325,8 @@ fn external_authoring_lowers_the_actual_closed_program_and_returns_canonical_inp #[test] fn authored_compile_is_atomic_and_projects_a_closed_error() { - let source = PackageProgramSourceIdV1::new(1); - let input = PackageProgramSurfaceInputPortIdV1::new(50); + let source = SourceIdV1::new(1); + let input = SurfaceInputPortIdV1::new(50); let mut draft = fixed_nested_draft(1.0, source, input, input); draft.push_source(source, Srgb8::new([0xFF; 3])); @@ -356,70 +334,204 @@ fn authored_compile_is_atomic_and_projects_a_closed_error() { Ok(_) => panic!("duplicate declaration must not compile"), Err(error) => error, }; - assert_eq!( - error.kind(), - PackageProgramCompileErrorKindV1::DuplicateSource - ); + assert_eq!(error.kind(), CompileErrorKindV1::DuplicateSource); assert_eq!( error.primary_handle(), - Some(PackageProgramCompileErrorHandleV1::Source(source)) + Some(CompileErrorHandleV1::Source(source)) ); assert_eq!(error.related_handle(), None); } #[test] fn every_physical_constructor_and_both_remaining_constraint_modes_execute() { - let input = PackageProgramSurfaceInputPortIdV1::new(50); - let draft = fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), input, input); + let input = SurfaceInputPortIdV1::new(50); + let draft = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input); let owner = draft.compile().unwrap(); assert_eq!(owner.surface_input_ports().collect::>(), [input]); let mut session = owner.instantiate(13).unwrap(); let white = [Srgb8::new([0xFF; 3])]; - let scenarios = [PackageProgramScenarioV1::new(1, &white)]; + let scenarios = [ScenarioV1::new(1, &white)]; let state = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &scenarios, }, ) .unwrap(); - assert_eq!(state.evidence().kind(), PackageProgramStateKindV1::Ready); - let Some(PackageProgramCertificateV1::Verified(certificate)) = - state.evidence().certificates().next() - else { + assert_eq!(state.evidence().kind(), StateKindV1::Ready); + let Some(CertificateV1::Verified(certificate)) = state.evidence().certificates().next() else { panic!("a fixed target must produce one Verified certificate"); }; assert_eq!(certificate.selected_state_index(), None); let mut operations = state.operations(); - let Some(PackageProgramOperationV1::Set(set)) = operations.next() else { + let Some(OperationV1::Set(set)) = operations.next() else { panic!("Ready must emit one Set operation"); }; - assert_eq!(set.output_slot(), PackageProgramOutputSlotIdV1::new(12)); + assert_eq!(set.output_slot(), OutputSlotIdV1::new(12)); assert_eq!(set.source(), Srgb8::new([0; 3])); assert_eq!(set.opacity(), 1.0); assert_eq!(set.certificate().observation().revision(), 1); assert!(operations.next().is_none()); } +#[test] +fn owner_and_update_errors_preserve_content_and_input_identity() { + let input = SurfaceInputPortIdV1::new(50); + let owner = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input) + .compile() + .unwrap(); + let owner_identity = owner.content_identity(); + let mut session = owner.instantiate(13).unwrap(); + + let no_scenarios = []; + let error = match owner.update( + &mut session, + UpdateV1::Observed { + revision: 1, + scenarios: &no_scenarios, + }, + ) { + Ok(_) => panic!("an empty physical domain must fail"), + Err(error) => error, + }; + assert_eq!(error.kind(), UpdateErrorKindV1::InvalidObservation); + assert_eq!(error, UpdateErrorV1::EmptyScenarioSet); + + let white = [Srgb8::new([0xFF; 3])]; + let duplicate = [ScenarioV1::new(70, &white), ScenarioV1::new(70, &white)]; + let error = match owner.update( + &mut session, + UpdateV1::Observed { + revision: 1, + scenarios: &duplicate, + }, + ) { + Ok(_) => panic!("duplicate scenario provenance must fail"), + Err(error) => error, + }; + assert_eq!(error.kind(), UpdateErrorKindV1::InvalidObservation); + let UpdateErrorV1::DuplicateScenarioId { scenario } = error else { + panic!("duplicate provenance must retain its scenario ID"); + }; + assert_eq!(scenario.value(), 70); + + let no_values = []; + let malformed = [ScenarioV1::new(71, &no_values)]; + let error = match owner.update( + &mut session, + UpdateV1::Observed { + revision: 1, + scenarios: &malformed, + }, + ) { + Ok(_) => panic!("schema-short public input must fail"), + Err(error) => error, + }; + assert_eq!(error.kind(), UpdateErrorKindV1::InvalidObservation); + let UpdateErrorV1::ScenarioValueCountMismatch { + scenario, + expected, + actual, + } = error + else { + panic!("schema mismatch must retain its exact scenario and arity"); + }; + assert_eq!(scenario.value(), 71); + assert_eq!(expected, 1); + assert_eq!(actual, 0); + assert_eq!(session.evidence().kind(), StateKindV1::Waiting); + assert_eq!( + session.evidence().observation_head(), + ObservationHeadV1::Empty + ); + + let valid = [ScenarioV1::new(72, &white)]; + let projection = owner + .update( + &mut session, + UpdateV1::Observed { + revision: 2, + scenarios: &valid, + }, + ) + .unwrap(); + let certificate = projection.evidence().certificates().next().unwrap(); + assert_eq!(owner_identity, certificate.content_identity()); + + let error = match owner.update( + &mut session, + UpdateV1::Unknown { + revision: 1, + reason_id: 9, + }, + ) { + Ok(_) => panic!("older revision must fail"), + Err(error) => error, + }; + assert_eq!(error.kind(), UpdateErrorKindV1::RevisionOutOfOrder); + assert_eq!( + error, + UpdateErrorV1::RevisionOutOfOrder { + current: 2, + incoming: 1, + } + ); + + let error = match owner.update( + &mut session, + UpdateV1::Unknown { + revision: 2, + reason_id: 9, + }, + ) { + Ok(_) => panic!("changed payload at the same revision must fail"), + Err(error) => error, + }; + assert_eq!(error.kind(), UpdateErrorKindV1::RevisionConflict); + assert_eq!(error, UpdateErrorV1::RevisionConflict { revision: 2 }); + assert_eq!(session.evidence().kind(), StateKindV1::Ready); + let ObservationHeadV1::Observed { stream, revision } = session.evidence().observation_head() + else { + panic!("failed update must keep the last admitted observation"); + }; + assert_eq!(stream.value(), 13); + assert_eq!(revision, 2); + + let foreign = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input) + .compile() + .unwrap(); + assert_eq!(foreign.content_identity(), owner.content_identity()); + let error = match foreign.update( + &mut session, + UpdateV1::Unknown { + revision: 3, + reason_id: 9, + }, + ) { + Ok(_) => panic!("equal content must not confer owner authority"), + Err(error) => error, + }; + assert_eq!(error.kind(), UpdateErrorKindV1::OwnerMismatch); + assert_eq!(error, UpdateErrorV1::OwnerMismatch); +} + #[test] fn certificate_and_set_retain_the_same_nonunit_opacity() { - let source = PackageProgramSourceIdV1::new(1); - let target = PackageProgramTargetIdV1::new(2); - let opacity = PackageProgramOpacityInputIdV1::new(3); - let solid = PackageProgramPaintIdV1::new(4); - let translucent = PackageProgramPaintIdV1::new(5); - let input = PackageProgramSurfaceInputPortIdV1::new(6); - let surface = PackageProgramSurfaceIdV1::new(7); - let occurrence = PackageProgramOccurrenceIdV1::new(8); - let constraint = PackageProgramConstraintIdV1::new(9); - let output = PackageProgramOutputSlotIdV1::new(10); - let context = - PackageProgramAppearanceContextV1::try_new(64.0, 0.2, PackageProgramSurroundV1::Average) - .unwrap(); - let mut draft = PackageProgramDraftV1::new(); + 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 = SurfaceInputPortIdV1::new(6); + let surface = SurfaceIdV1::new(7); + let occurrence = OccurrenceIdV1::new(8); + let constraint = ConstraintIdV1::new(9); + let output = OutputSlotIdV1::new(10); + let context = AppearanceContextV1::try_new(64.0, 0.2, SurroundV1::Average).unwrap(); + let mut draft = DraftV1::new(); draft.push_source(source, Srgb8::new([0; 3])); draft.push_fixed_target(target, source); draft.push_surface_input_port(input); @@ -434,28 +546,24 @@ fn certificate_and_set_retain_the_same_nonunit_opacity() { let owner = draft.compile().unwrap(); let mut session = owner.instantiate(17).unwrap(); let white = [Srgb8::new([0xFF; 3])]; - let scenarios = [PackageProgramScenarioV1::new(1, &white)]; + let scenarios = [ScenarioV1::new(1, &white)]; let state = owner .update( &mut session, - PackageProgramUpdateV1::Observed { + UpdateV1::Observed { revision: 1, scenarios: &scenarios, }, ) .unwrap(); - let Some(PackageProgramCertificateV1::Verified(certificate)) = - state.evidence().certificates().next() - else { + let Some(CertificateV1::Verified(certificate)) = state.evidence().certificates().next() else { panic!("the exact emitted midpoint must be verified"); }; - let PackageProgramAssessmentV1::ExactSrgb8(assessment) = - certificate.cells().next().unwrap().assessment() + let AssessmentV1::ExactSrgb8(assessment) = certificate.cells().next().unwrap().assessment() else { panic!("the authored exact constraint must retain Exact evidence"); }; - let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(physical) = - assessment.binding().physical(); + let PhysicalPointV1::EncodedSrgb8SourceOver(physical) = assessment.binding().physical(); assert_eq!(physical.opacity().to_bits(), 0.5_f64.to_bits()); assert_eq!(physical.visible(), Srgb8::new([0x80; 3])); assert_eq!( @@ -463,7 +571,7 @@ fn certificate_and_set_retain_the_same_nonunit_opacity() { physical.opacity().to_bits() ); - let Some(PackageProgramOperationV1::Set(set)) = state.operations().next() else { + let Some(OperationV1::Set(set)) = state.operations().next() else { panic!("the verified output must emit one Set"); }; assert_eq!(set.opacity().to_bits(), physical.opacity().to_bits()); @@ -471,115 +579,83 @@ fn certificate_and_set_retain_the_same_nonunit_opacity() { #[test] fn invalid_context_and_opacity_are_typed_and_fail_closed() { - let context_error = - PackageProgramAppearanceContextV1::try_new(64.0, 1.01, PackageProgramSurroundV1::Dark) - .unwrap_err(); - assert_eq!( - context_error.kind(), - PackageProgramAppearanceContextErrorKindV1::Domain - ); + let context_error = AppearanceContextV1::try_new(64.0, 1.01, SurroundV1::Dark).unwrap_err(); + assert_eq!(context_error.kind(), AppearanceContextErrorKindV1::Domain); assert_eq!( context_error.field(), - Some(PackageProgramAppearanceContextFieldV1::BackgroundLuminanceRatioYbYw) - ); - assert_eq!( - context_error.reason(), - Some(PackageProgramNumericDomainErrorV1::AboveOne) + Some(AppearanceContextFieldV1::BackgroundLuminanceRatioYbYw) ); + assert_eq!(context_error.reason(), Some(NumericDomainErrorV1::AboveOne)); - let input = PackageProgramSurfaceInputPortIdV1::new(50); - let error = match fixed_nested_draft(f64::NAN, PackageProgramSourceIdV1::new(1), input, input) - .compile() - { + let input = SurfaceInputPortIdV1::new(50); + let error = match fixed_nested_draft(f64::NAN, SourceIdV1::new(1), input, input).compile() { Ok(_) => panic!("non-finite opacity must not compile"), Err(error) => error, }; - assert_eq!( - error.kind(), - PackageProgramCompileErrorKindV1::OpacityOutOfDomain - ); + assert_eq!(error.kind(), CompileErrorKindV1::OpacityOutOfDomain); assert_eq!( error.primary_handle(), - Some(PackageProgramCompileErrorHandleV1::OpacityInput( - PackageProgramOpacityInputIdV1::new(3) - )) + Some(CompileErrorHandleV1::OpacityInput(OpacityInputIdV1::new(3))) ); assert_eq!(error.related_handle(), None); } #[test] fn relational_compile_errors_keep_both_typed_handles() { - let input = PackageProgramSurfaceInputPortIdV1::new(50); - let missing_source = PackageProgramSourceIdV1::new(99); + let input = SurfaceInputPortIdV1::new(50); + let missing_source = SourceIdV1::new(99); let error = match fixed_nested_draft(1.0, missing_source, input, input).compile() { Ok(_) => panic!("a target cannot reference an undeclared source"), Err(error) => error, }; - assert_eq!( - error.kind(), - PackageProgramCompileErrorKindV1::MissingTargetSource - ); + assert_eq!(error.kind(), CompileErrorKindV1::MissingTargetSource); assert_eq!( error.primary_handle(), - Some(PackageProgramCompileErrorHandleV1::Target( - PackageProgramTargetIdV1::new(2) - )) + Some(CompileErrorHandleV1::Target(TargetIdV1::new(2))) ); assert_eq!( error.related_handle(), - Some(PackageProgramCompileErrorHandleV1::Source(missing_source)) + Some(CompileErrorHandleV1::Source(missing_source)) ); } #[test] fn declared_and_referenced_surface_inputs_cannot_drift() { - let declared = PackageProgramSurfaceInputPortIdV1::new(50); - let missing = PackageProgramSurfaceInputPortIdV1::new(51); - let error = match fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), declared, missing) - .compile() - { + let declared = SurfaceInputPortIdV1::new(50); + let missing = SurfaceInputPortIdV1::new(51); + let error = match fixed_nested_draft(1.0, SourceIdV1::new(1), declared, missing).compile() { Ok(_) => panic!("an undeclared physical input must not compile"), Err(error) => error, }; - assert_eq!( - error.kind(), - PackageProgramCompileErrorKindV1::MissingSurfaceInputPort - ); + assert_eq!(error.kind(), CompileErrorKindV1::MissingSurfaceInputPort); assert_eq!( error.primary_handle(), - Some(PackageProgramCompileErrorHandleV1::Surface( - PackageProgramSurfaceIdV1::new(6) - )) + Some(CompileErrorHandleV1::Surface(SurfaceIdV1::new(6))) ); assert_eq!( error.related_handle(), - Some(PackageProgramCompileErrorHandleV1::SurfaceInputPort( - missing - )) + Some(CompileErrorHandleV1::SurfaceInputPort(missing)) ); } #[test] fn declared_input_ports_form_an_exact_bijection_with_input_surfaces() { - let input = PackageProgramSurfaceInputPortIdV1::new(50); - let extra = PackageProgramSurfaceInputPortIdV1::new(51); - let mut unused = fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), input, input); + let input = SurfaceInputPortIdV1::new(50); + let extra = SurfaceInputPortIdV1::new(51); + let mut unused = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input); unused.push_surface_input_port(extra); let error = match unused.compile() { Ok(_) => panic!("an unused declared input must not compile"), Err(error) => error, }; - assert_eq!( - error.kind(), - PackageProgramCompileErrorKindV1::UnusedSurfaceInputPort - ); + assert_eq!(error.kind(), CompileErrorKindV1::UnusedSurfaceInputPort); assert_eq!( error.primary_handle(), - Some(PackageProgramCompileErrorHandleV1::SurfaceInputPort(extra)) + Some(CompileErrorHandleV1::SurfaceInputPort(extra)) ); - let duplicate_surface = PackageProgramSurfaceIdV1::new(60); - let mut duplicate = fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), input, input); + let duplicate_surface = SurfaceIdV1::new(60); + let mut duplicate = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input); duplicate.push_input_surface(duplicate_surface, input); let error = match duplicate.compile() { Ok(_) => panic!("two input surfaces must not bind one declared port"), @@ -587,23 +663,21 @@ fn declared_input_ports_form_an_exact_bijection_with_input_surfaces() { }; assert_eq!( error.kind(), - PackageProgramCompileErrorKindV1::DuplicateSurfaceInputBinding + CompileErrorKindV1::DuplicateSurfaceInputBinding ); assert_eq!( error.primary_handle(), - Some(PackageProgramCompileErrorHandleV1::SurfaceInputPort(input)) + Some(CompileErrorHandleV1::SurfaceInputPort(input)) ); assert_eq!( error.related_handle(), - Some(PackageProgramCompileErrorHandleV1::Surface( - duplicate_surface - )) + Some(CompileErrorHandleV1::Surface(duplicate_surface)) ); assert_eq!( error, - PackageProgramCompileErrorV1::DuplicateSurfaceInputBinding { + CompileErrorV1::DuplicateSurfaceInputBinding { input, - first: PackageProgramSurfaceIdV1::new(6), + first: SurfaceIdV1::new(6), duplicate: duplicate_surface, } ); @@ -611,25 +685,25 @@ fn declared_input_ports_form_an_exact_bijection_with_input_surfaces() { #[test] fn duplicate_candidate_signal_preserves_both_candidates_and_exact_stimulus() { - let input = PackageProgramSurfaceInputPortIdV1::new(50); - let target = PackageProgramTargetIdV1::new(70); - let first = PackageProgramTargetCandidateIdV1::new(701); - let duplicate = PackageProgramTargetCandidateIdV1::new(702); + let input = SurfaceInputPortIdV1::new(50); + let target = TargetIdV1::new(70); + let first = TargetCandidateIdV1::new(701); + let duplicate = TargetCandidateIdV1::new(702); let encoded_srgb8 = Srgb8::new([17, 33, 65]); - let mut draft = fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), input, input); + let mut draft = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input); draft.push_finite_target( target, - PackageProgramSourceIdV1::new(1), + SourceIdV1::new(1), vec![ - PackageProgramTargetCandidateV1::new(first, encoded_srgb8), - PackageProgramTargetCandidateV1::new(duplicate, encoded_srgb8), + TargetCandidateV1::new(first, encoded_srgb8), + TargetCandidateV1::new(duplicate, encoded_srgb8), ], ); attach_target_assessment(&mut draft, target); assert_eq!( compile_error(draft), - PackageProgramCompileErrorV1::DuplicateTargetCandidateSignal { + CompileErrorV1::DuplicateTargetCandidateSignal { target, first, duplicate, @@ -640,71 +714,56 @@ fn duplicate_candidate_signal_preserves_both_candidates_and_exact_stimulus() { #[test] fn joint_diagnostics_preserve_state_and_total_order_details() { - let input = PackageProgramSurfaceInputPortIdV1::new(50); - let target = PackageProgramTargetIdV1::new(70); - let first = PackageProgramTargetCandidateIdV1::new(701); - let second = PackageProgramTargetCandidateIdV1::new(702); + let input = SurfaceInputPortIdV1::new(50); + let target = TargetIdV1::new(70); + let first = TargetCandidateIdV1::new(701); + let second = TargetCandidateIdV1::new(702); let candidates = vec![ - PackageProgramTargetCandidateV1::new(first, Srgb8::new([0; 3])), - PackageProgramTargetCandidateV1::new(second, Srgb8::new([255; 3])), + TargetCandidateV1::new(first, Srgb8::new([0; 3])), + TargetCandidateV1::new(second, Srgb8::new([255; 3])), ]; - let mut duplicate_target = - fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), input, input); - duplicate_target.push_finite_target( - target, - PackageProgramSourceIdV1::new(1), - candidates.clone(), - ); + let mut duplicate_target = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input); + duplicate_target.push_finite_target(target, SourceIdV1::new(1), candidates.clone()); attach_target_assessment(&mut duplicate_target, target); duplicate_target - .set_joint_selection(vec![PackageProgramJointStateV1::new(vec![ - PackageProgramJointChoiceV1::new(target, first), - PackageProgramJointChoiceV1::new(target, second), + .set_joint_selection(vec![JointStateV1::new(vec![ + JointChoiceV1::new(target, first), + JointChoiceV1::new(target, second), ])]) .unwrap(); assert_eq!( compile_error(duplicate_target), - PackageProgramCompileErrorV1::JointStateDuplicateTarget { state: 0, target } + CompileErrorV1::JointStateDuplicateTarget { state: 0, target } ); - let mut incomplete = fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), input, input); - incomplete.push_finite_target(target, PackageProgramSourceIdV1::new(1), candidates); + let mut incomplete = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input); + incomplete.push_finite_target(target, SourceIdV1::new(1), candidates); attach_target_assessment(&mut incomplete, target); incomplete - .set_joint_selection(vec![PackageProgramJointStateV1::new(vec![ - PackageProgramJointChoiceV1::new(target, first), - ])]) + .set_joint_selection(vec![JointStateV1::new(vec![JointChoiceV1::new( + target, first, + )])]) .unwrap(); assert_eq!( compile_error(incomplete), - PackageProgramCompileErrorV1::InvalidJointOrder( - PackageProgramJointOrderErrorV1::IncompleteOrder { - expected: 2, - actual: 1, - } - ) + CompileErrorV1::InvalidJointOrder(JointOrderErrorV1::IncompleteOrder { + expected: 2, + actual: 1, + }) ); } #[test] fn dependency_cycles_retain_all_typed_core_members_without_reallocation() { - let input = PackageProgramSurfaceInputPortIdV1::new(50); - let first_paint = PackageProgramPaintIdV1::new(70); - let second_paint = PackageProgramPaintIdV1::new(71); - let mut paint_cycle = fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), input, input); - paint_cycle.push_opacity_paint( - first_paint, - second_paint, - PackageProgramOpacityInputIdV1::new(3), - ); - paint_cycle.push_opacity_paint( - second_paint, - first_paint, - PackageProgramOpacityInputIdV1::new(3), - ); + let input = SurfaceInputPortIdV1::new(50); + let first_paint = PaintIdV1::new(70); + let second_paint = PaintIdV1::new(71); + let mut paint_cycle = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input); + paint_cycle.push_opacity_paint(first_paint, second_paint, OpacityInputIdV1::new(3)); + paint_cycle.push_opacity_paint(second_paint, first_paint, OpacityInputIdV1::new(3)); let error = compile_error(paint_cycle); - let PackageProgramCompileErrorV1::PaintCycle(cycle) = error else { + let CompileErrorV1::PaintCycle(cycle) = error else { panic!("expected an exact paint cycle") }; assert_eq!( @@ -712,21 +771,19 @@ fn dependency_cycles_retain_all_typed_core_members_without_reallocation() { [first_paint, second_paint] ); - let cyclic_surface = PackageProgramSurfaceIdV1::new(80); - let cyclic_occurrence = PackageProgramOccurrenceIdV1::new(81); - let context = - PackageProgramAppearanceContextV1::try_new(64.0, 0.2, PackageProgramSurroundV1::Average) - .unwrap(); - let mut render_cycle = fixed_nested_draft(1.0, PackageProgramSourceIdV1::new(1), input, input); + let cyclic_surface = SurfaceIdV1::new(80); + let cyclic_occurrence = OccurrenceIdV1::new(81); + let context = AppearanceContextV1::try_new(64.0, 0.2, SurroundV1::Average).unwrap(); + let mut render_cycle = fixed_nested_draft(1.0, SourceIdV1::new(1), input, input); render_cycle.push_occurrence_surface(cyclic_surface, cyclic_occurrence); render_cycle.push_source_over_occurrence( cyclic_occurrence, - PackageProgramPaintIdV1::new(4), + PaintIdV1::new(4), cyclic_surface, context, ); let error = compile_error(render_cycle); - let PackageProgramCompileErrorV1::RenderCycle(cycle) = error else { + let CompileErrorV1::RenderCycle(cycle) = error else { panic!("expected an exact render cycle") }; assert_eq!(cycle.surfaces().collect::>(), [cyclic_surface]); @@ -734,23 +791,20 @@ fn dependency_cycles_retain_all_typed_core_members_without_reallocation() { } #[test] -fn the_code_owned_observation_group_reports_package_authored_port_semantics() { +fn the_code_owned_observation_group_reports_public_authored_port_semantics() { assert_eq!( - compile_error(PackageProgramDraftV1::new()), - PackageProgramCompileErrorV1::EmptySurfaceInputPortSet + compile_error(DraftV1::new()), + CompileErrorV1::EmptySurfaceInputPortSet ); } #[test] fn singleton_joint_order_cannot_be_silently_replaced() { - let mut draft = PackageProgramDraftV1::new(); + let mut draft = DraftV1::new(); draft.set_joint_selection(vec![]).unwrap(); let error = match draft.set_joint_selection(vec![]) { Ok(_) => panic!("a singleton declaration must not be replaced"), Err(error) => error, }; - assert_eq!( - error, - PackageProgramDraftErrorV1::JointSelectionAlreadyDeclared - ); + assert_eq!(error, DraftErrorV1::JointSelectionAlreadyDeclared); } diff --git a/crates/labcolors-core/tests/program_public_api.rs b/crates/labcolors-core/tests/program_public_api.rs new file mode 100644 index 00000000..be7fbb76 --- /dev/null +++ b/crates/labcolors-core/tests/program_public_api.rs @@ -0,0 +1,58 @@ +//! Минимальный внешний контракт лаконичной публичной поверхности Program. + +use labcolors_core::{Srgb8, program}; + +#[test] +fn public_program_api_is_module_qualified_without_transport_prefixes() { + let source = program::SourceIdV1::new(1); + let target = program::TargetIdV1::new(2); + let input = program::SurfaceInputPortIdV1::new(3); + let paint = program::PaintIdV1::new(4); + let surface = program::SurfaceIdV1::new(5); + let occurrence = program::OccurrenceIdV1::new(6); + let constraint = program::ConstraintIdV1::new(7); + let output = program::OutputSlotIdV1::new(8); + let context = + program::AppearanceContextV1::try_new(64.0, 0.2, program::SurroundV1::Average).unwrap(); + let mut draft = program::DraftV1::new(); + + draft.push_source(source, Srgb8::new([0, 0, 0])); + draft.push_fixed_target(target, source); + draft.push_surface_input_port(input); + draft.push_solid_paint(paint, target); + draft.push_input_surface(surface, input); + draft.push_source_over_occurrence(occurrence, paint, surface, context); + draft.push_exact_hard(constraint, occurrence, Srgb8::new([0, 0, 0])); + draft.push_output(output, paint); + + let owner = draft.compile().unwrap(); + let mut session = owner.instantiate(1).unwrap(); + let white = [Srgb8::new([255, 255, 255])]; + let scenarios = [program::ScenarioV1::new(1, &white)]; + let projection = owner + .update( + &mut session, + program::UpdateV1::Observed { + revision: 1, + scenarios: &scenarios, + }, + ) + .unwrap(); + let Some(program::OperationV1::Set(set)) = projection.operations().next() else { + panic!("the exact program must emit one certified Set"); + }; + assert_eq!(set.output_slot(), output); + assert_eq!(set.source(), Srgb8::new([0, 0, 0])); +} + +#[test] +fn public_internal_failure_keeps_fact_and_contract_as_one_consistent_value() { + let source = program::UpdateInvariantFailureV1::OwnerAuthority; + assert_eq!( + source.contract(), + program::UpdateInvariantV1::OwnerAuthority + ); + + let error = program::UpdateErrorV1::InternalInvariant { source }; + assert_eq!(error.kind(), program::UpdateErrorKindV1::InternalInvariant); +} diff --git a/scripts/verify_point_support_surplus.py b/scripts/verify_point_support_surplus.py index 858b86ce..37c5fa71 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 = ( - "669326bce56a2901f7fbbd8b4c23f26f8b33daceb1471b81c98763940b41d3e4" + "371a19f9da8337a13fb9a474c0f0395b35117822fd2d9293c6228b1f01e97ca4" ) EXPECTED_Q55_PROOF_SHA256 = ( "ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"