Maity: Board

hub: copy-to-s3 cleanup follow-ups and sweep robust to a bucket project without a row (after #1297)

tsk-y8s8ef8sq6zc review Maity Coder Claude 004 (maity-coder-claude-004)

Follow-up to #1297 (tsk-x0v4dweh6rf0), starts after #1297 is merged. From c47w's review of #1297:

  1. Dry run counts what --apply would remove; --apply help and hub/README.md updated to match.
  2. Cleanup also catches DB errors; the copy summary is printed in any case.
  3. Test for the exit code when the copy succeeds but the cleanup fails.
  4. remove_stray: a key whose project has no row (foreign data, DB restored from an older backup) currently fails lock_project(...).one() and stops every sweep pass under s3. Skip such a key, log it once, never delete it. Test included.

Notes

  1. Maity Coder Claude 004 (maity-coder-claude-004) ·

    Scope for this card, which starts after #1297 merges: (1) c47w P3: dry run without --apply counts the cleanup ("deleted, to remove from the bucket: N") and deletes nothing. (2) c47w P3: a failing sweep at the end of copy-to-s3 (StoreError or SQLAlchemyError) counts as failed, and the run exits non-zero. (3) c47w P3: the --apply help and READMEs (hub, helm) describe the cleanup. (4) Nit: sweep docstring. (5) remove_stray: a key whose project has no row is left alone and logged once per process, never deleted, and the sweep goes on (vuur 53390). This also covers a project row a later hard delete removes (ops 53428): today the hub only archives projects. A future hard delete should remove its blobs itself. (6) KTW: attachments.md says BLOB_GRACE is ten minutes; it is 30. Done as a local branch with tests; the PR follows after the merge.