diff --git a/docs/research/phase-0.md b/docs/research/phase-0.md index fe266ac..8e9c9c3 100644 --- a/docs/research/phase-0.md +++ b/docs/research/phase-0.md @@ -1,30 +1,28 @@ --- status: draft -last_updated: 2026-10-04 (POC #2 ran and passed — OQ-ST-03 resolved: -per-engine drivers, rusqlite+honker-core for SQLite / tokio-postgres+ -deadpool-postgres for Postgres, findings in poc-pg-posture-findings.md; -OQ-ST-04 now has measured ground on both engines, wake contract -numbers + tx-seam shapes + listener-recovery semantics recorded from -POC #2; OQ-ST-05/06 carry both POCs' per-subsystem votes. Findings -review-pass verified 2026-10-04: contract suite runs green sequentially -(`--test-threads=1`); parallel invocation fails by cross-test -interference (shared db/channels harness) — recorded in the findings, -not a contract failure.) +last_updated: 2026-10-04 (Phase 0 research complete. Both POCs ran, passed, +and are folded into the register; the register itself was restructured +after the findings review for consistency. OQ-ST-01/02/03/07 resolved; +OQ-ST-04/05/06 de-risked with only contract/design work remaining; +OQ-ST-08 open. Convergence recorded in §Convergence. Next: Phase 1.) --- # alkstore — Phase 0 (Exploration) This document captures Phase 0 (Exploration) for the `alkstore` crate: vision, guiding principles, prior art, the open-question register -(OQ-ST-01..NN), and the POC register. Phase 0's objective per -`docs/sdd_process.md`: *capture -vision and guiding principles; research options; validate approaches; -converge on a recommended approach.* The scope question (OQ-ST-01) has -been answered per-feature from the consumers' documents -(`consumer-inventory.md`); the driver question (OQ-ST-03) has a named -SQLite option space with POC #1 specified against it; the reactive -contract (OQ-ST-04) and ownership questions (OQ-ST-05/06) are the open -work the research rounds feed. +(OQ-ST-01..NN), the POC register, and the convergence the phase +objective asks for. Phase 0's objective per `docs/sdd_process.md`: +*capture vision and guiding principles; research options; validate +approaches; converge on a recommended approach.* All of that +objective is now met: the scope question (OQ-ST-01) is answered +per-feature from the consumers' documents, the crate-split (OQ-ST-02) +is decided, the driver question (OQ-ST-03) is closed POC-backed on +both engines, the reactive contract (OQ-ST-04) is de-risked down to +paper work, and the ownership postures (OQ-ST-05/06) have recorded +per-subsystem votes. §Convergence collects the recommendation; the +OQ register's open residue is Phase 1 architecture work, not further +research. Context for why this crate starts now: **alkblobs** (`/workspace/@alkdev/alkblobs` — spec + POCs only, paused mid-planning) @@ -59,10 +57,19 @@ data_version` (default 1 ms → single-digit-ms delivery) and re-reads indexed state after every wake. Its own Prior Art section names the lineage: `pg_notify`, pg-boss, Oban, Huey. -**The interface finding (2026-10-03, from the four honker.dev guides + -`packages/honker-rs/src/lib.rs`, the Rust binding): honker's *surface* -is already the unified-interface candidate.** Its Rust binding exposes -exactly the surface this crate wants, engine-clean: +**The goal is not "port honker to Postgres." The goal is the +interface:** one store API whose consumer code (queues, streams, notify) +looks the same whether the backing engine is SQLite or Postgres, while +each engine uses its own native wake/delivery story under the hood. The +honker docs recommend pgboss + `pg_notify` for the Postgres equivalent — +that recommendation is the design brief for this crate's Postgres engine. + +### The interface finding + +*(2026-10-03, from the four honker.dev guides + +`packages/honker-rs/src/lib.rs`, the Rust binding, v0.5.0.)* Honker's +*surface* is already the unified-interface candidate. Its Rust binding +exposes exactly the surface this crate wants, engine-clean: - `db.queue(name, QueueOpts)` → `enqueue / enqueue_tx / claim_one / claim_batch / ack_batch / cancel / get_job / sweep_expired / @@ -102,16 +109,6 @@ Postgres side: `pg_notify` gives fast triggers with no retry or visibility semantics; pg-boss/Oban are the durable-layer gold standards — "If you already run Postgres, use the Postgres tools." -The goal is **not** "port honker to Postgres." The goal is the interface: -one store API whose consumer code (queues, streams, notify) looks the -same whether the backing engine is SQLite or Postgres, while each engine -uses its own native wake/delivery story under the hood. The honker docs -recommend pgboss + `pg_notify` for the Postgres equivalent — that -recommendation is the design brief for this crate's Postgres engine — -and honker's Rust binding (`honker-rs`, v0.5.0) is the concrete -candidate for what that unified interface literally looks like (§Interface -finding). - **Consumer shape (from the paused alkblobs planning):** any crate that today uses the ecosystem's repo-pattern + in-memory adapter should be able to swap in an alkstore-backed engine and get durability + true @@ -137,7 +134,9 @@ Guiding principles: as the business write; rollback drops both. The unified surface must preserve this on any engine, because the ecosystem's repo pattern assumes it (a business write that loses its side-effect notification - is the dual-write problem honker names). + is the dual-write problem honker names). Both POCs verified this + property holds natively on their engines — it is now evidence, not + aspiration. 3. **Ownership of the whole stack.** honker is third-party; pgboss-rs is third-party. Whether any of them are adopted, forked, or used as schema/design reference only is a deliberate per-question decision — @@ -152,63 +151,50 @@ Guiding principles: 5. **No panics, `tokio`, `thiserror`, lean base crate, feature-gated optional engines** — family-standard, pre-committed (AGENTS.md). -## The driver conflict (Phase 0's central tension) +## The driver conflict (Phase 0's central tension — now resolved) -The immediate design fork, flagged by the user: +The immediate design fork, flagged by the user early in Phase 0: +a single crate with both engines means a driver decision, and the +reactivity story is entangled with it. The facts that made the tension +real: - **The alkblobs POCs used `tokio-postgres` + `deadpool-postgres`** (validated in `poc-postgres-kv-findings.md`, `poc-pglo-findings.md` — - including Large Objects work). -- **pgboss-rs (`/workspace/pgboss-rs` @ 98f7d9e) uses `sqlx`** (`sqlx Postgres - runtime-tokio`). -- honker-core uses `rusqlite`. - -A single crate with both engines means a driver decision, and the -reactivity story is entangled with it: - + including Large Objects work), while **pgboss-rs** + (`/workspace/pgboss-rs` @ 98f7d9e) uses `sqlx`, and **honker-core + uses `rusqlite`.** - **pgboss-rs currently has no `LISTEN`/`NOTIFY` at all** (verified 2026-10-03 against the checkout — `src/` contains no LISTEN/NOTIFY - usage; consumption is `fetch_job` polling). The node original relies - on `pg-boss`'s own maintenance/polling; the port did not pick up a - push channel. So *even* "use pgboss for the queue" does not deliver - reactivity — LISTEN/NOTIFY wiring would be new work either way, and - the driver choice determines *whose* LISTEN plumbing (sqlx's - `PgListener` is built-in; tokio-postgres uses its `Connection` - notifications). + usage; consumption is `fetch_job` polling). So *even* "use pgboss + for the queue" does not deliver reactivity — LISTEN/NOTIFY wiring + would be new work either way, and the driver choice determines + *whose* LISTEN plumbing. - **Honker's reactivity on SQLite is a watcher polling `PRAGMA data_version`** — a fundamentally different mechanism from LISTEN/NOTIFY. The unified reactive trait must abstract over both without collapsing to the polling behavior of the weaker side. +- **honker-rs is sync** (`std` threads + blocking iterators; + parking_lot + rusqlite, no tokio) while the family standard is + tokio-async — so even the SQLite side looked like a port-and-adapt, + and the async question was entangled with whether rusqlite-in-a-pool + or a native-async driver (sqlx sqlite) is the right shape. -Two corrections to the original framing (2026-10-03, after reading -honker-rs and the honker.dev guides): +Two corrections sharpened the framing before the POCs landed: the +honker-rs *interface* is largely driver-independent, with the +transactional seam (`*_tx` methods assuming a live transaction handle +from the caller's driver) as the one genuinely driver-coupled design +point; and the alktty REQ-TTY-01 family precedent ("backends are not +required to be natively async" — bridge-at-the-seam is a supported +posture, not a workaround) already blesses the sync-machinery + +async-facing-trait shape, weakening native-async's main differentiator. -1. **The honker-rs *interface* is largely driver-independent.** The - Queue/Stream/notify/scheduler surface (§Interface finding) speaks in - domain terms (channels, offsets, job ids, visibility timeouts), not - driver terms. What *is* driver-coupled is the transactional seam: - `enqueue_tx`/`publish_tx`/`save_offset_tx` and the extension's - notify-in-transaction all assume the caller can hand the engine a - live transaction handle from *its* driver. The unified trait's - transactional seam is therefore the driver-sensitive design point — - and it interacts with the engines' different transaction models - (SQLite: single writer, synchronous; Postgres: interactive - transactions over a pool, transaction-scoped LISTEN). -2. **honker-rs is sync (`std` threads + blocking iterators) — parking_lot - + rusqlite, no tokio.** The family standard is tokio-async. So even - the SQLite side is a *port-and-adapt* (sync → async), not an adopt; - and the driver question for SQLite is also entangled with whether - tokio-native sqlite drivers (sqlx sqlite) change the queue/wake - machinery's shape versus rusqlite-in-a-pool. - -This tension is OQ-ST-04 below. It is *not* resolved by "pgboss is well -written so start there" — that is exactly the inherited-assumption -shape the SDD process flags. What pgboss-rs genuinely offers (schema -DDL, job states, retry semantics, the node-compatible API) is design -reference regardless of driver. The reactivity gap is the concrete, -verified difference between pgboss-rs and what this crate needs — -whether forked or re-derived, the push channel is work this crate -builds itself (OQ-ST-05). +The tension dissolved with the POCs (details and measurements at +OQ-ST-03): **per-engine drivers under OQ-ST-02's crate split**, with +the bridged-rusqlite seam measurably *faster* than sqlx's native async +on SQLite, and tokio-postgres natively async-native on Postgres. What +pgboss-rs genuinely offers (schema DDL, job states, retry semantics, +the node-compatible API) is design reference regardless of driver; +the push channel is this crate's own work either way (OQ-ST-05). ## Prior art @@ -223,8 +209,7 @@ recorded per AGENTS.md §3 when code is adopted, not while only reading. ### honker — the SQLite-side template `/workspace/honker` (checkout @ f4e53c6; SQLite extension + -bindings). What matters for -this crate: +bindings). What matters for this crate: - **The full feature set to match on Postgres** (its §What It Does): notify/listen across processes, durable at-least-once queues @@ -248,23 +233,24 @@ this crate: inside the caller's transaction. This is the pattern the unified API must keep visible and cheap. - **The honker-rs binding is the concrete interface prior art** (v0.5.0, - `packages/honker-rs`, read 2026-10-03): the full surface per §Interface - finding. Notable honest limitations documented by its own guides — - the per-binding processing-guarantees table (auto-checkpoint cadence - vs manual offset save; several bindings "may persist an offset on a - cadence... without knowing whether downstream application work - committed"), the Node reverse-order consumer-checkpoint bug, per- - binding feature gaps (JVM missing cancel/get_job, Go/Bun/C++ - missing typed pruning) — are exactly the seams a single-crate version + `packages/honker-rs`, read 2026-10-03): the full surface per + §Interface finding. Notable honest limitations documented by its own + guides — the per-binding processing-guarantees table (auto-checkpoint + cadence vs manual offset save; several bindings "may persist an + offset on a cadence... without knowing whether downstream application + work committed"), the Node reverse-order consumer-checkpoint bug, + per-binding feature gaps (JVM missing cancel/get_job, Go/Bun/C++ + missing typed pruning) — are exactly the seams a single-crate version designed-for-the-contract from day one can clean up. Its sync-only shape (std threads, blocking iterators, no tokio) is a port-and-adapt constraint, not an adopt candidate as-is. ### pgboss-rs — the Postgres queue family reference -`/workspace/pgboss-rs` (checkout @ 98f7d9e; v0.1.0-rc6, MIT/Apache-2.0 dual). Ported from -node pg-boss: builder-based queue/job API, retry/delay/priority/ -singleton/dead-letter concepts, `sqlx` 0.8, schema-scoped DDL. +`/workspace/pgboss-rs` (checkout @ 98f7d9e; v0.1.0-rc6, MIT/Apache-2.0 +dual). Ported from node pg-boss: builder-based queue/job API, +retry/delay/priority/singleton/dead-letter concepts, `sqlx` 0.8, +schema-scoped DDL. - Verified gap (2026-10-03): **no LISTEN/NOTIFY** anywhere in `src/` — consumption is polling `fetch_job`. Any push-reactivity is new @@ -308,174 +294,202 @@ split (store layer stays substrate-free), the store layer here stays alkcall-free; any networked surface is an ops module/sibling concern and a separate decision. +## Convergence + +Phase 0's objective — converge on a recommended approach — is met. +The recommendation, assembled from the OQ resolutions below (each +carries its own evidence): + +**Shape (OQ-ST-02):** a reactive-core crate carrying the trait +surface/types, plus per-engine crates implementing it (SQLite, +Postgres; a mem-shaped test engine as a third impl if useful). The +split makes the engines' real asymmetry of work structural: the +SQLite engine rides honker's existing machinery; the Postgres engine +is the build-heavy side; any future engine is additive. It also keeps +each engine binary single-driver, which the dependency constraints +below effectively require. + +**Engines (OQ-ST-03):** + +- *SQLite engine:* rusqlite + published `honker-core` 0.5.0 + (POC posture 1) — we own connection/schema/watcher wiring per the + alknet-filesystem POC's shape; the async seam is the bridge-at-the-seam + posture (writer-slot + `spawn_blocking`, REQ-TTY-01 precedent), + measured ~2× faster at p50 than sqlx's native async; honker-core's + `SharedUpdateWatcher` is inherited for wake (p50 ≈ 1.4 ms, + battle-tested failure handling). No `.so` runtime artifact, no + vendored patches. +- *Postgres engine:* tokio-postgres + deadpool-postgres (POC #2) — + pooled connections for queries/claims (`FOR UPDATE SKIP LOCKED`), + a dedicated non-pooled listener connection with a hand-rolled ~90-line + LISTEN forwarder (immediate reconnect, synthetic reconnect-wake on a + reserved channel closing the no-replay hole, identifier quoting), + LISTEN-driven claim beats poll 5–16× at p50. `postgres-notify` + evaluated and passed over (derive-not-adopt). + +**Contract starting shape (OQ-ST-04):** the honker-rs surface +(§Interface finding), scoped to the inventory-confirmed features, is +the starting artifact for contract pinning. The load-bearing pieces +hold identically on both engines, POC-verified: the opaque-wake + +re-read listener contract; notify = fire-and-forget commit-atomic (no +replay) vs streams = durable with explicit per-consumer offsets; and +the caller-held tx handle (`*_tx` on the handle) whose per-engine +difference is bridging mechanism, not trait shape. + +**Ownership (OQ-ST-05/06):** published-library dependencies as the +default posture — honker-core (SQLite), tokio-postgres + +deadpool-postgres (Postgres); the pg queue machinery is re-derived on +our driver with the pg-boss schema family as design reference; the +hand-rolled listener forwarder replaces `postgres-notify`. Named fork +triggers remain: a Phase 1 quality read of honker-core's +watcher/transactional core, or a needed change upstream won't take. + +**Scope (OQ-ST-01):** notify/listen, named locks, queues, outbox, +scheduler, streams are in (with the inventory's per-row evidence +grades); rate limits and result storage are cut-flags; the loadable +extension surface is out (OQ-ST-07); honker's exclusion lines (DAGs, +task chains/chords, multi-writer replication, distributed locking) +stay out. + +**Phase 1 inherits, as architecture work over complete evidence:** +the contract-pinning itself (OQ-ST-04's remainder — which surface +parts become contract, per the inventory rows, and the per-engine +capability surface, OQ-ST-08); queue semantics depth +(retry/backoff/dead-letter/sweep design, OQ-ST-05); the honker-core +quality read (OQ-ST-06's fork trigger); and the deployment-matrix / +capability-flags decision (OQ-ST-08). No further Phase 0 research is +required. The known deployment constraints to design around: +honker-core 0.5.0 pins rusqlite ^0.40.1 whose rustc requirement +(≥1.99) is a deployment note; pooled connections cannot carry LISTEN +(deadpool#360) so the listener connection is a per-process budget line +outside the pool; notify payloads are ≤ 8000 bytes (large payloads +ride a table row with the id in the notification — the honker-outbox +shape); the reserved reconnect-wake channel name needs a namespace +convention in the contract. + ## Open Questions Register in `docs/research/phase-0.md`; IDs OQ-ST-NN (stable, append only). Promotion target: Phase 1 `docs/architecture/open-questions.md`. +Status conventions: `resolved` (evidence recorded here); `open — +` where the work-type names the remaining work and the +entry is de-risked (the remainder is Phase 1 architecture work, not +further research); `open` (genuinely open). ### OQ-ST-01: Scope boundary — which honker features are in-scope? -**Answered by the consumer inventory (2026-10-04) — -`docs/research/consumer-inventory.md`; scope votes now shrink to named -rows, not the whole feature list.** The original framing ("blocked on a +**Status: resolved (2026-10-04).** Answered by the consumer inventory — +`docs/research/consumer-inventory.md`; scope votes shrink to named +rows, not the whole feature list. The original framing ("blocked on a consumer-driven inventory pass") was circular hedging: the consumers are paused, so the input would never arrive — but their *documents* are stable evidence, and the inventory walks them per feature. -In-short: notify/listen and named locks are pinned or ADR-shaped -(alkfs path-tree invalidation; alkblobs fleet sweeper lock; alkfs -OQ-FS-05 writer coordination); queues and the outbox helper are -documented needs (alkfs sync/fetch-on-miss outbox; alkblobs -embedder-owned maintenance cadence); the scheduler is documented-thin -(the family-wide "who sweeps/renews/reaps" problem, possibly collapsing -into queues); streams was upgraded by an operator-authority record -(the 2026-10-04 correction to the inventory's initial read: -type-filtered event watching from several places — repo-change -subscriptions in a git app, cross-app event watching — a reactivity -requirement notify cannot serve honestly, being fire-and-forget); -rate limits and result storage have **no -named consumer** — carried per the keep-until-implementation posture -with cut-flags visible, cut later rather than silently included. -Per-feature exclusion lines (honker's own: DAGs, task chains/chords, -multi-writer replication, distributed locking) have no consumer either; -they stay out unless a consumer document grows one. New consumers -(alksftp, the alknet rewrite) add a row to the inventory *before* being -assumed into scope. +- In scope, first-class: **notify/listen** (pinned: alkfs path-tree + invalidation), **streams** (operator-authority record — + type-filtered event watching from several places, e.g. repo-change + subscriptions; a reactivity requirement notify cannot serve honestly, + being fire-and-forget). +- In scope: **named locks** (pinned: alkblobs fleet sweeper lock; + documented: alkfs OQ-FS-05 writer coordination), **queues + the + outbox helper** (documented: alkfs sync/fetch-on-miss outbox; + alkblobs embedder-owned maintenance cadence), **scheduler** + (documented-thin: the family-wide "who sweeps/renews/reaps" problem, + possibly collapsing into queues — watch at OQ-ST-04). +- Cut-flags (no named consumer; carried per the + keep-until-implementation posture, cut later rather than silently + included): **rate limits** (alkgit enforces budgets in its own wire + layer — an in-crate alternative exists), **result storage**. +- Out: honker's exclusion lines (DAGs, task chains/chords, + multi-writer replication, distributed locking) — no consumer names + these either; they stay out unless a consumer document grows one. + +New consumers (alksftp, the alknet rewrite) add a row to the inventory +*before* being assumed into scope. ### OQ-ST-02: Crate scope — one store crate, or reactive-core + engines? -Options include: single crate with feature-gated engines (the alk* -feature-gate pattern); a core trait crate + per-engine crates; engine -crates consuming a thin core. The answer constrains the driver decision -(OQ-ST-03) and the base-crate-lean invariant. +**Status: resolved (2026-10-04, operator decision): reactive-core + +engine crates** — a core crate carrying the trait surface/types, +per-engine crates implementing it (sqlite, postgres; mem-shaped test +engine as a third impl if useful). Options considered: single crate +with feature-gated engines (the alk* feature-gate pattern); a core +trait crate + per-engine crates; engine crates consuming a thin core. -**Resolved (2026-10-04, operator decision): reactive-core + engine -crates — a core crate carrying the trait surface/types, per-engine -crates implementing it (sqlite, postgres; mem-shaped test engine as a -third impl if useful).** The reasoning, recorded because it overrode -the inventory's lean: the split isolates the engines' real asymmetry of -work — the SQLite engine rides honker's existing machinery as the -baseline (port-and-adapt sync→async), the Postgres engine is the -build-heavy side (LISTEN/NOTIFY wiring + pg-boss-family schema work, -OQ-ST-05) — and it makes any future engine (alkfs's in-tree needs, an -ops-surface engine) additive rather than a feature-graph edit to one -crate. More future-proof by construction; the base-crate-lean -invariant becomes structural rather than a feature-discipline. +The reasoning, recorded because it overrode the inventory's lean: the +split isolates the engines' real asymmetry of work — the SQLite engine +rides honker's existing machinery as the baseline (port-and-adapt +sync→async), the Postgres engine is the build-heavy side +(LISTEN/NOTIFY wiring + pg-boss-family schema work, OQ-ST-05) — and it +makes any future engine (alkfs's in-tree needs, an ops-surface engine) +additive rather than a feature-graph edit to one crate. +Base-crate-lean becomes structural rather than a feature-discipline. +It also keeps each engine binary single-driver, which the +libsqlite3-sys link-collision constraint (OQ-ST-03) effectively +requires. -The inventory's uniform-feature-family fact (2026-10-04) still stands -and is not contradicted: it now reads as "the core contract can stay -small — one feature family, both engines" instead of "the crates should -merge." Its single-crate lean was inductive from that fact; the -structural reasoning above supersedes it (correction recorded in +The inventory's uniform-feature-family fact still stands, not +contradicted: it reads as "the core contract can stay small — one +feature family, both engines," not as an argument for the crates to +merge (its single-crate lean was inductive from that fact; the +structural reasoning here supersedes it — correction recorded in consumer-inventory.md too). ### OQ-ST-03: Driver story — sqlx, tokio-postgres, or per-engine drivers? -The named tension (§The driver conflict), restated as the decision: +**Status: resolved (2026-10-04, both halves, POC-backed): per-engine +drivers — rusqlite + honker-core 0.5.0 (SQLite engine); tokio-postgres +0.7.18 + deadpool-postgres 0.14.2 (Postgres engine) — under OQ-ST-02's +per-engine-crate split.** -- **One driver across engines**: sqlx (both sqlite + postgres native - support, one API — but the alkblobs POC evidence is tokio-postgres) - vs tokio-postgres per-engine (sqlite story unclear — tokio-postgres - is pg-only; rusqlite is the sqlite native). -- **Per-engine drivers under a unified trait**: tokio-postgres + - deadpool-postgres (POC-validated in alkblobs findings) + rusqlite - (honker's choice, so honker's SQLite machinery ports cleanly). -- **Adopt/fork pgboss-rs**: brings sqlx along where the queue lives. +Original option list (the decision as first framed): one driver across +engines (sqlx: both engines native, one API — but the alkblobs POC +evidence is tokio-postgres); per-engine drivers under a unified trait +(tokio-postgres + deadpool-postgres, POC-validated in alkblobs +findings, + rusqlite, honker's choice); adopt/fork pgboss-rs (brings +sqlx along where the queue lives). Honest unknowns at framing time: +does sqlx support SQLite `data_version`/extension-style machinery +equally well; does a unified trait over `(tokio-postgres, rusqlite)` +pay more trait-fitting cost than sqlx's single-API convenience costs +elsewhere; extension loading under sqlx vs rusqlite; and the async +question honker-rs's sync shape forces — rusqlite-in-a-pool with a +bridge, or a native-async driver? -Honest unknowns worth surfacing: does sqlx support SQLite -`data_version`/extension-style machinery equally well? Does a unified -trait over `(tokio-postgres, rusqlite)` pay more trait-fitting cost -than sqlx's single-API convenience costs elsewhere? What does -SQLITE_ENABLE/extension loading look like under sqlx vs rusqlite? -(And the async question honker-rs forces, §driver-conflict correction -2: is rusqlite-in-a-pool the right tokio shape, or does sqlx-sqlite's -native async change the watcher machinery's design?) - -**Family precedent bearing on the async sub-question (2026-10-04): -the async-facing-trait + sync-bridge posture is already family-standard, -twice over.** alktty REQ-TTY-01 (`/workspace/@alkdev/alktty/docs/ -architecture/tty-backend.md` §REQ-TTY-01): "backends are not required -to be natively async" — the trait's adapter-facing types are the async -contract; a backend may expose blocking handles internally and bridge -them (blocking std::io on dedicated std threads or `spawn_blocking`, -feeding tokio mpsc/oneshot channels) as a **documented, supported -implementation strategy, not a workaround** (the wezterm/portable_pty -pattern; the local-PTY reference impl runs three bridge threads). -alkblobs store-api.md pins the same execution posture from the storage -side: blocking file work lives in `spawn_blocking` inside engine impls -(the alkgit trait-execution pattern) — the store never blocks the -executor. Together these reframe the honker-rs correction above: the -sync→async port's *shape* is less "rewrite onto a native-async driver" -and more "keep the sync machinery (rusqlite + watcher thread, blocking -iterators) and bridge at the trait seam" — which weakens sqlx's main -differentiator (native async) for the SQLite side specifically, and -makes the honest OQ-ST-03 comparison: bridge-posture rusqlite -(honker-machinery ports verbatim, bridge cost measured in threads not -rewrites) vs sqlx-sqlite (native async, but the watcher/extension -machinery must be re-validated against a driver that owns its -connection/pool internals differently). - -Genuinely open; needs research rounds (library capabilities vs the -unified-trait shape) and possibly a POC. Not deferred — this is the -central Phase 0 research question. - -**The SQLite option space, named explicitly (2026-10-04, operator + -verified against the checkout @ f4e53c6):** three distinct postures, not -one "rusqlite vs sqlx" axis — +The SQLite option space, named explicitly (2026-10-04, operator + +verified against the checkout @ f4e53c6) — three distinct postures, not +one "rusqlite vs sqlx" axis: 1. **honker-core on our own rusqlite connection** (the `attach_honker_functions` shape — the alknet-filesystem POC's actual usage): we own the connection, the schema bootstrap, and the watcher; honker supplies the SQL-function machinery. 2. **honker-rs as the crate's SQLite substrate** (`Database::open`, - typed Queue/Stream/Transaction primitives; the guide's "it *is* the - integration" posture): the trade is ownership — honker-rs opens and - holds its own connections, its `Database` wraps a connection mutex - (transactions pin the mutex; same-thread `*_tx` methods only, - deadlock-by-mutex on cross-thread reuse), and the whole engine is - sync under OUR async core (bridge at every seam). Maximum reuse, - least control; the transactional seam inherits honker-rs's - mutex-pinned transaction model rather than ours. + typed Queue/Stream/Transaction primitives): maximum reuse, least + control — honker-rs opens and holds its own connections, its + `Database` wraps a connection mutex (transactions pin the mutex; + same-thread `*_tx` methods only), and the whole engine is sync + under OUR async core (bridge at every seam). 3. **raw SQL over sqlx-sqlite with the honker loadable extension** - (guides/orm/rust §sqlx: `SqliteConnectOptions::extension(ext)` + - `SELECT honker_bootstrap()`, then every feature is plain SQL — - `honker_enqueue`, `notify`, ... — callable through - `SqliteExecutor<'e>`, satisfied by pool, connection, AND - Transaction alike). **Verified in-harness**: the pattern is CI-proven - in the honker checkout itself (scripts/proof/orm/rust — async - business-write + `honker_enqueue` inside a `conn.begin()` tx, - commit-visibility + rollback-drops-job asserted), so the - transactional property holds *natively async* here — no sync bridge - on the call path at all. + (`SqliteConnectOptions::extension(ext)` + `SELECT + honker_bootstrap()`, then every feature is plain SQL callable + through `SqliteExecutor<'e>` — pool, connection, AND Transaction + alike). Verified in-harness: the pattern is CI-proven in the honker + checkout itself (scripts/proof/orm/rust — async business-write + + `honker_enqueue` inside a `conn.begin()` tx, commit-visibility + + rollback-drops-job asserted). -Option 3 is the genuinely interesting one: it dissolves most of the -async tension for the SQLite engine (native-async calls, sqlx-owned -pooling, transactional enqueue proven in the checkout's own proof -suite), keeps honker's machinery as a library-free extension artifact -we don't own code-wise, and moves ALL our custom logic (watcher -ownership, stream cursors, extra SQL) into our own crate. The cost -side: a runtime-loaded .so (build-or-download per the guide) is a -packaging/deployment dependency the other options don't have; sqlx's -load-then-disable extension discipline is worth pinning at the source -(the guide documents it — `SqliteConnectOptions::extension` loads -during connect only, then disables the C load-extension API); and the -watcher (whose machinery otherwise rides option 1/2's honker-core -library linkage) becomes OUR component watching a sqlx-managed pool — -`PRAGMA data_version` re-read design (OQ-ST-04's SQLite wake side) -lands in our lap either way, but under option 3 we can't lean on -honker-core's `SharedUpdateWatcher` thread shape as-is. Options are not -mutually exclusive across engines: option 3 (or 1) for SQLite is -compatible with tokio-postgres for the Postgres engine under the -OQ-ST-02 split. +Family precedent bearing on the async sub-question (2026-10-04): the +async-facing-trait + sync-bridge posture is family-standard, twice +over — alktty REQ-TTY-01 ("backends are not required to be natively +async": blocking work on dedicated threads or `spawn_blocking` feeding +tokio channels is a documented, supported implementation strategy, not +a workaround; the wezterm/portable_pty pattern), and alkblobs +store-api.md (blocking file work in `spawn_blocking` inside engine +impls). This reframed the sync→async port: less "rewrite onto a +native-async driver," more "keep the sync machinery and bridge at the +trait seam" — weakening sqlx's main differentiator for the SQLite side. -This expands OQ-ST-03's option list; the comparison matrix ("honker's -machinery as a linked library on our connection" vs "honker's machinery -as a loaded extension under sqlx") is now the concrete research round, -and a POC comparing options 1 and 3 directly (same queue/stream/notify -surface both ways, incl. watcher wiring) is the natural first POC for -the register. - -**SQLite half resolved by POC #1 (2026-10-04; findings: +**Resolution, SQLite half — POC #1 (2026-10-04; findings: `poc-sqlite-posture-findings.md`): posture 1 — honker-core on our rusqlite.** All three gate conditions fired in A's favor, mildly: the bridged path is ~2× sqlx's native-async at p50 (0.354 vs 0.707 ms on @@ -491,7 +505,7 @@ honker-core=0.5.0 pins rusqlite ^0.40.1 whose rustc requirement currently need a vendored one-line libsqlite3-sys patch — OQ-ST-02's per-engine-crate split is what keeps the engine binary single-driver. -**Postgres half resolved by POC #2 (2026-10-04; findings: +**Resolution, Postgres half — POC #2 (2026-10-04; findings: `poc-pg-posture-findings.md`): tokio-postgres + deadpool-postgres.** All three gate conditions held: the transactional property is native (in-tx NOTIFY delivers only at commit; rollback drops job row + @@ -511,40 +525,21 @@ quoting control, no dependency posture) over `postgres-notify` 0.3.8 unquoted-identifier LISTEN — derive-not-adopt; fallback if upstream improves). The sqlx `PgListener` fallback retired unfired. -**OQ-ST-03 resolution: per-engine drivers — rusqlite + honker-core -(SQLite engine), tokio-postgres + deadpool-postgres (Postgres -engine), under OQ-ST-02's per-engine-crate split.** Resolved -(2026-10-04), POC-backed on both halves. - -**Resolved (2026-10-04, both halves): per-engine drivers — -rusqlite + honker-core for the SQLite engine; tokio-postgres + -deadpool-postgres for the Postgres engine.** The Postgres half is -POC #2's (findings: `poc-pg-posture-findings.md`): every gate -condition fired affirmatively on the tokio-postgres posture — -unified surface (enqueue_tx/claim/ack/notify_tx/listen/stream-offset/ -locks) with the transactional property intact (in-tx NOTIFY delivers -only on commit; rollback drops all), LISTEN-driven wake beats poll -5–16× at p50 (3–6 ms vs 32–50 ms end-to-end claim latency; isolated -wakes 1.1 ms p50, 300/300 delivered), the tx-seam is *simpler* on pg -than SQLite (tokio-postgres Client is Send+Sync — no spawn_blocking -rigging), pooled-LISTEN discard (deadpool#360) verified and pinned, -and `postgres-notify` 0.3.8 evaluated and passed over (derive-not- -adopt: lazy reconnect, initial-connect script gap, identifier -quoting; the hand-rolled forwarder is ~90 lines and pitfalls are -pinned by tests). The sqlx `PgListener` fallback never fired and is -retired. - ### OQ-ST-04: The reactive abstraction — what does the unified notify surface look like? -The two engines' mechanisms are structurally different: SQLite = -watcher polling `PRAGMA data_version` (deliver on commit; no -server-side push exists), Postgres = LISTEN/NOTIFY (server push, -connection-bound, no retry/visibility semantics). The reactive trait -must have a shape both implement without one emulating the other's -weaknesses. +**Status: open — contract work (de-risked; both engine sides +POC-verified). The remainder is contract-pinning paper work over a +complete evidence base — Phase 1, not further research.** + +Background: the two engines' wake mechanisms are structurally +different — SQLite = watcher polling `PRAGMA data_version` (deliver +on commit; no server-side push exists), Postgres = LISTEN/NOTIFY +(server push, connection-bound, no retry/visibility semantics). The +reactive trait must have a shape both implement without one emulating +the other's weaknesses. The honker-rs surface (§Interface finding) is the concrete starting -point — the question decomposes into contract-pinning rather than +point — the work decomposes into contract-pinning rather than shape-invention: - Which parts of the honker-rs surface become the crate's *contract*: @@ -567,91 +562,92 @@ shape-invention: promise durability it only has on one engine (that's what streams are for)? - The transactional seam (`enqueue_tx`/`publish_tx`/`save_offset_tx`) - across two transaction models — the driver-coupled point (§driver- - conflict correction 1). + across two transaction models — the driver-coupled point (§The + driver conflict). +- How a caching subscriber receives sufficient invalidation + information (keys? table/channel names? opaque wake + re-read + contract?) — rides the same contract decision. -How does a caching subscriber (the ecosystem's hot-path pattern) -receive sufficient invalidation information (keys? table/channel -names? opaque wake + re-read contract?) — rides the same contract -decision. +Evidence now in hand (both POCs, 2026-10-04): -**POC #1 input (2026-10-04):** the opaque-wake + re-read contract -survives both SQLite postures unchanged (same `data_version` mechanism -underneath — wake coalescing verified correct by design, p50 -1.4–2.2 ms at the default 1 ms cadence, missed-wake stress passes with -correct re-reads on both); listener semantics (start at MAX(id), no -replay) unchanged. The `*_tx` seam question crystallized into -something concrete: the SQLite-side tx handle is an owned writer-slot -lease whose ops each ride `spawn_blocking` (the connection is not -`Sync`; holding it across await points is wrong) — Phase 1's contract -work starts from that shape plus the POC's `TxHandle` sketch (with its -two recorded frictions: the `as_any_mut` downcast and the -thread-affinity of rusqlite tx ops). - -**POC #2 input (2026-10-04, closing the pg side):** the same -opaque-wake + re-read contract holds on Postgres and the wake -mechanism is *already* the shape the contract wants — LISTEN delivers -push (~1.1 ms p50, 300/300 isolated, no coalescing needed), has no -replay (gap commits recovered by the listener broadcasting a synthetic -reconnect-wake on a reserved channel, verified: subscribers wake and -re-read state correctly through a killed-connection recovery), and -notify is commit-atomic natively (delivers only at tx commit; rollback -drops it — the exact analogue of honker's notify-in-tx property). The -tx-seam resolves to the same *shape* both engines: caller-held tx -handle (`*_tx` methods on the handle); pg's instance is async-native -(tokio-postgres Client is Send+Sync — the handle holds the pooled -connection directly, no spawn_blocking), SQLite's is a bridged writer- -slot lease. The core-crate `TxHandle` trait from POC #1's sketch -stands unchanged; the per-engine difference is bridging mechanism, not -trait shape. Delivery-guarantee contract is now measurable, native on -both sides: notify = fire-and-forget (commit-atomic, no replay), -streams = durable with explicit offsets. What remains open on OQ-ST-04 -is the contract-pinning work itself (which parts of the honker-rs -surface become contract, per the inventory rows) — paper work over a -now-complete evidence base, for Phase 1. - -Open; this is the second central research question, coupled to OQ-ST-03 -(the driver determines what LISTEN plumbing exists) — **both engine -sides now de-risked (POC #1 SQLite, POC #2 Postgres); what remains is -the contract-pinning paper work.** +- **The opaque-wake + re-read contract holds on both engines, + unchanged.** SQLite: same `data_version` mechanism under either + posture (wake coalescing verified correct by design, p50 1.4–2.2 ms + at the default 1 ms cadence, missed-wake stress passes with correct + re-reads on both). Postgres: LISTEN delivers push (~1.1 ms p50, + 300/300 isolated, no coalescing needed), no replay, and the + reconnect gap is made recoverable by the listener broadcasting a + synthetic reconnect-wake on a reserved channel (verified through a + killed-connection recovery — subscribers wake and re-read state + completely despite the in-gap notification never being delivered). + The two engines now share the *same* wake contract. +- **notify is commit-atomic natively on both** (in-tx NOTIFY delivers + only at commit; rollback drops it — the exact analogue of honker's + notify-in-tx property). Delivery-guarantee split is measurable and + native: notify = fire-and-forget (commit-atomic, at-most-once per + listener session, no replay); streams = durable with explicit + offsets. The trait must NOT promise replay under `listen()`. +- **The `*_tx` seam resolves to the same shape both engines:** + caller-held tx handle (`*_tx` methods on the handle); pg's instance + is async-native (tokio-postgres Client is Send+Sync — the handle + holds the pooled connection directly, no spawn_blocking), SQLite's + is a bridged writer-slot lease. The core-crate `TxHandle` trait + from POC #1's sketch stands unchanged; the per-engine difference is + bridging mechanism, not trait shape. Phase 1 starts from that shape + plus its two recorded frictions (the `as_any_mut` downcast and the + thread-affinity of rusqlite tx ops — the latter pg-side only). ### OQ-ST-05: Queue semantics — adopt, fork, or re-derive? -If queues land in scope (OQ-ST-01), the pg-boss schema family is the -Postgres-side incumbent and honker's queue design is the SQLite-side -one. Options: adopt pgboss-rs as a dependency (new feature-gated -option); targeted-fork the relevant subsystem (alksocks precedent, -ported to our conventions); schema/design-reference only (re-derive on -our driver). Fork-vs-derive depends on how much of pgboss-rs is -queue-machinery vs driver-wiring (the sqlx coupling — OQ-ST-03), on -our tolerance for the alpha-state rc port, and on the verified gap -(§pgboss-rs): the push-reactivity half has to be built on top of any -choice, so the queue-machinery reuse value is the honest comparison -point, not the whole. +**Status: open — design work (posture resolved; the remainder is +semantics-depth design on measured ground — Phase 1, not further +research).** -Open; inputs: OQ-ST-01's scope vote on queues (inventory: documented -need) + OQ-ST-03 resolution. **POC #1 input (2026-10-04):** on the -SQLite side the adopt question dissolved — honker-core is consumed as a -published-library dependency (the inventory-confirmed feature rows ride -its machinery; OQ-ST-06 holds the fork-vs-reference question). **POC #2 -input (2026-10-04):** on the Postgres side the re-derive posture is -strengthened — the minimal queue table + `FOR UPDATE SKIP LOCKED` claim -+ LISTEN wake is the driver-coupled hard part and it was proven in -~40 lines of SQL over the pool (all claim/atomicity properties pass); -reactivity is built by this crate either way (the verified pgboss-rs -LISTEN/NOTIFY gap stands). The remaining OQ-ST-05 question is the -*semantics depth* (retry/backoff/dead-letter/sweep design on that -ground), with pgboss-rs as schema/design reference. +If queues land in scope (OQ-ST-01: they do, documented need), the +pg-boss schema family is the Postgres-side incumbent and honker's +queue design is the SQLite-side one. Options: adopt pgboss-rs as a +dependency (new feature-gated option); targeted-fork the relevant +subsystem (alksocks precedent, ported to our conventions); +schema/design-reference only (re-derive on our driver). Fork-vs-derive +depends on how much of pgboss-rs is queue-machinery vs driver-wiring +(the sqlx coupling — OQ-ST-03), on our tolerance for the alpha-state +rc port, and on the verified gap (§Prior art → pgboss-rs): the +push-reactivity half has to be built on top of any choice, so the +queue-machinery reuse value is the honest comparison point, not the +whole. + +**Posture evidence (both POCs, 2026-10-04):** + +- SQLite side: the adopt question dissolved — honker-core is consumed + as a published-library dependency; the inventory-confirmed feature + rows ride its machinery (fork-vs-reference for that consumption is + OQ-ST-06's calculus). +- Postgres side: the re-derive posture is strengthened — the minimal + queue table + `FOR UPDATE SKIP LOCKED` claim + LISTEN wake is ~40 + lines of SQL over the pool (all claim/atomicity properties pass in + POC #2's suite); reactivity is built by this crate either way (the + verified pgboss-rs LISTEN/NOTIFY gap stands). The pg engine's + default consumption posture is LISTEN-driven claim with a re-poll + safety net; poll-only remains the fallback (measured: p50 3–6 ms + vs 32–50 ms). + +Remaining question: the *semantics depth* — retry/backoff/dead-letter/ +sweep design on that ground, with pgboss-rs (and the node original) +as schema/design reference. ### OQ-ST-06: Honker relationship — reference, fork, or vendor? -Design-reference only (read, don't copy), targeted fork of -honker-core's engine machinery, or vendor the extension? Honker is +**Status: open — quality-read gate (default posture evidenced; +the remainder is the Phase 1 fork-trigger assessment).** + +Options: design-reference only (read, don't copy); targeted fork of +honker-core's engine machinery; vendor the extension. Honker is alpha-quality per its own README, MIT/Apache-2.0 dual-licensed, and covers only the SQLite side — but it embodies exactly the watcher/ transactional design this crate wants on SQLite, and honker-rs -demonstrates the interface shape is sound. Three refinements from the -2026-10-03 reading: +demonstrates the interface shape is sound. + +Refinements from the 2026-10-03 reading: - honker-rs is **sync-only** (std threads, blocking iterators) — the tokio port is required work under any fork posture, which changes @@ -665,52 +661,53 @@ demonstrates the interface shape is sound. Three refinements from the behavior verbatim — closer to the alkblobs "borrow conclusions, not wire surface" principle than to alksocks' verbatim extraction. -Open; needs the license/provenance check (AGENTS.md §3) and a quality -assessment of honker-core's watcher/transactional core. **POC #1 input -(2026-10-04):** under the resolved SQLite posture (posture 1), honker-core -is consumed as a *published library* (Writer/Readers/SharedUpdateWatcher/ -attach_* — nearly all its surface minus the experimental backends), not -vendored or forked to ship; 0.5.0 is published with clean deps and the -reference-usage posture works as-is. The remaining fork trigger would be -the Phase-1 quality read (the watcher/transactional core assessment) or a -needed change upstream won't take — the calculus is unchanged in kind, -but the *default* posture is now evidenced: depend on the published crate. -**POC #2 input (2026-10-04):** the pg-side dependencies are -published-library use as-is (tokio-postgres 0.7.18 + deadpool-postgres -0.14.2: clean, zero conflicts, actively maintained); `postgres-notify` -0.3.8 evaluated in-probe and passed over (derive-not-adopt — lazy -reconnect, no connect_script on initial connect, unquoted identifier -LISTENs, single-maintainer posture; the hand-rolled ~90-line forwarder -with test-pinned pitfalls is the preferred shape; this is the OQ-ST-06 -calculus applied per-subsystem, recorded, not a Phase 0 ADR). +**Default posture, evidenced by POC #1 (2026-10-04):** depend on the +published crate. Under the resolved SQLite posture, honker-core is +consumed as a *published library* (Writer/Readers/SharedUpdateWatcher/ +attach_* — nearly all its surface minus the experimental backends), +not vendored or forked to ship; 0.5.0 is published with clean deps and +the reference-usage posture works as-is. The fork trigger is now +specifically: the Phase-1 quality read of honker-core's +watcher/transactional core, or a needed change upstream won't take. + +**Per-subsystem dependency votes (POC #2, 2026-10-04):** the pg-side +dependencies are published-library use as-is (tokio-postgres 0.7.18 + +deadpool-postgres 0.14.2: clean, zero conflicts, actively maintained); +`postgres-notify` 0.3.8 evaluated in-probe and passed over +(derive-not-adopt — lazy reconnect, no connect_script on initial +connect, unquoted identifier LISTENs, single-maintainer posture; the +hand-rolled ~90-line forwarder with test-pinned pitfalls is the +preferred shape; fallback if upstream improves). This is the OQ-ST-06 +calculus applied per-subsystem, recorded, not a Phase 0 ADR. ### OQ-ST-07: SQLite-side scope — loadable extension, embedded rusqlite, or both? +**Status: resolved (cut-only, 2026-10-04 via the inventory + POC #1).** Honker ships as a loadable extension usable by *any* SQLite client, -plus per-language bindings. This crate (a Rust library) may not need -the loadable-extension surface at all — embedding the engine machinery -in-process may be the whole story (the honker-core shape minus the -extension/binding packaging). Determines how much of honker is even -candidate material. - -**Sharpened by the inventory (2026-10-04):** every identified consumer -is in-process Rust attaching to its own connection (the +plus per-language bindings. This crate (a Rust library) does not need +the loadable-extension surface: every identified consumer is in-process +Rust attaching to its own connection (the `honker-core`/`attach_honker_functions` shape — the alknet-filesystem -POC's actual usage). No consumer needs the loadable-extension surface. -The question is now cut-only: loadable extension is out unless a -consumer appears; the open residue is just how much of honker-core's -machinery survives the extraction. +POC's actual usage), and POC #1 sealed the engine posture as library +linkage on our own rusqlite. The loadable-extension option is out +unless a consumer appears; nothing remains to extract or decide here. ### OQ-ST-08: Multi-host / deployment posture +**Status: open.** + Honker is explicitly single-machine (file-backed SQLite). Postgres is -natively multi-host. The unified surface must not pretend SQLite is +natively multi-host — POC #2 verified the pg engine side has no +single-host assumption to remove (the property tests ran +all-through-network over the docker bridge; the listener/wake +machinery is connection-based, per-process). The open question is the +*trait-surface* half: the unified surface must not pretend SQLite is multi-host, but where does the honest boundary live — per-engine capability flags? A documented deployment matrix? Does the trait need to expose engine capabilities at all? -Open; partially rides OQ-ST-04 (the trait's shape constrains where -capability differences can surface). +Open; rides OQ-ST-04 (the trait's shape constrains where capability +differences can surface). ## POC register @@ -722,90 +719,80 @@ land in `docs/research/` here. Named per the OQ each feeds: | 1 | SQLite engine posture: honker-core-on-rusqlite vs honker-extension-over-sqlx (async seam, watcher, transactional contract, packaging, interop) | [poc-sqlite-posture-spec.md](poc-sqlite-posture-spec.md) | [poc-sqlite-posture-findings.md](poc-sqlite-posture-findings.md) — **passed** (verdict: Arm A; ran 2026-10-04) | | 2 | Postgres engine posture: LISTEN/NOTIFY plumbing, tx-seam over the pool (caller-owned tx vs closure-scoped), wake-vs-poll claim latency, reconnect recovery | [poc-pg-posture-spec.md](poc-pg-posture-spec.md) | [poc-pg-posture-findings.md](poc-pg-posture-findings.md) — **passed** (verdict: tokio-postgres+deadpool, hand-rolled listener, caller-tx seam; ran 2026-10-04) | -## Phase 0 plan +## Phase 0 plan — final state -Iteration expected; this register grows as research rounds land. -Expected sequence (deliberately rough): +The expected sequence, with what actually happened: -1. ~~Consumer-driven scope inventory (OQ-ST-01)~~ — **done - (2026-10-04)**: `consumer-inventory.md`, run against the paused +1. **Consumer-driven scope inventory (OQ-ST-01)** — done + (2026-10-04): `consumer-inventory.md`, run against the paused consumers' documents (alkfs phase-0, alkgit architecture, alkblobs architecture + the alknet-filesystem POC). OQ-ST-01 answered down to named per-feature rows; OQ-ST-02/07 sharpened by it. -1. ~~Crate scope (OQ-ST-02)~~ — **resolved (2026-10-04, operator - decision)**: reactive-core + engine crates. Reasoning and the +2. **Crate scope (OQ-ST-02)** — resolved (2026-10-04, operator + decision): reactive-core + engine crates. Reasoning and the superseded inventory lean recorded at the OQ and in the inventory. -2. Research rounds on OQ-ST-03/04 (drivers + reactive shape): - - ~~SQLite driver posture~~ — **POC #1 passed (2026-10-04)**: - posture 1 (honker-core on our rusqlite); findings + constraint - notes in the register row and OQ-ST-03. - - ~~Postgres side of OQ-ST-03 (tokio-postgres LISTEN/pool/tx-seam - validation)~~ — **POC #2 passed (2026-10-04)**: tokio-postgres + - deadpool posture validated end-to-end; OQ-ST-03 **closed** with - per-engine drivers; findings in the register row and OQ-ST-03/04. - - OQ-ST-04's contract pinning — the honker-rs surface (§Interface - finding) is the concrete starting artifact: pinning its contract - costs less and is more honest than inventing a parallel shape — - now scoped against the inventory's confirmed features rather than - the full honker menu, shaped as the core-crate trait surface per - OQ-ST-02's split, with the tx-seam shape **resolved by both POCs** - (caller-held tx handle; SQLite bridges via writer-slot lease + - spawn_blocking, Postgres holds the pooled connection directly - — same shape, different bridging) — **paper work remains, over a - complete evidence base.** -3. Ownership decisions (OQ-ST-05/06) — adopt/fork/derive per subsystem, - ~~after the driver and shape questions narrow the option space~~ — - the option space is narrow now (both POCs ran; per-subsystem votes - recorded at OQ-ST-05/06): the remaining work is the *semantics-depth* - design inputs (OQ-ST-05: retry/dead-letter/sweep on the pg ground - POC #2 proved) and the Phase-1 quality read (OQ-ST-06's fork trigger - assessment). -4. Converge; Phase 1 opens with the ADR backlog this register becomes. +3. **Driver + reactive-shape research rounds (OQ-ST-03/04)** — + resolved/de-risked by the two POCs: + - POC #1 (SQLite driver posture) passed — posture 1 (honker-core + on our rusqlite). + - POC #2 (Postgres side: tokio-postgres LISTEN/pool/tx-seam + validation) passed — OQ-ST-03 closed with per-engine drivers. + - OQ-ST-04's remaining contract pinning is Phase 1 paper work + over a complete evidence base (§OQ-ST-04). +4. **Ownership decisions (OQ-ST-05/06)** — the option space narrowed + with the POCs; per-subsystem votes recorded at the OQs. Remaining: + the semantics-depth design inputs (OQ-ST-05) and the Phase-1 + quality read (OQ-ST-06's fork trigger). +5. **Converge** — done (§Convergence). Phase 1 opens with the ADR + backlog this register becomes. ## References - honker — `/workspace/honker` (git checkout @ f4e53c6 of - github.com/russellromney/honker; README + - `honker-core/src/` read 2026-10-03): the SQLite-side - feature/wake template. The four - guides (queues/streams/pubsub/scheduler on honker.dev) + - `packages/honker-rs/src/lib.rs` (v0.5.0) are the interface prior art - (§Interface finding). + github.com/russellromney/honker; README + `honker-core/src/` read + 2026-10-03): the SQLite-side feature/wake template. The four guides + (queues/streams/pubsub/scheduler on honker.dev) + + `packages/honker-rs/src/lib.rs` (v0.5.0) are the interface prior + art (§Interface finding). - pgboss-rs — `/workspace/pgboss-rs` (git checkout @ 98f7d9e of - github.com/rustworthy/pgboss-rs, v0.1.0-rc6; read 2026-10-03, - LISTEN/NOTIFY-absence verified): the Postgres queue-family reference. -- honker's own prior-art section: pg_notify, pg-boss, Oban, Huey — the - external lineage this crate inherits from both sides. + github.com/rustworthy/pgboss-rs, v0.1.0-rc6; read 2026-10-03, + LISTEN/NOTIFY-absence verified): the Postgres queue-family + reference. +- honker's own prior-art section: pg_notify, pg-boss, Oban, Huey — + the external lineage this crate inherits from both sides. - alkblobs — `/workspace/@alkdev/alkblobs` (spec+POCs, paused): the paused planning this crate unblocks; its POC findings (`poc-postgres-kv-findings.md`, `poc-pglo-findings.md`) are the - tokio-postgres evidence base. + tokio-postgres evidence base; its store-api.md pins the + spawn_blocking engine-execution posture (family precedent with + alktty REQ-TTY-01). - alkgit — `/workspace/@alkdev/alkgit` (paused mid-Phase-1, architecture reviewed): its backend.md trait seam and ADR set are consumer evidence for the inventory (queues/locks rows). -- alkfs — `/workspace/@alkdev/alkfs` (Phase 0 drafted, 2026-09-23): its - phase-0 OQs (OQ-FS-05/07/14/16/17) are consumer evidence for the - inventory (notify/locks/queues rows). +- alkfs — `/workspace/@alkdev/alkfs` (Phase 0 drafted, 2026-09-23): + its phase-0 OQs (OQ-FS-05/07/14/16/17) are consumer evidence for + the inventory (notify/locks/queues rows). - alknet-filesystem POC — `/workspace/@alkdev/alknet/docs/research/alknet-filesystem/ poc-summary.md`: the ran-once evidence that the honker-coordination layer works (notify-on-commit test; named-locks and outbox usage identified). - consumer-inventory.md (`docs/research/consumer-inventory.md`) — the - per-feature synthesis (2026-10-04) answering OQ-ST-01 from the above. + per-feature synthesis (2026-10-04) answering OQ-ST-01 from the + above. - alkcall — `/workspace/@alkdev/alkcall`: the family substrate; - referenced for the store-layer-isolation principle only. + referenced for the store-layer-isolation principle only. - alkstore-sqlite-posture-poc — `/workspace/alkstore-sqlite-posture-poc` - (standalone POC crate, published-deps-only): POC #1's code — - both arms end-to-end, property tests, seam/watcher probes. + (standalone POC crate, published-deps-only): POC #1's code — both + arms end-to-end, property tests, seam/watcher probes. - alkstore-pg-posture-poc — `/workspace/alkstore-pg-posture-poc` - (standalone POC crate, published-deps-only): POC #2's code — - the pg engine posture end-to-end (engine + hand-rolled listener + - postgres-notify wrapper), 11-test contract suite, seam/wake/ - burst/claim/pollvlisten/pnlisten probes; harness server - `pglo-poc` (postgres:16-alpine, :15432). -- alktty — `/workspace/@alkdev/alktty` (architecture reviewed): REQ-TTY-01 - (`docs/architecture/tty-backend.md`) — the async-facing-trait + - sync-bridge posture ("backends are not required to be natively - async"), the family precedent bearing on OQ-ST-03's async - sub-question. \ No newline at end of file + (standalone POC crate, published-deps-only): POC #2's code — the + pg engine posture end-to-end (engine + hand-rolled listener + + postgres-notify wrapper), 11-test contract suite, seam/wake/ + burst/claim/pollvlisten/pnlisten probes; harness server `pglo-poc` + (postgres:16-alpine, :15432). +- alktty — `/workspace/@alkdev/alktty` (architecture reviewed): + REQ-TTY-01 (`docs/architecture/tty-backend.md`) — the + async-facing-trait + sync-bridge posture ("backends are not + required to be natively async"), the family precedent bearing on + OQ-ST-03's async sub-question. \ No newline at end of file