Native public media routes
API2 natively serves the unauthenticated Rails compatibility routes
/rails/active_storage/blobs/redirect/:signed_id/*filename and
/conversation_images/:signed_id for both GET and HEAD. Valid Rails
Active Storage signed IDs are looked up in active_storage_blobs; only the
cloudflare service is served natively.
Contract
Redirect requests return a short-lived, path-style R2 presigned URL with Rails
content-disposition and content-type overrides. The URL tail is not used as
the filename. Conversation images stream the R2 object without buffering and
derive their MIME type from the sanitized filename extension. HEAD performs
only a HeadObject request.
Deviations
- Missing R2 objects return an empty
404rather than Rails’ storage error. - Conversation image responses intentionally omit
ETag: generating Rails’ Rack ETag would require hashing the complete body, which would defeat streaming. - Filename transliteration is a JavaScript approximation of
I18n.transliterate(NFKD, combining-mark removal, and?replacement). - Presigned URLs use the R2 path-style host; this is expected for the S3 compatible endpoint.
Rollback
Flipper’s pause_api2_public_media_routes is a regional kill switch with safe
state off. When enabled, API2 forwards to the legacy Rails origin if the
fallback is enabled; otherwise it returns 404. Blobs using an unsupported
service follow the same fallback path. Flipper lookup failures continue
natively and emit a flipper_error telemetry outcome.
Production rollout plan
- Deploy with
pause_api2_public_media_routesoff in both regions. - In each region (US and EU), fetch a known-good redirect URL and a
conversation_imagesURL with bothcurl -sIandcurl -s -o /dev/null -w '%{http_code}'. Expect the redirect to return302, aLocationon the R2 host, andCache-Control: max-age=300, private. Expect the conversation image to return200,Content-Disposition: inline, andContent-Lengthfor both GET and HEAD. Also request a tampered signed ID and expect404. - Watch
native_public_medialogs per region for outcome distribution (served/redirectedversus 404s and 5xx responses), comparing against the baseline of approximately 2,340 redirect and 832conversation_imagesevents per nine hours. Confirm thelegacy_rails_gatewaylog for these paths drops to zero. - Roll back immediately in an affected region by enabling
pause_api2_public_media_routes; requests fall back to Rails whileLEGACY_RAILS_FALLBACK_ENABLEDis true. A code rollback reverts the PR.
Local checks
cd rulebase-web/rulebase-api2
pnpm vitest run src/routes/public-media src/lib/rails-active-storage-serving src/lib/rails-signed-id src/lib/legacy-rails-gateway
pnpm typecheck