fix(cleanup): keep shared chat files, sweep inline images by prefix, and make room refetches wait

Merged
WA
1d ago
+1230-404

Summary

Fixes from the v0.9.37 release review that apply to #9419 and #9427.

  • Room deliveries wait for every room's refetch: RoomChangedCallback is now () => Promise<unknown>, so a callback that drops its invalidation no longer type-checks; the tables and workflows rooms return the invalidations they already made, so a burst no longer cancels and restarts their in-flight list refetches
  • Deployed-state hint follows the active version: only loadDeployedWorkflowState sets the hint, to the active version id, on every successful read; by-id reads of other versions no longer repoint it, so active reads stop re-fetching the state blob after an older version executes
  • Chat files another chat still references are kept: forks share message attachment keys (organization and message-only copilot attachments are never copied, and a failed blob copy keeps the source key), so cleanup now checks every collected chat-scoped key against the surviving chats' attachments in its scope, one query per scope, before deleting it
  • Inline images are swept by their chat prefix: they have no row, so a transcript read could miss ones written later; a dedicated outbox event sweeps chat-images/<chatId>/ for every chat whose row is gone, paged, concurrent and resumable under the outbox deadline, on every delete path (hard delete, account and admin removal, retention jobs)
  • Malformed attachment keys are skipped instead of throwing out of the purge
  • Events queued before this change are still accepted (legacy organizationId refs and inline-image keys), with the legacy paths marked for removal once they drain
  • sweepStoragePrefix moves into lib/uploads/core/prefix-sweep.ts beside the listing it pages

Type of Change

  • Bug fix

Testing

  • Regression tests for each fix, each failing on the previous code: tables room refetch not restarted by a second broadcast; deployed-state hint after a by-id read and after a rollback to a cached version; fork-referenced upload kept, unreferenced one deleted; inline images written after the cleanup read are purged while a surviving chat's prefix is untouched; numeric attachment key skipped; image sweep checkpoint resumes from the lowest unfinished chat; an id no image key can hold is skipped instead of failing the event
  • Unit: 361 files across cleanup, mothership chat, uploads, project files, workflow persistence, outbox, auth and the workspace app; integration: 17 files against a fresh database and Redis
  • bun run lint, bun run check:audits (61), bun run type-check, check:test-types

Checklist

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