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).
This commit is contained in:
@@ -1829,9 +1829,8 @@ 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**
|
||||
(each its own milestone issue):
|
||||
content-addressed topology identity in the manifest (#158, C18); retiring the pre-C24
|
||||
**Milestone delivered — 2026-06-30, cycle 0090 — the serializable format + loader + construction service.** A green end-to-end milestone fieldtest (`fieldtests/milestone-topology-as-data/`) proves the full author → serialize → load → construct → introspect → reproduce story from the public surface alone, 0 behavioural bugs; the round-trippable format (#155), the construction service (#157), and the forward-compat two-tier discipline (#156) all shipped. Polish filed forward: op-script grammar docs + a stale example (#163), the canonical trailing-newline / `Composite` value ergonomics (#164), CLI discoverability of `build`/`introspect` (#159).
|
||||
**Deferred to the World / project-as-crate cycle** (moved out of this milestone — they address a problem that only arises once the World *generates* runs from blueprint-data, which does not exist yet: `commit` still fully identifies every hard-wired topology): content-addressed topology identity in the manifest (#158, C18) — premature until a run is built *from* a serializable blueprint; retiring the pre-C24
|
||||
hard-wired `aura-cli` harnesses (`HarnessKind`, `run_stage1_r`, `*_sweep_family`) once
|
||||
the project-as-crate layer lands (#159, paired with #157's data-authoring surface). **Out of the first cut's round-trippable set**
|
||||
(deliberate; fails clean as `UnknownNodeType`, never a silent wrong graph): recording
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
# 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).
|
||||
|
||||
```rust
|
||||
#[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):
|
||||
|
||||
```rust
|
||||
/// `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:
|
||||
|
||||
```rust
|
||||
/// `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).
|
||||
@@ -1,204 +0,0 @@
|
||||
# Blueprint format forward-compatibility: the two-tier discipline — Design Spec
|
||||
|
||||
**Date:** 2026-06-30
|
||||
**Status:** Draft — awaiting user spec review
|
||||
**Authors:** orchestrator + Claude
|
||||
|
||||
## Goal
|
||||
|
||||
Close #156: guarantee the blueprint data format (C24) extends **additively** —
|
||||
later additions land without breaking existing serializations — under a single,
|
||||
codified two-tier discipline:
|
||||
|
||||
- **Tier-1 (additive-optional, ignore / default-safe).** A new optional field or
|
||||
section is tolerated by an older reader: serde ignores unknown fields (no
|
||||
`deny_unknown_fields` anywhere — verified `rg = 0`), `skip_serializing_if`
|
||||
omits absent options, and **no `format_version` bump** occurs. Constraint (C1):
|
||||
a new optional field's default equals prior behaviour, so an old blueprint
|
||||
loaded by a new engine reproduces the **same** graph.
|
||||
- **Tier-2 (load-bearing, must-understand).** A new node type, edge semantics, or
|
||||
structural-axis kind **must bump `format_version`**, so an old reader refuses
|
||||
cleanly (`LoadError::UnsupportedVersion`) rather than silently building a
|
||||
different graph. An out-of-vocabulary node likewise refuses
|
||||
(`LoadError::UnknownNodeType`).
|
||||
|
||||
Most of #156 already shipped with #155 (cycle 0087). This cycle is a small
|
||||
**verification-plus-codification** iteration: it adds the **one** missing proof —
|
||||
forward-tolerance of an unknown optional field on the current loader — and writes
|
||||
the discipline durably into the `LoadError` doc and the C24 ledger entry. It is
|
||||
**not** a new mechanism.
|
||||
|
||||
### What #155 already delivers (verified, green — the grounding evidence)
|
||||
|
||||
- The `format_version` envelope: `BLUEPRINT_FORMAT_VERSION = 1`, `BlueprintDoc {
|
||||
format_version, blueprint }` (`blueprint_serde.rs:17-24`).
|
||||
- Canonical omit-defaults form: `#[serde(default, skip_serializing_if = ...)]` on
|
||||
`CompositeData::{edges,input_roles,output}` and `PrimitiveData::{name,bound}`.
|
||||
Pinned by `signal_serializes_to_canonical_golden`.
|
||||
- **Both Tier-2 refusals at the load path**, each with a green test:
|
||||
- `LoadError::UnsupportedVersion { found, supported }` —
|
||||
`blueprint_serde.rs:162-167`, test `unsupported_version_fails_named`
|
||||
(`format_version: 2` → refused).
|
||||
- `LoadError::UnknownNodeType(String)` — `blueprint_serde.rs:128`, test
|
||||
`unknown_node_type_fails_named` (`"NoSuchNode"` → refused).
|
||||
|
||||
There is **no CLI `load-a-blueprint-JSON` command** today; the version-refusal has
|
||||
no CLI surface, so the engine-level typed `LoadError` **is** the Tier-2 contract.
|
||||
The CLI *build* path (`aura graph build`) already exit-codes an unknown node type
|
||||
via `OpError` (cycle 0088). This cycle adds no CLI surface.
|
||||
|
||||
## Architecture
|
||||
|
||||
The forward-compat guarantee is a **property of the existing serde projection**,
|
||||
not a new component. Tier-1 tolerance is already true (serde's default
|
||||
unknown-field handling + the omit-defaults discipline); it is simply **unproven**.
|
||||
The deliverable is therefore:
|
||||
|
||||
1. one **executable proof** (the Tier-1 forward-tolerance test), and
|
||||
2. a **documentation codification** turning the currently-deferred
|
||||
"must-understand horizon … is #156" note into the settled two-tier rule.
|
||||
|
||||
No struct, signature, or runtime behaviour changes. The derived design fork (the
|
||||
per-section required-flag / PNG critical-vs-ancillary mechanism) is **not built**
|
||||
— recorded on #156 with its rationale: the version envelope already gives the
|
||||
must-understand refusal at version granularity, there is no current Tier-2 section
|
||||
to validate a finer scheme against, and C1 holds either way.
|
||||
|
||||
## Concrete code shapes
|
||||
|
||||
### The deliverable: the Tier-1 forward-tolerance test (new — the additive property as executable evidence)
|
||||
|
||||
Added to the `blueprint_serde.rs` test module (it already has `run_recording`,
|
||||
`sink_free_sma_cross_signal`, and `std_vocabulary` in scope):
|
||||
|
||||
```rust
|
||||
#[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",
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### The codification: `LoadError::UnsupportedVersion` doc (before → after)
|
||||
|
||||
Before (`blueprint_serde.rs:110-113`):
|
||||
|
||||
```rust
|
||||
/// `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.
|
||||
UnsupportedVersion { found: u32, supported: u32 },
|
||||
```
|
||||
|
||||
After (the discipline is now settled, not deferred):
|
||||
|
||||
```rust
|
||||
/// `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 `BLUEPRINT_FORMAT_VERSION` doc at `:15-17` already states "Bumped only by a
|
||||
load-bearing (Tier-2) change; additive optional fields do not bump it (#156)" and
|
||||
needs no change — it is consistent with the settled discipline.)
|
||||
|
||||
### The codification: C24 ledger Status block (before → after, shape only)
|
||||
|
||||
The C24 entry's `**Remaining**` list (`INDEX.md:1823-1827`) names "the
|
||||
forward-compat *must-understand* gate (#156)". This cycle moves #156 out of
|
||||
Remaining into a shipped note stating the two-tier discipline and the proof test.
|
||||
Exact prose is the planner's; the shape: drop #156 from Remaining, add one
|
||||
sentence to Status that the forward-compat discipline is codified (Tier-1
|
||||
serde-default tolerance proven; Tier-2 = version/vocabulary refusal, already
|
||||
green) — leaving #158 and #159 in Remaining.
|
||||
|
||||
## Components
|
||||
|
||||
- `crates/aura-engine/src/blueprint_serde.rs` — add one test
|
||||
(`unknown_optional_field_is_tolerated_byte_identically`); reword the
|
||||
`LoadError::UnsupportedVersion` doc comment. No code/behaviour change.
|
||||
- `docs/design/INDEX.md` — C24 Status/Remaining update (codification).
|
||||
|
||||
## Data flow
|
||||
|
||||
Unchanged. `blueprint_from_json` → `serde_json::from_str::<BlueprintDoc>` (ignores
|
||||
unknown fields) → version check → `reconstruct` (resolves each `type_id` through
|
||||
the injected vocabulary). The test exercises exactly this path with an
|
||||
unknown-key-bearing input.
|
||||
|
||||
## Error handling
|
||||
|
||||
Unchanged and already complete: `LoadError::{Json, UnsupportedVersion,
|
||||
UnknownNodeType}` — typed, named, never a panic, never a silent wrong graph. The
|
||||
two Tier-2 arms are the must-understand refusals; the new test asserts the Tier-1
|
||||
path does **not** error.
|
||||
|
||||
## Testing strategy
|
||||
|
||||
- **New (Tier-1, the gap):** `unknown_optional_field_is_tolerated_byte_identically`
|
||||
— an unknown optional key at doc and primitive level is ignored; the
|
||||
reconstructed graph re-serializes byte-identically to the canonical form and
|
||||
runs bit-identically (C1).
|
||||
- **Existing (Tier-2, cited as grounding evidence, unchanged):**
|
||||
`unsupported_version_fails_named`, `unknown_node_type_fails_named`.
|
||||
- Full workspace gate: `cargo test --workspace`; lint `cargo clippy --workspace
|
||||
--all-targets -- -D warnings`.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] **(a) Tier-1 additive forward-tolerance** — a blueprint JSON carrying an
|
||||
unknown optional field/section loads on the current loader, the unknown key does
|
||||
not leak into the graph (byte-identical re-serialization), and the run is
|
||||
bit-identical. Pinned by the new test.
|
||||
- [ ] **(b) Tier-2 must-understand refusal** — a too-new `format_version` and an
|
||||
out-of-vocabulary node each yield a clean, named `LoadError` (never a panic,
|
||||
never a silent graph change). Already green (`unsupported_version_fails_named`,
|
||||
`unknown_node_type_fails_named`); unchanged by this cycle.
|
||||
- [ ] The two-tier discipline is codified in the `LoadError` doc and the C24
|
||||
ledger entry; the deferred per-section required-flag mechanism is recorded as
|
||||
out-of-scope on #156.
|
||||
- [ ] `cargo test --workspace` green; `cargo clippy --workspace --all-targets -- -D
|
||||
warnings` clean.
|
||||
Reference in New Issue
Block a user