3f75d59598
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).
211 lines
13 KiB
Markdown
211 lines
13 KiB
Markdown
# 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_role` → `Sma::builder().named().bind()` → `Sub` → `Bias` → `expose`),
|
|
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 name** —
|
|
`UnsupportedVersion { 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 `as`→`name` | 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 |
|