Skip to content

symfonic.services.switching.ports

ports

DMC-1 — the one binding port, declared once (CON-P, CON-S-3).

BindingSource is the entire seam between "which generation governs this invocation?" and the machinery that answers it. The facade compiler and the admission controller consume this protocol; neither ever learns whether a control plane or a compiled-in static vector is behind it, which is what makes DMC-8's "no third mode" enforceable — a new deployment shape is a new implementation of this protocol or it is an ADR.

TA8.54 declares the second name here, and it is the same seam asked in the other direction: :func:~symfonic.services.switching.invocation_profile.resolve_legacy_pin answers "which generation governs this build?" with a frozen value object and tells the caller nothing about how it was decided. It is re-exported from this module rather than imported directly because dependency-matrix.md gives facade-compiler -> runtime-service the cell port, and symfonic.agent.cutover.legacy_pin — the composition root that builds the switchboard's pin — sits on the facade side. A narrow function over value objects is what that cell licenses; reaching into invocation_profile directly is what it forbids.

BindingSource

Bases: Protocol

Resolve a route bundle's binding. Async-first, read-only, fail-closed.

describe

describe() -> str

CUT-PIN-3 — the effective vector and its source, never credentials.

Source code in src/symfonic/services/switching/ports.py
def describe(self) -> str:
    """CUT-PIN-3 — the effective vector and its source, never credentials."""
    ...

resolve async

resolve(bundle_id: str) -> BundleBinding

The current binding snapshot, or a denial. Never a default.

Source code in src/symfonic/services/switching/ports.py
async def resolve(self, bundle_id: str) -> BundleBinding:
    """The current binding snapshot, or a denial. Never a default."""
    ...

resolve_epoch async

resolve_epoch(bundle_id: str, epoch: int) -> BundleBinding

CUT-SS-7 — the binding an earlier admission ran under.

Resume paths need it: a pin names an epoch, and placing the resumed work back on that epoch's vector is the only way a switch that landed while the work was paused does not silently follow it.

Source code in src/symfonic/services/switching/ports.py
async def resolve_epoch(self, bundle_id: str, epoch: int) -> BundleBinding:
    """CUT-SS-7 — the binding an earlier admission ran under.

    Resume paths need it: a pin names an epoch, and placing the resumed
    work back on that epoch's vector is the only way a switch that landed
    while the work was paused does not silently follow it.
    """
    ...

LegacyPin dataclass

LegacyPin(capabilities: frozenset[str] = frozenset(), package_version: str = '', vector_hash: str = GenerationVector().vector_hash, source: str = 'release-static', binding: str = 'binding[static,unbuilt] (no profile was constructed)')

Which capabilities a build holds on their legacy bodies, and on whose say.

source is StaticBindingSource's own CUT-PIN-3 vocabulary: release-static when the line's published contract is what put a capability on legacy, local-override when the adopter pinned it. The two are not interchangeable and an operator reading describe() at 03:00 is owed the difference.

resolve_legacy_pin

resolve_legacy_pin(capabilities: Sequence[str] | None, *, package_version: str, legacy_defaults: frozenset[str] = frozenset()) -> LegacyPin

Build the release profile and the binding source, and read the pin back.

This is the construction site. Both objects are built here, on every call, including the call an unpinned build makes — a construction reachable only when somebody opts in would be the original finding wearing a number.

Refusals propagate rather than degrade, and each keeps its own name:

  • an empty or unrecognised pin — :class:ConfigurationError from :func:legacy_pin_vector;
  • a pin on a release at or past FIRST_REJECTING_RETIREMENT_VERSIONLegacyPinRetiredError from StaticBindingSource._apply_override, carrying the migration procedure;
  • an undocumented combination, or one the constraint set rejects — the ConfigurationError / ConstraintViolationError that source already raises.

Nothing here answers "unpinned" to a bad pin.

Source code in src/symfonic/services/switching/invocation_profile.py
def resolve_legacy_pin(
    capabilities: Sequence[str] | None,
    *,
    package_version: str,
    legacy_defaults: frozenset[str] = frozenset(),
) -> LegacyPin:
    """Build the release profile and the binding source, and read the pin back.

    **This is the construction site.** Both objects are built here, on every
    call, including the call an unpinned build makes — a construction reachable
    only when somebody opts in would be the original finding wearing a number.

    Refusals propagate rather than degrade, and each keeps its own name:

    * an empty or unrecognised pin — :class:`ConfigurationError` from
      :func:`legacy_pin_vector`;
    * a pin on a release at or past
      ``FIRST_REJECTING_RETIREMENT_VERSION`` — ``LegacyPinRetiredError`` from
      ``StaticBindingSource._apply_override``, carrying the migration
      procedure;
    * an undocumented combination, or one the constraint set rejects — the
      ``ConfigurationError`` / ``ConstraintViolationError`` that source already
      raises.

    Nothing here answers "unpinned" to a bad pin.
    """
    overrides = (
        None
        if capabilities is None
        else {INVOCATION_BUNDLE: legacy_pin_vector(capabilities)}
    )
    profile = invocation_profile(package_version, frozenset(legacy_defaults))
    source = StaticBindingSource(profile, overrides=overrides)
    vector = source.effective_vectors[INVOCATION_BUNDLE]
    return LegacyPin(
        capabilities=frozenset(
            PINNABLE_CAPABILITIES[port] for port, _generation in vector
        ),
        package_version=package_version,
        vector_hash=vector.vector_hash,
        source="local-override" if overrides else "release-static",
        binding=source.describe(),
    )