symfonic.services.conversation.errors¶
errors ¶
The conversation-service error taxonomy.
One root (ConversationServiceError) so an adopter can catch the whole
package, and one leaf per decision a caller can actually act on differently.
The two that matter most are deliberately not merged:
- :class:
UnsafeBoundaryError— "we will not translate this state" — is a permanent property of the artifact, and - :class:
ResumabilityHorizonExpiredError— "we no longer support this format" — is a property of the support window, and carries the route to a human.
Collapsing them would tell an adopter with recoverable state to open a support ticket, and an adopter with expired state to keep retrying.
AmbiguousThreadKeyError ¶
Bases: SessionIdentityError
A thread key's tenant segment cannot be reconstructed unambiguously.
Raised in both directions, because the ambiguity is symmetric:
- forward — a
tenant_id/sub_tenant_idcarrying the thread-key separator is refused, so no new key can be minted that parses back as a different tenant, and - backward — a key read out of a backend whose tenant segment is not provably whole (more separators than the derivation puts there) is quarantined for attribution: it may still be parsed positionally and resumed, but it may not be used to name a tenant.
The migrated path never re-attributes such state. Pre-existing state
written by the legacy path under a separator-bearing tenant stays readable
at its key and stays quarantined for tenant attribution until an operator
records the owning tenant explicitly (CheckpointRef.tenant_id), which
is the one attribution this package treats as authoritative.
CheckpointFormatError ¶
Bases: ConversationServiceError
Persisted state does not match a format this package can vouch for.
Covers an envelope from a future format version, a corrupt envelope, and a reserved key already occupied by something else. Every one of those is a refusal, never a silent downgrade to "assume legacy".
ConversationServiceError ¶
Bases: SymfonicError
Root of the conversation/session/transcript/checkpoint taxonomy.
IssuanceFrozenError ¶
Bases: ConversationServiceError
New checkpoint issuance is frozen; only reads and replays remain.
The freeze is how a retirement drain reaches a fixed point: no new state of the retiring shape can appear while the existing state is migrated out.
So the drain itself is not what this refuses. Registering the output of a safe-boundary replay, and adopting rows read back out of the backend after a restart, both stay open while frozen — a freeze that closed them would guarantee that nothing could ever be drained. Raised while frozen only for genuinely new state, including a "migration" of a legacy ref the registry never accounted for, which is new state under another name.
ResumabilityHorizonExpiredError ¶
Bases: ConversationServiceError
The artifact is past its published support horizon.
Always carries the support route in its message: an expiry an adopter cannot act on is indistinguishable from a bug.
Source code in src/symfonic/services/conversation/errors.py
SessionIdentityError ¶
Bases: ConversationServiceError
A session identity could not be derived, parsed, or trusted.
Raised rather than defaulted: a guessed tenant is a cross-tenant read, and a guessed thread id silently forks one conversation into two.
TranscriptUnavailableError ¶
Bases: ConversationServiceError
The transcript cannot be served — no durable source, or no timestamps.
Distinct from "the transcript is empty", which is a legitimate answer.
UnsafeBoundaryError ¶
Bases: ConversationServiceError
The state is mid-frame and has no contract-tested safe boundary.
There is no translation path by design. Arbitrary mid-frame state carries node-local invariants nobody re-validated, so the only supported crossing is replay from a boundary that was tested as a boundary.