9cf4fe5513
`sessions optimize` could consume several GB of disk instead of freeing any, filling the host to 100% on exactly the large databases it exists to shrink. Two causes, both in the WAL lifecycle: 1. No `journal_size_limit`. SQLite defaults to -1 (unlimited), so after a checkpoint the WAL is reused in place and never truncated — `state.db-wal` permanently keeps the high-water mark of the largest transaction ever run. `hermes_cli/kanban_db.py` already bounds its WAL with `wal_autocheckpoint=100`; the session store, by far the larger database, had no equivalent. 2. `vacuum()` checkpoints BEFORE `VACUUM` but not after. VACUUM rewrites every page through the WAL, so the pre-checkpoint does nothing about the slack VACUUM itself creates. Measured on a 3.0 GB state.db: `hermes sessions optimize` reported "3143.9 MB -> 3155.1 MB (reclaimed -11.2 MB)" while leaving a 3.07 GB state.db-wal behind. Free space fell from 6.9 GB to 772 MB (100% full) and stayed there. A manual `PRAGMA wal_checkpoint(TRUNCATE)` recovered the full 3.07 GB, confirming it was slack, not data. Fix: set `journal_size_limit` (64 MiB) when enabling WAL, and truncate the WAL again after VACUUM. Both are best-effort and never raise — a failure costs disk slack and must not stop the DB from opening. Tests assert the contract (limit is a finite positive bound; VACUUM does not leave an oversized WAL) rather than pinning the byte count, which is a tunable. They skip where WAL is unavailable — including hosts where Hermes falls back to journal_mode=DELETE due to the SQLite 3.50.4 WAL-reset bug. Verified: 462 passed / 3 skipped in tests/test_hermes_state.py, and _apply_wal_size_limit flips a real WAL database from -1 to 67108864. Tested on Linux (aarch64, Python 3.11).