Skip to content

Three emitted StableErrorCode values are undocumented in the consumer wire contract — and the new drift guard does not cover this axis #5

Description

@iceteaSA

Verified at 5df8f8e. Same class as #2 (absence from the consumer contract), on a different axis, and the guard shipped in 96bfd39 does not reach it.

Result

StableErrorCode in crates/synapse-core/src/error_contract.rs declares 23 variants. Enumerating each against docs/wire-contract-v1.md:

20 documented. 3 absent:

code reachable? evidence
owned_cuda_unsupported yes crates/synapse-core/src/cuda.rs:41, :198, :207 — real capability-gating logic (11 refs)
op_not_supported_for_remote yes constructor at error_contract.rs:251; crates/synapse-module/tests/skeleton_e2e.rs:672 asserts the wire string, so it is emitted over the wire today
sentinel_calibration_refused yes constructor at error_contract.rs:260; emitted at crates/synapse-module/src/remote/gateway.rs:266 in remote provider calibration

I checked reachability specifically because a doc gap for a dead variant is not a gap. All three are live.

The doc's Errors section lists a fixed set — queue_full, deadline_exceeded, model_loading, not_certified, substitution_rejected, artifact_invalid, engine_crashed, probe_required, migration_required, module_restarted, grammar_disabled — and tells consumers to branch on class and handle codes by name. A consumer implementing that list exhaustively will meet three codes it has never seen.

(Side note: grammar_disabled appears in the doc but is not a StableErrorCode — zero references in synapse-core. It looks like a separate refusal vocabulary. Worth a line saying which vocabulary a reader is looking at, since the doc presents them together.)

Why this survived #2's fix

wire_contract_documents_every_management_operation (96bfd39) derives names from management_operations() and asserts each appears in the doc. That closes the operation-name axis mechanically, and I mutation-proved it works.

It does not touch error codes. StableErrorCode is not referenced anywhere in that test or its file. So the same document has a second enumerable axis with no guard on it, currently three gaps wide.

Suggested fix

Extend the existing guard rather than adding a parallel one — same shape, same file:

// alongside the operation-name loop
for code in StableErrorCode::ALL {           // or strum/iter equivalent
    assert!(CONTRACT.contains(&format!("`{}`", code.as_wire_str())),);
}

That requires a way to enumerate the variants, which the enum does not currently expose. If adding that is unwelcome, a hand-maintained sentinel array like the one already in that test would still be an improvement over nothing — it would at least fail when a documented code is deleted.

Why I went looking

This came out of the claustrum seam work on #3. The generalisable point, which I think is the useful part of this issue:

Testing what a doc says cannot reveal what it fails to say. That needs enumerating the surface against the doc — a different act, and one no consumer has a reason to perform.

A false statement in a contract is self-correcting under use: a consumer runs it, it disagrees with reality, it gets fixed. An omission generates no contradiction at all — AFT and MC integrate against what the doc shows and have no reason to enumerate what it does not. #2's sixteen missing operations survived a public release with two live consumers for exactly that reason.

Which is why the mechanical guard matters more here than usual: enumeration is the only thing that finds this class, and nobody performs it on a schedule. A test does it every run.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions