Skip to content

symfonic.agent.cutover.continuation_removal_refusals

continuation_removal_refusals

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]}"