102 codes.
No bare errors.
An error is not a verdict. It means no verdict was produced — so every code carries a class, an exit code, the exact string a user will see, and a recovery path. A code that is not documented may not exist; a test fails the build if one does.
| Class | Behaviour | Exit | Appears in the verdict? |
|---|---|---|---|
| Fatal | Stop. No verdict is produced. | 2 / 3 / 4 | No — the process exits |
| Degrade | Continue; record an undetermined[] or a warning | 0 (5 with --strict) | Yes — visibly |
| Warn | Continue; record a notes[] entry | 0 | Yes — visibly |
| Internal | Stop; print a reportable message, not a stack trace | 4 | No |
Configuration · 13 codes · exit 2
Every config error is fatal, because no intent means no answer. The tool refuses to guess — even helpfully.
Scanner / filesystem · 21 codes
Only four of twenty-one are fatal, and each fatal case is "the tool genuinely cannot proceed". Skipping a hostile symlink is correct behaviour, not a failure — so it is a warning.
Parsing · 10 codes · all degrade except two
Refusing a hostile file and continuing is the right behaviour — the scan of the rest of the tree is still valid. Where a file is an attack pattern, the message says so, because a user needs to know that a weird lockfile is a security signal and not a typo.
Corpus · 12 codes · exit 3
The supply-chain guards. Both signature failures are fatal and both state that the previous corpus is unchanged — the user is never left with no corpus at all.
Policy / verdict · 10 codes
The pattern is stark: only two of these can happen at runtime. Every other policy error is a corpus bug that a release guard should have caught before shipping.
The last twenty-one
Three domains with one idea each: a renderer failure is always a bug, a network failure is never fatal, and an internal code exists so a bug produces a triageable message instead of a stack trace.
The fifteen that only exist once the network is armed
Both domains are off by default. No AI call is possible until --ai arms it, and the MCP server runs only when you start it. Every code in these two domains marks a boundary being tested, not a routine outcome.
Of the 102, three carry the product's integrity
If these three behave correctly, the product is safe to ship even if the other ninety-nine codes are imperfect.
Unexpected outbound call
If this ever fires in the wild, the privacy promise is broken. It is the runtime tripwire for INV-3 and it should never fire in a correct build.
Invalid corpus signature
If this fires, someone tried to poison the corpus. The refusal is the moat working — and the previous corpus is left untouched.
No corpus entry for a licence
The only routine runtime condition, and it produces UNDETERMINED rather than a guess. This is INV-7 in practice.
| Rule | Enforcement |
|---|---|
| Every code has a constant in internal/cerr | TestEveryErrorCodeIsDocumented |
| Every code appears in this file | the same test, reversed |
| No errors.New or fmt.Errorf without a code | custom linter |
| Codes are never renumbered or reused | a frozen range per domain |
| Every DEGRADE code has a fixture that triggers it | TestEveryDegradeCodeHasFixture |
| No unexpected egress, ever | TestNoUnexpectedEgress |