Skip to content

fix(storage): let crash-recovery finalize tolerate legitimate reclassification - #3370

Merged
Sinity merged 1 commit into
masterfrom
fix/interrupted-finalize-tolerates-reclassification-ewfp
Jul 28, 2026
Merged

fix(storage): let crash-recovery finalize tolerate legitimate reclassification#3370
Sinity merged 1 commit into
masterfrom
fix/interrupted-finalize-tolerates-reclassification-ewfp

Conversation

@Sinity

@Sinity Sinity commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Fix the final layer of the live incident chain (PRs #3368, #3369 fixed two real bugs and measurably improved the writer-hold from 543s to 210s): crash-recovery finalization was applying the same strict "no plan may change" invariant as a normal apply, which a stuck multi-day-old census can never satisfy once its classification legitimately moves on.

Problem

recover_interrupted_raw_authority_frontier runs on every daemon startup and finalizes every still-planned census. One census had sat unfinalized across multiple days (verified via created_at_ms timestamps). A stopped-daemon read-only diagnostic showed the affected sessions now classify as unresolved_provenance/refine_quarantined_raw (the underlying raw got quarantined by an unrelated safety mechanism) — not duplicate_alias anymore, as they were when the census was originally planned.

finalize_raw_authority_census's postflight check required every retryable/carried-forward plan to survive byte-identical in the fresh post-census, regardless of whether this was a normal apply or crash recovery — even though interrupted was already threaded through as a parameter, just not consulted by this specific check. This left the census permanently unable to finalize: every recovery attempt raised on the same stale plan_ids, on every single daemon restart.

Solution

Skip the byte-identical-persistence check when interrupted=True. Correct for a normal apply (nothing else should touch the archive mid-call); wrong for crash recovery, whose entire purpose is reconciling against current ground truth after an arbitrary gap.

Verification

  • devtools test tests/unit/storage/test_raw_authority_ledger.py tests/unit/storage/test_duplicate_raw_identity_repair.py → 41 passed (new regression test proves interrupted=True tolerates exactly the shape the existing non-interrupted test asserts DOES raise; anti-vacuity confirmed by reverting)
  • mypy polylogue/storage/raw_authority.py → clean
  • devtools verify --quick → exit 0

Ref polylogue-ewfp, polylogue-ihc8

Co-Authored-By: Claude noreply@anthropic.com

Summary by CodeRabbit

  • Bug Fixes

    • Improved crash-recovery handling when finalizing interrupted authority censuses.
    • Interrupted finalization now succeeds when carried-forward plans have been reclassified during recovery.
    • Normal finalization continues to detect unexpected plan changes.
  • Tests

    • Added regression coverage for interrupted finalization with reclassified carried-forward plans.

…ification

## Problem

Discovered live 2026-07-28, after deploying PRs #3368/#3369 (which fixed
two real bugs and measurably improved the writer-hold on this recurring
failure from 543s to 210s): the daemon still crashed on every restart with
`RuntimeError: raw authority postflight changed a retryable/carried-forward
plan`, citing the exact same 4 plan_ids every time.

Root cause (a third, distinct layer): `recover_interrupted_raw_authority_frontier`
runs on every daemon startup and calls `finalize_raw_authority_census(...,
interrupted=True)` on EVERY still-`planned` census -- not just ones with
unrecorded outcomes. A "planned" census can sit unfinalized across an
arbitrary gap (this one spanned multiple days, verified via `created_at_ms`
timestamps in `raw_authority_census_plans`). During that gap, a retryable/
carried-forward plan's own evidence can legitimately shift for reasons
unrelated to what the census originally selected -- confirmed via a
stopped-daemon read-only diagnostic: all 4 affected sessions now classify
as `unresolved_provenance`/`refine_quarantined_raw` (the underlying raw got
quarantined by an unrelated safety mechanism), not `duplicate_alias`
anymore.

`finalize_raw_authority_census`'s strict "every retryable/carried-forward
plan must survive byte-identical in the fresh post-census" check applied
identically whether `interrupted=True` (crash recovery) or not, even
though the `interrupted` parameter was already threaded through to every
caller -- it just wasn't consulted by this specific check. This left the
stuck census permanently unable to finalize: every recovery attempt
re-selected the same stale plan_ids, recomputed a genuinely different
`post_ids` set (since the true classification had moved on), and raised --
on every single daemon restart, indefinitely.

## Solution

Skip the byte-identical-persistence check when `interrupted=True`. This
invariant is correct for a normal, uninterrupted apply (nothing else
should touch the archive between planning and finalizing that same call),
but wrong for crash recovery, whose entire purpose is reconciling against
CURRENT ground truth after an arbitrary gap, not demanding continuity with
a stale snapshot.

New regression test proves `interrupted=True` tolerates the exact plan
shape the existing `test_postflight_rejects_carried_plan_shared_with_retryable_selection`
asserts DOES raise for a normal apply (kept unchanged, still passing) --
confirming the fix is scoped precisely to crash recovery. Anti-vacuity:
reverting the fix reproduces the exact failure shape.

## Verification
- `devtools test tests/unit/storage/test_raw_authority_ledger.py tests/unit/storage/test_duplicate_raw_identity_repair.py` -> 41 passed
- `mypy polylogue/storage/raw_authority.py` -> clean
- `devtools verify --quick` -> exit 0

Ref polylogue-ewfp, polylogue-ihc8

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 704caed0-3173-47c6-aa73-06370d3e3c58

📥 Commits

Reviewing files that changed from the base of the PR and between b0558fd and ec59957.

📒 Files selected for processing (2)
  • polylogue/storage/raw_authority.py
  • tests/unit/storage/test_raw_authority_ledger.py

📝 Walkthrough

Walkthrough

Interrupted raw-authority census finalization now tolerates carried-forward plan reclassification during crash recovery. A regression test verifies the census reaches the interrupted lifecycle state, while normal finalization retains the existing invariant check.

Changes

Raw-authority recovery

Layer / File(s) Summary
Interrupted finalization guard and regression coverage
polylogue/storage/raw_authority.py, tests/unit/storage/test_raw_authority_ledger.py
The carried-forward plan subset invariant is enforced only for non-interrupted finalization, and a test verifies interrupted recovery with an absent carried-forward plan completes as "interrupted".

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • Sinity/polylogue#2961: Introduced durable raw-authority ledger and replay accounting for this finalization path.
  • Sinity/polylogue#2962: Adjusted finalization invariants for crash-recovery and interrupted execution.
  • Sinity/polylogue#3269: Modified the retryable/carried-forward postflight persistence criteria at the same code point.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the crash-recovery finalization behavior change and matches the code changes.
Description check ✅ Passed The description covers summary, problem, solution, and verification; only optional changelog and follow-up sections are omitted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/interrupted-finalize-tolerates-reclassification-ewfp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec599579ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# finalize, repeatedly re-selecting and re-failing the same already
# -reclassified plan_ids on every subsequent recovery attempt
# (observed live: a 200+s writer-lock hold on every daemon restart).
if not interrupted and not persistent.issubset(post_ids):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict the bypass to actual crash recovery

interrupted=True does not exclusively identify recovery after an arbitrary gap: apply_raw_authority_frontier passes it whenever any strategy returns a retryable outcome (raw_reconciler.py:1393-1399), while still finalizing in the same invocation. If a strategy fails after partially committing durable work—for example, browser-origin copy-forward commits its source stage before the separate index stage—this condition now suppresses the check that would detect the immutable plan disappearing or changing, and publishes the census as interrupted without creating the stale-plan blocker that previously kept the archive fail-closed. Use a distinct actual-recovery flag, or keep this invariant enabled for the ordinary apply caller even when its outcome is retryable.

Useful? React with 👍 / 👎.

@Sinity
Sinity merged commit 2083c5d into master Jul 28, 2026
2 of 3 checks passed
@Sinity
Sinity deleted the fix/interrupted-finalize-tolerates-reclassification-ewfp branch July 28, 2026 10:47
Sinity added a commit that referenced this pull request Jul 28, 2026
… follow-up

polylogue-ewfp's crash is fixed and confirmed live: PR #3370 (crash-recovery
finalize tolerance) resolves the true recurring-crash mechanism after
#3368/#3369 fixed two adjacent layers of the same stack. Verified: the
stuck census finally finalized (outcome=success), every previously-starved
daemon actor now runs cleanly, no more crash across multiple subsequent
passes.

Filed polylogue-zaiz for the distinct, unattempted residual: 3 fan-out
sessions now correctly classify as refine_quarantined_raw (a separate
actuator/workflow) rather than crashing -- non-urgent, daemon operates
normally around it.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant