symfonic.agent.cutover.retirement¶
retirement ¶
The 11.0 retirement — what a caller may no longer ask for (TA8.5, TA8.18).
TA8.4 measured which supported configurations still enter a legacy body and handed this task a list. Three of the four levers on that list are retired here, and the word retired is doing exact work: the call still exists, it still takes the arguments it always took, and it refuses, by name, saying which line retired it and which capability was asked for.
That shape is the whole point, and each alternative to it was rejected for a reason worth stating:
- Not deleted. An operator who types the 03:00 procedure into a REPL and
gets
AttributeErrorlearns that something changed, not what. The lever answers for itself. - Not ignored. A call that returns
Noneand moves nothing is the worst of the four: the operator watches the incident continue while believing the capability is back on legacy. - Not silently redirected. Serving the turn on the kernel because the rollback was refused, without saying so, is the same failure wearing a route.
- Not accepted-then-no-op. Recording the reason and never reading it again
would leave
rollback_reasonanswering truthfully about a route that never moved.
So: an explicit refusal, every time, naming the line and the capability.
Levers and arguments, one vocabulary. TA8.5 retired three operator
levers; TA8.18 retires three per-call arguments — callbacks,
extra_metadata and state_overrides. They are different kinds of fact
and they get different types (:class:RetiredLeverError and
:class:RetiredArgumentError) so a handler and the reachability probe can tell
them apart, but they share this module, this line constant and this shape of
refusal, because "retired on 11.0" is one rule and three spellings of it would
be three rules. The four bullets above apply to the arguments verbatim: an
argument is not deleted from the signature, not ignored, not silently
redirected onto another surface, and never accepted-then-no-op.
Which "11.0" this is. :data:LEVER_RETIREMENT_LINE is the source line —
the release this checkout becomes — not a reading of the installed
distribution. The distinction matters because the reachability probe simulates
older release lines by patching stream_contract.installed_release_line, and
a refusal keyed on that seam would answer "the levers are still here" for every
simulated 10.4 row. The levers are gone from this code; an install on a
line at or below that bound runs its own code, which still has them -- today
that means 9.12, the newest such release actually published. That is what the
compatibility window promises and it is kept by not backporting this module,
never by a runtime branch that re-honours a retired lever or a retired argument.
That is also why the refusals below are unconditional: a check that re-admitted
callbacks when the install reported an older line would honour a retired
argument in 11.0 code, which is the one thing the window does not promise.
The marker mirrors services.switching.release.FIRST_REJECTING_RETIREMENT_VERSION
— the normative name from T1.2.6 — and is mirrored rather than imported for the
same layering reason stream_contract states: the facade may only reach a
runtime service through a declared port. tests/agent/cutover/test_lever_retirement.py
fails if the two strings ever disagree.
RetiredArgument
dataclass
¶
One retired per-call argument: what was written, and what replaces it.
Two fields rather than one string because the refusal owes two different
things. call is the call the adopter made -- quoted back so the
traceback names the thing they typed. instead is the next step, and it
lives here rather than being supplied at the raise site on purpose:
refuse_retired_lever makes detail a required parameter so that a
refusal can never be a dead end, and a table that carries the next step
keeps the same guarantee without asking every call site to remember it.
The obligation becomes structural instead of conventional.
RetiredArgumentError ¶
Bases: CutoverPathError
A per-call argument retired on the 11.0 line was supplied.
Subclasses :class:~symfonic.services.shadow.errors.CutoverPathError, and
through it ConfigurationError and SymfonicError, for the reason
:class:RetiredLeverError gives for its own ancestry: an adopter's
existing except around the cutover API keeps catching this. A widening,
never a rename.
Named separately from :class:RetiredLeverError rather than folded into
it. The reachability probe's validity predicate keys on the lever
vocabulary, and RETIRED_LEVERS' keys are exactly the lever names that
probe drives; a per-call argument in that table would make one vocabulary
carry two different kinds of fact. Two exact classifiers beat one loose
one.
Source code in src/symfonic/agent/cutover/retirement.py
RetiredLeverError ¶
Bases: CutoverPathError
A lever retired on the 11.0 line was pulled.
Subclasses :class:~symfonic.services.shadow.errors.CutoverPathError, and
through it ConfigurationError and SymfonicError, so an adopter's
existing except around the cutover API still catches this — a widening
of the ancestry, never a rename. The type is named all the same, because
"the lever is gone" and "the evidence was filed under a path this
capability may not use" are different incidents and an operator triaging at
03:00 should not have to read the message to tell them apart.
Source code in src/symfonic/agent/cutover/retirement.py
refuse_retired_argument ¶
refuse_retired_argument(argument: str, *, entry_point: str, received: Iterable[str] = ()) -> NoReturn
Refuse argument on entry_point, citing the line that retired it.
received names the keywords that actually arrived. It matters for
state_overrides, which is an open **kwargs map rather than a named
parameter: without it the refusal would say "state_overrides" to an adopter
who typed sesion_id= and never wrote that word.
There is no detail parameter, and its absence is the same guarantee
:func:refuse_retired_lever buys by making one required: the next step
comes from :data:RETIRED_ARGUMENTS, so every refusal carries one and no
call site can forget to pass it.
Source code in src/symfonic/agent/cutover/retirement.py
refuse_retired_lever ¶
Refuse lever for capability, citing the line that retired it.
detail says what the caller can do instead. It is required rather than
optional: a refusal that names the line and stops is a dead end, and the
operator pulling a rollback lever at 03:00 is the last person who should
have to go read a migration document to find the next step.
Source code in src/symfonic/agent/cutover/retirement.py
retired_argument_supplied ¶
The first retired argument actually supplied, in table order, or None.
The single place that decides "was a retired argument used?", so the guard
loop's answer and the entry points' answer cannot drift. Emptiness is not
use: callbacks=None and callbacks=[] are what an adopter who
attaches nothing passes, and refusing them would retire the parameter
rather than the behaviour.
An unknown keyword raises rather than being skipped. A silent skip would turn a misspelling here into a guard that quietly stopped guarding.