diff --git a/docs/design/INDEX.md b/docs/design/INDEX.md index a04a7c6..1459f16 100644 --- a/docs/design/INDEX.md +++ b/docs/design/INDEX.md @@ -201,6 +201,13 @@ the repo, not session memory, is the source of truth. optional gated document `description`); descriptions never influence execution or identity ids, and registered artifacts are never retroactively invalidated. +- **[C30 — Stability discipline: artifacts are stable, code is movable](contracts/c30-stability-discipline.md)** — + No Rust-API stability before 1.0 (no semver, no shims): node crates ride + the engine checkout as path deps, external embeddings pin a rev, and the + cdylib handshake refuses non-identical aura-core builds (build-identity + stamp, #348). The compatibility promise attaches to the artifact plane + (C18/C29/invariant 8); no surface may claim "stable API" while this + contract stands. --- diff --git a/docs/design/contracts/c30-stability-discipline.md b/docs/design/contracts/c30-stability-discipline.md new file mode 100644 index 0000000..b2411f5 --- /dev/null +++ b/docs/design/contracts/c30-stability-discipline.md @@ -0,0 +1,79 @@ +# 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 is the whole contract: updating it 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. 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. *(Transition: + the shipped handshake still stamps the crate version; re-arming it is + tracked as #348.)* + +4. **The artifact plane is the stable tier.** What was already law, + stated as the positive promise: registered artifacts are never + retroactively invalidated ([C29](c29-self-description.md)); document + format changes carry `format_version` with readers keeping old forms + readable ([C18](c18-registry.md)); deploy artifacts are frozen + ([C13](c13-hot-reload-frozen-deploy.md), 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](c28-stratification.md) +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](c13-hot-reload-frozen-deploy.md) — frozen deploy artifacts (the promise at the deploy edge) +- [C16](c16-engine-project-split.md) — engine/project split; reuse is cargo-native (invariant 9) +- [C18](c18-registry.md) — the run registry and `format_version` (the artifact plane's mechanics) +- [C28](c28-stratification.md) — the movable crate ladder this contract licenses +- [C29](c29-self-description.md) — registered artifacts never retroactively invalidated +- Decision record: #296 (options weighed, skeptic findings); re-arming the handshake: #348 diff --git a/docs/project-layout.md b/docs/project-layout.md index 9f3804a..bb95f2d 100644 --- a/docs/project-layout.md +++ b/docs/project-layout.md @@ -91,6 +91,15 @@ deploy, spanning both halves. > nodes new` scaffolder emits this line; a hand-rolled node crate adds it > itself. +> **Compatibility across the seam +> ([C30](design/contracts/c30-stability-discipline.md)).** The node crate +> consumes `aura-core` as a *path* dependency into the engine checkout — +> the pair moves together, and cargo rebuilds the crate when the engine +> changes. No Rust API stability is promised before 1.0: an external +> embedding in its own repo instead pins the engine as a git dependency at +> a rev, and the load handshake refuses a dylib built against a different +> aura-core. + ## Where reusable nodes live (three tiers) Everything that plugs into the engine is fractally a `Node`. Reuse is plain