Polish construction error/bind ergonomics — by-identifier finalize, prose bind-mismatch, discoverable bind form #162

Closed
opened 2026-06-29 22:15:33 +02:00 by Brummel · 1 comment
Owner

The cycle-0088 fieldtest (fieldtests/cycle-0088-construction-op-script/FINDINGS.md) confirmed the construction op-script surface (#157) authors cleanly by-identifier, but three error/bind presentation defects break that register at the edges. All three are message-quality only -- the faults are correctly detected and exit non-zero.

1. The holistic finalize error speaks raw machine indices

aura graph build < c0088_2_partial.json prints:

aura: finalize: UnconnectedPort { node: 2, slot: 1 }

The sibling introspect --unwired reports the same open slot by-identifier as sub.rhs:F64, so the identifier mapping demonstrably exists at finalize time. The build finalize path drops to the raw node/slot index Debug form, forcing the author to count nodes (0=fast, 1=slow, 2=sub) to decode it. Route finalize faults through the same by-identifier formatter --unwired already uses.

2. Bind-kind-mismatch leaks the #[derive(Debug)] struct

aura graph build < c0088_3g_bad_bind_kind.json prints:

op 1 (add): bad param on fast: KindMismatch { param: "length", expected: I64, got: F64 }

The parallel connect-mismatch is hand-phrased prose (producer Bool, consumer F64). Two kind-mismatch errors, two registers -- phrase the bind mismatch like the connect one.

3. The typed-Scalar bind form is not discoverable

introspect --node SMA shows param length:I64 but never that a bind value is wrapped {"I64": 2}. The natural first guess "bind":{"length":2} fails with a generic serde error (invalid type: integer 2, expected string or map) carrying no domain hint about the {"I64": <v>} shape. Show the bind-value form in introspect --node (e.g. param length:I64 (bind {"I64": <v>})) or hint it in the serde error. (Capitalized kind is already discoverable, both from introspect --node and the self-documenting lowercase-kind error.)

Source: cycle-0088 fieldtest. Low-urgency polish on a shipped surface; the happy path and eager per-op diagnostics already read well.

The cycle-0088 fieldtest (`fieldtests/cycle-0088-construction-op-script/FINDINGS.md`) confirmed the construction op-script surface (#157) authors cleanly by-identifier, but three error/bind presentation defects break that register at the edges. All three are message-quality only -- the faults are correctly detected and exit non-zero. ## 1. The holistic `finalize` error speaks raw machine indices `aura graph build < c0088_2_partial.json` prints: ``` aura: finalize: UnconnectedPort { node: 2, slot: 1 } ``` The sibling `introspect --unwired` reports the *same* open slot by-identifier as `sub.rhs:F64`, so the identifier mapping demonstrably exists at finalize time. The build finalize path drops to the raw `node`/`slot` index Debug form, forcing the author to count nodes (0=fast, 1=slow, 2=sub) to decode it. Route finalize faults through the same by-identifier formatter `--unwired` already uses. ## 2. Bind-kind-mismatch leaks the `#[derive(Debug)]` struct `aura graph build < c0088_3g_bad_bind_kind.json` prints: ``` op 1 (add): bad param on fast: KindMismatch { param: "length", expected: I64, got: F64 } ``` The parallel connect-mismatch is hand-phrased prose (`producer Bool, consumer F64`). Two kind-mismatch errors, two registers -- phrase the bind mismatch like the connect one. ## 3. The typed-Scalar bind form is not discoverable `introspect --node SMA` shows `param length:I64` but never that a bind *value* is wrapped `{"I64": 2}`. The natural first guess `"bind":{"length":2}` fails with a generic serde error (`invalid type: integer 2, expected string or map`) carrying no domain hint about the `{"I64": <v>}` shape. Show the bind-value form in `introspect --node` (e.g. `param length:I64 (bind {"I64": <v>})`) or hint it in the serde error. (Capitalized `kind` is already discoverable, both from `introspect --node` and the self-documenting lowercase-kind error.) Source: cycle-0088 fieldtest. Low-urgency polish on a shipped surface; the happy path and eager per-op diagnostics already read well.
Author
Owner

Design decisions (boss run — spec production for this issue)

Two derived forks, recorded before the spec is written.

  • Fork: where to translate the raw node/slot/role index into the by-identifier form for a finalize fault. → Derived: in GraphSession::finish() (crates/aura-engine/src/construction.rs), not in the CLI presenter. Rationale: the construction layer owns the id↔name map (ids, schemas, the composite's input_roles()); the engine OpError already carries by-identifier data for every other variant and the CLI's format_op_error is a thin presenter over that data — doing the translation in finish() keeps that split intact, whereas threading the name map into the CLI would invert it. The holistic gates (validate_wiring, check_param_namespace_injective, check_root_roles_bound) stay byte-for-byte unchanged; only finish()'s wrapping of their CompileError changes.

  • Fork: which finalize faults to map by-identifier. → Derived: the three index-carrying holistic faults reachable on a well-formed-but-incomplete authored op-list — UnconnectedPort { node, slot } (the observed sub.rhs totality case), RoleKindMismatch { role } (reachable — feed does not eager-check the fanned-in slot kinds, construction.rs feed() only resolves + coverage-checks), and UnboundRootRole { role } (an Input role left unbound at the root). Each becomes a new by-identifier OpError variant carrying String ids. The remaining CompileError cases stay OpError::Incomplete: DoubleWiredPort is caught eagerly by cover()/feed (SlotAlreadyWired), so it is unreachable at finish; DuplicateParamPath already carries a by-identifier path string; BadInteriorIndex / OutputPortOutOfRange / Bootstrap are construction-internal inconsistencies a validly-replayed op-list does not produce (and carry no per-node identifier). Net validity-floor statement: after this change, every finalize fault a hand-author can actually trigger reads by-identifier.

Items 2 (bind-mismatch prose) and 3 (discoverable bind form) are CLI-presenter-only changes in graph_construct.rs with no fork.

## Design decisions (boss run — spec production for this issue) Two derived forks, recorded before the spec is written. - **Fork: where to translate the raw node/slot/role index into the by-identifier form for a finalize fault.** → Derived: in `GraphSession::finish()` (crates/aura-engine/src/construction.rs), not in the CLI presenter. Rationale: the construction layer owns the id↔name map (`ids`, `schemas`, the composite's `input_roles()`); the engine `OpError` already carries by-identifier *data* for every other variant and the CLI's `format_op_error` is a thin presenter over that data — doing the translation in `finish()` keeps that split intact, whereas threading the name map into the CLI would invert it. The holistic gates (`validate_wiring`, `check_param_namespace_injective`, `check_root_roles_bound`) stay byte-for-byte unchanged; only `finish()`'s wrapping of their `CompileError` changes. - **Fork: which finalize faults to map by-identifier.** → Derived: the three index-carrying holistic faults reachable on a well-formed-but-incomplete authored op-list — `UnconnectedPort { node, slot }` (the observed `sub.rhs` totality case), `RoleKindMismatch { role }` (reachable — `feed` does not eager-check the fanned-in slot kinds, construction.rs feed() only resolves + coverage-checks), and `UnboundRootRole { role }` (an `Input` role left unbound at the root). Each becomes a new by-identifier `OpError` variant carrying `String` ids. The remaining `CompileError` cases stay `OpError::Incomplete`: `DoubleWiredPort` is caught eagerly by `cover()`/`feed` (`SlotAlreadyWired`), so it is unreachable at finish; `DuplicateParamPath` already carries a by-identifier path string; `BadInteriorIndex` / `OutputPortOutOfRange` / `Bootstrap` are construction-internal inconsistencies a validly-replayed op-list does not produce (and carry no per-node identifier). Net validity-floor statement: after this change, every finalize fault a hand-author can actually trigger reads by-identifier. Items 2 (bind-mismatch prose) and 3 (discoverable bind form) are CLI-presenter-only changes in `graph_construct.rs` with no fork.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#162