Skip to main content

devscout_rs/graph/compiler_facts/
reasons.rs

1// The closed refusal vocabulary `admit` and `run` return. Every variant
2// writes a stable, machine-readable token (never a formatted sentence) so a
3// caller can match on the string alone; a human-readable detail, when one
4// exists, is carried separately by the caller rather than folded into the
5// token itself. Two disjoint families: identity (a mismatch between the
6// candidate and what this checkout expects) and broken-run (a candidate that
7// cannot be trusted at all, whether killed before it produced one or
8// self-contradictory once parsed). A structurally valid artifact that
9// declares incomplete coverage is admitted, never refused -- see
10// `super::admit::Coverage`.
11
12use std::fmt;
13
14/// Why a candidate compiler-facts artifact was refused.
15///
16/// `Display` writes the exact token named in each variant's doc comment;
17/// nothing here is reworded once shipped, since callers match on it.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum RefusalReason {
20    // --- broken-run family: the candidate cannot be trusted at all -----
21    /// `engine-killed`: the local engine process exited with a non-zero or
22    /// signalled status, or could not be started at all.
23    EngineKilled,
24    /// `engine-timeout`: the local engine process did not finish inside the
25    /// configured wall-clock budget and was killed.
26    EngineTimeout,
27    /// `output-bounded`: the local engine process wrote more than the
28    /// configured output cap and was killed before finishing.
29    OutputBounded,
30    /// `malformed-encoding`: the candidate bytes do not parse as the
31    /// documented JSON envelope shape (invalid JSON, a non-object root, an
32    /// unrecognised `format`, or a header field of the wrong type).
33    MalformedEncoding,
34    /// `missing-completion-record`: the candidate has no explicit terminal
35    /// completion record, so a killed or truncated run cannot be told apart
36    /// from a genuinely finished one by parse success alone.
37    MissingCompletionRecord,
38    /// `incoherent-inventory`: the candidate's own declared per-unit
39    /// inventory contradicts itself (a unit id claimed both processed and
40    /// missing).
41    IncoherentInventory,
42    /// `occurrence-capability-mismatch`: the candidate's declared
43    /// `capabilities.provided` and its own occurrence payload disagree about
44    /// whether occurrence facts are present (key presence only -- an empty
45    /// `sites` array with the capability provided is fine).
46    OccurrenceCapabilityMismatch,
47    /// `occurrence-compilation-unknown`: an occurrence names a compilation
48    /// identity absent from the candidate's own embedded context envelope.
49    OccurrenceCompilationUnknown,
50    /// `occurrence-compilation-unsupported`: an occurrence names a
51    /// compilation whose matched envelope entry carries a `null`
52    /// fingerprint (the envelope's own "unsupported compilation" case).
53    OccurrenceCompilationUnsupported,
54
55    // --- identity family: a well-formed candidate that does not match ---
56    /// `producer-mismatch`: the candidate's declared producer name does not
57    /// match the producer this admission path accepts. Keeps the contract
58    /// additive and producer-tagged: a future producer format change (or
59    /// substitution) is refused here on its own token rather than falling
60    /// through to a less specific identity check.
61    ProducerMismatch,
62    /// `engine-revision-mismatch`: the candidate's producer engine revision
63    /// does not match the revision this checkout's admission path expects.
64    EngineRevisionMismatch,
65    /// `contract-version-mismatch`: the candidate's wire-contract version
66    /// does not match the version this admission path was built for.
67    ContractVersionMismatch,
68    /// `profile-mismatch`: the candidate's requested compilation profile
69    /// (target, configuration, platform) does not match the profile that
70    /// was requested of it.
71    ProfileMismatch,
72    /// `dependency-fingerprint-mismatch`: the candidate's engine-dependency
73    /// fingerprint does not match the fingerprint this admission path
74    /// expects of the shipped engine build.
75    DependencyFingerprintMismatch,
76    /// `context-envelope-version-unrecognised`: the candidate embeds a
77    /// compilation-context envelope version this admission path does not
78    /// recognise.
79    ContextEnvelopeVersionUnrecognised,
80    /// `context-fingerprint-mismatch`: the candidate's header-level context
81    /// fingerprint summary does not match the fingerprint carried inside
82    /// its own embedded context envelope -- a self-consistency check,
83    /// distinct from `DependencyFingerprintMismatch`, which is about the
84    /// engine's own build rather than the compilation it analysed. Fails
85    /// closed: a candidate missing either fingerprint (the header summary,
86    /// the envelope's own, or both) is refused on this same token rather
87    /// than silently skipping the check, so a future context-envelope
88    /// producer that omits one side cannot disable this identity class by
89    /// omission.
90    ContextFingerprintMismatch,
91    /// `source-snapshot-mismatch`: the candidate's declared source-snapshot
92    /// identity (`headSha`) does not match this checkout's own HEAD.
93    SourceSnapshotMismatch,
94}
95
96impl RefusalReason {
97    /// The stable machine-readable token, as an existing precedent expects:
98    /// see `graph::imports::parse_imported_edges`'s own "message naming the
99    /// offending value" convention, applied here as a fixed token first.
100    pub const fn token(self) -> &'static str {
101        match self {
102            RefusalReason::EngineKilled => "engine-killed",
103            RefusalReason::EngineTimeout => "engine-timeout",
104            RefusalReason::OutputBounded => "output-bounded",
105            RefusalReason::MalformedEncoding => "malformed-encoding",
106            RefusalReason::MissingCompletionRecord => "missing-completion-record",
107            RefusalReason::IncoherentInventory => "incoherent-inventory",
108            RefusalReason::ProducerMismatch => "producer-mismatch",
109            RefusalReason::EngineRevisionMismatch => "engine-revision-mismatch",
110            RefusalReason::ContractVersionMismatch => "contract-version-mismatch",
111            RefusalReason::ProfileMismatch => "profile-mismatch",
112            RefusalReason::DependencyFingerprintMismatch => "dependency-fingerprint-mismatch",
113            RefusalReason::ContextEnvelopeVersionUnrecognised => {
114                "context-envelope-version-unrecognised"
115            }
116            RefusalReason::ContextFingerprintMismatch => "context-fingerprint-mismatch",
117            RefusalReason::SourceSnapshotMismatch => "source-snapshot-mismatch",
118            RefusalReason::OccurrenceCapabilityMismatch => "occurrence-capability-mismatch",
119            RefusalReason::OccurrenceCompilationUnknown => "occurrence-compilation-unknown",
120            RefusalReason::OccurrenceCompilationUnsupported => "occurrence-compilation-unsupported",
121        }
122    }
123}
124
125impl fmt::Display for RefusalReason {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.write_str(self.token())
128    }
129}