ADR-017: contract versioning — core crate's semver is the contract version (OQ-10 resolved)
This commit is contained in:
1 parent
8c8fec5cb8
commit
eba77909a7
9 files changed
+473
-51
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user