Follow-up to #9006/#58899. The gateway routing index (session_key ->
SessionEntry) now lives in a new gateway_routing table in state.db as the
primary store; sessions.json is demoted to an optional legacy mirror.
- hermes_state.py: schema v19 — gateway_routing table (scope + session_key
PK; scope = resolved sessions_dir so multiple stores sharing one state.db
never cross-contaminate) with save/replace/load/delete methods
- gateway/session.py: _save() writes the whole index atomically to the DB
(mirrors the old full-file JSON rewrite semantics) and only falls back to
JSON when the DB write fails; _ensure_loaded reads the DB first and folds
in legacy sessions.json entries for keys the DB lacks (pre-migration
import; DB entries win over stale JSON)
- gateway/config.py + hermes_cli/config.py: new write_sessions_json flag
(default true for compat/downgrade safety); gateway.write_sessions_json:
false stops producing the file entirely
- sessions.json _README updated to say it's a legacy mirror + how to
disable it
Rehydration is now lossless across restarts even with sessions.json deleted:
suspended/resume_pending/model_override/token state all round-trip through
the DB (the old sessions-table recovery only rebuilt the bare key mapping).