Skip to main content

kranz_engine/
standards_coverage.rs

1//! The Flight Rules rule coverage matrix (ticket
2//! `.kranz/tickets/flight-rules-finding-provenance.md`, KRZ-343; design
3//! `docs/scoping/flight-rules-engineering-standards.md`, decision D-H —
4//! "standards evidence is first-class"): one fold over the mission's event
5//! log that joins every applicable pinned rule to the evidence that named
6//! it — gate verdicts carrying `ruleIds`, findings carrying a
7//! [`crate::types::RuleCitation`] — and renders each rule's disposition as
8//! `passed`, `failed`, `advisory`, `waived`, `not-evaluated`, or
9//! `not-applicable`, with mechanism and artefact references.
10//!
11//! WHY a fold over the log alone: D-H makes the append-only log the audit
12//! substrate, so the matrix must reconstruct without re-reading the pack,
13//! the plan file, or any model prose. The pin itself rides the
14//! `plan.approved` event's `standardsManifest` (KRZ-342 — the reducer
15//! deliberately never re-reads one from `plan.revised`), the selection
16//! provenance rides `standards.resolved`, the drift refusals ride
17//! `standards.drifted`, and the evidence rides `gate.result` /
18//! `validation.finding`. Every join is STRUCTURED: rule id + revision +
19//! pinned digest, never a parse of `subject`/`evidence` text (the ticket's
20//! "extend the shipped evidence spine; do NOT parse orchestrator prose").
21//!
22//! WHY absence is never pass (D-H): a rule nobody evaluated is
23//! `not-evaluated`, full stop. `passed` requires POSITIVE evidence — a
24//! `gate.result` pass that named the rule — and no failing join. The same
25//! failing evidence renders `failed` against an effectively ENFORCED rule
26//! and `advisory` against an approved one (D-B: an advisory rule's violation
27//! could not have blocked). `waived` renders when every failing join on the
28//! row is covered by a valid, unexpired, exactly-matching
29//! `standards.waiver.approved` (KRZ-344, D-I) — the structured human
30//! exception event, joined on the full binding (rule id + pinned revision +
31//! manifest digest + approval seq + finding fingerprint + human surface).
32//! The ordinary orchestrator finding-waiver remains insufficient authority:
33//! a rule-cited finding waived as prose still renders `failed`/`advisory`.
34//!
35//! WHY `not-applicable` rows exist: evidence occasionally names a rule the
36//! approved pin does not carry (a hand-authored or stale citation at
37//! another revision/digest). Joining it would corrupt the matrix against
38//! the consent artifact; dropping it would hide the citation. The fold
39//! surfaces it as its own row — the mission was not judged by that rule,
40//! and the audit says so.
41//!
42//! Determinism (the ticket's byte-identity hint): the fold consults no
43//! clock, no filesystem, no hash map in output order — rows follow the
44//! pin's stable id order (then not-applicable rows sorted by id/revision/
45//! digest), evidence follows log seq order, and every timestamp is the
46//! log's own data. Waiver expiry is judged against the LOG'S OWN FRONTIER
47//! (the latest event instant in the mission's slice), never a wall clock,
48//! so the same log folds byte-identically at any wall time — a replay
49//! renders the world as of the evidence, and an enforcement decision
50//! re-judges expiry against its own clock (KRZ-346). A mission with no pin
51//! folds to `None`, and every consumer renders NOTHING — pre-Flight-Rules
52//! logs stay byte-identical through report.md, provenance replay, and the
53//! evidence bundle.
54
55use crate::events::{Event, EventKind};
56use crate::gate::GateVerdict;
57use crate::types::{RuleCitation, StandardsPin};
58use chrono::{DateTime, Utc};
59use serde::{Deserialize, Serialize};
60
61/// One rule's disposition in the coverage matrix — D-H's closed vocabulary.
62/// Serde kebab-case: the spellings are the design's own (`not-evaluated`,
63/// `not-applicable`), so the machine form and the prose surfaces can never
64/// drift apart.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "kebab-case")]
67pub enum RuleDisposition {
68    /// Positive evidence joined the rule (a `gate.result` pass named it)
69    /// and no failing evidence did. Never rendered from absence.
70    Passed,
71    /// Failing evidence joined an effectively ENFORCED rule.
72    Failed,
73    /// Failing evidence joined an effectively APPROVED rule — recorded,
74    /// but the advisory lifecycle means it could not block (D-B).
75    Advisory,
76    /// Every joined failure was excepted by a valid, unexpired,
77    /// exactly-matching `standards.waiver.approved` (KRZ-344, D-I) — the
78    /// authorized human exception. One waiver subtracts exactly one
79    /// failure: any failing join left uncovered renders
80    /// `failed`/`advisory` instead.
81    Waived,
82    /// The rule applied but no evidence names it. The honest zero state:
83    /// absence of evidence is never rendered as pass (D-H).
84    NotEvaluated,
85    /// Evidence cites a rule/revision/digest the approved pin does not
86    /// carry — surfaced as its own row, never joined and never dropped.
87    NotApplicable,
88}
89
90impl RuleDisposition {
91    /// The wire/serde spelling for text surfaces.
92    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/// One evidence join behind a row's disposition: which event named the
105/// rule, the mechanism that produced it, its bearing, and the reference the
106/// bytes re-found from — everything an auditor needs to locate the primary
107/// record in the log.
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct CoverageEvidence {
111    /// The event's seq — the join anchor into the log.
112    pub seq: u64,
113    /// The recording event's wire name (`gate.result` or
114    /// `validation.finding`) — the entry names its own source kind.
115    pub event: String,
116    /// The mechanism that produced it: the gate name for a gate verdict,
117    /// the citing run id for a finding.
118    pub mechanism: String,
119    /// The verdict bearing (`pass`/`fail`/`waived`; findings are failures
120    /// by construction). `waived` marks a failing join covered by a valid
121    /// `standards.waiver.approved` (KRZ-344) — the `waiver` field then
122    /// names it.
123    pub bearing: String,
124    /// The artefact handle (a gate's `artefactRef`, verbatim — its
125    /// resolution status stays with the gate ladder's total classifier) or
126    /// the finding's subject.
127    pub reference: String,
128    /// The waiver that excepts this failure (KRZ-344, D-I), joined through
129    /// the structured event — never parsed from orchestrator prose.
130    /// Present exactly when `bearing` is `waived`; additive.
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub waiver: Option<WaiverJoin>,
133}
134
135/// The waiver join behind a `waived` evidence entry (KRZ-344): everything
136/// the audit needs to name the exception without re-reading the event —
137/// its seq anchor, the approver principal + invocation surface, the
138/// recorded reason, and the expiry.
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct WaiverJoin {
142    /// The waiver event's seq — the join anchor into the log.
143    pub seq: u64,
144    pub approver: String,
145    pub surface: String,
146    pub reason: String,
147    pub expires_at: DateTime<Utc>,
148}
149
150/// One row of the coverage matrix: a pinned rule (or a citation that
151/// failed to join one), its pinned identity, the disposition the evidence
152/// earned, and the joins behind it.
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct RuleCoverage {
156    pub id: String,
157    pub revision: u64,
158    /// The pinned effective lifecycle (`approved`/`enforced`) and RFC-2119
159    /// level (`must`/`should`), verbatim from the pin (or from the citation
160    /// on a `not-applicable` row).
161    pub lifecycle: String,
162    pub level: String,
163    /// The pinned checker binding (`gate:<id>`, `agent-judgement`,
164    /// `manual-attestation`) — the mechanism column.
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub checker: Option<String>,
167    /// The pinned normative statement, so the machine form names what was
168    /// judged, not just ids. Empty (and absent) on `not-applicable` rows:
169    /// a citation carries no statement, and the fold never invents one.
170    #[serde(default, skip_serializing_if = "String::is_empty")]
171    pub statement: String,
172    pub disposition: RuleDisposition,
173    /// The joined evidence in log (seq) order. Empty exactly when the
174    /// disposition is `not-evaluated`.
175    #[serde(default, skip_serializing_if = "Vec::is_empty")]
176    pub evidence: Vec<CoverageEvidence>,
177    /// Why a `not-applicable` row exists (the digest the citation joined
178    /// against versus the pin's); absent on pinned rows.
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub note: Option<String>,
181}
182
183/// One `standards.drifted` merge refusal, replayed: both digests and the
184/// changed applicable enforced rules, pinned to its seq (KRZ-342 D-E/D-H).
185#[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    /// `None` when the live base no longer yielded a readable manifest at
191    /// all (removed or malformed — the ultimate drift, failed closed).
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub current_digest: Option<String>,
194    pub changed_rules: Vec<String>,
195}
196
197/// The folded coverage matrix for one mission: the approved pin's
198/// identity, its resolution provenance, every applicable rule's
199/// disposition, and any merge-time drift refusals.
200#[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    /// sha256 over the pinned normalized manifest — the content binding
207    /// every citation join checks.
208    pub digest: String,
209    /// `repo-tracked` or `external-pinned` (the pin's source).
210    pub source: String,
211    /// The seq of the `plan.approved` event the pin rode in on.
212    pub approval_seq: u64,
213    /// The `standards.resolved` selection record's seq, when the log
214    /// carries it (a hand-cut log may hold a pinned plan without it).
215    #[serde(default, skip_serializing_if = "Option::is_none")]
216    pub resolution_seq: Option<u64>,
217    /// The effective-time evaluation instant: the `standards.resolved`
218    /// event's own `ts` — resolution runs in the same approve_plan call as
219    /// the emission, so the append stamp IS the instant the effective
220    /// statuses were judged (events.rs's D-H verification note).
221    #[serde(default, skip_serializing_if = "Option::is_none")]
222    pub resolved_at: Option<DateTime<Utc>>,
223    /// Every applicable rule's disposition, in the pin's stable id order,
224    /// then any `not-applicable` citation rows (sorted by id, revision,
225    /// digest).
226    pub rules: Vec<RuleCoverage>,
227    /// `standards.drifted` merge refusals, in log order.
228    #[serde(default, skip_serializing_if = "Vec::is_empty")]
229    pub drift: Vec<DriftRecord>,
230}
231
232/// Fold one mission's standards coverage matrix from its event slice.
233/// `events` may contain other missions' events (filtered out, the
234/// provenance idiom). Pure and total: no clock, no filesystem, no git — a
235/// mission with no approved standards pin folds to `None`, and every
236/// render surface then emits nothing.
237///
238/// The pin source is the LATEST `plan.approved` carrying a
239/// `standardsManifest` — exactly the reducer's authority (`plan.revised`
240/// never re-reads one, so neither does this fold; the two can never
241/// disagree about which manifest the mission consented to).
242pub 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    // The fold's evaluation instant: the log's own frontier (the latest
250    // event instant in the mission's slice), never a wall clock — waiver
251    // expiry is judged as of the evidence, keeping replays byte-identical.
252    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                // Mirror the reducer's fold EXACTLY: the pin is replaced on
262                // every plan.approved, cleared included — a re-approval
263                // carrying no manifest means no standards govern from that
264                // point, and the resolution record resets with the pin.
265                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                // The selection provenance joins the pin EXACTLY: the
273                // resolution event names the `plan.approved` seq it pins,
274                // so a re-approval's second resolution never attributes the
275                // FIRST approval's instant to the standing pin — and a
276                // hand-cut log whose seqs disagree records no resolution
277                // rather than a mismatched one.
278                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    // A pinned mission has at least its plan.approved event, so the
309    // frontier exists; the expect can never fire.
310    let now = frontier.expect("a pinned mission slice is non-empty");
311    // Waivers consumed by a join: one waiver subtracts EXACTLY ONE failure
312    // (D-I), so a matched waiver can never cover a second entry — even one
313    // with an identical fingerprint.
314    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        // Findings join on the FULL citation key — id, pinned revision, and
319        // pinned digest — so a citation against any other manifest snapshot
320        // can never leak into this pin's row.
321        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                    // KRZ-344 (D-I): a valid, unexpired, exactly-matching
336                    // human waiver excepts THIS one failure. The join is
337                    // the structured event's full binding — rule id,
338                    // pinned revision, manifest digest, approval seq,
339                    // finding fingerprint, human surface — never a parse
340                    // of orchestrator decision prose.
341                    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                    // A waiver binds a finding fingerprint; gate-result
379                    // failures join no waiver in this slice — the
380                    // enforced-MUST gate binding (KRZ-346) owns its own
381                    // exception check.
382                    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            // Coverage keeps the full history below, but disposition is the
411            // latest checker state. A repaired rule that later passes must
412            // not remain failed forever or offer an obsolete waiver action.
413            RuleDisposition::Passed
414        } else if unwaived_finding_failure
415            || (gate_failure
416                // A standards gate event and its cited finding are two
417                // evidence views of ONE checker failure. An exact D-I waiver
418                // binds the finding, so the companion gate event must not
419                // resurrect the same block in replay.
420                && !(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            // Every failing join is covered by a valid waiver (a surviving
430            // "fail" bearing took the branch above); the row names the
431            // exception rather than the block.
432            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    // Citations that join NO pinned rule (stale revision, another digest,
452    // or a rule the mission never pinned): one not-applicable row per
453    // distinct (id, revision, digest), evidence in seq order. A finding's
454    // own row in the validation history is unaffected — this row is the
455    // matrix's honest "cited, but not this mission's approved policy".
456    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            // A not-applicable citation never joins a waiver: the waiver
476            // binds the PINNED rule, and this citation joined none.
477            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
506/// One orphan citation's row: the citation's own identity spellings (the
507/// pin cannot vouch for them) and a note naming the digest mismatch.
508fn 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
530/// Collapse whitespace runs to single spaces (the evidence_bundle
531/// `one_line` idiom) so one logical cell stays one physical line.
532fn one_line(text: &str) -> String {
533    text.split_whitespace().collect::<Vec<_>>().join(" ")
534}
535
536/// Escape a markdown table cell (the evidence_bundle `md_cell` idiom):
537/// pipes would break the column structure, newlines the row structure.
538fn md_cell(text: &str) -> String {
539    one_line(text).replace('|', "\\|")
540}
541
542/// The coverage matrix as markdown — the ONE renderer report.md and the
543/// evidence-bundle summary share, so the two surfaces can never drift.
544/// Pure: same coverage in, same bytes out.
545pub 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                        // A waived failure names its exception through the
599                        // structured event (KRZ-344, D-I): seq anchor,
600                        // approver + surface, expiry, reason.
601                        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    // ---- fixtures ---------------------------------------------------------
674
675    /// A fixed instant for every fixture event (the fold treats ts as data;
676    /// a constant keeps expected outputs pinnable).
677    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    /// The full-matrix fixture: a pin with four rules — one passed (gate
779    /// pass named it), one failed (enforced, cited by a finding), one
780    /// advisory (approved, cited by a finding), one never evaluated — plus
781    /// one orphan citation (a revision the pin does not carry). The pin's
782    /// rules are in stable id order, exactly as the resolver pins them.
783    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            // The orphan: the pin carries ZZ-FAIL-001 at r2, never r1.
845            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    // ---- the fold --------------------------------------------------------
860
861    #[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        // passed: the gate pass named ZZ-PASS-001; positive evidence.
878        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        // failed: an unwaived finding cites an enforced rule at the pinned
886        // revision and digest.
887        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        // advisory: the same failing evidence against an approved rule.
893        let advisory = by_key("ZZ-ADV-001", 3);
894        assert_eq!(advisory.disposition, RuleDisposition::Advisory);
895        // not-evaluated: applicable, but nothing named it.
896        let quiet = by_key("ZZ-QUIET-001", 1);
897        assert_eq!(quiet.disposition, RuleDisposition::NotEvaluated);
898        assert!(quiet.evidence.is_empty());
899        // not-applicable: the r1 citation joins no pinned rule.
900        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        // Pin order first (the fixture is stable id order, like a real
910        // pin), the orphan row after.
911        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    // ---- additive contract fields (D-H) -----------------------------------
995
996    /// The finding's rule citation is additive: a pre-KRZ-343 finding (no
997    /// `rule` key) folds with `None`, a rule-less finding serializes
998    /// byte-identically to the legacy shape, and a cited finding
999    /// round-trips the full join key verbatim.
1000    #[test]
1001    fn flight_rules_provenance_finding_rule_citation_folds_byte_compatibly() {
1002        // A legacy finding — no `rule` key — folds with the citation None.
1003        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        // A rule-less finding serializes byte-identically to the legacy
1014        // shape: the new field never hits the wire as null/empty.
1015        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        // A cited finding round-trips the whole join key.
1025        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        // …and the human/assertion handle stays exactly what it was —
1038        // provenance is never smuggled into `subject`.
1039        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    /// `ruleIds` on `gate.result` is additive: a gate with no standards
1047    /// linkage serializes byte-identically to the pre-KRZ-343 shape, a
1048    /// linked gate round-trips its ids, and the emission mapping
1049    /// ([`crate::gate_results::gate_result_events`]) carries them from the
1050    /// outcome verbatim.
1051    #[test]
1052    fn flight_rules_provenance_gate_result_rule_ids_are_additive() {
1053        // The wire shape without linkage: no ruleIds key at all.
1054        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        // With linkage: the ids ride the payload and round-trip.
1062        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        // The emission mapping: outcome.rule_ids → event, in pipeline order.
1080        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    /// D-H's central honesty rule: an applicable rule nothing evaluated is
1096    /// `not-evaluated` — never pass. The markdown says so out loud, and the
1097    /// only `passed` row in the full fixture is the one with a passing gate
1098    /// verdict behind it.
1099    #[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        // The one `passed` cell belongs to the gate-verdict row.
1122        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    /// The `waived` slot renders (D-H's vocabulary), though this slice wires
1131    /// no waiver signal — KRZ-344's `standards.waiver.approved` is the fold
1132    /// input. This test pins the render against a synthetic row so the slot
1133    /// cannot rot.
1134    #[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        // Serde kebab-case: the machine form spells it the design's way.
1150        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    /// A `standards.drifted` refusal is named in the matrix with both
1159    /// digests and the changed applicable rules (D-H/KRZ-342) — the merge
1160    /// refusal's evidence joins the same record the dispositions live in.
1161    #[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    /// Byte-identity: the same log folds to the same struct, the same
1197    /// markdown, and the same JSON — twice, independently (the ticket's
1198    /// determinism hint). The matrix consults no clock and no filesystem.
1199    #[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        // Other missions' events in the slice never leak into this
1214        // mission's matrix (the provenance fold's discipline).
1215        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    /// Pre-Flight-Rules logs fold to `None` — no pin, no matrix, and every
1234    /// consumer renders nothing (the byte-compat regression contract). A
1235    /// `plan.revised` carrying a manifest NEVER supplies one either: the
1236    /// reducer deliberately keeps the approval-time pin authority, and this
1237    /// fold mirrors it exactly.
1238    #[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        // A revision's carried manifest is never folded (apply_revised_plan
1278        // keeps the approval pin; the fold agrees).
1279        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    /// The markdown renderer's full shape: pin identity header, the
1291    /// resolution provenance line, one row per disposition with mechanism
1292    /// and artefact references, and the orphan's self-explaining note.
1293    #[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        // Mechanism and artefact reference ride the evidence cell.
1310        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        // The orphan row says why it is not applicable.
1319        assert!(
1320            md.contains("not-applicable (cited against digest sha256:"),
1321            "{md}"
1322        );
1323    }
1324
1325    // ---- the waiver join (KRZ-344, D-I) -----------------------------------
1326
1327    /// The waiver fixture: a pin whose enforced ZZ-FAIL-001 (r2) declares
1328    /// `waivable: true`, cited by one finding at seq 4 (run v-1, subject
1329    /// a-1); ZZ-OTHER-001 passes via a gate verdict, so every test can
1330    /// prove the waiver grants no authority beyond its one finding.
1331    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    /// The fixture finding the waiver binds (kept in one place so the
1366    /// fingerprint the test computes is byte-identical to the fold's).
1367    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    /// A fully valid waiver over the fixture finding; expiry one hour past
1379    /// the fixture instant, so the fixed-ts log frontier sees it live.
1380    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    /// `valid_waiver` with one field mutated — each invalidation clause of
1397    /// the D-I binding gets its own exact probe.
1398    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    /// A valid, unexpired, exactly-matching waiver renders `waived`, and
1413    /// the row names the exception through the structured event — the seq
1414    /// anchor, the approver and surface, the expiry, and the reason — in
1415    /// the ONE markdown renderer report.md, provenance replay, and the
1416    /// evidence bundle share (D-H/D-I; the ticket's "replay/report/
1417    /// evidence name the waiver" hint).
1418    #[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        // Unrelated rows receive no authority: the passing rule is
1436        // untouched by a waiver that never named it.
1437        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        // The machine form names it too (camelCase, additive). The
1461        // fixture pin's first rule is ZZ-FAIL-001.
1462        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    /// One waiver subtracts EXACTLY ONE matching failure (D-I): a second
1495    /// failing join on the same rule — a distinct finding, or even an
1496    /// identical duplicate one waiver could pattern-match twice — keeps
1497    /// the row failed, because the fold consumes each waiver once.
1498    #[test]
1499    fn flight_rules_waiver_subtracts_exactly_one_failure() {
1500        // Distinct second finding (subject a-9, same rule/revision/digest).
1501        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        // Identical duplicate (same run, same content, later seq): the
1520        // fingerprint matches both, but the consumed waiver cannot cover
1521        // the second occurrence.
1522        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    /// Expiry restores the block: the fold judges the waiver against the
1540    /// log's own frontier, so an event appended after the expiry instant
1541    /// flips the row back to failed — deterministically, with no wall
1542    /// clock. (An enforcement decision re-judges expiry against its own
1543    /// clock; this fold is the audit.)
1544    #[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    /// A revision bump, a substituted manifest, or a re-approval
1562    /// invalidates the waiver: the recorded revision / manifest digest /
1563    /// approval seq must match the standing pin exactly, or the join
1564    /// simply does not happen.
1565    #[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    /// A finding-fingerprint change invalidates the waiver: a waiver bound
1603    /// to one finding covers no other — the block stays.
1604    #[test]
1605    fn flight_rules_waiver_fingerprint_mismatch_restores_the_block() {
1606        let mut events = waiver_matrix_events();
1607        // Bound to a DIFFERENT finding's fingerprint (subject a-9, which
1608        // no recorded failure carries).
1609        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    /// The unauthorized-actor clause, consumption-side (D-I): a model may
1617    /// request a waiver but can never approve one, so an event claiming a
1618    /// model/orchestrator/worker surface — or no accountable approver at
1619    /// all — carries no authority and the block stands. No engine code
1620    /// path emits this event; this is the second fence against a hand-cut
1621    /// log laundering model discretion into human approval.
1622    #[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    /// A `waivable: false` rule can never be excepted: the record path
1655    /// refuses to write the event, and a hand-cut event joins nothing —
1656    /// the fold re-checks the pinned waiver posture rather than trusting
1657    /// the log (fail closed).
1658    #[test]
1659    fn flight_rules_waiver_non_waivable_rule_never_joins() {
1660        // full_matrix_events pins ZZ-FAIL-001 r2 with waivable: false,
1661        // cited by the seq-4 finding.
1662        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    /// The event is additive (AGENTS.md contract rule): the wire name and
1671    /// camelCase payload round-trip, an empty path set omits the key, and
1672    /// a log without waiver events folds byte-identically to before — no
1673    /// `waiver` key materializes anywhere in the machine form.
1674    #[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        // Pre-KRZ-344 logs: no waiver events, so no waiver key anywhere.
1712        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}