Skip to content

symfonic.services.switching.mutation

mutation

The one mutation path every switch-state change goes through.

Split out of :mod:.control_plane because it is the mechanism — authorize, validate, check the freeze, compare-and-swap, chain the audit record — and the control plane is the vocabulary built on top of it. Keeping them apart is what makes "there is exactly one way switch state changes" checkable by reading one class rather than auditing five methods for drift.

SwitchMutator

SwitchMutator(*, store: InMemorySwitchStore, constraints: ConstraintSet, audit: HashChainAuditLog, authorizer: SwitchAuthorizer, validated_vectors: Sequence[GenerationVector] = (), clock: Callable[[], float] = time.time)

Authorize → validate → freeze-check → CAS → audit. In that order, once.

Source code in src/symfonic/services/switching/mutation.py
def __init__(
    self,
    *,
    store: InMemorySwitchStore,
    constraints: ConstraintSet,
    audit: HashChainAuditLog,
    authorizer: SwitchAuthorizer,
    validated_vectors: Sequence[GenerationVector] = (),
    clock: Callable[[], float] = time.time,
) -> None:
    self._store = store
    self._constraints = constraints
    self._audit = audit
    self._authorizer = authorizer
    self._validated = frozenset(validated_vectors)
    self._clock = clock
    self._lock = asyncio.Lock()