release-deployment-matrix: deployment matrix finalized as release text — every row verified as implemented (corrections: consumer-side FULL durability option was false — synchronous hardwired NORMAL at bootstrap, no surface; both-engines -> all-three queue stamps; max_size one-guard understated -> three guards listed; NFS honesty re-cited to ADR-016; unsourced mem sub-millisecond latency -> structural statement + honest no-POC latency bullet), env-boundary grep re-run clean (harness-only) and pinned in Configuration boundary, N-1 growth acceptance folded (positioning pointer removed, D-16 attributed, POC #1/#2/pgdiag/wave-6/alkblobs-path pointers removed), status draft -> stable; task -> completed with Notes + Summary

This commit is contained in:
glm-5.3-flash committed 2026-10-11 01:24:14 +00:00
1 parent 0344d101d1
commit f5e0e8e199
2 files changed
+135 -31

No files matched your search

+47 -22
View File
@@ -1,7 +1,10 @@
---
status: draft
last_updated: "2026-10-11 (the N-1 growth-posture acceptance — per-engine
record of the three unconstrained-growth paths, consciously accepted)"
status: stable
last_updated: "2026-10-11 (the deployment-matrix release final pass —
every row verified true as implemented across the three engines, the
env-variable boundary pinned, the growth-posture acceptance folded
into the release text, the fixed `synchronous = NORMAL` posture
corrected, internal task/wave pointers removed)"
---
# Deployment
@@ -19,8 +22,8 @@ document holds the facts.
| Engine | Host posture | Notes |
|---|---|---|
| SQLite | **single-machine**, file-backed | NFS two-writers unsupported (honker's honesty posture, inherited, [ADR-003](decisions/003-sqlite-driver.md)). Cross-process *on one host* is verified POC ground (`data_version` is cross-process by nature). |
| Postgres | **multi-host native** | Nothing assumes a shared host; POC #2 ran all-through-network (docker bridge) with the same properties ([ADR-004](decisions/004-postgres-driver.md)). |
| SQLite | **single-machine**, file-backed | NFS two-writers unsupported — a `.db` on network storage is not a supportable deployment posture, inherited from the upstream fork ([ADR-016](decisions/016-deployment-honesty.md), [ADR-003](decisions/003-sqlite-driver.md)). Cross-process *on one host* is verified territory (`data_version` is cross-process by nature). |
| Postgres | **multi-host native** | Nothing assumes a shared host; the engine ran fully across a network bridge (docker) with the same properties, measured ([ADR-004](decisions/004-postgres-driver.md)). |
| Mem | **single-process, ephemeral** ([ADR-024](decisions/024-mem-engine.md) §2) | In-memory, per-instance; no cross-process story at all — two engine instances share nothing by design. No durability, by design: loss on process exit is the documented posture, and the dependency on `alkstore-mem` *is* the ephemerality declaration (honest for workloads that accept it). Never fleet-valid — the shared-pool predicate ([ADR-010](decisions/010-queue-semantics-depth.md) §3) fails structurally; there is no pool. Compile-clean on `wasm32-unknown-unknown` ([ADR-024](decisions/024-mem-engine.md) §5) — the family's member that runs in the sandbox. |
The unified trait must not pretend SQLite is multi-host — and it does
@@ -73,10 +76,9 @@ Options for OQ-08, with their outcome
|---|---|---|
| Pool | `max_size` per process | claims/queries via deadpool |
| Listener | **+1** per LISTEN-ing process | non-pooled, dedicated; pooled connections cannot carry LISTEN (deadpool#360, test-pinned — [ADR-004](decisions/004-postgres-driver.md)) |
| — | — | Sizing rule: `max_size + 1` per process; verify end-to-end accounting (POC #2's pgdiag-st-4: pool 6 + 1 listener + probe conns = exact server-side count) |
| — | — | Sizing rule: `max_size + 1` per process; verify end-to-end accounting (`pg_stat_activity` shows the exact server-side count) |
Shared-server co-tenancy (the alkblobs ADR-008 precedent —
`/workspace/@alkdev/alkblobs/docs/architecture/decisions/` — consumer
Shared-server co-tenancy (the family's queue precedent — consumer
tables co-tenant the pg instance) is supported and expected — the
[naming / reserved namespace contract](core-contract.md#naming--reserved-namespace)
protects reserved names; queue/stream/lock/schedule tables live in one
@@ -120,7 +122,7 @@ deliberately leaves to the caller's constants; the
[ADR-023](decisions/023-fourth-review-round.md) §2 domain table
covers the trait-surface arguments, not these. This applies to the
queue stamps (`QueueOpts`: `visibility_timeout_s`,
`dead_letter_retention_s`, `max_attempts`) on both engines:
`dead_letter_retention_s`, `max_attempts`) on all three engines:
- Negative or zero `visibility_timeout_s` stamps claims whose deadline
is already past — every claim is instantly reclaimable (the
@@ -138,22 +140,42 @@ queue stamps (`QueueOpts`: `visibility_timeout_s`,
shape) — repair the opts before enqueueing rather than repairing
rows afterwards.
The same trusted-as-given shape applies to `PgOpts::max_size`'s
*positive-integer* contract — 0 is typed-failed at open, the one knob
with an engine-side guard; everything above has none. The
consumer-obligation shape here is the visibility-budgeting precedent
The same trusted-as-given shape applies to the engine opts' other
numeric fields — with the exceptions the code actually carries:
`PgOpts::max_size`'s *positive-integer* contract (`0` is typed-failed
at open, no round trip) and `SqliteOpts::poll_interval`'s
non-zero contract (`0` rejected at open); `SqliteOpts::max_readers`
clamps `0` up to one reader. The queue stamps above are the
unvalidated surface. The consumer-obligation shape here is the
visibility-budgeting precedent
([ADR-010](decisions/010-queue-semantics-depth.md) §2): the constant is
the consumer's, the behavior of a mis-set one is documented, nothing
runtime is fabricated.
## Configuration boundary
Env reads are harness-only; production configuration is the
opts-constructor surface. No production path in any of the three
engine crates or the core consults the process environment — grep
re-verified across the workspace at release: every `env::var` read
lives in a test target (the pg test modules' DSN plumbing; test temp
files use `std::env::temp_dir()`, not configuration). Connection
settings, credentials, and knobs arrive exclusively through `open`'s
arguments / option structs (`PgOpts`, `SqliteOpts`, the mem
constructor), handed in by the consumer's own config layer — so no
credential (or any setting) can arrive implicitly. If a future
consumer-facing surface ever wants env conveniences, the family
pattern is caller-side: the app's config layer reads env and passes
the values into the opts.
## Durability knobs
| Engine | Knob | Shape |
|---|---|---|
| SQLite | `synchronous` | WAL + `NORMAL` shipped ([ADR-003](decisions/003-sqlite-driver.md)); FULL is available consumer-side for stricter durability; commit fsyncs land at WAL checkpoints (the ~1000-commit spike cadence, POC #1) |
| SQLite | `synchronous` | not a knob — the engine hardwires `synchronous = NORMAL` at connection bootstrap (WAL shipped, [ADR-003](decisions/003-sqlite-driver.md)); commit fsyncs land at WAL checkpoints (POC-measured: checkpoint spikes roughly every ~1000 commits, hundreds of ms each); there is no consumer surface to raise the sync level (a `FULL` option would be a post-v1 engine-surface addition) |
| SQLite | `poll_interval` | the watcher's `data_version` poll cadence — 1 ms shipping default ([ADR-023](decisions/023-fourth-review-round.md) §4, the measured-wake-latency posture), carried on `SqliteOpts`; the idle cost is ~1000 poll reads/sec/instance; raising the interval trades wake latency (interval-bound) for idle CPU — the tuning recipe for latency-tolerant deployments |
| Postgres | `synchronous_commit` | per-session knob; `on` is ship config (p50 2.40 ms seam); `off` trades max-tail (40.9 ms) for slightly better p50 — measured, honest trade ([ADR-004](decisions/004-postgres-driver.md)); session-level SET mechanics POC-verified |
| Mem | — (none) | There is no durability knob and no durability: the honest row is the absence itself ([ADR-024](decisions/024-mem-engine.md) §2). Wake latency is sub-millisecond in-process; no tuning surface exists |
| Mem | — (none) | There is no durability knob and no durability: the honest row is the absence itself ([ADR-024](decisions/024-mem-engine.md) §2). Wake delivery is an in-process channel send at the commit boundary — no network or driver round-trip exists to budget; no tuning surface exists |
These are engine-configuration concerns, *not* trait surface. What
part of engine config is contract-level shape vs engine-crate docs is
@@ -174,7 +196,7 @@ non-goal — adding it would be a scope change.
| Growth path | SQLite | Postgres | Mem |
|---|---|---|---|
| Dead letters (`__alkstore_dead` / pg dead table / mem's dead table) | grows forever unless the consumer opts in and sweeps — accepted | same shape — accepted | grows in memory; erased at process exit; retention still sweep-on-call — accepted |
| Notify transport (`__alkstore_notifications`) | the engine's only unconstrained-growth-with-hygiene table — pruned to its newest 10,000 rows *at attach only* (D-16); an idle-but-listening process never prunes — accepted with the cadence caveat | **no table, no path** — `pg_notify` is transient transport; nothing persists (≤ 8000 bytes per notify, [ADR-004](decisions/004-postgres-driver.md)) | **no storage** — wakes deliver at the commit boundary to open receivers; per-subscriber feeds are unbounded in-memory, ending at engine drop |
| Notify transport (`__alkstore_notifications`) | the engine's only unconstrained-growth-with-hygiene table — pruned to its newest 10,000 rows *at attach only* (the at-attach cap, [ADR-010](decisions/010-queue-semantics-depth.md) §6); an idle-but-listening process never prunes — accepted with the cadence caveat | **no table, no path** — `pg_notify` is transient transport; nothing persists (≤ 8000 bytes per notify, [ADR-004](decisions/004-postgres-driver.md)) | **no storage** — wakes deliver at the commit boundary to open receivers; per-subscriber feeds are unbounded in-memory, ending at engine drop |
| Stream log | durable-log posture by contract ([ADR-015](decisions/015-streams-depth.md)); `trim_to` is the consumer-invoked cap — accepted | same durable-log shape — accepted | in-memory event log, process-lifetime-bounded; `trim_to` available |
The per-path rationale:
@@ -189,8 +211,10 @@ The per-path rationale:
wants dead-letter hygiene budgets the sweep (a `@every` schedule is
the natural recipe). Growth is therefore fully opted-in twice:
retention off by default, and no automatic cadence when on.
2. **Notifications** — SQLite-only growth. The D-16 at-attach cap
(oldest-first trim to 10,000 rows, every connection open) is the
2. **Notifications** — SQLite-only growth. The at-attach prune cap
(oldest-first trim to 10,000 rows, every connection open; the
substrate provenance register's D-16 port delta,
[ADR-010](decisions/010-queue-semantics-depth.md) §6) is the
*only* prune site: there is no background sweeper and no consumer-
visible prune operation (the table is engine-internal wake
transport, kept off the contract by ADR-008 §4's disposition).
@@ -216,9 +240,6 @@ log, and unconsumed wake feeds alike — is process-lifetime-bounded by
design, ending at engine drop or process exit. Accepted structurally
rather than via caps.
This record is the input the deployment-matrix final pass folds into
the document's release durability/growth text.
## Toolchain / platform notes
| Note | Engine | Affects |
@@ -227,7 +248,7 @@ the document's release durability/growth text.
| `bundled-sqlite` adds a C build (~10 s dev, cacheable) | SQLite | build/CI time |
| `libsqlite3-sys` collision with sqlx today | any mixed-driver binary | structurally avoided ([ADR-001](decisions/001-crate-split.md) single-driver rule) |
| Listener `application_name` set for diagnosability (kill-targetable) | Postgres | ops runbooks ([ADR-004](decisions/004-postgres-driver.md)) |
| `alkstore-mem` compiles clean on `wasm32-unknown-unknown` (wave-6 acceptance, [ADR-024](decisions/024-mem-engine.md) §5); tokio features limited to the wasm-supported subset (sync + time); current-thread-runtime-clean internals (the suite's mem column runs under that flavor) | Mem | wasm targets, sandboxes, downstream ffi/napi/python adapters — the only engine that can compile to wasm (rusqlite/tokio-postgres are structurally out) |
| `alkstore-mem` compiles clean on `wasm32-unknown-unknown` ([ADR-024](decisions/024-mem-engine.md) §5); tokio features limited to the wasm-supported subset (sync + time); current-thread-runtime-clean internals (the contract suite's mem column runs under that flavor) | Mem | wasm targets, sandboxes, downstream ffi/napi/python adapters — the only engine that can compile to wasm (rusqlite/tokio-postgres are structurally out) |
## Consumer-facing latency profile (indicative, POC-measured)
@@ -239,6 +260,10 @@ From both POCs (single-box, relative shapes are the deliverable —
- Wake: ~1.1–2.2 ms p50 both engines at default cadence.
- Queue claim: LISTEN-driven 3–6 ms p50 (pg); poll-only interval-bound
(32–50 ms at a 50 ms poll).
- Mem: no POC measured it (the engine postdates the POCs); its wake
and tx costs are in-process channel/lock operations, not the
seam/network classes above — see [engine-mem.md](engine-mem.md)'s
architecture for what it realizes instead.
- Absolute numbers will differ per hardware/network; they set
*expectations of order*, not SLAs — contract docs must not bake
them in ([ADR-007](decisions/007-transactional-seam.md)'s pg-POC