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}