Skip to content

symfonic.kernel.contracts.run_preclaim

run_preclaim

Admit a run's lineage before a durable effect made ahead of its start (CORE-194/199).

A caller that spends something durable before the kernel issues its run -- the private receipt carrier claims its invocation first -- would otherwise learn only at the run's start that its lineage refuses, with the claim already spent. :func:admit_before_start asks the run start's own questions first: the lineage is resolved here as _issue_run_identity will resolve it, from the same task, and nothing is registered. The run's start still proves it again; this narrows the window, it does not replace that proof. The admitted lineage is returned for the caller to hold until its run issues: it holds the runs it descends from strongly, where _ISSUED holds them only weakly.

admit_before_start async

admit_before_start(run_id: str, root_run_id: str, parent_run_id: str | None) -> RunLineage

Refuse now what a run of these ids, started from here, would refuse at its start.

In prove_inherited's order: the declared provers prove the ancestry first, and a prover's refusal propagates as raised (the caller decides what of it to expose); only then does an unresolved lineage refuse with the kernel's own UNPROVABLE_LINEAGE. An empty run_id stands for the fresh id the run's context will mint.

Returns the admitted lineage. A caller that spends something durable before its run issues keeps it alive until then, so a named run whose last other reference is released meanwhile still resolves at the run's start.

Source code in src/symfonic/kernel/contracts/run_preclaim.py
async def admit_before_start(
    run_id: str, root_run_id: str, parent_run_id: str | None
) -> RunLineage:
    """Refuse now what a run of these ids, started from here, would refuse at its start.

    In ``prove_inherited``'s order: the declared provers prove the ancestry
    first, and a prover's refusal propagates as raised (the caller decides what
    of it to expose); only then does an unresolved lineage refuse with the
    kernel's own ``UNPROVABLE_LINEAGE``. An empty ``run_id`` stands for the
    fresh id the run's context will mint.

    Returns the admitted lineage. A caller that spends something durable
    before its run issues keeps it alive until then, so a named run whose last
    other reference is released meanwhile still resolves at the run's start.
    """
    from symfonic.kernel.contracts.run_admission import (
        UNPROVABLE_LINEAGE,
        prove_ancestry_of,
    )

    own = run_id or uuid.uuid4().hex
    lineage = prospective_lineage(RunIdentity(own, root_run_id or own, parent_run_id))
    await prove_ancestry_of(lineage)
    if lineage.unresolved:
        from symfonic.core.contracts.errors import ContractViolationError

        raise ContractViolationError(UNPROVABLE_LINEAGE)
    return lineage

prospective_lineage

prospective_lineage(identity: RunIdentity) -> RunLineage

The lineage identity would be issued now, built but not registered.

Mirrors run_identity._issue_run_identity: the run descends from the run this context executes in (or the task's issuing or spawning run) and from every issued run it names; it is unresolved when it names one it cannot reach. Re-issuing the running run's own id keeps that run's lineage.

Source code in src/symfonic/kernel/contracts/run_preclaim.py
def prospective_lineage(identity: RunIdentity) -> RunLineage:
    """The lineage ``identity`` would be issued now, built but not registered.

    Mirrors ``run_identity._issue_run_identity``: the run descends from the run
    this context executes in (or the task's issuing or spawning run) and from
    every issued run it names; it is unresolved when it names one it cannot
    reach. Re-issuing the running run's own id keeps that run's lineage.
    """
    from symfonic.kernel.contracts.run_identity import (
        _ISSUED,
        _LINEAGE,
        _names_unknown,
        _running_loop,
    )
    from symfonic.kernel.contracts.run_spawn import (
        issuing_lineage,
        spawned_lineage,
        track_spawns,
    )

    track_spawns()
    current = _LINEAGE.get() or issuing_lineage() or spawned_lineage()
    if current is not None and current.run_id == identity.run_id:
        return current
    parents = [] if current is None else [current]
    for run_id in (identity.parent_run_id, identity.root_run_id):
        named = _ISSUED.get(run_id) if run_id else None
        if (
            named is not None
            and named.run_id != identity.run_id
            and all(named is not known for known in parents)
        ):
            parents.append(named)
    return RunLineage(
        identity.run_id,
        tuple(parents),
        unresolved=_names_unknown(identity, parents),
        loop=_running_loop(),
    )