Files
Aura/docs/plans/0090-blueprint-forward-compat.md
T
Brummel ead064df50 plan: 0090 blueprint forward-compat
Single-task iteration for #156: append the Tier-1 forward-tolerance
characterization test (unknown_optional_field_is_tolerated_byte_identically),
reword the LoadError::UnsupportedVersion doc to the settled two-tier
discipline, and codify it in the C24 ledger (Status sentence + drop #156 from
Remaining). No struct/signature/runtime change. refs #156
2026-06-30 13:16:21 +02:00

8.5 KiB

Blueprint format forward-compatibility: the two-tier discipline — Implementation Plan

Parent spec: docs/specs/0090-blueprint-forward-compat.md

For agentic workers: REQUIRED SUB-SKILL: use the implement skill to run this plan. Steps use - [ ] checkboxes for tracking.

Goal: Pin the blueprint format's Tier-1 additive forward-tolerance with one characterization test and codify the two-tier discipline in the LoadError doc and the C24 ledger entry (#156).

Architecture: No struct, signature, or runtime behaviour changes. Tier-1 tolerance is already true (serde ignores unknown fields — no deny_unknown_fields anywhere; omit-defaults via skip_serializing_if); this iteration proves it and documents the discipline. Both Tier-2 refusals already ship green (LoadError::UnsupportedVersion / UnknownNodeType).

Tech Stack: crates/aura-engine/src/blueprint_serde.rs (test + doc), docs/design/INDEX.md (C24 ledger).


Files this plan creates or modifies:

  • Test: crates/aura-engine/src/blueprint_serde.rs:171-283 — append the Tier-1 forward-tolerance characterization test to the #[cfg(test)] mod tests block.
  • Modify: crates/aura-engine/src/blueprint_serde.rs:110-112 — reword the LoadError::UnsupportedVersion doc comment to state the settled two-tier discipline.
  • Modify: docs/design/INDEX.md:1822-1827 — add one codification sentence to the C24 Status block and drop #156 from the Remaining list (keep #158/#159).

All three changes are one cohesive, independently-reviewable unit → a single task.


Task 1: Tier-1 forward-tolerance pin + two-tier codification

Files:

  • Test: crates/aura-engine/src/blueprint_serde.rs:171-283

  • Modify: crates/aura-engine/src/blueprint_serde.rs:110-112

  • Modify: docs/design/INDEX.md:1822-1827

  • Step 1: Append the Tier-1 forward-tolerance test

In crates/aura-engine/src/blueprint_serde.rs, inside the #[cfg(test)] mod tests block, insert the following test immediately after the closing } of unsupported_version_fails_named (currently line 282) and before the module's closing } (currently line 283). All referenced helpers are in scope: run_recording (:188), crate::test_fixtures::sink_free_sma_cross_signal (used at :212/:225/:237/:258), the &|t| aura_std::std_vocabulary(t) closure form (used at :214/:227/:250/:273/:280), blueprint_to_json/blueprint_from_json (via use super::*; at :173).

    #[test]
    fn unknown_optional_field_is_tolerated_byte_identically() {
        // Forward-compat (C24 / #156, Tier-1): a future writer that has additively
        // extended the format emits optional keys the CURRENT loader does not know.
        // The loader must silently ignore them and reconstruct the IDENTICAL graph —
        // never refuse, never let an unknown key leak into the built blueprint. serde
        // ignores unknown fields by default (no deny_unknown_fields). This pins the
        // additive property the canonical-golden + omit-defaults discipline claims but
        // no existing test exercises.
        let canonical =
            blueprint_to_json(&crate::test_fixtures::sink_free_sma_cross_signal()).expect("serializes");

        // Inject unknown optional keys at two additive shapes a new writer might emit:
        // a doc-level section ("metadata") and a per-primitive key ("annotations").
        let with_unknown = canonical
            .replacen(
                r#"{"format_version":1,"#,
                r#"{"format_version":1,"metadata":{"author":"future"},"#,
                1,
            )
            .replacen(r#"{"type":"Sub"}"#, r#"{"type":"Sub","annotations":["future"]}"#, 1);
        assert_ne!(with_unknown, canonical, "probe must actually inject unknown keys");

        // tolerated, not refused
        let loaded = blueprint_from_json(&with_unknown, &|t| aura_std::std_vocabulary(t))
            .expect("an unknown optional field is tolerated, not refused");

        // the unknown keys did not leak into the graph: re-serialization equals the
        // canonical form WITHOUT them (byte-identical reconstruction).
        assert_eq!(blueprint_to_json(&loaded).expect("re-serializes"), canonical);

        // and the loaded graph runs bit-identically to the plain canonical one (C1).
        let plain = blueprint_from_json(&canonical, &|t| aura_std::std_vocabulary(t)).expect("loads");
        assert_eq!(
            run_recording(loaded),
            run_recording(plain),
            "run diverged under an unknown optional field",
        );
    }
  • Step 2: Run the new test to verify it passes (characterization pin)

This test pins ALREADY-EXISTING behaviour, so it goes GREEN on first run (it is a forward-tolerance characterization pin, NOT a RED test).

Run: cargo test -p aura-engine --lib unknown_optional_field_is_tolerated_byte_identically Expected: PASS — test result: ok. 1 passed (the filter matches exactly this one new test by name).

  • Step 3: Reword the LoadError::UnsupportedVersion doc comment

In crates/aura-engine/src/blueprint_serde.rs, replace the exact current doc comment (lines 110-112, verbatim):

    /// `format_version` the loader does not understand. The full must-understand
    /// horizon (multiple versions, unknown-section policy) is #156; #155 supports
    /// exactly one version and refuses any other, cleanly.

with:

    /// `format_version` the loader does not understand — the **Tier-2
    /// (must-understand)** refusal. The format extends under a two-tier discipline
    /// (#156): a **Tier-1** additive-optional change (a new optional field /
    /// section) does NOT bump the version — an older reader tolerates it (serde
    /// ignores unknown fields; see `unknown_optional_field_is_tolerated_byte_identically`)
    /// and, by C1, a new optional field defaults to prior behaviour. A **Tier-2**
    /// load-bearing change (a new node type, edge semantics, or structural-axis
    /// kind) MUST bump `BLUEPRINT_FORMAT_VERSION`, so an old reader refuses here
    /// rather than silently building a different graph (C1 / C18). The
    /// per-section required-flag scheme is deferred until a real sub-version Tier-2
    /// addition needs finer granularity than a version bump.
    UnsupportedVersion { found: u32, supported: u32 },

(The replacement includes the unchanged UnsupportedVersion { ... } variant line as the trailing anchor so the edit lands at the right spot. The BLUEPRINT_FORMAT_VERSION doc at :15-16 is left unchanged — it already states the rule.)

  • Step 4: Add the C24 codification sentence to the ledger Status block

In docs/design/INDEX.md, the C24 Status block currently ends (line 1822) with ...the no-second-validator lockstep preserved), only translating their result. immediately followed by **Remaining** on line 1823. Replace the exact current text:

no-second-validator lockstep preserved), only translating their result.
**Remaining**

with:

no-second-validator lockstep preserved), only translating their result.
**Cycle 0090 (#156) codifies the forward-compat two-tier discipline.** Tier-1
(additive-optional) is serde-default silent-ignore with no `format_version` bump
(a new optional field defaults to prior behaviour, C1) — now proven by
`unknown_optional_field_is_tolerated_byte_identically` (an unknown optional key
loads byte-identically and runs bit-identically). 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`,
already green). The per-section required-flag scheme is deferred (no current
Tier-2 section to validate it; recorded on #156).
**Remaining**
  • Step 5: Drop #156 from the C24 Remaining list

In docs/design/INDEX.md, the Remaining block (line 1824) currently reads:

(each its own milestone issue): the forward-compat *must-understand* gate (#156);

Replace that exact line with:

(each its own milestone issue):

This leaves the #158 / #159 entries on lines 1825-1827 intact. Do NOT touch the other four #156 occurrences (blueprint_serde.rs:15-16, blueprint_serde.rs:111 in the reworded doc, aura-std/src/vocabulary.rs:18, INDEX.md:1831) — they are semantically distinct and stay.

  • Step 6: Full workspace gate

Run: cargo test --workspace Expected: PASS — all suites green, including the new unknown_optional_field_is_tolerated_byte_identically. No regressions.

  • Step 7: Lint gate

Run: cargo clippy --workspace --all-targets -- -D warnings Expected: clean (no warnings).