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 1e529242..5c918468 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":"fd8cc51bc850523f600cc850b10af56afb450dccaeb928c795d2c45c421df8e4","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":"2e63424882231ec2f00ae79911bf74cfb935bb96f11368bce53240468f7f2dd4","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":"e361cf144e0630a4f2ff52261e6515c9394188fc1351c97b98ea9c0ec87ec39d"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"195a67327a3bd86d7816b634481389930bf68577bb1202fad14c2ea152df8625"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/exact.rs","sha256":"33b959f11366415143b5b03fcfe370d1fb7e61e46ed05349ed17560d10663ff7"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/mod.rs","sha256":"37cff33755c5a700853ccbc08bd539d2235325b9eb208be696444799d4cf819e"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/wcag22.rs","sha256":"7e10638e8da68dc1279f078e0a0daa5caf10af403eb00c1cbbd5506190e74d9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/hash.rs","sha256":"f97a0fd7d6ad3162f0f1dfb326fccfb7ed40da9a8fa67a5b8a239a1ae2ae49c3"},{"kind":"rust-source","path":"crates/labcolors-core/src/lcs_occurrence.rs","sha256":"a58acbae5b06a8cd9a45adf93ddfbdfb569c5916b60140bd4046182aab2a9518"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"0f0e0ed726aba4defc750b793a76435a3f3825e91002c2cd90d982be7260f180"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"abed15a542b5e8031f0ebfe36138232920c7898c9eae1a2037c6455033ecd91e"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"945755f2eb91eb21aeee9b7a7b0e67b093d7e766af35a121480003669d88a053"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"0a1fdf10c9ecae497cfcd447a6af937f2b07d43b81b2cd28a50d998e11197fe9"},{"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":"a8f1b4f65a45bf18ad8c2ae21cc88e5cd270a5ee438d625153b66d26660a7e90"} +{"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":"d731e6b3d068906ee02ebd90611b0d101dda6db4b0c98ff9dc4bdc79058618a8","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":"f5e813b21df3bad8c93fa3525190715110b4965792f5b7a2a0bfde7a3b4297d3","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":"e361cf144e0630a4f2ff52261e6515c9394188fc1351c97b98ea9c0ec87ec39d"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"195a67327a3bd86d7816b634481389930bf68577bb1202fad14c2ea152df8625"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/exact.rs","sha256":"33b959f11366415143b5b03fcfe370d1fb7e61e46ed05349ed17560d10663ff7"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/mod.rs","sha256":"37cff33755c5a700853ccbc08bd539d2235325b9eb208be696444799d4cf819e"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/wcag22.rs","sha256":"7e10638e8da68dc1279f078e0a0daa5caf10af403eb00c1cbbd5506190e74d9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/hash.rs","sha256":"f97a0fd7d6ad3162f0f1dfb326fccfb7ed40da9a8fa67a5b8a239a1ae2ae49c3"},{"kind":"rust-source","path":"crates/labcolors-core/src/lcs_occurrence.rs","sha256":"a58acbae5b06a8cd9a45adf93ddfbdfb569c5916b60140bd4046182aab2a9518"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"0f0e0ed726aba4defc750b793a76435a3f3825e91002c2cd90d982be7260f180"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"abed15a542b5e8031f0ebfe36138232920c7898c9eae1a2037c6455033ecd91e"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"0755210e3e591d7049f293a0f0b7647681631f32feee5ad7d3b3309cffca8f9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"ab36d9e9339cf1b1030963d78eba42e7f898b4d3757712c7908e1d696bc620c9"},{"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":"a201ca9d971d9b7b9928ccef498752c837ce17d6feb1b2ee8d2d7187c3cebb5e"} diff --git a/crates/labcolors-core/src/generic_boundary_tests.rs b/crates/labcolors-core/src/generic_boundary_tests.rs index 38e750e4..33956b55 100644 --- a/crates/labcolors-core/src/generic_boundary_tests.rs +++ b/crates/labcolors-core/src/generic_boundary_tests.rs @@ -207,8 +207,11 @@ fn shared_observation_ssot_has_one_backing_without_lifecycle_or_adapter_facades( "production must have exactly one generic revision-bound Session owner", ); for required in [ + "type OwnerLease;", "type Verified: SessionEvidenceV1;", "type Violation: SessionEvidenceV1;", + "fn try_acquire_owner(&self) -> Option;", + "SessionUpdateError::OwnerExpired", ".is_same_binding_as(expected_observation)", "SessionUpdateError::EvidenceBindingInvariant", ] { @@ -239,7 +242,6 @@ fn shared_observation_ssot_has_one_backing_without_lifecycle_or_adapter_facades( "into_session_recheck", "ObservationStreamBinding", "ProgramExpired", - "Weak<", ] { assert!( !source.contains(forbidden), @@ -247,6 +249,31 @@ fn shared_observation_ssot_has_one_backing_without_lifecycle_or_adapter_facades( ); } } + for (path, source) in [ + ("session.rs", SESSION_SOURCE), + ("point_support.rs", POINT_SUPPORT_SOURCE), + ] { + assert!( + !source.contains("Weak<"), + "{path} must not create another weak ownership boundary", + ); + } + + let update = normalized_source_scope( + SESSION_SOURCE, + "pub(crate) fn update(", + "/// Move exactly one retained verified witness", + ); + let owner_preflight = update + .find(".try_acquire_owner()") + .expect("Session update must acquire the exact owner generation"); + let admission = update + .find("prepare_observation(") + .expect("Session update must perform canonical admission"); + assert!( + owner_preflight < admission, + "owner expiry must precede raw admission and physical execution", + ); let consuming_entry = source_scope( POINT_SUPPORT_SOURCE, @@ -419,9 +446,31 @@ fn program_session_owns_context_bound_lcs_evidence_and_one_session_scratch_cache let plan = source_scope( PROGRAM_SESSION_SOURCE, - "pub struct ProgramSessionPlan", + "pub(crate) struct ProgramSessionPlan", "impl session_private::PlanSealed for ProgramSessionPlan", ); + assert_eq!( + plan.matches("owner_generation: Weak>,") + .count(), + 1, + "a Program Session must hold exactly one weak compiled-generation binding", + ); + assert!( + !plan.contains("epoch: Rc>,"), + "a Program Session must not prolong its CompiledProgram owner", + ); + let compiled = source_scope( + PROGRAM_SESSION_SOURCE, + "pub struct CompiledProgram", + "impl CompiledProgram", + ); + assert_eq!( + compiled + .matches("owner_generation: Rc>,") + .count(), + 1, + "CompiledProgram must be the one strong owner of its generation", + ); assert_eq!( plan.matches("modeled_occurrences: Vec>,") .count(), diff --git a/crates/labcolors-core/src/point_support.rs b/crates/labcolors-core/src/point_support.rs index bb997588..94a01b54 100644 --- a/crates/labcolors-core/src/point_support.rs +++ b/crates/labcolors-core/src/point_support.rs @@ -322,16 +322,22 @@ impl CompiledPointSupportRecheckV1 { impl session_private::PlanSealed for CompiledPointSupportRecheckV1 {} impl SessionPlanV1 for CompiledPointSupportRecheckV1 { + type OwnerLease = (); type Verified = VerifiedPointSupportV1; type Violation = PointSupportViolationV1; type Error = PointSupportEvaluationErrorV1; + fn try_acquire_owner(&self) -> Option { + Some(()) + } + fn observation_schema(&self) -> &CanonicalObservationSchemaV1 { &self.surface_schema } fn evaluate( &mut self, + _owner: &Self::OwnerLease, observation: RevisionBoundObservationV1, _permit: SessionObservationBindingPermitV1, ) -> Result, Self::Error> { diff --git a/crates/labcolors-core/src/program_joint_integration_tests.rs b/crates/labcolors-core/src/program_joint_integration_tests.rs index a04d8b3c..db1ce5d2 100644 --- a/crates/labcolors-core/src/program_joint_integration_tests.rs +++ b/crates/labcolors-core/src/program_joint_integration_tests.rs @@ -9,8 +9,9 @@ use crate::lcs_occurrence::{ BackgroundLuminanceRatio, ColorSignal, IEC_SRGB_D65_XYZ_FRAME_V1, SurroundProfileId, }; use crate::observation::{ - ObservationGroupId, ObservationPayloadInput, ObservationStreamId, ObservationUpdateInput, - ObservedScenarioSetInput, Revision, ScenarioId, ScenarioInput, SurfaceInputBinding, + ObservationGroupId, ObservationHeadViewV1, ObservationPayloadInput, ObservationStreamId, + ObservationUpdateInput, ObservedScenarioSetInput, Revision, ScenarioId, ScenarioInput, + SurfaceInputBinding, }; use crate::program_session::{ CompositionProfile, ConstraintId, ConstraintInvocation, ConstraintSet, @@ -1077,6 +1078,109 @@ fn every_fallible_fixed_preflight_reservation_precedes_evaluator_work() { } } +fn counting_fixed_program( + evaluator: CountingProgramWcag22Srgb8V1, +) -> crate::program_session::CompiledProgram { + Program::new( + vec![Source::new(SOURCE, signal(0xFF))], + vec![Target::fixed(TARGET, SOURCE)], + ObservationGroup::new(GROUP, vec![SURFACE_PORT]), + vec![], + vec![Paint::Solid { + id: PAINT, + target: TARGET, + }], + vec![Surface::Input { + id: BACKDROP, + input: SURFACE_PORT, + }], + vec![Occurrence::new( + OCCURRENCE, + PAINT, + BACKDROP, + CompositionProfile::EncodedSrgb8SourceOverV1, + appearance_context(), + )], + ConstraintSet::new( + vec![ConstraintInvocation::hard( + ConstraintId::new(1), + OCCURRENCE, + Wcag22CriterionV1::Sc143TextLargeScale, + )], + vec![], + ), + vec![OutputBinding::new(OUTPUT, PAINT)], + evaluator, + ) + .compile() + .unwrap() +} + +#[test] +fn expired_program_generation_precedes_composition_and_evaluation_without_allocation() { + let evaluator = CountingProgramWcag22Srgb8V1::default(); + let calls = evaluator.clone(); + let compiled = counting_fixed_program(evaluator); + let mut session = compiled.instantiate(STREAM).unwrap(); + + crate::composition::reset_source_over_evaluation_count(); + let SessionState::Ready { current } = session.update(update(1, 0x00)).unwrap() else { + panic!("control generation must certify"); + }; + assert_eq!(current.report().observation().revision(), Revision::new(1)); + assert_eq!(calls.calls().len(), 1); + assert_eq!(crate::composition::source_over_evaluation_count(), 1); + + drop(compiled); + let expired_update = update(2, 0x00); + let (error, allocations) = crate::test_support::measured_allocations(|| { + session.update(expired_update).map(|_| ()).unwrap_err() + }); + assert_eq!(error, SessionUpdateError::OwnerExpired); + assert_eq!(allocations, 0); + assert_eq!(calls.calls().len(), 1); + assert_eq!(crate::composition::source_over_evaluation_count(), 1); + assert_eq!(session.raw_head().revision(), Some(Revision::new(1))); + let SessionState::Ready { current } = session.state() else { + panic!("expiry must retain the previous committed state"); + }; + assert_eq!(current.report().observation().revision(), Revision::new(1)); +} + +#[test] +fn equivalent_recompiled_owner_is_a_new_generation_and_cannot_revive_old_sessions() { + let first_evaluator = CountingProgramWcag22Srgb8V1::default(); + let first_calls = first_evaluator.clone(); + let mut compiled = counting_fixed_program(first_evaluator); + let mut old_session = compiled.instantiate(STREAM).unwrap(); + assert!(matches!( + old_session.update(update(1, 0x00)).unwrap(), + SessionState::Ready { .. } + )); + + let replacement_evaluator = CountingProgramWcag22Srgb8V1::default(); + let replacement_calls = replacement_evaluator.clone(); + compiled = counting_fixed_program(replacement_evaluator); + assert!(matches!( + old_session.update(update(2, 0x00)), + Err(SessionUpdateError::OwnerExpired), + )); + assert_eq!(first_calls.calls().len(), 1); + assert!(replacement_calls.calls().is_empty()); + assert_eq!(old_session.raw_head().revision(), Some(Revision::new(1))); + + let mut replacement_session = compiled.instantiate(STREAM).unwrap(); + assert!(matches!( + replacement_session.update(update(1, 0x00)).unwrap(), + SessionState::Ready { .. } + )); + assert_eq!(replacement_calls.calls().len(), 1); + assert!(matches!( + replacement_session.raw_head(), + ObservationHeadViewV1::Observed(_) + )); +} + #[test] fn final_recheck_violation_is_typed_and_retains_the_previous_certificate() { let evaluator = FinalRecheckMutantProgramEvaluatorV1::default(); diff --git a/crates/labcolors-core/src/program_session.rs b/crates/labcolors-core/src/program_session.rs index 1f713c9b..97e668b4 100644 --- a/crates/labcolors-core/src/program_session.rs +++ b/crates/labcolors-core/src/program_session.rs @@ -11,7 +11,7 @@ //! is renderer observation or human-subject evidence. use std::marker::PhantomData; -use std::rc::Rc; +use std::rc::{Rc, Weak}; use crate::Srgb8; use crate::appearance::{ @@ -555,7 +555,7 @@ where pub fn compile(self) -> Result, ProgramCompileError> { prepare_program(self).map(|epoch| CompiledProgram { - epoch: Rc::new(epoch), + owner_generation: Rc::new(epoch), }) } } @@ -750,13 +750,21 @@ where joint_selection: Option, } +/// Transaction-local strong pin for one exact compiled Program generation. +/// Construction is possible only by upgrading a Session plan's weak binding; +/// the contained epoch never becomes an independently shareable API. +pub(crate) struct ProgramOwnerLeaseV1(Rc>) +where + Evaluation: ProgramPointEvaluatorV1, + ProgramPointInvocation: Copy; + /// Fully validated immutable Program, not yet attached to runtime. pub struct CompiledProgram where Evaluation: ProgramPointEvaluatorV1, ProgramPointInvocation: Copy, { - epoch: Rc>, + owner_generation: Rc>, } impl CompiledProgram @@ -765,53 +773,55 @@ where ProgramPointInvocation: Copy, { pub fn observation_group_id(&self) -> ObservationGroupId { - self.epoch.observation_group.id + self.owner_generation.observation_group.id } pub fn surface_input_ports(&self) -> &[SurfaceInputPortId] { - self.epoch.observation_group.schema.as_slice() + self.owner_generation.observation_group.schema.as_slice() } pub fn constraint_ids(&self) -> impl ExactSizeIterator + '_ { - self.epoch + self.owner_generation .constraints .iter() .map(|constraint| constraint.id) } pub fn outputs(&self) -> impl ExactSizeIterator + '_ { - self.epoch + self.owner_generation .outputs .iter() .map(|output| (output.output, output.paint_id)) } - /// Create one independent stream-affine Session from the immutable - /// compiled epoch. The graph/evaluator/schema stay shared by strong - /// ownership; mutable bindings and workspace belong only to this Session. + /// Create one independent stream-affine Session for this exact compiled + /// owner generation. Mutable bindings and workspace belong to the Session, + /// while executable graph/evaluator state is reached only through a weak + /// generation binding and expires when this owner is dropped or replaced. pub(crate) fn instantiate( &self, stream: ObservationStreamId, ) -> Result>, ProgramSessionInstantiateError> { let bindings = self - .epoch + .owner_generation .binding_template .try_clone_v1() .map_err(map_session_instantiate_error)?; let workspace = self - .epoch + .owner_generation .graph .new_workspace() .map_err(map_session_instantiate_error)?; let mut modeled_occurrences = Vec::new(); modeled_occurrences - .try_reserve_exact(self.epoch.occurrence_contexts.len()) + .try_reserve_exact(self.owner_generation.occurrence_contexts.len()) .map_err(|_| ProgramSessionInstantiateError::ResourceExhausted)?; - modeled_occurrences.resize(self.epoch.occurrence_contexts.len(), None); + modeled_occurrences.resize(self.owner_generation.occurrence_contexts.len(), None); Ok(Session::new( stream, ProgramSessionPlan { - epoch: Rc::clone(&self.epoch), + owner_generation: Rc::downgrade(&self.owner_generation), + schema: self.owner_generation.observation_group.schema.clone(), bindings, workspace, modeled_occurrences, @@ -1180,6 +1190,28 @@ where counts: ProgramEvaluationCellCountsV1, } +struct SelectedProgramEvaluationBuffersV1 +where + Evaluation: ProgramPointEvaluatorV1, +{ + cells: Vec>, + outputs: Vec, + expected_cell_count: usize, +} + +impl PreparedProgramEvaluationBuffersV1 +where + Evaluation: ProgramPointEvaluatorV1, +{ + fn take_selected(&mut self) -> SelectedProgramEvaluationBuffersV1 { + SelectedProgramEvaluationBuffersV1 { + cells: std::mem::take(&mut self.selected_cells), + outputs: std::mem::take(&mut self.outputs), + expected_cell_count: self.counts.selected, + } + } +} + fn prepare_program_evaluation_buffers( epoch: &ProgramEpochV1, observation: &RevisionBoundObservationV1, @@ -1223,13 +1255,16 @@ where }) } -/// Per-Session mutable execution state backed by one strong immutable epoch. -pub struct ProgramSessionPlan +/// Per-Session mutable execution state bound weakly to one immutable compiled +/// owner generation. A transaction pins the generation before raw admission; +/// the Session itself cannot prolong the owner lifetime. +pub(crate) struct ProgramSessionPlan where Evaluation: ProgramPointEvaluatorV1, ProgramPointInvocation: Copy, { - epoch: Rc>, + owner_generation: Weak>, + schema: CanonicalObservationSchemaV1, bindings: AdmittedAppearanceBindings, workspace: AppearanceWorkspace, modeled_occurrences: Vec>, @@ -1247,61 +1282,65 @@ where Evaluation: ProgramPointEvaluatorV1, ProgramPointInvocation: Copy, { + type OwnerLease = ProgramOwnerLeaseV1; type Verified = ProgramVerifiedV1; type Violation = ProgramConflictV1; type Error = ProgramSessionEvaluationError>; + fn try_acquire_owner(&self) -> Option { + self.owner_generation.upgrade().map(ProgramOwnerLeaseV1) + } + fn observation_schema(&self) -> &CanonicalObservationSchemaV1 { - &self.epoch.observation_group.schema + &self.schema } fn evaluate( &mut self, + owner: &Self::OwnerLease, observation: RevisionBoundObservationV1, _permit: SessionObservationBindingPermitV1, ) -> Result, Self::Error> { - evaluate_program_session(self, observation) + evaluate_program_session(self, &owner.0, observation) } } fn evaluate_program_session( plan: &mut ProgramSessionPlan, + epoch: &ProgramEpochV1, observation: RevisionBoundObservationV1, ) -> ProgramSessionEvaluationResult where Evaluation: ProgramPointEvaluatorV1, ProgramPointInvocation: Copy, { - let epoch = Rc::clone(&plan.epoch); let Some(selection) = &epoch.joint_selection else { - let mut buffers = prepare_program_evaluation_buffers(&epoch, &observation, None)?; + let mut buffers = prepare_program_evaluation_buffers(epoch, &observation, None)?; return collect_program_candidate_into( plan, + epoch, observation, None, 1, - std::mem::take(&mut buffers.selected_cells), - std::mem::take(&mut buffers.outputs), - buffers.counts.selected, + buffers.take_selected(), ); }; let state_count = selection.order.tuples().len(); - let mut buffers = prepare_program_evaluation_buffers(&epoch, &observation, Some(state_count))?; + let mut buffers = prepare_program_evaluation_buffers(epoch, &observation, Some(state_count))?; for (state_index, tuple) in selection.order.tuples().enumerate() { apply_joint_candidate(plan, &epoch.finite_targets, tuple)?; - if !scan_program_candidate(plan, &observation, state_index, None, None)? { + if !scan_program_candidate(plan, epoch, &observation, state_index, None, None)? { // A selected tuple is never certified from its allocation-free // search pass. Re-apply and collect fresh terminal evidence. apply_joint_candidate(plan, &epoch.finite_targets, tuple)?; match collect_program_candidate_into( plan, + epoch, observation.clone(), Some(state_index), state_index + 1, - std::mem::take(&mut buffers.selected_cells), - std::mem::take(&mut buffers.outputs), - buffers.counts.selected, + buffers.take_selected(), )? { SessionDecision::Verified(verified) => { return Ok(SessionDecision::Verified(verified)); @@ -1335,6 +1374,7 @@ where apply_joint_candidate(plan, &epoch.finite_targets, tuple)?; if !scan_program_candidate( plan, + epoch, &observation, state_index, Some(&mut buffers.conflict_cells), @@ -1382,27 +1422,32 @@ where fn collect_program_candidate_into( plan: &mut ProgramSessionPlan, + epoch: &ProgramEpochV1, observation: RevisionBoundObservationV1, selected_state_index: Option, considered_state_count: usize, - mut cells: Vec>, - mut outputs: Vec, - expected_cell_count: usize, + buffers: SelectedProgramEvaluationBuffersV1, ) -> ProgramSessionEvaluationResult where Evaluation: ProgramPointEvaluatorV1, ProgramPointInvocation: Copy, { + let SelectedProgramEvaluationBuffersV1 { + mut cells, + mut outputs, + expected_cell_count, + } = buffers; if !cells.is_empty() || cells.capacity() < expected_cell_count || !outputs.is_empty() - || outputs.capacity() < plan.epoch.outputs.len() + || outputs.capacity() < epoch.outputs.len() { return Err(ProgramSessionEvaluationError::InternalInvariant); } let candidate_state_index = selected_state_index.unwrap_or(0); let has_hard_violation = scan_program_candidate( plan, + epoch, &observation, candidate_state_index, Some(&mut cells), @@ -1428,6 +1473,7 @@ where fn scan_program_candidate( plan: &mut ProgramSessionPlan, + epoch: &ProgramEpochV1, observation: &RevisionBoundObservationV1, candidate_state_index: usize, mut cells: Option<&mut Vec>>, @@ -1437,7 +1483,6 @@ where Evaluation: ProgramPointEvaluatorV1, ProgramPointInvocation: Copy, { - let epoch = &plan.epoch; let schema = &epoch.observation_group.schema; if !observation.shares_schema_backing_with(schema) { observation diff --git a/crates/labcolors-core/src/program_session_tests.rs b/crates/labcolors-core/src/program_session_tests.rs index 7618e9ae..50059864 100644 --- a/crates/labcolors-core/src/program_session_tests.rs +++ b/crates/labcolors-core/src/program_session_tests.rs @@ -16,7 +16,7 @@ use crate::program_session::{ Source, SourceId, Surface, Target, TargetId, canonical_surface_input_port_sequence_matches, check_render_node_count, }; -use crate::session::SessionState; +use crate::session::{SessionState, SessionUpdateError}; const SOURCE: SourceId = SourceId::new(1); const TARGET: TargetId = TargetId::new(1); @@ -521,7 +521,7 @@ fn canonical_helpers_and_checked_cardinality_fail_closed() { } #[test] -fn independently_instantiated_streams_survive_the_compiled_handle() { +fn independently_instantiated_streams_expire_with_their_compiled_owner_generation() { let compiled = exact_compiled(ConstraintSet::new( vec![ConstraintInvocation::hard( REQUIRED, @@ -534,23 +534,18 @@ fn independently_instantiated_streams_survive_the_compiled_handle() { let mut second = compiled.instantiate(STREAM_B).unwrap(); drop(compiled); - let first_state = first - .update(observed_update(STREAM_A, 1, &[(1, [0xFF; 3])])) - .unwrap(); - let SessionState::Ready { current: first } = first_state else { - panic!("first independent stream must verify"); - }; - assert_eq!(first.outputs().len(), 1); - - let second_state = second - .update(observed_update(STREAM_B, 9, &[(2, [0xFF; 3])])) - .unwrap(); - let SessionState::Ready { current: second } = second_state else { - panic!("second independent stream must verify after compiled handle drop"); - }; - assert_eq!(second.outputs().len(), 1); - assert_eq!(first.report().observation().revision(), Revision::new(1)); - assert_eq!(second.report().observation().revision(), Revision::new(9)); + assert!(matches!( + first.update(observed_update(STREAM_A, 1, &[(1, [0xFF; 3])])), + Err(SessionUpdateError::OwnerExpired), + )); + assert!(matches!( + second.update(observed_update(STREAM_B, 9, &[(2, [0xFF; 3])])), + Err(SessionUpdateError::OwnerExpired), + )); + assert!(matches!(first.state(), SessionState::Waiting)); + assert!(matches!(second.state(), SessionState::Waiting)); + assert_eq!(first.raw_head(), ObservationHeadViewV1::Empty); + assert_eq!(second.raw_head(), ObservationHeadViewV1::Empty); } #[test] diff --git a/crates/labcolors-core/src/session.rs b/crates/labcolors-core/src/session.rs index 2497219b..3e8643d3 100644 --- a/crates/labcolors-core/src/session.rs +++ b/crates/labcolors-core/src/session.rs @@ -66,14 +66,21 @@ where /// A compiled, statically dispatched evaluator used by the sole [`Session`] /// lifecycle. Implementations own their per-Session scratch directly. pub(crate) trait SessionPlanV1: private::PlanSealed { + /// One owned lease over the exact compiled owner generation used by an + /// update. Acquiring it is the first operation in the transaction, so an + /// expired plan cannot admit raw state or reach physical execution. + type OwnerLease; type Verified: SessionEvidenceV1; type Violation: SessionEvidenceV1; type Error; + fn try_acquire_owner(&self) -> Option; + fn observation_schema(&self) -> &CanonicalObservationSchemaV1; fn evaluate( &mut self, + owner: &Self::OwnerLease, observation: RevisionBoundObservationV1, permit: SessionObservationBindingPermitV1, ) -> Result, Self::Error>; @@ -128,6 +135,7 @@ impl ObservationOwnerV1 for SessionObservationHeadV1 { /// An update failed before either raw-head or lifecycle commit. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum SessionUpdateError { + OwnerExpired, Observation(ObservationError), Plan(PlanError), EvidenceBindingInvariant, @@ -139,8 +147,10 @@ type SessionUpdateResult<'session, Plan> = Result< >; /// The only production owner of revision admission and evaluator lifecycle. -/// `Plan` is monomorphized; there is no plan enum, dynamic dispatch, adapter, -/// weak owner or expiration branch. +/// `Plan` is monomorphized; there is no plan enum, dynamic dispatch or adapter. +/// A plan may keep only a weak reference to its compiled owner generation; +/// every update pins that exact generation before admission and releases it +/// after commit or rollback. #[derive(Debug)] pub(crate) struct Session { stream: ObservationStreamId, @@ -179,6 +189,10 @@ impl Session { &mut self, update: ObservationUpdateInput, ) -> SessionUpdateResult<'_, Plan> { + let owner = self + .plan + .try_acquire_owner() + .ok_or(SessionUpdateError::OwnerExpired)?; let prepared = prepare_observation(&mut self.raw_head, self.stream, &self.schema, update) .map_err(SessionUpdateError::Observation)?; @@ -205,7 +219,11 @@ impl Session { let next_raw_head = SessionObservationHeadV1::Observed(observation.clone()); let decision = self .plan - .evaluate(observation, SessionObservationBindingPermitV1::mint()) + .evaluate( + &owner, + observation, + SessionObservationBindingPermitV1::mint(), + ) .map_err(SessionUpdateError::Plan)?; let SessionObservationHeadV1::Observed(expected_observation) = &next_raw_head else { diff --git a/crates/labcolors-core/src/session_tests.rs b/crates/labcolors-core/src/session_tests.rs index 454090b9..2da91b7f 100644 --- a/crates/labcolors-core/src/session_tests.rs +++ b/crates/labcolors-core/src/session_tests.rs @@ -82,16 +82,22 @@ struct SentinelPlan { impl session_private::PlanSealed for SentinelPlan {} impl SessionPlanV1 for SentinelPlan { + type OwnerLease = (); type Verified = SentinelVerified; type Violation = SentinelViolation; type Error = SentinelError; + fn try_acquire_owner(&self) -> Option { + Some(()) + } + fn observation_schema(&self) -> &CanonicalObservationSchemaV1 { &self.schema } fn evaluate( &mut self, + _owner: &Self::OwnerLease, observation: RevisionBoundObservationV1, _permit: SessionObservationBindingPermitV1, ) -> Result, Self::Error> { @@ -126,6 +132,52 @@ impl SessionPlanV1 for SentinelPlan { } } +#[derive(Debug)] +struct ReplacingOwnerPlan { + schema: CanonicalObservationSchemaV1, + generation: std::rc::Weak<()>, + owner_slot: Rc>>>, + evaluations: Rc>, +} + +impl session_private::PlanSealed for ReplacingOwnerPlan {} + +impl SessionPlanV1 for ReplacingOwnerPlan { + type OwnerLease = Rc<()>; + type Verified = SentinelVerified; + type Violation = SentinelViolation; + type Error = SentinelError; + + fn try_acquire_owner(&self) -> Option { + self.generation.upgrade() + } + + fn observation_schema(&self) -> &CanonicalObservationSchemaV1 { + &self.schema + } + + fn evaluate( + &mut self, + owner: &Self::OwnerLease, + observation: RevisionBoundObservationV1, + _permit: SessionObservationBindingPermitV1, + ) -> Result, Self::Error> { + self.evaluations.set(self.evaluations.get() + 1); + let old_generation = self + .owner_slot + .borrow_mut() + .replace(Rc::new(())) + .expect("the first owner generation must still be installed"); + assert!(Rc::ptr_eq(owner, &old_generation)); + drop(old_generation); + assert!( + self.generation.upgrade().is_some(), + "the transaction lease must pin its starting owner generation" + ); + Ok(SessionDecision::Verified(SentinelVerified { observation })) + } +} + fn session() -> ( Session, SentinelControl, @@ -413,6 +465,40 @@ fn detached_plan_evidence_is_rejected_before_raw_or_lifecycle_commit() { assert_shared_observation(raw_observed(&session), ¤t_observation); } +#[test] +fn reentrant_owner_replacement_finishes_on_its_pinned_generation_then_expires() { + let schema = canonicalize_observation_schema(vec![SURFACE]).unwrap(); + let first_generation = Rc::new(()); + let owner_slot = Rc::new(RefCell::new(Some(Rc::clone(&first_generation)))); + let evaluations = Rc::new(Cell::new(0)); + let mut session = Session::new( + STREAM, + ReplacingOwnerPlan { + schema, + generation: Rc::downgrade(&first_generation), + owner_slot: Rc::clone(&owner_slot), + evaluations: Rc::clone(&evaluations), + }, + ); + drop(first_generation); + + let SessionState::Ready { current } = session.update(observed_update(1, [255; 3])).unwrap() + else { + panic!("the transaction pinned before replacement must commit"); + }; + assert_eq!(current.observation.revision(), Revision::new(1)); + assert_eq!(evaluations.get(), 1); + assert!(owner_slot.borrow().is_some()); + + assert_eq!( + session.update(observed_update(2, [0; 3])), + Err(SessionUpdateError::OwnerExpired), + ); + assert_eq!(evaluations.get(), 1); + assert_eq!(session.raw_head().revision(), Some(Revision::new(1))); + assert_eq!(verified_revision(session.state()), Some(Revision::new(1))); +} + #[test] fn session_source_contains_one_generic_update_owner_and_no_legacy_runtime() { let source = include_str!("session.rs"); @@ -421,7 +507,6 @@ fn session_source_contains_one_generic_update_owner_and_no_legacy_runtime() { "PointSupportSessionV1", "PointSupportSessionStateV1", "PointSupportSessionUpdateErrorV1", - "Weak<", "ProgramExpired", "ObservationStreamBinding", "SurfaceUpdate", diff --git a/scripts/verify_point_support_surplus.py b/scripts/verify_point_support_surplus.py index feb34d3f..8dbf5b53 100644 --- 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 = ( - "2e63424882231ec2f00ae79911bf74cfb935bb96f11368bce53240468f7f2dd4" + "f5e813b21df3bad8c93fa3525190715110b4965792f5b7a2a0bfde7a3b4297d3" ) EXPECTED_Q55_PROOF_SHA256 = ( "ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd" @@ -211,7 +211,7 @@ def verify_source_binding() -> tuple[str, int]: (SESSION_SOURCE, b" let next_raw_head = SessionObservationHeadV1::Observed(observation.clone());\n", b" let next_raw_head = SessionObservationHeadV1::Empty;\n"), (SESSION_SOURCE, b" *raw_head = next_raw_head;\n", b" *raw_head = SessionObservationHeadV1::Empty;\n"), (SESSION_SOURCE, b" Some(previous) => SessionState::Stale { previous },", b" Some(_) => SessionState::Waiting,"), - (SESSION_SOURCE, b" .evaluate(observation, SessionObservationBindingPermitV1::mint())", b" .evaluate(observation, SessionObservationBindingPermitV1::for_test())"), + (SESSION_SOURCE, b" SessionObservationBindingPermitV1::mint(),", b" SessionObservationBindingPermitV1::for_test(),"), (SESSION_SOURCE, b" SessionDecision::Verified(current) => SessionState::Ready { current },", b" SessionDecision::Verified(current) => SessionState::Stale { previous: current },"), (SESSION_SOURCE, b" SessionDecision::Violation(cause) => SessionState::Failed { cause, previous },", b" SessionDecision::Violation(_) => SessionState::Waiting,"), (SESSION_SOURCE, b" return Err(SessionUpdateError::EvidenceBindingInvariant);\n", b" unreachable!();\n"),