1use crate::events::{Event, EventKind};
56use crate::gate::GateVerdict;
57use crate::types::{RuleCitation, StandardsPin};
58use chrono::{DateTime, Utc};
59use serde::{Deserialize, Serialize};
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "kebab-case")]
67pub enum RuleDisposition {
68 Passed,
71 Failed,
73 Advisory,
76 Waived,
82 NotEvaluated,
85 NotApplicable,
88}
89
90impl RuleDisposition {
91 pub fn as_str(&self) -> &'static str {
93 match self {
94 Self::Passed => "passed",
95 Self::Failed => "failed",
96 Self::Advisory => "advisory",
97 Self::Waived => "waived",
98 Self::NotEvaluated => "not-evaluated",
99 Self::NotApplicable => "not-applicable",
100 }
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct CoverageEvidence {
111 pub seq: u64,
113 pub event: String,
116 pub mechanism: String,
119 pub bearing: String,
124 pub reference: String,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub waiver: Option<WaiverJoin>,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct WaiverJoin {
142 pub seq: u64,
144 pub approver: String,
145 pub surface: String,
146 pub reason: String,
147 pub expires_at: DateTime<Utc>,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct RuleCoverage {
156 pub id: String,
157 pub revision: u64,
158 pub lifecycle: String,
162 pub level: String,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub checker: Option<String>,
167 #[serde(default, skip_serializing_if = "String::is_empty")]
171 pub statement: String,
172 pub disposition: RuleDisposition,
173 #[serde(default, skip_serializing_if = "Vec::is_empty")]
176 pub evidence: Vec<CoverageEvidence>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub note: Option<String>,
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct DriftRecord {
188 pub seq: u64,
189 pub approved_digest: String,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub current_digest: Option<String>,
194 pub changed_rules: Vec<String>,
195}
196
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct StandardsCoverage {
203 pub pack_name: String,
204 pub pack_dir: String,
205 pub standards_root: String,
206 pub digest: String,
209 pub source: String,
211 pub approval_seq: u64,
213 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub resolution_seq: Option<u64>,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub resolved_at: Option<DateTime<Utc>>,
223 pub rules: Vec<RuleCoverage>,
227 #[serde(default, skip_serializing_if = "Vec::is_empty")]
229 pub drift: Vec<DriftRecord>,
230}
231
232pub fn standards_coverage(mission_id: &str, events: &[Event]) -> Option<StandardsCoverage> {
243 let mut pin: Option<(u64, StandardsPin)> = None;
244 let mut resolution: Option<(u64, DateTime<Utc>)> = None;
245 let mut findings: Vec<(u64, &crate::types::Finding, &str)> = Vec::new();
246 let mut gates: Vec<(u64, &str, GateVerdict, &str, &[String])> = Vec::new();
247 let mut drift: Vec<DriftRecord> = Vec::new();
248 let mut waivers: Vec<crate::standards_waiver::WaiverRecord> = Vec::new();
249 let mut frontier: Option<DateTime<Utc>> = None;
253
254 for event in events.iter().filter(|e| e.mission_id == mission_id) {
255 frontier = Some(frontier.map_or(event.ts, |seen| seen.max(event.ts)));
256 if let Some(record) = crate::standards_waiver::WaiverRecord::from_event(event) {
257 waivers.push(record);
258 }
259 match &event.kind {
260 EventKind::PlanApproved { plan, .. } => {
261 resolution = None;
266 pin = plan
267 .standards_manifest
268 .as_deref()
269 .map(|manifest| (event.seq, manifest.clone()));
270 }
271 EventKind::StandardsResolved { approval_seq, .. } => {
272 if pin.as_ref().is_some_and(|(seq, _)| seq == approval_seq) {
279 resolution = Some((event.seq, event.ts));
280 }
281 }
282 EventKind::ValidationFinding {
283 finding, run_id, ..
284 } => findings.push((event.seq, finding, run_id.as_str())),
285 EventKind::GateResult {
286 gate,
287 verdict,
288 artefact_ref,
289 rule_ids,
290 ..
291 } => gates.push((event.seq, gate.as_str(), *verdict, artefact_ref, rule_ids)),
292 EventKind::StandardsDrifted {
293 approved_digest,
294 current_digest,
295 changed_rules,
296 ..
297 } => drift.push(DriftRecord {
298 seq: event.seq,
299 approved_digest: approved_digest.clone(),
300 current_digest: current_digest.clone(),
301 changed_rules: changed_rules.clone(),
302 }),
303 _ => {}
304 }
305 }
306
307 let (approval_seq, pin) = pin?;
308 let now = frontier.expect("a pinned mission slice is non-empty");
311 let mut used_waivers: std::collections::BTreeSet<u64> = std::collections::BTreeSet::new();
315 let mut rules: Vec<RuleCoverage> = Vec::new();
316 for pinned in &pin.rules {
317 let mut evidence: Vec<CoverageEvidence> = Vec::new();
318 for (seq, finding, run_id) in &findings {
322 if let Some(citation) = &finding.rule {
323 if citation.id == pinned.id
324 && citation.revision == pinned.revision
325 && citation.digest == pin.digest
326 {
327 let mut entry = CoverageEvidence {
328 seq: *seq,
329 event: "validation.finding".to_string(),
330 mechanism: (*run_id).to_string(),
331 bearing: "fail".to_string(),
332 reference: finding.subject.clone(),
333 waiver: None,
334 };
335 let fingerprint = crate::standards_waiver::finding_fingerprint(run_id, finding);
342 if let Some(waiver) = waivers.iter().find(|waiver| {
343 !used_waivers.contains(&waiver.seq)
344 && crate::standards_waiver::waiver_covers(
345 waiver,
346 pinned,
347 &pin,
348 approval_seq,
349 &fingerprint,
350 now,
351 )
352 }) {
353 used_waivers.insert(waiver.seq);
354 entry.bearing = "waived".to_string();
355 entry.waiver = Some(WaiverJoin {
356 seq: waiver.seq,
357 approver: waiver.approver.clone(),
358 surface: waiver.surface.clone(),
359 reason: waiver.reason.clone(),
360 expires_at: waiver.expires_at,
361 });
362 }
363 evidence.push(entry);
364 }
365 }
366 }
367 for (seq, gate, verdict, artefact_ref, rule_ids) in &gates {
368 if rule_ids.iter().any(|id| id == &pinned.id) {
369 evidence.push(CoverageEvidence {
370 seq: *seq,
371 event: "gate.result".to_string(),
372 mechanism: (*gate).to_string(),
373 bearing: match verdict {
374 GateVerdict::Pass => "pass".to_string(),
375 GateVerdict::Fail => "fail".to_string(),
376 },
377 reference: (*artefact_ref).to_string(),
378 waiver: None,
383 });
384 }
385 }
386 evidence.sort_by_key(|entry| entry.seq);
387 let unwaived_finding_failure = evidence
388 .iter()
389 .any(|entry| entry.event == "validation.finding" && entry.bearing == "fail");
390 let gate_failure = evidence
391 .iter()
392 .any(|entry| entry.event == "gate.result" && entry.bearing == "fail");
393 let waived_finding = evidence
394 .iter()
395 .any(|entry| entry.event == "validation.finding" && entry.bearing == "waived");
396 let latest_pass = evidence
397 .iter()
398 .filter(|entry| entry.bearing == "pass")
399 .map(|entry| entry.seq)
400 .max();
401 let latest_failure = evidence
402 .iter()
403 .filter(|entry| entry.bearing == "fail")
404 .map(|entry| entry.seq)
405 .max();
406 let mode = crate::standards_enforcement::rule_mode(pinned);
407 let disposition = if latest_pass
408 .is_some_and(|pass| latest_failure.is_none_or(|failure| pass > failure))
409 {
410 RuleDisposition::Passed
414 } else if unwaived_finding_failure
415 || (gate_failure
416 && !(mode == crate::standards_enforcement::RuleMode::Authoritative
421 && waived_finding))
422 {
423 if mode == crate::standards_enforcement::RuleMode::Authoritative {
424 RuleDisposition::Failed
425 } else {
426 RuleDisposition::Advisory
427 }
428 } else if evidence.iter().any(|entry| entry.bearing == "waived") {
429 RuleDisposition::Waived
433 } else if evidence.iter().any(|entry| entry.bearing == "pass") {
434 RuleDisposition::Passed
435 } else {
436 RuleDisposition::NotEvaluated
437 };
438 rules.push(RuleCoverage {
439 id: pinned.id.clone(),
440 revision: pinned.revision,
441 lifecycle: pinned.effective_status.clone(),
442 level: pinned.level.clone(),
443 checker: pinned.checker.clone(),
444 statement: pinned.statement.clone(),
445 disposition,
446 evidence,
447 note: None,
448 });
449 }
450
451 let mut orphans: Vec<((String, u64, String), RuleCoverage)> = Vec::new();
457 for (seq, finding, run_id) in &findings {
458 let Some(citation) = &finding.rule else {
459 continue;
460 };
461 let joins = pin.rules.iter().any(|pinned| {
462 pinned.id == citation.id
463 && pinned.revision == citation.revision
464 && pin.digest == citation.digest
465 });
466 if joins {
467 continue;
468 }
469 let entry = CoverageEvidence {
470 seq: *seq,
471 event: "validation.finding".to_string(),
472 mechanism: (*run_id).to_string(),
473 bearing: "fail".to_string(),
474 reference: finding.subject.clone(),
475 waiver: None,
478 };
479 let key = (
480 citation.id.clone(),
481 citation.revision,
482 citation.digest.clone(),
483 );
484 match orphans.iter_mut().find(|(seen, _)| *seen == key) {
485 Some((_, row)) => row.evidence.push(entry),
486 None => orphans.push((key, not_applicable_row(citation, &pin, entry))),
487 }
488 }
489 orphans.sort_by(|(a, _), (b, _)| a.cmp(b));
490 rules.extend(orphans.into_iter().map(|(_, row)| row));
491
492 Some(StandardsCoverage {
493 pack_name: pin.pack_name.clone(),
494 pack_dir: pin.pack_dir.clone(),
495 standards_root: pin.standards_root.clone(),
496 digest: pin.digest.clone(),
497 source: pin.source.as_str().to_string(),
498 approval_seq,
499 resolution_seq: resolution.map(|(seq, _)| seq),
500 resolved_at: resolution.map(|(_, ts)| ts),
501 rules,
502 drift,
503 })
504}
505
506fn not_applicable_row(
509 citation: &RuleCitation,
510 pin: &StandardsPin,
511 entry: CoverageEvidence,
512) -> RuleCoverage {
513 RuleCoverage {
514 id: citation.id.clone(),
515 revision: citation.revision,
516 lifecycle: citation.lifecycle.clone(),
517 level: citation.level.clone(),
518 checker: citation.checker.clone(),
519 statement: String::new(),
520 disposition: RuleDisposition::NotApplicable,
521 evidence: vec![entry],
522 note: Some(format!(
523 "cited against digest sha256:{} — the approved pin (sha256:{}) carries no such \
524 rule/revision, so the mission was not judged by it",
525 citation.digest, pin.digest
526 )),
527 }
528}
529
530fn one_line(text: &str) -> String {
533 text.split_whitespace().collect::<Vec<_>>().join(" ")
534}
535
536fn md_cell(text: &str) -> String {
539 one_line(text).replace('|', "\\|")
540}
541
542pub fn render_coverage_markdown(coverage: &StandardsCoverage) -> String {
546 use std::fmt::Write as _;
547 let mut out = String::new();
548 let _ = writeln!(out, "## Flight Rules standards coverage\n");
549 let _ = writeln!(
550 out,
551 "Pack `{}` (`{}`, source {}) — standards root `{}`, digest `sha256:{}`.",
552 coverage.pack_name,
553 coverage.pack_dir,
554 coverage.source,
555 coverage.standards_root,
556 coverage.digest
557 );
558 let resolution = match (coverage.resolution_seq, coverage.resolved_at) {
559 (Some(seq), Some(ts)) => format!(
560 "selection recorded as `standards.resolved` seq {seq} (evaluated {})",
561 ts.to_rfc3339()
562 ),
563 _ => "no `standards.resolved` event in this log (a hand-cut log)".to_string(),
564 };
565 let _ = writeln!(
566 out,
567 "Pinned at plan approval (seq {}); {resolution}. This pin — not a later branch or \
568 filesystem read — governs the matrix.\n",
569 coverage.approval_seq
570 );
571 if coverage.rules.is_empty() {
572 let _ = writeln!(
573 out,
574 "No rules applied to this mission's selection inputs.\n"
575 );
576 } else {
577 let _ = writeln!(
578 out,
579 "| rule | rev | lifecycle | level | mechanism | disposition | evidence |\n\
580 |------|----:|-----------|-------|-----------|-------------|----------|"
581 );
582 for rule in &coverage.rules {
583 let checker = rule.checker.as_deref().unwrap_or("-");
584 let evidence = if rule.evidence.is_empty() {
585 "—".to_string()
586 } else {
587 rule.evidence
588 .iter()
589 .map(|entry| {
590 let mut cell = format!(
591 "{} seq {} {} {} `{}`",
592 entry.event,
593 entry.seq,
594 md_cell(&entry.mechanism),
595 entry.bearing,
596 md_cell(&entry.reference)
597 );
598 if let Some(waiver) = &entry.waiver {
602 let _ = write!(
603 cell,
604 " (waiver seq {} by {} via {}, expires {}: \"{}\")",
605 waiver.seq,
606 md_cell(&waiver.approver),
607 md_cell(&waiver.surface),
608 waiver.expires_at.to_rfc3339(),
609 md_cell(&waiver.reason)
610 );
611 }
612 cell
613 })
614 .collect::<Vec<_>>()
615 .join("; ")
616 };
617 let disposition = match &rule.note {
618 Some(note) => format!("{} ({})", rule.disposition.as_str(), md_cell(note)),
619 None => rule.disposition.as_str().to_string(),
620 };
621 let _ = writeln!(
622 out,
623 "| {} | r{} | {} | {} | {} | {} | {} |",
624 md_cell(&rule.id),
625 rule.revision,
626 rule.lifecycle,
627 rule.level,
628 md_cell(checker),
629 disposition,
630 evidence
631 );
632 }
633 let _ = writeln!(
634 out,
635 "\nAbsence of evidence is never rendered as pass: `not-evaluated` means no gate \
636 verdict or finding named the rule, and `not-applicable` marks citations the \
637 approved pin does not carry. `waived` names an authorized human exception \
638 (`standards.waiver.approved`, D-I) — one waiver subtracts exactly one failure, \
639 and any rule, finding, scope, diff, or expiry change restores the block.\n"
640 );
641 }
642 if !coverage.drift.is_empty() {
643 let _ = writeln!(
644 out,
645 "Policy drift refusals (merge re-resolved the live base against the approved \
646 pin):"
647 );
648 for record in &coverage.drift {
649 let current = record
650 .current_digest
651 .as_deref()
652 .map(|digest| format!("sha256:{digest}"))
653 .unwrap_or_else(|| "(no readable manifest on the live base)".to_string());
654 let _ = writeln!(
655 out,
656 "- seq {}: approved `sha256:{}` → current `{}`: {}",
657 record.seq,
658 record.approved_digest,
659 current,
660 record.changed_rules.join("; ")
661 );
662 }
663 out.push('\n');
664 }
665 out
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671 use crate::types::{PinnedRule, Plan, StandardsPinSource};
672
673 fn ts() -> DateTime<Utc> {
678 chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2026, 1, 2, 3, 4, 5).unwrap()
679 }
680
681 fn ev(seq: u64, kind: EventKind) -> Event {
682 Event {
683 seq,
684 ts: ts(),
685 mission_id: "m-1".to_string(),
686 kind,
687 }
688 }
689
690 fn pinned_rule(id: &str, revision: u64, status: &str, level: &str) -> PinnedRule {
691 PinnedRule {
692 id: id.to_string(),
693 revision,
694 rfc: "RFC-001".to_string(),
695 level: level.to_string(),
696 effective_status: status.to_string(),
697 statement: format!("statement for {id}"),
698 domains: Vec::new(),
699 stages: vec!["validation".to_string()],
700 when_paths: Vec::new(),
701 task_classes: Vec::new(),
702 checker: Some("gate:zz-gate".to_string()),
703 waivable: false,
704 }
705 }
706
707 fn pin(rules: Vec<PinnedRule>) -> StandardsPin {
708 StandardsPin {
709 pack_name: "zz-pack".to_string(),
710 pack_dir: "vendor/pack".to_string(),
711 standards_root: "standards".to_string(),
712 digest: "ab".repeat(32),
713 source: StandardsPinSource::RepoTracked,
714 task_class: None,
715 touch_set: vec!["crates/**".to_string()],
716 context_paths: Vec::new(),
717 gates: Vec::new(),
718 rules,
719 }
720 }
721
722 fn plan_with_pin(rules: Vec<PinnedRule>) -> Plan {
723 Plan {
724 goal: "g".to_string(),
725 validation_contract: Vec::new(),
726 milestones: Vec::new(),
727 considered_alternatives: None,
728 command_grants: Vec::new(),
729 touch_set: Vec::new(),
730 standards_manifest: Some(Box::new(pin(rules))),
731 reviewer_independence: None,
732 }
733 }
734
735 fn citation(id: &str, revision: u64, digest: &str, status: &str) -> RuleCitation {
736 RuleCitation {
737 id: id.to_string(),
738 revision,
739 source: "zz-pack standards".to_string(),
740 digest: digest.to_string(),
741 lifecycle: status.to_string(),
742 level: "must".to_string(),
743 checker: Some("gate:zz-gate".to_string()),
744 }
745 }
746
747 fn finding_with_rule(subject: &str, rule: Option<RuleCitation>) -> crate::types::Finding {
748 crate::types::Finding {
749 subject: subject.to_string(),
750 severity: "major".to_string(),
751 evidence: "zz evidence".to_string(),
752 suggested_fix: String::new(),
753 class: String::new(),
754 rule,
755 }
756 }
757
758 fn gate_result(
759 gate: &str,
760 verdict: GateVerdict,
761 artefact_ref: &str,
762 rule_ids: Vec<String>,
763 ) -> EventKind {
764 EventKind::GateResult {
765 gate: gate.to_string(),
766 surface: crate::gate::GateSurface::FinalGate,
767 kind: crate::gate::GateKind::Deterministic,
768 index: 0,
769 verdict,
770 artefact_ref: artefact_ref.to_string(),
771 artefact_detail: None,
772 score: None,
773 threshold: None,
774 rule_ids,
775 }
776 }
777
778 fn full_matrix_events() -> Vec<Event> {
784 let rules = vec![
785 pinned_rule("ZZ-ADV-001", 3, "approved", "should"),
786 pinned_rule("ZZ-FAIL-001", 2, "enforced", "must"),
787 pinned_rule("ZZ-PASS-001", 1, "enforced", "must"),
788 pinned_rule("ZZ-QUIET-001", 1, "enforced", "must"),
789 ];
790 vec![
791 ev(
792 1,
793 EventKind::PlanApproved {
794 plan: plan_with_pin(rules),
795 base_sha: Some("deadbeef".to_string()),
796 },
797 ),
798 ev(
799 2,
800 EventKind::StandardsResolved {
801 source: "repo-tracked".to_string(),
802 pack_name: "zz-pack".to_string(),
803 standards_root: "standards".to_string(),
804 digest: "ab".repeat(32),
805 stage: "approval".to_string(),
806 task_class: None,
807 touch_set: vec!["crates/**".to_string()],
808 context_paths: Vec::new(),
809 rules: Vec::new(),
810 approval_seq: 1,
811 },
812 ),
813 ev(
814 3,
815 gate_result(
816 "zz-gate",
817 GateVerdict::Pass,
818 "file:runs/gate-zz.jsonl",
819 vec!["ZZ-PASS-001".to_string()],
820 ),
821 ),
822 ev(
823 4,
824 EventKind::ValidationFinding {
825 milestone_id: "ms-1".to_string(),
826 run_id: "v-1".to_string(),
827 finding: finding_with_rule(
828 "a-1",
829 Some(citation("ZZ-FAIL-001", 2, &"ab".repeat(32), "enforced")),
830 ),
831 },
832 ),
833 ev(
834 5,
835 EventKind::ValidationFinding {
836 milestone_id: "ms-1".to_string(),
837 run_id: "v-1".to_string(),
838 finding: finding_with_rule(
839 "a-2",
840 Some(citation("ZZ-ADV-001", 3, &"ab".repeat(32), "approved")),
841 ),
842 },
843 ),
844 ev(
846 6,
847 EventKind::ValidationFinding {
848 milestone_id: "ms-1".to_string(),
849 run_id: "v-2".to_string(),
850 finding: finding_with_rule(
851 "a-3",
852 Some(citation("ZZ-FAIL-001", 1, &"ab".repeat(32), "enforced")),
853 ),
854 },
855 ),
856 ]
857 }
858
859 #[test]
862 fn flight_rules_provenance_coverage_matrix_assigns_each_disposition() {
863 let coverage = standards_coverage("m-1", &full_matrix_events()).expect("a pin folds");
864 assert_eq!(coverage.pack_name, "zz-pack");
865 assert_eq!(coverage.digest, "ab".repeat(32));
866 assert_eq!(coverage.approval_seq, 1);
867 assert_eq!(coverage.resolution_seq, Some(2));
868 assert_eq!(coverage.resolved_at, Some(ts()));
869
870 let by_key = |id: &str, revision: u64| {
871 coverage
872 .rules
873 .iter()
874 .find(|row| row.id == id && row.revision == revision)
875 .unwrap_or_else(|| panic!("row {id} r{revision} present"))
876 };
877 let passed = by_key("ZZ-PASS-001", 1);
879 assert_eq!(passed.disposition, RuleDisposition::Passed);
880 assert_eq!(passed.evidence.len(), 1);
881 assert_eq!(passed.evidence[0].event, "gate.result");
882 assert_eq!(passed.evidence[0].mechanism, "zz-gate");
883 assert_eq!(passed.evidence[0].bearing, "pass");
884 assert_eq!(passed.evidence[0].reference, "file:runs/gate-zz.jsonl");
885 let failed = by_key("ZZ-FAIL-001", 2);
888 assert_eq!(failed.disposition, RuleDisposition::Failed);
889 assert_eq!(failed.evidence.len(), 1);
890 assert_eq!(failed.evidence[0].event, "validation.finding");
891 assert_eq!(failed.evidence[0].reference, "a-1");
892 let advisory = by_key("ZZ-ADV-001", 3);
894 assert_eq!(advisory.disposition, RuleDisposition::Advisory);
895 let quiet = by_key("ZZ-QUIET-001", 1);
897 assert_eq!(quiet.disposition, RuleDisposition::NotEvaluated);
898 assert!(quiet.evidence.is_empty());
899 let orphan = by_key("ZZ-FAIL-001", 1);
901 assert_eq!(orphan.disposition, RuleDisposition::NotApplicable);
902 assert_eq!(orphan.evidence.len(), 1);
903 assert_eq!(orphan.evidence[0].reference, "a-3");
904 let note = orphan.note.as_deref().expect("the row explains itself");
905 assert!(
906 note.contains(&format!("sha256:{}", "ab".repeat(32))),
907 "{note}"
908 );
909 let ids: Vec<(&str, u64)> = coverage
912 .rules
913 .iter()
914 .map(|row| (row.id.as_str(), row.revision))
915 .collect();
916 assert_eq!(
917 ids,
918 [
919 ("ZZ-ADV-001", 3),
920 ("ZZ-FAIL-001", 2),
921 ("ZZ-PASS-001", 1),
922 ("ZZ-QUIET-001", 1),
923 ("ZZ-FAIL-001", 1),
924 ]
925 );
926 }
927
928 #[test]
929 fn flight_rules_enforcement_enforced_should_failure_is_advisory() {
930 let rule = pinned_rule("ZZ-SHOULD-001", 1, "enforced", "should");
931 let events = vec![
932 ev(
933 1,
934 EventKind::PlanApproved {
935 plan: plan_with_pin(vec![rule]),
936 base_sha: Some("deadbeef".to_string()),
937 },
938 ),
939 ev(
940 2,
941 gate_result(
942 "zz-gate",
943 GateVerdict::Fail,
944 "inline:failed",
945 vec!["ZZ-SHOULD-001".to_string()],
946 ),
947 ),
948 ];
949 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
950 assert_eq!(coverage.rules[0].disposition, RuleDisposition::Advisory);
951 }
952
953 #[test]
954 fn flight_rules_dashboard_latest_pass_supersedes_historical_failure() {
955 let rule = pinned_rule("ZZ-REPAIRED-001", 1, "enforced", "must");
956 let events = vec![
957 ev(
958 1,
959 EventKind::PlanApproved {
960 plan: plan_with_pin(vec![rule]),
961 base_sha: Some("deadbeef".to_string()),
962 },
963 ),
964 ev(
965 2,
966 EventKind::ValidationFinding {
967 milestone_id: "ms-1".to_string(),
968 run_id: crate::reducer::ENGINE_RUN_ID.to_string(),
969 finding: finding_with_rule(
970 "flight-rule:ZZ-REPAIRED-001",
971 Some(citation("ZZ-REPAIRED-001", 1, &"ab".repeat(32), "enforced")),
972 ),
973 },
974 ),
975 ev(
976 3,
977 gate_result(
978 "zz-gate",
979 GateVerdict::Pass,
980 "inline:passed after repair",
981 vec!["ZZ-REPAIRED-001".to_string()],
982 ),
983 ),
984 ];
985 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
986 assert_eq!(coverage.rules[0].disposition, RuleDisposition::Passed);
987 assert_eq!(
988 coverage.rules[0].evidence.len(),
989 2,
990 "history remains visible"
991 );
992 }
993
994 #[test]
1001 fn flight_rules_provenance_finding_rule_citation_folds_byte_compatibly() {
1002 let legacy = r#"{
1004 "subject": "a-1",
1005 "severity": "major",
1006 "evidence": "it broke",
1007 "suggestedFix": "fix it",
1008 "class": ""
1009 }"#;
1010 let folded: crate::types::Finding = serde_json::from_str(legacy).unwrap();
1011 assert_eq!(folded.rule, None);
1012
1013 let plain = finding_with_rule("a-1", None);
1016 let json = serde_json::to_string(&plain).unwrap();
1017 assert!(
1018 !json.contains("rule"),
1019 "a rule-less finding carries no rule key: {json}"
1020 );
1021 let reparsed: crate::types::Finding = serde_json::from_str(&json).unwrap();
1022 assert_eq!(reparsed.rule, None);
1023
1024 let cited = finding_with_rule(
1026 "a-1",
1027 Some(citation("ZZ-FAIL-001", 2, &"ab".repeat(32), "enforced")),
1028 );
1029 let json = serde_json::to_value(&cited).unwrap();
1030 assert_eq!(json["rule"]["id"], "ZZ-FAIL-001");
1031 assert_eq!(json["rule"]["revision"], 2);
1032 assert_eq!(json["rule"]["digest"], "ab".repeat(32));
1033 assert_eq!(json["rule"]["lifecycle"], "enforced");
1034 assert_eq!(json["rule"]["level"], "must");
1035 assert_eq!(json["rule"]["checker"], "gate:zz-gate");
1036 assert_eq!(json["rule"]["source"], "zz-pack standards");
1037 assert_eq!(json["subject"], "a-1");
1040 let back: crate::types::Finding = serde_json::from_value(json).unwrap();
1041 assert_eq!(back.rule, cited.rule);
1042 assert_eq!(back.subject, cited.subject);
1043 assert_eq!(back.class, cited.class);
1044 }
1045
1046 #[test]
1052 fn flight_rules_provenance_gate_result_rule_ids_are_additive() {
1053 let plain = gate_result("zz-gate", GateVerdict::Pass, "ref", Vec::new());
1055 let json = serde_json::to_value(&plain).unwrap();
1056 assert!(
1057 json["payload"].get("ruleIds").is_none(),
1058 "no linkage, no key: {json}"
1059 );
1060
1061 let linked = gate_result(
1063 "zz-gate",
1064 GateVerdict::Pass,
1065 "ref",
1066 vec!["ZZ-PASS-001".to_string(), "ZZ-QUIET-001".to_string()],
1067 );
1068 let json = serde_json::to_value(&linked).unwrap();
1069 assert_eq!(
1070 json["payload"]["ruleIds"],
1071 serde_json::json!(["ZZ-PASS-001", "ZZ-QUIET-001"])
1072 );
1073 let back: EventKind = serde_json::from_value(json).unwrap();
1074 let EventKind::GateResult { rule_ids, .. } = back else {
1075 panic!("wrong variant");
1076 };
1077 assert_eq!(rule_ids, ["ZZ-PASS-001", "ZZ-QUIET-001"]);
1078
1079 let outcome = crate::gate::GateOutcome::pass(crate::gate::ArtefactRef::new("ref zz"))
1081 .with_rule_ids(vec!["ZZ-PASS-001".to_string()]);
1082 let reports = vec![crate::gate::GateReport {
1083 name: "zz-gate".to_string(),
1084 kind: crate::gate::GateKind::Deterministic,
1085 outcome,
1086 }];
1087 let events =
1088 crate::gate_results::gate_result_events(crate::gate::GateSurface::FinalGate, &reports);
1089 let EventKind::GateResult { rule_ids, .. } = &events[0] else {
1090 panic!("wrong variant");
1091 };
1092 assert_eq!(rule_ids, &["ZZ-PASS-001".to_string()]);
1093 }
1094
1095 #[test]
1100 fn flight_rules_provenance_absent_evidence_is_never_pass() {
1101 let coverage = standards_coverage("m-1", &full_matrix_events()).expect("a pin folds");
1102 let quiet = coverage
1103 .rules
1104 .iter()
1105 .find(|row| row.id == "ZZ-QUIET-001")
1106 .expect("pinned rule present");
1107 assert_eq!(quiet.disposition, RuleDisposition::NotEvaluated);
1108 assert!(quiet.evidence.is_empty());
1109
1110 let md = render_coverage_markdown(&coverage);
1111 assert!(
1112 md.contains(
1113 "| ZZ-QUIET-001 | r1 | enforced | must | gate:zz-gate | not-evaluated | — |"
1114 ),
1115 "{md}"
1116 );
1117 assert!(
1118 md.contains("Absence of evidence is never rendered as pass"),
1119 "{md}"
1120 );
1121 let passed_lines: Vec<&str> = md
1123 .lines()
1124 .filter(|line| line.contains("| passed |"))
1125 .collect();
1126 assert_eq!(passed_lines.len(), 1, "{md}");
1127 assert!(passed_lines[0].contains("ZZ-PASS-001"), "{md}");
1128 }
1129
1130 #[test]
1135 fn flight_rules_provenance_waived_disposition_slot_renders() {
1136 let mut coverage = standards_coverage("m-1", &full_matrix_events()).expect("a pin folds");
1137 let row = coverage
1138 .rules
1139 .iter_mut()
1140 .find(|row| row.id == "ZZ-FAIL-001" && row.revision == 2)
1141 .expect("the failed row");
1142 row.disposition = RuleDisposition::Waived;
1143 row.evidence[0].bearing = "waived".to_string();
1144 let md = render_coverage_markdown(&coverage);
1145 assert!(
1146 md.contains("| ZZ-FAIL-001 | r2 | enforced | must | gate:zz-gate | waived |"),
1147 "{md}"
1148 );
1149 let json = serde_json::to_value(RuleDisposition::Waived).unwrap();
1151 assert_eq!(json, "waived");
1152 let json = serde_json::to_value(RuleDisposition::NotEvaluated).unwrap();
1153 assert_eq!(json, "not-evaluated");
1154 let json = serde_json::to_value(RuleDisposition::NotApplicable).unwrap();
1155 assert_eq!(json, "not-applicable");
1156 }
1157
1158 #[test]
1162 fn flight_rules_provenance_drift_refusal_is_named_with_both_digests() {
1163 let mut events = full_matrix_events();
1164 events.push(ev(
1165 7,
1166 EventKind::StandardsDrifted {
1167 approved_digest: "ab".repeat(32),
1168 current_digest: Some("cd".repeat(32)),
1169 surface: "merge".to_string(),
1170 changed_rules: vec![
1171 "ZZ-FAIL-001 r2 -> r3 (changed on the live base since approval)".to_string(),
1172 ],
1173 },
1174 ));
1175 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1176 assert_eq!(coverage.drift.len(), 1);
1177 assert_eq!(coverage.drift[0].seq, 7);
1178 assert_eq!(coverage.drift[0].approved_digest, "ab".repeat(32));
1179 assert_eq!(
1180 coverage.drift[0].current_digest.as_deref(),
1181 Some("cd".repeat(32).as_str())
1182 );
1183 let md = render_coverage_markdown(&coverage);
1184 assert!(md.contains("Policy drift refusals"), "{md}");
1185 assert!(
1186 md.contains(&format!("approved `sha256:{}`", "ab".repeat(32))),
1187 "{md}"
1188 );
1189 assert!(
1190 md.contains(&format!("current `sha256:{}`", "cd".repeat(32))),
1191 "{md}"
1192 );
1193 assert!(md.contains("ZZ-FAIL-001 r2 -> r3"), "{md}");
1194 }
1195
1196 #[test]
1200 fn flight_rules_provenance_same_inputs_render_byte_identical() {
1201 let events = full_matrix_events();
1202 let first = standards_coverage("m-1", &events).expect("a pin folds");
1203 let second = standards_coverage("m-1", &events).expect("a pin folds");
1204 assert_eq!(first, second);
1205 assert_eq!(
1206 serde_json::to_string_pretty(&first).unwrap(),
1207 serde_json::to_string_pretty(&second).unwrap()
1208 );
1209 assert_eq!(
1210 render_coverage_markdown(&first),
1211 render_coverage_markdown(&second)
1212 );
1213 let mut mixed = full_matrix_events();
1216 mixed.push(Event {
1217 mission_id: "m-other".to_string(),
1218 ..ev(
1219 99,
1220 EventKind::StandardsDrifted {
1221 approved_digest: "x".to_string(),
1222 current_digest: None,
1223 surface: "merge".to_string(),
1224 changed_rules: vec!["ZZ-X r1".to_string()],
1225 },
1226 )
1227 });
1228 let filtered = standards_coverage("m-1", &mixed).expect("a pin folds");
1229 assert_eq!(filtered, first);
1230 assert!(filtered.drift.is_empty());
1231 }
1232
1233 #[test]
1239 fn flight_rules_provenance_pre_flight_rules_logs_fold_to_none() {
1240 let legacy = vec![
1241 ev(
1242 1,
1243 EventKind::MissionCreated {
1244 goal: "g".to_string(),
1245 base_branch: "main".to_string(),
1246 mission_branch: "kranz/mission-m-1".to_string(),
1247 config: crate::types::MissionConfig::default(),
1248 },
1249 ),
1250 ev(
1251 2,
1252 EventKind::PlanApproved {
1253 plan: Plan {
1254 goal: "g".to_string(),
1255 validation_contract: Vec::new(),
1256 milestones: Vec::new(),
1257 considered_alternatives: None,
1258 command_grants: Vec::new(),
1259 touch_set: Vec::new(),
1260 standards_manifest: None,
1261 reviewer_independence: None,
1262 },
1263 base_sha: None,
1264 },
1265 ),
1266 ev(
1267 3,
1268 EventKind::ValidationFinding {
1269 milestone_id: "ms-1".to_string(),
1270 run_id: "v-1".to_string(),
1271 finding: finding_with_rule("a-1", None),
1272 },
1273 ),
1274 ];
1275 assert!(standards_coverage("m-1", &legacy).is_none());
1276
1277 let mut revised = legacy.clone();
1280 revised.push(ev(
1281 4,
1282 EventKind::PlanRevised {
1283 revision: 1,
1284 plan: plan_with_pin(vec![pinned_rule("ZZ-SNEAK-001", 1, "enforced", "must")]),
1285 },
1286 ));
1287 assert!(standards_coverage("m-1", &revised).is_none());
1288 }
1289
1290 #[test]
1294 fn flight_rules_provenance_markdown_renders_pin_rows_and_mechanisms() {
1295 let coverage = standards_coverage("m-1", &full_matrix_events()).expect("a pin folds");
1296 let md = render_coverage_markdown(&coverage);
1297 assert!(md.contains("## Flight Rules standards coverage"), "{md}");
1298 assert!(
1299 md.contains(&format!(
1300 "Pack `zz-pack` (`vendor/pack`, source repo-tracked) — standards root \
1301 `standards`, digest `sha256:{}`.",
1302 "ab".repeat(32)
1303 )),
1304 "{md}"
1305 );
1306 assert!(md.contains("Pinned at plan approval (seq 1)"), "{md}");
1307 assert!(md.contains("`standards.resolved` seq 2"), "{md}");
1308 assert!(md.contains("evaluated 2026-01-02T03:04:05"), "{md}");
1309 assert!(
1311 md.contains("gate.result seq 3 zz-gate pass `file:runs/gate-zz.jsonl`"),
1312 "{md}"
1313 );
1314 assert!(
1315 md.contains("validation.finding seq 4 v-1 fail `a-1`"),
1316 "{md}"
1317 );
1318 assert!(
1320 md.contains("not-applicable (cited against digest sha256:"),
1321 "{md}"
1322 );
1323 }
1324
1325 fn waiver_matrix_events() -> Vec<Event> {
1332 let mut fail = pinned_rule("ZZ-FAIL-001", 2, "enforced", "must");
1333 fail.waivable = true;
1334 vec![
1335 ev(
1336 1,
1337 EventKind::PlanApproved {
1338 plan: plan_with_pin(vec![
1339 fail,
1340 pinned_rule("ZZ-OTHER-001", 1, "enforced", "must"),
1341 ]),
1342 base_sha: Some("deadbeef".to_string()),
1343 },
1344 ),
1345 ev(
1346 2,
1347 gate_result(
1348 "zz-gate",
1349 GateVerdict::Pass,
1350 "file:runs/gate-zz.jsonl",
1351 vec!["ZZ-OTHER-001".to_string()],
1352 ),
1353 ),
1354 ev(
1355 4,
1356 EventKind::ValidationFinding {
1357 milestone_id: "ms-1".to_string(),
1358 run_id: "v-1".to_string(),
1359 finding: waiver_finding("a-1"),
1360 },
1361 ),
1362 ]
1363 }
1364
1365 fn waiver_finding(subject: &str) -> crate::types::Finding {
1368 finding_with_rule(
1369 subject,
1370 Some(citation("ZZ-FAIL-001", 2, &"ab".repeat(32), "enforced")),
1371 )
1372 }
1373
1374 fn waiver_fingerprint(subject: &str) -> String {
1375 crate::standards_waiver::finding_fingerprint("v-1", &waiver_finding(subject))
1376 }
1377
1378 fn valid_waiver(fingerprint: &str) -> EventKind {
1381 EventKind::StandardsWaiverApproved {
1382 rule_id: "ZZ-FAIL-001".to_string(),
1383 rule_revision: 2,
1384 manifest_digest: "ab".repeat(32),
1385 approval_seq: 1,
1386 finding_fingerprint: fingerprint.to_string(),
1387 paths: vec!["crates/engine/src/x.rs".to_string()],
1388 diff_digest: "cd".repeat(32),
1389 reason: "upstream false positive, tracked as zz-123".to_string(),
1390 approver: "local-operator".to_string(),
1391 surface: "cli".to_string(),
1392 expires_at: ts() + chrono::Duration::hours(1),
1393 }
1394 }
1395
1396 fn mutated_waiver(fingerprint: &str, mutate: impl FnOnce(&mut EventKind)) -> EventKind {
1399 let mut kind = valid_waiver(fingerprint);
1400 mutate(&mut kind);
1401 kind
1402 }
1403
1404 fn fail_row(coverage: &StandardsCoverage) -> &RuleCoverage {
1405 coverage
1406 .rules
1407 .iter()
1408 .find(|row| row.id == "ZZ-FAIL-001")
1409 .expect("the fixture row")
1410 }
1411
1412 #[test]
1419 fn flight_rules_waiver_valid_waiver_renders_waived_and_names_the_exception() {
1420 let mut events = waiver_matrix_events();
1421 events.push(ev(7, valid_waiver(&waiver_fingerprint("a-1"))));
1422 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1423
1424 let row = fail_row(&coverage);
1425 assert_eq!(row.disposition, RuleDisposition::Waived);
1426 assert_eq!(row.evidence.len(), 1);
1427 assert_eq!(row.evidence[0].bearing, "waived");
1428 let join = row.evidence[0].waiver.as_ref().expect("the waiver joins");
1429 assert_eq!(join.seq, 7);
1430 assert_eq!(join.approver, "local-operator");
1431 assert_eq!(join.surface, "cli");
1432 assert_eq!(join.reason, "upstream false positive, tracked as zz-123");
1433 assert_eq!(join.expires_at, ts() + chrono::Duration::hours(1));
1434
1435 let other = coverage
1438 .rules
1439 .iter()
1440 .find(|row| row.id == "ZZ-OTHER-001")
1441 .expect("the passing row");
1442 assert_eq!(other.disposition, RuleDisposition::Passed);
1443
1444 let md = render_coverage_markdown(&coverage);
1445 assert!(
1446 md.contains("| ZZ-FAIL-001 | r2 | enforced | must | gate:zz-gate | waived |"),
1447 "{md}"
1448 );
1449 assert!(
1450 md.contains(
1451 "validation.finding seq 4 v-1 waived `a-1` (waiver seq 7 by local-operator \
1452 via cli, expires "
1453 ),
1454 "{md}"
1455 );
1456 assert!(
1457 md.contains("upstream false positive, tracked as zz-123"),
1458 "{md}"
1459 );
1460 let json = serde_json::to_value(&coverage).unwrap();
1463 let waiver = &json["rules"][0]["evidence"][0]["waiver"];
1464 assert_eq!(waiver["seq"], 7);
1465 assert_eq!(waiver["approver"], "local-operator");
1466 assert_eq!(waiver["surface"], "cli");
1467 assert!(waiver["expiresAt"].is_string());
1468 }
1469
1470 #[test]
1471 fn flight_rules_enforcement_exact_waiver_covers_companion_gate_failure() {
1472 let mut events = waiver_matrix_events();
1473 events.push(ev(
1474 6,
1475 gate_result(
1476 "zz-gate",
1477 GateVerdict::Fail,
1478 "inline:failed",
1479 vec!["ZZ-FAIL-001".to_string()],
1480 ),
1481 ));
1482 events.push(ev(7, valid_waiver(&waiver_fingerprint("a-1"))));
1483 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1484 let row = fail_row(&coverage);
1485 assert_eq!(row.disposition, RuleDisposition::Waived);
1486 assert_eq!(row.evidence.len(), 2);
1487 assert!(row.evidence.iter().any(|entry| entry.bearing == "waived"));
1488 assert!(row
1489 .evidence
1490 .iter()
1491 .any(|entry| entry.event == "gate.result"));
1492 }
1493
1494 #[test]
1499 fn flight_rules_waiver_subtracts_exactly_one_failure() {
1500 let mut events = waiver_matrix_events();
1502 events.push(ev(
1503 5,
1504 EventKind::ValidationFinding {
1505 milestone_id: "ms-1".to_string(),
1506 run_id: "v-1".to_string(),
1507 finding: waiver_finding("a-9"),
1508 },
1509 ));
1510 events.push(ev(7, valid_waiver(&waiver_fingerprint("a-1"))));
1511 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1512 let row = fail_row(&coverage);
1513 assert_eq!(row.disposition, RuleDisposition::Failed);
1514 assert_eq!(row.evidence.len(), 2);
1515 assert_eq!(row.evidence[0].bearing, "waived");
1516 assert_eq!(row.evidence[1].bearing, "fail");
1517 assert!(row.evidence[1].waiver.is_none());
1518
1519 let mut events = waiver_matrix_events();
1523 events.push(ev(
1524 5,
1525 EventKind::ValidationFinding {
1526 milestone_id: "ms-1".to_string(),
1527 run_id: "v-1".to_string(),
1528 finding: waiver_finding("a-1"),
1529 },
1530 ));
1531 events.push(ev(7, valid_waiver(&waiver_fingerprint("a-1"))));
1532 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1533 let row = fail_row(&coverage);
1534 assert_eq!(row.disposition, RuleDisposition::Failed);
1535 assert_eq!(row.evidence[0].bearing, "waived");
1536 assert_eq!(row.evidence[1].bearing, "fail");
1537 }
1538
1539 #[test]
1545 fn flight_rules_waiver_expiry_restores_the_block() {
1546 let mut events = waiver_matrix_events();
1547 events.push(ev(7, valid_waiver(&waiver_fingerprint("a-1"))));
1548 let live = standards_coverage("m-1", &events).expect("a pin folds");
1549 assert_eq!(fail_row(&live).disposition, RuleDisposition::Waived);
1550
1551 let mut later = ev(8, EventKind::MissionPaused {});
1552 later.ts = ts() + chrono::Duration::hours(2);
1553 events.push(later);
1554 let expired = standards_coverage("m-1", &events).expect("a pin folds");
1555 let row = fail_row(&expired);
1556 assert_eq!(row.disposition, RuleDisposition::Failed);
1557 assert_eq!(row.evidence[0].bearing, "fail");
1558 assert!(row.evidence[0].waiver.is_none());
1559 }
1560
1561 #[test]
1566 fn flight_rules_waiver_mismatched_revision_digest_or_pin_joins_nothing() {
1567 let fingerprint = waiver_fingerprint("a-1");
1568 let probes = [
1569 mutated_waiver(&fingerprint, |kind| {
1570 if let EventKind::StandardsWaiverApproved { rule_revision, .. } = kind {
1571 *rule_revision = 3;
1572 }
1573 }),
1574 mutated_waiver(&fingerprint, |kind| {
1575 if let EventKind::StandardsWaiverApproved {
1576 manifest_digest, ..
1577 } = kind
1578 {
1579 *manifest_digest = "ff".repeat(32);
1580 }
1581 }),
1582 mutated_waiver(&fingerprint, |kind| {
1583 if let EventKind::StandardsWaiverApproved { approval_seq, .. } = kind {
1584 *approval_seq = 99;
1585 }
1586 }),
1587 ];
1588 for (idx, probe) in probes.into_iter().enumerate() {
1589 let mut events = waiver_matrix_events();
1590 events.push(ev(7, probe));
1591 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1592 let row = fail_row(&coverage);
1593 assert_eq!(
1594 row.disposition,
1595 RuleDisposition::Failed,
1596 "probe {idx} must not join"
1597 );
1598 assert!(row.evidence[0].waiver.is_none(), "probe {idx}");
1599 }
1600 }
1601
1602 #[test]
1605 fn flight_rules_waiver_fingerprint_mismatch_restores_the_block() {
1606 let mut events = waiver_matrix_events();
1607 events.push(ev(7, valid_waiver(&waiver_fingerprint("a-9"))));
1610 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1611 let row = fail_row(&coverage);
1612 assert_eq!(row.disposition, RuleDisposition::Failed);
1613 assert!(row.evidence[0].waiver.is_none());
1614 }
1615
1616 #[test]
1623 fn flight_rules_waiver_model_or_anonymous_surface_carries_no_authority() {
1624 let fingerprint = waiver_fingerprint("a-1");
1625 let probes = [
1626 mutated_waiver(&fingerprint, |kind| {
1627 if let EventKind::StandardsWaiverApproved { surface, .. } = kind {
1628 *surface = "model".to_string();
1629 }
1630 }),
1631 mutated_waiver(&fingerprint, |kind| {
1632 if let EventKind::StandardsWaiverApproved { surface, .. } = kind {
1633 *surface = "orchestrator".to_string();
1634 }
1635 }),
1636 mutated_waiver(&fingerprint, |kind| {
1637 if let EventKind::StandardsWaiverApproved { approver, .. } = kind {
1638 *approver = " ".to_string();
1639 }
1640 }),
1641 ];
1642 for (idx, probe) in probes.into_iter().enumerate() {
1643 let mut events = waiver_matrix_events();
1644 events.push(ev(7, probe));
1645 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1646 assert_eq!(
1647 fail_row(&coverage).disposition,
1648 RuleDisposition::Failed,
1649 "probe {idx} must carry no authority"
1650 );
1651 }
1652 }
1653
1654 #[test]
1659 fn flight_rules_waiver_non_waivable_rule_never_joins() {
1660 let mut events = full_matrix_events();
1663 events.push(ev(7, valid_waiver(&waiver_fingerprint("a-1"))));
1664 let coverage = standards_coverage("m-1", &events).expect("a pin folds");
1665 let row = fail_row(&coverage);
1666 assert_eq!(row.disposition, RuleDisposition::Failed);
1667 assert!(row.evidence[0].waiver.is_none());
1668 }
1669
1670 #[test]
1675 fn flight_rules_waiver_event_is_additive_and_old_logs_fold_unchanged() {
1676 let kind = valid_waiver(&"ef".repeat(32));
1677 let json = serde_json::to_value(&kind).unwrap();
1678 assert_eq!(json["type"], "standards.waiver.approved");
1679 assert_eq!(json["payload"]["ruleId"], "ZZ-FAIL-001");
1680 assert_eq!(json["payload"]["ruleRevision"], 2);
1681 assert_eq!(json["payload"]["manifestDigest"], "ab".repeat(32));
1682 assert_eq!(json["payload"]["approvalSeq"], 1);
1683 assert_eq!(json["payload"]["findingFingerprint"], "ef".repeat(32));
1684 assert_eq!(
1685 json["payload"]["paths"],
1686 serde_json::json!(["crates/engine/src/x.rs"])
1687 );
1688 assert_eq!(json["payload"]["diffDigest"], "cd".repeat(32));
1689 assert_eq!(
1690 json["payload"]["reason"],
1691 "upstream false positive, tracked as zz-123"
1692 );
1693 assert_eq!(json["payload"]["approver"], "local-operator");
1694 assert_eq!(json["payload"]["surface"], "cli");
1695 assert!(json["payload"]["expiresAt"].is_string());
1696 let back: EventKind = serde_json::from_value(json).unwrap();
1697 assert!(matches!(back, EventKind::StandardsWaiverApproved { .. }));
1698 assert_eq!(back.type_name(), "standards.waiver.approved");
1699
1700 let sparse = mutated_waiver(&"ef".repeat(32), |kind| {
1701 if let EventKind::StandardsWaiverApproved { paths, .. } = kind {
1702 paths.clear();
1703 }
1704 });
1705 let json = serde_json::to_value(&sparse).unwrap();
1706 assert!(
1707 json["payload"].get("paths").is_none(),
1708 "an empty path set carries no key: {json}"
1709 );
1710
1711 let coverage = standards_coverage("m-1", &full_matrix_events()).expect("a pin folds");
1713 let json = serde_json::to_string(&coverage).unwrap();
1714 assert!(
1715 !json.contains("\"waiver\""),
1716 "old logs fold byte-identically: {json}"
1717 );
1718 }
1719}