test(sqlite): red tests for the pinned-engine, busy-drop, swallowed-schema family (T17 finding 2)

Finds recorded red on the pre-fix tree (audit base a52624b3 re-verified on
current master 79ff61b3): pin-floor lint naming the seven stale modules
(v1.28.0, bundled SQLite 3.41.2 below the 3.51.3 boundary-law floor),
connect-surface contract (no busy timeout anywhere, synchronous=NORMAL in
three modules), two-writer concurrency regression dropping instantly with
SQLITE_BUSY, and connect() swallowing every schema-setup error. Plans doc
records finding -> test -> commit per the bugfix contract.
This commit is contained in:
Nick committed 2026-10-26 00:37:35 +00:00
1 parent 79ff61b361
commit 6296ea583e
6 files changed
+645

No files matched your search

@@ -0,0 +1,100 @@
# 2026-10-26 — SQLite floor repair (T17 audit finding 2, red-first)
Base: gitea/master 79ff61b3 (frozen audit base a52624b3; master moved by one
release-bump commit — every pin and behavior re-verified on CURRENT code
before a single line was written). Worktree:
`~/.config/compose/worktrees/rune/fix-sqlite-floor` (branch
`fix/sqlite-floor-audits`, created exclusively for this wave).
## The findings (T17, finding 2 + behavior family)
Plain language for the owner: seven Go modules that write our shared
databases (the memory store, caches, auto-task ledger, abstraction state)
compile against a **five-generation-stale SQLite engine**, have **no wait
policy for lock contention** (the Python side waits 5 seconds; Go fails
instantly with "database is locked"), and **silently swallow every schema
upgrade error at startup** (including the unavoidable "duplicate column"
error from repeated migrations) — plus two modules run **fsync-light writes**
(`synchronous=NORMAL`) against the crash-anywhere durable-write law.
1. **PIN FLOOR** — modules pin `modernc.org/sqlite v1.28.0` (bundled engine
SQLite 3.41.2; owner floor = bundled SQLite ≥ 3.51.3, the WAL-reset fix,
per `docs/architecture/boundary-law.md` line 116): persona-definition,
memory-store-deep, memory-ops, connection-pool, cache, auto_tasks,
abstraction. Compliant references: memory-continuity, curator (v1.49.1 =
SQLite 3.53.0).
2. **BUSY TIMEOUT** — no Go store waits on contention; the Python store
waits 5s (`src/rune/memory_store.py:153` = `sqlite3.connect` default
timeout). Concurrent agent writers on the shared
`~/.rune/mempalace/memories.db` fail instantly (`SQLITE_BUSY`, error 5).
3. **LOUD SCHEMA SETUP** — `memory-store-deep/connect()` executed the WAL
switch, CREATEs, and ALTERs with every error dropped
(main.go:74-101 pre-fix). The `ALTER TABLE ... ADD COLUMN` pair errors
with "duplicate column" on every already-migrated store — never noticed
because errors were swallowed. The honest fix ports the Python store's
conditional migration (`src/rune/memory_store.py:246-263`): read
`PRAGMA table_info`, ALTER only missing columns.
4. **SYNCHRONOUS=NORMAL** — memory-ops, memory-store-deep, cache ran
fsync-light commits; the house standard is the SQLite default FULL
(crash-anywhere durable writes). No Go module ever set FULL at the
driver layer, and post-open `db.Exec("PRAGMA ...")` pragmas apply to a
single pooled connection only — the policy is now per-connection DSN
(`_pragma` query params, documented by the modernc.org/sqlite driver at
driver.go:49-52), so it survives `database/sql` pooling.
## Red tests (written FIRST, run RED on the pre-fix tree, evidence frozen)
| # | Test | RED evidence on pre-fix code |
|---|------|------------------------------|
| F1a | `tests/test_sqlite_pin_floor.py::test_every_sqlite_module_bundles_at_least_the_owner_floor` | FAIL naming exactly the seven modules at v1.28.0 (`bundles a pre-floor engine`) |
| F1b | `tests/test_sqlite_pin_floor.py::test_all_sqlite_modules_share_one_engine_version` | FAIL: two divergent engines (1.28.0 / 1.49.1) |
| F2a | `tests/test_go_connect_pragmas.py::test_every_sqlite_open_carries_a_busy_timeout_dsn` | FAIL listing all 10 files with `sql.Open` sites missing `_pragma=busy_timeout` |
| F2b | `tests/test_go_connect_pragmas.py::test_no_synchronous_normal_pragma_in_go_sources` | FAIL: cache.go, memory-ops/main.go, memory-store-deep/main.go |
| F2c | `src/go/memory-ops/busy_timeout_test.go::TestConcurrentWritersBothSucceedWithinBusyTimeout` | FAIL: both writers dropped at **0.00s** with `database is locked (5) (SQLITE_BUSY)`, 0/2 rows landed |
| F3 | `src/go/memory-store-deep/schema_errors_test.go::TestConnectPropagatesSchemaSetupErrors` | FAIL: `connect() returned no error for an unprovisionable store` |
| F3b | `.../TestConnectConditionalMigrationOnLegacyStore` | supporting green-path test (idempotent migration, legacy data preserved) |
F2c exercises the fix path end-to-end: writer A holds the write lock and
releases at 2s; writers B and C go through the module's `connect()` and must
both land within the 5s busy budget on a synthetic DB in a private temp dir
(isolated GOCACHE, `GOPROXY=off`, no production stores, no network).
## The fix (root cause, not symptom)
- **One engine everywhere**: all seven modules → `modernc.org/sqlite
v1.49.1` (bundled SQLite 3.53.0 ≥ 3.51.3 floor — source: the module's
own CHANGELOG, `modernc.org/sqlite@v1.49.1/CHANGELOG.md`, entry
"2026-04-17 v1.49.0: Upgrade to SQLite 3.53.0",
https://sqlite.org/releaselog/3_53_0.html). go.sum regenerated by
`go mod tidy -go=1.25.0` (offline: `GOPROXY=off GOSUMDB=off`) — the
resulting dep graph is byte-identical to the compliant memory-continuity
module's (libc v1.72.0 …). The go directive moves 1.21 → 1.25.0 to match
the driver's requirement (repo toolchain 1.27.1 per go-version.txt; CI
asserts the same).
- **Busy policy at every open site** (15 sites / 10 files): DSN carries
`_pragma=busy_timeout%3d5000` — 5s, matching Python's connect wait.
Writers additionally get `_pragma=journal_mode(WAL)` (behavior preserved)
and, where NORMAL had been set, `_pragma=synchronous(FULL)` (law
alignment). Read backends get the shared busy wait only (ro conns must
not attempt journal changes; silent-nil oracle failure shapes untouched).
- **Loud, idempotent schema setup**: memory-store-deep provisioning split
into `provisionSchema(db)` with every statement checked; batch-1 columns
migrate conditionally (`PRAGMA table_info` first). cache's two
`CREATE INDEX` statements now checked too.
- **Structural CI guard**: the two Python lint tests fail the gate if any
future module pins below the floor, pins a second engine, opens sqlite
without a ≥5000ms busy DSN, or reintroduces synchronous=NORMAL.
## Discharge record (finding → red test → commit)
FILLED AFTER COMMITS LAND (see §Discharge-final below).
## Constraints honored
- Worktree exclusive; existing worktrees (release-reporting,
/mnt/workdrive/rune, …) untouched.
- Offline: all go tooling ran with `GOPROXY=off`/`GOSUMDB=off`; no
production stores, services, or network involved.
- Red-first per docs/bugfix-contract.md; tests are frozen regressions.
- Admission wave: `scripts/admit_wave_sqlite_floor.py`
(classify→activate→admit) naming the seven modules and the lint test.
+129
View File
@@ -0,0 +1,129 @@
"""Admission dispatch for the sqlite-floor repair wave (T17 finding 2).
classify + activate + admit (the headless scaffolding entry law,
tests/test_construction_admission.py pattern): a bounded repair raising the
seven stale modernc.org/sqlite pins to the owner floor bundle, setting the
5s busy-timeout policy at every Go open site, making schema setup loud and
idempotent, and shipping the CI-enforced pin-lint + pragma contract tests;
user-dispatch approved.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"bounded repair (T17 finding 2, red-first): raise the seven stale "
"modernc.org/sqlite pins (persona-definition, memory-store-deep, "
"memory-ops, connection-pool, cache, auto_tasks, abstraction — all "
"v1.28.0, bundling SQLite 3.41.2) to the owner floor bundle v1.49.1 "
"(SQLite 3.53.0 >= 3.51.3 boundary-law floor), set a 5s "
"PRAGMA busy_timeout policy at every Go sqlite open site (Python store "
"parity, memory_store.py:153), replace synchronous=NORMAL with the "
"durable-write FULL standard, make memory-store-deep schema setup loud "
"and idempotent (conditional migration port of memory_store.py:246-263), "
"and land CI-enforced guards: the pin-lint test and the Go connect "
"pragma contract test, plus a two-concurrent-writers regression test "
"on a synthetic temp-store"
)
AFFECTED = [
"src/go/persona-definition/go.mod",
"src/go/memory-store-deep/go.mod",
"src/go/memory-ops/go.mod",
"src/go/connection-pool/go.mod",
"src/go/cache/go.mod",
"src/go/auto_tasks/go.mod",
"src/go/abstraction/go.mod",
"src/go/memory-ops/main.go",
"src/go/memory-store-deep/main.go",
"src/go/memory-store-deep/gaps_io.go",
"src/go/memory-store-deep/gaps_stats.go",
"src/go/connection-pool/main.go",
"src/go/cache/cache.go",
"src/go/auto_tasks/auto_tasks.go",
"src/go/abstraction/abstraction.go",
"src/go/abstraction/replay.go",
"src/go/curator/store.go",
"src/go/memory-continuity/store.go",
"src/go/persona-definition/gaps.go",
"tests/test_sqlite_pin_floor.py",
"tests/test_go_connect_pragmas.py",
"src/go/memory-ops/busy_timeout_test.go",
"src/go/memory-store-deep/schema_errors_test.go",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-sqlite-floor",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-sqlite-floor",
"affected_surface": AFFECTED,
"evidence_refs": [
"docs/architecture/boundary-law.md",
"src/rune/memory_store.py",
"src/go/memory-store-deep/main.go",
"plans/2026-10-26-sqlite-floor-audit-t17-finding2.md",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-sqlite-floor")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-sqlite-floor",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-sqlite-floor",
expected_new_entities=[
"tests/test_sqlite_pin_floor.py (CI-enforced pin lint)",
"tests/test_go_connect_pragmas.py (connect-surface contract)",
],
expected_runtime_effects=[
"all nine modernc.org/sqlite modules pin one release bundling "
"SQLite >= 3.51.3 (v1.49.1 = 3.53.0)",
"concurrent writers on a shared store wait up to 5s instead of "
"dropping with instant SQLITE_BUSY",
"schema setup failures propagate instead of being swallowed",
"Go stores commit with synchronous=FULL (durable-write law)",
],
verification_obligations=[
"pytest tests/test_sqlite_pin_floor.py tests/test_go_connect_pragmas.py green",
"go test ./... in the nine sqlite modules green (frozen suites)",
"henxels check holds for the changed surfaces",
],
approval_ref="user:T17 finding 2 repair dispatch",
contract_id="ADMIT-wave-sqlite-floor",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
+141
View File
@@ -0,0 +1,141 @@
package main
// busy_timeout_test.go — T17-finding-2 concurrency regression (audit
// a52624b3): two simultaneous writers on one shared store must BOTH succeed
// within the 5s busy timeout (the Python store's connect wait,
// src/rune/memory_store.py:153), never again dropping instantly with
// "database is locked (5)". Synthetic DB in a private temp dir; the writer
// under test is connect() itself. Red on the pre-fix connect(): writer B
// fails while A holds the write lock.
import (
"database/sql"
"os"
"path/filepath"
"strings"
"testing"
"time"
_ "modernc.org/sqlite"
)
const busyFixtureSchema = `CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
importance REAL DEFAULT 0.5,
created_at TEXT,
last_accessed TEXT,
topic TEXT DEFAULT '',
source TEXT DEFAULT '',
method TEXT DEFAULT 'curator',
status TEXT DEFAULT 'active',
confidence REAL DEFAULT 0.7,
temperature REAL DEFAULT 1.0,
always_on INTEGER DEFAULT 0,
priority INTEGER DEFAULT 5,
tier TEXT DEFAULT 'warm',
memory_type TEXT,
wash_count INTEGER DEFAULT 0,
superseded_by INTEGER,
rock_status TEXT DEFAULT 'washable'
)`
func TestConcurrentWritersBothSucceedWithinBusyTimeout(t *testing.T) {
dir := t.TempDir()
t.Setenv("RUNE_MEMORY_DIR", dir)
store := filepath.Join(dir, ".rune", "mempalace", "memories.db")
// Fixture: entries schema + persistent WAL via an independent handle —
// mirroring the real shared store (the Python connect provisions WAL;
// journal_mode is a persistent property, so later conns don't race a
// journal-mode CONVERSION, only the write lock).
if err := os.MkdirAll(filepath.Dir(store), 0o755); err != nil {
t.Fatalf("fixture dir: %v", err)
}
setup, err := sql.Open("sqlite", store)
if err != nil {
t.Fatalf("open fixture: %v", err)
}
if _, err := setup.Exec(busyFixtureSchema); err != nil {
t.Fatalf("fixture schema: %v", err)
}
var mode string
if err := setup.QueryRow("PRAGMA journal_mode=WAL").Scan(&mode); err != nil {
t.Fatalf("fixture WAL: %v", err)
}
if mode != "wal" {
t.Fatalf("fixture WAL switch: got %q", mode)
}
setup.Close()
// Writer A: raw handle with busy_timeout pinned to 0, holding the write
// lock for ~2s — the cross-writer contention the shared store sees.
a, err := sql.Open("sqlite", store)
if err != nil {
t.Fatalf("open writer A: %v", err)
}
defer a.Close()
a.SetMaxOpenConns(1)
if _, err := a.Exec("PRAGMA busy_timeout=0"); err != nil {
t.Fatalf("writer A busy_timeout: %v", err)
}
if _, err := a.Exec("BEGIN IMMEDIATE"); err != nil {
t.Fatalf("writer A lock: %v", err)
}
if _, err := a.Exec("INSERT INTO entries (text, created_at, status) VALUES ('writer A holds the lock', '2026-01-01T00:00:00Z', 'active')"); err != nil {
a.Exec("ROLLBACK")
t.Fatalf("writer A insert: %v", err)
}
results := make(chan error, 2)
start := time.Now()
// A releases the write lock 2s in — inside the 5s busy budget the
// writers under test must tolerate (Python-store parity).
go func() {
time.Sleep(2 * time.Second)
a.Exec("ROLLBACK")
}()
for _, writer := range []string{"B", "C"} {
go func(name string) {
db, err := connect() // the module path under test
if err != nil {
results <- err
return
}
defer db.Close()
_, err = db.Exec("INSERT INTO entries (text, created_at, status) VALUES (?, ?, 'active')",
"writer "+name+" during A transaction", "2026-01-01T00:00:01Z")
results <- err
}(writer)
}
for i := 0; i < 2; i++ {
select {
case err := <-results:
if err != nil {
if strings.Contains(err.Error(), "locked") || strings.Contains(err.Error(), "SQLITE_BUSY") {
t.Errorf("writer dropped on contention (%.2fs into the window): %v", time.Since(start).Seconds(), err)
continue
}
t.Errorf("writer failed for an unrelated reason: %v", err)
}
case <-time.After(15 * time.Second):
t.Fatalf("writers stalled past the 5s busy timeout (15s elapsed)")
}
}
a.Exec("ROLLBACK")
db, err := sql.Open("sqlite", store)
if err != nil {
t.Fatalf("verification open: %v", err)
}
defer db.Close()
var rows int
if err := db.QueryRow("SELECT COUNT(*) FROM entries WHERE text LIKE '%during A transaction%'").Scan(&rows); err != nil {
t.Fatalf("verification: %v", err)
}
if rows != 2 {
t.Errorf("both concurrent writers must land: %d/2 rows (writer A's lock-holder txn rolled back)", rows)
}
}
@@ -0,0 +1,118 @@
package main
// schema_errors_test.go — T17-finding-2 regression (audit a52624b3):
// connect()'s startup statements (WAL switch, CREATE, ALTER at
// main.go:74-101) must fail LOUDLY. Before the fix every Exec error was
// dropped, including the blind ALTERs that error "duplicate column name"
// on every already-migrated store — a schema setup failure surfaced only
// as a later, mysteriously-shaped write failure. The migration now ports
// the Python store's conditional pattern (src/rune/memory_store.py:246-263):
// PRAGMA table_info first, ALTER only when the column is missing.
import (
"database/sql"
"os"
"path/filepath"
"testing"
_ "modernc.org/sqlite"
)
// TestConnectPropagatesSchemaSetupErrors — a store layout that cannot be
// provisioned must return an error from connect(), not a half-open handle.
func TestConnectPropagatesSchemaSetupErrors(t *testing.T) {
dir := t.TempDir()
// mempalace as a regular file: the memories.db directory cannot exist,
// so every schema statement must fail and the failure must surface.
if err := os.WriteFile(filepath.Join(dir, "mempalace"), []byte("not a directory"), 0o644); err != nil {
t.Fatal(err)
}
t.Setenv("RUNE_MEMORY_DIR", dir)
db, err := connect()
if err == nil {
db.Close()
t.Fatal("connect() returned no error for an unprovisionable store — " +
"schema setup failures are still being swallowed (T17 finding 2)")
}
}
// TestConnectConditionalMigrationOnLegacyStore — a pre-batch-1 store gets
// the missing columns exactly once, and every later connect stays green
// (the old blind ALTERs errored on the second connect, silently).
func TestConnectConditionalMigrationOnLegacyStore(t *testing.T) {
dir := t.TempDir()
t.Setenv("RUNE_MEMORY_DIR", dir)
store := filepath.Join(dir, "mempalace", "memories.db")
if err := os.MkdirAll(filepath.Dir(store), 0o755); err != nil {
t.Fatalf("fixture dir: %v", err)
}
seed, err := sql.Open("sqlite", store)
if err != nil {
t.Fatalf("open legacy store: %v", err)
}
// The legacy shape is the pre-batch-1 Go CREATE (all columns it ever
// made, WITHOUT promotion_eligible/last_washed — those only ever came
// from the ALTERs).
legacy := `CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
importance REAL DEFAULT 0.5,
created_at TEXT,
last_accessed TEXT,
topic TEXT DEFAULT '',
entities TEXT DEFAULT '',
source TEXT DEFAULT '',
method TEXT DEFAULT 'curator',
status TEXT DEFAULT 'active',
valid_until TEXT,
confidence REAL DEFAULT 0.7,
temperature REAL DEFAULT 1.0,
always_on INTEGER DEFAULT 0,
priority INTEGER DEFAULT 5,
tier TEXT DEFAULT 'warm',
memory_type TEXT,
wash_count INTEGER DEFAULT 0,
superseded_by INTEGER,
rock_status TEXT DEFAULT 'washable'
)`
if _, err := seed.Exec(legacy); err != nil {
seed.Close()
t.Fatalf("legacy schema: %v", err)
}
if _, err := seed.Exec("INSERT INTO entries (text, created_at) VALUES ('legacy row', '2026-01-01T00:00:00Z')"); err != nil {
seed.Close()
t.Fatalf("legacy seed row: %v", err)
}
seed.Close()
db, err := connect()
if err != nil {
t.Fatalf("connect() on legacy store: %v", err)
}
var migrated int
if err := db.QueryRow(
"SELECT COUNT(*) FROM pragma_table_info('entries') WHERE name IN ('promotion_eligible','last_washed')",
).Scan(&migrated); err != nil {
db.Close()
t.Fatalf("legacy columns after migrate: %v", err)
}
if migrated != 2 {
db.Close()
t.Fatalf("legacy columns not migrated: %d/2 present", migrated)
}
// The previously-dropped data must survive the migration.
var text string
if err := db.QueryRow("SELECT text FROM entries ORDER BY id").Scan(&text); err != nil {
db.Close()
t.Fatalf("legacy row lost: %v", err)
}
db.Close()
// Second connect on the now-migrated store must stay green.
db2, err := connect()
if err != nil {
t.Fatalf("second connect() on migrated store: %v", err)
}
db2.Close()
}
+73
View File
@@ -0,0 +1,73 @@
"""Go SQLite open-surface contract (T17 finding 2 — busy/durability family).
Structural CI enforcement in the discharge contract's infra-test doctrine
(docs/bugfix-contract.md: "CI/workflow bugs: the red test is the
contract assertion" — parsed-source assertions for machinery bugs).
Two invariants, each RED on master before the fix:
1. Every non-test Go source calling sql.Open("sqlite", ...) builds a DSN
carrying PRAGMA busy_timeout >= 5000ms via the driver's documented
`_pragma` query parameter (modernc.org/sqlite driver.go:49-52; applied
per pooled connection — the only way the policy survives database/sql
pooling). 5000ms matches the Python store's connect default wait
(src/rune/memory_store.py:153 — sqlite3.connect's 5s busy timeout), the
uniformity the shared ~/.rune/mempalace/memories.db needs: agent
writers must never again fail instantly with "database is locked (5)".
2. No Go source sets PRAGMA synchronous=NORMAL. The house standard is the
SQLite compile-time default FULL (crash-anywhere durable writes); the
Python store sets nothing and runs FULL — NORMAL is the finding-2
contradiction (fsync-light commits on shared state).
"""
import re
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
BUSY_MS_MIN = 5000
def _go_sources():
for f in sorted((REPO / "src" / "go").rglob("*.go")):
if f.name.endswith("_test.go"):
continue
yield f
def test_every_sqlite_open_carries_a_busy_timeout_dsn():
offenders = []
for f in _go_sources():
text = f.read_text(encoding="utf-8")
if 'sql.Open("sqlite"' not in text:
continue
timeouts = [
int(ms)
for ms in re.findall(r"busy_timeout%{1,2}3d(\d+)", text)
]
if not timeouts:
offenders.append(
f"{f.relative_to(REPO)}: sql.Open(\"sqlite\", ...) has no "
"_pragma=busy_timeout DSN — concurrent writers fail with "
"instant SQLITE_BUSY (T17 finding 2)"
)
continue
too_short = [ms for ms in timeouts if ms < BUSY_MS_MIN]
if too_short:
offenders.append(
f"{f.relative_to(REPO)}: busy_timeout {too_short[0]}ms is "
f"below the {BUSY_MS_MIN}ms house wait (Python store parity)"
)
assert not offenders, "\n".join(offenders)
def test_no_synchronous_normal_pragma_in_go_sources():
offenders = [
str(f.relative_to(REPO))
for f in _go_sources()
if re.search(r"PRAGMA\s+synchronous\s*=\s*NORMAL", f.read_text(encoding="utf-8"))
]
assert not offenders, (
"PRAGMA synchronous=NORMAL contradicts the crash-anywhere "
"durable-write standard (sync FULL, the SQLite default the Python "
f"store runs): {offenders}"
)
+84
View File
@@ -0,0 +1,84 @@
"""Pin-floor lint for bundled SQLite engines (T17 finding 2, audit a52624b3).
Owner law (docs/architecture/boundary-law.md, "INDEXED STATE BACKBONE")):
every Go module binding modernc.org/sqlite must pin a release whose BUNDLED
SQLite engine is >= 3.51.3 (the WAL-reset bug was fixed there). This module
lints the pins so no module can regress below the floor silently.
Mapping used (documented source, not folklore): modernc.org/sqlite v1.49.1
bundles SQLite 3.53.0 — per the module's own CHANGELOG.md entry
"2026-04-17 v1.49.0: Upgrade to SQLite 3.53.0"
(https://sqlite.org/releaselog/3_53_0.html), verified against the module
cache at modernc.org/sqlite@v1.49.1/CHANGELOG.md. v1.49.1 was verified on
master before this test landed (memory-continuity/curator already run it).
The lint also requires ONE engine version repo-wide: two bundled engines
serving the same store files was the finding-2 harm class (v1.28.0 bundles
SQLite 3.41.2 — six generations below the floor — and drops SQLITE_BUSY
instantly while the Python store waits 5s on the same memories.db).
"""
import re
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
# (1, 49, 1) = the repo-standard pin: bundles SQLite 3.53.0 >= the owner
# floor 3.51.3 (boundary-law.md line 116). Any lower modernc pin fails.
FLOOR = (1, 49, 1)
FLOOR_SOURCE = (
"modernc.org/sqlite CHANGELOG.md (module cache modernc.org/sqlite@v1.49.1/"
"CHANGELOG.md): '2026-04-17 v1.49.0: Upgrade to SQLite 3.53.0' "
"(https://sqlite.org/releaselog/3_53_0.html); owner floor "
"docs/architecture/boundary-law.md line 116 (>= 3.51.3)"
)
def _parse_pin(version: str) -> tuple[int, int, int]:
parts = tuple(int(p) for p in version.split("."))
return parts # modernc.org/sqlite uses plain X.Y.Z releases
def sqlite_pins() -> dict[Path, str]:
"""Map every go.mod under src/ that requires modernc.org/sqlite to its pin."""
pins: dict[Path, str] = {}
for gomod in sorted((REPO / "src").rglob("go.mod")):
text = gomod.read_text(encoding="utf-8")
m = re.search(r"modernc\.org/sqlite\s+v(\d+\.\d+\.\d+)", text)
if m:
pins[gomod] = m.group(1)
return pins
def fmt(version: tuple) -> str:
return ".".join(str(p) for p in version)
def test_every_sqlite_module_bundles_at_least_the_owner_floor():
pins = sqlite_pins()
assert pins, "lint scope broken: no modernc.org/sqlite pin found under src/"
violations = {
str(p.relative_to(REPO)): v
for p, v in pins.items()
if _parse_pin(v) < FLOOR
}
assert not violations, (
"modules pin a modernc.org/sqlite release OLDER than the repo floor "
f"{fmt(FLOOR)} — bundled SQLite engine must be >= 3.51.3 (WAL-reset "
f"fix; source: {FLOOR_SOURCE}):\n"
+ "\n".join(
f" {path}: v{ver} (bundles a pre-floor engine)"
for path, ver in sorted(violations.items())
)
)
def test_all_sqlite_modules_share_one_engine_version():
pins = sqlite_pins()
assert pins, "lint scope broken: no modernc.org/sqlite pin found under src/"
versions = sorted({_parse_pin(v) for v in pins.values()})
assert len(versions) <= 1, (
"divergent modernc.org/sqlite engines serve the same store files "
f"({[fmt(v) for v in versions]}): T17 finding-2 harm class — raise or "
f"align every module to one release >= {fmt(FLOOR)} ({FLOOR_SOURCE})"
)