Skip to content

fix(persistence): allocate collision-free checkpoint snapshot IDs - #39

Draft
4444J99 wants to merge 1 commit into
mainfrom
ranked-queue/issue-38-checkpoint-collision
Draft

4444J99 wants to merge 1 commit into
mainfrom
ranked-queue/issue-38-checkpoint-collision

Conversation

@4444J99

@4444J99 4444J99 commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Closes #38.

Defect (reproduced pre-fix at exact base)

CheckpointManager.create_checkpoint() derived snapshot_id to whole-second precision (SNAP_{now.strftime('%Y%m%d_%H%M%S')}). With datetime.now() fixed to one instant for two calls:

  • both checkpoints returned the same ID (SNAP_20261023_231500)
  • only one snapshot file remained on disk
  • CHECKPOINT_REGISTRY.json held two entries with that ID
  • get_checkpoint_by_name("first") loaded the SECOND checkpoint's state (data loss)

Fix

  • rege/persistence/checkpoint.py: new _allocate_snapshot_id() — keeps the SNAP_YYYYMMDD_HHMMSS base with a zero-padded _NNN sequence suffix (the layout the module docstring already documents; mirrors rege/protocols/recovery.py). The sequence is derived from IDs claimed in the checkpoint registry plus snapshot files on disk, so same-second creates get distinct IDs even when the clock never advances, across manager instances and restarts.
  • Save-then-register ordering unchanged: a failed snapshot write raises PersistenceError before any registry entry is created (fail closed).
  • rege/tests/test_checkpoint_collision.py (new): 5 regression tests — same-second distinct IDs/files, per-entry state resolution, cross-instance sequence continuation, fail-closed write failure, legacy unsuffixed-ID coexistence.

Scope honored: only checkpoint.py + the new regression test; no workflow, packaging, or archive changes. models.py's StateSnapshot fallback keeps its existing behavior (out of scope, noted for follow-up).

Verification (local, base 685e682 worktree)

  • Issue repro post-fix: distinct IDs _001/_002, two files, each name loads its own state — PASS
  • python -m pytest -q rege/tests/test_checkpoint_collision.py → 5 passed
  • python -m pytest -q rege/tests/test_persistence.py rege/tests/test_persistence_coverage.py → 47 passed, 4 failed — the 4 failures are pre-existing environment failures (permission-denied tests running as root); verified identical on pristine base, untouched assertions.

Base / head

Draft per the issue's delivery boundary (no merge/release authority granted). CI must be honestly green at the exact head before this counts as done.

Summary by Sourcery

Allocate collision-free checkpoint snapshot IDs to prevent same-second checkpoints from overwriting one another.

Bug Fixes:

  • Prevent checkpoint snapshot ID collisions when multiple checkpoints are created within the same second, preserving distinct snapshots and correct state lookup.

Enhancements:

  • Continue snapshot ID allocation safely across existing registry entries, snapshot files, manager instances, and restarts while retaining compatibility with legacy unsuffixed IDs.

Tests:

  • Add regression coverage for same-second collisions, state resolution, cross-instance allocation, failed writes, and legacy snapshot coexistence.

Closes #38.

CheckpointManager.create_checkpoint() derived snapshot_id to whole-second
precision, so two checkpoints created within the same second shared one ID:
the second write replaced the first snapshot file while CHECKPOINT_REGISTRY.json
kept two entries, and loading the first name returned the second state
(reproducible data loss).

The fix allocates IDs as SNAP_YYYYMMDD_HHMMSS_NNN (the layout already
documented in the module docstring, mirroring rege/protocols/recovery.py),
deriving the sequence from IDs claimed in the checkpoint registry and
snapshot files on disk so allocation stays correct across manager instances
and process restarts, even when the clock does not advance between calls.

Save-then-register ordering is unchanged: a failed snapshot write raises
PersistenceError before any registry entry is created (fail closed).

Tests: rege/tests/test_checkpoint_collision.py (5 regression tests:
same-second distinct IDs/files, per-entry state resolution, cross-instance
sequence continuation, fail-closed write failure, legacy unsuffixed ID
coexistence). Verified: new file 5 passed; rege/tests/test_persistence.py +
test_persistence_coverage.py 47 passed with 4 pre-existing environment
failures (root-user permission tests, identical on pristine base).
@coderabbitai

coderabbitai Bot commented Oct 24, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@sourcery-ai

sourcery-ai Bot commented Oct 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes same-second checkpoint collisions by allocating timestamp-based IDs with zero-padded sequence suffixes derived from both registry entries and on-disk snapshots, while retaining save-before-register semantics. New regression tests cover collision prevention, state integrity, restart continuity, write failures, and legacy snapshot compatibility.

Sequence diagram for collision-free checkpoint creation

sequenceDiagram
    participant Manager as CheckpointManager
    participant Registry as CheckpointRegistry
    participant Disk as SnapshotDirectory

    Manager->>Registry: load_checkpoint_registry()
    Registry-->>Manager: claimed snapshot IDs
    Manager->>Disk: glob(SNAP_*.json)
    Disk-->>Manager: existing snapshot files
    Manager->>Manager: _allocate_snapshot_id(when)
    Manager->>Disk: _save_snapshot(snapshot)
    alt snapshot write succeeds
        Manager->>Registry: register checkpoint
    else snapshot write fails
        Manager-->>Manager: raise PersistenceError
    end
Loading

File-Level Changes

Change Details Files
Allocate collision-free checkpoint snapshot IDs while preserving the existing timestamp format and persistence ordering.
  • Add sequence-suffixed IDs (_NNN) based on IDs found in the registry and snapshot files.
  • Continue saving snapshots before registering them so write failures leave no registry entry.
  • Preserve coexistence with legacy unsuffixed snapshot IDs.
rege/persistence/checkpoint.py
Add regression coverage for same-second checkpoint creation and ID allocation across persistence boundaries.
  • Verify distinct IDs, files, and per-name state resolution under a frozen clock.
  • Verify sequence continuation across manager instances and fail-closed write behavior.
  • Verify legacy unsuffixed files do not collide with newly allocated IDs.
rege/tests/test_checkpoint_collision.py

Assessment against linked issues

Issue Objective Addressed Explanation
#38 Allocate distinct checkpoint snapshot IDs for checkpoints created at the same wall-clock second, preventing one checkpoint from overwriting another and ensuring each name loads its own saved state. ✅
#38 Fail closed so a failed snapshot write does not leave a registry entry pointing to missing or incorrect snapshot data. ✅
#38 Preserve existing checkpoint behavior and add regression coverage for the collision and relevant persistence paths. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

4444J99 commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

Green progress receipt (exact-head 8bde311): Python CI workflow completed with success on this draft PR. Core tests for the collision-free snapshot ID allocation are green. Awaiting remaining checks before any further progress.

This branch has not been deployed

No deployments
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.

Prevent same-second checkpoint ID collisions from overwriting snapshots

1 participant