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