EqualifyEverything / equalify-iris

fix(startup): a database that opens but cannot be written names its remedy (#478)
A database that opens but cannot be written now names its remedy. `openStorage()` guarded opening the database and nothing else. The first WRITE — `failStaleSessions()`, clearing what a previous shutdown orphaned — sat 77 lines past the end of the try/catch, so it threw outside every message the guard exists to print. SQLite opens a database it cannot write without complaint and raises only when something writes, so a root-owned `iris.sqlite` bind-mounted into a container that has dropped to an unprivileged uid got past `mkdirSync`, past `accessSync`, past `new Store()`, and died on the first write with a bare `attempt to write a readonly database` carrying no errno, path or uid — while this same file already held the `sudo chown 1000:1000 …` that fixes it, three lines below the throw. The UIC deployment paid for that eight times on 2026-09-14: the image started running as uid 1000, the host data directory was uid 0, the container exited at boot, and the deploy's health gate rolled it back. Eight runs, none naming ownership. Three rounds, all approved, each of the first two carrying real notes: - The first write moved inside the guard; `openStorage()` returns `{ store, stale }`. - Round 1: the WAL sidecars are probed too. This deployment runs in WAL, `-wal` and `-shm` are separate files with their own owners, and switching an existing file to WAL needs no write — so a root-owned set reached the same throw. Without them the one-file `chown` the guard prints is a remedy that does not work, and the next boot reports that the cause is not ownership at all. The three checks are spelled out rather than filtered, because the enumeration pin has to be able to read which paths they are. - Round 1: the exactly-once pin counted prose, not calls. - Round 2: the failure line dropped the SQLite condition. `e.code ?? e.message` never reaches the message when every node:sqlite error carries the same code, so the operator read `(ERR_SQLITE_ERROR)` and the string identifying the failure appeared nowhere. Nothing had pinned that line before; three assertions and a vacuity guard do now. - Round 2: the count's residual — trailing and block comments. - Round 3: nothing new. Every pin was mutation-checked, because none of them adds a test case and the count stayed at 1708 throughout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blake Bertuccelli-Booth Blake Bertuccelli-Booth committed on Sep 14, 2026, 07:37 PM
Showing 2 changed files +130 additions -15 deletions
Browse files at this commit →