Skip to content

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

clear_process_rollbacks() -> None

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
def clear_process_rollbacks() -> None:
    """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.
    """
    _ROLLED_BACK.clear()

process_rollback_reason

process_rollback_reason(capability: str) -> str | None

Why capability is held on legacy process-wide, or None.

Source code in src/symfonic/agent/cutover/process.py
def process_rollback_reason(capability: str) -> str | None:
    """Why ``capability`` is held on legacy process-wide, or ``None``."""
    switch_for(capability)
    return _ROLLED_BACK.get(capability)

process_rollbacks

process_rollbacks() -> Mapping[str, str]

Every capability currently held on legacy process-wide, with reasons.

Source code in src/symfonic/agent/cutover/process.py
def process_rollbacks() -> Mapping[str, str]:
    """Every capability currently held on legacy process-wide, with reasons."""
    return MappingProxyType(dict(_ROLLED_BACK))

restore_process_wide

restore_process_wide(capability: str) -> None

Lift the process-wide override. Per-board rollbacks are untouched.

Source code in src/symfonic/agent/cutover/process.py
def restore_process_wide(capability: str) -> None:
    """Lift the process-wide override. Per-board rollbacks are untouched."""
    switch_for(capability)
    _ROLLED_BACK.pop(capability, None)

rollback_process_wide

rollback_process_wide(capability: str, *, reason: str | None = None) -> None

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.

Source code in src/symfonic/agent/cutover/process.py
def rollback_process_wide(capability: str, *, reason: str | None = None) -> None:
    """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.
    """
    switch_for(capability)
    refuse_retired_lever(
        "process-rollback",
        capability,
        detail=(
            "It cannot be put back on its legacy body, and a process-wide "
            "global was the wrong scope for a per-deployment "
            "fact even while it worked: it redefined routing for every agent "
            "in the interpreter, tenants included, and for agents built after "
            "the call. Lifting one is still supported -- restore_process_wide "
            "and clear_process_rollbacks are untouched. Installs on 10.4 and "
            "below keep this lever for the length of the compatibility "
            "window; it is retired on this line and not backported."
        ),
    )