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:

  1. missing_input / duplicate_input (Rule 6)

  2. fork (Rule 1)

  3. stub (Rule 2)

  4. required_aux / unknown_class (Rule 3)

  5. cycle (Rule 4)

  6. container_mode (Rule 5)

  7. 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

fork

error

Image-flow ports have at most one wire

2

stub

error

All blocks reachable from Input Image

3

required_aux

error

Required aux ports must be wired

4

cycle

error

No cycles in the edge graph

5

container_mode

error

Container left/right wiring consistency

6

missing_input, duplicate_input

error

Exactly one Input Image per scope

7

stage_order_hint

advisory

Stage ordering respects ops → meas → post

unknown_class

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).