Skip to content

fix: Orphan sweep API deletes product source files#25

Open
jonathanchang31 wants to merge 1 commit into
aglover1221:mainfrom
jonathanchang31:fix/Orphan-sweep-api-delete
Open

fix: Orphan sweep API deletes product source files#25
jonathanchang31 wants to merge 1 commit into
aglover1221:mainfrom
jonathanchang31:fix/Orphan-sweep-api-delete

Conversation

@jonathanchang31
Copy link
Copy Markdown

Summary

POST /api/pipeline/orphans could delete product source files when sources.yaml was missing. The orphan sweep treated a missing manifest as an empty allowlist, classified every top-level file in source/ as orphaned, and deleted them by default.

This change makes missing manifests non-destructive, makes POST dry-run by default, and requires explicit confirmation before any orphan deletion can occur.

Related Issue

Fixed: #24

Change Type

  • Security fix
  • Bug fix
  • Data-loss prevention
  • API behavior hardening
  • Regression tests
  • New feature
  • UI change
  • Documentation only
  • Refactor only

What Changed

  • listOrphans() now reports:

    • manifestPath
    • manifestMissing
  • If sources.yaml is missing:

    • GET /api/pipeline/orphans returns manifestMissing: true
    • no files are returned as deletable orphans
    • deletion is refused
  • POST /api/pipeline/orphans is now dry-run by default.

  • Real deletion now requires:

{
  "productSlug": "r770",
  "dryRun": false,
  "confirmDelete": true
}
  • Even with confirmation, deletion is blocked with 409 Conflict if sources.yaml is missing.

  • Added regression tests covering:

    • missing sources.yaml does not classify files as orphans
    • missing sources.yaml refuses deletion
    • default POST is dry-run
    • deletion requires explicit confirmation
    • manifest-listed files are preserved
    • only confirmed orphan files are deleted

Real Behavior Proof

Before Fix

With sources.yaml missing:

sources.yaml before: missing
file before: present

A normal POST deleted the source file:

curl -sS -i -X POST http://localhost:3210/api/pipeline/orphans \
  -H 'Content-Type: application/json' \
  --data '{"productSlug":"r770"}'

Result:

HTTP/1.1 200 OK

{"productSlug":"r770","deleted":["orphan-delete-proof-before-fix.txt"],"errors":[]}
file after: missing

After Fix

With sources.yaml still missing:

sources.yaml before: missing
file before: present

GET now reports the missing manifest and no deletable orphans:

{
  "productSlug": "r770",
  "manifestMissing": true,
  "manifestFilenames": [],
  "orphans": []
}

Default POST is safe and does not delete:

HTTP/1.1 200 OK

{
  "productSlug": "r770",
  "dryRun": true,
  "manifestMissing": true,
  "requiresConfirmation": false,
  "refusedReason": "Refusing to delete orphans because sources.yaml is missing",
  "deleted": [],
  "errors": []
}

file after default POST: present

Even explicit confirmed delete is refused when manifest is missing:

HTTP/1.1 409 Conflict

{
  "productSlug": "r770",
  "dryRun": false,
  "manifestMissing": true,
  "requiresConfirmation": false,
  "refusedReason": "Refusing to delete orphans because sources.yaml is missing",
  "deleted": [],
  "errors": []
}

file after confirmed missing-manifest POST: present

Validation Performed

npm run migrate:status

Result:

applied: 2
✓ 0001_init
✓ 0002_durable_jobs
up to date.
npm test

Result:

Test Files  5 passed (5)
Tests       40 passed (40)
npm run build

Result:

✓ Compiled successfully
✓ Generating static pages (26/26)
npm run dev

Result:

Local: http://localhost:3210
✓ Ready

Runtime route checks:

GET /              200 OK
GET /products/r770 200 OK

Security / Business Impact

This prevents accidental or unauthenticated deletion of product source files.

Risk before fix:

  • A missing sources.yaml caused all top-level files in source/ to be treated as orphans.
  • A plain POST deleted files immediately.
  • Raw source evidence could be lost before parsing, extraction, audit, or verification.
  • Destructive behavior was the default.

Risk after fix:

  • Missing manifests are non-destructive.
  • Default POST is dry-run.
  • Deletion requires explicit confirmation.
  • Confirmed deletion is still blocked if the manifest is missing.

Checklist

  • Reproduced the original issue locally
  • Confirmed dependencies were already installed
  • Avoided reinstalling existing environment
  • Added missing-manifest safety handling
  • Made POST dry-run by default
  • Required explicit confirmation for destructive deletion
  • Blocked deletion when sources.yaml is missing
  • Added regression tests
  • Ran unit tests successfully
  • Ran production build successfully
  • Started the app locally
  • Verified main app route works
  • Verified product page works
  • Re-ran original deletion flow and confirmed file is preserved
  • Cleaned up controlled proof file

@jonathanchang31
Copy link
Copy Markdown
Author

@aglover1221 Could you plz review my PR?

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.

[Bug] Orphan sweep API deletes product source files when sources.yaml is missing

1 participant