session recovery's salvage classifier decided "is this cell a session id?"
with SESSION_ID_PATTERN alone (^\d{8}_\d{6}_), which only covers ids minted
by new_session_id. Several first-class surfaces DERIVE an id instead:
cron/scheduler.py f"cron_{job_id}_{now:%Y%m%d_%H%M%S}"
hermes_cli/cli_commands_mixin.py f"bg_{now:%H%M%S}_{uuid4().hex[:6]}" (/bg)
gateway/.../api_server_room_dispatch.py f"room_{sha256(seed)[:32]}"
Measured against a copy of a real 3,373-session store: 379 ids (11.2%)
failed the pattern -- 375 cron, 1 bg, plus 3 non-session test artifacts.
job_id is normally uuid4().hex[:12] (cron/jobs.py) but legacy/user ids like
"job-1" are real and still present in live stores, so the cron recognizer
accepts a general job-id token.
Two distinct consequences, both fixed:
1. DROPPED SESSIONS. classify_lost_and_found_row() returned None for those
rows, so recovery silently discarded every cron/bg/room session. On the
real store: 2994 of 3373 rows kept before, 3370 after (the 3 still
dropped are 's'/'session-key'/'pre-compress-key' -- not sessions,
correctly rejected).
2. POISONED LAYOUT INFERENCE. parent_session_id is a _TEXT_SHAPE_RULES
column checked with the same predicate. A cron job's CHILD session has an
ordinary timestamp id, so it classifies fine and becomes layout evidence
-- while carrying the unrecognised cron id in parent_session_id. Since one
bad sampled value vetoes a candidate layout, a single such row collapsed
inference for the whole sessions table, i.e. the positional-guessing path
#101409's fix exists to prevent. Proven on a synthetic store: 20 clean +
cron parent + child-of-cron -> EMPTY before, 59/59 columns named and 0
wrong after.
Fix: hermes_state_ids gains SESSION_ID_RECOGNIZERS + is_known_session_id(),
the recognition side of the minting contract; _is_session_id() consults it.
Each pattern is anchored on exactly what its minting site fixes -- these are
layout sentinels, so a loose pattern would buy recall at the cost of
wrong-column mappings.
Tests: tests/hermes_state/test_session_id_recognition_recovery.py (new, 43
cases) covers recognition, non-dropping, the parent_session_id poisoning
path, and adversarial wrong-layout rejection (rotated layouts still vetoed;
near-miss and arbitrary text still rejected). Mutation-checked: with the
added recognizers removed, 12 of 43 fail -- including the end-to-end
poisoning test.
(cherry picked from commit f82b7182f03e1eee55c832a3f90fffbe580d1f5c)