Row-first gate: consumer-inventory.md gains an engine-tier section — the mem-engine need recorded operator-authority, dated 2026-10-10 (wasm32 sandboxing across the alk protocol family + downstream ffi/napi/python-adapter economics; rusqlite/tokio-postgres structurally out on wasm, so mem is the only triad member the sandbox can carry). ADR-024 (decisions/024-mem-engine.md) pins the decision set: (1) separate `alkstore-mem` crate — discharges and amends ADR-001 §4 (superseded in part: full contract-v1 engine, not an "implementation convenience"; annotation on ADR-001 §4 + Status, class-1 window still open); (2) honest-ephemeral posture — no durability, single-process, ephemeral by design, never fleet-valid (ADR-010 §3's shared-pool predicate fails structurally), riding ADR-016's carriers unchanged (identity + docs + matrix); (3) full contract v1 tier with the asymmetry classification — `PayloadTooLarge` never produced (SQLite arm), no reconnection concept (reconnect-wake/watcher rows absent from mem's per-engine column), receiver close at engine drop, wake coalescing N/A; (4) `MemStore::new()` engine-native constructor — the one documented exception to the open-prose, core-contract.md Store concept amended; shared-instance (two opens → one engine) rejected with the ADR-016 §4-style re-entry gate (consumer-inventory row), register stays closed; (5) wasm32 — compile-clean for wasm32-unknown-unknown plus the suite's mem column green on host under a current-thread runtime flavor are wave-6 acceptance items; on-target suite execution scoped out with a named collapse condition (wasm-bindgen-test adapter crate) — a scope decision, not a pending-client hedge; (6) mem is not a core dev-dependency — core's doc examples stay mock-based, ADR-001 §4's doctest posture declines. engine-mem.md (draft): the in-process mapping — guarded per-mechanism state (no pool, no schema bootstrap, no forwarder, no spawn_blocking seam), the tx overlay (staged *_tx effects merged atomically at commit; read-your-own-writes easy, drop-=-rollback trivial; wake-at-commit structural — the seam pg needed pg-fix-tx-wake for, mem pins from birth via the existing tx-commit-atomicity rows), ADR-012 §2 third owner of the curve/opts/@every arithmetic (three-way equivalence, ADR-022's StoreFactory reused verbatim — born pinned). deployment.md: host-semantics row (single-process, ephemeral, never fleet-valid, wasm32-clean), mem connection-budget subsection (none — nothing to budget), durability-knobs row (the honest row is the absence), toolchain row (wasm32 subset + current-thread-clean, acceptance-cited). README/overview: engine-mem.md row, ADR-024 row, family table entry (alkstore-mem, no driver deps), current-state updated to the implementation phase with engine specs' stable statuses reflected. implementation.md: wave table gains wave 6 (mem engine, depends waves 1 + 5) and renumbers release readiness to wave 7 (fuzzing decision — the one remaining decided-at-implementation deferral — rides wave 7); wave-6 section records the born-pinned posture, acceptance items, the one suite-harness pre-task (past_stamp_sleep blocking sleep → async sleep, review 003's properties.rs:1683 note), and the window riders (review 003 Finding 1: the pg transient-fault seam, MEDIUM, rides wave 6; Findings 2–3 follow their park into wave 7's pre-release); wave-6 review gate defined; Decided points updated (mem discharged, fuzzing renumbered); stale wave-6 references swept across review docs and the landed tasks' notes (release-readiness items renumbered or annotated). Docs-only change; verified by cross-reference audit (all ADR/OQ/anchor references resolve, residual wave-6 mentions are correct under the new numbering or carry dated annotations).
9.4 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||
|---|---|---|---|---|---|---|---|---|---|---|
| pg-engine-schema | Postgres engine — schema bootstrap (engine-owned schema, table family) | completed | narrow | low | component | implementation |
|
Description
Stand up the pg engine's data model: the engine-owned PostgreSQL schema
and its table family — the dependency root every later wave-4 task
rides. Per ADR-010 §8 (one engine-owned schema, default alkstore,
queues as rows — no per-queue tables/schemas) and engine-postgres.md's
mapping table.
A schema.rs module in alkstore-postgres:
- Schema name is engine configuration (default
alkstore) — it ridesPgOpts(the open task wires it; this task's bootstrap takes the schema name as an argument). - Bootstrap is idempotent DDL, runnable at open on any connection:
CREATE SCHEMA IF NOT EXISTS+CREATE TABLE IF NOT EXISTS+ indexes. All engine-owned objects live in the one schema; consumer tables co-tenant the instance untouched (deployment.md's co-tenancy posture). - Table family (column sets follow the contract's value shapes —
the
#[doc(hidden)]constructors' full field lists inalkstore/src/{job,stream,schedule}.rsare the field lists of record; ADR-019 §1/§3, ADR-015 §3, ADR-009 §3, ADR-021 §2):- job table (live rows — pending and processing): queue name,
payload, enqueue/ready-time resolution columns (
run_at,expires_at),priority, the stampedQueueOptscolumns (visibility_timeout_s,max_attempts,backoff_base_s,dead_letter_retention_s— ADR-010 §3a),attempts,worker_id,claimed_at,claim_expires_at,last_error. Claim-ordering index per ADR-010's ordering row (priority DESC, run_at ASC, enqueue order). - dead table: the job row's diagnosis fields +
died_at,last_error; retention index for the sweep's retention enforcement. - stream events table:
id BIGSERIAL(the offset — engine- assigned, monotone, immutable, never renumbered), stream name, nullablekeycolumn (carried metadata, ADR-015 §1), payload,created_at(unix-seconds, informational). The(stream, id)index serving theoffset ASCread paths. - offsets table: (stream, consumer) → offset, the monotone checkpoint upsert target.
- schedule table (ADR-009): name (unique),
@everyspec, target queue, payload, theScheduleOptsstamps, and the boundary-state columns the tick math advances (the substrate's__alkstore_scheduler_tasksshape is the reference —reference-honker-machinery.md). - locks table: name (unique), owner, expiry timestamp — the TTL lock machinery's storage (the substrate's lock-table semantics, re-derived; the locks task builds ops on it).
- job table (live rows — pending and processing): queue name,
payload, enqueue/ready-time resolution columns (
- Payload columns store the exact encoded bytes (
bytea) — notJSONB: JSONB normalization (key reordering, duplicate folding) would break the contract suite's byte-identical-rows row (ADR-020 §4). The engine stores exactly whatencode_payloadreturned. - No notifications table — pg notify is native (
pg_notify); the SQLite notify-table carriage has no pg counterpart. The 8000-byte check is client-side (the seam task), not storage-shaped. - No migration machinery in v1 — tables are created fresh per schema; append-column migrations are the substrate's evolution discipline, not a greenfield v1 need (note the posture in the module docs; wave 7's release pass revisits if the deployment story demands in-place upgrades).
Tests use a raw tokio_postgres::Connection/Client against the
harness server (no engine code yet): bootstrap runs twice cleanly
(idempotence), every table exists with its pinned columns, the
bigserial offset behaves (monotone, gap-on-delete), the claim-ordering
index exists.
Acceptance Criteria
schema.rswith an idempotentbootstrap(conn, schema_name)creating the full table family + indexes in the one schema- Column sets match the contract's value shapes (the constructors'
field lists; stamp columns per ADR-010 §3a;
claimed_atper ADR-021 §2) - Payload columns are
bytea(exact-bytes posture, JSONB rejected — documented in the module docs with the reason) - Schema name is a parameter (default
alkstoredecided at thePgOptslayer, next task) - Indexes: idempotence + table-shape tests green against the harness server
cargo test -p alkstore-postgres, clippy-D warnings, fmt clean
References
- docs/architecture/engine-postgres.md (Identity and posture; mapping table)
- docs/architecture/decisions/010-queue-semantics-depth.md §3a, §8
- docs/architecture/decisions/015-streams-depth.md §1, §3
- docs/architecture/decisions/009-scheduler-collapse.md §3
- docs/architecture/deployment.md (co-tenancy)
- docs/research/reference-honker-machinery.md (schema reference read)
- alkstore/src/job.rs, stream.rs, schedule.rs (the field lists of record)
Notes
Decisions of record made while implementing (the description didn't pin them):
- Table names:
job,dead,stream_events,stream_offsets,schedule,locks— fixed constants exported asalkstore_postgres::tablesfor the later mechanism tasks' SQL. Table names do not ride configuration; only the schema name does. - Index-name qualification: Postgres rejects schema-qualified
index names at
CREATE INDEX, butIF NOT EXISTSon a bare name checks existence across all schemas — a co-tenant's unrelatedpublic.job_claim_idxwould silently skip our index creation. So every engine index is named{schema}_{table}_{suffix}(job_claim_idx,job_pending_idx,job_deadline_idx,dead_retention_idx,events_read_idx,schedule_fire_idx,locks_expiry_idx, exported asindexes::) — schema-prefixed and quoted, tested against a co-tenant carrying the bare name. Sub-HOT-path indexes (job_pending,job_deadline, the substrate's trio) carried over from the reference shapes — the claim/heartbeat paths the queue task re-derives ride them. - Reserved-word columns: the offsets checkpoint column is
"offset"and the events key column"key"— both quoted here and flagged in the module docs so every later task's SQL keeps the quotes. The offsets column carries a non-negative CHECK (cursors start at 0); monotonicity itself stays in the streams task's GREATEST-guarded upsert. - Identifier quoting:
quote_identifier(doubles embedded") is the engine's injection boundary for the consumer-supplied schema name, exported for later tasks; tested hostile (SQL-breakout schema name stays inert single-schema DDL). - Schedule columns anchor to the substrate's
__alkstore_scheduler_tasksconcrete-stamp shape,enableddropped (no pause/resume in the collapse surface):namePK,spec,queue,payload,priority,expires_s,next_fire_at(the tick's boundary column),max_attempts(default 3),visibility_timeout_s(default 300),backoff_base_s(default 5),dead_letter_retention_s. Nolast_fire_at(next_fire_atis the only state the tick math needs). - Dead table mirrors the job table's live columns + the diagnosis
fields (
last_error,died_at NOT NULL),ida plain BIGINT PK (carried from the move, not a sequence); retention index(queue, died_at). - Job table = the
Job::from_rowfield list of record verbatim + the five stamp columns;state TEXT DEFAULT 'pending'(SQLite parity — the claim predicates read the text values); three indexes (claim-ordering partialWHERE state IN ('pending','processing')per ADR-010 §1's ordering row, pending(queue, run_at), processing deadline(queue, claim_expires_at)). thiserroradded to the crate (v2, same as core/SQLite) forBootstrapError; theopentask maps it ontoError::Databasenext task.- Harness convention (ride env, never hardcoded, skip-clean
server-less):
ALKSTORE_PG_HOST/PORT/USER/PASSWORD/DB— host 127.0.0.1, port 15432, user/passwordpostgres/poc, dbblobs(thepglo-poccontainer's). The next tasks' tests should reuse these variable names.bootstrapitself takes any&tokio_postgres::Client(NoTls in tests; TLS is theopentask's concern via deadpool config, not this module's).
Summary
Landed: alkstore-postgres/src/schema.rs — the engine-owned schema
module (bootstrap(conn, schema_name) idempotent DDL over
batch_execute; DEFAULT_SCHEMA = "alkstore"; quote_identifier;
tables/indexes name constants; BootstrapError), wired into
lib.rs re-exports, plus tests/schema_tests.rs (9 tests) driven
against the harness server via env-carried DSN, skipping cleanly
server-less. Table family per the acceptance criteria (job/dead/
stream_events/stream_offsets/schedule/locks, bytea payloads, bigserial
offsets, claim-ordering partial index with the pinned ordering row).
Verified: 9/9 green against the harness server (idempotence ×3 runs,
per-table column sets, bytea types, bigserial monotone + gap-on-delete,
claim index def fragments incl. priority DESC + partiality, schema
name as parameter incl. reserved-word name select, co-tenancy
re-bootstrap, co-tenant bare-index-name collision exclusion, hostile
schema-name quoting); workspace cargo test green server-less (236
tests, pg tests skipped per convention); clippy -D warnings and fmt
clean workspace-wide.