Gateway and isolation
The web client uses authorized upload procedures for direct byte transport and the files-sdk gateway at /api/files for browsing, downloads and deletion. Every operation checks the current Session and membership. The server applies orgs/<organizationId>/; a filename cannot select or escape an Organization. One private R2 bucket serves all Organizations.
Keys preserve the User's filename, including spaces and Unicode, under ADR-0001. Treat keys as opaque and encode them for URLs. Do not reintroduce filename sanitization that silently changes the display name.
Durable upload lifecycle
uploads.prepare persists an intent after validating membership, MIME type and size. A signed PUT binds the exact key, size, content type and conditional create. Files above 16 MiB use multipart uploads with 8 MiB parts, at most three concurrent parts and at most three attempts per part. The browser sends File.slice() objects and resumes from server-listed parts.
Completion validates part lengths and ETags, then checks HEAD metadata before confirming the file. Signed URLs expire after 15 minutes; intents last 24 hours. Cron recovers missing acknowledgements and aborts abandoned multipart sessions. Explicit cancellation aborts multipart; single-PUT cleanup waits 16 minutes to cover a late upload through an issued URL.
Images can be up to 100 MiB, documents 1 GiB and ZIP archives 5 GiB. The legacy gateway proxy remains capped at 10 MiB and reauthorizes PUTs through the files-sdk patch. Soft delete is enabled; versioning is not. Text previews stop at 64 KiB, and downloads can use range requests without loading the whole original into the Worker.
Confirmed supported files enter the separate Document Text workflow. Storage limits and conversion limits are independent; a stored original can remain downloadable when text preparation fails. ADR-0008 explains both paths.
Configure and extend
Supply STORAGE_BUCKET, STORAGE_ENDPOINT, STORAGE_ACCESS_KEY_ID and STORAGE_SECRET_ACCESS_KEY in the intended environment. Setup does not create an R2 bucket. Configure bucket CORS for the actual web origins, upload/read methods and signed headers; expose ETag so multipart completion can read it. apps/web/r2-cors.json is the starting template, not a list of every worktree or deployed origin.
Extend size/MIME rules in storage config and retain admission, confirmation and source-version checks. Keep files-sdk on one compatible copy across web, API and storage and rebase its pinned patch on upgrades. Verify upload, download byte equality, anonymous denial, prefix isolation, multipart resume/cancel and interrupted confirmation.