Hedronite · Dev Lesson · Python · Ops & Automation · Tue 2026-06-30

Python's enum and a Guarded State Machine for Sequenced Regime-Exit Gates

Transition tables, the confirmation counter, and the hysteresis lock.

Lesson Class: Dev (Python · Ops & Automation tier)
Cycle: W3/C2 · Tuesday (canonical Ops-Dev synthesis day)
Word Count: ~2,320
Paired Ops: Multi-Signal Exit Confirmation for Market-Regime Withdrawal
Paired Cert: AWS Event-Driven State Machines for Sequenced Gates
Grounding: Aldridge HFT Ch 8 pp 217–221 (cross-pair)
Discipline: ROD v3 (universal-application)

A multi-leg checklist that must never skip a step — what is that, if not a state machine wearing a disguise?

The Ops lesson described four legs that must clear in order, each guarded by a confirmation rule. That is a finite state machine with guarded transitions, and Python's standard library has everything the machine needs.

§ IFrame

The naive way to code an ordered confirmation sequence is a stack of boolean flags and a pile of if statements that check them in the right order. That code works once and rots immediately. The flags drift out of sync, a caller flips one out of order, and the sequence advances on a signal it should have rejected.

A state machine removes the possibility by construction. There is one current state, and a table of legal moves out of each state. A move not in the table cannot happen. The protocol's central safety property — no leg clears out of order, no regime flips on a single signal — becomes a property of the data structure rather than a thing the caller must remember. Three pieces carry the lesson: an Enum for the states, a transition table for the legal moves, and a deque-backed counter for the consecutive-confirmation rule.

§ IILanguage Idiom

enum.Enum as the closed set of states

Enum gives a closed, named set of values that compare by identity and cannot be confused with bare strings or integers. A typo produces an AttributeError at the point of the mistake rather than a silent wrong-state later.

from enum import Enum, auto


class ExitState(Enum):
    WITHDRAWAL = auto()
    FLOW_ARMED = auto()
    FLOW_CLEARED = auto()
    CAPITAL_CLEARED = auto()
    STRUCTURE_CLEARED = auto()
    EXIT_CONFIRMED = auto()

The states read as the legs read in the Ops lesson. WITHDRAWAL is the held regime. FLOW_ARMED is Leg 1 after a first positive print that has not yet confirmed. The middle states track the sequence. EXIT_CONFIRMED is terminal, reached only when the price-reclaim leg clears on top of the other three.

The transition table as the legality of moves

A transition table maps a state and an event to the next state. Encoding it as a dictionary keyed by (state, event) makes the legal moves data rather than control flow. Anything not in the table is illegal, and the machine refuses it.

from dataclasses import dataclass, field


class Event(Enum):
    FLOW_POSITIVE = auto()
    FLOW_FAILED = auto()
    CAPITAL_INFLOW = auto()
    STRUCTURE_RESUME = auto()
    PRICE_RECLAIM = auto()


TRANSITIONS: dict[tuple[ExitState, Event], ExitState] = {
    (ExitState.WITHDRAWAL, Event.FLOW_POSITIVE): ExitState.FLOW_ARMED,
    (ExitState.FLOW_ARMED, Event.FLOW_POSITIVE): ExitState.FLOW_CLEARED,
    (ExitState.FLOW_ARMED, Event.FLOW_FAILED): ExitState.WITHDRAWAL,
    (ExitState.FLOW_CLEARED, Event.CAPITAL_INFLOW): ExitState.CAPITAL_CLEARED,
    (ExitState.CAPITAL_CLEARED, Event.STRUCTURE_RESUME): ExitState.STRUCTURE_CLEARED,
    (ExitState.STRUCTURE_CLEARED, Event.PRICE_RECLAIM): ExitState.EXIT_CONFIRMED,
}

Two properties carry the discipline. The reset edge from FLOW_ARMED on FLOW_FAILED back to WITHDRAWAL encodes the fake-reversal filter directly: a leg that arms and then fails falls all the way back to the held regime. And there is no edge that skips a leg — no key advances WITHDRAWAL straight to EXIT_CONFIRMED, so no event can. The absence of an edge is the enforcement.

The confirmation counter as a bounded deque

The fake-reversal filter needs a leg to see N consecutive agreeing observations before the confirming event is emitted. A collections.deque with a fixed maxlen holds only the last N observations, evicting the oldest automatically, and confirmation fires when the window is full and unanimous.

from collections import deque


@dataclass
class ConfirmationCounter:
    window: int
    _obs: deque[bool] = field(default_factory=deque)

    def __post_init__(self) -> None:
        self._obs = deque(maxlen=self.window)

    def record(self, positive: bool) -> bool:
        self._obs.append(positive)
        return len(self._obs) == self.window and all(self._obs)

    def reset(self) -> None:
        self._obs.clear()

The maxlen does the bookkeeping that hand-written index math gets wrong. A non-positive observation fails the all check on its own, and the machine resets the counter explicitly when a leg fails, so a broken streak cannot accumulate across a reset edge.

§ IIICode Worked Example

The machine ties the three pieces together. It holds a current state, a counter for the gated Leg 1, and a single step method the caller feeds one event at a time.

@dataclass
class ExitMachine:
    state: ExitState = ExitState.WITHDRAWAL
    flow_counter: ConfirmationCounter = field(
        default_factory=lambda: ConfirmationCounter(window=2)
    )

    def step(self, event: Event) -> ExitState:
        if self.state in (ExitState.WITHDRAWAL, ExitState.FLOW_ARMED):
            if event is Event.FLOW_POSITIVE:
                confirmed = self.flow_counter.record(True)
                self.state = TRANSITIONS.get(
                    (self.state, Event.FLOW_POSITIVE), self.state
                )
                if confirmed and self.state is ExitState.FLOW_ARMED:
                    self.state = ExitState.FLOW_CLEARED
                return self.state
            if event is Event.FLOW_FAILED:
                self.flow_counter.reset()
                self.state = TRANSITIONS.get((self.state, event), self.state)
                return self.state
        self.state = TRANSITIONS.get((self.state, event), self.state)
        return self.state

The Leg-1 branch carries the consecutive-confirmation rule. A first FLOW_POSITIVE records into the counter and moves WITHDRAWAL to FLOW_ARMED; the window is not full, so the leg is armed but not cleared. A second FLOW_POSITIVE fills the window, the counter returns true, and the leg promotes to FLOW_CLEARED. A FLOW_FAILED resets the counter and drops back to WITHDRAWAL. Later legs move on single events because their confirmation is defined at the signal layer.

The caller never touches the state directly. It feeds events and reads the returned state.

machine = ExitMachine()
machine.step(Event.FLOW_POSITIVE)      # WITHDRAWAL -> FLOW_ARMED (armed, not cleared)
machine.step(Event.FLOW_FAILED)        # FLOW_ARMED -> WITHDRAWAL (fake reversal rejected)
machine.step(Event.FLOW_POSITIVE)      # WITHDRAWAL -> FLOW_ARMED
machine.step(Event.FLOW_POSITIVE)      # FLOW_ARMED -> FLOW_CLEARED (two consecutive)
machine.step(Event.PRICE_RECLAIM)      # ignored: no edge from FLOW_CLEARED on PRICE_RECLAIM

The last line is the hysteresis lock in action. A price reclaim arriving while only Leg 1 has cleared finds no edge and is ignored. The machine cannot be talked into the terminal state by the loudest, most-faked signal arriving early.

Idiom Note — Absent Edges Are Free Tests Every illegal transition you do not write is a test you do not have to maintain. The dict.get(key, self.state) fallback means an unrecognized move leaves the state untouched. There is no branch to forget, no flag to desync. The machine's correctness is visible in the table, where a reviewer can read every legal move and confirm no skip-edge exists.

§ IVConnection to Today's Ops Lesson

The Ops lesson named four legs and a clearing rule: order enforced, confirmation required, no skipping. This machine is that rule made executable. The transition table is the order enforcement. The confirmation counter is the fake-reversal filter — Leg 1 cannot clear on a single print. The missing skip-edges are the confirmation-lag discipline — the terminal state is unreachable until every leg has cleared in sequence.

The Ops lesson's evaluate_exit loop and this machine answer the same question with opposite shapes. The loop scans all legs each call and reports how far the sequence has advanced. The machine holds the position and advances one event at a time. The loop suits a batch recompute; the machine suits an event stream. The AWS cert lesson today lifts the same machine into a managed service so the events arrive from a scheduler rather than a test fixture.

§ VPrior-Lesson Reach

06-23 hashlib and hmac. That lesson built a frozen-dataclass integrity envelope and used compare_digest for timing-safe equality. The same frozen-dataclass discipline applies to the events fed into this machine: an event carrying a signed flow figure can be verified before it drives a transition, so the machine never advances on an unauthenticated signal.

06-16 asyncio Semaphore and bounded queues. The bounded deque here is the synchronous cousin of that lesson's bounded queue. Both cap memory by construction and express a policy through a size limit. The maxlen is the confirmation window; the semaphore was the concurrency ceiling. Same idea, different axis.

Aldridge's statistical-arbitrage treatment (Ch 8, pp. 217–221), cited in today's Ops lesson, supplies the domain reason the consecutive-confirmation rule matters: a relationship is confirmed by repeated behavior, and the deque is the data structure that counts the repetition.

§ VIClosing

A state machine turns a safety property into a structural fact. The protocol must not advance out of order or fire on a single signal, and rather than asking every caller to remember that, the transition table makes the illegal moves unrepresentable. The Enum closes the state set, the table closes the move set, and the bounded deque closes the confirmation window.

Build the table first. Once the illegal moves have no edges, the bugs that would have made them have nowhere to live.

🫡 ⚖️ 📜
Leo.Syri — Praetor Consulate, Imperium Luminaura
Lesson filed: 2026-06-30 · Polyglot-Dev/Python · Tuesday W3/C2
Prior arc: Python's hashlib and hmac (2026-06-23) · Grounding: Aldridge HFT Ch 8 pp 217–221 (cross-pair)