820d3ca65d
Follow-up to the two cherry-picked commits from #105308 (@JoaoMarcos44), closing the three blockers raised on that thread plus one regression the salvage found: - Prefix-only on the send path. build_api_messages now canonicalizes only messages[:current_turn_user_idx]; rows the current turn appended (its own tool calls/results) pass through verbatim. Canonicalizing the live tail rewrote a block the previous iteration had already sent whenever a tool result matched the interrupt heuristic, which is exactly the mid-turn prefix rewrite this fix exists to remove, and it also made the dangling-tail transform order-dependent on when the user row was appended. - Exact interrupt marker. is_interrupted_tool_result matched "exit_code" + ("130" | "-1") + "interrupt" as substrings, so an ordinary `grep KeyboardInterrupt` result next to a diff hunk header rewrote a terminal result to an orphan notice (or dropped a read-only block). That heuristic was tolerable at resume time only; it now runs per request. Match the executors' bracketed markers ("[Command interrupted", "[execution interrupted") and nothing else. - Admission-time clock. The frozen expiry clock was the input's platform-event stamp, so a message queued 70 s before the turn ran kept a 129 s-old confirmation live on the send path while replay expired it. _reset_per_turn_agent_state stamps time.time() once at admission; the three other writes (bind identity, stage message, build_api_messages write-back under suppress(Exception)) are gone. - Fail closed on corrupt stamps. A present-but-unparseable timestamp (`"nan"`, `"not_a_number"`) made strip_stale_dangerous_confirmations keep the confirmation and its api_content sidecar. Coerce through hermes_cli.timefmt.coerce_epoch and treat an unknowable age as expired; missing stamps (legacy rows) are still left alone. - Shape: drop the canonicalize_history_for_send alias (no consumer, never existed on main), the `now=` kwarg (no production caller), and the getattr/hasattr rewrite of _reset_per_turn_agent_state (only the test double needed it). - Tests: 17 → 2 invariant tests. Real SessionDB round trip → canonicalize → ChatCompletionsTransport bytes, equal to the send path with sidecars applied and the durable list untouched, live tail preserved; admission-clock freeze across iterations + corrupt-stamp fail-closed. Each is red under the matching mutation (send path unpatched, whole-list canonicalization, per-request clock, fail-open, loose heuristic).