ADR-014: transactional outbox enqueue — outbox_enqueue_tx on TxHandle (OQ-13 resolved)

This commit is contained in:
glm-5.3-flash committed 2026-10-05 13:28:39 +00:00
1 parent d401908f13
commit 04a04651dc
8 files changed
+359 -122

No files matched your search

+86 -93
View File
@@ -21,18 +21,19 @@ boundaries, fidelity posture, port deltas; its substrate-side residue
is OQ-11); **the fork packaging folded** (2026-10-05,
[ADR-013](decisions/013-fold-substrate-into-sqlite.md) — no
`alkstore-substrate` crate; the forked machinery is a module subtree of
`alkstore-sqlite`). Next: OQ-08 (rides the now-pinned trait shape and the
ADR-011 substrate fork), 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-11
(fork follow-through items — substrate-side, non-consumer-facing),
**OQ-12** (streams depth — a Phase 1 review find: key semantics,
`StreamEvent` shape, ordering row, retention; evidence gathered), and
**OQ-13** (the transactional outbox enqueue shape — a Phase 1 review
find: the v1 `TxHandle` surface cannot express the outbox's one
load-bearing operation).
`alkstore-sqlite`); **OQ-13 resolved** (2026-10-05,
[ADR-014](decisions/014-outbox-tx-enqueue.md) —
`outbox_enqueue_tx` joins the `TxHandle` trait, the hole is closed).
Next: **OQ-12** (streams depth — a Phase 1 review
find: key semantics, `StreamEvent` shape, ordering row, retention;
evidence gathered; check consumer rows for per-key-ordering need
before choosing among its options), OQ-08 (rides the now-pinned trait
shape and the ADR-011 substrate fork), 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-11 (fork follow-through items — substrate-side, non-consumer-facing).
Resolved questions stay listed with their resolution; they are not
deleted.
@@ -127,9 +128,10 @@ narrowed to the pinning work its own record already scoped.)*
surface; the contract is the trait it returns. (7) Locks guarantee
row added to ADR-006's table (TTL-bounded mutual exclusion, silent
expiry); the scheduler row is explicitly transferred to OQ-09's
resolution. A verification backlog (core-contract.md §Verification
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.
- **Cross-references**: OQ-01, OQ-10, OQ-09, OQ-08; OQ-13 (the one
gap the v1 pinning's own Phase 1 review found in it).
## Theme: Queues and scheduling
@@ -232,42 +234,6 @@ narrowed to the pinning work its own record already scoped.)*
[ADR-011](decisions/011-sqlite-substrate-fork.md); ADR-005's posture
calculus applied, not renegotiated.
## Theme: Deployment and capabilities
### OQ-08: Where does the honest single-host/multi-host boundary live in the trait surface? *(== OQ-ST-08)*
- **Origin**: [deployment.md](deployment.md)
- **Status**: open
- **Priority**: medium
- **Resolution**: open. The pg engine is natively multi-host (POC #2
verified — no single-host assumption to remove); SQLite is
single-machine by nature (file-backed, NFS-two-writers unsupported —
honker's honesty posture, inherited by the forked substrate). The
unified surface must not pretend SQLite is multi-host. Options:
per-engine capability flags (`Store::capabilities()`), a documented
deployment matrix only ([deployment.md](deployment.md) carries the
facts), or compile-time knowledge only (a consumer choosing the
SQLite engine knows). Rides the now-pinned contract shape
([ADR-008](decisions/008-contract-v1-pinning.md)): the trait
constrains where capability differences can surface.
- **Cross-references**: OQ-04, [ADR-006](decisions/006-wake-and-delivery-contract.md), [ADR-012](decisions/012-forked-substrate-design.md) (the substrate inherits the honesty posture).
### OQ-10: How do engine crates track core-contract version changes?
- **Origin**: [overview.md](overview.md),
[ADR-001](decisions/001-crate-split.md)
- **Status**: open
- **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.
### OQ-11: Forked-substrate follow-through — scaffold, provenance register, and cherry-pick discipline
- **Origin**: [ADR-012](decisions/012-forked-substrate-design.md)
@@ -302,6 +268,46 @@ narrowed to the pinning work its own record already scoped.)*
carries the fold's provenance duties in-tree), ADR-011, ADR-012,
ADR-013.
### OQ-10: How do engine crates track core-contract version changes?
- **Origin**: [overview.md](overview.md),
[ADR-001](decisions/001-crate-split.md)
- **Status**: open
- **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.
## Theme: Deployment and capabilities
### OQ-08: Where does the honest single-host/multi-host boundary live in the trait surface? *(== OQ-ST-08)*
- **Origin**: [deployment.md](deployment.md)
- **Status**: open
- **Priority**: medium
- **Resolution**: open. The pg engine is natively multi-host (POC #2
verified — no single-host assumption to remove); SQLite is
single-machine by nature (file-backed, NFS-two-writers unsupported —
honker's honesty posture, inherited by the forked substrate). The
unified surface must not pretend SQLite is multi-host. Options:
per-engine capability flags (`Store::capabilities()`), a documented
deployment matrix only ([deployment.md](deployment.md) carries the
facts), or compile-time knowledge only (a consumer choosing the
SQLite engine knows). Rides the now-pinned contract shape
([ADR-008](decisions/008-contract-v1-pinning.md)): the trait
constrains where capability differences can surface.
- **Cross-references**: OQ-04, [ADR-006](decisions/006-wake-and-delivery-contract.md), [ADR-012](decisions/012-forked-substrate-design.md) (the substrate inherits the honesty posture).
## Theme: Core contract (Phase 1 review finds)
### OQ-12: Streams depth — key semantics, `StreamEvent` shape, ordering row, retention
- **Origin**: [core-contract.md](core-contract.md) streams section,
@@ -356,60 +362,47 @@ narrowed to the pinning work its own record already scoped.)*
open), OQ-01 (scope row), ADR-002, ADR-006 (the guarantee table
the ordering row extends), ADR-008 §1.
### OQ-13: Transactional outbox enqueue shape — the `TxHandle` surface cannot express it
### OQ-13: Transactional outbox enqueue shape — the `TxHandle` surface cannot express it — **RESOLVED**
- **Origin**: [core-contract.md](core-contract.md) (outbox section),
[ADR-008](decisions/008-contract-v1-pinning.md) §1 (Phase 1
architecture review, 2026-10-05)
- **Status**: open
- **Status**: resolved (2026-10-05, Phase 1 —
[ADR-014](decisions/014-outbox-tx-enqueue.md))
- **Priority**: high (the outbox's *only reason to exist* is the
commit-atomic enqueue; as pinned, the v1 surface cannot perform it
— a decomposition off the current text would produce an
unimplementable task)
- **Resolution**: open. The hole, stated precisely: the outbox is
"enqueue inside the business transaction + delivery workers"
([ADR-002](decisions/002-feature-scope.md)); the delivery side is
pinned (`run_once`, plus plain queue consumption of the backing
queue), but the enqueue side has no method. The derived backing
queue name `__alkstore_outbox:{name}`
([ADR-008](decisions/008-contract-v1-pinning.md) §4) is
reserved-prefix — every entry point **rejects** it, so
`handle.enqueue_tx("__alkstore_outbox:x", …)` is contract-illegal
by construction; and `Outbox` (the handle `store.outbox(name)`
returns) is not in the `TxHandle` method set
([ADR-008](decisions/008-contract-v1-pinning.md) §1, §2), so there
is no `outbox_enqueue_tx` either. Honker solves it with a
different seam: `outbox.enqueue_tx(&tx, …)` routes through the
caller's raw `Transaction`
(`packages/honker-rs/src/lib.rs:506-513`) — a seam our handle
design ([ADR-007](decisions/007-transactional-seam.md),
[ADR-008](decisions/008-contract-v1-pinning.md) §2 — the handle
*is* the tx, no downcast) deliberately does not have. Options
(starter set, not exhaustive):
(a) `outbox_enqueue_tx(name, payload)` on the `TxHandle` trait —
symmetric with the other `*_tx` methods, one method, engine resolves
the derived name on the engine side (bypasses the entry-point
rejection legitimately, since the *method* takes the outbox name,
not the derived queue name);
(b) an `OutboxHandle` obtained from the `TxHandle`
(`handle.outbox(name) -> OutboxHandle` with enqueue) — more
symmetrical with the store-side handle, two hops;
(c) non-transactional only — `outbox.enqueue` auto-commit, dropping
the commit-atomic property (defeats the purpose; listed to be
rejected explicitly).
Decision rule: smallest surface that restores the
transactional-local-adjacency property (guiding principle 2) for
the outbox without opening the reserved namespace; (a) reads
strongest against ADR-008 §2's rationale and needs one contract
suite row.
- **Cross-references**: OQ-04 (the pinning that missed this),
OQ-12 (the sibling depth gap found by the same review), ADR-002
- **Resolution**: Pinned by
[ADR-014](decisions/014-outbox-tx-enqueue.md): one method,
`outbox_enqueue_tx(outbox, opts, payload) -> job_id`, added to the
`TxHandle` trait. The method takes the **outbox name** (validated
like `store.outbox(name)` — `InvalidName`/`ReservedName`), derives
the backing queue name `__alkstore_outbox:{outbox}` engine-side,
and lands the job row in the caller's transaction with
`EnqueueOpts` stamped per [ADR-010](decisions/010-queue-semantics-
depth.md) §3a over the backing queue's derived `QueueOpts` —
commit-atomic with the business write, rollback drops both (the
no-ghosts property). The reserved-prefix rejection is unchanged: it
governs directly-supplied names; the derivation is legitimate
engine-internal naming (the same status `run_once`'s consumption of
the backing queue already had). Option (b) (an `OutboxHandle` off
the tx handle) is rejected — a second boxed handle to deliver one
op; option (c) (non-transactional only) is rejected — it drops the
property the mechanism exists for. Honker's raw-`Transaction` seam
is not inherited (no engine-neutral `Transaction` type exists; it
would force a core→engine dependency or a downcast seam — the two
things ADR-008 §2 dissolved). Framed as completing contract v1 in
place, pre-implementation — no versioning event, OQ-10 untouched.
Contract-suite row added (commit-atomicity on both engines).
- **Cross-references**: OQ-04 (the pinning that missed this), OQ-12
(the sibling depth gap found by the same review), ADR-002
(outbox scope row), ADR-007/ADR-008 (the seam design), ADR-010 §3
(the outbox's derived QueueOpts — unchanged by this).
## Deferred / Blocked
None currently. Every open OQ above is actionable Phase 1 work
(streams depth, outbox enqueue shape, capability-surface shape,
versioning discipline, fork-scaffold follow-through) with its evidence
base complete — no external arrivals are being waited on.
(streams depth, capability-surface shape, versioning discipline,
fork-scaffold follow-through) with its evidence base complete — no
external arrivals are being waited on.