Skip to content

symfonic.services.shadow.errors

errors

The shadow / record-replay error taxonomy.

Each class answers a distinct operational question, because each leads to a different response: "did the run touch a port nobody classified?" (stop shipping shadow evidence until it is classified), "did it reach an opaque extension?" (this capability needs the non-shadow cutover path), "did an extension declared port-mediated escape its ports?" (the trust declaration was wrong and the suppression claim is void), "was this invocation ineligible for capture?" (proceed unrecorded), "would this comparison duplicate a real effect?" (refuse the comparison outright).

A caller that had to parse a message to tell those apart would eventually parse it wrong, so they are types.

CaptureRefusedError

Bases: ShadowError

Safe capture could not be established; the invocation is unrecorded.

Recording fails closed. Callers catch this and continue serving the tenant's request — never the other way round.

ComparisonUnsafeError

Bases: ShadowError

TM-29d — this comparison would duplicate an externally visible effect.

CutoverPathError

Bases: ConfigurationError

Cutover evidence was filed under a path the capability may not use.

IncompleteClassificationError

Bases: ConfigurationError

An effect family the architecture declares has no classified port.

MisdeclaredExtensionError

Bases: ShadowAbortedError

An extension declared port-mediated performed a direct effect.

The declaration was wrong; every suppression claim that depended on it is withheld, and the extension is demoted to opaque.

OpaqueExtensionError

Bases: ShadowAbortedError

An opaque tool, plugin, or contributed stage is non-shadowable.

Raised before the extension runs. Pretending an opaque extension's effects were suppressed is the failure mode this type exists to prevent.

RecordingAccessError

Bases: ShadowError

TM-29a — an actor read (or tried to read) beyond its authorization.

RecordingStoreError

Bases: ShadowError

The recording store refused a write it cannot govern.

ShadowAbortedError

Bases: ShadowError

The shadow run stopped before (or instead of) performing an effect.

Raised on its own when a run is aborted for a reason that has no more specific type; the fail-closed subclasses below are the usual cause.

ShadowEffectDenied

Bases: ShadowError

A classified DENY port refused an effect inside a shadow run.

Not an abort: denial is the port doing its job. The caller decides whether the denial is fatal to the scenario it was exercising.

ShadowError

Bases: SymfonicError

Root of the shadow/replay taxonomy. Never raised directly.

TrustDeclarationError

Bases: ConfigurationError

A trust-class assignment does not meet the port-mediated bar.

A ConfigurationError on purpose: a self-declared or unreviewed port-mediated claim is a misconfiguration of the evidence pipeline, and adopters already catch that taxonomy.

UnclassifiedEffectError

Bases: ShadowAbortedError

SEC-FCP-5 — an effect was attempted through a port nobody classified.

Fail-closed by construction: an unclassified port is not "probably safe", it is a hole in the evidence, and the run that found it is void.

UndigestibleValueError

Bases: ShadowError

A value's identity cannot be established, so no digest is produced.

The digest is the comparator's only notion of "same request". A value the canonicaliser cannot see inside would digest identically to every other instance of its type, and the comparator would read that collision as parity. Refusing is the fail-closed answer: an unrecorded or aborted comparison is recoverable, a false parity claim is not.