Skip to main content

max / makenotwork

Clear legacy project covers that no URL rebuild can repair Migration 152 derived cover_s3_key by stripping scheme and host off cover_image_url, assuming the canonical {cdn_base}/{s3_key} form. Rows written before CDN_BASE_URL became required config held a path-style presigned URL, so the strip kept the bucket segment and produced keys like makenotwork-files/projects/<id>/image/<file>.png. Those 404 through the CDN, which fronts the public bucket, and the presigned URLs expired the day after they were written. The objects were never promoted to the public bucket under either spelling, so there is nothing to repoint at. Null the cover columns and return cover_scan_status to 'pending' so a re-upload re-enters the scan gate. Scoped by shape, not by id: no current writer can produce a presigned URL in a durable column.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-31 14:34 UTC
Signed with PGP, not checked
Commit: e796dd28abb1f3c24c1d89b0d9953c8c3f886d4b
Parent: 105a300
1 file changed, +42 insertions, -0 deletions
@@ -1,0 +1,42 @@
1 + -- Clear legacy covers that no URL rebuild can repair (audit problem b2046f13,
2 + -- observed in prod 2026-07-29).
3 + --
4 + -- Migration 152 backfilled `cover_s3_key` by stripping scheme and host off
5 + -- `cover_image_url`, on the premise that a stored cover URL is always the
6 + -- canonical `{cdn_base}/{s3_key}` that `build_project_image_url` composes. Rows
7 + -- written before CDN_BASE_URL became required config held a path-style presigned
8 + -- URL instead (`{endpoint}/{bucket}/{key}?X-Amz-...`), so the strip kept the
9 + -- bucket segment and produced keys shaped
10 + -- `makenotwork-files/projects/<id>/image/<file>.png`.
11 + --
12 + -- Both halves of such a row are dead. The URL is a 24-hour presigned link that
13 + -- expired the day after it was written, and rebuilding it against the now-correct
14 + -- CDN base still 404s: cdn.makenot.work fronts the public bucket, so a
15 + -- bucket-prefixed key resolves to `makenotwork-public/makenotwork-files/...`.
16 + -- The objects were never promoted to the public bucket under either spelling, so
17 + -- there is no key that serves them. Clearing the row renders no cover instead of
18 + -- a broken image; the owner re-uploads through the normal presign/confirm path,
19 + -- which mints a staging key, scans it, and writes a canonical URL.
20 + --
21 + -- Scoped by shape rather than by id. A presigned URL in a durable column is the
22 + -- exact artifact, and no current writer can produce one: the only writers of
23 + -- these columns are `build_project_image_url` and the scan worker's promote,
24 + -- which both compose `{cdn_base}/{key}`. `cover_scan_status` returns to
25 + -- 'pending', the no-cover value from migration 162, so a future upload re-enters
26 + -- the scan gate. The size columns are deliberately untouched: they are NULL on
27 + -- the affected prod rows, so nothing was ever counted against storage, and a
28 + -- migration is the wrong place to move a storage counter.
29 +
30 + UPDATE projects
31 + SET cover_image_url = NULL,
32 + cover_s3_key = NULL,
33 + cover_scan_status = 'pending'
34 + WHERE cover_image_url LIKE '%X-Amz-Signature=%';
35 +
36 + -- Migration 152 backfilled items the same way, so the same artifact is possible
37 + -- there. No prod row matches today; this keeps the two tables from drifting.
38 + UPDATE items
39 + SET cover_image_url = NULL,
40 + cover_s3_key = NULL,
41 + cover_scan_status = 'pending'
42 + WHERE cover_image_url LIKE '%X-Amz-Signature=%';