cadrumo.application.flows.engine module¶
The pure flow engine: immutable state plus typed transitions.
The engine is the single flow authority. It owns every flow semantic —
visibility, navigation, answer commitment, staleness, deferral, reset,
restart — as pure functions over an immutable FlowState.
Frontends (full-screen TUI, line mode, scripted driver, non-interactive
flags) project the state and dispatch intents; they contain zero flow
logic, so every interaction surface is consistent by construction.
Two invariants are load-bearing:
Staleness, never silent deletion. Changing an answer whose value gates later pages marks the dependent answers
STALE(they surface on the review projection and block submission) rather than deleting them. Shrinking a repeating group likewise marks the orphaned instance answers stale.Deferral never resolves silently. A
COMPARE_SELECTpage answered with the reserved defer token staysDEFERREDuntil the operator revisits it; submission refuses while any deferral remains.
Visibility is recomputed from the canonical answer map on every transition; nothing is cached across transitions.
- SECTION_VERDICT_PREFIX¶
Key prefix under which section-exit verdicts land in
FlowState.verdicts.
- class VisiblePage(**data)[source]¶
Bases:
BaseModelOne entry in the currently-visible page sequence.
keyis the canonical answer-map key: the bare page id for a top-level page,<group>#<index>.<page>for a repeating-group instance page.- Parameters:
data (Any)
- key: str¶
- page: FlowPage¶
- section_id: str¶
- group_id: str | None¶
- instance_index: int | None¶
- class FlowState(**data)[source]¶
Bases:
BaseModelImmutable engine state; every transition returns a new instance.
answersholds canonical tokens keyed by visible-page key.verdictsholds the failing validation verdicts currently blocking a page (keyed by page key) or a section exit (keyedsection:<id>).staleholds page keys whose committed answer outlived a gating change.instance_countsholds the live instance count per repeating group, seeded from the group’scount_fromanswer and adjustable from review.Immutability convention:
frozen=Trueblocks field reassignment; the dict-typed fields are additionally never mutated in place — every transition builds fresh containers and returns amodel_copy. Callers must follow the same convention.- Parameters:
data (Any)
- flow_id: str¶
- mode: FlowMode¶
- answers: dict[str, str]¶
- verdicts: dict[str, tuple[ValidationVerdict, ...]]¶
- stale: frozenset[str]¶
- instance_counts: dict[str, int]¶
- cursor: str | None¶
- visited: tuple[str, ...]¶
- start_flow(definition, *, mode)[source]¶
Return the initial state with the cursor on the first visible page.
- Return type:
- Parameters:
definition (FlowDefinition)
mode (FlowMode)
- visible_sequence(definition, state)[source]¶
Compute the currently-visible page sequence from the answer map.
Repeating groups expand to their live instance count (the
count_fromanswer unless review adjusted it). A gated page is visible when any of its clauses holds against the answers; clause targets resolve instance-locally first so a group page may gate on a sibling within the same instance.- Return type:
- Parameters:
definition (FlowDefinition)
state (FlowState)
- answer(definition, state, page_key, raw)[source]¶
Commit
rawto the page atpage_keyand return the new state.A failing validation leaves the previous answer in place and records the verdicts under
page_keyso the frontend renders them in place. A passing commit stores the canonical token, clears the page’s verdicts, marks dependents of a changed gating answer stale, and refreshes the owning repeating group’s instance count when the page is a declared count source.- Return type:
- Parameters:
definition (FlowDefinition)
state (FlowState)
page_key (str)
raw (str)
- next_page(definition, state)[source]¶
Advance the cursor to the next visible page.
Crossing out of a section runs that section’s exit validators; any failure records the verdicts under
section:<id>and leaves the cursor in place, blocking forward navigation with a rendered, typed result. Moving backwards is never gated.- Return type:
- Parameters:
definition (FlowDefinition)
state (FlowState)
- jump_to(definition, state, page_key)[source]¶
Set the cursor onto any currently-visible page (the review jump).
- Return type:
- Parameters:
definition (FlowDefinition)
state (FlowState)
page_key (str)
- reset_page(definition, state, page_key)[source]¶
Clear one page’s answer back to undeclared and mark its dependents stale.
The committed answer is removed (the page’s descriptor default is a render-time prefill, not a committed answer), the page’s verdicts and staleness clear, and — exactly as with a changed gating answer — every answered dependent of the cleared page is marked stale rather than deleted.
- Return type:
- Parameters:
definition (FlowDefinition)
state (FlowState)
page_key (str)
- restart_flow(definition, state)[source]¶
Wipe the whole state and return to the first page (confirmed upstream).
The confirmation is a frontend responsibility; the engine transition itself is unconditional.
- Return type:
- Parameters:
definition (FlowDefinition)
state (FlowState)
- page_status(state, page_key)[source]¶
Return the review status of one page key.
- Return type:
- Parameters:
- first_unanswered_key(definition, state)[source]¶
Return the key of the first visible entry the state has not answered.
Lives beside the visibility walk it depends on, because the answer changes with visibility: an entry hidden by a prior answer is not unanswered, it is not asked. Two callers each carried a copy of this loop.
- Return type:
- Parameters:
definition (FlowDefinition)
state (FlowState)