1use serde::{Deserialize, Serialize};
4#[cfg(any(test, feature = "test-support"))]
5use std::cell::Cell;
6use std::collections::{BTreeMap, BTreeSet};
7
8pub const EVIDENCE_GRAPH_SCHEMA_VERSION_V0: &str = "0";
9pub const EVIDENCE_GRAPH_PRODUCT_V0: &str = "omena-evidence-graph.graph";
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub enum GuaranteeKindV0 {
14 Floor,
15 SampledFixtureWitness,
16 SchedulerPriorityFixtureWitness,
17 MetricInputFixtureWitness,
18 IncrementalLayerEvidenceOnly,
19 AlphaRenamingStableHashFixtureWitness,
20 NotClaimedExactTraversal,
21}
22
23impl GuaranteeKindV0 {
24 pub const fn for_label_less_family() -> Self {
25 Self::Floor
26 }
27
28 pub const fn existing_label(self) -> Option<&'static str> {
29 match self {
30 Self::Floor => None,
31 Self::SampledFixtureWitness => Some("sampledFixtureWitnessNotEquivalenceProof"),
32 Self::SchedulerPriorityFixtureWitness => Some("fixtureWitnessSchedulerPriority"),
33 Self::MetricInputFixtureWitness => Some("fixtureWitnessMetricInput"),
34 Self::IncrementalLayerEvidenceOnly => Some("m6IncrementalLayerEvidenceOnly"),
35 Self::AlphaRenamingStableHashFixtureWitness => {
36 Some("fixtureWitnessAlphaRenamingStableHash")
37 }
38 Self::NotClaimedExactTraversal => Some("notClaimedExactTraversal"),
39 }
40 }
41
42 pub fn from_existing_label(label: &str) -> Option<Self> {
43 match label {
44 "sampledFixtureWitnessNotEquivalenceProof" => Some(Self::SampledFixtureWitness),
45 "fixtureWitnessSchedulerPriority" => Some(Self::SchedulerPriorityFixtureWitness),
46 "fixtureWitnessMetricInput" => Some(Self::MetricInputFixtureWitness),
47 "m6IncrementalLayerEvidenceOnly" => Some(Self::IncrementalLayerEvidenceOnly),
48 "fixtureWitnessAlphaRenamingStableHash" => {
49 Some(Self::AlphaRenamingStableHashFixtureWitness)
50 }
51 "notClaimedExactTraversal" => Some(Self::NotClaimedExactTraversal),
52 _ => None,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub enum GuaranteeFamilyV0 {
62 ByteIdentityOracle,
63 ExternalReplicaDifferential,
64 ExternalTool,
65 PropertyCorpusWitness,
66 TypedInvariantWitness,
67 ProseObligationDischarged,
68 FloorAssumption,
69 LedgerBackedObligationDischarge,
70 SampledFixtureCorpus,
71}
72
73impl GuaranteeFamilyV0 {
74 pub const fn describe(self) -> &'static str {
75 match self {
76 Self::ByteIdentityOracle => "byteIdentityOracle",
77 Self::ExternalReplicaDifferential => "externalReplicaDifferential",
78 Self::ExternalTool => "externalTool",
79 Self::PropertyCorpusWitness => "propertyCorpusWitness",
80 Self::TypedInvariantWitness => "typedInvariantWitness",
81 Self::ProseObligationDischarged => "proseObligationDischarged",
82 Self::FloorAssumption => "floorAssumption",
83 Self::LedgerBackedObligationDischarge => "ledgerBackedObligationDischarge",
84 Self::SampledFixtureCorpus => "sampledFixtureCorpus",
85 }
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90struct FamilyStampSealV0(());
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct ByteIdentityOracleTokenV0(FamilyStampSealV0);
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub struct ExternalReplicaDifferentialTokenV0(FamilyStampSealV0);
97
98#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct ExternalToolRunWitnessV0 {
102 pub tool_name: String,
103 pub tool_version: String,
104 pub input_digest: String,
105 pub exit_status: i32,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub struct PropertyCorpusWitnessTokenV0(FamilyStampSealV0);
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct PropertyCorpusWitnessEvidenceV0 {
113 pub record_count: usize,
114 pub measured_comparison_count: usize,
115 pub all_records_have_one_verdict: bool,
116 pub all_oracle_baselines_match: bool,
117 pub all_verdicts_match_measurements: bool,
118 pub all_divergences_reasoned: bool,
119 pub all_passes_accounted_for: bool,
120 pub all_families_non_vacuous_or_named_gap: bool,
121}
122
123impl PropertyCorpusWitnessTokenV0 {
124 pub fn from_conformance_ledger(evidence: PropertyCorpusWitnessEvidenceV0) -> Option<Self> {
125 (evidence.record_count > 0
126 && evidence.measured_comparison_count > 0
127 && evidence.all_records_have_one_verdict
128 && evidence.all_oracle_baselines_match
129 && evidence.all_verdicts_match_measurements
130 && evidence.all_divergences_reasoned
131 && evidence.all_passes_accounted_for
132 && evidence.all_families_non_vacuous_or_named_gap)
133 .then_some(Self(FamilyStampSealV0(())))
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub struct SampledFixtureCorpusTokenV0(FamilyStampSealV0);
139
140impl SampledFixtureCorpusTokenV0 {
141 pub fn from_matching_string_vectors(
142 sampled_values: &[String],
143 control_values: &[String],
144 reported_match: bool,
145 ) -> Option<Self> {
146 let recomputed_match = sampled_values == control_values;
147 (!sampled_values.is_empty()
148 && !control_values.is_empty()
149 && recomputed_match
150 && reported_match == recomputed_match)
151 .then_some(Self(FamilyStampSealV0(())))
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct TypedInvariantWitnessTokenV0(FamilyStampSealV0);
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub struct ProseObligationProvenanceV0(FamilyStampSealV0);
169
170impl ProseObligationProvenanceV0 {
171 pub fn from_provenance_labels(labels: &[String]) -> Option<Self> {
172 labels
173 .iter()
174 .any(|label| {
175 label.starts_with("obligation:")
176 || label.starts_with("cascadeObligationDeclared:")
177 || label.starts_with("enforcedAt:")
178 || label.starts_with("primitive:")
179 })
180 .then_some(Self(FamilyStampSealV0(())))
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185pub struct LedgerDischargeWitnessV0(FamilyStampSealV0);
186
187impl LedgerDischargeWitnessV0 {
188 pub fn from_discharge_cell_key_v0(cell_key: &str) -> Option<Self> {
189 (cell_key.len() == 64 && cell_key.bytes().all(|byte| byte.is_ascii_hexdigit()))
190 .then_some(Self(FamilyStampSealV0(())))
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub struct FamilyStampV0 {
196 earned_via: GuaranteeFamilyV0,
197 _seal: FamilyStampSealV0,
198}
199
200impl FamilyStampV0 {
201 pub const fn floor_assumption() -> Self {
202 Self::from_family(GuaranteeFamilyV0::FloorAssumption)
203 }
204
205 pub const fn byte_identity_oracle(_token: &ByteIdentityOracleTokenV0) -> Self {
206 Self::from_family(GuaranteeFamilyV0::ByteIdentityOracle)
207 }
208
209 pub const fn external_replica_differential(
210 _token: &ExternalReplicaDifferentialTokenV0,
211 ) -> Self {
212 Self::from_family(GuaranteeFamilyV0::ExternalReplicaDifferential)
213 }
214
215 pub const fn external_tool(_witness: &ExternalToolRunWitnessV0) -> Self {
226 Self::from_family(GuaranteeFamilyV0::ExternalTool)
227 }
228
229 pub const fn property_corpus_witness(_token: &PropertyCorpusWitnessTokenV0) -> Self {
230 Self::from_family(GuaranteeFamilyV0::PropertyCorpusWitness)
231 }
232
233 pub const fn sampled_fixture_corpus(_token: &SampledFixtureCorpusTokenV0) -> Self {
234 Self::from_family(GuaranteeFamilyV0::SampledFixtureCorpus)
235 }
236
237 pub const fn typed_invariant_witness(_token: &TypedInvariantWitnessTokenV0) -> Self {
238 Self::from_family(GuaranteeFamilyV0::TypedInvariantWitness)
239 }
240
241 pub const fn prose_obligation_discharged(_provenance: &ProseObligationProvenanceV0) -> Self {
242 Self::from_family(GuaranteeFamilyV0::ProseObligationDischarged)
243 }
244
245 pub const fn ledger_backed_obligation_discharge(_witness: &LedgerDischargeWitnessV0) -> Self {
246 Self::from_family(GuaranteeFamilyV0::LedgerBackedObligationDischarge)
247 }
248
249 pub const fn earned_via(self) -> GuaranteeFamilyV0 {
250 self.earned_via
251 }
252
253 const fn from_family(earned_via: GuaranteeFamilyV0) -> Self {
254 Self {
255 earned_via,
256 _seal: FamilyStampSealV0(()),
257 }
258 }
259}
260
261#[cfg(any(test, feature = "test-support"))]
262thread_local! {
263 static EARNED_GUARANTEE_FAMILY_READS_V0: Cell<u64> = const { Cell::new(0) };
264}
265
266#[cfg(any(test, feature = "test-support"))]
267pub fn reset_earned_guarantee_family_read_count_v0() {
268 EARNED_GUARANTEE_FAMILY_READS_V0.with(|count| count.set(0));
269}
270
271#[cfg(any(test, feature = "test-support"))]
272pub fn earned_guarantee_family_read_count_v0() -> u64 {
273 EARNED_GUARANTEE_FAMILY_READS_V0.with(Cell::get)
274}
275
276pub const REWRITE_OBLIGATION_FAMILY_PRODUCT_V0: &str =
277 "omena-evidence-graph.rewrite-obligation-family-closure";
278pub const REWRITE_OBLIGATION_FAMILY_COUNT_V0: usize = 45;
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
281#[serde(rename_all = "camelCase")]
282pub enum ObligationFamilyIdV0 {
283 CascadeSafetyFloor,
284 CascadeObligationDeclaration,
285 ComputedValuePreservation,
286 WhitespaceBoundary,
287 CommentSourceMapProvenance,
288 NumericLiteralEquivalence,
289 DimensionComputedValue,
290 ColorLiteralEquivalence,
291 UrlTokenGrammar,
292 StringTextAndFontValue,
293 SelectorSpecificityAndCascade,
294 LonghandShorthandCascadeOutcome,
295 DeclarationCascadeOrder,
296 RuleMergeWinnerOrder,
297 SelectorIdentityAndModuleSemantics,
298 SemanticMarkerRetention,
299 TargetPrefixAddition,
300 StalePrefixRemovalMapping,
301 TargetFallbackBranch,
302 ColorSpaceTargetEquivalence,
303 TargetColorPrecision,
304 DirectionalityOption,
305 NestedSelectorSpecificity,
306 ScopedMatching,
307 LayerOrderComparison,
308 TargetFeaturePredicate,
309 MediaPredicate,
310 ContainerPredicate,
311 NativeCssStaticValue,
312 CalcExpressionEquivalence,
313 ImportWrapperProvenance,
314 ScssNamespaceProvenance,
315 LessNamespaceProvenance,
316 SelectorIdentityMap,
317 ComposedClassProvenance,
318 ValueGraphResolution,
319 CustomPropertyFixedPoint,
320 SourceClassReachability,
321 AnimationNameReachability,
322 ValueGraphReachability,
323 VarReachability,
324 DeadMediaWitness,
325 DeadSupportsWitness,
326 DesignTokenPackageProvenance,
327 SourceMapTransformTrace,
328}
329
330impl ObligationFamilyIdV0 {
331 pub const fn as_str(self) -> &'static str {
332 match self {
333 Self::CascadeSafetyFloor => "cascadeSafetyFloor",
334 Self::CascadeObligationDeclaration => "cascadeObligationDeclaration",
335 Self::ComputedValuePreservation => "computedValuePreservation",
336 Self::WhitespaceBoundary => "whitespaceBoundary",
337 Self::CommentSourceMapProvenance => "commentSourceMapProvenance",
338 Self::NumericLiteralEquivalence => "numericLiteralEquivalence",
339 Self::DimensionComputedValue => "dimensionComputedValue",
340 Self::ColorLiteralEquivalence => "colorLiteralEquivalence",
341 Self::UrlTokenGrammar => "urlTokenGrammar",
342 Self::StringTextAndFontValue => "stringTextAndFontValue",
343 Self::SelectorSpecificityAndCascade => "selectorSpecificityAndCascade",
344 Self::LonghandShorthandCascadeOutcome => "longhandShorthandCascadeOutcome",
345 Self::DeclarationCascadeOrder => "declarationCascadeOrder",
346 Self::RuleMergeWinnerOrder => "ruleMergeWinnerOrder",
347 Self::SelectorIdentityAndModuleSemantics => "selectorIdentityAndModuleSemantics",
348 Self::SemanticMarkerRetention => "semanticMarkerRetention",
349 Self::TargetPrefixAddition => "targetPrefixAddition",
350 Self::StalePrefixRemovalMapping => "stalePrefixRemovalMapping",
351 Self::TargetFallbackBranch => "targetFallbackBranch",
352 Self::ColorSpaceTargetEquivalence => "colorSpaceTargetEquivalence",
353 Self::TargetColorPrecision => "targetColorPrecision",
354 Self::DirectionalityOption => "directionalityOption",
355 Self::NestedSelectorSpecificity => "nestedSelectorSpecificity",
356 Self::ScopedMatching => "scopedMatching",
357 Self::LayerOrderComparison => "layerOrderComparison",
358 Self::TargetFeaturePredicate => "targetFeaturePredicate",
359 Self::MediaPredicate => "mediaPredicate",
360 Self::ContainerPredicate => "containerPredicate",
361 Self::NativeCssStaticValue => "nativeCssStaticValue",
362 Self::CalcExpressionEquivalence => "calcExpressionEquivalence",
363 Self::ImportWrapperProvenance => "importWrapperProvenance",
364 Self::ScssNamespaceProvenance => "scssNamespaceProvenance",
365 Self::LessNamespaceProvenance => "lessNamespaceProvenance",
366 Self::SelectorIdentityMap => "selectorIdentityMap",
367 Self::ComposedClassProvenance => "composedClassProvenance",
368 Self::ValueGraphResolution => "valueGraphResolution",
369 Self::CustomPropertyFixedPoint => "customPropertyFixedPoint",
370 Self::SourceClassReachability => "sourceClassReachability",
371 Self::AnimationNameReachability => "animationNameReachability",
372 Self::ValueGraphReachability => "valueGraphReachability",
373 Self::VarReachability => "varReachability",
374 Self::DeadMediaWitness => "deadMediaWitness",
375 Self::DeadSupportsWitness => "deadSupportsWitness",
376 Self::DesignTokenPackageProvenance => "designTokenPackageProvenance",
377 Self::SourceMapTransformTrace => "sourceMapTransformTrace",
378 }
379 }
380
381 pub const fn descriptor(self) -> RewriteObligationFamilyDescriptorV0 {
382 match self {
383 Self::CascadeSafetyFloor => {
384 descriptor(self, "", GuaranteeKindV0::for_label_less_family())
385 }
386 Self::CascadeObligationDeclaration => descriptor(
387 self,
388 "must declare the rewrite-safety obligation family before cascade-sensitive rewrite evidence is emitted",
389 GuaranteeKindV0::for_label_less_family(),
390 ),
391 Self::ComputedValuePreservation => descriptor(
392 self,
393 "must preserve computed value semantics when a rewrite candidate claims computed-value preservation",
394 GuaranteeKindV0::for_label_less_family(),
395 ),
396 Self::WhitespaceBoundary => descriptor(
397 self,
398 "may remove only whitespace outside string, url, attr, and calc-sensitive token boundaries",
399 GuaranteeKindV0::for_label_less_family(),
400 ),
401 Self::CommentSourceMapProvenance => descriptor(
402 self,
403 "may remove comments only when source-map provenance preserves the removed span",
404 GuaranteeKindV0::for_label_less_family(),
405 ),
406 Self::NumericLiteralEquivalence => descriptor(
407 self,
408 "may rewrite only numerically equivalent literal tokens",
409 GuaranteeKindV0::for_label_less_family(),
410 ),
411 Self::DimensionComputedValue => descriptor(
412 self,
413 "may normalize only dimension values whose computed value is unchanged",
414 GuaranteeKindV0::for_label_less_family(),
415 ),
416 Self::ColorLiteralEquivalence => descriptor(
417 self,
418 "may rewrite only color-equivalent literal tokens",
419 GuaranteeKindV0::for_label_less_family(),
420 ),
421 Self::UrlTokenGrammar => descriptor(
422 self,
423 "may remove url quotes only when the unquoted token grammar remains equivalent",
424 GuaranteeKindV0::for_label_less_family(),
425 ),
426 Self::StringTextAndFontValue => descriptor(
427 self,
428 "may normalize string quotes and font keyword aliases only when computed text and font values remain equivalent",
429 GuaranteeKindV0::for_label_less_family(),
430 ),
431 Self::SelectorSpecificityAndCascade => descriptor(
432 self,
433 "must preserve selector specificity, keyframe timeline positions, and matching semantics under the cascade model",
434 GuaranteeKindV0::for_label_less_family(),
435 ),
436 Self::LonghandShorthandCascadeOutcome => descriptor(
437 self,
438 "must prove longhand and shorthand cascade outcomes are equivalent",
439 GuaranteeKindV0::for_label_less_family(),
440 ),
441 Self::DeclarationCascadeOrder => descriptor(
442 self,
443 "must preserve origin, layer, specificity, and order for every surviving declaration",
444 GuaranteeKindV0::for_label_less_family(),
445 ),
446 Self::RuleMergeWinnerOrder => descriptor(
447 self,
448 "must prove merged rule order cannot change declaration winners",
449 GuaranteeKindV0::for_label_less_family(),
450 ),
451 Self::SelectorIdentityAndModuleSemantics => descriptor(
452 self,
453 "must preserve selector identity and post-hash module semantics",
454 GuaranteeKindV0::for_label_less_family(),
455 ),
456 Self::SemanticMarkerRetention => descriptor(
457 self,
458 "may remove rules only when no source-visible semantic marker is attached",
459 GuaranteeKindV0::for_label_less_family(),
460 ),
461 Self::TargetPrefixAddition => descriptor(
462 self,
463 "must add target-required prefixed declarations without changing modern target outcomes",
464 GuaranteeKindV0::for_label_less_family(),
465 ),
466 Self::StalePrefixRemovalMapping => descriptor(
467 self,
468 "may remove prefixed declarations only when an explicit mapping and exact unprefixed peer prove the prefix stale",
469 GuaranteeKindV0::for_label_less_family(),
470 ),
471 Self::TargetFallbackBranch => descriptor(
472 self,
473 "must lower only when target data requires fallback branches and provenance tracks both branches",
474 GuaranteeKindV0::for_label_less_family(),
475 ),
476 Self::ColorSpaceTargetEquivalence => descriptor(
477 self,
478 "must lower only when color-space conversion is target-equivalent",
479 GuaranteeKindV0::for_label_less_family(),
480 ),
481 Self::TargetColorPrecision => descriptor(
482 self,
483 "must preserve color semantics within the configured target fallback precision",
484 GuaranteeKindV0::for_label_less_family(),
485 ),
486 Self::DirectionalityOption => descriptor(
487 self,
488 "must run only under explicit directionality options",
489 GuaranteeKindV0::for_label_less_family(),
490 ),
491 Self::NestedSelectorSpecificity => descriptor(
492 self,
493 "must preserve nested selector expansion and specificity",
494 GuaranteeKindV0::for_label_less_family(),
495 ),
496 Self::ScopedMatching => descriptor(
497 self,
498 "must preserve scoped matching semantics or emit a blocked result",
499 GuaranteeKindV0::for_label_less_family(),
500 ),
501 Self::LayerOrderComparison => descriptor(
502 self,
503 "must preserve layer order in CascadeKey comparison",
504 GuaranteeKindV0::for_label_less_family(),
505 ),
506 Self::TargetFeaturePredicate => descriptor(
507 self,
508 "may remove branches only when the target feature predicate is known",
509 GuaranteeKindV0::for_label_less_family(),
510 ),
511 Self::MediaPredicate => descriptor(
512 self,
513 "may remove branches only when the configured media predicate is known",
514 GuaranteeKindV0::for_label_less_family(),
515 ),
516 Self::ContainerPredicate => descriptor(
517 self,
518 "may remove @container branches only when the size condition is provably unsatisfiable regardless of container context",
519 GuaranteeKindV0::for_label_less_family(),
520 ),
521 Self::NativeCssStaticValue => descriptor(
522 self,
523 "may fold native CSS if() and function calls only when the evaluator proves a concrete static value and preserves runtime-dependent constructs verbatim",
524 GuaranteeKindV0::for_label_less_family(),
525 ),
526 Self::CalcExpressionEquivalence => descriptor(
527 self,
528 "may reduce only syntax-equivalent or computed-value-equivalent calc expressions",
529 GuaranteeKindV0::for_label_less_family(),
530 ),
531 Self::ImportWrapperProvenance => descriptor(
532 self,
533 "must preserve import-site media, supports, layer wrappers, and source provenance",
534 GuaranteeKindV0::for_label_less_family(),
535 ),
536 Self::ScssNamespaceProvenance => descriptor(
537 self,
538 "must preserve SCSS namespace, show/hide, mixin, variable, and source provenance facts",
539 GuaranteeKindV0::for_label_less_family(),
540 ),
541 Self::LessNamespaceProvenance => descriptor(
542 self,
543 "must preserve Less variable, mixin, namespace, and source provenance facts",
544 GuaranteeKindV0::for_label_less_family(),
545 ),
546 Self::SelectorIdentityMap => descriptor(
547 self,
548 "must rewrite every source and style reference through the same selector identity map",
549 GuaranteeKindV0::for_label_less_family(),
550 ),
551 Self::ComposedClassProvenance => descriptor(
552 self,
553 "must preserve exported class set and composed class provenance",
554 GuaranteeKindV0::for_label_less_family(),
555 ),
556 Self::ValueGraphResolution => descriptor(
557 self,
558 "must preserve @value graph resolution and cycle diagnostics",
559 GuaranteeKindV0::for_label_less_family(),
560 ),
561 Self::CustomPropertyFixedPoint => descriptor(
562 self,
563 "must preserve custom-property fixed-point semantics or emit a provenance-backed blocked result",
564 GuaranteeKindV0::for_label_less_family(),
565 ),
566 Self::SourceClassReachability => descriptor(
567 self,
568 "may remove classes only when bridge reachability proves no reachable source expression observes them",
569 GuaranteeKindV0::for_label_less_family(),
570 ),
571 Self::AnimationNameReachability => descriptor(
572 self,
573 "may remove keyframes only when animation-name reachability proves they are unobservable",
574 GuaranteeKindV0::for_label_less_family(),
575 ),
576 Self::ValueGraphReachability => descriptor(
577 self,
578 "may remove @value declarations only when value-graph traversal proves they are unreachable",
579 GuaranteeKindV0::for_label_less_family(),
580 ),
581 Self::VarReachability => descriptor(
582 self,
583 "may remove custom properties only when var() reachability proves they are unobservable",
584 GuaranteeKindV0::for_label_less_family(),
585 ),
586 Self::DeadMediaWitness => descriptor(
587 self,
588 "may remove @media branches only when target and cascade witnesses prove deadness",
589 GuaranteeKindV0::for_label_less_family(),
590 ),
591 Self::DeadSupportsWitness => descriptor(
592 self,
593 "may remove @supports branches only when target and cascade witnesses prove deadness",
594 GuaranteeKindV0::for_label_less_family(),
595 ),
596 Self::DesignTokenPackageProvenance => descriptor(
597 self,
598 "must preserve design-token provenance while routing declarations across package boundaries",
599 GuaranteeKindV0::for_label_less_family(),
600 ),
601 Self::SourceMapTransformTrace => descriptor(
602 self,
603 "must emit a source-map trace for every non-trivia transformed span",
604 GuaranteeKindV0::for_label_less_family(),
605 ),
606 }
607 }
608
609 pub const fn declares_cascade_obligation(self) -> bool {
610 !matches!(self, Self::CascadeSafetyFloor)
611 }
612
613 pub const fn preserves_computed_value(self) -> bool {
614 matches!(self, Self::ComputedValuePreservation)
615 }
616
617 pub const fn from_computed_value_preservation(preserved: bool) -> Self {
618 if preserved {
619 Self::ComputedValuePreservation
620 } else {
621 Self::CascadeSafetyFloor
622 }
623 }
624}
625
626#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
627#[serde(rename_all = "camelCase")]
628pub struct RewriteObligationFamilyDescriptorV0 {
629 pub id: ObligationFamilyIdV0,
630 pub family_name: &'static str,
631 pub obligation: &'static str,
632 pub justifiable_guarantee: GuaranteeKindV0,
633}
634
635const fn descriptor(
636 id: ObligationFamilyIdV0,
637 obligation: &'static str,
638 justifiable_guarantee: GuaranteeKindV0,
639) -> RewriteObligationFamilyDescriptorV0 {
640 RewriteObligationFamilyDescriptorV0 {
641 id,
642 family_name: id.as_str(),
643 obligation,
644 justifiable_guarantee,
645 }
646}
647
648pub const fn list_rewrite_obligation_families_v0()
649-> [RewriteObligationFamilyDescriptorV0; REWRITE_OBLIGATION_FAMILY_COUNT_V0] {
650 [
651 ObligationFamilyIdV0::CascadeSafetyFloor.descriptor(),
652 ObligationFamilyIdV0::CascadeObligationDeclaration.descriptor(),
653 ObligationFamilyIdV0::ComputedValuePreservation.descriptor(),
654 ObligationFamilyIdV0::WhitespaceBoundary.descriptor(),
655 ObligationFamilyIdV0::CommentSourceMapProvenance.descriptor(),
656 ObligationFamilyIdV0::NumericLiteralEquivalence.descriptor(),
657 ObligationFamilyIdV0::DimensionComputedValue.descriptor(),
658 ObligationFamilyIdV0::ColorLiteralEquivalence.descriptor(),
659 ObligationFamilyIdV0::UrlTokenGrammar.descriptor(),
660 ObligationFamilyIdV0::StringTextAndFontValue.descriptor(),
661 ObligationFamilyIdV0::SelectorSpecificityAndCascade.descriptor(),
662 ObligationFamilyIdV0::LonghandShorthandCascadeOutcome.descriptor(),
663 ObligationFamilyIdV0::DeclarationCascadeOrder.descriptor(),
664 ObligationFamilyIdV0::RuleMergeWinnerOrder.descriptor(),
665 ObligationFamilyIdV0::SelectorIdentityAndModuleSemantics.descriptor(),
666 ObligationFamilyIdV0::SemanticMarkerRetention.descriptor(),
667 ObligationFamilyIdV0::TargetPrefixAddition.descriptor(),
668 ObligationFamilyIdV0::StalePrefixRemovalMapping.descriptor(),
669 ObligationFamilyIdV0::TargetFallbackBranch.descriptor(),
670 ObligationFamilyIdV0::ColorSpaceTargetEquivalence.descriptor(),
671 ObligationFamilyIdV0::TargetColorPrecision.descriptor(),
672 ObligationFamilyIdV0::DirectionalityOption.descriptor(),
673 ObligationFamilyIdV0::NestedSelectorSpecificity.descriptor(),
674 ObligationFamilyIdV0::ScopedMatching.descriptor(),
675 ObligationFamilyIdV0::LayerOrderComparison.descriptor(),
676 ObligationFamilyIdV0::TargetFeaturePredicate.descriptor(),
677 ObligationFamilyIdV0::MediaPredicate.descriptor(),
678 ObligationFamilyIdV0::ContainerPredicate.descriptor(),
679 ObligationFamilyIdV0::NativeCssStaticValue.descriptor(),
680 ObligationFamilyIdV0::CalcExpressionEquivalence.descriptor(),
681 ObligationFamilyIdV0::ImportWrapperProvenance.descriptor(),
682 ObligationFamilyIdV0::ScssNamespaceProvenance.descriptor(),
683 ObligationFamilyIdV0::LessNamespaceProvenance.descriptor(),
684 ObligationFamilyIdV0::SelectorIdentityMap.descriptor(),
685 ObligationFamilyIdV0::ComposedClassProvenance.descriptor(),
686 ObligationFamilyIdV0::ValueGraphResolution.descriptor(),
687 ObligationFamilyIdV0::CustomPropertyFixedPoint.descriptor(),
688 ObligationFamilyIdV0::SourceClassReachability.descriptor(),
689 ObligationFamilyIdV0::AnimationNameReachability.descriptor(),
690 ObligationFamilyIdV0::ValueGraphReachability.descriptor(),
691 ObligationFamilyIdV0::VarReachability.descriptor(),
692 ObligationFamilyIdV0::DeadMediaWitness.descriptor(),
693 ObligationFamilyIdV0::DeadSupportsWitness.descriptor(),
694 ObligationFamilyIdV0::DesignTokenPackageProvenance.descriptor(),
695 ObligationFamilyIdV0::SourceMapTransformTrace.descriptor(),
696 ]
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
700#[serde(rename_all = "camelCase")]
701pub struct RewriteObligationFamilyRetirementRecordV0 {
702 pub family_name: &'static str,
703 pub reason: &'static str,
704}
705
706#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
707#[serde(rename_all = "camelCase")]
708pub struct RewriteObligationFamilyCarrierBindingV0 {
709 pub family: ObligationFamilyIdV0,
710 pub carrier: &'static str,
711}
712
713#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
714#[serde(rename_all = "camelCase")]
715pub struct RewriteObligationFamilyClosureSummaryV0 {
716 pub schema_version: &'static str,
717 pub product: &'static str,
718 pub registered_family_count: usize,
719 pub carrier_bound_family_count: usize,
720 pub retirement_record_count: usize,
721 pub orphan_family_names: Vec<&'static str>,
722 pub extra_carrier_family_names: Vec<&'static str>,
723 pub retirement_records: Vec<RewriteObligationFamilyRetirementRecordV0>,
724 pub untyped_carrier_count: usize,
725 pub untyped_prose_arm_count: usize,
726 pub closure_passed: bool,
727}
728
729pub fn summarize_rewrite_obligation_family_closure_v0(
730 carrier_bindings: impl IntoIterator<Item = RewriteObligationFamilyCarrierBindingV0>,
731 retirement_records: impl IntoIterator<Item = RewriteObligationFamilyRetirementRecordV0>,
732 untyped_carrier_count: usize,
733 untyped_prose_arm_count: usize,
734) -> RewriteObligationFamilyClosureSummaryV0 {
735 summarize_rewrite_obligation_family_closure_from_names_v0(
736 carrier_bindings
737 .into_iter()
738 .map(|binding| binding.family.as_str()),
739 retirement_records,
740 untyped_carrier_count,
741 untyped_prose_arm_count,
742 )
743}
744
745pub fn summarize_rewrite_obligation_family_closure_from_names_v0(
746 carrier_bound_family_names: impl IntoIterator<Item = &'static str>,
747 retirement_records: impl IntoIterator<Item = RewriteObligationFamilyRetirementRecordV0>,
748 untyped_carrier_count: usize,
749 untyped_prose_arm_count: usize,
750) -> RewriteObligationFamilyClosureSummaryV0 {
751 let registered_family_names = list_rewrite_obligation_families_v0()
752 .into_iter()
753 .map(|descriptor| descriptor.family_name)
754 .collect::<BTreeSet<_>>();
755 let carrier_bound_family_names = carrier_bound_family_names
756 .into_iter()
757 .collect::<BTreeSet<_>>();
758 let retirement_records = retirement_records.into_iter().collect::<Vec<_>>();
759 let retired_family_names = retirement_records
760 .iter()
761 .map(|record| record.family_name)
762 .collect::<BTreeSet<_>>();
763
764 let orphan_family_names = registered_family_names
765 .difference(&carrier_bound_family_names)
766 .copied()
767 .filter(|family| !retired_family_names.contains(family))
768 .collect::<Vec<_>>();
769 let extra_carrier_family_names = carrier_bound_family_names
770 .difference(®istered_family_names)
771 .copied()
772 .collect::<Vec<_>>();
773 let closure_passed = orphan_family_names.is_empty()
774 && extra_carrier_family_names.is_empty()
775 && untyped_carrier_count == 0
776 && untyped_prose_arm_count == 0;
777
778 RewriteObligationFamilyClosureSummaryV0 {
779 schema_version: EVIDENCE_GRAPH_SCHEMA_VERSION_V0,
780 product: REWRITE_OBLIGATION_FAMILY_PRODUCT_V0,
781 registered_family_count: registered_family_names.len(),
782 carrier_bound_family_count: carrier_bound_family_names.len(),
783 retirement_record_count: retirement_records.len(),
784 orphan_family_names,
785 extra_carrier_family_names,
786 retirement_records,
787 untyped_carrier_count,
788 untyped_prose_arm_count,
789 closure_passed,
790 }
791}
792
793#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
794#[serde(rename_all = "camelCase")]
795pub struct EvidenceNodeKeyV0 {
796 pub query_identity: String,
797 pub input_identity: String,
798}
799
800impl EvidenceNodeKeyV0 {
801 pub fn new(query_identity: impl Into<String>, input_identity: impl Into<String>) -> Self {
802 Self {
803 query_identity: query_identity.into(),
804 input_identity: input_identity.into(),
805 }
806 }
807}
808
809#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
810#[serde(rename_all = "camelCase")]
811pub struct EvidenceNodeSeedV0 {
812 pub key: EvidenceNodeKeyV0,
813 pub provenance: Vec<String>,
814 #[serde(skip_serializing_if = "Option::is_none")]
815 pub precision: Option<EvidenceAnalysisPrecisionV0>,
816 pub guarantee: GuaranteeKindV0,
817 pub earned_via: GuaranteeFamilyV0,
818}
819
820impl EvidenceNodeSeedV0 {
821 pub fn new(
822 key: EvidenceNodeKeyV0,
823 provenance: Vec<String>,
824 guarantee: GuaranteeKindV0,
825 ) -> Self {
826 Self::with_precision(key, provenance, None, guarantee)
827 }
828
829 pub fn with_precision(
830 key: EvidenceNodeKeyV0,
831 provenance: Vec<String>,
832 precision: Option<EvidenceAnalysisPrecisionV0>,
833 guarantee: GuaranteeKindV0,
834 ) -> Self {
835 Self::with_precision_and_family(
836 key,
837 provenance,
838 precision,
839 guarantee,
840 FamilyStampV0::floor_assumption(),
841 )
842 }
843
844 pub fn with_family(
845 key: EvidenceNodeKeyV0,
846 provenance: Vec<String>,
847 guarantee: GuaranteeKindV0,
848 family_stamp: FamilyStampV0,
849 ) -> Self {
850 Self::with_precision_and_family(key, provenance, None, guarantee, family_stamp)
851 }
852
853 pub fn with_precision_and_family(
854 key: EvidenceNodeKeyV0,
855 provenance: Vec<String>,
856 precision: Option<EvidenceAnalysisPrecisionV0>,
857 guarantee: GuaranteeKindV0,
858 family_stamp: FamilyStampV0,
859 ) -> Self {
860 Self {
861 key,
862 provenance,
863 precision,
864 guarantee,
865 earned_via: family_stamp.earned_via(),
866 }
867 }
868}
869
870#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
871#[serde(rename_all = "camelCase")]
872pub struct EvidenceAnalysisPrecisionV0 {
873 pub product: String,
874 pub value_domain: String,
875 pub flow_sensitivity: String,
876 pub context_sensitivity: String,
877 pub revision_axis: String,
878}
879
880impl EvidenceAnalysisPrecisionV0 {
881 pub fn new(
882 product: impl Into<String>,
883 value_domain: impl Into<String>,
884 flow_sensitivity: impl Into<String>,
885 context_sensitivity: impl Into<String>,
886 revision_axis: impl Into<String>,
887 ) -> Self {
888 Self {
889 product: product.into(),
890 value_domain: value_domain.into(),
891 flow_sensitivity: flow_sensitivity.into(),
892 context_sensitivity: context_sensitivity.into(),
893 revision_axis: revision_axis.into(),
894 }
895 }
896}
897
898#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
899#[serde(rename_all = "camelCase")]
900pub struct EvidenceDemandEdgeV0 {
901 pub from_query_identity: String,
902 pub to_node_key: EvidenceNodeKeyV0,
903 pub edge_kind: String,
904}
905
906impl EvidenceDemandEdgeV0 {
907 pub fn new(
908 from_query_identity: impl Into<String>,
909 to_node_key: EvidenceNodeKeyV0,
910 edge_kind: impl Into<String>,
911 ) -> Self {
912 Self {
913 from_query_identity: from_query_identity.into(),
914 to_node_key,
915 edge_kind: edge_kind.into(),
916 }
917 }
918}
919
920#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
921#[serde(rename_all = "camelCase")]
922pub struct EvidenceNodeV0 {
923 pub key: EvidenceNodeKeyV0,
924 pub provenance: Vec<String>,
925 #[serde(skip_serializing_if = "Option::is_none")]
926 pub precision: Option<EvidenceAnalysisPrecisionV0>,
927 pub guarantee: GuaranteeKindV0,
928 earned_via: GuaranteeFamilyV0,
929}
930
931impl EvidenceNodeV0 {
932 pub fn earned_via(&self) -> GuaranteeFamilyV0 {
933 #[cfg(any(test, feature = "test-support"))]
934 EARNED_GUARANTEE_FAMILY_READS_V0.with(|count| count.set(count.get().saturating_add(1)));
935 self.earned_via
936 }
937}
938
939#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
940#[serde(rename_all = "camelCase")]
941pub struct EvidenceGraphV0 {
942 pub schema_version: &'static str,
943 pub product: &'static str,
944 pub nodes: Vec<EvidenceNodeV0>,
945 pub edges: Vec<EvidenceDemandEdgeV0>,
946}
947
948impl EvidenceGraphV0 {
949 pub fn node_input_identities(&self) -> BTreeSet<String> {
950 self.nodes
951 .iter()
952 .map(|node| node.key.input_identity.clone())
953 .collect()
954 }
955
956 pub fn edge_input_identities(&self) -> BTreeSet<String> {
957 self.edges
958 .iter()
959 .map(|edge| edge.to_node_key.input_identity.clone())
960 .collect()
961 }
962}
963
964#[derive(Debug, Clone, PartialEq, Eq)]
965pub enum EvidenceGraphBuildErrorV0 {
966 MissingDemandNode(EvidenceNodeKeyV0),
967}
968
969fn fixture_witness_family_matches(
970 guarantee: GuaranteeKindV0,
971 earned_via: GuaranteeFamilyV0,
972) -> bool {
973 !matches!(
974 guarantee,
975 GuaranteeKindV0::SampledFixtureWitness
976 | GuaranteeKindV0::SchedulerPriorityFixtureWitness
977 | GuaranteeKindV0::MetricInputFixtureWitness
978 | GuaranteeKindV0::AlphaRenamingStableHashFixtureWitness
979 ) || matches!(
980 earned_via,
981 GuaranteeFamilyV0::SampledFixtureCorpus | GuaranteeFamilyV0::FloorAssumption
982 )
983}
984
985pub fn build_salsa_demand_evidence_graph_v0(
986 all_node_seeds: impl IntoIterator<Item = EvidenceNodeSeedV0>,
987 demand_edges: impl IntoIterator<Item = EvidenceDemandEdgeV0>,
988) -> Result<EvidenceGraphV0, EvidenceGraphBuildErrorV0> {
989 build_evidence_graph_from_edges_v0(all_node_seeds, demand_edges)
990}
991
992pub fn build_evidence_graph_from_edges_v0(
993 all_node_seeds: impl IntoIterator<Item = EvidenceNodeSeedV0>,
994 demand_edges: impl IntoIterator<Item = EvidenceDemandEdgeV0>,
995) -> Result<EvidenceGraphV0, EvidenceGraphBuildErrorV0> {
996 let all_nodes = all_node_seeds
997 .into_iter()
998 .map(|seed| (seed.key.clone(), seed))
999 .collect::<BTreeMap<_, _>>();
1000 let edges = demand_edges.into_iter().collect::<Vec<_>>();
1001 let demanded_keys = edges
1002 .iter()
1003 .map(|edge| edge.to_node_key.clone())
1004 .collect::<BTreeSet<_>>();
1005
1006 let mut nodes = Vec::new();
1007 for key in demanded_keys {
1008 let Some(seed) = all_nodes.get(&key) else {
1009 return Err(EvidenceGraphBuildErrorV0::MissingDemandNode(key));
1010 };
1011 assert!(
1017 fixture_witness_family_matches(seed.guarantee, seed.earned_via),
1018 "evidence node {:?} pairs fixture-witness guarantee {:?} with earned family {:?}",
1019 seed.key,
1020 seed.guarantee,
1021 seed.earned_via,
1022 );
1023 nodes.push(EvidenceNodeV0 {
1024 key: seed.key.clone(),
1025 provenance: seed.provenance.clone(),
1026 precision: seed.precision.clone(),
1027 guarantee: seed.guarantee,
1028 earned_via: seed.earned_via,
1029 });
1030 }
1031
1032 Ok(EvidenceGraphV0 {
1033 schema_version: EVIDENCE_GRAPH_SCHEMA_VERSION_V0,
1034 product: EVIDENCE_GRAPH_PRODUCT_V0,
1035 nodes,
1036 edges,
1037 })
1038}
1039
1040#[cfg(test)]
1041mod tests {
1042 use super::*;
1043
1044 fn node_seed(input_identity: &str) -> EvidenceNodeSeedV0 {
1045 EvidenceNodeSeedV0::new(
1046 EvidenceNodeKeyV0::new("memo_style_fact_entry", input_identity),
1047 vec!["fixture-provenance".to_string()],
1048 GuaranteeKindV0::for_label_less_family(),
1049 )
1050 }
1051
1052 #[test]
1053 fn rewrite_obligation_family_registry_is_closed_and_label_honest() {
1054 let descriptors = list_rewrite_obligation_families_v0();
1055 assert_eq!(descriptors.len(), REWRITE_OBLIGATION_FAMILY_COUNT_V0);
1056 let family_names = descriptors
1057 .iter()
1058 .map(|descriptor| descriptor.family_name)
1059 .collect::<BTreeSet<_>>();
1060 assert_eq!(family_names.len(), REWRITE_OBLIGATION_FAMILY_COUNT_V0);
1061 for descriptor in descriptors {
1062 assert_eq!(descriptor.family_name, descriptor.id.as_str());
1063 assert!(
1064 [
1065 GuaranteeKindV0::Floor,
1066 GuaranteeKindV0::SampledFixtureWitness,
1067 GuaranteeKindV0::SchedulerPriorityFixtureWitness,
1068 GuaranteeKindV0::MetricInputFixtureWitness,
1069 GuaranteeKindV0::IncrementalLayerEvidenceOnly,
1070 GuaranteeKindV0::AlphaRenamingStableHashFixtureWitness,
1071 GuaranteeKindV0::NotClaimedExactTraversal,
1072 ]
1073 .contains(&descriptor.justifiable_guarantee)
1074 );
1075 }
1076 }
1077
1078 #[test]
1079 fn rewrite_obligation_family_closure_reports_both_directions() {
1080 let summary = summarize_rewrite_obligation_family_closure_from_names_v0(
1081 ["computedValuePreservation", "unknownFamily"],
1082 [RewriteObligationFamilyRetirementRecordV0 {
1083 family_name: "cascadeSafetyFloor",
1084 reason: "test fixture",
1085 }],
1086 0,
1087 0,
1088 );
1089 assert!(
1090 summary
1091 .orphan_family_names
1092 .contains(&"cascadeObligationDeclaration")
1093 );
1094 assert!(
1095 summary
1096 .extra_carrier_family_names
1097 .contains(&"unknownFamily")
1098 );
1099 assert!(!summary.closure_passed);
1100 }
1101
1102 #[test]
1103 fn rewrite_obligation_family_closure_has_non_vacuous_retirement_branch() {
1104 let all_live_except_floor = list_rewrite_obligation_families_v0()
1105 .into_iter()
1106 .filter(|descriptor| descriptor.family_name != "cascadeSafetyFloor")
1107 .map(|descriptor| RewriteObligationFamilyCarrierBindingV0 {
1108 family: descriptor.id,
1109 carrier: "fixture",
1110 })
1111 .collect::<Vec<_>>();
1112 let summary = summarize_rewrite_obligation_family_closure_v0(
1113 all_live_except_floor,
1114 [RewriteObligationFamilyRetirementRecordV0 {
1115 family_name: "cascadeSafetyFloor",
1116 reason: "test fixture",
1117 }],
1118 0,
1119 0,
1120 );
1121 assert!(summary.closure_passed);
1122 assert_eq!(summary.retirement_record_count, 1);
1123 }
1124
1125 #[test]
1126 fn guarantee_kind_round_trips_existing_labels_without_upgrading_label_less_nodes() {
1127 for kind in [
1128 GuaranteeKindV0::SampledFixtureWitness,
1129 GuaranteeKindV0::SchedulerPriorityFixtureWitness,
1130 GuaranteeKindV0::MetricInputFixtureWitness,
1131 GuaranteeKindV0::IncrementalLayerEvidenceOnly,
1132 GuaranteeKindV0::AlphaRenamingStableHashFixtureWitness,
1133 GuaranteeKindV0::NotClaimedExactTraversal,
1134 ] {
1135 let label = kind.existing_label();
1136 assert_eq!(
1137 label.and_then(GuaranteeKindV0::from_existing_label),
1138 Some(kind)
1139 );
1140 }
1141 assert_eq!(
1142 GuaranteeKindV0::for_label_less_family(),
1143 GuaranteeKindV0::Floor
1144 );
1145 assert_eq!(GuaranteeKindV0::Floor.existing_label(), None);
1146 }
1147
1148 #[test]
1149 fn guarantee_family_descriptions_are_closed_and_honest() {
1150 let families = [
1151 (GuaranteeFamilyV0::ByteIdentityOracle, "byteIdentityOracle"),
1152 (
1153 GuaranteeFamilyV0::ExternalReplicaDifferential,
1154 "externalReplicaDifferential",
1155 ),
1156 (GuaranteeFamilyV0::ExternalTool, "externalTool"),
1157 (
1158 GuaranteeFamilyV0::PropertyCorpusWitness,
1159 "propertyCorpusWitness",
1160 ),
1161 (
1162 GuaranteeFamilyV0::SampledFixtureCorpus,
1163 "sampledFixtureCorpus",
1164 ),
1165 (
1166 GuaranteeFamilyV0::TypedInvariantWitness,
1167 "typedInvariantWitness",
1168 ),
1169 (
1170 GuaranteeFamilyV0::ProseObligationDischarged,
1171 "proseObligationDischarged",
1172 ),
1173 (GuaranteeFamilyV0::FloorAssumption, "floorAssumption"),
1174 (
1175 GuaranteeFamilyV0::LedgerBackedObligationDischarge,
1176 "ledgerBackedObligationDischarge",
1177 ),
1178 ];
1179
1180 assert_eq!(families.len(), 9);
1181 for (family, description) in families {
1182 assert_eq!(family.describe(), description);
1183 }
1184 }
1185
1186 #[test]
1187 fn evidence_nodes_preserve_floor_and_mechanism_families() -> Result<(), &'static str> {
1188 let floor_key = EvidenceNodeKeyV0::new("floor_query", "floor_input");
1189 let prose_key = EvidenceNodeKeyV0::new("prose_query", "prose_input");
1190 let prose_labels = vec![
1191 "pass:rule-merge".to_string(),
1192 "obligation:preserve declaration winner order".to_string(),
1193 ];
1194 let Some(prose_provenance) =
1195 ProseObligationProvenanceV0::from_provenance_labels(&prose_labels)
1196 else {
1197 return Err("prose provenance labels must mint a wrapper");
1198 };
1199 let graph = build_evidence_graph_from_edges_v0(
1200 [
1201 EvidenceNodeSeedV0::new(
1202 floor_key.clone(),
1203 vec!["floor-input".to_string()],
1204 GuaranteeKindV0::for_label_less_family(),
1205 ),
1206 EvidenceNodeSeedV0::with_family(
1207 prose_key.clone(),
1208 prose_labels,
1209 GuaranteeKindV0::for_label_less_family(),
1210 FamilyStampV0::prose_obligation_discharged(&prose_provenance),
1211 ),
1212 ],
1213 [
1214 EvidenceDemandEdgeV0::new("floor_query", floor_key, "fixture-edge"),
1215 EvidenceDemandEdgeV0::new("prose_query", prose_key, "fixture-edge"),
1216 ],
1217 )
1218 .map_err(|_| "fixture graph must build")?;
1219
1220 assert_eq!(graph.nodes[0].guarantee, GuaranteeKindV0::Floor);
1221 assert_eq!(
1222 graph.nodes[0].earned_via(),
1223 GuaranteeFamilyV0::FloorAssumption
1224 );
1225 assert_eq!(graph.nodes[1].guarantee, GuaranteeKindV0::Floor);
1226 assert_eq!(
1227 graph.nodes[1].earned_via(),
1228 GuaranteeFamilyV0::ProseObligationDischarged
1229 );
1230 Ok(())
1231 }
1232
1233 #[test]
1234 fn fixture_witness_kind_rejects_an_unearned_family() -> Result<(), &'static str> {
1235 let key = EvidenceNodeKeyV0::new("fixture_query", "fixture_input");
1236 let panic = match std::panic::catch_unwind(|| {
1237 build_evidence_graph_from_edges_v0(
1238 [EvidenceNodeSeedV0 {
1239 key: key.clone(),
1240 provenance: vec!["fixture-input".to_string()],
1241 precision: None,
1242 guarantee: GuaranteeKindV0::SampledFixtureWitness,
1243 earned_via: GuaranteeFamilyV0::TypedInvariantWitness,
1244 }],
1245 [EvidenceDemandEdgeV0::new(
1246 "fixture_query",
1247 key,
1248 "fixture-edge",
1249 )],
1250 )
1251 }) {
1252 Ok(_) => return Err("mismatched fixture witness family must be rejected"),
1253 Err(panic) => panic,
1254 };
1255 let message = panic
1256 .downcast_ref::<String>()
1257 .map(String::as_str)
1258 .or_else(|| panic.downcast_ref::<&str>().copied())
1259 .ok_or("panic must carry the mismatch detail")?;
1260
1261 for detail in [
1262 "fixture_input",
1263 "SampledFixtureWitness",
1264 "TypedInvariantWitness",
1265 ] {
1266 assert!(
1267 message.contains(detail),
1268 "mismatch message must name {detail}: {message}"
1269 );
1270 }
1271 Ok(())
1272 }
1273
1274 #[test]
1275 fn sampled_fixture_corpus_token_requires_nonempty_matching_evidence() -> Result<(), &'static str>
1276 {
1277 let sampled = vec!["source".to_string(), "style".to_string()];
1278 let control = sampled.clone();
1279 let mismatch = vec!["source".to_string()];
1280
1281 assert!(
1282 SampledFixtureCorpusTokenV0::from_matching_string_vectors(&sampled, &mismatch, false,)
1283 .is_none()
1284 );
1285 assert!(
1286 SampledFixtureCorpusTokenV0::from_matching_string_vectors(&sampled, &mismatch, true,)
1287 .is_none()
1288 );
1289 assert!(
1290 SampledFixtureCorpusTokenV0::from_matching_string_vectors(&sampled, &control, false,)
1291 .is_none()
1292 );
1293 assert!(
1294 SampledFixtureCorpusTokenV0::from_matching_string_vectors(&[], &[], true).is_none()
1295 );
1296
1297 let token =
1298 SampledFixtureCorpusTokenV0::from_matching_string_vectors(&sampled, &control, true)
1299 .ok_or("consistent nonempty fixture evidence should mint a token")?;
1300 assert_eq!(
1301 FamilyStampV0::sampled_fixture_corpus(&token).earned_via(),
1302 GuaranteeFamilyV0::SampledFixtureCorpus
1303 );
1304 Ok(())
1305 }
1306
1307 #[test]
1308 fn ledger_discharge_stamp_requires_cell_key_shape() {
1309 assert!(
1310 LedgerDischargeWitnessV0::from_discharge_cell_key_v0(
1311 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
1312 )
1313 .is_some()
1314 );
1315 assert!(LedgerDischargeWitnessV0::from_discharge_cell_key_v0("not-a-cell").is_none());
1316 }
1317
1318 #[test]
1319 fn external_tool_stamp_preserves_invocation_facts_without_a_verdict() {
1320 let witness = ExternalToolRunWitnessV0 {
1321 tool_name: "fixture-tool".to_string(),
1322 tool_version: "1.2.3".to_string(),
1323 input_digest: "0123456789abcdef".to_string(),
1324 exit_status: 0,
1325 };
1326
1327 assert_eq!(witness.tool_name, "fixture-tool");
1328 assert_eq!(witness.tool_version, "1.2.3");
1329 assert_eq!(witness.input_digest, "0123456789abcdef");
1330 assert_eq!(witness.exit_status, 0);
1331 assert_eq!(
1332 FamilyStampV0::external_tool(&witness).earned_via(),
1333 GuaranteeFamilyV0::ExternalTool
1334 );
1335 }
1336
1337 #[test]
1338 fn earned_family_read_counter_records_access_path() -> Result<(), &'static str> {
1339 reset_earned_guarantee_family_read_count_v0();
1340 let key = EvidenceNodeKeyV0::new("counter_query", "counter_input");
1341 let graph = build_evidence_graph_from_edges_v0(
1342 [EvidenceNodeSeedV0::new(
1343 key.clone(),
1344 vec!["counter-input".to_string()],
1345 GuaranteeKindV0::for_label_less_family(),
1346 )],
1347 [EvidenceDemandEdgeV0::new(
1348 "counter_query",
1349 key,
1350 "fixture-edge",
1351 )],
1352 )
1353 .map_err(|_| "fixture graph must build")?;
1354
1355 assert_eq!(graph.nodes.len(), 1);
1356 assert_eq!(earned_guarantee_family_read_count_v0(), 0);
1357 assert_eq!(
1358 graph.nodes[0].earned_via(),
1359 GuaranteeFamilyV0::FloorAssumption
1360 );
1361 assert_eq!(
1362 graph.nodes[0].earned_via(),
1363 GuaranteeFamilyV0::FloorAssumption
1364 );
1365 assert_eq!(earned_guarantee_family_read_count_v0(), 2);
1366 Ok(())
1367 }
1368
1369 #[test]
1370 fn salsa_demand_graph_keys_on_edges_not_the_full_node_list() -> Result<(), &'static str> {
1371 let graph = build_salsa_demand_evidence_graph_v0(
1372 [
1373 node_seed("/workspace/src/App.module.scss"),
1374 node_seed("/workspace/src/_theme.scss"),
1375 ],
1376 [EvidenceDemandEdgeV0::new(
1377 "memo_workspace_diagnostics_substrate",
1378 EvidenceNodeKeyV0::new("memo_style_fact_entry", "/workspace/src/App.module.scss"),
1379 "salsa-demand-read",
1380 )],
1381 )
1382 .map_err(|_| "demand edge must target a known node")?;
1383
1384 assert_eq!(
1385 graph.node_input_identities(),
1386 BTreeSet::from(["/workspace/src/App.module.scss".to_string()])
1387 );
1388 assert_eq!(
1389 graph.edge_input_identities(),
1390 BTreeSet::from(["/workspace/src/App.module.scss".to_string()])
1391 );
1392 assert_eq!(graph.nodes.len(), 1);
1393 assert_eq!(graph.edges.len(), 1);
1394 Ok(())
1395 }
1396
1397 #[test]
1398 fn salsa_demand_graph_rejects_fabricated_edges() {
1399 let result = build_salsa_demand_evidence_graph_v0(
1400 [node_seed("/workspace/src/App.module.scss")],
1401 [EvidenceDemandEdgeV0::new(
1402 "memo_workspace_diagnostics_substrate",
1403 EvidenceNodeKeyV0::new("memo_style_fact_entry", "/workspace/src/_missing.scss"),
1404 "salsa-demand-read",
1405 )],
1406 );
1407
1408 assert_eq!(
1409 result,
1410 Err(EvidenceGraphBuildErrorV0::MissingDemandNode(
1411 EvidenceNodeKeyV0::new("memo_style_fact_entry", "/workspace/src/_missing.scss")
1412 ))
1413 );
1414 }
1415
1416 #[test]
1417 fn graph_serializes_without_shape_specific_runtime_dependencies() -> Result<(), &'static str> {
1418 let graph = build_salsa_demand_evidence_graph_v0(
1419 [node_seed("/workspace/src/App.module.scss")],
1420 [EvidenceDemandEdgeV0::new(
1421 "memo_workspace_diagnostics_substrate",
1422 EvidenceNodeKeyV0::new("memo_style_fact_entry", "/workspace/src/App.module.scss"),
1423 "salsa-demand-read",
1424 )],
1425 )
1426 .map_err(|_| "demand edge must target a known node")?;
1427 let json = serde_json::to_value(&graph).map_err(|_| "graph must serialize")?;
1428 assert_eq!(json["schemaVersion"], "0");
1429 assert_eq!(json["product"], EVIDENCE_GRAPH_PRODUCT_V0);
1430 assert_eq!(json["nodes"][0]["guarantee"], "floor");
1431 Ok(())
1432 }
1433
1434 #[test]
1435 fn graph_preserves_optional_precision_payload() -> Result<(), &'static str> {
1436 let key = EvidenceNodeKeyV0::new("source_diagnostic_precision", "missingSelector");
1437 let graph = build_salsa_demand_evidence_graph_v0(
1438 [EvidenceNodeSeedV0::with_precision(
1439 key.clone(),
1440 vec!["omena-query.source-syntax-index".to_string()],
1441 Some(EvidenceAnalysisPrecisionV0::new(
1442 "omena-query.analysis-precision",
1443 "classValueResolution",
1444 "sourceSyntaxIndex",
1445 "perSourceReference",
1446 "OmenaQuerySourceDiagnosticsForFileV0.input",
1447 )),
1448 GuaranteeKindV0::for_label_less_family(),
1449 )],
1450 [EvidenceDemandEdgeV0::new(
1451 "source_diagnostic_precision",
1452 key,
1453 "diagnostic-evidence",
1454 )],
1455 )
1456 .map_err(|_| "precision edge must target a known node")?;
1457
1458 let precision = graph.nodes[0]
1459 .precision
1460 .as_ref()
1461 .ok_or("precision payload must round-trip through the graph")?;
1462 assert_eq!(precision.value_domain, "classValueResolution");
1463 assert_eq!(precision.flow_sensitivity, "sourceSyntaxIndex");
1464 assert_eq!(precision.context_sensitivity, "perSourceReference");
1465 Ok(())
1466 }
1467}