Skip to content

Commit e461d47

Browse files
fengmk2codex
andcommitted
fix(remote-cache): align RFC responses and isolate deployments
Co-authored-by: GPT-6 Codex <codex@openai.com>
1 parent b762d23 commit e461d47

12 files changed

Lines changed: 386 additions & 105 deletions

File tree

‎packages/remote-cache/README.md‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@ The package contains no `vp run` client adapter. Cache keys, values, and blobs r
88

99
The endpoint is `https://<host>/projects/<namespace>`. Namespaces use 1–63 lowercase letters, digits, or hyphens, starting with a letter or digit. An operator can register up to 100 namespaces per deployment.
1010

11-
| Request | Success |
12-
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
13-
| `POST /fetch`, `application/cbor`, `{ key: bytes, secondary_key: bytes }` | CBOR `{ kind: "exact", value: bytes, blob_id: string \| null }`, or `{ kind: "fallback", key: bytes, value: bytes, blob_id: string \| null }` |
14-
| `GET /blob/{blob_id}` | Raw bytes with `application/octet-stream` |
15-
| `POST /store`, `multipart/form-data` | CBOR `{ blob_id: string \| null }` |
11+
| Request | Success |
12+
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
13+
| `POST /fetch`, `application/cbor`, `{ key: bytes, secondary_key: bytes }` | CBOR `{ kind: "exact", value: bytes, blob_id: string \| null }`, or `{ kind: "fallback", key: bytes }` |
14+
| `GET /blob/{blob_id}` | Raw bytes with `application/octet-stream` |
15+
| `POST /store`, `multipart/form-data` | CBOR `{ blob_id: string \| null }` |
1616

1717
`/store` requires one `metadata` part with content type `application/cbor` and fields `{ key: bytes, secondary_key: bytes, value: bytes }`. An optional `blob` part has content type `application/octet-stream`. Either part order works. An omitted blob returns `null`; an empty blob gets an ID. Duplicate parts, unknown parts, duplicate envelope fields, extra fields, invalid types, and truncated bodies are rejected.
1818

1919
Empty and non-UTF-8 keys work. CBOR definite and indefinite maps and byte strings work, including noncanonical lengths. The bounded envelope decoder supports only the protocol's map and string types. It never decodes `value` contents. Chunked strings have a 4,096-chunk bookkeeping limit.
2020

21-
Fetch gives exact matches priority. A fallback follows the latest secondary-key association. A store replaces both mappings atomically. Reassigning a secondary key does not delete its former target. Replacing an entry changes the value seen through every association to that entry.
21+
Fetch gives exact matches priority. A fallback follows the latest secondary-key association and returns only the stored key, without reading R2. A store replaces both mappings atomically. Reassigning a secondary key does not delete its former target. Replacing an entry preserves the other associations to its key.
2222

23-
HTTP status codes follow the [local RFC](docs/0001-remote-cache.md#4-http-api-mapping). A fetch returns `404` when neither key resolves to a live entry. An unavailable blob also returns `404`, including when its R2 object is missing. These responses use plain text. A missing or unreadable value for a live entry is a storage failure and returns `503`, as specified in [the read design](docs/0001-remote-cache.md#7-fetch-and-download-implementation).
23+
HTTP status codes follow the [local RFC](docs/0001-remote-cache.md#4-http-api-mapping). A fetch returns `404` when neither key resolves to a live entry. An unavailable blob also returns `404`, including when its R2 object is missing. These responses use plain text. A missing or unreadable value for a live exact match is a storage failure and returns `503`, as specified in [the read design](docs/0001-remote-cache.md#7-fetch-and-download-implementation).
2424

2525
Errors have `Content-Type: text/plain; charset=utf-8`. Codes are `400` for invalid input, `401` for invalid/missing/expired tokens, `403` for a signed token that fails write policy, `404` for absent data or unavailable namespaces/routes, `413` for size limits, `429` for admission limits, `500` for an incomplete operation, and `503` for unavailable authorization/storage, failed publication guards, quotas, or concurrency admission. `429` and `503` include `Retry-After: 60`.
2626

@@ -53,7 +53,7 @@ pnpm operator setup --name my-public-cache --namespace docs --repo owner/reposit
5353

5454
Use the account's actual Workers subdomain. For a custom domain, set `--origin https://cache.example.com`. Setup disables the unused `workers.dev` alias for a custom domain and disables preview URLs. It rejects an R2 bucket with public custom domains and disables its `r2.dev` access.
5555

56-
Setup discovers resources by name, creates missing resources, applies migrations, resolves public repository and owner IDs through GitHub, records the default branch and exact audience, installs lifecycle/Cron settings, deploys, and prints the endpoint. Repeated setup does not duplicate resources or re-enable a withdrawn namespace. Reusing an existing namespace for a different repository is rejected. Configuration is written to the ignored `wrangler.operator.json`; keep a secure backup of this non-secret file.
56+
Setup discovers resources by name, creates missing resources, applies migrations, resolves public repository and owner IDs through GitHub, records the default branch and exact audience, installs lifecycle/Cron settings, deploys, and prints the endpoint. Repeated setup does not duplicate resources or re-enable a withdrawn namespace. An incompatible origin or repository is rejected before setup changes existing policies, bucket settings, or saved configuration. Configuration is written to the ignored `wrangler.operator.json`; keep a secure backup of this non-secret file.
5757

5858
The default `free` **resource profile** retains the RFC's 8 GB budget, 20,000 entries, 20,000 associations, and 16 generations per Cron run. It is not proof of compatibility with the Workers Free CPU limit. **Use Workers Paid for the full payload limits until production CPU measurements establish a supported Free profile.** Local measurements already exceed 10 ms in several cases. Setup does not purchase or change a Workers subscription.
5959

@@ -90,7 +90,7 @@ GitHub authorization uses `jose`, `RS256`, the fixed issuer `https://token.actio
9090

9191
Tokens are limited to 16 KiB. JWKS bodies are limited to 64 KiB, requests to five seconds, cached keys to ten minutes, and refresh attempts to one per 30 seconds per isolate, including failures. Unknown key IDs cannot cause unlimited refreshes. Missing usable keys fail closed. A maintainer's policy change increments its version; publication checks that version, current scope/deployment state, token expiry, lease, bytes, and identity quotas inside the D1 transaction.
9292

93-
The rate bindings apply before database or object access. Defaults are 600 requests/minute for each namespace's fetch/blob operation and 30 store attempts/minute. Invalid credentials consume store admission too. Unknown routes share catch-all identities. Limits are approximate per Cloudflare location; they are not a global billing cap. Reads do not write D1 counters or refresh retention.
93+
The rate bindings apply before database or object access. Defaults are 600 requests/minute for each namespace's fetch/blob operation and 30 store attempts/minute. Invalid credentials consume store admission too. Unknown routes share catch-all identities. Setup and upgrade derive stable limiter IDs from the Worker and binding names, so separate deployments have separate counters. Run `pnpm operator upgrade` for an existing deployment to replace the old shared IDs. Limits are approximate per Cloudflare location; they are not a global billing cap. Reads do not write D1 counters or refresh retention.
9494

9595
## Operations
9696

‎packages/remote-cache/docs/0001-remote-cache.md‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ For a match, return HTTP `200`, `Content-Type: application/cbor`, with one of:
8484

8585
```text
8686
{ kind: "exact", value: bytes, blob_id: string | null }
87-
{ kind: "fallback", key: bytes, value: bytes, blob_id: string | null }
87+
{ kind: "fallback", key: bytes }
8888
```
8989

90-
Check `key` first. If no live entry exists, resolve `secondary_key` to a stored key. Check that entry. Include the stored key only in the fallback variant. If neither resolves, return HTTP `404`. Fetch does not change entries or associations.
90+
Check `key` first. If no live entry exists, resolve `secondary_key` to a stored key. Check that entry. For a fallback match, return only the stored key so the client can explain what changed. If neither resolves, return HTTP `404`. Fetch does not change entries or associations.
9191

9292
### Download a blob
9393

@@ -120,13 +120,13 @@ If the request omits the blob, return `null`. A present zero-byte blob receives
120120

121121
Each successful store replaces `entries[key]` and sets `associations[secondary_key] = key`. For example:
122122

123-
| Operation | Entries afterward | Association afterward |
124-
| ------------------- | ----------------------------------------------- | --------------------- |
125-
| Store `(A, S, VA)` | `A → VA` | `S → A` |
126-
| Store `(B, S, VB)` | `A → VA`, `B → VB` | `S → B` |
127-
| Fetch `(A, S)` | Unchanged; returns exact `VA` | Still `S → B` |
128-
| Fetch `(C, S)` | Unchanged; returns fallback key `B`, value `VB` | Still `S → B` |
129-
| Store `(A, T, VA2)` | `A → VA2`, `B → VB` | `S → B`, `T → A` |
123+
| Operation | Entries afterward | Association afterward |
124+
| ------------------- | ----------------------------------- | --------------------- |
125+
| Store `(A, S, VA)` | `A → VA` | `S → A` |
126+
| Store `(B, S, VB)` | `A → VA`, `B → VB` | `S → B` |
127+
| Fetch `(A, S)` | Unchanged; returns exact `VA` | Still `S → B` |
128+
| Fetch `(C, S)` | Unchanged; returns fallback key `B` | Still `S → B` |
129+
| Store `(A, T, VA2)` | `A → VA2`, `B → VB` | `S → B`, `T → A` |
130130

131131
Other secondary keys that already point to `A` also resolve to `VA2`. A change to `S` does not evict entry `A`.
132132

@@ -359,7 +359,7 @@ First, apply rate limits. Check that the public scope is enabled. Decode CBOR wi
359359

360360
Use one indexed D1 query to select the exact live entry, or the secondary fallback if no exact entry exists. Select the response kind, stored key, and generation references from one database snapshot. Separate exact and fallback reads could observe different commits.
361361

362-
Read the selected generation's value from R2. Return the corresponding CBOR variant. Return `503` if D1 identifies a live generation but its value object is missing or unreadable. This condition is a storage failure. Expired or deleted entries count as absent. The client still validates the exact value before it downloads outputs.
362+
For an exact match, read the selected generation's value from R2 and return the exact CBOR variant. Return `503` if its value object is missing or unreadable. This condition is a storage failure. For a fallback match, return the stored key without reading R2. Expired or deleted entries count as absent. The client still validates the exact value before it downloads outputs.
363363

364364
For `/blob/{blob_id}`, check that the public scope is enabled, without authentication. Resolve the blob ID in D1. Stream the R2 object to the response. A blob ID from another scope must not expose data.
365365

@@ -607,7 +607,7 @@ Public reads require no identity subscription. GitHub issues tokens for publishi
607607
Use these variables for daily traffic:
608608

609609
- `L`: Public fetches after local misses.
610-
- `F`: Fetches that return a value.
610+
- `F`: Exact fetches that return a value.
611611
- `H`: Blob downloads after successful client validation.
612612
- `P`: Entries successfully published through explicit CI pushes from the main branch, including overwrites.
613613

@@ -621,7 +621,7 @@ Use these variables for stored data:
621621
- `E`: Live exact keys.
622622
- `R`: Retention days.
623623

624-
Each store takes one Worker request. Each metadata fetch takes one request. The client makes a blob request only when it needs the blob. The Worker also reads the opaque value from R2. Internal R2 multipart upload changes storage operation counts but adds no client requests:
624+
Each store takes one Worker request. Each metadata fetch takes one request. The client makes a blob request only when it needs the blob. For an exact match, the Worker also reads the opaque value from R2. Internal R2 multipart upload changes storage operation counts but adds no client requests:
625625

626626
```text
627627
A per store = 1 # no blob: value PUT
@@ -678,7 +678,7 @@ For planning, interpret the approximate KB values as decimal. `V = 250,000` byte
678678

679679
D1's provisional 4 KiB estimate for each key covers index and generation records only. Validate it with the reported key sizes. Include repeated key bytes in indexes and associations.
680680

681-
Every exact or fallback response transfers the value, even if client validation prevents a blob download. Before protocol overhead and retries, daily response payload equals `F * V + H * B` bytes. At the example's sizes, 1,000 fetches with values and 800 blob downloads transfer about 450 MB/day. This includes 250 MB of values. These transfers affect time, CBOR work, and memory. Request and R2 operation counts follow the same equations.
681+
Every exact response transfers the value, even if client validation prevents a blob download. Before protocol overhead and retries, daily value and blob payload equals `F * V + H * B` bytes. At the example's sizes, 1,000 fetches with values and 800 blob downloads transfer about 450 MB/day. This includes 250 MB of values. These transfers affect time, CBOR work, and memory. Request and R2 operation counts follow the same equations.
682682

683683
### Artifact-size evidence
684684

0 commit comments

Comments
 (0)