docs(design): C30 — stability discipline: artifacts stable, code movable
Decide #296: no Rust-API stability before 1.0 (no semver, no shims; the workspace version communicates nothing). Two consumer classes with two mechanisms — native node crates ride the engine checkout as path deps (what the scaffolder already emits), external embeddings pin a rev. The compatibility promise attaches to the artifact plane (C18/C29/invariant 8), never to Rust API, with an erosion guard against 'stable API' claims. The skeptic pass surfaced that a frozen 0.1.0 disarms the cdylib handshake's CORE_VERSION refusal; the contract commits the load seam to a build-identity fingerprint instead (transition tracked as #348). Decision minutes: #296 (options a-e, rationale). closes #296
This commit is contained in:
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user