feat(sessions): hermes sessions set-journal-mode delete|wal converts an existing WAL store offline (#100896)

`database.journal_mode: delete` can never self-apply to a store that is already WAL:
apply_wal_with_fallback deliberately never live-downgrades (#68545 — other gateway/cron/worker
connections may hold uncheckpointed WAL commits), so operators applying the containment for the
multi-writer corruption class saw one ERROR per process forever and the only escape hatch was an
undocumented hand-run PRAGMA on the file.

The new pre-DB `sessions set-journal-mode` verb is the sanctioned offline path: it refuses while ANY
foreign process holds the file or a sidecar (the same foreign_state_db_holders scan doctor/repair
admission uses, naming each PID), flips through _set_journal_mode_no_wait (busy_timeout=0, so an
opener appearing mid-way makes SQLite refuse instead of racing it), verifies header bytes 18/19,
and reminds the operator when config.yaml disagrees. `--db PATH` covers kanban.db / cron stores
that log the same ERROR. The never-live-downgrade invariant is untouched; the ERROR, doctor hints
and docs now name the command instead of the raw PRAGMA.
This commit is contained in:
teknium1
2026-09-20 16:00:32 -07:00
committed by Teknium
parent 95b1f4c855
commit 96da5d97fc
10 changed files with 206 additions and 17 deletions

View File

@@ -971,9 +971,15 @@ def _cmd_repair_profiles(args):
return cmd_repair_profiles(args)
def _cmd_set_journal_mode(args):
from hermes_cli.sessions_cmd_journal_mode import cmd_set_journal_mode
return cmd_set_journal_mode(args)
_PRE_DB_HANDLERS = {
"repair": _cmd_repair, "recover": _cmd_recover, "import": _cmd_import,
"repair-profiles": _cmd_repair_profiles, # opens every profile's store itself
"set-journal-mode": _cmd_set_journal_mode, # offline: must not open the store it converts
}
_OBSERVATIONAL_DB_ACTIONS = frozenset({"list", "stats", "pinned"})
_DB_HANDLERS = {