fix(projects): sweep only retired Project prefixes

Merged
WA
15d ago
+28-454

Summary

Follow-up to #8609. The Project storage inventory (project-file.storage.reconcile) listed the project/ prefix in the bucket and deleted every project/<id>/ whose id was not a row in this database. The database is not the authority for the bucket: after a point-in-time restore the next sweep would permanently delete every Project created after the restore point, and two deployments sharing a bucket would delete each other's Project files. It was armed in production by account deletion and revived on every outbox tick.

  • Stop arming the inventory; retired prefixes keep both per-Project sweeps (immediate, and after upload TTL + skew) and late provider responses still re-queue cleanup.
  • Keep the event registered as a no-op so rows already armed drain to completed without touching storage.
  • Remove the inventory, its recovery hook in the outbox processor, and its tests.
  • List Azure blobs without includeUncommitedBlobs: deleteIfExists cannot delete uncommitted-only blobs, so a page of them made a prefix sweep loop until Azure GC.
  • Drop the "retirement reconciliation will retry" claim from the upload cleanup log.

Residual: a background write that lands under project/<id>/ after the delayed sweep now orphans bytes instead of being found by the inventory. Covering that safely needs an ownership-safe signal (a tombstone table), not a bucket-vs-database diff.

Type of Change

  • Bug fix

Testing

private-purge.integration.ts: retirement batches no longer arm the inventory, and an already-armed row completes while an unowned project/<id>/ object survives (fails on the old code, which swept it). Full validation in CI.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)