symfonic.agent.cutover.process¶
process ¶
The process-wide rollback lever — retired on the 11.0 line (TA8.5).
:func:rollback_process_wide refuses. Read the rest of this docstring as the
account of what it was and why the retirement is not merely bookkeeping: this
lever deserved to go first regardless of dormancy.
A process-wide global is the wrong scope for a per-deployment fact. It is the defect class this repository already named on the scope resolver, and here it is worse: the override silently redefines routing for every agent in the process, including every tenant's, and it does so for agents constructed after the call by code that never consented. An operator holding one capability on legacy for one deployment cannot express that here; what they get instead is a process-wide answer to a question that was never process-wide.
What remains is the way back. :func:restore_process_wide,
:func:clear_process_rollbacks, :func:process_rollback_reason and
:func:process_rollbacks all still work, and that asymmetry is deliberate: the
retirement removes the way to reach legacy, never the way to leave it. A
process that somehow holds an override — an older module in the same
interpreter, a test driving the dormant body — must still be liftable, and
nobody should have to enumerate what they turned on during an incident.
Why it existed at all, kept because route_for still consults this module.
:meth:CutoverSwitchboard.rollback
<symfonic.agent.cutover.switchboard.CutoverSwitchboard.rollback> moved one
board, and every SymfonicAgent builds its own board from the compiled-in
criteria. That was the right scope for a test and the wrong scope for an
incident: a host that keeps several agents alive, or that constructs one per
request, would leave every other agent — and every agent built after the
call — on the migrated path, while the operator watched a route flip in their
REPL and believed the capability was back on legacy.
So the lever an operator reached for was module state: one answer per process,
consulted by every board before its own rollback map, so the override won no
matter which instance served the turn or when it was constructed. It read no
environment variable and no file. Both rollbacks are retired now, and
_ROLLED_BACK is written by nothing this package exports — what reads it
below is the way back out, and the parity suites that still have to drive the
dormant body for a comparison.
clear_process_rollbacks ¶
Lift every process-wide override.
Module state that only grows is a test-isolation hazard: anything that
pins a capability here without restoring holds every agent constructed
later in the same process on legacy, and the symptom is unrelated tests
passing for the wrong reason. Resetting is therefore an exported call
rather than a loop over :func:process_rollbacks copied into each test
module — a fixture teardown has one obvious thing to invoke.
Deliberately unguarded by a reason: clearing an override is the safe direction, and an incident is exactly when nobody should have to enumerate what they turned on.
Source code in src/symfonic/agent/cutover/process.py
process_rollback_reason ¶
Why capability is held on legacy process-wide, or None.
process_rollbacks ¶
restore_process_wide ¶
Lift the process-wide override. Per-board rollbacks are untouched.
rollback_process_wide ¶
Refused since 11.0: the process-wide rollback lever is retired.
Raises :class:~symfonic.agent.cutover.retirement.RetiredLeverError,
naming the line and the capability. reason is optional only so that a
call written from muscle memory refuses by name rather than raising
TypeError; it is not read, and nothing is recorded.
switch_for runs first so a mistyped capability still reports itself as
unknown rather than as retired.