Files
alkstore/docs/architecture/open-questions.md
T

559 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: draft
last_updated: 2026-10-08 (fourth review round — waves-1–2 general-review findings resolved as ADR-023; question set remains closed)
---
# alkstore — Open Questions
Centralized tracker. IDs `OQ-NN` are stable — never renumber; append.
Promoted, one-to-one, from Phase 0's OQ-ST register. Resolution order
so far: **OQ-04 resolved** (2026-10-05,
[ADR-008](decisions/008-contract-v1-pinning.md) — the contract surface
everything else hangs off); **OQ-09 + OQ-05 resolved** (2026-10-05,
[ADR-009](decisions/009-scheduler-collapse.md) and
[ADR-010](decisions/010-queue-semantics-depth.md) — the queue semantics
track and the scheduler collapse/guarantee row); **OQ-06 resolved**
(2026-10-05, [ADR-011](decisions/011-sqlite-substrate-fork.md) — the
fork trigger fired on the published-artifact facts); **the fork
design follow-through** (2026-10-05,
[ADR-012](decisions/012-forked-substrate-design.md) — substrate
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`); **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);
**OQ-12 resolved** (2026-10-05,
[ADR-015](decisions/015-streams-depth.md) — streams depth pinned:
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); **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);
**OQ-11 resolved** (2026-10-06,
[ADR-018](decisions/018-provenance-register-and-cherry-picks.md) —
`PROVENANCE.md` in the substrate subtree; per-delta category-tagged
entries;
five-step cherry-pick procedure recorded at adoption). **The Phase 1
question set is closed** — no open OQs remain; Phase 1 moves to
architecture review and the implementation-phase gates.
**Second review round (2026-10-06, pre-decomposition):** the review
found no *new* open questions — every finding was decidable from
decided material, so it resolved directly as
[ADR-019](decisions/019-mechanism-handle-surfaces.md) (handle-trait
surfaces, `Job`/`Schedule` shapes, claimant identity, `StopToken` —
closing the "pin at implementation" residue before ADR-017's class-1
window ends at first release) and
[ADR-020](decisions/020-enqueue-opt-semantics-and-bridges.md)
(`delay`/`run_at` precedence, relative `expires`, scheduler-fired
jobs' stamp source, the serde_json payload encoding bridge), plus
mechanical fixes (ADR index staleness in overview/engine-postgres,
stale wording, framing drift). **Third review round (2026-10-07,
pre-decomposition):** likewise found no *new* open questions — five
findings (a factually-incorrect ADR-019 §1 claim about tx-side reads,
an ADR-010/ADR-019 `Job`-struct disagreement, an unvalidated
`schedule()` write path into reserved names, the unpinned tx-handle
drop disposition, unpinned `EventReceiver` error/close arms), all
decidable from decided material, resolved directly as
[ADR-021](decisions/021-tx-reads-and-value-shape-fixes.md) (tx-read
methods on `TxHandle`, `claimed_at` restored, schedule queue-argument
validation, drop = rollback, receiver arms pinned). **Fourth
review round (2026-10-08, the waves-1–2 general review — the
first round after implementation began):** the code review's four
decision-needing findings (`encode_payload`'s empty-Vec failure
arm, `claim_batch(n <= 0)`'s `LIMIT`-dialect semantics, the
inherited `SQLITE_OPEN_URI` flag, the 1 ms watcher-poll default)
were likewise all decidable from decided material — resolved
directly as
[ADR-023](decisions/023-fourth-review-round.md) (the helper
fallible with `Codec`; numeric-argument domains pinned by kind —
extents clamp empty, durations reject, boundaries total — a
core-contract amendment, since engine-side divergence here would
break engine-agnostic composition; plain-path `open`; the 1 ms
default standing with the cadence carried onto `SqliteOpts`). No
new open questions; the register stays closed.
Resolved questions stay listed with their resolution; they are not
deleted.
## Theme: Scope
### OQ-01: Scope boundary — which honker features are in-scope? *(== OQ-ST-01)*
- **Origin**: [consumer-inventory.md](../research/consumer-inventory.md),
[ADR-002](decisions/002-feature-scope.md)
- **Status**: resolved (2026-10-04, Phase 0)
- **Priority**: medium
- **Resolution**: Answered by the consumer inventory — scope votes
shrink to named rows, not the whole honker feature list. First-class:
notify/listen (pinned), streams (operator-authority record). In
scope: named locks, queues + outbox (documented), scheduler
(documented-thin). Cut-flags: rate limits, result storage. Out:
honker's exclusion lines. Decision recorded in
[ADR-002](decisions/002-feature-scope.md).
- **Cross-references**: OQ-07, OQ-04.
### OQ-02: Crate scope — one store crate, or reactive-core + engines? *(== OQ-ST-02)*
- **Origin**: [overview.md](overview.md)
- **Status**: resolved (2026-10-04, operator decision; Phase 0)
- **Priority**: medium
- **Resolution**: Reactive-core crate + per-engine engine crates; the
split isolates the engines' asymmetry of work and keeps engine
binaries single-driver (which the libsqlite3-sys collision
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 (resolved —
[ADR-017](decisions/017-contract-versioning.md)).
### OQ-03: Driver story — sqlx, tokio-postgres, or per-engine drivers? *(== OQ-ST-03)*
- **Origin**: [overview.md](overview.md)
- **Status**: resolved (2026-10-04, POC-backed both engines; Phase 0)
- **Priority**: medium
- **Resolution**: Per-engine drivers — rusqlite + honker-core 0.5.0
(SQLite; bridged seam), tokio-postgres 0.7.18 + deadpool-postgres
0.14.2 (Postgres), under OQ-02's crate split. Decisions recorded in
[ADR-003](decisions/003-sqlite-driver.md) and
[ADR-004](decisions/004-postgres-driver.md); measurements in
`docs/research/poc-sqlite-posture-findings.md` and
`docs/research/poc-pg-posture-findings.md`.
- **Cross-references**: OQ-02, OQ-05, OQ-06.
### OQ-07: SQLite-side scope — loadable extension? *(== OQ-ST-07)*
- **Origin**: [ADR-002](decisions/002-feature-scope.md)
- **Status**: resolved (cut-only, 2026-10-04; Phase 0)
- **Priority**: low
- **Resolution**: The loadable-extension surface is out: every
identified consumer is in-process Rust attaching to its own
connection (the honker-core `attach_honker_functions` shape). Folded
into OQ-01's scope resolution and [ADR-002](decisions/002-feature-scope.md)
(explicitly, so the slot's disposition is visible — the Phase 0
register carried it as its own entry).
- **Cross-references**: OQ-01.
## Theme: Core contract
### OQ-04: Contract pinning — the exact trait surface and its semantics *(== OQ-ST-04)* — **RESOLVED**
*(Retitled in promotion: OQ-ST-04's framing — "the reactive
abstraction — what does the unified notify surface look like?" —
narrowed to the pinning work its own record already scoped.)*
- **Origin**: [core-contract.md](core-contract.md),
[ADR-006](decisions/006-wake-and-delivery-contract.md),
[ADR-007](decisions/007-transactional-seam.md)
- **Status**: resolved (2026-10-05, Phase 1 — [ADR-008](decisions/008-contract-v1-pinning.md))
- **Priority**: high
- **Resolution**: Pinned as contract v1 by
[ADR-008](decisions/008-contract-v1-pinning.md): (1) the v1
surface partition — notify/streams/queue-skeleton/locks/outbox/tx
seam are contract; queue depth rides OQ-05, scheduler shape rides
OQ-09, capabilities ride OQ-08, `claim_waker` stays engine-internal,
cut-flag rows stay out. (2) `TxHandle`: the `*_tx` methods live on
the handle trait — boxed `dyn` handle, no downcast, no enum (the
enum would invert ADR-001's dependency direction); the POCs'
`as_any_mut` friction dissolves by construction. (3) `listen()`
returns a `WakeReceiver` delivering opaque `Wake { channel }` —
honker's payload transport is not surfaced (uniformity with the
opaque contract, ADR-006). (4) Reserved prefix `__alkstore_`, with
`__alkstore_listener_reconnected__` as the one v1-reserved string
(the POC's ad-hoc `__listener_reconnected__` renamed at
implementation). (5) Error taxonomy: `PayloadTooLarge` /
`ReservedName` / `InvalidName` / `Closed` / `Codec` / `Database`,
pinned by the act-differently rule. (6) `Store::open` is engine-crate
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
backlog) tracks the one-engine-pinned properties.
- **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)).
## Theme: Queues and scheduling
### OQ-05: Queue semantics depth — retry/backoff/dead-letter/sweep design *(== OQ-ST-05)* — **RESOLVED**
- **Origin**: [queues.md](queues.md)
- **Status**: resolved (2026-10-05, Phase 1 — [ADR-010](decisions/010-queue-semantics-depth.md))
- **Priority**: high
- **Resolution**: Pinned by ADR-010: (1) job state machine — three
states (`pending/processing/dead`), delete-on-ack, move-to-dead
(honker's model, pg-boss's seven-state enum not inherited),
`get_job` sees dead rows (`last_error`/`died_at` — post-mortem by
API; `cancel` unconditional, not an interrupt). (2)
Visibility/heartbeat — renewal semantics (absolute reset),
late-heartbeat refusal (dual-execution window is contract-honest),
**reclaim-consumes-an-attempt** stated as contract text. (3)
Retry/backoff — `retry(err, None)` computes the queue's
equal-jitter exponential curve (range definitionally pinned, not a
labeled distribution; base `backoff_base_s`, cap 1 h, attempt index
= row `attempts` at retry), `Some(d)` overrides; queue-level
`QueueOpts {
visibility_timeout_s, max_attempts, backoff_base_s,
dead_letter_retention_s }` **stamped onto each job row at enqueue**
(§3a — no per-queue registry; two handles with different opts for
one queue name don't fight). (4) Dead-letter — move-to-table,
retention forever by default, no redrive API (recipe: `get_job` +
fresh `enqueue`). (5) `sweep_expired` — the no-stranded-rows
property (every past-expiry row in any state moves to dead; honker's
expired-processing zombie hole fails this — fixed at contract
level, SQLite-side realization rides OQ-06). (6) Sweep cadence —
no ambient sweeper; the recipe is the collapse machinery
(schedule + runner). (7) Result-storage cut-flag reconsidered and
**stands** (delete-on-ack; pg-boss's completed-row model is result
storage under another name). (8) Table layout — pg engine-owned
schema (`alkstore` default), queues are rows not tables; SQLite
rides `_honker_*`. *(Item 8's SQLite half superseded 2026-10-05:
the `_honker_*` family is re-owned as `__alkstore_*` by the fork —
[ADR-011](decisions/011-sqlite-substrate-fork.md), designed per
[ADR-012](decisions/012-forked-substrate-design.md).)* No new error
variants (ADR-008 §5's rule).
- **Cross-references**: OQ-09, OQ-06.
### OQ-09: Is the scheduler a first-class mechanism, or queues + `schedule()`? — **RESOLVED**
- **Origin**: [queues.md](queues.md) (spin-out of OQ-05's Phase 0
framing, where the scheduler's boundary question lived)
- **Status**: resolved (2026-10-05, Phase 1 — [ADR-009](decisions/009-scheduler-collapse.md))
- **Priority**: medium
- **Resolution**: **Collapse confirmed**, decided against the
inventory rows (alkblobs sweep cadence, alkfs orphan reaping — both
intervals; no row names schedule objects or wall-clock cron): the
contract surface is `schedule(name, spec, queue, payload, opts)`
(upsert by name), `unschedule(name)`, and the opt-in
`run_schedules(stop)` runner — no `Scheduler` handle, no
schedule objects; update = re-register, pause = unregister +
re-register. **Spec grammar v1: `@every <n><unit>` only** — cron
strings rejected (`InvalidSpec`), dissolving the timezone question
honker's local-time cron brittleness poses (extension path requires
a consumer-inventory row). Boundary guarantee row pinned (the
transfer from [ADR-008](decisions/008-contract-v1-pinning.md) §7
resolved): at-least-once per elapsed boundary while a leader runs,
fire+advance commit atomically under a row lock on the schedule row
(engine-generic no-double-fire floor; the leadership lock is the
efficiency layer), bounded catch-up (**fixed contract-constant cap
64**) with skip-forward; leadership lock is the reserved
`__alkstore_scheduler` name; `run_schedules` returns
`Err(LeadershipLost)` on lock loss, `Ok(())` on clean stop. Schedule
storage engine-internal; schedules never fire without a runner (no
ambient timers).
- **Cross-references**: OQ-05, OQ-04.
## Theme: Engines and dependencies
### OQ-06: honker-core quality read — does the default posture hold? *(== OQ-ST-06)* — **RESOLVED**
- **Origin**: [ADR-005](decisions/005-dependency-ownership.md),
[engine-sqlite.md](engine-sqlite.md)
- **Status**: resolved (2026-10-05, Phase 1 —
[ADR-011](decisions/011-sqlite-substrate-fork.md))
- **Priority**: high (fork trigger is a gate on the SQLite engine's
dependency posture)
- **Resolution**: **The fork trigger fires.** Evidence:
[quality-read-honker-core.md](../research/quality-read-honker-core.md).
The read's original target is clean (Writer/Readers, the polling
watcher's three-layer failure handling, `WatcherDeathGuard` — all
verified in source, present in 0.5.0; schema migrations minor). But
the published artifact is materially behind the reference revision:
crates.io's honker-core 0.5.0 predates upstream's own fix train for
its documented defect class (issue #133's savepoint hardening —
silent job loss on mid-flight errors in the dead-letter paths; five
`.ok()` error-swallows mapping every SQLite error to "no row"/"lock
held"), stranded unreleased with no announced date. And contract v1's
queue depth ([ADR-010](decisions/010-queue-semantics-depth.md) §3a/§5)
requires engine-owned enqueue/claim/sweep/get_job SQL in any posture
— making a post-fix ride a minority-shape, dual-owning the schema.
Resolution: fork honker-core at the reference revision into owned
code, inherit the clean watcher/transactional core + test suites,
re-derive the queue ops on contract v1, drop cron/experimental
backends, rename `_honker_*` → `__alkstore_*`. Recorded in
[ADR-011](decisions/011-sqlite-substrate-fork.md); ADR-005's posture
calculus applied, not renegotiated.
### OQ-11: Forked-substrate follow-through — scaffold, provenance register, and cherry-pick discipline — **RESOLVED**
- **Origin**: [ADR-012](decisions/012-forked-substrate-design.md)
(the fork design; substrate-side residue), [ADR-011](decisions/011-sqlite-substrate-fork.md),
[ADR-013](decisions/013-fold-substrate-into-sqlite.md)
- **Status**: resolved (2026-10-06, Phase 1 —
[ADR-018](decisions/018-provenance-register-and-cherry-picks.md);
item (1) dissolved 2026-10-05 by
[ADR-013](decisions/013-fold-substrate-into-sqlite.md))
- **Priority**: medium (nothing consumer-facing rides on it; it
resolves within the fork-scaffold task, which it does not gate)
- **Resolution**: Pinned by
[ADR-018](decisions/018-provenance-register-and-cherry-picks.md):
(1) **dissolved** by the fold
([ADR-013](decisions/013-fold-substrate-into-sqlite.md) §4) — no
separate substrate crate, no wiring question. (2) **Register
format/granularity**: a single in-tree file,
`alkstore-sqlite/src/substrate/PROVENANCE.md` (the ADR-013
placement, the file named;
timing and initial contents stay ADR-012 §1's as carried in-tree),
carrying two sections — upstream identity, and the delta register
(cherry-picks ride it as `cherry-pick`-category entries; no
separate log). The delta register is
**per-delta, category-tagged, not per-commit** (this repo's git
history is the
per-commit record; a per-commit register would duplicate it), each
entry pinning category / what / where / why / lineage (upstream
commit hashes for cherry-picks, "ours" for born deltas). The
initial entries are fixed by ADR-011's scope register and ADR-012
§3–§5 — the register formats them, decides nothing new. (3)
**Cherry-pick procedure**: five steps — notice (no ambient sweep;
triggered candidate arrival), evaluation against ADR-012 §6's
port-cost logic (fixes for re-derived-half classes are *not
adoptable* — the correct response there is an owned fix), hand
application preserving ADR-012 §3's fidelity posture and verified
against the inherited suites + contract suite, record **at
adoption** (register entry + one engine-changelog line citing the
register — visible but non-versioning per
[ADR-017](decisions/017-contract-versioning.md) §2 class 4), and a
commit whose message names the upstream hash(es), cross-linking
git history and register. A change touching the re-derived half or
crossing the contract boundary is *not* a cherry-pick — it is an
owned fork change under the engine's own discipline and, if
contract-visible, ADR-017's taxonomy; ADR-012 §2's boundary is
what splits the two record types. Its content lands as the
fork-scaffold task's opening section; gates nothing.
- **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 — 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? — **RESOLVED**
- **Origin**: [overview.md](overview.md),
[ADR-001](decisions/001-crate-split.md)
- **Status**: resolved (2026-10-06, Phase 1 —
[ADR-017](decisions/017-contract-versioning.md))
- **Priority**: medium
- **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: Core contract (Phase 1 review finds)
### OQ-12: Streams depth — key semantics, `StreamEvent` shape, ordering row, retention — **RESOLVED**
- **Origin**: [core-contract.md](core-contract.md) streams section,
[ADR-008](decisions/008-contract-v1-pinning.md) §1 (Phase 1
architecture review, 2026-10-05)
- **Status**: resolved (2026-10-05, Phase 1 —
[ADR-015](decisions/015-streams-depth.md))
- **Priority**: high (streams is a first-class
[ADR-002](decisions/002-feature-scope.md) mechanism; ADR-008's
"no placeholder semantics remain" positive consequence was false
for `publish_with_key` until this resolved — the contract suite
also could not pin cross-engine stream equivalence without an
ordering row)
- **Resolution**: Pinned by
[ADR-015](decisions/015-streams-depth.md). (1) **Key semantics —
option (a)**: the key is carried metadata (honker's shape — a
stored nullable column, round-tripped on every read, no
engine-enforced behavioral role); the ordering guarantee stays
**global FIFO by offset per stream**; per-key in-order reading is
the documented emergent pattern, not a promise. Option (b)
(server-enforced per-key ordering) rejected on its own decision
rule — no consumer row names the need; the consumer inventory's
streams row and the alkcall ecosystem-shape row (added for this
decision — alkcall's Sub/Pub/fan-out vocabulary routes the
topic/event-type dimension to *separate streams*, never per-key
lanes) both name none. Re-entry gate: a consumer-inventory row
naming server-enforced per-key ordering. Option (c) (cut
`publish_with_key`) rejected — removing a pinned v1 method name
would be a contract-breaking event for OQ-10, the cost is one
nullable column the inherited realizations already have, and the
key is a legitimately useful grouping token. (2) **A seam gap the
key decision exposed, closed in the same ADR**:
`publish_with_key_tx(stream, key, payload) -> offset` joins the
`TxHandle` trait — keyed publishes were otherwise unreachable
commit-atomically, the same hole shape OQ-13 exposed for the
outbox (`publish_tx` = the `None`-key call, not a separate engine
path; empty-`Some`-key → `InvalidName`, no new variants).
(3) **`StreamEvent` shape** — honker's near-verbatim with one
rename: `{ offset: i64, stream: String, key: Option<String>,
payload: Vec<u8>, created_at: i64 }` (`topic` → `stream` — one
term everywhere; the substrate's column name stays per ADR-012 §3
fidelity); `offset` immutable, never renumbered (gaps after trim
legal); `created_at` unix-seconds-at-publish, informational, not
an ordering field. (4) **Ordering guarantee row** — ADR-006 §2's
streams row extended: global FIFO by offset (read paths yield
`offset ASC`), offsets immutable — the row the contract suite
pins cross-engine equivalence against. (5) **Retention** —
`trim_to(horizon)` added to the stream handle (delete
`offset <= horizon`), consumer-invoked, no engine-default
retention, no ambient sweeper (the ADR-010 §6 posture); the
replay-forever default is documented with the in-contract tool to
bound it; reads from a trimmed-away region resume at the horizon;
saved offsets below the horizon stay valid. Framed as completing
contract v1 in place, pre-implementation — no versioning event,
OQ-10 untouched. Contract-suite rows added (cross-engine stream
equivalence, keyed-tx commit-atomicity, `trim_to` semantics —
both engines).
- **Cross-references**: OQ-04 (the pinning that left this depth
open), OQ-13 (the sibling depth gap; the amend-in-place framing),
OQ-01 (scope row), ADR-002, ADR-006 §2 (the guarantee table the
ordering row extends), ADR-008 §1/§4/§5/§8, ADR-010 §6 (the
no-ambient-sweeper posture the trim decision applies), ADR-012 §3
(the fidelity posture the column-name keep rides).
### 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**: 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**: 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 — resolved the
same day, [ADR-015](decisions/015-streams-depth.md)), ADR-002
(outbox scope row), ADR-007/ADR-008 (the seam design), ADR-010 §3
(the outbox's derived QueueOpts — unchanged by this).
## Theme: Deployment and capabilities
### OQ-08: Where does the honest single-host/multi-host boundary live in the trait surface? *(== OQ-ST-08)* — **RESOLVED**
- **Origin**: [deployment.md](deployment.md)
- **Status**: resolved (2026-10-06, Phase 1 —
[ADR-016](decisions/016-deployment-honesty.md))
- **Priority**: medium
- **Resolution**: Pinned by
[ADR-016](decisions/016-deployment-honesty.md): **no runtime
capability surface — in v1 and by default ever**. The honest
single-host/multi-host boundary lives in the two places it is
already true, which compose rather than rival: (1)
**compile-time engine identity** — the engine crate a binary
depends on *is* the deployment statement (single-driver binaries,
[ADR-001](decisions/001-crate-split.md); constructors in engine
crates, [ADR-008](decisions/008-contract-v1-pinning.md) §6 —
"the engine choice is a dependency-graph fact, not a runtime
branch"); (2) **deployment.md's documented matrix** — the
ops-facing facts of record, unchanged. Option 2
(`Store::capabilities()`) rejected field-by-field under ADR-008
§5's act-differently rule generalized to surface: host semantics
admit no in-process action (a flag would invite the engine-type
branch principle 4 bans); payload limits already have their runtime
carriage — the universal, contract-wide matchable
`PayloadTooLarge` variant (a `capabilities()` field would be a
second normative home); wake cadence and knobs are engine-crate
config (§6's split); and no consumer-inventory row names any
runtime-adapt need. No `engine_name()`, no `#[cfg]` capability
axes. The "must not pretend SQLite is multi-host" obligation
resolves into three standing statements (contract text carries
asymmetries via the taxonomy, engine-crate docs carry posture,
deployment.md carries ops facts); the misconfiguration case
(SQLite as shared network storage) follows the family's
deployment-asserts-truth posture (alkblobs precedent) — documented
boundary, no fabricated detection. Contract-suite row added:
`PayloadTooLarge` occurrence asymmetry (pg client-side pre-round-
trip, SQLite never) — with no capabilities API, the variant is the
one runtime carriage of an engine asymmetry, so its matchability
is pinned. Re-entry gate: a consumer-inventory row naming a
runtime-adapt need.
- **Cross-references**: OQ-04 (the pinning that parked this),
OQ-10 (narrowed by this — no capability struct to govern), OQ-11,
[ADR-001](decisions/001-crate-split.md),
[ADR-006](decisions/006-wake-and-delivery-contract.md),
[ADR-008](decisions/008-contract-v1-pinning.md) §1/§5/§6,
[ADR-012](decisions/012-forked-substrate-design.md),
[deployment.md](deployment.md).
## Deferred / Blocked
None. **The open question set is empty** — every OQ above is resolved
(2026-10-04 through 2026-10-06, ADR-001 through ADR-018). Phase 1's
remaining work is architecture review closure and the implementation
phase's gates; no external arrivals are being waited on. The review
rounds after closure (2026-10-06 second, 2026-10-07 third, 2026-10-08
fourth) likewise produced no new OQs — their findings resolved
directly as ADR-019/020, ADR-021, and
[ADR-023](decisions/023-fourth-review-round.md) respectively.