Files
Brummel 3f75d59598 audit(0090): cycle + C24 milestone close — fieldtest green; ledger + rm ephemeral spec/plan
Closes the cycle-0090 (#156) work and delivers the "Topology-as-data:
blueprint serialization & loader (C24)" milestone.

Cycle-0090 audit (architect drift review, range 7ad7f58..HEAD): drift-clean
on substance. Tier-1 forward-tolerance is genuinely serde-default
(deny_unknown_fields = 0 in every production struct), pinned at envelope +
primitive across both the in-crate and public-seam tests; Tier-2 refusals
real and green; no struct/signature/runtime change; the invariant-5
acyclicity lockstep (construction gate <-> bootstrap Kahn sort) undisturbed;
invariant-9 resolver stays injected, no registry. The one medium item the
architect flagged — the C24 Remaining block carried no deferral signal for
#158/#159 — is fixed in this commit's ledger update.

Milestone fieldtest (fieldtests/milestone-topology-as-data/, the close-gate
evidence): GREEN, 0 behavioural bugs. A downstream consumer ran the full
author -> serialize -> load -> construct -> introspect -> reproduce story from
the public surface alone — round-trip bit-exact on both authoring surfaces
(Rust builder + op-script CLI), build-free introspection, Tier-1/Tier-2
forward-compat by name, fail-fast diagnostics by identifier. Verified the
headline claims myself (the two Rust binaries + the CLI build/diff + the
fail-fast exits), not just the agent's report.

Findings triaged to the forward queue (none blocking): op-script grammar
undocumented + a stale cycle-0088 example (#163); canonical trailing-newline
divergence + Composite Debug/PartialEq ergonomics (#164); CLI discoverability
of build/introspect + no `graph run` (commented on #159); the newline as a
content-addressing prerequisite (commented on #158).

#158 (content-address topology in the manifest) and #159 (retire the
hard-wired harnesses) were moved OUT of this milestone: both are premature
until the World generates runs from blueprint-data (commit still fully
identifies every hard-wired topology), so they belong to the
project-as-crate / World cycle. The C24 ledger Status records the delivery
and the deferral.

Ephemeral cycle-0090 spec + plan removed per the lifecycle convention.
The Gitea milestone container is closed on user ratification (this run
tees up the close, does not click it).
2026-06-30 14:43:28 +02:00

13 KiB

Fieldtest — milestone: Topology-as-data (C24) — 2026-06-30

Status: Draft — awaiting orchestrator triage Author: fieldtester (dispatched by fieldtest skill)

Scope

The milestone-close gate for C24 — the blueprint is a serializable, World-owned data value. The milestone promises a downstream researcher can run the whole topology-as-data story end to end: author a param-generic graph with the Rust builder, serialize it to a canonical versioned data value, load it back (data → blueprint → runnable), construct the same topology declaratively through the CLI op-script (aura graph build), introspect the closed node vocabulary without a build (aura graph introspect), and reproduce a run bit-identically (C1). Spanning cycles 0087 (#155 serde first-cut), 0088 (#157 construction service), 0089 (#161/#162 diagnostics + acyclicity), 0090 (#156 forward-compat two-tier).

Run entirely from the public interface: the C24 ledger entry, cargo doc doc-comments, aura graph introspect's own output, and the existing op-script fixture shape. No crates/*/src was read. Artefact under test: target/debug/aura + the engine path-dep crates, both rebuilt from HEAD (cargo build --workspace) before the run.

Examples

All fixtures under fieldtests/milestone-topology-as-data/.

mt_1_roundtrip.rs — Rust round-trip + run identity (tasks 1)

  • Authors an SMA(2)/SMA(4)-cross bias signal with GraphBuilder (source_roleSma::builder().named().bind()SubBiasexpose), serializes with blueprint_to_json, loads with blueprint_from_json + aura_std::std_vocabulary, re-serializes, and runs both the original and the reloaded twin through an identical Recorder harness.
  • Fits scope: the headline C24 acceptance (serialize → load → reproduce).
  • Outcome: built, ran, matched. (a) canonical JSON round-trips byte-identically (647 bytes, serialize→load→re-serialize equal). (b) 9 recorded bias rows value-for-value identical between original and reload (C1).

mt_2_sma_cross.ops.json + mt_2_blueprint.cli.json — declarative construction + cross-surface identity (task 2)

  • The same topology as a CLI op-script; aura graph build < ops.json emits the #155 blueprint. Compared against mt_1_rust_blueprint.json (the library emit).
  • Fits scope: C1 identity across authoring surfaces (Rust builder vs op-script).
  • Outcome: built, ran, matched (modulo one byte). The two blueprints are character-for-character identical except the CLI appends a trailing \n (648 vs 647 bytes). See spec_gap below.

mt_3_*.out + mt_3_partial.ops.json — build-free introspection (task 3)

  • introspect --vocabulary (22 closed types), --node SMA|Sub|Bias|EMA|RollingMax (ports, kinds, bind form), and --unwired on a partial op-list.
  • Fits scope: discover the vocabulary / a node's ports / unwired slots without a build.
  • Outcome: ran, matched. --unwired on a partial doc reports sub.rhs:F64 (the one open slot, by identifier, no build). --node LinComb/Recorder correctly refuse (unknown node type, exit 2) — they are outside the round-trippable set, as the ledger states.

mt_4_forward_compat.rs — forward-compat Tier-1/Tier-2 + cross-surface interop (tasks 4)

  • Loads the CLI-emitted blueprint via the library loader (cross-surface interop); injects unknown optional fields at envelope and node level (Tier-1); bumps format_version to 999 and swaps a node type to FOO (Tier-2).
  • Fits scope: the #156 two-tier discipline from the loader's seat.
  • Outcome: built, ran, matched. CLI blueprint loaded + ran identically (trailing newline tolerated). Tier-1 unknown fields tolerated, dropped on re-emit (back to canonical), ran bit-identical. Tier-2 refused by nameUnsupportedVersion { found: 999, supported: 1 } and UnknownNodeType("FOO") — no panic, no silent wrong graph.

mt_5{a,b,c}_*.ops.json + .err/.exit — fail-fast CLI diagnostics (task 5)

  • Three malformed op-lists fed to aura graph build: unknown node type, a connect that closes a cycle, an unconnected input slot.
  • Fits scope: invalid construction must fail fast, by identifier, non-zero exit.
  • Outcome: ran, matched. Each reported by identifier with empty stdout and exit 2 (verbatim below).

Findings

[working] The C24 round-trip is real and bit-exact, both surfaces

  • Examples: mt_1, mt_2, mt_4.
  • What happened: a builder-authored signal serialized → loaded → re-serialized byte-identically (647 bytes) and the reload ran value-for-value identical to its twin (9 bias rows). The CLI op-script emitted a blueprint whose content is character-identical to the library emit, and that CLI blueprint loaded through the library loader and ran identically.
  • Why working: the milestone's central promise (author → serialize → load → reproduce, identical across the Rust and CLI authoring surfaces) holds on first try from the public surface alone.
  • Recommended action: carry-on. These are the C24 acceptance pins, empirically confirmed downstream.

[working] Build-free introspection answers the three discovery questions

  • Examples: mt_3.
  • What happened: --vocabulary listed the closed 22-type set; --node surfaced ports/kinds and the exact bind form (param length:I64 (bind {"I64": <v>})); --unwired named the single open slot sub.rhs:F64 with no build. Out-of-vocabulary --node LinComb/Recorder refused cleanly (exit 2).
  • Why working: a consumer can discover the entire authorable surface (what nodes exist, each node's ports, what a partial doc still needs) without compiling anything — exactly the #157 introspection promise.
  • Recommended action: carry-on.

[working] Forward-compat + fail-fast diagnostics are by-name / by-identifier

  • Examples: mt_4, mt_5.
  • What happened: Tier-1 unknown fields tolerated and dropped on re-emit; Tier-2 refused by name with values (found: 999, supported: 1; "FOO"). The three build failures each named the cause at the offending site: op 1 (add): unknown node type "FOO"; op 5 (connect): connecting b.value -> a.rhs would close a cycle; finalize: slot sub.rhs is unconnected — all exit 2, all with empty stdout (no half-emitted blueprint).
  • Why working: every refusal is actionable and points at an identifier, never a raw index or a panic — the #161/#162 polish landed and holds downstream.
  • Recommended action: carry-on.

[friction] The construction surface is undiscoverable from the CLI's own help

  • Examples: observed probing aura --help, aura graph --help, aura graph.
  • What happened: top-level usage lists only aura graph (no build / introspect). aura graph --help reprints the same top-level usage. aura graph with no subcommand silently streams a ~1.4 MB HTML viewer to stdout and exits 0. The only way I learned build/introspect existed was the C24 ledger; the only way I learned the introspect flags was triggering its usage line by passing a bad arg.
  • Why friction: the task completed, but a consumer with only the binary cannot find the milestone's headline CLI surface. (Known/filed as #159.)
  • Recommended action: plan — add a graph subcommand help listing build | introspect and their flags; make bare aura graph print usage rather than dump HTML to a TTY.

[friction] The op-script JSON schema is documented nowhere durable; the only example is stale

  • Examples: mt_2 (and the failed build of fieldtests/cycle-0088-…/c0088_1).
  • What happened: no durable public doc (ledger, glossary, README) states the op-list grammar — the verbs (source/add/feed/connect/expose) or their keys. aura graph build --help does not exist (empty input just prints a JSON parse error). The only worked example is the cycle-0088 fixture corpus, which still uses the retired as key for add; replaying it verbatim fails with op 2 (add): duplicate identifier "sma" because add's naming key was renamed as → name (commit feat: construction add names a node via name). A consumer copying the visible example hits this immediately.
  • Why friction: I had to reconstruct the correct schema ("name" not "as" for add; "as" retained only for expose) by cross-reading the commit log — a downstream consumer reading docs + the example corpus would be misled.
  • Recommended action: plan / docwriter — document the op-script grammar in a durable doc (or aura graph build --help), and either refresh or clearly date-stamp the cycle-0088 corpus as pre-rename.

[friction] Closing the loop to a run drops out of the data surface into hand-wired Rust

  • Examples: mt_1, mt_4 (the Recorder wrapper).
  • What happened: the serializable unit is the sink-less signal composite (a Recorder captures an mpsc::Sender, deliberately outside the round-trippable set). To observe a reloaded blueprint actually running, I had to drop to Rust, hand-author a wrapper Composite by raw index, attach a Recorder, and bootstrap. There is no aura graph run <blueprint.json> (cf. #28): the CLI can author and emit topology but cannot run an emitted blueprint or show its trace.
  • Why friction: the milestone's "data → blueprint → runnable" is library-complete but, from the CLI alone, dead-ends at the emitted JSON; the reproduce step requires writing Rust and re-introducing a non-serializable sink. (Intended per the ledger, but a real seam a consumer feels.)
  • Recommended action: plan — track under the #28/#159 build→consume edge (a CLI consumer that loads a blueprint, attaches a sink/tap, and runs).

[spec_gap] "Canonical" form: CLI graph build appends a trailing newline the library emit omits

  • Examples: mt_2 (mt_2_blueprint.cli.json 648 B vs mt_1_rust_blueprint.json 647 B; last bytes 5d 7d 7d 0a vs 7d 5d 7d 7d).
  • What happened: both paths emit the documented "canonical, versioned" blueprint, byte-identical in content, but the CLI frames it with a trailing \n and the library blueprint_to_json does not. The ledger calls the format canonical and lists content-addressed topology identity in the manifest (#158) as remaining — yet a content hash of the same blueprint differs by which surface emitted it.
  • Why spec_gap: the ledger does not constrain whether the canonical artifact includes a trailing newline; I picked the reading "the JSON content is canonical, the CLI's newline is incidental framing," but the reading "the CLI's stdout is the canonical on-disk artifact" is equally plausible, and the two disagree by one byte — load-bearing once #158 content-addresses topology.
  • Repro: diff <(aura graph build < mt_2_sma_cross.ops.json) mt_1_rust_blueprint.json → differs only by the final newline.
  • Recommended action: ratify / tighten the ledger — state whether canonical includes a trailing newline and make the two emit paths byte-identical before #158 lands.

[spec_gap] Composite (the loaded value) has no Debug / PartialEq

  • Examples: mt_4 (a match had to avoid {:?}-ing the Ok(Composite) arm; a direct assert_eq! on two loaded composites is impossible).
  • What happened: blueprint_from_json returns Result<Composite, LoadError>, but Composite derives neither Debug nor PartialEq, so unwrap_err(), {:?}, and composite-level equality assertions don't compile. A consumer must route every identity/round-trip assertion through blueprint_to_json (string compare), which is what I did.
  • Why spec_gap: the ledger frames the blueprint as a first-class data value the World "owns, stores, mutates, structurally searches" — value-like ergonomics (compare, debug-print) are implied but unspecified, and the type as shipped supports neither.
  • Recommended action: ratify / tighten — decide whether the public Composite value should carry Debug/PartialEq (string-via-JSON is the current contract); document the intended equality surface.

Recommendation summary

Finding Class Action
C24 round-trip bit-exact, both surfaces working carry-on
Build-free introspection (vocab/node/unwired) working carry-on
By-name / by-identifier forward-compat + fail-fast working carry-on
Construction surface undiscoverable from CLI help friction plan (#159)
Op-script schema undocumented; example stale on asname friction plan / docwriter
Run-the-loop requires hand-wired Rust sink (no graph run) friction plan (#28/#159)
Trailing-newline divergence vs "canonical" / #158 spec_gap ratify / tighten
Composite value lacks Debug/PartialEq spec_gap ratify / tighten