Files
alkstore/docs/architecture/open-questions.md
T
glm-5.3-flash 79a135c934 docs: resolve OQ-05 + OQ-09 — queue semantics depth (ADR-010) and scheduler collapse (ADR-009)
ADR-009: scheduler collapses into queues — schedule()/unschedule()/
run_schedules (opt-in, no ambient timers), @every-only v1 grammar
(dissolves honker's local-TZ cron brittleness), boundary guarantee
row (at-least-once per boundary, fixed 64-cap catch-up with
skip-forward, row-locked fire tx as engine-generic no-double-fire
floor), __alkstore_scheduler leadership lock, InvalidSpec +
LeadershipLost taxonomy additions.

ADR-010: queue depth pinned engine-uniformly — three-state machine
(pending/processing/dead) with delete-on-ack, get_job sees dead rows,
heartbeat = renewal with late-heartbeat refusal, reclaim-consumes-
an-attempt stated as contract text, equal-jitter exponential backoff
(range definitionally pinned, 1 h cap), QueueOpts stamped onto job
rows at enqueue (no per-queue registry), move-to-dead dead-letter
with retention-sweep support and no redrive API, sweep_expired
carries the no-stranded-rows property (fixes honker's expired-
processing zombie hole — SQLite-side realization rides OQ-06 as a
concrete fork candidate), one engine-owned pg schema, queues are
rows not tables, result-storage cut-flag stands.

Also: full honker-machinery and pgboss-rs reference reads persisted
(docs/research/reference-*.md — the honker defect list pre-stages the
OQ-06 quality read), queues.md rewritten from design-space frame to
resolved-depth spec, core-contract/engines/README/overview/deployment/
ADR-002 propagated.
2026-10-05 03:10:52 +00:00

245 lines
12 KiB
Markdown

---
status: draft
last_updated: 2026-10-05
---
# 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). Next: OQ-06
(the SQLite dependency gate — now carrying two concrete fork
candidates from the queue-depth design), OQ-08 (rides the now-pinned
trait shape), OQ-10 (versioning discipline for contract extensions).
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.
### 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, OQ-09, OQ-08.
## 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_*`. 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)*
- **Origin**: [ADR-005](decisions/005-dependency-ownership.md),
[engine-sqlite.md](engine-sqlite.md)
- **Status**: open
- **Priority**: high (fork trigger is a gate on the SQLite engine's
dependency posture)
- **Resolution**: open. ADR-005 fixed the calculus and the trigger: the
Phase 1 quality read of honker-core 0.5's watcher/transactional core
(Writer/Readers/SharedUpdateWatcher/attach_*) — looking for defects
the POCs wouldn't surface, unsafe assumptions in the watcher
failure-handling, and schema-migration brittleness. Outcomes: posture
holds (no ADR change), or a fork/patch need is named (fork is normal
work per ADR-005).
- **Cross-references**: ADR-003, ADR-005, OQ-05 (the queue-depth read
handed it two concrete fork candidates: the expired-
processing-row zombie fix and dead-row `get_job` visibility —
complement-over-machinery vs fork is exactly its calculus).
## 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). 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).
### 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.
## Deferred / Blocked
None currently. Every open OQ above is actionable Phase 1 architecture
work (quality read, capability-surface shape, versioning discipline)
with its evidence base complete — no external arrivals are being
waited on.