First slice of declared taps: a Tap { name, from: TapWire { node, field } }
is the output-side twin of input_roles — a pure data declaration naming an
interior output wire, no sink endpoint. Composite gains a taps field
(with_taps/taps(), additive) and the serde mirror CompositeData carries it
(Tier-1 additive: no format-version bump, absent-taps documents byte-stable).
Tap/TapWire are re-exported from the crate root beside Role/OutField so
downstream crates can author tap-bearing blueprints.
refs #282
The blueprint data format (C24) extends additively under one codified
two-tier discipline:
- Tier-1 (additive-optional): a new optional field/section is tolerated by an
older reader (serde ignores unknown fields — no deny_unknown_fields), with no
format_version bump; by C1 a new optional field defaults to prior behaviour,
so an old blueprint reproduces the same graph.
- Tier-2 (must-understand: a new node type, edge semantics, or structural-axis
kind): bumps format_version, so an old reader refuses cleanly
(LoadError::UnsupportedVersion / UnknownNodeType) rather than silently
building a different graph.
Most of #156 already shipped with #155 (cycle 0087): the envelope, omit-defaults
canonical form, and both Tier-2 load-path refusals were present and green. This
cycle pins the one previously-unproven property — Tier-1 forward-tolerance of an
unknown optional field on the current loader — and codifies the discipline.
Changes:
- New characterization pin (in-crate, blueprint_serde.rs):
unknown_optional_field_is_tolerated_byte_identically — an unknown optional key
at doc and primitive level loads byte-identically and runs bit-identically.
- Two public-seam E2E pins (blueprint_serde_e2e.rs, added by the E2E phase):
unknown_envelope_field_tolerated_through_public_api and
unknown_primitive_field_tolerated_through_public_api — split the two distinct
tolerance surfaces (envelope vs primitive) across the World/cdylib crate
boundary, where a deny_unknown_fields added to one would break forward-compat
while leaving the in-crate test green.
- LoadError::UnsupportedVersion doc reworded from the deferred "is #156" note to
the settled two-tier discipline statement.
- C24 ledger (docs/design/INDEX.md): Status codification sentence added; #156
dropped from Remaining (#158/#159 stay).
Derived fork (recorded on #156): the per-section required-flag mechanism (PNG
critical-vs-ancillary) is NOT built — the version envelope already gives the
must-understand refusal at version granularity, no current Tier-2 section
validates a finer scheme, and C1 holds either way.
The pin is a characterization test (GREEN on first run) — it documents existing
serde silent-ignore behaviour, no production code changed. Verified: cargo test
-p aura-engine green (all targets, three new tests confirmed by name); cargo
clippy --workspace --all-targets -- -D warnings clean.
C24 first cut (#155): a blueprint — the param-generic, named graph-as-data a
Rust builder produces — is now a first-class serializable data value with a
canonical, versioned format, and the engine has the missing load path
(data -> blueprint -> FlatGraph), not only the Rust-builder construction path.
What ships:
- `blueprint_to_json` / `blueprint_from_json` in a new `aura-engine`
blueprint_serde module: a faithful serde DTO projection (the build closures
dropped; re-derived on load), top-level `format_version` envelope, canonical
compact JSON (struct field order, defaults omitted via skip_serializing_if).
- A resolver seam: the loader is generic over an injected
`Fn(&str) -> Option<PrimitiveBuilder>`; the concrete closed `match` over the
aura-std vocabulary lives in aura-std (`std_vocabulary`), never in the engine
(the engine stays domain-free: lib deps aura-core + aura-analysis only, never
the node-vocabulary crate). This is C24's compiled-in closed-set referenced by
type identity, NOT a dynamic/marketplace node registry (domain invariant 9).
- serde derives on the serialized plain types (Edge, Target, OutField, Role,
BoundParam, ScalarKind); BoundParam additionally re-exported from aura-core's
root (an additive fix the plan's file list missed).
Load-bearing scope decisions (derived, logged on #155):
- Sinks are excluded from the serialized blueprint — a recording sink captures an
mpsc::Sender (runtime identity, not param/topology, C19 value-empty); sink
addressing is the C18-registry / #101 concern. The round-trip test attaches
identical sinks post-load to both twins.
- Construction-arg builders (LinComb(arity), CostSum(n), SimBroker(pip),
Session(..)) are out of the round-trippable set: their structural args are a
C20 structural-axis concern the param-generic format does not yet encode; an
absent type_id fails the load cleanly (UnknownNodeType), never a silent wrong
graph. #155's vocabulary is the 22 zero-arg aura-std builders. Additively
extensible later (#156).
Orchestrator hardening on review: the serializer now emits bound params in
ascending original-slot order (mirroring the loader), so the canonical form is
bind-order-independent — a precondition for content-addressing (#158). Identity
today (every #155 node has <=1 param); holds by construction for future
multi-param nodes.
Acceptance (all green): a serialized blueprint runs bit-identical (C1) to its
Rust-built twin; serializer/loader are inverse (canonical idempotence, incl. a
recursive nested composite); unknown-type and unsupported-version fail named,
never panicking. cargo test --workspace 748 passed; clippy --all-targets
-D warnings clean.
closes#155