Files
Aura/docs/plans/0040-wiring-totality-check.md
T
Brummel 21c1621bd0 docs,engine: drop coined "compilat", use FlatGraph / "flat graph"
"Compilat" (German "Kompilat") was a coined noun for the product of the
bootstrap compilation — neither English nor a natural fit. The runtime
artifact already has a code identifier for exactly this thing: the
`FlatGraph` struct (harness.rs). Replace the coinage with that identifier:

- prose mentions          -> "flat graph" (mirrors the type, reads plainly)
- definitional anchors    -> `FlatGraph` (C11, C23, the running-graph line)
- `render_compilat`       -> `render_flat_graph` (historical render symbol;
                             keeps the `render_blueprint` / `render_flat_graph`
                             source-vs-product pairing)
- `intra-compilat`        -> `intra-graph`
- `from_compilat` (test)  -> `from_flat`

"compilation" / "re-compilation" / "bootstrap-as-compilation" (the process,
ordinary English) are deliberately left untouched. Behaviour-preserving:
only comments, design ledger, specs/plans, one test-local variable and its
assert messages change. Full workspace test suite green; clippy clean.
2026-06-14 17:02:15 +02:00

25 KiB

Wiring-totality check — Implementation Plan

Parent spec: docs/specs/0040-wiring-totality-check.md

For agentic workers: REQUIRED SUB-SKILL: use the implement skill to run this plan. Steps use - [ ] checkboxes for tracking.

Goal: Reject a graph that leaves an interior input slot unwired or wires one slot from more than one producer, at compile time, via a single name-free structural check in validate_wiring.

Architecture: A new check_ports_connected(nodes, edges, roles) is added to validate_wiring (crates/aura-engine/src/blueprint.rs), after the existing index/kind checks and before the nested-composite recursion. It counts coverage of each (interior-node, slot) uniformly across interior edges and role targets, and rejects any interior input slot covered 0 times (UnconnectedPort) or >1 times (DoubleWiredPort). Because validate_wiring is called once from compile_with_params and recurses, both the raw Composite::new path and the GraphBuilder::build() path inherit it at every nesting level. The blast radius is exactly four currently-green tests (empirically probed), fixed here.

Tech Stack: crates/aura-engine/src/blueprint.rs (the check, the two CompileError variants, raw-surface tests, four flipped-test fixes), crates/aura-engine/src/builder.rs (one builder-surface test), docs/design/INDEX.md (C8 realization note).


Files this plan creates or modifies:

  • Modify: crates/aura-engine/src/blueprint.rs — add two CompileError variants (:489), add check_ports_connected + its call site in validate_wiring (:538/:546), re-wire 3 negative tests (:1382/:1452/:1473), fully wire 1 param-order test (:1877), add 5 raw-surface tests (test module).
  • Modify: crates/aura-engine/src/builder.rs — add 1 builder-surface negative test (after builder_harness_compiles_identically_to_hand_wired, ~:272).
  • Modify: docs/design/INDEX.md — add the C8 cycle-0040 realization note (after the cycle-0027 realization block, ~:316).

Task 1: The wiring-totality check (two variants + the function + call site)

Files:

  • Modify: crates/aura-engine/src/blueprint.rs:489 (CompileError), :538/:546 (validate_wiring + new fn)

  • Step 1: Add the two CompileError variants

In crates/aura-engine/src/blueprint.rs, replace:

    /// A root input role `role` has no bound source (`source: None`) — an open port
    /// at the root, which has no enclosing graph to wire it. Only a fully source-
    /// bound composite is runnable.
    UnboundRootRole { role: usize },
}

with:

    /// A root input role `role` has no bound source (`source: None`) — an open port
    /// at the root, which has no enclosing graph to wire it. Only a fully source-
    /// bound composite is runnable.
    UnboundRootRole { role: usize },
    /// An interior node's input `slot` is covered by no edge and no role target — a
    /// required port left unconnected (it would bootstrap a silent empty column).
    UnconnectedPort { node: usize, slot: usize },
    /// An interior node's input `slot` is covered by more than one edge/role target
    /// combined — a slot holds exactly one column, so >1 producer is ill-formed.
    DoubleWiredPort { node: usize, slot: usize },
}
  • Step 2: Add check_ports_connected and call it from validate_wiring

In crates/aura-engine/src/blueprint.rs, replace the tail of validate_wiring:

    // recurse into nested composites
    for item in nodes {
        if let BlueprintNode::Composite(c) = item {
            validate_wiring(c.nodes(), c.edges(), c.input_roles(), c.output())?;
        }
    }
    Ok(())
}

with (insert the call before the recursion, and define the new fn right after):

    // wiring totality: every interior input slot covered by exactly one wiring act
    check_ports_connected(nodes, edges, roles)?;
    // recurse into nested composites
    for item in nodes {
        if let BlueprintNode::Composite(c) = item {
            validate_wiring(c.nodes(), c.edges(), c.input_roles(), c.output())?;
        }
    }
    Ok(())
}

/// Every interior node's every declared input slot must be covered by exactly one
/// wiring act — one interior edge OR one role target, counted uniformly. Zero = a
/// forgotten connection (would bootstrap a silent empty column); >1 = an ill-formed
/// slot (a slot holds one column). Index-based, name-free; presupposes in-range
/// edge/role indices (runs after the existing index-range checks). Mirrors the
/// single-site shape of `check_param_namespace_injective` (the wiring-side sibling).
fn check_ports_connected(
    nodes: &[BlueprintNode],
    edges: &[Edge],
    roles: &[Role],
) -> Result<(), CompileError> {
    let mut coverage: std::collections::HashMap<(usize, usize), usize> =
        std::collections::HashMap::new();
    for e in edges {
        *coverage.entry((e.to, e.slot)).or_insert(0) += 1;
    }
    for role in roles {
        for t in &role.targets {
            *coverage.entry((t.node, t.slot)).or_insert(0) += 1;
        }
    }
    for (n, item) in nodes.iter().enumerate() {
        for slot in 0..item.signature().inputs.len() {
            match coverage.get(&(n, slot)).copied().unwrap_or(0) {
                1 => {}
                0 => return Err(CompileError::UnconnectedPort { node: n, slot }),
                _ => return Err(CompileError::DoubleWiredPort { node: n, slot }),
            }
        }
    }
    Ok(())
}
  • Step 2b: Make derive_signature bounds-total (the check exposes a latent panic)

check_ports_connected (and the existing edge/role checks) compute signature() on every interior node before the recursion validates that node's interior. For a structurally-invalid composite (an out-of-range OutField or role target), the current derive_signature indexes unguarded and panics instead of letting validate_wiring's guarded loops report OutputPortOutOfRange / BadInteriorIndex. Make the two indexers bounds-total (a placeholder kind for an invalid index; the real fault is still reported by validate_wiring).

In crates/aura-engine/src/blueprint.rs, replace the output map in derive_signature:

            let kind = c.nodes()[of.node].signature().output[of.field].kind;

with:

            // bounds-total: a structurally-invalid OutField (out-of-range node/field)
            // yields a placeholder kind, never a panic — the real fault is reported by
            // validate_wiring's guarded output check (OutputPortOutOfRange). signature()
            // is computed speculatively by the parent's edge/role/connectivity checks
            // before the recursion validates this composite's interior (cycle 0040).
            let kind = c
                .nodes()
                .get(of.node)
                .and_then(|n| n.signature().output.get(of.field).map(|f| f.kind))
                .unwrap_or(ScalarKind::F64);

and replace interior_slot_kind's body:

    nodes[t.node].signature().inputs[t.slot].kind

with:

    // bounds-total: a target into a missing node/slot yields a placeholder kind, never a
    // panic — the real fault is reported by validate_wiring's guarded index checks.
    nodes
        .get(t.node)
        .and_then(|n| n.signature().inputs.get(t.slot).map(|p| p.kind))
        .unwrap_or(ScalarKind::F64)
  • Step 3: Build and confirm exactly the four expected flips

Run: cargo build -p aura-engine Expected: builds with 0 errors (the two usize-only variants break no derive or exhaustive match; HashMap is used by its full path, no new import).

Run: cargo test -p aura-engine 2>&1 | tail -3 Expected: test result: FAILED. ... 4 failed ... — and the four are exactly bad_interior_index_rejected, role_kind_mismatch_rejected, output_port_out_of_range_rejected, and param_space_mirrors_compiled_flat_node_param_order_under_nesting. (Any other failure is an un-enumerated flip — STOP and re-survey; the probe established these four are the complete set.)


Task 2: Re-wire the three under-wired negative tests (covering root role)

Each wraps a faulty composite c in a root with empty edges + empty roles, so the new check pre-empts the deep fault with UnconnectedPort at the root level. Add a covering root role targeting c's one input slot (mirroring the existing pattern at blueprint.rs:1414). The deep fault then surfaces via the recursion (the index/kind/ output checks inside validate_wiring(c) run before c's own connectivity check), so each STILL asserts its original variant.

Files:

  • Modify: crates/aura-engine/src/blueprint.rs:1382, :1452, :1473

  • Step 1: Re-wire bad_interior_index_rejected

Replace:

        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(c)],
            vec![],
            vec![],
            vec![], // output
        );
        // the Ok arm holds Box<dyn Node> (not Debug), so assert via the Err arm.
        assert_eq!(bp.compile().err(), Some(CompileError::BadInteriorIndex));

with:

        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(c)],
            vec![],
            vec![Role { name: "src".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
            vec![], // output
        );
        // the Ok arm holds Box<dyn Node> (not Debug), so assert via the Err arm.
        assert_eq!(bp.compile().err(), Some(CompileError::BadInteriorIndex));
  • Step 2: Re-wire role_kind_mismatch_rejected

Replace:

        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(c)],
            vec![],
            vec![],
            vec![], // output
        );
        // the Ok arm holds Box<dyn Node> (not Debug), so assert via the Err arm.
        assert_eq!(bp.compile().err(), Some(CompileError::RoleKindMismatch { role: 0 }));

with:

        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(c)],
            vec![],
            vec![Role { name: "src".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
            vec![], // output
        );
        // the Ok arm holds Box<dyn Node> (not Debug), so assert via the Err arm.
        assert_eq!(bp.compile().err(), Some(CompileError::RoleKindMismatch { role: 0 }));
  • Step 3: Re-wire output_port_out_of_range_rejected

(This is the test whose deep fault is an out-of-range OUTPUT field. The covering role forces the root's role-kind loop to compute c.signature(), which only stays panic-free because of the derive_signature guard in Task 1 Step 2b; the recursion then reports OutputPortOutOfRange as before.)

Replace:

        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(c)],
            vec![],
            vec![],
            vec![], // output
        );
        // the Ok arm holds Box<dyn Node> (not Debug), so assert via the Err arm.
        assert_eq!(bp.compile().err(), Some(CompileError::OutputPortOutOfRange));

with:

        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(c)],
            vec![],
            vec![Role { name: "src".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
            vec![], // output
        );
        // the Ok arm holds Box<dyn Node> (not Debug), so assert via the Err arm.
        assert_eq!(bp.compile().err(), Some(CompileError::OutputPortOutOfRange));
  • Step 4: Verify the three pass

Run: cargo test -p aura-engine bad_interior_index_rejected Expected: PASS (1 passed). Run: cargo test -p aura-engine role_kind_mismatch_rejected Expected: PASS (1 passed). Run: cargo test -p aura-engine output_port_out_of_range_rejected Expected: PASS (1 passed).


Task 3: Fully wire the param-order nesting test

param_space_mirrors_compiled_flat_node_param_order_under_nesting compiles a deliberately under-wired strategy (LinComb's two term inputs never fed) only to read its flat param order. Fully wire it: fan fast_slow's output into both LinComb terms, and add a covering root role for strategy's price input. Wiring changes no node and no param, so the asserted space == from_flat equality is unchanged.

Files:

  • Modify: crates/aura-engine/src/blueprint.rs:1877

  • Step 1: Wire the strategy edges and the root role

In the function param_space_mirrors_compiled_flat_node_param_order_under_nesting, replace (this span is unique to that function — it ends with the "nested composite compiles" compile line, which the sibling param-order test lacks):

        let strategy = Composite::new(
            "strategy",
            vec![BlueprintNode::Composite(fast_slow), LinComb::builder(2).into()],
            vec![],
            vec![Role { name: "price".into(), targets: vec![Target { node: 0, slot: 0 }], source: None }],
            vec![OutField { node: 0, field: 0, name: "out".into() }],
        );
        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(strategy)],
            vec![],
            vec![],
            vec![], // output
        );

        // the aggregated, path-qualified projection (borrows; take it first since
        // compile() consumes self — same ordering as the single-level mirror test)
        let space = bp.param_space();

        // the same blueprint, compiled to its flat node array; each flat node's
        // own declared params, concatenated in flat-node order
        let flat = bp.compile_with_params(&[Scalar::I64(2), Scalar::I64(4), Scalar::F64(1.0), Scalar::F64(-1.0)]).expect("nested composite compiles");

with:

        let strategy = Composite::new(
            "strategy",
            vec![BlueprintNode::Composite(fast_slow), LinComb::builder(2).into()],
            // fan fast_slow's output into both LinComb terms so every interior slot
            // is wired (the totality check, cycle 0040); param order is unaffected.
            vec![
                Edge { from: 0, to: 1, slot: 0, from_field: 0 },
                Edge { from: 0, to: 1, slot: 1, from_field: 0 },
            ],
            vec![Role { name: "price".into(), targets: vec![Target { node: 0, slot: 0 }], source: None }],
            vec![OutField { node: 0, field: 0, name: "out".into() }],
        );
        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(strategy)],
            vec![],
            vec![Role { name: "price".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
            vec![], // output
        );

        // the aggregated, path-qualified projection (borrows; take it first since
        // compile() consumes self — same ordering as the single-level mirror test)
        let space = bp.param_space();

        // the same blueprint, compiled to its flat node array; each flat node's
        // own declared params, concatenated in flat-node order
        let flat = bp.compile_with_params(&[Scalar::I64(2), Scalar::I64(4), Scalar::F64(1.0), Scalar::F64(-1.0)]).expect("nested composite compiles");
  • Step 2: Verify it passes

Run: cargo test -p aura-engine param_space_mirrors_compiled_flat_node_param_order_under_nesting Expected: PASS (1 passed).


Task 4: New raw-surface tests (unwired, double-wired, nested, open-role boundary)

Add these five tests to the #[cfg(test)] mod tests block in crates/aura-engine/src/blueprint.rs (alongside the existing negative tests). They use the in-module helpers pass1() (one f64 input "in", one output) and sink_f64() (one f64 input "in", no output), and the in-scope Composite, Edge, Role, Target, OutField, BlueprintNode, CompileError, ScalarKind.

Files:

  • Modify: crates/aura-engine/src/blueprint.rs (test module)

  • Step 1: Add the five tests

    #[test]
    fn unconnected_interior_slot_rejected() {
        // pass1 is fed by a role; sink_f64's one input is left unwired -> rejected.
        let bp = Composite::new(
            "root",
            vec![pass1(), sink_f64()],
            vec![],
            vec![Role { name: "src".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
            vec![],
        );
        assert_eq!(
            bp.compile().err(),
            Some(CompileError::UnconnectedPort { node: 1, slot: 0 })
        );
    }

    #[test]
    fn double_wired_slot_rejected_edge_and_role() {
        // sink_f64's one input is targeted by BOTH an edge (from pass1) and a role.
        let bp = Composite::new(
            "root",
            vec![pass1(), sink_f64()],
            vec![Edge { from: 0, to: 1, slot: 0, from_field: 0 }],
            vec![
                Role { name: "src".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) },
                Role { name: "extra".into(), targets: vec![Target { node: 1, slot: 0 }], source: Some(ScalarKind::F64) },
            ],
            vec![],
        );
        assert_eq!(
            bp.compile().err(),
            Some(CompileError::DoubleWiredPort { node: 1, slot: 0 })
        );
    }

    #[test]
    fn double_wired_slot_rejected_two_edges() {
        // two producers' edges land on sink_f64's single input slot.
        let bp = Composite::new(
            "root",
            vec![pass1(), pass1(), sink_f64()],
            vec![
                Edge { from: 0, to: 2, slot: 0, from_field: 0 },
                Edge { from: 1, to: 2, slot: 0, from_field: 0 },
            ],
            vec![
                Role { name: "a".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) },
                Role { name: "b".into(), targets: vec![Target { node: 1, slot: 0 }], source: Some(ScalarKind::F64) },
            ],
            vec![],
        );
        assert_eq!(
            bp.compile().err(),
            Some(CompileError::DoubleWiredPort { node: 2, slot: 0 })
        );
    }

    #[test]
    fn unconnected_slot_in_nested_composite_rejected() {
        // inner composite c: pass1 (fed by c's role) + sink_f64 (interior slot
        // unwired). The root covers c's one input, so the fault surfaces only via
        // the recursion into c -> UnconnectedPort at c's interior index (1, 0).
        let c = Composite::new(
            "c",
            vec![pass1(), sink_f64()],
            vec![],
            vec![Role { name: "in".into(), targets: vec![Target { node: 0, slot: 0 }], source: None }],
            vec![OutField { node: 0, field: 0, name: "out".into() }],
        );
        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(c)],
            vec![],
            vec![Role { name: "src".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
            vec![],
        );
        assert_eq!(
            bp.compile().err(),
            Some(CompileError::UnconnectedPort { node: 1, slot: 0 })
        );
    }

    #[test]
    fn open_role_provider_is_not_flagged_unconnected() {
        // a composite whose OPEN input role (source: None) feeds its interior slot,
        // used as a nested node with that role covered by the enclosing root, must
        // compile — the open role is a provider, not an unwired consumer.
        let c = Composite::new(
            "c",
            vec![pass1()],
            vec![],
            vec![Role { name: "in".into(), targets: vec![Target { node: 0, slot: 0 }], source: None }],
            vec![OutField { node: 0, field: 0, name: "out".into() }],
        );
        let bp = Composite::new(
            "root",
            vec![BlueprintNode::Composite(c)],
            vec![],
            vec![Role { name: "src".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
            vec![],
        );
        assert!(bp.compile().is_ok(), "open role as provider must not be mis-flagged");
    }
  • Step 2: Verify the five pass

Run: cargo test -p aura-engine unconnected_interior_slot_rejected Expected: PASS (1 passed). Run: cargo test -p aura-engine double_wired_slot_rejected_edge_and_role Expected: PASS (1 passed). Run: cargo test -p aura-engine double_wired_slot_rejected_two_edges Expected: PASS (1 passed). Run: cargo test -p aura-engine unconnected_slot_in_nested_composite_rejected Expected: PASS (1 passed). Run: cargo test -p aura-engine open_role_provider_is_not_flagged_unconnected Expected: PASS (1 passed).


Task 5: Builder-surface forgotten-leg test

Mirror builder_harness_compiles_identically_to_hand_wired but omit the exposure→broker connection: every name resolves so build() succeeds, but compile_with_params rejects the uncovered broker exposure slot. Proves the GraphBuilder surface inherits the check.

Files:

  • Modify: crates/aura-engine/src/builder.rs (test module, after builder_harness_compiles_identically_to_hand_wired)

  • Step 1: Add the test

    #[test]
    fn unconnected_port_rejected_on_builder_surface() {
        use crate::CompileError;
        use aura_core::Scalar;
        // The forgotten-exposure-leg harness: every port/field name resolves, so
        // build() succeeds; but broker.input("exposure") is never connected, so the
        // wiring-totality check rejects it at compile (broker is node 1, slot 0).
        let (tx_eq, _r1) = mpsc::channel();
        let mut g = GraphBuilder::new("root");
        let expo = g.add(Exposure::builder());
        let broker = g.add(SimBroker::builder(0.0001));
        let eq = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq));
        let price = g.source_role("price", ScalarKind::F64);
        g.feed(price, [expo.input("signal"), broker.input("price")]);
        // BUG: g.connect(expo.output("exposure"), broker.input("exposure")) missing.
        g.connect(broker.output("equity"), eq.input("col[0]"));
        let compiled = g
            .build()
            .expect("all port/field names resolve")
            .compile_with_params(&[Scalar::F64(0.5)]);
        assert_eq!(
            compiled.err(),
            Some(CompileError::UnconnectedPort { node: 1, slot: 0 })
        );
    }
  • Step 2: Verify it passes

Run: cargo test -p aura-engine unconnected_port_rejected_on_builder_surface Expected: PASS (1 passed).


Task 6: C8 ledger realization note

Files:

  • Modify: docs/design/INDEX.md:316 (after the cycle-0027 realization block, before the blank line preceding ### C9)

  • Step 1: Insert the cycle-0040 realization note

Find the end of the C8 "Realization (cycle 0027 — name input ports…)" block (the paragraph ending "…a name-consuming validation is its own future cycle.", ~line 316), and insert a blank line then this paragraph immediately after it:

**Realization (cycle 0040 — wiring totality: every input slot connected exactly
once, #65).** The node contract's "the engine provides a window into each input"
presupposes each input is actually fed; until now an unwired interior input slot was
accepted and bootstrapped to a silent empty column (`harness.rs`), and two producers
into one slot — ill-formed, since a slot holds one column — was likewise
uncompiled-against. Cycle 0040 makes a valid graph **total and single-valued** over
its interior input slots: `check_ports_connected` (in `validate_wiring`, run at every
nesting level) requires every interior node's every declared input slot to be covered
by **exactly one** wiring act — one interior `Edge { to, slot }` or one role
`Target { node, slot }`, edges and role targets counted uniformly. Zero coverage is
`CompileError::UnconnectedPort`, more than one is `CompileError::DoubleWiredPort`. A
composite's **own** input roles (`source: None`) are coverage *providers* — the
wired-by-enclosing boundary, the root case already guarded by `UnboundRootRole` —
never consumers, so only interior input slots are subject to the rule. There is **no
optional-input concept**: every declared input port is required (no shipped node runs
meaningfully without one; an unwarmed mode-A input is *wired-but-not-yet-valued*, not
unwired). The check is **index-based and name-free** — it touches no name machinery
and emits nothing into the flat graph, so C23 is untouched (it proves the existing
raw-index wiring is total and single-valued). Inherited identically by the raw
`Composite::new` path and the `GraphBuilder::build()` path (both compile via
`compile_with_params`).
  • Step 2: Confirm the note landed

Run: grep -c "cycle 0040 — wiring totality" docs/design/INDEX.md Expected: 1


Task 7: Full-suite + lint gate

Files: none (verification only)

  • Step 1: Full workspace test

Run: cargo test --workspace 2>&1 | grep -E "test result:" | grep -v "0 failed" | head Expected: no output (every test result: line reports 0 failed).

  • Step 2: Clippy

Run: cargo clippy --workspace --all-targets -- -D warnings Expected: finishes with 0 warnings/errors.