Files
Aura/docs/design/contracts/c30-stability-discipline.md
claude 99e93000c5 fix(aura-runner, aura-cli): milestone-fieldtest bugs — library exposes-neither guard, override class, doc truth
RED-first fixes for the two code bugs the safe-to-embed milestone
fieldtest caught: (1) run_signal_r panicked (exit 101) on a blueprint
exposing neither bias nor a declared tap — the CLI pre-validated but the
library seam, the very surface the milestone promises is kill-free, did
not; the guard now refuses class 2 with the CLI's prose family before
wrap_r (run_measurement has no wrap seam and no hole). (2) The override
unknown-param refusal exited 1 through the one inline site the #297
adjudication missed; it and its wrapped-retired sibling are class 2 now,
three existing pins re-pinned with the adjudication comment.

Doc truth from the same fieldtest: the guide's campaign no-data class
corrected (contained cell faults exit 3, not 1), TapPlanError's rustdoc
no longer advertises the retired exit-1 register, and C30 records that
rev + the embedding's committed lockfile are the reproducible-build
contract (branch-referenced engine deps resolve at lock time).

refs #296
2026-07-26 20:06:07 +02:00

5.1 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.

  1. 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).

  2. 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, template CARGO_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 names the engine; the embedding's own committed Cargo.lock freezes the rest of the graph — dependencies the engine itself takes as git-branch references (e.g. the data-server) resolve at lock time, so rev + lockfile together are the reproducible-build contract (fieldtest finding, 2026-07-26). Updating either is a deliberate act that accepts whatever moved, with the design ledger and commit bodies as the changelog.
  3. 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 — source-level identity, an accepted limit: the consuming build's lockfile and feature selection stay outside the stamp, so the seam refuses staleness of aura-core itself, never the full link graph. A hand-maintained version that never moves disarms the refusal (the pre-C30 state: CORE_VERSION frozen 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.)

  4. 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_version with 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.

  5. 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