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.0profile 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
legacy_pin_vector ¶
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
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:
ConfigurationErrorfrom :func:legacy_pin_vector; - a pin on a release at or past
FIRST_REJECTING_RETIREMENT_VERSION—LegacyPinRetiredErrorfromStaticBindingSource._apply_override, carrying the migration procedure; - an undocumented combination, or one the constraint set rejects — the
ConfigurationError/ConstraintViolationErrorthat source already raises.
Nothing here answers "unpinned" to a bad pin.