symfonic.agent.cutover.continuation_redemption_removed¶
continuation_redemption_removed ¶
What a redemption says when the body it routes into is no longer there (TA8.58).
TA8.57 rehearsed TA9.2's deletion and measured the two redemptions that survive
it badly. A pause the continuation router sends to legacy -- an engine-minted
HMAC token, or a deployment the migration window admitted -- reaches
self._legacy_continuation_impl and, on a tree where that body is gone, the
caller is handed::
AttributeError: 'SymfonicAgent' object has no attribute '_legacy_continuation_impl'
That is a capability loss wearing a stack trace. TA8.42 refused a silent
capability loss for ask_user_enabled; an internal attribute error leaking to
an adopter is the same defect, and the window case is worse in kind, because the
framework has already told that adopter -- in a ContinuationWindowWarning it
emitted on the way in -- that the redemption was admitted.
This module is the answer that replaces the AttributeError. It is the
refusal TA9.2 raises where the fallback branch used to call the deleted body,
and it is shipped ahead of the deletion so the wording, the class and the
distinguishability can be driven before anything is removed. What the refusal
says belongs to
:mod:~symfonic.agent.cutover.continuation_removal_refusals; this module owns
the failure itself.
It is a fourth answer, and it stays apart from the other three. HK2 made an
expired token, a missing checkpoint and a refused axis three separate answers so
a caller could decide between re-asking and escalating. A removed redemption
path is none of those: the token may be perfectly live, the checkpoint may be
sitting in the store, and no axis was ever checked because nothing was redeemed.
The class is a :class:~symfonic.services.shadow.ports.CutoverPathError -- the
retirement vocabulary, catchable by an adopter's existing except around the
agent API -- and deliberately not a
:class:~symfonic.capabilities.human.errors.HumanInteractionError, so the
separation holds by isinstance and not only by prose.
This module raises nothing on its own. No dispatch site calls it yet;
wiring it is TA9.2's, at the same moment it removes the body. TA8.58 moves no
release line, claims no RET row and moves no gate. The conflict that produces
this failure is characterised, with its three exits, in
RET-PREP/window-retirement-conflict.md.
ContinuationRedemptionRemovedError ¶
ContinuationRedemptionRemovedError(*, entry_point: str, token_fingerprint: str, mint: str, setting: str | None = None, admitted_under_window: bool = False, message: str | None = None)
Bases: CutoverPathError
The route this pause takes has no body on this release.
Ancestry is :class:~symfonic.services.shadow.ports.CutoverPathError, the
same one
:class:~symfonic.agent.cutover.config_retirement.RetiredConfigurationError
carries, for the same reason: an adopter's existing except around the
agent API keeps catching it. A widening, never a rename -- and never a
HumanInteractionError, which is the family HK2's three answers live in.
Every attribute is a fact a handler would otherwise have to parse out of prose, and the two line attributes are read from the release constants so the exception and the message cannot disagree with them.
Source code in src/symfonic/agent/cutover/continuation_redemption_removed.py
refuse_removed_redemption ¶
refuse_removed_redemption(*, entry_point: str, pause_token: str, capability: Any = None, setting: str | None = None, admitted_under_window: bool = False) -> NoReturn
Refuse a redemption whose body is gone, naming all four things.
The provenance is read through
:func:~symfonic.agent.cutover.continuation_window.mint_provenance, the
same reader the window's own decision uses, so "what your token was" in this
message and "what the window thought your token was" cannot be two answers.
Source code in src/symfonic/agent/cutover/continuation_redemption_removed.py
removed_redemption
async
¶
removed_redemption(*, entry_point: str, pause_token: str, capability: Any = None, setting: str | None = None, admitted_under_window: bool = False) -> AsyncIterator[Any]
The refusal in the shape the dispatch site expects: an event stream.
Both continuation doors build a body and then iterate it inside
contextlib.aclosing. A drop-in replacement for the deleted call has to
be closeable and has to raise from the iteration, so the failure arrives
where the caller is already reading events rather than from an expression
the caller never sees. It yields nothing before it raises: no partial
answer, no half-written session.