Guarantee additive forward-compatibility of the blueprint format (optional-ignore vs load-bearing-must-understand) #156
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The blueprint format must extend additively: later additions land without breaking existing serializations. aura already runs the additive serde discipline elsewhere — verified: 15
#[serde(default)]and 14skip_serializing_ifacross crates/,serde(alias)for field renames (crates/aura-analysis/src/lib.rs:72), nodeny_unknown_fieldsanywhere (rg deny_unknown_fields crates-> 0 matches), and a dedicated shim home at crates/aura-registry/src/compat.rs. The gap: no format-version envelope exists today (rg 'format_version|schema_version' crates-> 0 matches); run identity is carried only by commit.Two tiers, and the danger is the middle — silently ignoring something load-bearing:
Tier 1 — additive-optional (ignore / default-safe): new optional fields and annotations via serde
default+skip_serializing_if, nodeny_unknown_fields. Determinism rule (C1, the determinism/reproducibility contract in docs/design/INDEX.md): a new optional field's default equals the 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. An old reader refuses cleanly and names the cause (unknown node type, or format_version above its horizon) — never silently builds a different graph (C1 / C18 reproducibility, docs/design/INDEX.md). Optional finer mechanism: a
requiredflag per extension section, so a non-required unknown section is skipped (Tier 1) and a required one refuses (Tier 2) — the PNG critical-vs-ancillary chunk model.Acceptance (validated property):
Depends on the serialization + loader issue #155 (the envelope and canonical form live there).
Scope reconciliation after #155 shipped (cycle 0087,
d5602ec)The #155 first cut already delivers a substantial part of this issue's
must-understand gate. Verified against the shipped code
(
crates/aura-engine/src/blueprint_serde.rs):format_versionenvelope exists (BLUEPRINT_FORMAT_VERSION = 1, thetop-level
BlueprintDoc).format_version→LoadError::UnsupportedVersion(testunsupported_version_fails_named); an unknown node type →LoadError::UnknownNodeType(testunknown_node_type_fails_named). Neitherpanics; neither silently builds a different graph (C1/C18-safe).
optional uses
serde(default, skip_serializing_if = ...), nodeny_unknown_fields; the canonical omit-defaults form makes an absentoptional byte-identical to the pre-extension bytes.
So this issue's remaining substantive scope is narrower than the body
implies:
field added later loads an old serialization unchanged (and leaves its
content id stable — shared with #158). #155 proved omit-defaults, not yet the
forward direction (new reader, old bytes).
requiredflag per extension section): genuinely new — the format today is a flat node
list with no "sections", so this is only worth building when a real ancillary
extension appears.
blueprint yet (that surface is #157/#159). The typed
LoadErroris thelibrary-level half; the exit-code mapping waits on that surface.
Recommendation for whoever picks this up: rescope to item 1 (a small
tdd-sizedforward-compat golden), and treat items 2-3 as deferred-until-needed (2: a real
ancillary extension; 3: the CLI load surface). Not a blocker on anything.
Cycle 0088 (#157) scaling note: the engine Op ↔ wire DTO needs the same must-understand discipline
The construction service (cycle 0088) keeps the engine
Openum serde-free anddeserializes the JSON op-list through a CLI-side DTO (
OpDoc,crates/aura-cli/src/graph_construct.rs) that maps intoOp. TheFrom<OpDoc> for Opis exhaustive over
OpDoc, but the reverse direction is unguarded: a future engineOpvariant — e.g. the construction-arg ops the format extensions here will add(
LinComb/CostSum/SimBroker/Session) — is not compiler-forced to gain awire encoding in
OpDoc. It would simply be unreachable from the document format,silently, until someone notices.
When the additive-extension work here lands construction-arg builders into the
serialized set, the op-list DTO should extend in lockstep — and the same
optional-ignore vs. load-bearing-must-understand tiering this issue defines for the
blueprint format applies to the op-list wire format too. A low scaling note, recorded
by the cycle-0088 architect drift review; non-blocking today (the current
Opvocabulary is fully covered by
OpDoc).Design reconciliation (specify, #156)
Spec being produced:
docs/specs/0090-blueprint-forward-compat.md. One load-bearing fork is decided here before the spec is written.Fork — granularity of the must-understand (Tier-2) refusal. The issue floats an optional finer mechanism: a required flag per extension section (the PNG critical-vs-ancillary chunk model), which would let a load-bearing addition refuse on an old reader without bumping the global
format_version.Decision (derived): do not build the per-section required-flag mechanism now. The single invariant "any must-understand change bumps
format_version" is adopted instead.Basis (derived, not a user decision):
blueprint_from_jsonrefuses anyformat_version != BLUEPRINT_FORMAT_VERSIONcleanly (LoadError::UnsupportedVersion,blueprint_serde.rs:162), and an out-of-vocabulary node refuses asLoadError::UnknownNodeType(:128). Both are already green-tested (unsupported_version_fails_named,unknown_node_type_fails_named).Net discipline this cycle codifies: Tier-1 (additive-optional) = serde-default silent-ignore, no version bump, with the C1 constraint that a new optional field defaults to prior behaviour (an old blueprint reproduces the same graph); Tier-2 (must-understand: a new node type, edge semantics, or structural-axis kind) = bump
format_version. The remaining unproven property — forward-tolerance of an unknown optional field on the current loader — becomes a green pin, and the discipline is written into the ledger (C24) and theLoadErrordoc.