Skip to content

fix(gridfs): operator-audited stale ownership recovery and crash matrix [WIP] - #13

Merged
rockyshi1993 merged 19 commits into
fix/gridfs-id-ownership-v01from
fix/gridfs-stale-ownership-recovery-v01
Nov 12, 2026
Merged

rockyshi1993 merged 19 commits into
fix/gridfs-id-ownership-v01from
fix/gridfs-stale-ownership-recovery-v01

Conversation

@rockyshi1993

@rockyshi1993 rockyshi1993 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Scope

Follow-up candidate for PR #12; do not merge until reviewed and all checks pass.

  • Add manual dry-run by default recovery utility scripts/maintenance/gridfs-recover.cjs. Requires verified external writer quiescence, exact observed status/updatedAt/ownership token, a durable recovering claim and audit record. Never auto-releases based on elapsed time.
  • Recover interrupted direct upload, delete, cleanup, rename, active append, finalize and abort with visible-file/chunk preservation checks. Fail closed on forged metadata SHA or visible-file/aborting conflicts.
  • Add deterministic 10-scenario crash/consistency regression test and include it in Node suite, server-driver matrix and MongoDB 7.0.37/8.0.26 GridFS workflow.
  • Document coordinated old-writer drain, legacy TTL migration and rollback procedures in English and Chinese.

Merge gate

  • Latest HEAD build/lint/type checks
  • Deterministic recovery tests on MongoDB 7.0.37 and 8.0.26
  • Node 18/20/22 + applicable Driver 6/7 matrix and Release Preflight
  • Independent security review that manually asserted quiescence is adequate and no visible-file chunk deletion is possible
  • Merge this into fix/gridfs-id-ownership-v01 only after it passes, then rerun the parent PR's checks on the new HEAD. No direct main edits, no tag/npm publish.

Note

High Risk
Changes GridFS ownership release rules and adds a maintenance tool that can delete chunks and complete deletes; misuse or unquiesced writers could cause data loss despite safeguards.

Overview
Introduces an operator-only maintenance path for stranded GridFS ownership rows in <bucket>.uploads, plus CI and docs to gate its use.

A new scripts/maintenance/gridfs-recover.cjs utility defaults to dry-run, requires exact status / updatedAt / token match and an explicit --quiesced apply, writes ownership_recovery_audit, and holds a durable recovering claim while it reconciles interrupted direct upload, delete, cleanup, rename, append, finalize, and abort states—with visible-file chunk checks and fail-closed behavior on SHA conflicts or ambiguous cases.

releaseOwner now deletes only when kind, ownerToken, and expected status all match, so a delayed original writer cannot drop an active recovery fence.

English and Chinese runbooks (gridfs-recovery.md) document drain/migration/rollback steps; gridfs.md links to them. A large gridfs-stale-recovery.test.ts matrix is wired into npm test, the memory-server matrix, and GridFS GitHub workflows, which also gain concurrency cancellation, timeouts, and triggers for fix/gridfs-id-ownership-v01.

Reviewed by Cursor Bugbot for commit 460b958. Bugbot is set up for automated code reviews on this repo. Configure here.

@rockyshi1993
rockyshi1993 marked this pull request as ready for review November 12, 2026 05:05
@rockyshi1993
rockyshi1993 merged commit 32cfee1 into fix/gridfs-id-ownership-v01 Nov 12, 2026
3 of 9 checks passed

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 460b958. Configure here.

await chunks.deleteMany({ files_id: id });
} else if ((sourceStatus === 'uploading' || sourceStatus === 'cleaning' ||
sourceStatus === 'aborting') && !visible) {
await chunks.deleteMany({ files_id: id });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale visibility drives chunk deletion

High Severity

Recovery records whether a files document exists before the durable recovering claim, then later runs chunks.deleteMany from that stale !visible flag for uploading, cleaning, and aborting. A file that becomes visible in that window still loses its chunks, violating the visible-file preservation invariant.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 460b958. Configure here.

async function verifyVisibleFile(db, bucket, id, session) {
const files = db.collection(bucket + '.files');
const doc = await files.findOne({ _id: id });
if (!doc) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing file treated as verified

Medium Severity

verifyVisibleFile returns false when the files document is gone instead of failing closed. The finalizing, uploading, and renaming callers ignore that result and still delete the ownership row, so a file that disappeared after the earlier visibility snapshot is treated as successfully verified.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 460b958. Configure here.

// Recovery intentionally retains the original kind/ownerToken while
// changing status to recovering. A late original writer must not be
// able to remove that new recovery fence with its stale ownerToken.
if (released.deletedCount !== 1) throw uploadBusy(operation, ctx, owner.id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cleanup deletes chunks past recovery

High Severity

The new status match in releaseOwner only stops a late writer from deleting a recovering row. cleanupFailedDirect still deleteManys chunks by files_id before that check, so a late failed upload can wipe bytes after recovery has fenced the owner or already released the id for reuse.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 460b958. Configure here.

@rockyshi1993
rockyshi1993 deleted the fix/gridfs-stale-ownership-recovery-v01 branch November 12, 2026 05:32
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