hermes-agent/tests/state
RelaxJonh b6ca4fc856 fix(state): heal session_model_usage PK unconditionally to restore token/cost accounting
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
2026-07-31 23:18:12 -07:00
..
test_compression_lineage_guard.py
test_disk_full_error.py fix(gateway): fail prompt.submit when session storage hits a full disk 2026-08-01 00:27:17 -05:00
test_fts_runtime_rebuild.py
test_no_more_rows_retry.py fix(state): retry transient 'no more rows available' across all sqlite3.Error classes 2026-07-31 23:18:12 -07:00
test_session_model_usage_pk_heal.py fix(state): heal session_model_usage PK unconditionally to restore token/cost accounting 2026-07-31 23:18:12 -07:00
test_write_lock_patience.py fix(state): time-based write-lock patience so busy sibling processes can't destroy turns 2026-07-29 17:59:02 -07:00