Wave 4 decomposed: Postgres engine — 11 tasks (schema, open/opts, seam, forwarder, mechanisms, scheduler/outbox, integration, review gate); plan synced

This commit is contained in:
glm-5.3-flash committed 2026-10-08 22:49:00 +00:00
1 parent ecb8211694
commit 4caea21098
11 files changed
+1287 -3

No files matched your search

+87 -3
View File
@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-10-08 (wave 3 implemented + reviewed; review-gate fixes: substrate boundary move, writer-slot error-arm release; wave 4/5 decomposition may proceed)
last_updated: 2026-10-08 (wave 4 decomposed — 11 tasks; waves 1–3 implemented + reviewed)
---
# alkstore — Implementation plan
@@ -35,7 +35,7 @@ parallel).
| [1](#wave-1--foundations) | Workspace scaffold; core crate (errors, value types, full trait surface); contract-suite scaffold | — | implemented + reviewed (2026-10-08) |
| [2](#wave-2--sqlite-substrate-fork) | honker-core fork into `alkstore-sqlite/src/substrate/`: port, deltas, provenance, test floor | wave 1 (workspace scaffold only) | implemented + reviewed (2026-10-08) |
| [3](#wave-3--sqlite-engine) | SQLite engine: connection architecture, re-derived queue ops on contract v1, scheduler/outbox, tx seam, SQLite backlog column | waves 1 + 2 | implemented + reviewed (2026-10-08) |
| 4 | Postgres engine: schema bootstrap, pool/open, listener/forwarder, all mechanisms, tx seam, pg backlog column | wave 1 | not yet decomposed |
| 4 | [Postgres engine](#wave-4--postgres-engine): schema bootstrap, pool/open, listener/forwarder, all mechanisms, tx seam, pg backlog column | wave 1 | decomposed (11 tasks) |
| 5 | Contract suite: the cross-engine equivalence properties (core-contract.md §Verification backlog), version-stamped per ADR-017 | waves 3 + 4 | not yet decomposed |
| 6 | Release readiness: crate docs, deployment matrix final pass, README (written last, honestly), publish prep; mem-engine and fuzzing decisions | wave 5 | not yet decomposed |
@@ -101,6 +101,86 @@ shaped this wave's task set:
panic probe → wave 5's contract-suite rows; N-1's growth-posture
review → wave 6.
## Wave 4 — Postgres engine
The second engine: the core contract implemented on tokio-postgres +
deadpool-postgres per engine-postgres.md — schema bootstrap (one
engine-owned schema, default `alkstore`, ADR-010 §8), the pool/open
constructor, the hand-rolled LISTEN forwarder (dedicated non-pooled
listener connection, bounded broadcast fanout, exponential-backoff
reconnect with re-LISTEN, the synthetic reconnect-wake on
`__alkstore_listener_reconnected__`), the full `Store`/`TxHandle`/
mechanism-handle trait impls (natively async — no `spawn_blocking`
seam; the tx handle holds the pooled object directly, ADR-007's pg
arm), the re-derived queue machinery (`FOR UPDATE SKIP LOCKED` claims,
LISTEN-driven wake with re-poll safety net), the scheduler leader loop
and outbox helper, and the engine's backlog column in the contract
suite (factory-parameterized; wave 5 runs the same rows against both
engines).
The wave is deliberately shaped as wave 3's structural twin — same
task rhythm (constructor → seam → mechanisms in parallel →
scheduler/outbox → integration → review gate) — because the two
engines implement the same contract and the wave-3 task set's
boundaries proved clean. The deltas are the engine's own: no
substrate (the pg machinery is re-derived greenfield per ADR-004/005,
with the pg-boss schema family as design reference), a natively-async
seam (Send+Sync client, POC #2 compile-probe), the forwarder as the
wake substrate (with its two POC-pinned deadlock pitfalls owned as
test-pinned failure modes), and the `PayloadTooLarge` client-side
check (this engine *produces* the variant — the one runtime carriage
of an engine asymmetry, ADR-016 §5).
Wave-3 outcomes absorbed into the task set:
- **The `#[doc(hidden)]` constructors** (`Job::from_row`,
`StreamEvent::from_row`, `Schedule::new`, `Wake::new`) already
exist — wave 3's pre-work task built them engine-agnostically; wave
4 consumes them as-is (no wave-4 equivalent needed).
- **The engine-side arithmetic is re-owned, not shared**: the
equal-jitter backoff curve, the opts-resolution rules
(delay-over-`run_at`, relative-`expires`, the derived stamp sets
300/3/5/none and 60/5/5), and the outbox backing-queue derivation
are each engine's own implementation (ADR-012 §2's one-owner rule
applied per engine); the pg tasks re-implement them from the ADR
text, and the contract suite (wave 5) pins the two engines'
arithmetic to identical outputs. The SQLite engine's `resolution.rs`
is reference, not a dependency.
- **The `@every` spec grammar has no shared parser on this side** —
the SQLite engine reuses the substrate's `parse_every_interval`
(one-line delta D-31's neighbor); the pg engine owns its parser
(the grammar is small and pinned: `@every <n><unit>`, `s|m|h|d`).
Equivalence of *behavior* (what fires, when) is suite-pinned; the
parsers themselves are per-engine.
- **The wave-3 review's deferred notes** (reader-pool close path,
commit-error-arm coverage) stay SQLite-scoped — the pg engine's
pool is deadpool-managed (no hand-rolled close path to review) and
its commit-error arm gets coverage in the pg tasks' own tests.
- **The suite's factory contract** (isolation + idempotent teardown)
is what the pg factory implements — a fresh schema per `open` per
the SQLite factory's precedent (fresh temp file there); the
POC's shared-server parallel-interference caveat (findings,
invocation note) is answered by exactly that isolation, not by
sequential-only harnesses.
Test posture: the engine's tests need a live Postgres server. The
harness convention is the POC's (dockerized `postgres:16-alpine` on
:15432, the `pglo-poc` container — still running in the dev
environment); connection settings ride environment/config, never
hardcoded. Tests that cannot reach a server skip cleanly (the
`SKIP`-posture), so the workspace gates stay green server-less — but
the wave's acceptance requires the suite green *against* the server.
One design point the architecture leaves to implementation (pinned in
the wave-4 tasks, flagged here for visibility): **the queue/stream
wake-channel naming**. The contract pins LISTEN as the pg wake
trigger and pins the channels/queues shared namespace, but no doc
names the channel an enqueue/publish notifies on. The tasks pin the
natural realization — the mechanism name *is* the wake channel
(`listen(queue_name)` receives that queue's enqueue wakes; same for
streams), legal by construction under the shared namespace — with
the reserved reconnect-wake channel staying the only reserved string.
## Decided points
- **Contract-suite layout — option (a)**: a small internal
@@ -159,4 +239,8 @@ decomposes. Specific gates:
`open_writer_connection` boundary move out of `substrate/mod.rs`;
the writer-slot error-arm stranding in
`with_writer`/`begin`/`commit`); two minor notes deferred to
wave 5/6 (reader-pool close path, commit-error-arm coverage).
wave 5/6 (reader-pool close path, commit-error-arm coverage).
- **Wave 4 decomposition** (2026-10-08) — shaped as wave 3's
structural twin; wave-3 outcomes absorbed (constructors exist;
arithmetic re-owned per engine; per-engine `@every` parser;
SQLite-scoped deferred notes stay put). See the Wave 4 section.