Skip to content

symfonic.services.switching.invocation_profile

invocation_profile

TA8.54 — the shipped invocation profile, and the legacy pin it can hold.

:mod:~symfonic.services.switching.release has always been able to describe a release that honours a documented legacy pin, and :mod:~symfonic.services.switching.static_source has always been able to apply one. Until this module, nothing in src/ ever built either: every ReleaseProfile(...) and every StaticBindingSource(...) in the tree lived under tests/. The mechanism was complete and unreachable, so no published version could hold a pin that did anything — which is the finding this module closes, and the whole of what it closes.

The bundle. One bundle, :data:INVOCATION_BUNDLE, whose ports are the four capability switches symfonic.agent.cutover.routes.CAPABILITY_SWITCHES dispatches on. A pin is therefore expressed in exactly the vocabulary the dispatch already reads, rather than in a second vocabulary that would have to be kept in agreement with it.

A vector names what is pinned, not what is not. The release-static vector is empty on the lines this distribution builds: no capability is held on its legacy body, and every switch answers from its filed criteria. An override adds one entry per pinned capability, <port>@1 — the legacy generation. Two consequences, both deliberate:

  • the retirement freeze a >= 11.0 profile computes from the override table (StaticBindingSource._package_freeze) contains exactly the four legacy generations, and not the kernel ones a full-width vector would have dragged in;
  • "pinned" is a property of the vector's keys, so reading a pin back is a membership test rather than a comparison against a second table.

legacy_defaults is a release-authoring input, never an adopter's. It moves the release-static vector, which is how a line whose published contract is the legacy one expresses that: on 10.4 the untyped stream still yields the LangGraph node mappings per task #25 option C, and that is a fact about the line rather than about anything the adopter set. See evidence/RET-PREP/legacy-pin-wiring.md §5 for what this does and does not prove: it shows the profile mechanism can express that line's behaviour; TA7.2 proves 10.4 actually yields the dicts, on a real artifact, and 10.4 does not exist as a published tag today.

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.

invocation_profile cached

invocation_profile(package_version: str, legacy_defaults: frozenset[str] = frozenset()) -> ReleaseProfile

The :class:ReleaseProfile this release ships for the invocation bundle.

Cached because it is compiled data — the same version always yields the same profile — and because every agent build asks for it.

Source code in src/symfonic/services/switching/invocation_profile.py
@lru_cache(maxsize=16)
def invocation_profile(
    package_version: str, legacy_defaults: frozenset[str] = frozenset()
) -> ReleaseProfile:
    """The :class:`ReleaseProfile` this release ships for the invocation bundle.

    Cached because it is compiled data — the same version always yields the
    same profile — and because every agent build asks for it.
    """
    static = _vector(legacy_defaults) if legacy_defaults else RELEASE_DEFAULT_VECTOR
    return ReleaseProfile(
        package_version=package_version,
        static_vectors={INVOCATION_BUNDLE: static},
        constraints=ConstraintSet(),
        override_table={INVOCATION_BUNDLE: DOCUMENTED_LEGACY_PINS},
    )

legacy_pin_vector

legacy_pin_vector(capabilities: Iterable[str]) -> GenerationVector

The vector pinning exactly capabilities to their legacy bodies.

Refuses an empty pin and refuses an unrecognised capability, both by name. An empty pin is not the release default spelled differently: it is a pin that would move nothing, and honouring it as a no-op would put the one mechanism adopters are told to rely on back into the silence this whole programme has spent its length removing.

Source code in src/symfonic/services/switching/invocation_profile.py
def legacy_pin_vector(capabilities: Iterable[str]) -> GenerationVector:
    """The vector pinning exactly ``capabilities`` to their legacy bodies.

    Refuses an empty pin and refuses an unrecognised capability, both by name.
    An empty pin is *not* the release default spelled differently: it is a pin
    that would move nothing, and honouring it as a no-op would put the one
    mechanism adopters are told to rely on back into the silence this whole
    programme has spent its length removing.
    """
    named = tuple(capabilities)
    if not named:
        raise ConfigurationError(
            "the legacy pin names no capability. An empty pin is refused "
            "rather than honoured as a no-op: pass None for 'this build sets "
            "no pin', or name one or more of "
            f"{sorted(PINNABLE_CAPABILITIES.values())}."
        )
    unknown = [
        capability
        for capability in named
        if not isinstance(capability, str) or capability not in _PORT_FOR
    ]
    if unknown:
        raise ConfigurationError(
            "the legacy pin names "
            f"{sorted(repr(entry) for entry in unknown)}, which this release "
            "does not switch. The pinnable capabilities are "
            f"{sorted(PINNABLE_CAPABILITIES.values())}. An unrecognised pin is "
            "refused rather than dropped: a mistyped pin that silently left "
            "the build on the route it already had would be indistinguishable "
            "from a pin that worked."
        )
    return _vector(frozenset(named))

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(),
    )