Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-279Done

5.2.7 Orphan-GC job — scheduled sweep of unlinked attachment rows past the safety window (blob + row), the blob-failure backstop

Estimate: 24m · Depends on: 5.2.1

The lifecycle backstop. Unlinked rows accumulate by design: create-modal uploads whose modal was cancelled, embeds removed from bodies (5.2.3 unlinks), issue deletions (5.2.1 SetNull), and best-effort blob deletes that failed (5.2.2). A real product does not leak storage forever (the stub's "workspace-scoped access" + finding #57 discipline applied to storage).

Job (lib/jobs/definitions/attachmentGc.ts, the 1.6 defineJob harness on a cron schedule like dailyHealthCheck): page through listOrphans({ olderThan: 7 days }) (the safety window — long enough that an in-flight create/edit never loses its upload; constant, admin-configurable is Epic-8), and per row delete the blob then the row (blob first — if the blob delete fails the row survives for the next pass; the inverse strands the blob unfindably). Cursor-bounded batches per run (e.g. 200) so a backlog never produces an unbounded run; idempotent (a re-run after partial failure converges); per-run summary logged through the JobRun ledger. NOT swept: linked rows, and orphans younger than the window. (Blobs with NO row — e.g. workspace-cascade deletions — are out of this job's reach; recorded as the known Epic-8 hardening extension: a prefix-listing sweep against the blob store.)

Acceptance criteria

  • The scheduled job exists on the 1.6 harness (cron, workspace-nullable system scope), sweeps unlinked rows older than the window in bounded cursor batches, deleting blob-then-row; younger orphans and linked rows are never touched.
  • A failed blob delete leaves the row for the next pass (verified via a stubbed blob error); re-runs are idempotent; the JobRun ledger records each run's summary (scanned/deleted counts).
  • The 5.2.2 post-commit blob-failure case is demonstrably swept by the next GC pass (the backstop contract).
  • @inngest/test coverage of the batch/window/failure paths; pnpm test:coverage holds the gate; the known no-row-blob limitation is documented in the job header + design-notes.

Context refs

  • lib/jobs/defineJob.ts + definitions/dailyHealthCheck.ts (1.6 — the scheduled system-job exemplar) + the JobRun/DLQ ledger
  • lib/blob/uploader.ts (the blob delete API) + attachmentRepository.listOrphans (5.2.1)
  • The lifecycle decisions (SetNull, best-effort-after-commit, 7-day window) in the Story 5.2 description