Skip to content

symfonic.capabilities.human.continuation_proof

continuation_proof

Persist and check the memory-free root proof beside a pause (CORE-194).

The authority is the kernel's continuation_attestation: it judges whether the live run qualifies and it alone admits a verified proof. This module only moves rows. The private _seal_written is called only by the private TurnCheckpointStore._record_issued, which only capability.pause calls, right after it wrote the turn state of the pause it just minted: the kernel seals those claims and the exact text that store encoded. The public TurnCheckpointStore.record writes and never seals. The host calls :func:admit_continuation last before the claim: it reads the turn row once, has the kernel verify the proof over that row's digest, and returns the decoded checkpoint the resume must use instead of reading again.

A pause whose ids are still live here keeps today's path once the resumed run's lineage over them is admitted before the claim. Everything else -- no attestor (a custom signer without one, a Keyset subclass), a resuming composition that reaches memory, an absent, corrupt, altered or rebound proof -- refuses typed before the claim, and the token stays unspent.

admit_continuation async

admit_continuation(capability: Any, claims: Any, agent: Any) -> tuple[Any, Any]

(admission, turn): (lineage, None) for live ids, else the verified pair.

Live ids are not proof by being registered: the resumed run's lineage over them is admitted as its start would admit it -- every declared prover over the named ancestry, then an unresolved lineage refuses -- and returned for the host to hold through the claim and the run. Raises the kernel's UNPROVABLE_LINEAGE (typed) when nothing proves the paused run, a prover's own refusal as raised, and the turn store's own errors when its row is missing.

Source code in src/symfonic/capabilities/human/continuation_proof.py
async def admit_continuation(capability: Any, claims: Any, agent: Any) -> tuple[Any, Any]:
    """``(admission, turn)``: ``(lineage, None)`` for live ids, else the verified pair.

    Live ids are not proof by being registered: the resumed run's lineage over
    them is admitted as its start would admit it -- every declared prover over
    the named ancestry, then an unresolved lineage refuses -- and returned for
    the host to hold through the claim and the run. Raises the kernel's
    ``UNPROVABLE_LINEAGE`` (typed) when nothing proves the paused run, a
    prover's own refusal as raised, and the turn store's own errors when its
    row is missing.
    """
    from symfonic.kernel.contracts.continuation_attestation import admit_root, turn_digest
    from symfonic.kernel.contracts.run_admission import refuse_unprovable, unprovable

    named = (claims.run_id, claims.root_run_id)
    if not unprovable(*named):
        from symfonic.kernel.contracts.run_preclaim import admit_before_start

        # The resumed run descends from the paused run and its root.
        return await admit_before_start("", claims.root_run_id, claims.run_id), None
    from symfonic.capabilities.human.turnstate import TurnCheckpointStore

    # Only the SDK's own turn store can hold a proof; a structural capability
    # without one is unprovable here, typed and before the claim.
    turns = getattr(capability, "turns", None)
    if not isinstance(turns, TurnCheckpointStore):
        refuse_unprovable(*named)
    attestor = turns._attestor  # noqa: SLF001 - this package's own store
    if attestor is None or resuming_memory(agent):
        refuse_unprovable(*named)
    raw = await turns.read_raw(claims)  # read once; the resume uses these bytes
    if not isinstance(raw, str):
        refuse_unprovable(*named)
    row = await _read_proof(turns, claims)
    admission = admit_root(attestor, row, _fields(claims, turn_digest(raw)))
    return admission, turns.decode(claims, raw)

resuming_memory

resuming_memory(agent: Any) -> bool

Whether the resuming agent's authority reaches memory, or cannot be read.

Through the agent's declared seams: authorized_effects (the grants its invocations run under) and composition_manifest (each folded stage's declared effects). Anything missing or unreadable counts as memory.

Source code in src/symfonic/capabilities/human/continuation_proof.py
def resuming_memory(agent: Any) -> bool:
    """Whether the resuming agent's authority reaches memory, or cannot be read.

    Through the agent's declared seams: ``authorized_effects`` (the grants its
    invocations run under) and ``composition_manifest`` (each folded stage's
    declared effects). Anything missing or unreadable counts as memory.
    """
    from symfonic.kernel.contracts.run_admission import memory_in

    try:
        effects = frozenset(agent.authorized_effects)
        stages = agent.composition_manifest["stages"]
        declared = {str(effect) for row in stages for effect in row[4]}
    except Exception:  # noqa: BLE001 - unreadable authority is not memory-free
        return True
    return memory_in(effects | declared, ())