Tighten the canonical blueprint contract: byte-exact emit across surfaces + value ergonomics #164

Closed
opened 2026-06-30 14:36:27 +02:00 by Brummel · 1 comment
Owner

The 2026-06-30 milestone fieldtest (fieldtests/milestone-topology-as-data/) surfaced two underspecified corners of the C24 "canonical, versioned" blueprint contract. Neither is a behavioural bug; both are contract gaps worth tightening.

  1. Trailing-newline divergence across emit surfaces. aura graph build frames its output with a trailing \n; the library blueprint_to_json does not (648 vs 647 bytes; content character-identical). Validated: diff <(aura graph build < ops.json) lib_emit.json differs only by the final newline. The ledger calls the format "canonical" but does not state whether the canonical artifact includes a trailing newline. This becomes load-bearing once content-addressed topology identity lands (#158): a content hash of the same blueprint would differ by which surface emitted it.

  2. The loaded Composite value carries no Debug / PartialEq. blueprint_from_json returns Result<Composite, LoadError>, but Composite derives neither, so {:?}, unwrap_err(), and composite-level equality assertions do not compile — every identity / round-trip check must route through a blueprint_to_json string compare. 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.

Acceptance:

  • The canonical blueprint artifact's trailing-newline rule is stated, and the CLI and library emit paths are byte-identical (a prerequisite for #158 content-addressing).
  • A decision is recorded on whether the public Composite value carries Debug/PartialEq, or that JSON-string-via-blueprint_to_json is the intended equality surface.
The 2026-06-30 milestone fieldtest (`fieldtests/milestone-topology-as-data/`) surfaced two underspecified corners of the C24 "canonical, versioned" blueprint contract. Neither is a behavioural bug; both are contract gaps worth tightening. 1. **Trailing-newline divergence across emit surfaces.** `aura graph build` frames its output with a trailing `\n`; the library `blueprint_to_json` does not (648 vs 647 bytes; content character-identical). Validated: `diff <(aura graph build < ops.json) lib_emit.json` differs only by the final newline. The ledger calls the format "canonical" but does not state whether the canonical artifact includes a trailing newline. This becomes load-bearing once content-addressed topology identity lands (#158): a content hash of the same blueprint would differ by which surface emitted it. 2. **The loaded `Composite` value carries no `Debug` / `PartialEq`.** `blueprint_from_json` returns `Result<Composite, LoadError>`, but `Composite` derives neither, so `{:?}`, `unwrap_err()`, and composite-level equality assertions do not compile — every identity / round-trip check must route through a `blueprint_to_json` string compare. 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. Acceptance: - [ ] The canonical blueprint artifact's trailing-newline rule is stated, and the CLI and library emit paths are byte-identical (a prerequisite for #158 content-addressing). - [ ] A decision is recorded on whether the public `Composite` value carries `Debug`/`PartialEq`, or that JSON-string-via-`blueprint_to_json` is the intended equality surface.
Brummel added the idea label 2026-06-30 14:36:27 +02:00
Author
Owner

Design decisions (cycle 0091, #164)

Two derivable forks, resolved with rationale. The second discharges acceptance
box 2's "or" arm.

Fork 1 — the canonical artifact carries no trailing newline. The library
blueprint_to_json returns the JSON value with no trailing newline (647 bytes
for the SMA-cross fixture); aura graph build added one via println! (648).
Resolution: the library serializer is the single canonical source, and
aura graph build switches to print! to emit those exact bytes. Basis
(derived): content-addressed topology identity (#158) is defined as a hash over
the canonical blueprint_to_json form, so the CLI is a transport that must not
mutate the canonical bytes, and a JSON value carries no line terminator. The
cycle-0088 .out.json goldens are re-recorded without the trailing newline; the
rule is stated in the C24 ledger entry. The loader keeps tolerating a trailing
newline (lenient parse, Tier-1 robustness), so a newline-bearing input still
loads.

Fork 2 — the canonical JSON is the blueprint equality/identity surface; no
second in-memory PartialEq / Debug is added.
Composite / BlueprintNode
cannot derive Debug / PartialEq: the underlying PrimitiveBuilder
(aura-core node.rs) holds a build: Box<dyn Fn(&[Cell]) -> Box<dyn Node>>
closure, which is neither Debug nor PartialEq. Equality could therefore only
be defined over the data fields (type identity + schema + bound params) via
manual impls eliding the closure. Resolution: do not add a second equality
notion; blueprint_to_json is the canonical equality / identity surface.
Basis
(derived): the canonical serialization already defines blueprint identity (it is
exactly what #158 content-addresses and what round-trip checks compare); a
separate in-memory PartialEq would be a redundant second source of truth that
must be kept in lockstep with the serialization — a drift hazard for no
functional gain. Test-side identity / round-trip checks compare the canonical
JSON string. This records the decision under acceptance box 2's "or
JSON-string-via-blueprint_to_json is the intended equality surface" arm.

## Design decisions (cycle 0091, #164) Two derivable forks, resolved with rationale. The second discharges acceptance box 2's "or" arm. **Fork 1 — the canonical artifact carries no trailing newline.** The library `blueprint_to_json` returns the JSON value with no trailing newline (647 bytes for the SMA-cross fixture); `aura graph build` added one via `println!` (648). Resolution: the **library serializer is the single canonical source**, and `aura graph build` switches to `print!` to emit those exact bytes. Basis (derived): content-addressed topology identity (#158) is defined as a hash over the canonical `blueprint_to_json` form, so the CLI is a transport that must not mutate the canonical bytes, and a JSON value carries no line terminator. The cycle-0088 `.out.json` goldens are re-recorded without the trailing newline; the rule is stated in the C24 ledger entry. The loader keeps tolerating a trailing newline (lenient parse, Tier-1 robustness), so a newline-bearing input still loads. **Fork 2 — the canonical JSON is the blueprint equality/identity surface; no second in-memory `PartialEq` / `Debug` is added.** `Composite` / `BlueprintNode` cannot derive `Debug` / `PartialEq`: the underlying `PrimitiveBuilder` (`aura-core` `node.rs`) holds a `build: Box<dyn Fn(&[Cell]) -> Box<dyn Node>>` closure, which is neither `Debug` nor `PartialEq`. Equality could therefore only be defined over the *data* fields (type identity + schema + bound params) via manual impls eliding the closure. Resolution: **do not add a second equality notion; `blueprint_to_json` is the canonical equality / identity surface.** Basis (derived): the canonical serialization already defines blueprint identity (it is exactly what #158 content-addresses and what round-trip checks compare); a separate in-memory `PartialEq` would be a redundant second source of truth that must be kept in lockstep with the serialization — a drift hazard for no functional gain. Test-side identity / round-trip checks compare the canonical JSON string. This records the decision under acceptance box 2's "or JSON-string-via-`blueprint_to_json` is the intended equality surface" arm.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#164