# Recovery and signed identity — 2026-09-13

Same tenant/key/payload retries retain the document and job IDs. After D1 reservation, an R2 failure returns 503; the next retry repairs the missing object. A queue-send failure returns `JOB_DISPATCH_UNAVAILABLE` (503), and a repeated incomplete request sends again. Completed repeats neither rewrite R2 nor dispatch. Concurrent reservation losers reread the canonical payload, including approval, before any R2 write.

The new `ingestion_claims` migration adds a five-minute processing lease. D1 claims, chunk replacement, document/job completion and lease release use atomic batches and ownership predicates. An active duplicate returns JOB_BUSY for retry. An expired owner cannot fail or overwrite a replacement consumer's result. Search writes use stable chunk IDs and immutable, hash-checked input. This is at-least-once recovery with caller/queue retries, not an atomic transaction spanning D1/R2/Queue/Vectorize. An abandoned upload with no retry remains pending; there is no unattended outbox sweeper. Long processing beyond the lease must retry.

Before using an existing local or remote database, apply `drizzle/0001_round_shriek.sql` using the matching Wrangler migration configuration. Do not deploy the new code against the old schema.

Remote requests now require an RS256 Cloudflare Access JWT (`cf-access-jwt-assertion`) with verified signature, issuer, audience, expiry, issued-at and subject. Set ACCESS_ISSUER to the HTTPS team origin and ACCESS_AUD to this application's AUD. The verified subject must already have an explicit tenant membership; unverified email/user headers cannot grant identity. Membership provisioning remains an administrative operation. No credentials or invented production configuration are committed. See [Cloudflare's JWT validation guide](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/validating-json/) and [jose](https://github.com/panva/jose).

`APP_ENV=local` plus `LOCAL_AUTH_BYPASS=true` retains the synthetic local demonstration. Staging is explicitly `hybrid` because only part of the live pipeline is bound. `live`/`cloudflare` modes fail closed without AI, Vectorize, Queue and gateway configuration. Binding selection remains `live-pending` until remote acceptance. JSON bodies are limited by actual streamed UTF-8 bytes and require JSON media type.

Validation: 36 tests, including real local Miniflare D1 migration/atomicity and actual-route queue failure/retry; signed-token negative cases; 16 local HTTP assertions; TypeScript, lint and production build. No new browser GUI test, remote deployment, real JWT issuer integration or remote AI acceptance. The earlier browser demo remains a historical record.
