b6ca4fc856
Installs whose state.db reached schema_version >= 22 before the task dimension was added carry a 5-column PRIMARY KEY on session_model_usage. The column reconciler ADDs task as a bare nullable, but SQLite cannot ALTER a primary key, and the version-gated v22 rebuild is unreachable (current_version < 22 already false), so the composite 6-column key never lands. Every upsert in _record_model_usage then fails with 'ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint', aborting the enclosing write transaction — token/cost accounting permanently dead (#73823). Add an idempotent _heal_session_model_usage_pk() modeled on _heal_gateway_routing_pk(), run unconditionally from _init_schema on every open. Salvaged from #73838 with fix-ups: - ported to SessionSchemaMixin in hermes_state_schema.py (the schema code moved out of hermes_state.py in 21c7ae8563; the PR targeted the old location) - rebuild wrapped in a PRAGMA foreign_keys=OFF/ON window: the connection enables FKs before _init_schema and OR IGNORE does NOT suppress FK violations, so a single orphaned usage row (session pruned while accounting was broken) would have aborted the heal - COALESCE('') on the nullable reconciler-added task column (and the billing columns) during the copy - stale-v22+ regression tests: rebuilt PK + restored upsert, orphan rows survive the FK window, healthy-DB no-op, no legacy leftover Fixes #73823