Skip to content

symfonic.services.switching.values

values

The values a switch decision is made of (CUT-SS, CUT-RB, CUT-AIR).

Everything here is a frozen, hashable value. That is deliberate: a generation vector that could be mutated after an invocation captured it would make CUT-SS-4's "one invocation observes exactly one epoch" unenforceable by anything stronger than convention.

BundleBinding dataclass

BundleBinding(bundle_id: str, epoch: int, generation_vector: GenerationVector, freeze_state: FreezeState = NO_FREEZE, source: str = 'release-static', fetched_at: float = 0.0, stale: bool = False)

DMC-1 — what a BindingSource answers with.

source is one of release-static, control-plane or local-override (CUT-PIN-3), which is what makes "where did this binding come from?" answerable in a diagnostic without guessing.

FreezeState dataclass

FreezeState(freeze_epoch_id: str | None = None, frozen_legacy_vector: GenerationVector | None = None, retiring: frozenset[str] = frozenset(), created_by: str | None = None, approved_by: str | None = None, created_at: float = 0.0)

SCP-FRZ-1 — the retirement-freeze epoch object, or its absence.

retiring is the set of generation ids the freeze disables for new admissions. It is computed once, when the freeze is committed, so a later vector change cannot silently widen or narrow what the freeze barred.

bars

bars(vector: GenerationVector) -> frozenset[str]

The retiring generations this vector would newly admit work onto.

Source code in src/symfonic/services/switching/values.py
def bars(self, vector: GenerationVector) -> frozenset[str]:
    """The retiring generations this vector would newly admit work onto."""
    if not self.active:
        return frozenset()
    return self.retiring & vector.generations()

GenerationVector dataclass

GenerationVector(entries: tuple[tuple[str, str], ...] = ())

CUT-RB-3 — {capability_or_port → generation_id}, canonically ordered.

Stored as a sorted tuple rather than a mapping so two vectors built from differently-ordered dicts are the same value, compare equal, and hash to the same vector_hash. Ordering is the whole reason the hash is stable enough to travel inside a checkpoint envelope.

vector_hash property

vector_hash: str

A stable digest of the whole vector (ENV-1 generation_vector_hash).

generations

generations() -> frozenset[str]

The generation ids this vector selects, without their port names.

Source code in src/symfonic/services/switching/values.py
def generations(self) -> frozenset[str]:
    """The generation ids this vector selects, without their port names."""
    return frozenset(generation for _, generation in self.entries)

InvocationPin dataclass

InvocationPin(bundle_id: str, epoch: int, vector_hash: str, source: str, stale_binding: bool = False, freeze_epoch_id: str | None = None)

The travelling half of a binding: what a checkpoint or token carries.

A pin names an epoch and a vector hash, never the vector's contents. A resuming worker resolves the contents from its own binding source and refuses if they disagree, so a pin cannot smuggle an unvalidated vector across a process boundary.

token

token() -> str

The compact form recorded on a RequestContext (RCX-5).

Source code in src/symfonic/services/switching/values.py
def token(self) -> str:
    """The compact form recorded on a ``RequestContext`` (RCX-5)."""
    return f"{self.bundle_id}@{self.epoch}:{self.vector_hash}"