Skip to content

fix(preservation-spine): classify races and make checkpoint create/unlink durable - #59

Open
kalisam wants to merge 4 commits into
feat/preservation-spine-standalonefrom
fix/pr43-unclassified-durability
Open

fix(preservation-spine): classify races and make checkpoint create/unlink durable#59
kalisam wants to merge 4 commits into
feat/preservation-spine-standalonefrom
fix/pr43-unclassified-durability

Conversation

@kalisam

@kalisam kalisam commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

What this is

Stacked on #43 (feat/preservation-spine-standalone @ 4daefa0). Classifies the remaining bare OSError / ValueError / UnicodeDecodeError paths and makes checkpoint create/unlink crash-durable on POSIX.

Does not change ADRs, integrity zomes, consensus logic, or delete anything.

Fixes

  • Capture resolve(strict=True) / read_bytes FileNotFound → CaptureDrift
  • POSIX parent-directory fsync after creating checkpoints.jsonl and creating/unlinking the append-intent file
  • os.fdopen failure closes the raw fd (checkpoint + seal)
  • Windows open_osfhandle OSError → CheckpointIntegrityError / CapsuleVerificationError
  • Non-ASCII bundle HEAD → CapsuleVerificationError
  • Quoted-path octal escape > 0o377CapsuleVerificationError
  • verification-digest-unbound is no longer masked by restore-check-blocked
  • Verify dedupes restore-verified; digest hashes canonical JSON after asserting file bytes match that form
  • Duplicate-path wording is plane-neutral

Left for later (called out on #43): frozen input_shas mutation; unbundled refs/stash; tracked plane stores diff+hash, not reconstructable worktree bytes.

Verification

347 passed, 10 skipped

Scope: downloaded packages/preservation_spine suite at 7f27578c plus the new regressions. Windows handle conversion and the Windows parent-fsync no-op were patched from source but not executed (Linux only).

python -m pytest -q packages/preservation_spine/tests

Summary by CodeRabbit

  • Bug Fixes
    • Improved checkpoint recovery and durability, including safer handling of incomplete or unpublished checkpoint files.
    • Verification records are now validated consistently, with accurate verification digests and clearer repeated-verification history.
    • Detects source-file changes during capture and reports them as capture drift.
    • Reports missing verification bindings as independent blockers.
    • Strengthened validation for malformed Git paths and history-bundle data.
    • Improved Windows file-handling error cleanup and reporting.
  • Tests
    • Added regression coverage for checkpoint durability, verification, capture drift, validation, and cross-platform file handling.

@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
floss Ready Ready Preview, v0 Sep 1, 2026 5:13am UTC

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 13bb2d05-f608-4c9e-8696-05cb5a133aa0

📥 Commits

Reviewing files that changed from the base of the PR and between 7f27578 and 48e38f8.

📒 Files selected for processing (13)
  • packages/preservation_spine/checkpoint.py
  • packages/preservation_spine/cli.py
  • packages/preservation_spine/git_capture.py
  • packages/preservation_spine/github_projection.py
  • packages/preservation_spine/manifest.py
  • packages/preservation_spine/restore.py
  • packages/preservation_spine/seal.py
  • packages/preservation_spine/tests/test_checkpoint.py
  • packages/preservation_spine/tests/test_cli.py
  • packages/preservation_spine/tests/test_git_capture.py
  • packages/preservation_spine/tests/test_github_projection.py
  • packages/preservation_spine/tests/test_manifest.py
  • packages/preservation_spine/tests/test_seal_restore.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Checkpoint publication and recovery now synchronize directory metadata and discard unpublished empty genesis files. Verification uses canonical records. Capture, manifest, restore, and Windows file handling now report standardized errors and clean up resources.

Changes

Preservation integrity hardening

Layer / File(s) Summary
Checkpoint publication and durability
packages/preservation_spine/checkpoint.py, packages/preservation_spine/tests/test_checkpoint.py
Checkpoint intents, targets, and removals now use explicit parent-directory fsyncs. Empty genesis files without intents are discarded. Windows descriptor failures close resources and raise CheckpointIntegrityError.
Verification records and checkpoint actions
packages/preservation_spine/cli.py, packages/preservation_spine/github_projection.py, packages/preservation_spine/tests/test_cli.py, packages/preservation_spine/tests/test_github_projection.py
Verification records are validated and canonicalized before digest calculation. Repeated verification removes duplicate restore-verified actions. Unbound verification digests remain visible as blockers.
Capture and capsule-data validation
packages/preservation_spine/git_capture.py, packages/preservation_spine/manifest.py, packages/preservation_spine/restore.py, packages/preservation_spine/tests/test_git_capture.py, packages/preservation_spine/tests/test_manifest.py, packages/preservation_spine/tests/test_seal_restore.py
Filesystem disappearance during capture raises CaptureDrift. Invalid Git path escapes and malformed history-bundle heads raise validation errors. Duplicate manifest paths use a generic error.
Safe Windows file and stream conversion
packages/preservation_spine/seal.py, packages/preservation_spine/tests/test_seal_restore.py
Failed native-handle and descriptor conversions now close resources and raise CapsuleVerificationError. Atomic pending-file creation follows the same cleanup path.

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

Merge Risk: ⚪ Minimal · up to 48e38

The change is merge-ready after normal checks and review; no actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 60 functions across 13 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description explains the purpose, lists the main fixes, states scope limits, and records Linux test results. It does not complete the required Truth status, Prior art and reuse, Docs and registrie… Complete all template sections. Add claim statuses with evidence, the ADR-18 reuse-gate verdict and required probes or an applicability explanation, docs and registry confirmations, blast radius, rollback plan, and explicit confirmation tha…
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly identifies the main changes: classifying preservation-spine races and making checkpoint creation and unlinking durable.
Full details: Description check

Explanation

The description explains the purpose, lists the main fixes, states scope limits, and records Linux test results. It does not complete the required Truth status, Prior art and reuse, Docs and registries, or Risk sections. It also does not confirm the required green-set command and checkbox requirements.

Resolution

Complete all template sections. Add claim statuses with evidence, the ADR-18 reuse-gate verdict and required probes or an applicability explanation, docs and registry confirmations, blast radius, rollback plan, and explicit confirmation that the required green-set command and new regression tests pass. Include the exact pytest summary for the required green set or explain any known limitation.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pr43-unclassified-durability

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 23, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
floss c1e34d9 Aug 24 2026, 12:13 AM

@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: c1e34d903c

ℹ️ 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".

raise CheckpointIntegrityError(
"checkpoint file changed while acquiring append handle"
)
_fsync_parent_directory(path)

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 Write the intent before durably publishing genesis

During the first checkpoint append, this fsync makes the newly created but still-empty checkpoints.jsonl directory entry durable before _append_with_intent creates the recovery intent. A crash in that interval therefore leaves a durable empty file with no intent; subsequent loads fail with checkpoint file is empty, while subsequent appends take the existing-file path and fail the same way, requiring manual deletion. Create and persist the intent before publishing the target, or recover an empty no-intent genesis file safely.

Useful? React with 👍 / 👎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
floss 7edd1c8 Commit Preview URL

Branch Preview URL
Sep 06 2026, 05:44 AM

@kalisam

kalisam commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the Codex P1 in 48e38f8.

Genesis now writes and fsyncs the append intent before publishing the empty checkpoints.jsonl directory entry. A leftover empty file with no intent is treated as unpublished genesis: load raises FileNotFoundError after discarding it, and the next append can create a real chain.

packages/preservation_spine/tests/test_checkpoint.py: 50 passed.

@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: 48e38f8e90

ℹ️ 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".

metadata = _validated_file_state(path)
if metadata.st_size != 0:
return
_remove_empty_checkpoint_file(path)

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 Avoid unlinking a checkpoint after a racy size check

When another process has this empty path open and writes a genesis record concurrently with load or append, this cleanup can delete the newly written checkpoint: _remove_empty_checkpoint_file only checks the size before os.unlink, while the directory lock is cooperative and does not prevent writes through an existing descriptor. A record written between the final size check and unlink becomes unnamed and is lost; the cleanup should retain and lock the file identity through removal or report drift when ownership of the empty file cannot be proven.

Useful? React with 👍 / 👎.

@kalisam kalisam left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Hermes adversarial review — verdict: COMMENT / land WITH #43 (stacked)

Head 48e38f8e, 2 commits / 13 files / +454 −27, strict fast-forward of #43 head 7f27578c (body cites 073c85e6 — wrong SHA, different tree).

Verified repairs of #43 bugs: empty-genesis wedge (intent-before-publish + _discard_unpublished_genesis), capture FileNotFound races → CaptureDrift, fd leak on fdopen/open_osfhandle failure, verification-digest-unbound unmasking, idempotent-verify byte recheck, octal >0o377 and non-ASCII bundle-head classification. Small, aimed, regression-tested. This is the right follow-up.

Still open here:

  1. _remove_empty_checkpoint_file (checkpoint.py:885-904) — size check then os.unlink is racy if any concurrent process can write genesis while holding no lock on this path; confirm _locked_directory scope covers all writers or hold the fd across check+unlink.
  2. Windows durability is a documented no-op (_fsync_parent_directory returns on nt; fsync tests skipped on Windows; CI ubuntu-only) — this workspace runs on Windows. Say "best-effort on NT" in the module docstring or add FlushFileBuffers on a directory handle + a windows-latest test job.
  3. The new dedupe test monkeypatches a PASS restore that production cannot emit (opaque-plane deadlock, see #43 review Critical 1) — green here does not witness the real pipeline.
  4. CaptureDrift/real CheckpointIntegrityError still swallowed by the CLI's dummy exception class (#43 cli.py:116-125) — your classification work is invisible to operators until that import is fixed.

Merge sequence: #43 with its blockers addressed, then this immediately. Do not merge #43 alone.

…reate/unlink durable

Bare FileNotFound during resolve/read aborted capture outside the evidence
hierarchy. Checkpoint names were fsynced but their directory entries were
not, so a crash could drop a committed intent. Windows handle errors and
malformed capsule input (non-ASCII bundle HEAD, octal > 0o377) raised
unclassified OSError/ValueError/UnicodeDecodeError.

Also: fdopen failures now close the raw fd; unbound verification digests
are their own blocker; verify dedupes restore-verified; duplicate-path
errors are plane-neutral.
… empty log

The first append fsynced an empty checkpoints.jsonl directory entry before
the recovery intent existed. A crash in that window left a durable empty
file: load failed with "checkpoint file is empty", and the next append took
the existing-file path and failed the same way.

Write and fsync the intent first, then publish the empty target. A leftover
empty file with no intent is unpublished genesis and is discarded.
…indows CI advisory

- checkpoint.py module docstring: add Windows durability note explaining
  that parent-directory fsync is a no-op on NT (no FlushFileBuffers on
  directory handles); directory-entry durability is best-effort on Windows.
- python-ci.yml: remove job-level continue-on-error from full-suite-advisory
  so a red advisory is VISIBLE (step-level continue-on-error remains so it
  never blocks); the job stays out of required checks.
- python-ci.yml: add preservation-spine-windows-advisory job on
  windows-latest — advisory only, step-level continue-on-error, not a
  required check. Surfaces Windows-specific durability behavior visibly.
- PR59 body: fix base SHA reference from 073c85e to 7f27578.
59-A:

_remove_empty_checkpoint_file now opens the target descriptor, verifies
regular-file type and zero length with os.fstat, closes the descriptor,
and unlinks while its callers hold _locked_directory(parent).

Both call sites are already under the directory lock, preventing a
concurrent checkpoint append between the size check and unlink.

Focused verification: 48 passed, 2 skipped.
@kalisam

kalisam commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

Hermes Review — Post-Fix Round

Verdict: Comment (fixes landed, rebased onto PR43 tip)

Fixed this round

  • 59-B — Windows durability honesty (docstring + CI advisory) + PR body base SHA corrected
  • 59-A 7edd1c8 — Unlink TOCTOU closed: os.openos.fstatos.unlink under directory lock

Verification

  • Focused checkpoint tests: 48 passed, 2 skipped
  • Post-rebase full suite: 131 passed, 2 skipped

Note

Branch was force-pushed after rebasing onto the updated PR43 tip (4daefa0). The base SHA in this PR body now reflects the current PR43 head.


Hermes Agent — FLOSSI0ULLK

@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: 7edd1c8ce8

ℹ️ 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".

Comment on lines 896 to +899
metadata = _validated_file_state(path)
if metadata.st_size != 0:
raise CheckpointIntegrityError(
"checkpoint recovery refused to remove a non-empty file"
)
return
_remove_empty_checkpoint_file(path)

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 Preserve empty committed logs as integrity failures

When an already committed checkpoints.jsonl is accidentally truncated to zero while no append is active, the normal absence of an intent causes this branch to classify it as unpublished and unlink it. append_checkpoint can then accept a new sequence-0 genesis, silently resetting the checkpoint chain; before this change, the empty file produced an integrity error. A zero-length file without an intent is insufficient evidence that no committed chain previously existed, so recovery needs a durable creation marker or must continue failing closed.

Useful? React with 👍 / 👎.

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