You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
jaehyuk.hyun
committed
docs(media-flip): record the staging flip, and drop two invented issue numbers
Staging is done, so the section that said "NOT run from here" is now the
record of what was actually run. Open question #3 — which of R2's two
public surfaces staging had on — is answered: no r2.dev URL, one custom
domain, disabled rather than removed so rollback stays a one-field PUT
instead of a DNS change.
The before/after is a real object, not a 404 probe. A missing key answers
404 whether the bucket is open or shut, so the first probe proved nothing;
the measurement that counts is 200 with 12856 bytes becoming 401.
Also written down, because both nearly undid the flip and neither is
obvious from the code:
- R2_PUBLIC_URL has to be RELATIVE. The absolute form reaches the gate and
looks right while re-creating exactly the rows the cleanup deleted. The
warning was already in this file and got read too late.
- Editing Cloud Run alone does not survive a deploy: deploy.yml uses
--set-env-vars and sources the value from a GitHub secret, so the next
deploy silently reverts it. And the secret is the _NEW-suffixed one —
the unsuffixed name belongs to the old project.
The order staging used (truncate, flip, then fix the URL) was wrong and
survived only because the database was empty. That is stated plainly so
production does not inherit it.
The #83 / #81 references throughout were never real — the repo has only
#4 and #5. They are replaced with the checklist items that actually block.
Request it twice. First request from a cold edge node is typically `MISS` or `EXPIRED`;
347
414
the second (from the same edge PoP) should show `HIT`. `DYNAMIC` or `BYPASS` on both means
348
-
nothing is being cached at all — the state #81 needs to fix.
415
+
nothing is being cached at all — the state the edge cache rule needs to fix.
349
416
350
417
**The check that must not be got wrong — private objects must NEVER show `HIT`:**
351
418
@@ -360,33 +427,33 @@ The M-5 route already sets `Cache-Control: private` for exactly these objects
360
427
(`src/app/api/media/[...key]/route.ts`), and Cloudflare's documented default honors that —
361
428
but a Cache Rule can override it (an "Eligible for cache: All" + "Edge TTL: Override
362
429
origin" rule ignores the origin's own `Cache-Control` header entirely). **This has to be
363
-
re-verified against whatever Cache Rule #81 actually ships**, not assumed safe because the
430
+
re-verified against whatever Cache Rule item 4 actually ships**, not assumed safe because the
364
431
default was safe before a rule existed.
365
432
366
433
**Status: UNKNOWN FROM HERE for live edge behavior** (no Cloudflare in front of
367
434
`localhost` — this cannot be exercised from local dev at all, by construction). **VERIFIED
368
435
FROM READING that no rule exists yet** — which makes the "private is never cached" property
369
436
trivially true today (nothing is cached, so nothing can leak) and the "public is cached"
370
-
property false (task not started). Both need re-checking once #81 ships a real rule —
437
+
property false (task not started). Both need re-checking once item 4 ships a real rule —
371
438
passing the first check the day the rule is deployed does not mean it stays true after the
372
439
next edit to that rule.
373
440
374
441
## 4. Rate limiting
375
442
376
-
This is the EDGE rate limit, and it is #81/M-7 — noted as a dependency here, not duplicated.
443
+
This is the EDGE rate limit, and it is item 4 — noted as a dependency here, not duplicated.
377
444
378
445
**Correction to an earlier draft of this doc**, which said "no rate-limit code exists on
379
446
`/api/media` today (verified by grep)". That was true when written and is now false:
380
447
`722436e` ("feat(media): cap how fast one caller can pull images") added
381
448
`src/lib/mediaRateLimit.ts`, and the route calls it —
382
449
`checkMediaReadRateLimit(identity)` → 429 with `Retry-After`
383
450
(`src/app/api/media/[...key]/route.ts:236`). So the APPLICATION-level cap now exists; what
384
-
remains for #81 is the edge-level one, in front of the origin, which is a different layer
451
+
remains for item 4 is the edge-level one, in front of the origin, which is a different layer
385
452
solving a different half of the problem (an edge limit spares the origin the request
386
453
entirely; the in-app limit still costs a Cloud Run invocation to answer 429).
387
454
388
455
Item 4's staging/production verdicts below are unchanged by this — staging is N/A by the
389
-
product decision, and production is blocked on #83 → #81/M-7 regardless.
456
+
product decision, and production is blocked on the item 3/4 edge work regardless.
390
457
391
458
**What breaks without it, once private:** every image load that isn't an edge-cache `HIT`
392
459
(item 3) reaches the app and runs the M-5 gate's DB query (topic visibility / membership
@@ -429,8 +496,8 @@ depended on bucket access either way, flip or no flip).
429
496
|---|---|---|---|---|
430
497
| 1 | No absolute media URLs stored |**Satisfied** — 54 + 30 found (84 posts), deleted, re-query returns 0 | Unknown from here — run the script's dry run | Unknown from here — run the script's dry run |
431
498
| 2 | Storage layer itself denies anonymous reads |**Satisfied** — MinIO now `private` by default; anonymous direct fetch 200 → 403, gate still 200, verified live | Not done — run the wrangler commands in item 2 | Not done, and blocked on 3/4 anyway |
432
-
| 3 | CDN cache exists, public cacheable, private never cached |**N/A** — no Cloudflare in front of localhost |**N/A by decision** — edge layer is production-only (see note below) | Unsatisfied — blocked on #83 -> #81/M-7|
433
-
| 4 | Rate limit in front of `/api/media`| In-app cap **exists** (`722436e`); edge cap N/A — no edge in front of localhost |**N/A by decision** — edge layer is production-only (see note below) | Unsatisfied — blocked on #83 -> #81/M-7|
499
+
| 3 | CDN cache exists, public cacheable, private never cached |**N/A** — no Cloudflare in front of localhost |**N/A by decision** — edge layer is production-only (see note below) | Unsatisfied — blocked on items 3/4|
500
+
| 4 | Rate limit in front of `/api/media`| In-app cap **exists** (`722436e`); edge cap N/A — no edge in front of localhost |**N/A by decision** — edge layer is production-only (see note below) | Unsatisfied — blocked on items 3/4|
434
501
| 5 | Rollback path known and its cost is understood |**Documented above** — cheap, not instant for real R2 | Same mechanism, unverified access to actually exercise it from here | Same mechanism, unverified access to actually exercise it from here |
435
502
436
503
### The edge layer is PRODUCTION-ONLY, by an explicit product decision
@@ -442,7 +509,7 @@ in front of production is precisely what makes it possible to attach these there
442
509
there.
443
510
444
511
Read the table accordingly: **staging's bucket flip does not wait on 3 or 4.** Only the
445
-
production flip is blocked, on #83 (production GCLB) -> #81/M-7 (CDN). Do not re-report
512
+
production flip is blocked, on the production GCLB and the CDN cache rule (items 3/4). Do not re-report
446
513
staging as "unsatisfied" on these two rows; that reading has cost the project the same
0 commit comments