Skip to content

symfonic.kernel.contracts.continuation_attestation

continuation_attestation

The persisted proof that lets a memory-free root pause resume in a new process.

CORE-194 owner answer (2026-10-15): an Agent WITHOUT memory keeps its durable pause across a restart. A fresh process never issued the paused run, so the resumed run could not prove its lineage (run_admission.UNPROVABLE_LINEAGE). This module is the one authority that can close that gap, and only for a run the kernel itself judged, while it was live, to be:

  • the issued lineage of the paused run id, a resolved root with no parents;
  • stamped memory-free from its compiled plan, with no descendant that touched memory (RunLineage.memory is monotone and propagates upward);
  • holding no capture any declared witness reports (an extra refusal only).

Issuance has no public entry: the human capability's turn-state store calls the private _seal_minted right after it wrote the turn state itself, with the SDK-minted claims and the exact bytes it encoded. No caller-chosen mapping of ids or digests is accepted. :func:admit_root is the only code that registers a recovered root in the issued table, and only for a row whose MAC a :class:ContinuationAttestorPort verifies and whose every bound field equals what the host expected. It returns an opaque :class:RootAdmission the host holds until the resumed turn ends.

Trust model. Issuance happens once per pause, inside capability.pause, for the claims it just minted (a fresh jti from uuid4) and the turn text it just encoded, and only while the kernel judges the live run a memory-free root. So a public capability.pause called inside such a run legitimately creates NEW authority for the state it is given -- a new token, never an existing one. No public surface re-authorizes an existing pause: TurnCheckpointStore.record writes and never seals; PauseBinding, current_run_binding, reconstructed or reused claims (a namespace, a real PauseClaims with equal fields, or the genuine object itself), stored rows, Keyset.sign, EnvelopeSigner.mint and a checkpoint adapter seeing only what it is asked to write cannot yield an accepted proof; outside a live memory-free root, or with memory, nothing issues. Excluded, and trusted as the deployment: a holder of the raw key secret, a deployment-supplied attestor (see :class:ContinuationAttestorPort), and any code that calls underscore names or rewrites SDK objects. Python offers no sandbox against the last; this module does not claim one.

ContinuationAttestorPort

Bases: ABC

Computes the attestation MAC: deployment authority, like a signer.

The SDK implementation is KeysetContinuationAttestor, offered by EnvelopeSigner.continuation_attestor(). A deployment that injects its own signer may offer its own attestor; it then carries the same authority as that signer's pause-token signatures, and a weak one weakens its proofs as a weak signer weakens its tokens. A signer offering none gets no durable NoMemory resume (the fresh-process refusal). The hooks are private: a public method that MACs a caller's body would be a minting oracle.

RootAdmission

RootAdmission(node: Any, guard: object)

A recovered root the kernel admitted; hold it while the resumed run lives.

Source code in src/symfonic/kernel/contracts/continuation_attestation.py
def __init__(self, node: Any, guard: object) -> None:
    if guard is not _GUARD:
        raise TypeError("a RootAdmission is issued by admit_root only")
    self._node, self.run_id = node, node.run_id

admit_root

admit_root(attestor: Any, row: Any, expected: Mapping[str, Any]) -> RootAdmission

Admit the root expected names, from a verified row; refuse otherwise.

Source code in src/symfonic/kernel/contracts/continuation_attestation.py
def admit_root(attestor: Any, row: Any, expected: Mapping[str, Any]) -> RootAdmission:
    """Admit the root ``expected`` names, from a verified row; refuse otherwise."""
    from symfonic.core.contracts.errors import ContractViolationError
    from symfonic.kernel.contracts.run_admission import UNPROVABLE_LINEAGE

    if not _verifies(attestor, row, expected):
        raise ContractViolationError(UNPROVABLE_LINEAGE)
    from symfonic.kernel.contracts.run_identity import _ISSUED, RunLineage, _running_loop

    run_id = expected["run_id"]
    node = _ISSUED.get(run_id)
    if node is None:
        node = RunLineage(run_id, (), loop=_running_loop())
        node.memory = False  # the verified proof's judgement, made while it ran
        _ISSUED[run_id] = node
    elif node.unresolved or node.parents or node.memory is not False:
        raise ContractViolationError(UNPROVABLE_LINEAGE)
    return RootAdmission(node, _GUARD)

turn_digest

turn_digest(encoded: str) -> str

The digest a proof binds: the exact encoded turn state the SDK wrote.

Source code in src/symfonic/kernel/contracts/continuation_attestation.py
def turn_digest(encoded: str) -> str:
    """The digest a proof binds: the exact encoded turn state the SDK wrote."""
    return hashlib.sha256(encoded.encode("utf-8")).hexdigest()