Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Preparing Ring content
Preparing Ring content
Preparing Ring content
Use Founder / Developer tabs in the docs sidebar to filter this page. Pair with Ring CDN for public delivery of uploaded objects. Ring File Cabinet is a first-class file() consumer (getCabinetStorageConfig(), gallery derivativesProfile).
RingFileBase is Ring’s self-hosted object-storage plane: an Express API (ring-filebase-api) that accepts authenticated multipart uploads and writes to S3-compatible storage (MinIO or Ceph RGW). Ring apps never talk to MinIO from the browser — they call file().upload(...), which selects RingBaseAdapter when the storage provider is ring_filebase.
| Concern | RingFileBase (this page) | Ring CDN |
|---|---|---|
| Role | Authenticated write API | Public read edge |
| Prod URL | In-cluster http://ring-filebase-api.ring-filebase.svc.cluster.local | https://cdn.<your-domain> |
| Dev / CI URL | Public https://filebase-api.ring-platform.org (k3s-or primary only) | Same CDN (multi-A edges; master push to slave ingest) |
| Client | Server-side file() / Server Actions | Browsers, <img>, OG tags |
| Auth | Bearer token (RINGBASE_API_TOKEN) | None (GET /files/...) |
Authority: writes always go to k3s-or (Oregon). The API then pushes objects to slave MinIO ingest endpoints (Finland / Ukraine). Slaves never pull from master; pull CronJobs are disabled.
Resolution order in lib/storage/storage-config.ts (mirrors db()):
NEXT_PUBLIC_STORAGE_PROVIDER or STORAGE_PROVIDERring-config.json → storage.providerlocal_storage (development) / vercel_blob (production)| Provider value | Adapter | When to use |
|---|---|---|
local_storage | LocalStorageAdapter | Local npm run dev, PVC-backed uploads |
vercel_blob | VercelAdapter | Vercel-hosted clones with BLOB_READ_WRITE_TOKEN |
ring_filebase | RingBaseAdapter | Self-hosted / Ringdom k8s with RingFileBase API |
firebase_storage | Falls back to local in selector | Not a first-class upload path today |
Product images, KYC scans, chat attachments, and ImageConductor outputs all need a durable home. Vercel Blob is fine for small OSS demos; a marketplace clone that owns its data usually wants RingFileBase so media stays on your cluster, behind your CDN hostname, without per-GB Blob invoices.
Keep storage.provider / env on local_storage — files land under public/uploads (or your PVC mount).
Set ring_filebase, point RINGBASE_API_URL at the in-cluster API, publish URLs via Ring CDN (see Related below).
Member uploads call with cabinet MIME/25MB limits; public gallery items reuse CDN .
Next-step: public /files/... edge, host→bucket map, and geo delivery for uploaded objects.
Same-workflow: member /file-cabinet uploads via file() + getCabinetStorageConfig; gallery uses derivativesProfile gallery; subscriber+ /profile/shared is ACL-only (no own uploads).
Depends-on: storage env block (RINGBASE_*) and generative conductor prerequisites.
See-also: object store is a separate backup asset from Postgres.
Use Founder / Developer tabs in the docs sidebar to filter this page. Pair with Ring CDN for public delivery of uploaded objects. Ring File Cabinet is a first-class file() consumer (getCabinetStorageConfig(), gallery derivativesProfile).
RingFileBase is Ring’s self-hosted object-storage plane: an Express API (ring-filebase-api) that accepts authenticated multipart uploads and writes to S3-compatible storage (MinIO or Ceph RGW). Ring apps never talk to MinIO from the browser — they call file().upload(...), which selects RingBaseAdapter when the storage provider is ring_filebase.
| Concern | RingFileBase (this page) | Ring CDN |
|---|---|---|
| Role | Authenticated write API | Public read edge |
| Prod URL | In-cluster http://ring-filebase-api.ring-filebase.svc.cluster.local | https://cdn.<your-domain> |
| Dev / CI URL | Public https://filebase-api.ring-platform.org (k3s-or primary only) | Same CDN (multi-A edges; master push to slave ingest) |
| Client | Server-side file() / Server Actions | Browsers, <img>, OG tags |
| Auth | Bearer token (RINGBASE_API_TOKEN) | None (GET /files/...) |
Authority: writes always go to k3s-or (Oregon). The API then pushes objects to slave MinIO ingest endpoints (Finland / Ukraine). Slaves never pull from master; pull CronJobs are disabled.
Resolution order in lib/storage/storage-config.ts (mirrors db()):
NEXT_PUBLIC_STORAGE_PROVIDER or STORAGE_PROVIDERring-config.json → storage.providerlocal_storage (development) / vercel_blob (production)| Provider value | Adapter | When to use |
|---|---|---|
local_storage | LocalStorageAdapter | Local npm run dev, PVC-backed uploads |
vercel_blob | VercelAdapter | Vercel-hosted clones with BLOB_READ_WRITE_TOKEN |
ring_filebase | RingBaseAdapter | Self-hosted / Ringdom k8s with RingFileBase API |
firebase_storage | Falls back to local in selector | Not a first-class upload path today |
Product images, KYC scans, chat attachments, and ImageConductor outputs all need a durable home. Vercel Blob is fine for small OSS demos; a marketplace clone that owns its data usually wants RingFileBase so media stays on your cluster, behind your CDN hostname, without per-GB Blob invoices.
Keep storage.provider / env on local_storage — files land under public/uploads (or your PVC mount).
Set ring_filebase, point RINGBASE_API_URL at the in-cluster API, publish URLs via Ring CDN (see Related below).
Member uploads call with cabinet MIME/25MB limits; public gallery items reuse CDN .
Next-step: public /files/... edge, host→bucket map, and geo delivery for uploaded objects.
Same-workflow: member /file-cabinet uploads via file() + getCabinetStorageConfig; gallery uses derivativesProfile gallery; subscriber+ /profile/shared is ACL-only (no own uploads).
Depends-on: storage env block (RINGBASE_*) and generative conductor prerequisites.
See-also: object store is a separate backup asset from Postgres.
Use Founder / Developer tabs in the docs sidebar to filter this page. Pair with Ring CDN for public delivery of uploaded objects. Ring File Cabinet is a first-class file() consumer (getCabinetStorageConfig(), gallery derivativesProfile).
RingFileBase is Ring’s self-hosted object-storage plane: an Express API (ring-filebase-api) that accepts authenticated multipart uploads and writes to S3-compatible storage (MinIO or Ceph RGW). Ring apps never talk to MinIO from the browser — they call file().upload(...), which selects RingBaseAdapter when the storage provider is ring_filebase.
| Concern | RingFileBase (this page) | Ring CDN |
|---|---|---|
| Role | Authenticated write API | Public read edge |
| Prod URL | In-cluster http://ring-filebase-api.ring-filebase.svc.cluster.local | https://cdn.<your-domain> |
| Dev / CI URL | Public https://filebase-api.ring-platform.org (k3s-or primary only) | Same CDN (multi-A edges; master push to slave ingest) |
| Client | Server-side file() / Server Actions | Browsers, <img>, OG tags |
| Auth | Bearer token (RINGBASE_API_TOKEN) | None (GET /files/...) |
Authority: writes always go to k3s-or (Oregon). The API then pushes objects to slave MinIO ingest endpoints (Finland / Ukraine). Slaves never pull from master; pull CronJobs are disabled.
Resolution order in lib/storage/storage-config.ts (mirrors db()):
NEXT_PUBLIC_STORAGE_PROVIDER or STORAGE_PROVIDERring-config.json → storage.providerlocal_storage (development) / vercel_blob (production)| Provider value | Adapter | When to use |
|---|---|---|
local_storage | LocalStorageAdapter | Local npm run dev, PVC-backed uploads |
vercel_blob | VercelAdapter | Vercel-hosted clones with BLOB_READ_WRITE_TOKEN |
ring_filebase | RingBaseAdapter | Self-hosted / Ringdom k8s with RingFileBase API |
firebase_storage | Falls back to local in selector | Not a first-class upload path today |
Product images, KYC scans, chat attachments, and ImageConductor outputs all need a durable home. Vercel Blob is fine for small OSS demos; a marketplace clone that owns its data usually wants RingFileBase so media stays on your cluster, behind your CDN hostname, without per-GB Blob invoices.
Keep storage.provider / env on local_storage — files land under public/uploads (or your PVC mount).
Set ring_filebase, point RINGBASE_API_URL at the in-cluster API, publish URLs via Ring CDN (see Related below).
Member uploads call with cabinet MIME/25MB limits; public gallery items reuse CDN .
Next-step: public /files/... edge, host→bucket map, and geo delivery for uploaded objects.
Same-workflow: member /file-cabinet uploads via file() + getCabinetStorageConfig; gallery uses derivativesProfile gallery; subscriber+ /profile/shared is ACL-only (no own uploads).
Depends-on: storage env block (RINGBASE_*) and generative conductor prerequisites.
See-also: object store is a separate backup asset from Postgres.
file()/files/{uuid}api. hostnames often route to the Ring app ingress. Uploads must hit ring-filebase-api (in-cluster) or https://filebase-api.ring-platform.org (public write on primary) — never the Next.js deployment. Wrong URL surfaces as HTML or HTTP 500 instead of JSON success/fileId/url.
lib/uploads/server/upload-core.tsUnified upload used by POST /api/uploads |
app/api/uploads/route.ts | Browser/form upload entry |
infrastructure/ring-file-base/services/ring-filebase-api/ | API source (derivatives + propagate) |
infrastructure/ring-file-base/k8s/ring-filebase/10-api-deployment.yaml | Deploy template |
infrastructure/k3s-or/ring-filebase/60-propagate-slaves-configmap.yaml | Slave ingest registry |
| Variable | Required for ring_filebase | Notes |
|---|---|---|
NEXT_PUBLIC_STORAGE_PROVIDER | Yes | Set to ring_filebase (aliases: ringbase, filebase) |
RINGBASE_API_URL | Yes | Host or host + /api/v1; adapter normalizes to …/api/v1 |
RINGBASE_API_TOKEN | Yes | Bearer for API auth middleware |
RINGBASE_PUBLIC_URL | Recommended | Public base used in returned URLs (usually CDN origin) |
NEXT_PUBLIC_RINGBASE_API_URL | Optional | Fallback if RINGBASE_API_URL unset |
BLOB_READ_WRITE_TOKEN | No | Only for vercel_blob |
NEXT_PUBLIC_LOCAL_STORAGE_URL | No | Only for local_storage |
ring-config.json example:
k8s ConfigMap / Secret pattern (see k8s/secrets.example.yaml):
resolveRingBaseUploadType)The API validates MIME types per type field. The adapter maps:
| Content / access | type sent to API |
|---|---|
access: 'private' | document |
image/* | image (first-class; product is a deprecated alias) |
video/* or audio/* | media |
| Everything else | other |
Optional overrides: ringbaseType, derivativesProfile (none | thumb | gallery | product | news).
| Pattern | Status |
|---|---|
{fileId}_v_{variant} e.g. _v_thumb.webp, _v_blur.webp, _v_sync_thumb.jpg | Current — only scheme the API writes |
{fileId}_v_video_frame_N.jpg / _v_video_frame_N_480.webp | Current — async HEIC/video frames (_480.webp under _v_ is intentional) |
{fileId}_thumb, {fileId}_thumb_v{N}, {fileId}_thumb_v{N}_480w.webp | Banned — never write; purge orphans with infrastructure/ring-file-base/scripts/purge-legacy-derivative-keys.sh |
Prefer upload type=image for rasters (product is a deprecated alias → image). Thumbnail status poll: GET /api/v1/files/{fileId}/thumbnail?type=image.
App wiring (2026-07-15): upload-core and /api/uploads request ladders when storage.provider is ring_filebase (purpose→profile: news→news, product media→product, nft/chat images→gallery, avatar/logos→thumb, docs→none). Responses include fileId + derivatives. Persist as MediaImageAsset on news (featuredImageAsset / gallery) and GalleryItem.derivatives on store/NFT. Display via pickImageSrc / pickGalleryDisplayUrl. Dead Vikka dual-compat (vikka-field-normalize / NEXT_PUBLIC_NEWS_VIKKA_COMPAT) was deleted — camelCase JSONB only.
Sending images as media fails validation (A/V + HEIC only in current API rules).
Dry-run on primary, then FI/UA slaves: ./scripts/purge-legacy-derivative-keys.sh --endpoint https://… --bucket ring-filebase. Add --delete only after reviewing the sample list. Does not match intentional _v_video_frame_*_480.webp or _v_thumb.webp.
Set env and/or ring-config.json storage.provider to ring_filebase. Restart Next.js after env changes.
Apply RingFileBase manifests under infrastructure/ring-file-base/k8s/ring-filebase/ (API Deployment/Service, MinIO namespace, CDN stack). Ensure the API can reach minio-service.ring-filebase-minio.svc.cluster.local:9000 and the target bucket exists.
API auth (services/ring-filebase-api/src/middleware/auth.ts) expects a JWT whose decoded claims include:
iss = kubernetes/serviceaccountkubernetes.io/serviceaccount/namespacekubernetes.io/serviceaccount/service-account.nameToday the middleware decodes claims and does not call TokenReview or enforce exp. Prefer a long-lived claim-shaped token in Secrets — do not paste a one-hour projected ServiceAccount JWT into RINGBASE_API_TOKEN and forget it.
Expect JSON with success: true, fileId, url, and optional derivatives under your CDN host. Within seconds, slave ingest HEAD for the same key should be 200 (master push).
Browser forms should use POST /api/uploads (unified upload core), not call RingFileBase directly.
| Symptom | Likely cause |
|---|---|
| HTML / 500 from upload URL | RINGBASE_API_URL points at Next.js (api. ingress) |
401 Invalid ServiceAccount token | Token missing legacy SA claim shape |
Invalid file type … got: image/png | type=media instead of image / other |
| Upload OK, CDN 404 on slave briefly | Rare race before push; edges must keep proxy_cache_valid 404 0 |
| Upload OK, CDN 403 | MinIO bucket lacks anonymous GetObject (or CDN Host header misconfigured) |
Node stream.isDisturbed on Blob | Prefer local_storage or ring_filebase; Vercel adapter uploads Uint8Array |
Propagate stuck failed | POST /api/v1/replication/redrive on primary (max 3 retries then stop) |
See-also: full integration catalog hub.
file()/files/{uuid}api. hostnames often route to the Ring app ingress. Uploads must hit ring-filebase-api (in-cluster) or https://filebase-api.ring-platform.org (public write on primary) — never the Next.js deployment. Wrong URL surfaces as HTML or HTTP 500 instead of JSON success/fileId/url.
lib/uploads/server/upload-core.tsUnified upload used by POST /api/uploads |
app/api/uploads/route.ts | Browser/form upload entry |
infrastructure/ring-file-base/services/ring-filebase-api/ | API source (derivatives + propagate) |
infrastructure/ring-file-base/k8s/ring-filebase/10-api-deployment.yaml | Deploy template |
infrastructure/k3s-or/ring-filebase/60-propagate-slaves-configmap.yaml | Slave ingest registry |
| Variable | Required for ring_filebase | Notes |
|---|---|---|
NEXT_PUBLIC_STORAGE_PROVIDER | Yes | Set to ring_filebase (aliases: ringbase, filebase) |
RINGBASE_API_URL | Yes | Host or host + /api/v1; adapter normalizes to …/api/v1 |
RINGBASE_API_TOKEN | Yes | Bearer for API auth middleware |
RINGBASE_PUBLIC_URL | Recommended | Public base used in returned URLs (usually CDN origin) |
NEXT_PUBLIC_RINGBASE_API_URL | Optional | Fallback if RINGBASE_API_URL unset |
BLOB_READ_WRITE_TOKEN | No | Only for vercel_blob |
NEXT_PUBLIC_LOCAL_STORAGE_URL | No | Only for local_storage |
ring-config.json example:
k8s ConfigMap / Secret pattern (see k8s/secrets.example.yaml):
resolveRingBaseUploadType)The API validates MIME types per type field. The adapter maps:
| Content / access | type sent to API |
|---|---|
access: 'private' | document |
image/* | image (first-class; product is a deprecated alias) |
video/* or audio/* | media |
| Everything else | other |
Optional overrides: ringbaseType, derivativesProfile (none | thumb | gallery | product | news).
| Pattern | Status |
|---|---|
{fileId}_v_{variant} e.g. _v_thumb.webp, _v_blur.webp, _v_sync_thumb.jpg | Current — only scheme the API writes |
{fileId}_v_video_frame_N.jpg / _v_video_frame_N_480.webp | Current — async HEIC/video frames (_480.webp under _v_ is intentional) |
{fileId}_thumb, {fileId}_thumb_v{N}, {fileId}_thumb_v{N}_480w.webp | Banned — never write; purge orphans with infrastructure/ring-file-base/scripts/purge-legacy-derivative-keys.sh |
Prefer upload type=image for rasters (product is a deprecated alias → image). Thumbnail status poll: GET /api/v1/files/{fileId}/thumbnail?type=image.
App wiring (2026-07-15): upload-core and /api/uploads request ladders when storage.provider is ring_filebase (purpose→profile: news→news, product media→product, nft/chat images→gallery, avatar/logos→thumb, docs→none). Responses include fileId + derivatives. Persist as MediaImageAsset on news (featuredImageAsset / gallery) and GalleryItem.derivatives on store/NFT. Display via pickImageSrc / pickGalleryDisplayUrl. Dead Vikka dual-compat (vikka-field-normalize / NEXT_PUBLIC_NEWS_VIKKA_COMPAT) was deleted — camelCase JSONB only.
Sending images as media fails validation (A/V + HEIC only in current API rules).
Dry-run on primary, then FI/UA slaves: ./scripts/purge-legacy-derivative-keys.sh --endpoint https://… --bucket ring-filebase. Add --delete only after reviewing the sample list. Does not match intentional _v_video_frame_*_480.webp or _v_thumb.webp.
Set env and/or ring-config.json storage.provider to ring_filebase. Restart Next.js after env changes.
Apply RingFileBase manifests under infrastructure/ring-file-base/k8s/ring-filebase/ (API Deployment/Service, MinIO namespace, CDN stack). Ensure the API can reach minio-service.ring-filebase-minio.svc.cluster.local:9000 and the target bucket exists.
API auth (services/ring-filebase-api/src/middleware/auth.ts) expects a JWT whose decoded claims include:
iss = kubernetes/serviceaccountkubernetes.io/serviceaccount/namespacekubernetes.io/serviceaccount/service-account.nameToday the middleware decodes claims and does not call TokenReview or enforce exp. Prefer a long-lived claim-shaped token in Secrets — do not paste a one-hour projected ServiceAccount JWT into RINGBASE_API_TOKEN and forget it.
Expect JSON with success: true, fileId, url, and optional derivatives under your CDN host. Within seconds, slave ingest HEAD for the same key should be 200 (master push).
Browser forms should use POST /api/uploads (unified upload core), not call RingFileBase directly.
| Symptom | Likely cause |
|---|---|
| HTML / 500 from upload URL | RINGBASE_API_URL points at Next.js (api. ingress) |
401 Invalid ServiceAccount token | Token missing legacy SA claim shape |
Invalid file type … got: image/png | type=media instead of image / other |
| Upload OK, CDN 404 on slave briefly | Rare race before push; edges must keep proxy_cache_valid 404 0 |
| Upload OK, CDN 403 | MinIO bucket lacks anonymous GetObject (or CDN Host header misconfigured) |
Node stream.isDisturbed on Blob | Prefer local_storage or ring_filebase; Vercel adapter uploads Uint8Array |
Propagate stuck failed | POST /api/v1/replication/redrive on primary (max 3 retries then stop) |
See-also: full integration catalog hub.
file()/files/{uuid}api. hostnames often route to the Ring app ingress. Uploads must hit ring-filebase-api (in-cluster) or https://filebase-api.ring-platform.org (public write on primary) — never the Next.js deployment. Wrong URL surfaces as HTML or HTTP 500 instead of JSON success/fileId/url.
lib/uploads/server/upload-core.tsUnified upload used by POST /api/uploads |
app/api/uploads/route.ts | Browser/form upload entry |
infrastructure/ring-file-base/services/ring-filebase-api/ | API source (derivatives + propagate) |
infrastructure/ring-file-base/k8s/ring-filebase/10-api-deployment.yaml | Deploy template |
infrastructure/k3s-or/ring-filebase/60-propagate-slaves-configmap.yaml | Slave ingest registry |
| Variable | Required for ring_filebase | Notes |
|---|---|---|
NEXT_PUBLIC_STORAGE_PROVIDER | Yes | Set to ring_filebase (aliases: ringbase, filebase) |
RINGBASE_API_URL | Yes | Host or host + /api/v1; adapter normalizes to …/api/v1 |
RINGBASE_API_TOKEN | Yes | Bearer for API auth middleware |
RINGBASE_PUBLIC_URL | Recommended | Public base used in returned URLs (usually CDN origin) |
NEXT_PUBLIC_RINGBASE_API_URL | Optional | Fallback if RINGBASE_API_URL unset |
BLOB_READ_WRITE_TOKEN | No | Only for vercel_blob |
NEXT_PUBLIC_LOCAL_STORAGE_URL | No | Only for local_storage |
ring-config.json example:
k8s ConfigMap / Secret pattern (see k8s/secrets.example.yaml):
resolveRingBaseUploadType)The API validates MIME types per type field. The adapter maps:
| Content / access | type sent to API |
|---|---|
access: 'private' | document |
image/* | image (first-class; product is a deprecated alias) |
video/* or audio/* | media |
| Everything else | other |
Optional overrides: ringbaseType, derivativesProfile (none | thumb | gallery | product | news).
| Pattern | Status |
|---|---|
{fileId}_v_{variant} e.g. _v_thumb.webp, _v_blur.webp, _v_sync_thumb.jpg | Current — only scheme the API writes |
{fileId}_v_video_frame_N.jpg / _v_video_frame_N_480.webp | Current — async HEIC/video frames (_480.webp under _v_ is intentional) |
{fileId}_thumb, {fileId}_thumb_v{N}, {fileId}_thumb_v{N}_480w.webp | Banned — never write; purge orphans with infrastructure/ring-file-base/scripts/purge-legacy-derivative-keys.sh |
Prefer upload type=image for rasters (product is a deprecated alias → image). Thumbnail status poll: GET /api/v1/files/{fileId}/thumbnail?type=image.
App wiring (2026-07-15): upload-core and /api/uploads request ladders when storage.provider is ring_filebase (purpose→profile: news→news, product media→product, nft/chat images→gallery, avatar/logos→thumb, docs→none). Responses include fileId + derivatives. Persist as MediaImageAsset on news (featuredImageAsset / gallery) and GalleryItem.derivatives on store/NFT. Display via pickImageSrc / pickGalleryDisplayUrl. Dead Vikka dual-compat (vikka-field-normalize / NEXT_PUBLIC_NEWS_VIKKA_COMPAT) was deleted — camelCase JSONB only.
Sending images as media fails validation (A/V + HEIC only in current API rules).
Dry-run on primary, then FI/UA slaves: ./scripts/purge-legacy-derivative-keys.sh --endpoint https://… --bucket ring-filebase. Add --delete only after reviewing the sample list. Does not match intentional _v_video_frame_*_480.webp or _v_thumb.webp.
Set env and/or ring-config.json storage.provider to ring_filebase. Restart Next.js after env changes.
Apply RingFileBase manifests under infrastructure/ring-file-base/k8s/ring-filebase/ (API Deployment/Service, MinIO namespace, CDN stack). Ensure the API can reach minio-service.ring-filebase-minio.svc.cluster.local:9000 and the target bucket exists.
API auth (services/ring-filebase-api/src/middleware/auth.ts) expects a JWT whose decoded claims include:
iss = kubernetes/serviceaccountkubernetes.io/serviceaccount/namespacekubernetes.io/serviceaccount/service-account.nameToday the middleware decodes claims and does not call TokenReview or enforce exp. Prefer a long-lived claim-shaped token in Secrets — do not paste a one-hour projected ServiceAccount JWT into RINGBASE_API_TOKEN and forget it.
Expect JSON with success: true, fileId, url, and optional derivatives under your CDN host. Within seconds, slave ingest HEAD for the same key should be 200 (master push).
Browser forms should use POST /api/uploads (unified upload core), not call RingFileBase directly.
| Symptom | Likely cause |
|---|---|
| HTML / 500 from upload URL | RINGBASE_API_URL points at Next.js (api. ingress) |
401 Invalid ServiceAccount token | Token missing legacy SA claim shape |
Invalid file type … got: image/png | type=media instead of image / other |
| Upload OK, CDN 404 on slave briefly | Rare race before push; edges must keep proxy_cache_valid 404 0 |
| Upload OK, CDN 403 | MinIO bucket lacks anonymous GetObject (or CDN Host header misconfigured) |
Node stream.isDisturbed on Blob | Prefer local_storage or ring_filebase; Vercel adapter uploads Uint8Array |
Propagate stuck failed | POST /api/v1/replication/redrive on primary (max 3 retries then stop) |
See-also: full integration catalog hub.