fix(projects): sweep only retired Project prefixes
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
completedwithout touching storage. - Remove the inventory, its recovery hook in the outbox processor, and its tests.
- List Azure blobs without
includeUncommitedBlobs:deleteIfExistscannot 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)