Pipeline builder validation rules#
Note
This page is generated by
scripts/generate_validation_reference.py from
phenotypic.gui.builder._validation.IssueKind plus a
hand-curated rule table that mirrors spec §4.6 verbatim. Run the
script after touching either to regenerate; the --check flag
is wired into CI to catch drift.
The DAG builder’s validation surface is a pure function
phenotypic.gui.builder._validation.validate() that walks every
scope reachable from state.root and emits a flat list of
Issue records. Six
blocking rules (severity "error") disable
Run preview and Save pipeline; the advisory hints (severity
"advisory") decorate the canvas with yellow borders but never
block.
Rules emit in deterministic order:
missing_input/duplicate_input(Rule 6)fork(Rule 1)stub(Rule 2)required_aux/unknown_class(Rule 3)cycle(Rule 4)container_mode(Rule 5)stage_order_hint(Rule 7, advisory)
Nested-scope issues are appended after the parent scope’s issues so snapshot-style tests stay stable.
Summary table#
Rule |
Kind |
Severity |
Title |
|---|---|---|---|
1 |
|
error |
Image-flow ports have at most one wire |
2 |
|
error |
All blocks reachable from Input Image |
3 |
|
error |
Required aux ports must be wired |
4 |
|
error |
No cycles in the edge graph |
5 |
|
error |
Container left/right wiring consistency |
6 |
|
error |
Exactly one Input Image per scope |
7 |
|
advisory |
Stage ordering respects ops → meas → post |
— |
|
advisory |
Class not in the operation registry |
Per-rule reference#
Rule 1 — Image-flow ports have at most one wire#
Issue kind(s): fork
Severity: error
Mechanic: Per-port wire-count over edge.kind == "image", plus a total-fan-out check across image + aux (one outgoing wire from any source, total).
Offender: The block whose output or input violates the rule. Three sub-cases all surface as kind="fork": a source with >1 outgoing image edge, an (target_block_id, "in") port with >1 incoming image edge, and a source with >1 outgoing wires total across image and aux.
Rule 2 — All blocks reachable from Input Image#
Issue kind(s): stub
Severity: error
Mechanic: BFS from the InputImage block across image-flow edges forward and aux edges in both directions. Any block not visited by the walk is flagged as a stub.
Offender: Each unreachable block (rendered with a dashed red border). Extra InputImage blocks are excluded from the stub set so they’re flagged once as duplicate_input instead of double-flagged.
Rule 3 — Required aux ports must be wired#
Issue kind(s): required_aux
Severity: error
Mechanic: For each block, walk the registry’s OperationInfo.parameters; for every op-typed parameter (param.is_operation or param.is_pipeline) without a default (not param.has_default), require at least one aux edge targeting (block_id, param_name).
Offender: The consumer block. The empty required port renders with a red ring.
Rule 4 — No cycles in the edge graph#
Issue kind(s): cycle
Severity: error
Mechanic: Iterative Tarjan’s strongly-connected-components over the combined edge graph (image + aux). Any block participating in a non-trivial SCC (size > 1 OR size 1 with a self-loop) is reported.
Offender: Every block in the strongly-connected cycle (sorted lexicographically for deterministic test output).
Rule 5 — Container left/right wiring consistency#
Issue kind(s): container_mode
Severity: error
Mechanic: For each Pipeline container, evaluate whether the outer left image-input is wired and what kind of port the right output wires to. The two valid modes are consumer-fed (left wired, right wires to image) and aux-fed (left unwired, right wires to aux). Mixed modes are rejected.
Offender: The container block whose wiring is inconsistent.
Rule 6 — Exactly one Input Image per scope#
Issue kind(s): missing_input, duplicate_input
Severity: error
Mechanic: Count InputImage blocks in each scope. Zero → emit missing_input as a scope-level issue. Two or more → emit one duplicate_input issue per extra block.
Offender: For missing_input: reported as a scope-level issue (block_id=None); the dispatcher’s auto-seed normally heals this on the next state-load pass. For duplicate_input: the extra block(s).
Rule 7 — Stage ordering respects ops → meas → post#
Issue kind(s): stage_order_hint
Severity: advisory
Mechanic: Walk each image-flow edge; if the source block’s stage (via _safe_stage(class_name)) is later in the canonical order than the target’s, emit a yellow-border advisory. The runtime partitions by isinstance so a misordered chain still works — this is a non-blocking nudge.
Offender: The source block of the out-of-order edge (yellow border + “?” badge).
Advisory — Class not in the operation registry#
Issue kind(s): unknown_class
Severity: advisory
Mechanic: Registry lookup for each non-sentinel block’s class_name. A miss emits unknown_class as an advisory so the rule-3 walk can skip the block cleanly without raising.
Offender: The block whose class is unknown to the registry (yellow border + “?” badge). Typically caused by registry drift (loading a pipeline.json saved by a newer build).