The load seam's aura-core stamp becomes AURA_CORE_FINGERPRINT: an FNV-1a
64 hash over aura-core's sorted src/**/*.rs (prefix-free records: path,
NUL, u64-LE content length, contents), emitted by build.rs beside the
rustc stamp and baked into the descriptor by aura_project!. validate_c_tier
compares fingerprints ('aura-core build'); a stale dylib — same rustc,
different aura-core sources — is refused instead of trusted at the
Rust-ABI tier. RED-first: the headline test pins that the frozen crate
version is never again the host stamp (plus a version-bump-proof twin and
an aura-core shape pin: 16 hex, never CARGO_PKG_VERSION).
Descriptor field renamed aura_core_version -> aura_core_fingerprint
(engine-internal; field names are not ABI, order is). Documented accepted
C30 limit: source-level identity only — the consuming build's lockfile/
features stay outside the stamp. C13/C30 prose aligned.
closes #348
4.6 KiB
C30 — Stability discipline: artifacts are stable, code is movable
Guarantee. The project's compatibility promise attaches to recorded data, never to Rust API. Before 1.0 the workspace's library surface promises nothing between commits; every cargo consumer either moves with the engine checkout (path-dep node crates, staleness refused at the load handshake) or pins a rev deliberately (external embeddings). The one handshake guarding the ABI seam compares build identity, never a hand-maintained version.
-
No API stability before 1.0. Signatures, module homes, crate rosters, and trait shapes move freely, commit over commit — no deprecation cycles, no compat shims, no semver bumps. The workspace version (0.1.0) is not a communication channel; nothing downstream reads it. What a change may not silently break is behaviour pinned by the artifact plane (point 4).
-
Two consumer classes, two mechanisms.
- A native node crate (scaffolded by
aura nodes new) consumes the engine as a cargo path dependency into the local engine checkout — the scaffolder emits exactly this (crates/aura-cli/src/scaffold.rs, templateCARGO_TOML). Node crate and engine move together: cargo rebuilds the crate when aura-core changes, and a dylib from any other engine state is refused at load (point 3), never trusted. - An external embedding (a World program in its own repo) consumes the workspace crates as a cargo git dependency pinned to a rev. The pin is the whole contract: updating it is a deliberate act that accepts whatever moved, with the design ledger and commit bodies as the changelog.
- A native node crate (scaffolded by
-
The load handshake refuses non-identical builds. The cdylib handshake already refuses a rustc mismatch (
RUSTC_VERSION, emitted by build.rs). The aura-core stamp must be equally honest: a build-identity fingerprint derived mechanically from aura-core's sources, so a node dylib loads only against the aura-core it was built from. A hand-maintained version that never moves disarms the refusal (the pre-C30 state:CORE_VERSIONfrozen at 0.1.0 matched every stale dylib); a hand-bumped one re-arms it only between bumps — an implicit ABI promise this contract refuses to make. (Re-armed by #348: a source-derived fingerprint replaced the crate-version stamp.) -
The artifact plane is the stable tier. What was already law, stated as the positive promise: registered artifacts are never retroactively invalidated (C29); document format changes carry
format_versionwith readers keeping old forms readable (C18); deploy artifacts are frozen (C13, invariant 8). Compatibility work happens here: a change that would orphan recorded artifacts is a design decision for the ledger, never a refactor. -
The erosion guard. No doc, README, or scaffold may describe any Rust surface as "stable API" while this contract stands. Inviting a consumer means naming the point-2 mechanism (path or pin), never promising stillness. Amending this contract is the only way to promise more.
Rationale. #296 recorded the tension: an invited external consumer
(#295 deliberately enlarges the public library surface) versus a working
discipline of behaviour-preserving reshaping (C28
relocated whole rosters twice in one pass). The resolution promises where
the project can keep promises — the recorded-data plane, whose guarantees
already exist — and refuses to promise where it cannot. The empirical
ground at decision time: the one real downstream project is data-only (an
Aura.toml directory with no Cargo.toml, consuming the released binary
and documents), so the only cargo consumers are engine-scaffolded node
crates (path-dep by construction) and the engine's own fieldtest
fixtures. A stability promise to nobody would cost every cycle and inform
no one; hardening toward 1.0 stays available at any time, while stability
promised early is practically irrevocable.
See also
- C13 — frozen deploy artifacts (the promise at the deploy edge)
- C16 — engine/project split; reuse is cargo-native (invariant 9)
- C18 — the run registry and
format_version(the artifact plane's mechanics) - C28 — the movable crate ladder this contract licenses
- C29 — registered artifacts never retroactively invalidated
- Decision record: #296 (options weighed, skeptic findings); re-arming the handshake: #348