cadrumo.application.flows.validators module

Validator contracts and registries for the flow substrate.

Three validator scopes exist, mirroring the three-tier validation the substrate renders: per-answer validators run at commit time on one page’s raw token; section-exit validators run when the operator leaves a section; flow-scope validators run at the review surface. All three are pure functions returning ValidationVerdict records carrying i18n message keys (never prose) and redacted context — raw operator answers never enter diagnostics.

Domain flows register their validators by id at import time and name those ids on the FlowDefinition; the registries reject duplicate ids so a validator has exactly one owner. Widget-level shape validation (blank/choice/integer/date/decimal/path canonicalisation) is built in and runs before any registered per-answer validator.

class ValidationVerdict(**data)[source]

Bases: BaseModel

One typed validation outcome, renderable on the page or review surface.

message_key is a translation key; context carries only redacted, non-sensitive interpolation values (counts, choice sets, field labels — never the raw answer).

Parameters:

data (Any)

ok: bool
message_key: str | None
context: dict[str, object]
classmethod passed()[source]

Return a successful verdict with no diagnostic payload.

Return type:

ValidationVerdict

classmethod failed(message_key, **context)[source]

Return a failed verdict carrying only redacted interpolation context.

Return type:

ValidationVerdict

Parameters:
register_answer_validator(validator_id, validator)[source]

Register a per-answer validator under a unique id.

Return type:

None

Parameters:
  • validator_id (str)

  • validator (AnswerValidator)

register_cross_field_validator(validator_id, validator)[source]

Register a section-exit or flow-scope validator under a unique id.

Return type:

None

Parameters:
  • validator_id (str)

  • validator (CrossFieldValidator)

resolve_answer_validator(validator_id)[source]

Return the registered per-answer validator or refuse loudly.

Return type:

TypeAliasType

Parameters:

validator_id (str)

resolve_cross_field_validator(validator_id)[source]

Return the registered cross-field validator or refuse loudly.

Return type:

TypeAliasType

Parameters:

validator_id (str)

validate_widget_shape(page, raw)[source]

Validate and canonicalise raw against the page’s widget shape.

Returns (canonical_token, verdict). The canonical token is meaningful only when the verdict passes. Blank-answer policy is keyed on the static descriptor exactly as the one-shot wizard’s validators were: a blank fails only for an unconditionally required page (required and no visible_when); a gated or optional page accepts blank as the undeclared state.

A DATE page carries its canonical answer as the ISO-8601 string (YYYY-MM-DD) and a DECIMAL page as the str(Decimal) form: both keep answer_type = str on the page rather than widening the FlowPage answer-type union to date / Decimal. The shape validation here is the sole guarantee the stored token parses, so the consuming domain re-parses the canonical string with the same core parser it validated against.

Return type:

tuple[str, ValidationVerdict]

Parameters:
run_answer_validation(page, raw)[source]

Run widget-shape validation then every registered per-answer validator.

Registered validators see the canonical token only after the widget shape passed; a failing widget verdict short-circuits.

Return type:

tuple[str, tuple[ValidationVerdict, ...]]

Parameters: