fallow_output/audit_focus.rs
1//! Audit focus-map output contracts.
2
3use serde::Serialize;
4
5/// The focus label for a review unit. EXACTLY two variants: `Skip` is NOT
6/// representable, so the type system is the guarantee that free mode never emits
7/// a `skip` label (safe explicit-skip is paid, runtime-backed only). Mirrors
8/// the decision surface's "cut category not representable" structural posture.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
10#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
11#[serde(rename_all = "kebab-case")]
12pub enum FocusLabel {
13 /// Review this unit.
14 ReviewHere,
15 /// Not prioritized, but still visible in the escape-hatch list.
16 NotPrioritized,
17}
18
19impl FocusLabel {
20 /// The wire token.
21 #[must_use]
22 pub const fn token(self) -> &'static str {
23 match self {
24 Self::ReviewHere => "review-here",
25 Self::NotPrioritized => "not-prioritized",
26 }
27 }
28}
29
30/// A per-unit confidence flag. The EXACT panel-decided strings: a dynamically-
31/// wired or re-export-heavy unit carries one so its static-reachability signal is
32/// not trusted as complete (the anti-silent-de-prioritization guard). The flag
33/// NEVER lowers the score; it is advisory provenance.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
35#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
36#[serde(rename_all = "kebab-case")]
37pub enum ConfidenceFlag {
38 /// The unit is dynamically wired.
39 DynamicDispatch,
40 /// The unit's reachability runs through re-export barrels.
41 ReExportIndirection,
42}
43
44impl ConfidenceFlag {
45 /// The wire message for this flag.
46 #[must_use]
47 pub const fn message(self) -> &'static str {
48 match self {
49 Self::DynamicDispatch => "low: dynamic dispatch detected",
50 Self::ReExportIndirection => "low: re-export indirection",
51 }
52 }
53}
54
55/// The composite attention score, with the four deterministic component
56/// sub-scores kept on the wire so the runtime seam can re-weight `total`
57/// without recomputing the signals.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
59#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
60pub struct FocusScore {
61 /// Fan-in/out blast-radius component.
62 pub fan_io: u32,
63 /// Security source -> sink taint-touch component (0 until a security pass is
64 /// threaded onto the brief path; the seam is built and tested).
65 pub security_taint: u32,
66 /// Risk-zone component (boundary / public-API / security-sensitive).
67 pub risk_zone: u32,
68 /// Change-shape component (new/widened export, signature change proxy).
69 pub change_shape: u32,
70 /// The summed total. The paid runtime layer multiplies a runtime hot/cold weight in here.
71 pub total: u32,
72}
73
74/// One review unit on the focus map: its file, composite score, label, human
75/// reason, and any confidence flags.
76#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
77#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
78pub struct FocusUnit {
79 /// Root-relative path of the changed file this unit covers.
80 pub file: String,
81 /// The composite attention score and its component breakdown.
82 pub score: FocusScore,
83 /// The focus label (`review-here` / `not-prioritized`; NEVER `skip`).
84 pub label: FocusLabel,
85 /// A human-readable reason for the label, built from the present signals.
86 pub reason: String,
87 /// Confidence flags (advisory; never lower the score). Sorted, deduped.
88 #[serde(default, skip_serializing_if = "Vec::is_empty")]
89 pub confidence: Vec<ConfidenceFlag>,
90}
91
92/// The weighted focus map: the ranked `review-here` units plus the FULL
93/// `deprioritized` escape-hatch list, so nothing is hidden.
94///
95/// Completeness invariant (the escape-hatch done-condition): the two lists
96/// partition the unit set, so `review_here.len() + deprioritized.len()` equals
97/// the total unit count by construction.
98#[derive(Debug, Clone, Default, Serialize)]
99#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
100pub struct FocusMap {
101 /// Units labeled `review-here`, ranked by composite score (descending), ties
102 /// broken by path for determinism.
103 pub review_here: Vec<FocusUnit>,
104 /// EVERY `not-prioritized` unit (the escape hatch). Always present and fully
105 /// enumerated so a reviewer can always "show me what you de-prioritized"; the
106 /// human brief collapses it by default and re-expands under
107 /// `--show-deprioritized`.
108 pub deprioritized: Vec<FocusUnit>,
109}
110
111impl FocusMap {
112 /// Total number of units.
113 #[must_use]
114 pub fn total_units(&self) -> usize {
115 self.review_here.len() + self.deprioritized.len()
116 }
117}