Skip to content

Commit 3412c8f

Browse files
committed
feat: US-012 - Document the SQLite read-mode write-mode invariant
1 parent 3931f30 commit 3412c8f

5 files changed

Lines changed: 28 additions & 2 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ docker-compose up -d
109109
- SQLite VFS file handles must enforce their reader or writer role; reader-owned handles fail closed on mutating callbacks.
110110
- Native SQLite single-statement work should route through the native execute path; keep `exec` as the multi-statement compatibility path.
111111
- Native SQLite manual transactions keep an idle writer open until autocommit returns; route subsequent work through the writer instead of reader classification.
112+
- Native SQLite read mode may hold multiple read-only connections, while write mode must hold exactly one writable connection and no readers; TypeScript must not be the routing policy boundary.
112113
- For NAPI bridge wiring (TSF callback layout, cancellation tokens, `#[napi(object)]` rules), see `docs-internal/engine/napi-bridge.md`.
113114

114115
## Agent Working Directory

docs-internal/engine/SQLITE_OPTIMIZATIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Range page-read protocol details live in `.agent/specs/sqlite-range-page-read-pr
2020
- `sqlite-storage` reassembles large chunked logical values with one bounded chunk-prefix range read by default; `RIVETKIT_SQLITE_OPT_BATCH_CHUNK_READS=false` selects serial 10 KB chunk gets for comparison runs.
2121
- `sqlite-storage` caches decoded DELTA/SHARD LTX blobs across repeated reads by default, with `RIVETKIT_SQLITE_OPT_DECODED_LTX_CACHE=false` preserving per-read decode behavior.
2222
- `sqlite-storage` compaction folds DELTA pages into SHARD blobs for steadier read behavior.
23+
- The native read-mode/write-mode SQLite connection manager routes read-only statements to pooled read-only connections and routes writes, transactions, and fallbacks through exclusive write mode. Read-pool v1 closes readers before writes and does not pin per-reader head txids.
2324

2425
## Recommended Optimizations
2526

@@ -31,7 +32,6 @@ Range page-read protocol details live in `.agent/specs/sqlite-range-page-read-pr
3132
- Return SQLite meta from `sqlite-storage::get_pages(...)` instead of doing a second META read in pegboard-envoy.
3233
- Persist capped VFS preload hints on sleep/close and feed them into `OpenConfig` on the next actor start.
3334
- Add a bulk or range page-read protocol so cold scans do not require page-list request loops.
34-
- Add a read-mode/write-mode SQLite connection manager for parallel read-only queries; read mode may hold multiple read-only connections, while write mode must close readers and hold exactly one writable connection.
3535
- Reduce storage read amplification from whole-blob LTX decode further with page-frame-addressable storage.
3636
- Benchmark compacted and un-compacted cold reads separately.
3737

docs-internal/engine/sqlite-vfs.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ The native VFS uses the same 4 KiB chunk layout and KV key encoding as the WASM
3131
- SQLite VFS aux-file create/open paths mutate `BTreeMap` state under one write lock with `entry(...).or_insert_with(...)`. Avoid read-then-write upgrade patterns.
3232
- SQLite VFS v2 storage keys use literal ASCII path segments under the `0x02` subspace prefix with big-endian numeric suffixes so `scan_prefix` and `BTreeMap` ordering stay numerically correct.
3333
- SQLite v2 slow-path staging writes encoded LTX bytes directly under DELTA chunk keys. Do not expect `/STAGE` keys or a fixed one-chunk-per-page mapping in tests or recovery code.
34+
35+
## Read-mode/write-mode connection manager
36+
37+
- The native connection manager is the SQLite read/write routing policy boundary. TypeScript and NAPI wrappers forward calls to native execution and must not decide routing from SQL text.
38+
- Read mode may hold multiple read-only SQLite connections against one shared VFS context. Write mode must hold exactly one writable SQLite connection and no reader connections.
39+
- Entering write mode stops admitting new readers, waits for active readers to release, closes idle readers, then opens or reuses the single writable connection.
40+
- Read-pool v1 intentionally does not let readers continue during writes and does not pin per-reader head txids or snapshots. Any future design that overlaps readers with writers must add explicit snapshot fencing.

scripts/ralph/prd.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@
207207
"Typecheck passes"
208208
],
209209
"priority": 12,
210-
"passes": false,
210+
"passes": true,
211211
"notes": ""
212212
}
213213
]

scripts/ralph/progress.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Started: Wed Apr 29 04:23:03 AM PDT 2026
1616
- TypeScript SQLite migration hooks should run inside native `writeMode` so setup queries use the writer connection and do not create readers.
1717
- SQLite read-pool rollout config lives in `sqlite-storage::optimization_flags`; build `NativeConnectionManagerConfig` from `sqlite_optimization_flags()` and use `RIVETKIT_SQLITE_OPT_READ_POOL_ENABLED=false` for single-writer compatibility.
1818
- Kitchen-sink SQLite real-world benchmark reporting should include read-pool route counters alongside VFS counters so parallel-read and read-write-transition workloads expose manager behavior.
19+
- Native SQLite read-pool v1 closes readers before writes and does not pin per-reader head txids; TypeScript/NAPI wrappers must treat native execution as the routing policy boundary.
1920

2021
## 2026-04-29 04:27:40 PDT - US-001
2122
- Implemented native SQLite statement classification with readonly detection, trailing-statement detection, authorizer action capture, and conservative reader eligibility.
@@ -259,3 +260,20 @@ Started: Wed Apr 29 04:23:03 AM PDT 2026
259260
- Fence-mismatch tests need to clear the VFS page caches after setup so the stale reader is forced to fetch through the engine and observe the replacement generation.
260261
- Native VFS registration tests can affect later tests because SQLite's VFS list is process-global; drop the stale registration before the replacement registration during cleanup.
261262
---
263+
## 2026-04-29 06:05:43 PDT - US-012
264+
- Documented the SQLite read-mode/write-mode connection manager invariant in internal VFS docs, including exclusive write mode, no reader/write overlap, and the native routing policy boundary.
265+
- Moved the read-mode/write-mode manager tracker entry from recommended work into existing optimizations.
266+
- Preserved the reusable invariant in the root agent notes for future SQLite changes.
267+
- Files changed:
268+
- `AGENTS.md`
269+
- `docs-internal/engine/sqlite-vfs.md`
270+
- `docs-internal/engine/SQLITE_OPTIMIZATIONS.md`
271+
- `scripts/ralph/prd.json`
272+
- `scripts/ralph/progress.txt`
273+
- Checks:
274+
- `cargo check -p rivetkit-sqlite`
275+
- **Learnings for future iterations:**
276+
- Read-pool v1 intentionally avoids reader/writer overlap instead of pinning per-reader head txids or snapshots.
277+
- Internal SQLite docs are the right home for cross-layer invariants; keep the optimization tracker limited to benchmark and performance status.
278+
- Root `AGENTS.md` already has a SQLite Package section for short reusable constraints that should apply across future implementation work.
279+
---

0 commit comments

Comments
 (0)