ADR-017: contract versioning — core crate's semver is the contract version (OQ-10 resolved)

This commit is contained in:
glm-5.3-flash committed 2026-10-06 07:29:09 +00:00
1 parent 8c8fec5cb8
commit eba77909a7
9 files changed
+473 -51

No files matched your search

+6 -4
View File
@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-10-06
last_updated: 2026-10-06 (ADR-017; OQ-10 resolved)
---
# alkstore — Architecture
@@ -24,7 +24,7 @@ pending architecture review and OQ resolution.
| Doc | Status | Purpose | Key OQs |
|---|---|---|---|
| [overview.md](overview.md) | draft | Crate family, feature surface, non-goals, evidence base | — |
| [core-contract.md](core-contract.md) | draft | The unified trait surface, delivery guarantees, tx seam | OQ-10 |
| [core-contract.md](core-contract.md) | draft | The unified trait surface, delivery guarantees, tx seam | OQ-10 (resolved) |
| [engine-sqlite.md](engine-sqlite.md) | draft | SQLite engine: forked-substrate/rusqlite mapping | OQ-06 (resolved), OQ-12 (resolved), OQ-13 (resolved) |
| [engine-postgres.md](engine-postgres.md) | draft | Postgres engine: tokio-postgres/LISTEN mapping | OQ-08 (resolved), OQ-12 (resolved), OQ-13 (resolved) |
| [queues.md](queues.md) | draft | Queue/scheduler/outbox semantics depth (resolved: ADR-009/ADR-010) | OQ-06 (resolved) |
@@ -51,6 +51,7 @@ pending architecture review and OQ resolution.
| [014](decisions/014-outbox-tx-enqueue.md) | Transactional outbox enqueue — `outbox_enqueue_tx` on the `TxHandle` trait | Accepted |
| [015](decisions/015-streams-depth.md) | Streams depth — carried-metadata keys, global-FIFO ordering row, `StreamEvent` shape, `publish_with_key_tx`, `trim_to` | Accepted |
| [016](decisions/016-deployment-honesty.md) | Deployment honesty — no runtime capability surface; compile-time engine identity + documented matrix | Accepted |
| [017](decisions/017-contract-versioning.md) | Contract versioning — core crate's semver is the contract version; change classes, pairing carriers, lockstep duties | Accepted |
## Open Questions
@@ -59,7 +60,6 @@ Phase 0 register's OQ-ST-01..08 promote one-to-one — OQ-NN mirrors
OQ-ST-NN — with new Phase 1 questions appended after). Open, in
suggested resolution order:
- **OQ-10** (medium): contract versioning across engine crates.
- **OQ-11** (medium): forked-substrate follow-through (provenance
register format, cherry-pick procedure; item (1) dissolved by
[ADR-013](decisions/013-fold-substrate-into-sqlite.md)).
@@ -77,7 +77,9 @@ scheduler guarantee row pinned), ~~OQ-05~~ (queue semantics depth —
[ADR-016](decisions/016-deployment-honesty.md)), ~~OQ-13~~
(transactional outbox enqueue shape —
[ADR-014](decisions/014-outbox-tx-enqueue.md)), ~~OQ-12~~ (streams
depth — [ADR-015](decisions/015-streams-depth.md)).
depth — [ADR-015](decisions/015-streams-depth.md)), ~~OQ-10~~
(contract versioning —
[ADR-017](decisions/017-contract-versioning.md)).
No deferred OQs: all open questions are actionable Phase 1 work with
complete evidence bases.
+19 -9
View File
@@ -18,9 +18,13 @@ taxonomy, config split — amended in place, pre-implementation, by
joins the `TxHandle` trait, and by
[ADR-015](decisions/015-streams-depth.md): streams depth — key
semantics, `StreamEvent` shape, the ordering row, `trim_to`,
and `publish_with_key_tx`); ADR-009/ADR-010 add the first *post-v1
contract extensions* (scheduler collapse surface, `QueueOpts` depth —
versioning discipline for such extensions is OQ-10's);
and `publish_with_key_tx`); ADR-009/ADR-010 add the first extensions
beyond ADR-008's original v1 partition (scheduler collapse surface,
`QueueOpts` depth — factually pre-release additions per ADR-017's
class 1, shipped inside the initial contract v1 text; versioning
discipline for the surface's changes is
[ADR-017](decisions/017-contract-versioning.md)'s, resolved: the
core crate's semver is the contract version);
[ADR-016](decisions/016-deployment-honesty.md) decides the parked
capability-surface question: **none, by default ever** — engine
differences surface at compile time (engine-crate identity) and in
@@ -509,13 +513,14 @@ before the engine specs are called `stable`:
| [006](decisions/006-wake-and-delivery-contract.md) | Wake contract | opaque wake + re-read; notify-vs-streams guarantee split |
| [007](decisions/007-transactional-seam.md) | Tx seam | caller-held handle, `*_tx` methods, native commit-atomicity |
| [008](decisions/008-contract-v1-pinning.md) | Contract v1 pinning | surface partition, `TxHandle` trait shape, `Wake` type, reserved strings, error taxonomy, config split, locks guarantee row |
| [009](decisions/009-scheduler-collapse.md) | Scheduler collapse (post-v1 extension) | queues + `schedule()`/`run_schedules`, `@every`-only v1, boundary guarantee row |
| [010](decisions/010-queue-semantics-depth.md) | Queue depth (post-v1 extension) | visibility/renewal, opts stamping, backoff curve, dead-letter, sweep, layout |
| [009](decisions/009-scheduler-collapse.md) | Scheduler collapse (pre-release extension) | queues + `schedule()`/`run_schedules`, `@every`-only v1, boundary guarantee row |
| [010](decisions/010-queue-semantics-depth.md) | Queue depth (pre-release extension) | visibility/renewal, opts stamping, backoff curve, dead-letter, sweep, layout |
| [011](decisions/011-sqlite-substrate-fork.md) | Substrate fork | SQLite substrate owned (`__alkstore_*` naming); queue ops re-derived on contract v1 |
| [012](decisions/012-forked-substrate-design.md) | Fork design | contract-blind substrate boundary; engine-side formula arithmetic pinned equivalent by the contract suite |
| [014](decisions/014-outbox-tx-enqueue.md) | Outbox tx enqueue (amends 008) | `outbox_enqueue_tx` on `TxHandle`; outbox-name validation; derived backing queue reached only through the outbox surface |
| [015](decisions/015-streams-depth.md) | Streams depth (amends 006/008) | key = carried metadata, global-FIFO ordering row, `StreamEvent` shape, `publish_with_key_tx`, `trim_to` |
| [016](decisions/016-deployment-honesty.md) | Deployment honesty (decides 008's parked question) | no runtime capability surface — compile-time engine identity + documented matrix; `PayloadTooLarge` occurrence asymmetry pinned |
| [017](decisions/017-contract-versioning.md) | Contract versioning (governs this surface's changes) | core crate's semver *is* the contract version; four change classes; amend-in-place ends at first release; pairing = manifest pin + version-stamped contract suite + docs |
## Open Questions
@@ -523,14 +528,19 @@ Open questions are tracked in
[open-questions.md](open-questions.md). Key
questions affecting this document:
- **OQ-10**: contract versioning discipline across engine crates
([open](open-questions.md))
- **OQ-10**: contract versioning discipline across engine crates —
**resolved** ([ADR-017](decisions/017-contract-versioning.md)):
the core crate's semver is the contract version; change classes
pinned; the verification backlog below matures into the
version-stamped contract suite (the pairing instrument).
Resolved on this document's surface: **OQ-09** (scheduler collapse —
[ADR-009](decisions/009-scheduler-collapse.md)), **OQ-05** (queue
semantics depth — [ADR-010](decisions/010-queue-semantics-depth.md)),
**OQ-13** (transactional outbox enqueue shape —
[ADR-014](decisions/014-outbox-tx-enqueue.md)), **OQ-12** (streams
depth — [ADR-015](decisions/015-streams-depth.md)), and **OQ-08**
depth — [ADR-015](decisions/015-streams-depth.md)), **OQ-08**
(capability-surface shape — none, by default ever;
[ADR-016](decisions/016-deployment-honesty.md)), 2026-10-05/06.
[ADR-016](decisions/016-deployment-honesty.md)), and **OQ-10**
(the versioning discipline this document's surface is governed by —
[ADR-017](decisions/017-contract-versioning.md)), 2026-10-05/06.
+10 -2
View File
@@ -81,7 +81,13 @@ crate. A consumer binary links at most one driver.
- A version-coordination duty: the core's trait surface is a contract
the engine crates must track. Version bumps in core must be adopted
by engines in lockstep when the contract changes (minor/major
discipline; no trait-default drift).
discipline; no trait-default drift). *(Discharged 2026-10-06: the
duty's concrete shape is the versioning discipline —
[ADR-017](017-contract-versioning.md), OQ-10's resolution: the
core crate's semver is the contract version, change classes +
lockstep duties pinned, the pairing tracked by manifest pin +
version-stamped contract suite + docs; released engines on a
breaking change keep their pinned majors — nothing forced.)*
- Slightly more crate plumbing; naming/publishing overhead.
- Consumers who want *both* engines in one binary (rare, unsupported-by
design) cannot get a both-features build today.
@@ -96,6 +102,8 @@ crate. A consumer binary links at most one driver.
link-collision constraint motivates this split.
- [ADR-004](004-postgres-driver.md) — the Postgres driver choice.
- OQ-10 (`docs/architecture/open-questions.md`) — trait-versioning
duties created here.
duties created here; resolved by
[ADR-017](017-contract-versioning.md); OQ-11 — the fork-follow-through
residue (per [ADR-013](013-fold-substrate-into-sqlite.md) §4).
[ADR-003]: 003-sqlite-driver.md
[ADR-004]: 004-postgres-driver.md
@@ -89,10 +89,17 @@ implement identically):
- **Queue semantics depth** (retry/backoff curve, dead-letter
move-vs-flag, visibility-renewal mechanics, sweep cadence, `QueueOpts`
fields beyond the v1 skeleton) — OQ-05's design surface. v1 pins the
skeleton above; depth additions extend the contract later.
skeleton above; depth additions extend the contract later. *(Resolved
2026-10-05 by [ADR-010](010-queue-semantics-depth.md); and re-framed
2026-10-06 by [ADR-017](017-contract-versioning.md): nothing had been
released when the depth landed, so it factually ships inside the
initial contract v1 text.)*
- **Scheduler surface shape** (first-class mechanism vs queues +
`schedule()`) — OQ-09 decides; the scheduler surface (if any) is not
part of contract v1.
part of contract v1. *(Resolved 2026-10-05 by
[ADR-009](009-scheduler-collapse.md); re-framed 2026-10-06 by
[ADR-017](017-contract-versioning.md) on the same basis — the
collapse surface ships in the initial contract v1 text.)*
- **Capability flags** — OQ-08 decides whether `Store` exposes them at
all; v1 has no capability surface. *(Resolved 2026-10-06 by
[ADR-016](016-deployment-honesty.md): no capability surface — by
@@ -445,4 +452,5 @@ prefix, §4).
- OQ-09 (scheduler row transfer), OQ-08 (capability surface —
resolved by [ADR-016](016-deployment-honesty.md), no runtime
surface), OQ-10
(versioning discipline for future contract extensions).
(versioning discipline for future contract extensions) —
resolved by [ADR-017](017-contract-versioning.md), 2026-10-06).
@@ -221,7 +221,12 @@ respawning; distinct from clean stop, §1).
**post-v1 contract extensions** — the first exercises of the
extension path [ADR-008](008-contract-v1-pinning.md) leaves open;
their versioning discipline (how engine crates track the additions)
is OQ-10's, still open.
is OQ-10's, still open. *(Resolved 2026-10-06 by
[ADR-017](017-contract-versioning.md): nothing was released when
these extensions were made, so they factually fall in ADR-017
class 1 — pre-implementation amend-in-place — and ship inside the
initial contract v1 text; the post-release extension *shape* this
ADR exercised is ADR-017's class 2.)*
## References
@@ -134,7 +134,11 @@ smallest surface that restores the property.
the v1 surface partition's tx-seam bullet and
[core-contract.md](../core-contract.md)'s seam. ADR-008's outbox
bullet carries a pointer annotation, mirroring how ADR-010 §8's
SQLite half recorded its supersession.
SQLite half recorded its supersession. *(Codified 2026-10-06 as
versioning class 1 by
[ADR-017](017-contract-versioning.md) — and bounded by it: this
amend-in-place frame terminates at the core crate's first
release.)*
- **Contract-suite row**: `outbox_enqueue_tx` commit-atomicity on both
engines — rollback drops the backing-queue job row with the business
write (no ghost job); commit makes it claimable by `run_once`;
@@ -127,7 +127,11 @@ the key the same way the non-tx form does (`None` or non-empty — an
empty `Some` key is `InvalidName`) with no new error variants
([ADR-008](008-contract-v1-pinning.md) §5's rule). Framed as
completing contract v1 in place, pre-implementation, like ADR-014 —
no versioning event, OQ-10 untouched. A plain-`publish_tx` is
no versioning event, OQ-10 untouched. *(Codified 2026-10-06 as
versioning class 1 by
[ADR-017](017-contract-versioning.md) — and bounded by it: this
amend-in-place frame terminates at the core crate's first
release.)* A plain-`publish_tx` is
`publish_with_key_tx(stream, None, payload)`, not a separate
engine path. *(Naming pinned here too: the publish family returns the
**assigned offset** — honker's `publish` already returns it — and the
@@ -0,0 +1,338 @@
# ADR-017: Contract versioning discipline — the core crate's semver *is* the contract's semver
## Status
Accepted (2026-10-06, Phase 1 — OQ-10's resolution; governs the
versioning of the surface [core-contract.md](../core-contract.md)
carries; discharges the version-coordination duty
[ADR-001](001-crate-split.md) records as its negative consequence)
## Context
The crate split makes the core crate's trait surface a contract the
engine crates must track — ADR-001's one structural negative
consequence ("version bumps in core must be adopted by engines in
lockstep when the contract changes"). OQ-10 framed three candidate
mechanisms for how: semver-bump-only-when-contract-changes, engines
pinning core ranges, and a contract-compatibility test suite the
engines run against the core's trait definitions — plus the direct
question: what happens to a *released* engine crate when core makes a
contract breaking change?
Much has been decided since the OQ was framed, and it narrows and
feeds the answer:
- **The contract's content is no longer just v1.** ADR-009 and
ADR-010 added the first *post-v1 extensions* (the scheduler
collapse surface; the `QueueOpts` depth) — ADR-009's negative
consequence explicitly deferred their versioning discipline to
OQ-10, and ADR-010's depth rode the same extension path
([ADR-008](008-contract-v1-pinning.md)'s framing). ADR-014 and
ADR-015 created
the other precedent class: *amend-in-place, pre-implementation* —
both framed "no versioning event, OQ-10 untouched." The discipline
must codify both frames and draw the line between them.
- **The fold ([ADR-013](013-fold-substrate-into-sqlite.md)) removed a
would-be versioning surface.** The forked substrate is a module
subtree of `alkstore-sqlite` — unpublished, path-invisible, with no
crate identity of its own. It has no version number to track
anything with; cherry-picks ride the engine crate's own semver and
OQ-11's provenance procedure. The pairing the discipline must track
is the *engine/core* pairing, full stop.
- **The capability surface is gone**
([ADR-016](016-deployment-honesty.md)): no descriptor struct that
contracts would have to keep honest across versions — one less
surface class for this discipline to govern.
- **No artifact is released.** No crate exists; the pinned text
(ADR-008 through ADR-016, and the specs it carries) is entirely
pre-implementation. The discipline is therefore forward policy —
every claim it makes about *released* engines is governable from
day one, and nothing needs retrofitting.
- **One structural fact shapes the whole answer**: the core crate is
a contract artifact and nothing else (`alkstore` — "the unified
trait surface, types, error model, and the contract documentation.
No driver dependencies," [ADR-001](001-crate-split.md) §1 as
annotated by ADR-016). A core release cannot be motivated by
anything *but* a contract change: the crate has no other behavior
to ship.
## Decision
### 1. The core crate's semver is the contract version — no parallel versioning surface
There is **no separate contract-version register, no version
descriptor API, no compatibility-matrix document**: the core crate's
version number *is* the contract version, because the crate is
nothing but contract. This is the one-normative-owner rule
([ADR-012](012-forked-substrate-design.md) §2) applied to the version
question — a second ledger would be a second normative home for
"which contract is this," exactly the drift surface ADR-016 rejected
in the runtime-descriptor case. A dependency edge *states* the
contract pairing; it cannot drift from the truth it states.
Concretely:
- The core crate's **changelog is the version ledger**: every
contract change entry cites its ADR (the "no silent contract
change" rule, §2 below makes the citation mandatory).
- [core-contract.md](../core-contract.md) is the *content* of record
for the current contract text; the ADR table there is the change
history. Neither duplicates the version number as a normative
field.
- The coupling binds fully at the core crate's 1.0 (contract major
⇔ crate major). **The initial release is 1.0.0** — contract v1's
content (ADR-008 through ADR-017's text, the class-1 additions
included) *is* crate major 1, so the identity holds at the moment
it binds rather than being reached by accumulation. Pre-1.0 (the
contingency of interim dev releases, not the planned path) follows
Cargo's 0.x semantics: a contract breaking change bumps `0.y`;
additions bump `0.y.z` — at 1.0 the count re-bases: contract v1's
breaking history is exactly the majors 1.x accumulates from. The
pinned text's name for the initial surface — **contract v1**
([ADR-008](008-contract-v1-pinning.md)) — is a content
designation, not yet a semver claim.
### 2. The event taxonomy: four classes of contract change
Every change to the contract text is exactly one of these, and
classes 1–3 name their ADR in the core crate's changelog entry
(**no silent contract changes** — a contract change without an ADR
and a changelog citation is a process defect, the guard this
discipline most depends on because semantic breaks are not
compiler-visible, §5; class 4's doc clarifications carry no ADR
duty — that is what makes them non-events).
1. **Pre-implementation amend-in-place** (the ADR-014/015 precedent)
— completing a pinned-but-under-pinned surface, before the core
crate first publishes. No versioning event, because there is no
versioned artifact for the change to be an event *on*. **This
class terminates permanently at the core crate's first release**:
once any consumer can pin the contract text by a version number,
"the contract changed under the same number" stops being
available. ADR-009/ADR-010, though framed as *post-v1
extensions*, fall in this class in fact — nothing was released
when they were made, so their additions ship inside the initial
contract v1 text (the initial release carries ADR-008 through
ADR-017 whole).
2. **Additive extension** (the ADR-009/010 *shape*, applied
post-release) — a new trait method, a new type, a new error
variant, a new field on a consumer-read value type (§3's
`#[non_exhaustive]` class), a new guarantee row, or a new
contract-suite row pinning new
behavior. Core bumps its minor (pre-1.0: patch). Existing
correct consumers are untouched; §5 carries the engine-side
consequences. (Opts-struct field additions do *not* ride this
class — §3 exempts those structs, pricing their field additions
class 3.)
3. **Breaking change** — any signature change, removal, or rename
(cutting a pinned v1 method name was already ruled a contract-
breaking event, [ADR-015](015-streams-depth.md) §1's option-(c)
reasoning), **and any semantics change a correct consumer's
behavior could depend on**: weakening a guarantee row, changing a
pinned contract constant (the 64-cap, the 1-hour backoff cap, the
8000-byte pg payload limit), changing a variant's meaning or
production conditions. *Compile-compatible is not the test* —
the act-differently measure is (ADR-008 §5's rule, generalized
exactly as ADR-016 §2 generalized it to surface): if consumer
code that was correct before the change behaves differently
after, the change is breaking regardless of whether it compiles
clean. Core bumps its major (pre-1.0: its `0.y`).
4. **Non-events** — engine-internal changes (engine opts, derived
names, substrate cherry-picks per OQ-11's procedure —
contract-blind by ADR-012 §2, so they cannot be contract events
by construction); and doc clarifications *only* where a correct
implementation could not have behaved differently before them
(the conservative inverse of the act-differently measure: if it
could have, it is class 3, not a clarification).
Extensions and breaking changes additionally obey the **row-first
discipline** both ADR-009 §2 and ADR-015 §1 applied: a consumer-
inventory row naming the need precedes the extension (breaking
changes have their ADR-008 §5-rule justification instead — the
change exists because the pinned text was *wrong*, which no row
names).
### 3. `#[non_exhaustive]` on the consumer-read contract types — opts structs exempt
Class 2 is only *mechanically* additive if Rust's rules agree, and
Rust's rules split the value types by who constructs them. Pinned:
`#[non_exhaustive]` on the types consumers **read but never
construct** — the top-level `Error`, `StreamEvent`, `Job`,
`Schedule`, and `Wake` — so field and variant additions there are
semver-minor under Cargo's rules, not major. On `Error` this also
makes the taxonomy's fallback posture contractual: consumers
matching `Error` *cannot* match exhaustively — the opaque
`Database`-carrying catch-all the act-differently rule designed is
the required shape, mechanically enforced. (`Job`'s dead-only
`last_error`/`died_at` fields — the ADR-010 §1 addition — are this
class's demonstrated growth surface, now made mechanically minor.)
The **opts structs (`EnqueueOpts`, `QueueOpts`, `ScheduleOpts`) are
deliberately *not* `#[non_exhaustive]`**: consumers construct them
(the pinned v1 field sets, ADR-008 §1, carried whole), and
`#[non_exhaustive]` bans struct-literal construction of the type
from outside the defining crate — functional-update syntax included
(E0639). Pinning it would silently remove the pinned construction
posture for every `enqueue`/`queue`/`schedule` call — itself a
semantics change by this ADR's own class-3 measure. The honest cost,
stated: an opts *field* addition post-release is a **class 3
breaking change** (major) — heavier than a minor for "one more
field," and priced that way deliberately; the row-first gate keeps
such additions rare (the v1 field sets are complete, and ADR-010's
depth is in them; a consumer row must name the need, and the
field must earn it).
Trait methods remain the additive class Rust's rules cannot paper
over (adding a method breaks every impl; `#[non_exhaustive]` does
not cover trait items). That cost is carried openly, not hidden:
the lockstep duty, §5.
### 4. The pairing: how an engine crate declares what it implements
The engine/core contract pairing is tracked by **three carriers of
one fact** (the ADR-016 pattern — statements at the right altitude,
not a runtime descriptor):
1. **The manifest pin**: each engine crate declares
`alkstore = "1.y"` (caret-within-major; pre-1.0, `0.y`)
— the dependency edge stating the contract version the engine's
code implements. Cargo's resolution makes cross-major mixing a
compile error rather than a runtime surprise.
2. **The contract suite**: the verification backlog
([core-contract.md](../core-contract.md) §Verification backlog)
matures into the contract suite — the *compatibility instrument*
(the OQ's option (c)), not a dev convenience. An engine release
claiming contract vN runs the suite's vN rows; **each row is
version-stamped with the contract change that added it** — the
same no-silent-change discipline (§2's ADR-citation duty is what
the stamp records), which is what makes the suite auditable
against the changelog. The suite's layout —
shared crate, workspace test target, or per-engine module — is a
test-side convenience decided at implementation
([ADR-001](001-crate-split.md) §4's posture for test artifacts).
3. **Engine-crate docs**: each engine's docs state the contract
version(s) it implements — the same standing-statement altitude
as its deployment posture (ADR-016 §3's carrier 2).
The engine crate's own semver stays independent — it versions the
engine's own surface (constructors, opts). A contract adoption
release is a normal engine release; nothing in the engine's version
number *must* encode the contract version (its manifest pin does,
§4.1).
### 5. Lockstep duties — and what a breaking change does to released engines
- **Contract minor (class 2)**: **both engine crates adopt by
release before-or-with the core minor's announcement** —
mandatory for new trait methods (their impls must exist or
downstream builds of the engine against the new core fail), and
the suite's new rows do not go green without the adoption either
way. A consumer feels class 2 as *available* engine updates; the
consumer adopts when it wants the new surface. An engine not yet
adopting stays a coherent crate against its last-verified core
minor, but note the mechanics: a consumer's *unpinned* caret
requirement resolves to the newest compatible core minor — against
which a method-method-addition non-adopter fails to compile. That
compile failure *is* the adoption duty's enforcement, not a
consumption guarantee; the grace window holds for consumers whose
graphs deliberately resolve the previous minor (a pinned
requirement or a lockfile), and for additions that are not trait
methods (new opts never ride trait additions; new suite rows test
what the engine already shipped).
- **Contract major (class 3)**: **nothing is forced on released
engines.** A released engine crate keeps its pinned core major
range; consumers keep working; the engine's docs record the
contract majors it serves. The new major gets new engine majors
(implementing it, per §4's carriers), and consumer migration is a
deliberate two-line dependency bump — the single-driver design
(ADR-001) makes the whole move visible in one place. This is the
OQ's direct question, answered: *the released engine serves its
pinned contract majors until archived; "what happens" is that the
ecosystem gains a new parallel line, and no released artifact has
anything pulled out from under it.*
- **The lockstep duty's edge is stated, not smoothed**
(ADR-001's "no trait-default drift" line), on both sides the
mechanics actually allow: **core** must not ship class-2
additions as default-bodied trait methods (a failing default
would make the addition optional for engines to implement and
silently non-additive for consumers — the exact drift the lockstep
duty exists to prevent; additions are required methods), and
**engines** must not stub required surface with
error-returning impls to fake adoption.
## Consequences
**Positive**
- The versioning machinery is *nothing new*: no descriptor, no
register, no compatibility API — the discipline rides the artifact
graph (crate version, manifest edge, suite, changelog), the same
compile-time-honesty posture as ADR-016. A dependency edge *is*
the pairing statement.
- The two precedent classes are codified with their boundary:
amend-in-place has a termination line (first release); the
extension shape has a mandatory-adoption rule. ADR-009/010 are
correctly understood as pre-implementation events; ADR-014/015's
"OQ-10 untouched" framing is vindicated and bounded.
- `#[non_exhaustive]` makes the read-type additive-minor rule
mechanically true and mechanically enforces the catch-all matching
posture the taxonomy already mandated in prose; opts-field
additions are honestly priced as breaking (class 3) rather than
smuggled in as minor.
- The suite graduates from backlog to compatibility instrument with
a version discipline — the thing that makes "engines still
implement the contract" an auditable claim rather than an
intention.
**Negative**
- Contract minor bumps carry a real (if small) maintenance cost:
both engines must publish adoption releases — mandatory for trait
methods (non-adopter engines fail to compile against the new
core), and suite-row readiness effectively demands it for the
rest. This is
the ADR-001 negative consequence made concrete, not removed.
- Semantic breaks ride semver only through this discipline's honesty
rule — the compiler cannot see a weakened guarantee row. The
guards are review, the no-silent-contract-change rule, and the
suite's version-stamped rows; a dishonestly-classified change is
a process defect, not a caught one.
- Pre-1.0, the coupling is looser (0.y bumps for breaks) — the
usual pre-release latitude, bounded at 1.0.
## References
- OQ-10 (`docs/architecture/open-questions.md`) — this ADR's
resolution; the three mechanisms it framed compose here (semver
discipline → §1/§2, range pins → §4.1, the suite → §4.2), the
house pattern of options-are-one-posture (ADR-016 §1).
- [ADR-001](001-crate-split.md) — the crate split and its
version-coordination negative consequence this ADR discharges;
§1's core-crate contents list (contract artifact, and nothing
else); §4's test-artifact posture the suite's layout defers by.
- [ADR-008](008-contract-v1-pinning.md) — contract v1 (the content
designation §1 keeps); §5's act-differently rule, generalized
twice (surface by ADR-016 §2, change classification by this
ADR's class 3).
- [ADR-009](009-scheduler-collapse.md) — the first extension shape
(its negative consequence named this discipline "still open");
§2's row-first extension path.
- [ADR-010](010-queue-semantics-depth.md) — the `QueueOpts`-depth
extension; its stamped-fields pattern is the opts addition §3's
`#[non_exhaustive]` rule anticipates.
- [ADR-012](012-forked-substrate-design.md) — §2's one-normative-
owner rule (§1) and the contract-blind boundary that makes
substrate changes structurally non-events (class 4).
- [ADR-013](013-fold-substrate-into-sqlite.md) — the fold that
removed the substrate's versioning surface entirely.
- [ADR-014](014-outbox-tx-enqueue.md) §4 /
[ADR-015](015-streams-depth.md) §2 — the amend-in-place
precedents ("no versioning event, pre-implementation") this ADR
codifies and terminates at first release.
- [ADR-016](016-deployment-honesty.md) — the capability-surface
rejection that narrowed this discipline's scope (no descriptor to
version); its three-carriers pattern (§4).
- OQ-11 — the fork-scaffold residue whose cherry-pick procedure
governs the substrate-side non-events (class 4).
- `docs/research/consumer-inventory.md` — the row-first gate on
extensions (class 2/§2's closing rule).
+73 -30
View File
@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-10-06
last_updated: 2026-10-06 (OQ-10 resolved)
---
# alkstore — Open Questions
@@ -30,15 +30,13 @@ carried-metadata keys, global-FIFO ordering row, `StreamEvent` shape,
`publish_with_key_tx`, `trim_to`); **OQ-08 resolved** (2026-10-06,
[ADR-016](decisions/016-deployment-honesty.md) — no runtime
capability surface; the honest boundary lives in compile-time engine
identity + the documented deployment matrix).
Next: **OQ-10** (versioning discipline
for contract extensions; note ADR-011 changes its substrate-side facts
for SQLite — the forked machinery lives in-tree inside the engine
crate per [ADR-013](decisions/013-fold-substrate-into-sqlite.md), so
the engine/core contract pairing is what the discipline must track;
OQ-08's resolution also narrowed its surface — no capability struct
to govern),
OQ-11 (fork follow-through items — substrate-side, non-consumer-facing).
identity + the documented deployment matrix); **OQ-10 resolved**
(2026-10-06, [ADR-017](decisions/017-contract-versioning.md) — the
core crate's semver is the contract version; four change classes
with amend-in-place terminating at first release; the pairing
tracked by manifest pin + version-stamped contract suite + docs).
Next: **OQ-11** (fork follow-through items — substrate-side,
non-consumer-facing).
Resolved questions stay listed with their resolution; they are not
deleted.
@@ -71,7 +69,8 @@ deleted.
effectively requires). Decision recorded in
[ADR-001](decisions/001-crate-split.md) — including the reasoning
that superseded the inventory's single-crate lean.
- **Cross-references**: OQ-03, OQ-10.
- **Cross-references**: OQ-03, OQ-10 (resolved —
[ADR-017](decisions/017-contract-versioning.md)).
### OQ-03: Driver story — sqlx, tokio-postgres, or per-engine drivers? *(== OQ-ST-03)*
@@ -135,7 +134,9 @@ narrowed to the pinning work its own record already scoped.)*
expiry); the scheduler row is explicitly transferred to OQ-09's
resolution. A verification backlog (core-contract.md §Verification
backlog) tracks the one-engine-pinned properties.
- **Cross-references**: OQ-01, OQ-10, OQ-09, OQ-08; OQ-13 (the one
- **Cross-references**: OQ-01, OQ-10 (resolved —
[ADR-017](decisions/017-contract-versioning.md), the discipline
these v1 extensions now fall under), OQ-09, OQ-08; OQ-13 (the one
gap the v1 pinning's own Phase 1 review found in it), OQ-12 (the
sibling depth gap — resolved, [ADR-015](decisions/015-streams-depth.md)).
@@ -270,25 +271,68 @@ narrowed to the pinning work its own record already scoped.)*
fork-scaffold task (its opening section), not before it and not as a
gate on writing it.
- **Cross-references**: OQ-08 (the contract surface the engine maps
the substrate under), OQ-10 (the engine's versioning discipline now
carries the fold's provenance duties in-tree), ADR-011, ADR-012,
ADR-013.
the substrate under), OQ-10 (the engine's versioning discipline
now carries the fold's provenance duties in-tree — and its
class-4 non-events rule is what keeps substrate cherry-picks from
being contract events;
[ADR-017](decisions/017-contract-versioning.md)), ADR-011,
ADR-012, ADR-013.
### OQ-10: How do engine crates track core-contract version changes?
### OQ-10: How do engine crates track core-contract version changes? — **RESOLVED**
- **Origin**: [overview.md](overview.md),
[ADR-001](decisions/001-crate-split.md)
- **Status**: open
- **Status**: resolved (2026-10-06, Phase 1 —
[ADR-017](decisions/017-contract-versioning.md))
- **Priority**: medium
- **Resolution**: open. The core crate's trait surface is a contract
the engine crates must track ([ADR-001](decisions/001-crate-split.md) negative consequence). What
is the versioning/sync discipline — semver-bump-only-when-
contract-changes, engines pin core ranges, a contract-compatibility
test suite the engines run against the core's trait definitions?
What happens to a released engine crate when core makes a contract
breaking change?
- **Cross-references**: OQ-04 (the contract being versioned — now
including its first post-v1 extensions, ADR-009/ADR-010), OQ-02.
- **Resolution**: Pinned by [ADR-017](decisions/017-contract-versioning.md):
**the core crate's semver is the contract version — no parallel
versioning surface** (the OQ's three candidate mechanisms compose
rather than rival: semver discipline → the change-class rule,
engine range pins → the manifest carrier, the contract suite →
the compatibility instrument). (1) One-normative-owner rule
applied to the version question: the crate is a contract artifact
and nothing else, so its version ledger *is* the contract ledger,
and its changelog cites the ADR of every contract change (no
silent contract changes — the semantic-break guard, since
guarantee-row breaks are not compiler-visible). (2) **Four change
classes**: amend-in-place (the ADR-014/015 precedent — terminates
*permanently* at the core crate's first release; ADR-009/010,
though framed "post-v1," factually fall in this class and ship in
the initial contract v1 text); additive extension (the
ADR-009/010 *shape*, post-release — core minor, row-first
discipline); breaking change (signatures *and* semantic breaks —
the act-differently measure generalized to change
classification; core major); non-events (engine-internal change —
structurally guaranteed by ADR-012 §2's contract-blind boundary:
substrate cherry-picks cannot be contract events). (3)
`#[non_exhaustive]` on the consumer-read value types (`Error`,
`StreamEvent`, `Job`, `Schedule`, `Wake`) — mechanically making
additions there minor and enforcing the catch-all matching
posture; the consumer-constructed opts structs are exempt
(non_exhaustive bans struct-literal construction), so their
field additions are breaking (class 3), priced deliberately;
trait methods stay the honest lockstep cost. (4) The pairing
tracked by three carriers of one
fact: the engine's manifest pin (`alkstore = "1.y"`), the
version-stamped contract suite (the backlog matures into the
OQ's option-(c) compatibility instrument), and engine-crate docs.
(5) **Released engines on a breaking change: nothing forced** —
the released engine serves its pinned contract majors until
archived; the new major gains its own engine line, migration is
the consumer's deliberate two-line bump; and no
default-trait-method shims to dodge adoption (the "no trait-
default drift" line, stated). Engine semver stays independent
(it versions the engine's own surface).
- **Cross-references**: OQ-04 (the contract being versioned — its
first post-v1 extensions, ADR-009/010, resolved pre-release into
contract v1's initial text), OQ-02, OQ-08 (the narrowing — no
capability struct to govern),
[ADR-011](decisions/011-sqlite-substrate-fork.md)/
[ADR-012](decisions/012-forked-substrate-design.md)/
[ADR-013](decisions/013-fold-substrate-into-sqlite.md) (the fold
that removed the substrate's versioning surface), OQ-11 (the
cherry-pick procedure governing the substrate-side non-events).
## Theme: Deployment and capabilities
@@ -448,7 +492,6 @@ narrowed to the pinning work its own record already scoped.)*
## Deferred / Blocked
None currently. Every open OQ above is actionable Phase 1 work
(versioning discipline, fork-scaffold
follow-through) with its evidence base complete — no
external arrivals are being waited on.
None currently. The one remaining open OQ above (OQ-11) is
actionable Phase 1 work (fork-scaffold follow-through) with its
evidence base complete — no external arrivals are being waited on.