Conversation
🔬 Gate certification — RED ⛔ (sentinel-skip is content-shape-based → suppresses legitimate backup recovery + reverses the #5532 recovery contract — CORE)Certified head: What I ran (rebased worktree
|
| Gate | Result |
|---|---|
| Rebase onto current master | ✅ git apply clean |
| Codex (reproduce) | SHIP-WITH-FIXES — 1 CORE (content-shape false-positive suppresses recovery); Codex verified against the impl + contract test |
| Full pytest suite | ✅ rc=0 green — but note the PR MODIFIED test_issue5532_session_clear_state_db_replay.py (+23), i.e. it changed the very test encoding the recovery contract it reverses (Codex flags :185) |
| PR's own test | ✅ 16/16 (test_issue5570_clear_backup_recovery.py) |
Findings
⛔ CORE (I + Codex CONFIRMED) — content-shape sentinel suppresses legitimate recovery (api/session_recovery.py:264): the branch returns no_action SOLELY on _session_records_clear_sentinel(session_path), which matches the sidecar's content shape (messages==[], context_messages==[], truncation_watermark/boundary==0.0, active_stream_id/pending_* None/[]). But that shape isn't unique to /api/session/clear: _advance_truncation_watermark_after_commit() intentionally leaves 0.0 (streaming.py:4956) and normal stream completion clears active/pending (streaming.py:8738-8742), so a session with REAL post-clear activity can carry the same fields. If a legitimate later shrink-to-empty then creates a .json.bak (models.py:1228-1246), recovery is suppressed purely because the live sidecar "looks like" a clear — silently refusing to restore legitimate messages, reversing the origin/master recovery contract (test_issue5532:185, which this PR edits). Fix (Codex-exact): don't infer intent from content shape — write explicit clear PROVENANCE from /api/session/clear (a marker / clear-generation stamp) and skip recovery ONLY when the backup predates that specific clear operation; otherwise keep restoring larger same-generation backups. Mark the clear operation itself, not "every later empty/0.0 sidecar."
✅ #5570 case is real (keep the goal): a crash right after clear CAN leave a stale larger backup that resurrects cleared content — worth fixing. The mechanism (provenance-marked clear + predates-check) is the safe version; the content-shape proxy is the flaw.
Recommendation to the next agent / author
RED — gate-fail/changes-requested (1 CORE): drive the backup-skip from explicit clear PROVENANCE (a marker/generation written by /api/session/clear), not from the live sidecar's content shape — skip only backups that predate that clear; keep restoring legitimate larger same-generation backups. The #5570 fix is worth having, but a content-shape sentinel is ambiguous with legitimately-empty sessions and reverses the #5532 recovery contract. Also revert/justify the test_issue5532:185 modification — changing the contract test to accommodate the regression is a flag. concept 4/5 (real #5570 fix; wrong provenance signal). Author @rodboev (T1). crit=3, data-integrity. (Gate value + recurring lesson: infer intent from PROVENANCE, not from CONTENT SHAPE — an empty/0.0 sidecar is not proof of an intentional clear; the same class as #5471 "identity+TTL can't tell new-value-same-key" and #5556. And watch a PR that MODIFIES the very test encoding a contract it changes — that's a signal the change reverses an intended invariant.)
_Gate-certifier layer (warm-up → gate → release). I do not merge/tag/deploy. Rebased onto current master; confirmed recovery skip is content-shape-only (session_recovery.py:264 returns no_action solely on session_records_clear_sentinel), the shape isn't clear-unique (0.0 watermark not advanced streaming.py:4956, pending cleared on normal completion 8738-8742), so a legit transiently-empty session's backup recovery is suppressed + the #5532 contract (test:185, which this PR edits) reversed. Fix: clear-provenance marker + predates-check, not content shape. Suite green (runtime provenance-ambiguity, not a suite fail). Cert valid for sha:e1897a1b.
e1897a1 to
fe0d683
Compare
|
Good catch. I reworked the recovery skip so it no longer treats the empty sidecar shape as proof of an intentional clear.
This keeps the #5570 crash-window fix for stale pre-clear backups, but it moves the decision from content shape to explicit clear provenance. |
🔬 Gate certification — GREEN ✅ · CONVERGED (round-1 content-shape CORE fixed → clear-provenance gate)Certified head: What I ran (rebased worktree
|
| Gate | Result |
|---|---|
| Rebase onto current master | ✅ git apply clean |
| Codex (reproduce) | SAFE TO SHIP — 0 findings; round-1 CORE confirmed fixed |
| Full pytest suite | ✅ 12039 passed, 0 failed |
| PR + #5532 contract tests | ✅ 17/17 (incl. the previously-reversed test_issue5532 — now honored, not loosened) |
Findings — round-1 CORE CLOSED
✅ Provenance-gated (no more content-shape false-positive): /api/session/clear sets s.clear_generation = uuid4().hex (only when had_sidecar_messages) at routes.py:13767 — a unique per-clear provenance stamp (server-set, a real Session field models.py:1056/1108). The recovery skip _session_records_clear_sentinel(session_path, bak_path) (session_recovery.py:228-231) now returns False UNLESS the live sidecar carries a non-empty clear_generation AND the backup does NOT carry the same generation — so:
- a legit transiently-empty session (no
clear_generation) is NEVER falsely skipped → recovery runs (round-1 CORE closed); - a genuinely cleared session's PRE-clear larger backup IS still skipped (Recovery: exempt the truncate-to-empty sentinel from .bak restore (clear crash-window) #5570 preserved);
- a same-generation post-clear backup STILL restores (not over-suppressed).
Codex confirmedclear_generationis server-set + unique (uuid4, no spoof/collision),Nonewhen no messages cleared (never triggers skip), and the /api/session/clear does not delete messages from state.db — history survives clear+refresh #5532 recovery contract is honored (test passes, not loosened). 17 targeted + full suite green (0 failures).
Recommendation to the next agent
Ready to merge — use branch gate-rebase/5584-clear-provenance-recovery (sha:6792e58d), NOT the PR's stale head fe0d6836. The #5570 fix is now provenance-based (not the ambiguous content-shape sentinel) — a cleared session's stale pre-clear backup is skipped, but legitimate transiently-empty sessions and same-generation backups still recover, and the #5532 contract is honored. Codex SAFE + 17 targeted + full suite green (0 failures), backend/data-integrity. concept 4/5 (real #5570 fix, converged to the correct provenance model). Author @rodboev (T1). crit=3, data-integrity.
Gate-certifier layer (warm-up → gate → release). I do not merge/tag/deploy. Rebased onto current master; round-1 content-shape CORE fixed via provenance — /api/session/clear stamps unique clear_generation (routes.py:13767, models.py:1056/1108), recovery skips only when live sidecar has the marker AND backup lacks the same generation (session_recovery.py:228-231); legit-empty not falsely skipped, #5570 preserved, same-generation restores, #5532 contract honored (test passes). Codex SAFE + 17 targeted + full suite green (0 failed). Cert valid for sha:6792e58d.
release #5584: skip clear-sentinel backups during recovery
|
Shipped in v0.51.875 — thanks @rodboev! 🎉 Clearing a conversation can no longer be undone by crash recovery. Gate (2 rounds): Codex round-1 caught a real CORE gap — after clear + one post-clear message with a surviving stale |
Thinking Path
/api/session/clearcan still crash after persisting an empty sidecar and before removing the pre-clear.json.bak.What Changed
api/models.py: persistsclear_generationas session metadata so post-clear saves keep the clear provenance.api/routes.py: writes a new clear generation during/api/session/clearonly when the clear actually shrinks a sidecar with messages, and verifies that marker before treating the stale backup cleanup as safe.api/session_recovery.py: skips restoring a larger.json.bakonly when the live sidecar has clear provenance and the backup does not carry the same generation.tests/test_issue5570_clear_backup_recovery.py: covers stale pre-clear skip, same-generation backup restore, clear-shaped files without provenance, malformed fail-open recovery, and compression recovery preservation.tests/test_issue5532_session_clear_state_db_replay.py: restores the later-backup recovery contract and verifies no-op clears of already-empty sessions keep existing recoverable backups.Why It Matters
Clearing a conversation stays durable across the crash window that leaves a stale pre-clear backup behind. Legitimate post-clear backup recovery still works because recovery checks provenance rather than content shape alone.
Verification
Full-suite CI context, not a required local check unless requested:
python -m pytest tests/ -v --timeout=60.Upstream
Closes #5570.
Model Used
GPT 5.5 via Codex CLI