What a removed redemption path says to the adopter reading it (TA8.58).
Split from :mod:~symfonic.agent.cutover.continuation_redemption_removed the
way continuation_window_refusals is split from continuation_window: that
module owns the failure -- its class, its attributes, the shape the dispatch
site needs -- and this one owns what the failure says. One question each.
Four things, because an actionable failure is not one sentence. A caller who
is told "cannot resume" and nothing else files a bug; a caller told a private
attribute is missing files a worse one. So every message here carries: that the
redemption path was removed and the pause is not at fault; on which line;
which pause, named without being quoted; and what to do now, including the fact
that nothing was consumed.
Every release line is read, never spelled. :data:LINES_DISAGREE exists
only while the two constants actually differ, so settling that conflict by
moving either one deletes the sentence rather than leaving a stale claim inside
an adopter's error.
describe_mint
describe_mint(provenance: MintProvenance) -> str
When this pause says it was minted, in a phrase an adopter can act on.
Three cases and three phrasings, for the reason
:func:~symfonic.agent.cutover.continuation_window_refusals.why gives for
its own: an adopter told the wrong thing about their token's provenance goes
looking in the wrong place.
Source code in src/symfonic/agent/cutover/continuation_removal_refusals.py
| def describe_mint(provenance: MintProvenance) -> str:
"""When this pause says it was minted, in a phrase an adopter can act on.
Three cases and three phrasings, for the reason
:func:`~symfonic.agent.cutover.continuation_window_refusals.why` gives for
its own: an adopter told the wrong thing about their token's provenance goes
looking in the wrong place.
"""
if provenance.kind == FOREIGN_MINT:
return (
"not minted as a capability envelope by this deployment's transport, "
"which is what an engine-minted (legacy HMAC) pause looks like from here"
)
if provenance.kind == UNREADABLE_MINT:
return "presented to a transport that could not read a mint time from it"
if not provenance.issued_at:
return "carrying no issued_at claim"
minted = datetime.fromtimestamp(provenance.issued_at, tz=UTC).isoformat()
return f"minted at {minted}"
|
removal_message
removal_message(*, entry_point: str, fingerprint: str, mint: str, setting: str | None, admitted_under_window: bool) -> str
The whole message, assembled in the order an adopter reads it.
What happened, on which line, which token, and what to do -- and the
separation paragraph last, because it is the sentence somebody returns to
after they have already tried the other three explanations.
Source code in src/symfonic/agent/cutover/continuation_removal_refusals.py
| def removal_message(
*,
entry_point: str,
fingerprint: str,
mint: str,
setting: str | None,
admitted_under_window: bool,
) -> str:
"""The whole message, assembled in the order an adopter reads it.
What happened, on which line, which token, and what to do -- and the
separation paragraph last, because it is the sentence somebody returns to
after they have already tried the other three explanations.
"""
horizon = datetime.fromtimestamp(WINDOW_CLOSES_AT, tz=UTC).isoformat()
parts = [
f"{entry_point}() cannot serve this redemption: the legacy continuation "
f"body it routes to was removed on the {LEVER_RETIREMENT_LINE} line. "
"Your pause is not invalid and this build has not lost it -- there is no "
"longer any code on this release that can continue it.",
f"The pause: {fingerprint}, {mint}.",
]
if admitted_under_window and setting is not None:
parts.append(
f"This redemption was admitted by the migration window for pauses "
f"minted before the continuation guards landed, on account of "
f"{setting}, and announced as admitted before it reached the body "
f"that is gone. The admission was wrong on this release: the window "
f"closes at {horizon}, and the body it admits into was already "
f"removed on the {LEVER_RETIREMENT_LINE} line."
)
elif setting is not None:
parts.append(
f"The configuration this redemption carries ({setting}) is retired "
f"on the {LEVER_RETIREMENT_LINE} line, so no route on this release "
"would have served it either."
)
# Only the window-admitted holder is owed this paragraph. A pause routed to
# legacy by recognition alone was never promised anything by the window, and
# a sentence about a contract conflict they are not inside would read as a
# second failure.
if admitted_under_window and LEVER_RETIREMENT_LINE != WINDOW_RELEASE_LINE:
parts.append(LINES_DISAGREE)
parts.append(
"What to do now: nothing was consumed -- the token was not redeemed, the "
"recorded turn state was not read and was not deleted, and no side "
"effect of the paused turn ran a second time. Ask the person your "
"question again on a fresh turn; a pause held across this upgrade has to "
"be redeemed on the release that minted it, before you upgrade, or "
"re-asked after."
)
parts.append(SEPARATE_FROM_THE_OTHER_THREE)
return " ".join(parts)
|
token_fingerprint
token_fingerprint(pause_token: str) -> str
A stable, short identity for a token, without quoting the token.
A pause token is a bearer credential; a refusal message travels into logs,
tickets and screenshots. The adopter still has to be able to say which
pause this was, and a support engineer has to be able to match it against
one the adopter holds -- a truncated digest does both and reveals nothing
that can be presented.
Source code in src/symfonic/agent/cutover/continuation_removal_refusals.py
| def token_fingerprint(pause_token: str) -> str:
"""A stable, short identity for a token, without quoting the token.
A pause token is a bearer credential; a refusal message travels into logs,
tickets and screenshots. The adopter still has to be able to say *which*
pause this was, and a support engineer has to be able to match it against
one the adopter holds -- a truncated digest does both and reveals nothing
that can be presented.
"""
digest = hashlib.sha256(pause_token.encode("utf-8", "replace")).hexdigest()
return f"sha256:{digest[:12]}"
|