The boot-time integrity check (scheduleBackgroundIntegrityCheck, ~60s
after init) ran PRAGMA integrity_check on the live connection, walking
every page and freezing the event loop for several seconds — the largest
single read-contention stall in normal operation.
Offload it to the sqlite3 CLI in a child process (async spawn), matching
the existing out-of-process pattern used by quickCheckSqliteFile and
.recover. The CLI connection is opened -readonly so it can never
checkpoint or write the live WAL; this works because the live process
holds the DB open (so the -shm exists). When the CLI is unavailable or
can't open read-only, fall back to the in-process check (verified=false),
preserving today's behavior on those environments.
- New integrityCheckSqliteFileAsync(dbPath, limit) module helper.
- New private runBackgroundIntegrityCheck() seam (offload + fallback) so
the scheduler has one testable, deterministic policy point.
- Background scheduler callback is now async (IIFE + finally) with errors
swallowed so a background timer can't crash the process.
VACUUM is intentionally NOT offloaded: the call graph shows it is invoked
only by the `fn db vacuum` CLI command and tests, never from the periodic
maintenance loop, so it is not a background event-loop stall — and an
out-of-process VACUUM on a live WAL DB would add corruption surface for no
hot-path benefit.
Tests updated to the async/offloaded seam (deterministic regardless of
whether the sqlite3 CLI exists in the environment), plus coverage for the
new helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>