559 lines
31 KiB
Markdown
559 lines
31 KiB
Markdown
---
|
||
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.
|