@jimhoyd/urlcode 0.4.1 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/urlcode-authoring/SKILL.md +60 -7
- package/.claude/skills/urlcode-operations/SKILL.md +4 -0
- package/README.md +19 -15
- package/SECURITY.md +5 -3
- package/dist/BUILD-MANIFEST.json +31 -28
- package/dist/agent-context.js +82 -0
- package/dist/agents-guide.js +42 -42
- package/dist/authoring.js +12 -2
- package/dist/body-schema.js +159 -0
- package/dist/build-cloudflare.js +2 -0
- package/dist/capabilities.js +1 -1
- package/dist/cli.js +29 -12
- package/dist/config.js +74 -6
- package/dist/context.js +4 -6
- package/dist/errors.js +3 -1
- package/dist/examples.js +1 -1
- package/dist/extensions.js +138 -3
- package/dist/http-policy.js +19 -4
- package/dist/http-response.js +2 -2
- package/dist/init-with.js +71 -9
- package/dist/mcp.js +15 -2
- package/dist/pattern-guard.js +32 -0
- package/dist/policies/security.js +0 -0
- package/dist/policy.js +16 -0
- package/dist/project-tests.js +35 -11
- package/dist/readiness.js +188 -32
- package/dist/recipes.js +1 -1
- package/dist/router.js +17 -0
- package/dist/runtime.js +26 -3
- package/dist/scaffold.js +0 -0
- package/dist/server.js +26 -3
- package/dist/site.js +0 -0
- package/dist/tooling.js +3 -1
- package/dist/types/agent-context.d.ts +44 -0
- package/dist/types/authoring.d.ts +3 -1
- package/dist/types/body-schema.d.ts +58 -0
- package/dist/types/config.d.ts +10 -2
- package/dist/types/context.d.ts +1 -1
- package/dist/types/errors.d.ts +9 -1
- package/dist/types/examples.d.ts +1 -1
- package/dist/types/extensions.d.ts +105 -2
- package/dist/types/http-policy.d.ts +3 -0
- package/dist/types/init-with.d.ts +10 -1
- package/dist/types/pattern-guard.d.ts +10 -0
- package/dist/types/project-tests.d.ts +9 -0
- package/dist/types/readiness.d.ts +71 -0
- package/dist/types/recipes.d.ts +1 -1
- package/dist/types/runtime.d.ts +4 -0
- package/dist/types/server.d.ts +9 -1
- package/dist/types/tooling.d.ts +2 -0
- package/dist/types/types.d.ts +13 -0
- package/dist/types.js +10 -2
- package/dist/typescript-authoring.js +5 -3
- package/dist/verify-deployment.js +16 -10
- package/examples/body-validation/README.md +16 -0
- package/examples/body-validation/example.yaml +17 -0
- package/examples/body-validation/tests/requests.json +15 -0
- package/examples/body-validation/urlcode.yaml +34 -0
- package/examples/coverage-waiver/README.md +8 -0
- package/examples/coverage-waiver/example.yaml +16 -0
- package/examples/coverage-waiver/functions/notes.mjs +2 -0
- package/examples/coverage-waiver/tests/requests.json +3 -0
- package/examples/coverage-waiver/urlcode.yaml +10 -0
- package/examples/data-dir/README.md +39 -0
- package/examples/data-dir/data/welcome.txt +1 -0
- package/examples/data-dir/example.yaml +22 -0
- package/examples/data-dir/functions/note.mjs +18 -0
- package/examples/data-dir/tests/requests.json +6 -0
- package/examples/data-dir/urlcode.yaml +6 -0
- package/examples/lifecycle/README.md +15 -0
- package/examples/lifecycle/example.yaml +19 -0
- package/examples/lifecycle/functions/notes.mjs +29 -0
- package/examples/lifecycle/tests/requests.json +12 -0
- package/examples/lifecycle/urlcode.yaml +29 -0
- package/examples/not-found/README.md +10 -0
- package/examples/not-found/example.yaml +17 -0
- package/examples/not-found/public/404.html +3 -0
- package/examples/not-found/public/index.html +3 -0
- package/examples/not-found/tests/requests.json +7 -0
- package/examples/not-found/urlcode.yaml +7 -0
- package/examples/shared-blocks/README.md +11 -0
- package/examples/shared-blocks/example.yaml +17 -0
- package/examples/shared-blocks/tests/requests.json +7 -0
- package/examples/shared-blocks/urlcode.yaml +37 -0
- package/llms-full.txt +436 -149
- package/llms.txt +44 -6
- package/package.json +15 -22
- package/recipes/static-page/README.md +9 -0
- package/recipes/static-page/public/index.html +11 -0
- package/recipes/static-page/recipe.yaml +21 -0
- package/recipes/static-page/tests/requests.json +22 -0
- package/recipes/static-page/urlcode.yaml +7 -0
- package/recipes/static-plus-api/README.md +6 -0
- package/recipes/static-plus-api/urlcode.yaml +4 -0
- package/recipes/store-crud/README.md +53 -0
- package/recipes/store-crud/recipe.yaml +31 -0
- package/recipes/store-crud/tests/requests.json +18 -0
- package/recipes/store-crud/urlcode.yaml +18 -0
- package/schemas/urlcode.schema.json +125 -60
- package/skills/urlcode/SKILL.md +53 -26
- package/starters/default/AGENTS.md +43 -43
- package/starters/page/README.md +14 -0
- package/starters/page/public/index.html +12 -0
- package/starters/page/tests/requests.json +17 -0
- package/starters/page/urlcode.yaml +6 -0
- package/.claude-plugin/marketplace.json +0 -18
- package/CONTRIBUTING.md +0 -112
- package/ROADMAP.md +0 -66
- package/docs/AI-AUTHORING.md +0 -338
- package/docs/ASSETS.md +0 -107
- package/docs/AUTH-BACKUP.md +0 -32
- package/docs/AWS.md +0 -86
- package/docs/BEST-PRACTICES.md +0 -276
- package/docs/BULK.md +0 -79
- package/docs/CAPABILITIES.md +0 -192
- package/docs/CAPACITY.md +0 -305
- package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
- package/docs/CI.md +0 -147
- package/docs/CLOUDFLARE.md +0 -109
- package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
- package/docs/COMPLIANCE.md +0 -239
- package/docs/COMPOSING-A-SITE.md +0 -278
- package/docs/CONDITIONS.md +0 -74
- package/docs/DEPLOYMENT-CHECKS.md +0 -108
- package/docs/DEVELOPMENT-PIPELINE.md +0 -270
- package/docs/EGRESS.md +0 -125
- package/docs/EXTENSIONS.md +0 -438
- package/docs/FRAMEWORK.md +0 -217
- package/docs/FUNCTION-SECURITY.md +0 -254
- package/docs/HTTP.md +0 -129
- package/docs/INSTALL.md +0 -128
- package/docs/INTERCHANGE.md +0 -134
- package/docs/LOAD-TESTING.md +0 -91
- package/docs/LOCAL-DEVELOPMENT.md +0 -102
- package/docs/MIDDLEWARE-EXAMPLES.md +0 -75
- package/docs/MIDDLEWARE.md +0 -102
- package/docs/MONITORING.md +0 -115
- package/docs/OBSERVABILITY.md +0 -222
- package/docs/OPEN-DECISIONS.md +0 -224
- package/docs/OPERATIONAL-PROOF.md +0 -41
- package/docs/OPERATIONS.md +0 -201
- package/docs/ORGANIZATION.md +0 -135
- package/docs/PERFORMANCE.md +0 -72
- package/docs/PLUGINS.md +0 -271
- package/docs/POLICIES.md +0 -211
- package/docs/PRERENDER.md +0 -245
- package/docs/PROJECT-DIRECTION.md +0 -118
- package/docs/PROVIDER-VERIFICATION.md +0 -84
- package/docs/READINESS.md +0 -150
- package/docs/README.md +0 -87
- package/docs/RECIPES.md +0 -99
- package/docs/RELEASE-0.4.0-alpha.3.md +0 -50
- package/docs/RELEASE-0.4.1.md +0 -73
- package/docs/RELEASE-READINESS.md +0 -117
- package/docs/RELEASE-SECURITY.md +0 -96
- package/docs/RESILIENCE.md +0 -161
- package/docs/ROUTING.md +0 -92
- package/docs/SANDBOX-REVIEW.md +0 -72
- package/docs/SCAFFOLDING.md +0 -70
- package/docs/SECURITY-AUDIT.md +0 -164
- package/docs/SITE.md +0 -150
- package/docs/SPECIFICATION.md +0 -359
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
- package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
- package/docs/SPIKE-CORE-LAYERING.md +0 -368
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -211
- package/docs/STANDARDS.md +0 -311
- package/docs/STARTERS.md +0 -83
- package/docs/STATIC.md +0 -105
- package/docs/TOOLING.md +0 -298
- package/docs/TUNNELS.md +0 -72
- package/docs/TYPESCRIPT-AUTHORING.md +0 -87
- package/docs/TYPESCRIPT.md +0 -123
- package/docs/VERCEL.md +0 -114
- package/docs/VERSION-ALIGNMENT.md +0 -80
- package/docs/YAML-GUIDE.md +0 -57
- package/docs/YAML-REFERENCE.md +0 -449
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
- package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
- package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
- package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
- package/docs/archive/2026-09-19/ROADMAP.md +0 -387
- package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
- package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
- package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
- package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
- package/docs/archive/README.md +0 -28
- package/docs/policies/agents.md +0 -182
- package/docs/policies/cache.md +0 -152
- package/docs/policies/compression.md +0 -169
- package/docs/policies/contract.md +0 -52
- package/docs/policies/hardened.md +0 -56
- package/docs/policies/interoperability.md +0 -169
- package/docs/policies/operations.md +0 -45
- package/docs/policies/security.md +0 -161
- package/docs/policies/throttle.md +0 -103
- package/docs/yaml/assets.md +0 -36
- package/docs/yaml/conditions.md +0 -20
- package/docs/yaml/functions.md +0 -168
- package/docs/yaml/middleware.md +0 -31
- package/docs/yaml/organization.md +0 -74
- package/docs/yaml/policies.md +0 -37
- package/docs/yaml/redirects.md +0 -64
- package/docs/yaml/responses.md +0 -57
- package/docs/yaml/site.md +0 -24
- package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -120
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +0 -108
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# Historical record
|
|
2
|
-
|
|
3
|
-
Archived 2026-09-19. This records an earlier implementation or proposal, not
|
|
4
|
-
current instructions. See the [current roadmap](../../../ROADMAP.md),
|
|
5
|
-
[current contract](../../SPECIFICATION.md) and [open decisions](../../OPEN-DECISIONS.md).
|
|
6
|
-
Remaining acceptance work is not declared complete by archiving this record.
|
|
7
|
-
|
|
8
|
-
<!-- trust-model-prose: historical-file -->
|
|
9
|
-
<!-- guidance-claims: ignore-file -->
|
|
10
|
-
|
|
11
|
-
# Usability review: URLCode against the tools people reach for instead
|
|
12
|
-
|
|
13
|
-
Status: an honest assessment written 2026-09-18 against the source in the four
|
|
14
|
-
repositories at that date. It is opinion grounded in the code and docs, not a
|
|
15
|
-
benchmark. Its purpose is to name the friction a first-time user or an AI
|
|
16
|
-
agent meets, compare it with what competing tools do, and rank the changes
|
|
17
|
-
that would matter most. It does not change any contract by itself.
|
|
18
|
-
|
|
19
|
-
> **Update:** written the day before `0.4.0-alpha.2` reversed the default
|
|
20
|
-
> execution mode. Where the table below credits functions and middleware with
|
|
21
|
-
> isolation, that now describes a route declaring `sandbox: true`; the default
|
|
22
|
-
> is trusted, in-process execution with full Node, npm and filesystem access.
|
|
23
|
-
> See [SPIKE-DEFAULT-TRUST-MODEL.md](../../SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
24
|
-
|
|
25
|
-
## 1. Who the competitors are, by rung
|
|
26
|
-
|
|
27
|
-
URLCode is not one product; it competes with a different tool on each rung
|
|
28
|
-
of [the ladder](../../FRAMEWORK.md#the-ladder).
|
|
29
|
-
|
|
30
|
-
| Rung | What people use today | Where URLCode is better | Where it is worse today |
|
|
31
|
-
|---|---|---|---|
|
|
32
|
-
| Redirects, responses | Netlify `_redirects`, Cloudflare `_redirects`/Rules, Vercel `vercel.json`, nginx | One portable file that validates before deploy, tests with fixtures, counts routes, converts from those formats and refuses lossy conversions | Those files are two columns; `urlcode.yaml` needs `version`, a route key and a handler object. Nobody needs a validator for ten redirects |
|
|
33
|
-
| Pages, files | Any static host, Astro, Hugo | Same file, no build step, native ranges/ETags, `site` conventions | No content pipeline, no templating for pages outside the extension kit |
|
|
34
|
-
| Functions, middleware | Cloudflare Workers, Vercel Functions, Hono, Express | Real isolation with a fresh heap per call; typed args from YAML; secrets only by revision-pinned grant; runs the same on a laptop | No `fetch`, no timers, no streaming, no npm packages in guests. Every framework above lets you call an API from a function; here you declare a `proxy` or stop |
|
|
35
|
-
| Live links | Bitly, Dub, Short.io, a Postgres table | Records without reloads, versioned writes, private management API, no account system to run | Single host SQLite that needs a patched Node build; no dashboard until you install admin |
|
|
36
|
-
| Accounts | Clerk, Auth0, Auth.js, Better Auth, Lucia, Supabase Auth, Devise | Everything is operator-owned and reviewable: no vendor, no callback URLs on someone else's dashboard, no per-MAU bill; passkeys, OIDC, TOTP, recovery and an account page in one package | Install is a reviewed-tarball ritual, a host file, a SHA-256 pin and a JSON-on-stdin bootstrap. Clerk is `npm i` and one env var. Better Auth is a config object and a CLI migration |
|
|
37
|
-
| Administration | Django admin, Rails Administrate, Retool, Forest Admin, Supabase Studio | Built on the same service with two-person cases and audited reveal, which none of the generic admins give you | Only manages auth's entities; there is no way to expose the project's own data. Django admin is one line per model |
|
|
38
|
-
| Full app in one place | Rails, Django, Laravel, Next.js + Clerk + Prisma, Supabase | The whole thing is declarative and portable; an agent can generate and validate it without a build; the trust boundary between generated code and the operator is explicit | Everything that is not a route, a function or an account is out of scope: no data model, no ORM, no forms for your own tables, no client bundle, no email templates for your app |
|
|
39
|
-
|
|
40
|
-
The honest summary: URLCode wins on portability, validation, isolation and
|
|
41
|
-
operator ownership, and loses on the first fifteen minutes and on anything
|
|
42
|
-
that needs application data.
|
|
43
|
-
|
|
44
|
-
## 2. The first fifteen minutes
|
|
45
|
-
|
|
46
|
-
What a new person does today, compared with the fastest competitor at each
|
|
47
|
-
step. Times are what the docs imply, not measurements.
|
|
48
|
-
|
|
49
|
-
| Step | URLCode today | Fastest competitor | Gap |
|
|
50
|
-
|---|---|---|---|
|
|
51
|
-
| Install | `npm i -g @jimhoyd/urlcode` or brew or a checksum script; three channels documented at equal weight | `npx create-next-app` | Too many equal choices on the first screen. Pick one, put the rest in the install guide |
|
|
52
|
-
| First project | `urlcode init` writes a function route, a redirect and fixtures | `wrangler init` | Equal |
|
|
53
|
-
| First redirect | 5 lines of YAML for one redirect | 1 line in `_redirects` | Acceptable once, heavy for 200. `bulk-import` exists but a person starting small never learns it |
|
|
54
|
-
| First function | Must know: `parameters` with `schema`, `args` with `from: path`, the Request/Response subset, that `fetch` is absent | Hono: `app.get('/x', c => c.json(...))` | The typed-args design is right but needs a two-line minimal form. Today the smallest function route is 10 lines |
|
|
55
|
-
| Add accounts | Clone two private repos, build tarballs with `pack-sources.mjs --core --auth --ui --admin --core-revision SHA`, install four tarballs, `urlcode-auth init`, compute `inspectExtensionRevision`, paste the SHA into an env var, `bootstrap` with JSON on stdin, `serve --host-file --origin` | Clerk: `npm i @clerk/nextjs`, one env var, wrap the app | This is the largest gap in the framework. Most of it is a consequence of the packages being unpublished and of the revision pin, and it is the step the README leads with |
|
|
56
|
-
| Add admin | `urlcode-admin init` writes everything | Django `admin.site.register(Model)` | Comparable once installed |
|
|
57
|
-
| Deploy | Container, Node process, or an adapter that refuses functions, links and extensions | `vercel deploy` | The refusals are correct but the person learns them at deploy time. `urlcode capabilities` exists and is not in the quick start |
|
|
58
|
-
|
|
59
|
-
## 3. What an AI agent meets
|
|
60
|
-
|
|
61
|
-
The framework's claim is that an agent should build from a handful of
|
|
62
|
-
redirects to a full application without rebuilding the core. Measured
|
|
63
|
-
against that claim:
|
|
64
|
-
|
|
65
|
-
- **Strong:** one JSON Schema, a generated field reference, fixtures the agent
|
|
66
|
-
must write, `validate`, `test` and `audit` that give exact failures with the
|
|
67
|
-
route named, an MCP server for read-only inspection, and a capability
|
|
68
|
-
matrix that lists what does not exist. Few frameworks tell an agent what it
|
|
69
|
-
cannot do. This is the right foundation.
|
|
70
|
-
- **Weak:** the documentation was organized by feature history rather than by
|
|
71
|
-
task. Before this review, `llms.txt` listed 40 documents at equal weight,
|
|
72
|
-
three of them status logs, and the README opened with release history and
|
|
73
|
-
"unreleased source" caveats. An agent reading it spent its context on
|
|
74
|
-
provenance rather than on the shape of a project. The framework page and the
|
|
75
|
-
reorganized index in this change address that; the remaining cost is the
|
|
76
|
-
size of the reference documents themselves (the YAML guide, policies and
|
|
77
|
-
dynamic links are each over 400 lines).
|
|
78
|
-
- **Missing:** an agent cannot yet discover the extension packages' YAML from
|
|
79
|
-
the core schema. `extensions.auth.config` is validated by auth's schema at
|
|
80
|
-
activation, but there is no way to ask the installed runtime "what config
|
|
81
|
-
does `auth` accept" without the host file. A `urlcode extensions --schema`
|
|
82
|
-
command that reads the host file and prints each extension's configuration
|
|
83
|
-
and policy schemas would close this, and would let `urlcode mcp` serve them.
|
|
84
|
-
- **Missing:** no single command creates the whole layered project. Today it
|
|
85
|
-
is `urlcode init`, then `urlcode-auth init`, then `urlcode-admin init`, each
|
|
86
|
-
with its own directory conventions. One `urlcode init --with auth,admin`
|
|
87
|
-
that delegates to the installed extension packages' scaffolds would make
|
|
88
|
-
the ladder real for an agent.
|
|
89
|
-
|
|
90
|
-
## 4. Ranked recommendations
|
|
91
|
-
|
|
92
|
-
Ordered by how much each would change the experience per unit of work, and
|
|
93
|
-
whether it touches a contract.
|
|
94
|
-
|
|
95
|
-
1. **Publish the three extension packages** (even as `0.1.0-alpha` with the
|
|
96
|
-
caveats their status files carry). Every install step in section 2's
|
|
97
|
-
"add accounts" row except the revision pin existed because they were
|
|
98
|
-
unpublished. This was a decision, not code; the repositories already had
|
|
99
|
-
the release checks. No contract change. Done 2026-09-18: all three are on
|
|
100
|
-
npm as `0.1.0-alpha.x` (review still pending, issue 58).
|
|
101
|
-
2. **`urlcode init --with auth,admin,ui`.** Delegate to each installed
|
|
102
|
-
package's existing scaffold; write one host file and one README. No contract
|
|
103
|
-
change; a CLI addition in core that calls into optional peers.
|
|
104
|
-
3. **Print extension schemas.** `urlcode extensions --host-file … --json`
|
|
105
|
-
listing each registered extension's name, version, configuration schema and
|
|
106
|
-
policy schema, and expose it through `urlcode mcp`. No contract change.
|
|
107
|
-
4. **A short form for the common function route.** Allow `function:
|
|
108
|
-
functions/hello.mjs` as a string with path parameters inferred as required
|
|
109
|
-
strings of bounded length, expanding to today's long form. This is a schema
|
|
110
|
-
addition (`version: "1"` stays valid) and the single largest cut in YAML
|
|
111
|
-
for first-time users and agents. Needs the usual generated-reference and
|
|
112
|
-
cookbook updates.
|
|
113
|
-
5. **Lead the README with the ladder, not the release history.** Done in this
|
|
114
|
-
change; keep it that way. Move status caveats to the readiness register.
|
|
115
|
-
6. **One install channel on the first screen.** npm first; brew, script and
|
|
116
|
-
container in the install guide.
|
|
117
|
-
7. **Fold the `presentation`/`ui` split.** Auth and admin still render through
|
|
118
|
-
the primitives while the kit is the documented way to restyle. Finishing
|
|
119
|
-
kit adoption (already listed in the ui status file) removes the one place
|
|
120
|
-
where the framework's story and its code differ.
|
|
121
|
-
8. **Later, and a real contract question:** application data. The thing every
|
|
122
|
-
competitor on the last rung has and URLCode does not is a place for the
|
|
123
|
-
project's own records with an admin view. The runtime already has one
|
|
124
|
-
bounded store (links) and one admin surface (auth's entities). Whether a
|
|
125
|
-
declared `collection` handler with operator-owned SQLite and an admin
|
|
126
|
-
registration belongs in the framework is the next spike worth writing.
|
|
127
|
-
Without it, "full-fledged application" means "site with accounts".
|
|
128
|
-
|
|
129
|
-
## 5. What is fine and should stay
|
|
130
|
-
|
|
131
|
-
- The operator host file. It is the reason the project can be untrusted and
|
|
132
|
-
portable at the same time; Clerk's convenience is bought with a vendor in
|
|
133
|
-
the loop. Keep the boundary, make the file generated.
|
|
134
|
-
- The revision pin. It is unusual and it is what makes "an agent changed the
|
|
135
|
-
YAML" a reviewable event rather than a silent grant. Keep it, print it
|
|
136
|
-
loudly, and make `init` write it.
|
|
137
|
-
- Refusing instead of degrading on every target. The failure names the route.
|
|
138
|
-
- The capability matrix in the AI guide. Extend it to the extension packages
|
|
139
|
-
rather than softening it.
|
package/docs/archive/README.md
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Historical records
|
|
2
|
-
|
|
3
|
-
These documents retain earlier reasoning and evidence. They are not authoring
|
|
4
|
-
instructions or an active backlog. Read [the current roadmap](../../ROADMAP.md),
|
|
5
|
-
[open decisions](../OPEN-DECISIONS.md) and [implemented contract](../SPECIFICATION.md)
|
|
6
|
-
first. An old example can describe a removed feature or an earlier trust model.
|
|
7
|
-
|
|
8
|
-
Archived on 2026-09-19 after checking the current code:
|
|
9
|
-
|
|
10
|
-
| Record | Why archived | Current owner of remaining work |
|
|
11
|
-
|---|---|---|
|
|
12
|
-
| [Monorepo spike](2026-09-19/SPIKE-MONOREPO.md) | The migration it proposed is complete: ui, auth and admin are workspace packages and all three have been released from this repository | [Development pipeline](../DEVELOPMENT-PIPELINE.md) and [release security](../RELEASE-SECURITY.md) |
|
|
13
|
-
| [Previous roadmap](2026-09-19/ROADMAP.md) | Release chronology mixed with current planning | Current roadmap and release readiness |
|
|
14
|
-
| [Detailed phases](2026-09-19/NEXT-STEPS.md) | Most authoring/tooling/UI work delivered; repeated stale backlog | Current roadmap, issues and open decisions |
|
|
15
|
-
| [Previous decisions](2026-09-19/OPEN-DECISIONS.md) | Mixed settled releases, obsolete repository counts and live questions | Current open decisions |
|
|
16
|
-
| [Next-phase implementation](2026-09-19/NEXT-PHASE-PLAN.md) | Delivered source snapshot for alpha.1 | Specification and provider evidence |
|
|
17
|
-
| [Extension delivery sequence](2026-09-19/EXTENSION-IMPLEMENTATION.md) | Registration, auth/admin and UI now implemented | Package status files and core issue 58 |
|
|
18
|
-
| [Policy/extension exploration](2026-09-19/SPIKE-EXTENSIONS.md) | Main design delivered; obsolete link APIs | Policies, plugins and extensions guides |
|
|
19
|
-
| [Extension model review](2026-09-19/SPIKE-EXTENSION-MODEL.md) | Historical design rationale | Current extension contract |
|
|
20
|
-
| [Usability review](2026-09-19/USABILITY-REVIEW.md) | Predates trusted execution and retired packages | New benchmark observations |
|
|
21
|
-
| [Management security](2026-09-19/MANAGEMENT-SECURITY.md) | Describes the removed core link-management API | No current core replacement |
|
|
22
|
-
| [Lambda compile spike](2026-09-19/SPIKE-LAMBDA-COMPILE.md) | Proposal declined 2026-09-19 in favour of one Node deployment per project | Open decisions; AWS and Vercel guides |
|
|
23
|
-
|
|
24
|
-
Open proposals stay outside the archive with their status stated at the top.
|
|
25
|
-
The accepted trust-by-default decision remains directly linked from the current
|
|
26
|
-
security contract; its `SPIKE-` filename is retained for existing references.
|
|
27
|
-
Security audits and operational evidence remain available: “implemented” never
|
|
28
|
-
means their unperformed review, deployment or recovery work is complete.
|
package/docs/policies/agents.md
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# The `agents` policy
|
|
2
|
-
|
|
3
|
-
Denies or reports requests by their `User-Agent`, before anything else runs.
|
|
4
|
-
It is the cheapest refusal in the request chain (`agents`, then `throttle`,
|
|
5
|
-
then the cache lookup), so a denied crawler never counts against a quota,
|
|
6
|
-
never reaches the sandbox and never fills the origin cache.
|
|
7
|
-
|
|
8
|
-
```yaml
|
|
9
|
-
version: "1"
|
|
10
|
-
policies: # project defaults, or per route under routes.<pattern>.policies
|
|
11
|
-
agents:
|
|
12
|
-
deny: [ai-crawlers] # bundled list names, or project-relative .json files
|
|
13
|
-
allow: [monitoring] # allow always wins over deny
|
|
14
|
-
denyPatterns: ["^curl/"] # linear-time regex subset, matched case-insensitively
|
|
15
|
-
allowPatterns: ["^Mozilla/5\\.0 \\(compatible; Googlebot"]
|
|
16
|
-
denyEmpty: false # deny a missing or blank User-Agent
|
|
17
|
-
status: 403 # 400-599
|
|
18
|
-
mode: enforce # enforce | report
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
The `hardened` profile sets `deny: [ai-crawlers]` with status 403. A route can
|
|
22
|
-
override any key, or set `agents: false` to switch the policy off for itself.
|
|
23
|
-
|
|
24
|
-
## Semantics
|
|
25
|
-
|
|
26
|
-
- Matching is against the `User-Agent` request header only and is
|
|
27
|
-
case-insensitive. Nothing else about the request (address, path, other
|
|
28
|
-
headers) takes part.
|
|
29
|
-
- Evaluation order: if any `allow` list or `allowPatterns` entry matches, the
|
|
30
|
-
request passes and nothing is logged. Otherwise the first `deny` list (in
|
|
31
|
-
the order written) or `denyPatterns` entry that matches denies it.
|
|
32
|
-
- `urlcode audit`, `test` and `benchmark` send generated probes as
|
|
33
|
-
`Mozilla/5.0 (compatible; RouteProbe/0.1)`;
|
|
34
|
-
a fixture may set its own `user-agent` header. Upstream lists include
|
|
35
|
-
short unanchored names (the `ai-crawlers` list carries `Code`, which
|
|
36
|
-
matches any agent containing that word), so a custom allow pattern for
|
|
37
|
-
your own tooling is worth declaring before denying a whole list.
|
|
38
|
-
- `denyEmpty: true` denies a request with no `User-Agent`, or one that is only
|
|
39
|
-
whitespace. Allow rules cannot match an empty header, so this always wins
|
|
40
|
-
for empty headers.
|
|
41
|
-
- A denial answers `status` (default 403) with `content-type:
|
|
42
|
-
text/plain; charset=utf-8`, `cache-control: no-store` and the body
|
|
43
|
-
`Forbidden\n`. Response policies that also run at request time (cache,
|
|
44
|
-
compression) are skipped on the early response; `security` headers are
|
|
45
|
-
still applied.
|
|
46
|
-
- Every denial is logged as `{ event: 'agents', route, list, outcome }`, where
|
|
47
|
-
`list` is the list name, the file path as written in YAML, `pattern` for
|
|
48
|
-
`denyPatterns`, or `empty` for `denyEmpty`. The raw header value is never
|
|
49
|
-
logged: a `User-Agent` is attacker-controlled text.
|
|
50
|
-
- `mode: report` logs the same event with `outcome: 'reported'` and never
|
|
51
|
-
denies. Run a new deny list in report mode for a release, read the log, then
|
|
52
|
-
switch to `enforce`.
|
|
53
|
-
|
|
54
|
-
## Allow before deny
|
|
55
|
-
|
|
56
|
-
Broad deny lists without an explicit allow are the most common self-inflicted
|
|
57
|
-
outage in this space. `crawlers` contains every search engine; `ai-crawlers`
|
|
58
|
-
contains agents some operators want (for example `ChatGPT-User` or
|
|
59
|
-
`Applebot-Extended` when a site chooses to appear in AI search). Keep an
|
|
60
|
-
explicit `allow` or `allowPatterns` entry for the agents you depend on, and
|
|
61
|
-
anchor it: `^Mozilla/5\.0 \(compatible; Googlebot` cannot be satisfied by a
|
|
62
|
-
`Googlebot` token dropped in the middle of an unrelated string.
|
|
63
|
-
|
|
64
|
-
Matching is string matching. A client can claim any `User-Agent`, and the
|
|
65
|
-
genuine search crawlers publish the way to check a claim (reverse DNS for
|
|
66
|
-
Googlebot, bingbot and Applebot; the IETF `web-bot-auth` HTTP Message
|
|
67
|
-
Signature drafts for newer agents). That verification needs network calls and
|
|
68
|
-
vendor data, so it belongs in a [host plugin](../archive/2026-09-19/SPIKE-EXTENSIONS.md) that
|
|
69
|
-
runs after this policy, not in the runtime.
|
|
70
|
-
|
|
71
|
-
## Bundled lists
|
|
72
|
-
|
|
73
|
-
| Name | Contents | Upstream | Licence | Refresh |
|
|
74
|
-
| --- | --- | --- | --- | --- |
|
|
75
|
-
| `ai-crawlers` | every agent in `robots.json` (AI training, AI search and assistant crawlers) | [ai-robots-txt/ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt) | MIT | pinned tag in `scripts/sync-agent-lists.ts` |
|
|
76
|
-
| `crawlers` | every crawler, bot and automated client in `crawler-user-agents.json` | [monperrus/crawler-user-agents](https://github.com/monperrus/crawler-user-agents) | MIT (revisions after 2016-11-07 only) | same |
|
|
77
|
-
| `seo` | `crawlers` entries tagged `seo` (backlink and rank trackers such as AhrefsBot, SemrushBot, MJ12bot) | derived from crawler-user-agents | MIT | same |
|
|
78
|
-
| `monitoring` | `crawlers` entries tagged `monitoring` (UptimeRobot, Pingdom, StatusCake, ...) | derived from crawler-user-agents | MIT | same |
|
|
79
|
-
|
|
80
|
-
Each list lives in `data/agents/<name>.json` as
|
|
81
|
-
`{ name, description, source: { repository, url, license, tag, commit, file, fetchedAt }, entries }`
|
|
82
|
-
with one entry per pattern: `{ name, pattern, source, sourceRevision, addedAt }`.
|
|
83
|
-
The generated `data/agents/index.js` mirrors the `[name, pattern]` pairs so the
|
|
84
|
-
policy module (which also runs inside the Cloudflare Worker) needs no
|
|
85
|
-
filesystem. The upstream licences are reproduced verbatim under
|
|
86
|
-
`data/agents/LICENSES/` and named in `NOTICE`, as Apache-2.0 section 4(d)
|
|
87
|
-
requires. `urlcode audit` and the runtime's policy inventory report the list
|
|
88
|
-
names, pattern counts and the pinned revision each list was built from, so a
|
|
89
|
-
deploy carries a known list version and a rollback rolls the list back too.
|
|
90
|
-
|
|
91
|
-
### Refreshing
|
|
92
|
-
|
|
93
|
-
```sh
|
|
94
|
-
node scripts/sync-agent-lists.ts # fetch pinned upstreams, validate, write data/agents/ (also: npm run sync:agents)
|
|
95
|
-
node scripts/sync-agent-lists.ts --check # exit 1 when the committed files are stale
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The script fetches each upstream at the tag and commit pinned in its `sources`
|
|
99
|
-
table, normalises entries to the schema above (an ai.robots.txt agent name
|
|
100
|
-
becomes an escaped literal pattern; a crawler-user-agents pattern is kept and
|
|
101
|
-
named by its literal prefix), validates every pattern against the subset below
|
|
102
|
-
(rewriting `{n,}` to `{n,64}`, and dropping and printing anything else),
|
|
103
|
-
preserves `addedAt` from the previous file or the upstream `addition_date`, and
|
|
104
|
-
writes the JSON lists, the index and the licence copies. To move to a newer
|
|
105
|
-
upstream release, change the `tag` and `commit` pins and rerun; the revision
|
|
106
|
-
appears in every entry and in the audit output. Refresh through an ordinary
|
|
107
|
-
pull request so the list diff is reviewed like code. Behind an HTTPS proxy,
|
|
108
|
-
set `NODE_USE_ENV_PROXY=1` so `fetch` honours `HTTPS_PROXY` and
|
|
109
|
-
`NODE_EXTRA_CA_CERTS`.
|
|
110
|
-
|
|
111
|
-
## Project lists
|
|
112
|
-
|
|
113
|
-
`deny` and `allow` also accept a project-relative path ending in `.json`:
|
|
114
|
-
|
|
115
|
-
```yaml
|
|
116
|
-
policies:
|
|
117
|
-
agents:
|
|
118
|
-
deny: [agents/deny.json]
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
The file is either an array of entries or `{ "entries": [...] }`, each entry
|
|
122
|
-
`{ "name": "curl", "pattern": "^curl/" }` (`name` optional; the other fields
|
|
123
|
-
of the bundled schema are ignored). It must stay inside the project, hold at
|
|
124
|
-
most 4096 entries and pass the same pattern validation as YAML patterns. The
|
|
125
|
-
log names the path as written. The file is read once at activation; the
|
|
126
|
-
Cloudflare build embeds its entries in the artifact so the Worker never reads a
|
|
127
|
-
file.
|
|
128
|
-
|
|
129
|
-
## The pattern subset
|
|
130
|
-
|
|
131
|
-
Patterns in `denyPatterns`, `allowPatterns` and every list file are validated
|
|
132
|
-
at activation. A pattern outside the subset fails activation with a
|
|
133
|
-
`ConfigError` naming the route, the key and the reason, so a project cannot
|
|
134
|
-
turn the matcher into a denial-of-service vector by editing YAML. Allowed:
|
|
135
|
-
|
|
136
|
-
- anchors `^` and `$`; literals; `.`
|
|
137
|
-
- escapes: `\d \w \s \D \W \S \b \B \t \n \r \f \v \0`, `\xHH`, `\uHHHH`, and
|
|
138
|
-
a backslash before any punctuation (`\.`, `\/`, `\(`, `\-`, ...)
|
|
139
|
-
- character classes `[...]` and `[^...]` with ranges and the escapes above
|
|
140
|
-
- groups `(...)` and `(?:...)`, and alternation `|`
|
|
141
|
-
- quantifiers `*`, `+`, `?`, `{n}` and `{n,m}` with `m <= 64`, on a single
|
|
142
|
-
atom (a literal, escape, class or `.`); `?` may also follow a group that
|
|
143
|
-
contains no quantifier, for optional words such as `(?:bot)?`
|
|
144
|
-
- at most 256 bytes
|
|
145
|
-
|
|
146
|
-
Rejected: backreferences (`\1`, `\k<name>`), lookahead and lookbehind, named
|
|
147
|
-
groups, unicode property escapes, `\c` control escapes, nested character
|
|
148
|
-
classes, `{n,}` and bounds above 64, lazy or stacked quantifiers (`+?`, `**`),
|
|
149
|
-
quantifiers on anchors, and `*`, `+` or `{n,m}` on a group (so `(a+)+` and
|
|
150
|
-
`(ab)*` fail). Each list is compiled into one alternated `RegExp` with the `i`
|
|
151
|
-
flag, so a request costs one pass per list rather than one per pattern.
|
|
152
|
-
|
|
153
|
-
## robots.txt
|
|
154
|
-
|
|
155
|
-
Denying an agent is not the same as asking it to stay away. Well-behaved
|
|
156
|
-
crawlers read `/robots.txt` (RFC 9309) before fetching anything, and the AI
|
|
157
|
-
crawlers in `ai-crawlers` are the ones that upstream tracks as respecting or
|
|
158
|
-
ignoring it. Generate one from the same bundled list with
|
|
159
|
-
[`site.robots`](../SITE.md) so the file and the policy cannot drift apart:
|
|
160
|
-
|
|
161
|
-
```yaml
|
|
162
|
-
site:
|
|
163
|
-
robots:
|
|
164
|
-
disallow: [ai-crawlers]
|
|
165
|
-
policies:
|
|
166
|
-
agents:
|
|
167
|
-
deny: [ai-crawlers]
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
This serves `/robots.txt` with one `User-agent:` line per list entry followed
|
|
171
|
-
by `Disallow: /`, then `User-agent: *` / `Allow: /`. A hand-written
|
|
172
|
-
`/robots.txt` `respond` route still works and takes precedence over the
|
|
173
|
-
generated one. The `agents` policy then enforces the same decision for clients
|
|
174
|
-
that ignore the file.
|
|
175
|
-
|
|
176
|
-
## Targets
|
|
177
|
-
|
|
178
|
-
| Target | Support |
|
|
179
|
-
| --- | --- |
|
|
180
|
-
| self-hosted (`urlcode serve`) | native |
|
|
181
|
-
| Vercel, AWS | native |
|
|
182
|
-
| Cloudflare | compiled: the build validates the policy, embeds the effective configuration (and any project list entries) in the artifact, and the Worker compiles it at startup without filesystem access |
|
package/docs/policies/cache.md
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# `policies.cache`
|
|
2
|
-
|
|
3
|
-
Named HTTP caching strategies, enforced on the host. A route picks one
|
|
4
|
-
strategy from a fixed catalogue; every row is a pattern with a name outside
|
|
5
|
-
this project and a defined header output, so a browser, a CDN and the
|
|
6
|
-
runtime's own origin memory cache all read the same thing. Explicit fields
|
|
7
|
-
(`maxAge`, `staleWhileRevalidate`, `staleIfError`, `cdnMaxAge`, `originTtl`)
|
|
8
|
-
override what a strategy implies.
|
|
9
|
-
|
|
10
|
-
```yaml
|
|
11
|
-
policies:
|
|
12
|
-
cache: { strategy: revalidate } # project default
|
|
13
|
-
routes:
|
|
14
|
-
/feed:
|
|
15
|
-
function: { source: feed.mjs }
|
|
16
|
-
policies:
|
|
17
|
-
cache: { strategy: swr, maxAge: 30, staleWhileRevalidate: 300, vary: [Accept-Language] }
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
A route may also write `cache` directly, next to `function` or `middleware`,
|
|
21
|
-
instead of nesting it under `policies`. The compiler expands it to
|
|
22
|
-
`policies.cache` before anything else reads the project ([policies](../POLICIES.md)),
|
|
23
|
-
so it is pure sugar over the form above and is refused alongside a direct
|
|
24
|
-
`policies.cache` on the same route:
|
|
25
|
-
|
|
26
|
-
```yaml
|
|
27
|
-
routes:
|
|
28
|
-
/feed:
|
|
29
|
-
function: { source: feed.mjs }
|
|
30
|
-
cache: { strategy: swr, maxAge: 30 } # same as policies: { cache: {...} }
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Strategies
|
|
34
|
-
|
|
35
|
-
| `strategy` | Emitted headers | Origin memory cache | Typical use |
|
|
36
|
-
|---|---|---|---|
|
|
37
|
-
| `no-store` | `Cache-Control: no-store` | off | personalized, secret-bearing |
|
|
38
|
-
| `revalidate` | `Cache-Control: no-cache`, `ETag` (a strong SHA-256 tag is computed for results without one); answers `304` to `If-None-Match`, and to `If-Modified-Since` when the result carries `Last-Modified` | off | HTML, anything that must be fresh but is cheap to validate |
|
|
39
|
-
| `public` | `Cache-Control: public, max-age=N` (`maxAge` required) | on only when `originTtl` > 0 | stable API answers, feeds |
|
|
40
|
-
| `immutable` | `Cache-Control: public, max-age=31536000, immutable` (RFC 8246; `maxAge` overrides the year) | off | content-hashed URLs only; refused elsewhere unless `force: true` |
|
|
41
|
-
| `swr` | `Cache-Control: public, max-age=N, stale-while-revalidate=M` (RFC 5861; both required) | on: fresh for `originTtl` (default `maxAge`), then stale served once | hot functions, link previews |
|
|
42
|
-
| `sie` | as `swr` plus `stale-if-error=K` (`staleIfError` required; `staleWhileRevalidate` optional) | on, as `swr` | keep answering during an upstream failure (headers only at the origin; see below) |
|
|
43
|
-
| `micro` | `Cache-Control: no-store` to clients | on: `originTtl` default 1 s, at most 5 s unless `force: true` | the NGINX micro-cache: absorb a thundering herd without changing what a browser sees |
|
|
44
|
-
| `cdn-only` | `Cache-Control: no-store` plus `CDN-Cache-Control: max-age=N` (RFC 9213; `cdnMaxAge` required) | off | let the CDN cache while browsers do not |
|
|
45
|
-
| `private` | `Cache-Control: private, max-age=N` (`maxAge` required) | off | per-user data a browser may keep |
|
|
46
|
-
|
|
47
|
-
`immutable` accepts a route whose pattern has a segment with eight or more
|
|
48
|
-
hex characters (`/app.3f2a9c1d.js`) or a parameter named like a digest
|
|
49
|
-
(`{hash}`, `{digest}`, `{sha}`, `{version}`, `{build}`, `{rev}`,
|
|
50
|
-
`{fingerprint}`). Anything else fails configuration with the route named
|
|
51
|
-
unless `force: true`.
|
|
52
|
-
|
|
53
|
-
## Who owns `Cache-Control`
|
|
54
|
-
|
|
55
|
-
Explicit beats strategy, in this order:
|
|
56
|
-
|
|
57
|
-
1. A `response.headers.cache-control` declared in YAML on the route is kept
|
|
58
|
-
as written; the strategy does not touch it.
|
|
59
|
-
2. An asset handler's `cacheControl` (`page`, `download`, `static`) is kept
|
|
60
|
-
when the cache policy is only inherited from the project or a profile.
|
|
61
|
-
When the route itself declares `policies.cache`, the strategy overrides it.
|
|
62
|
-
3. A handler result that already says `no-store` or `private` is kept and is
|
|
63
|
-
never stored: a personalized answer under a `public` route stays private.
|
|
64
|
-
4. Otherwise the strategy's header replaces whatever the handler emitted.
|
|
65
|
-
|
|
66
|
-
`Vary` is merged with the declared `vary` names (no duplicates, `*` left
|
|
67
|
-
alone), for every strategy, so the origin key and the wire header agree.
|
|
68
|
-
Compression adds `Accept-Encoding` to `Vary` after this policy.
|
|
69
|
-
|
|
70
|
-
## Origin memory cache
|
|
71
|
-
|
|
72
|
-
On for `swr`, `sie`, `micro`, and `public` with `originTtl` set. It is an
|
|
73
|
-
origin-side micro-cache, so a client's own `Cache-Control: no-cache` or
|
|
74
|
-
`max-age=0` request directive does not bypass it (RFC 9111 §5.2.1 lets an
|
|
75
|
-
origin decide that); conditional and range requests do, as below. Rules:
|
|
76
|
-
|
|
77
|
-
- Only `GET` and `HEAD` are looked up; only `GET` results are stored, and a
|
|
78
|
-
`HEAD` hit serves the `GET` entry's headers with an empty body and the
|
|
79
|
-
entry's `Content-Length`. A route whose `methods` exclude `GET` has the
|
|
80
|
-
origin cache off.
|
|
81
|
-
- Only statuses in `statuses` (default `200, 301, 302, 404, 410`) are stored.
|
|
82
|
-
- A request carrying `If-None-Match`, `If-Modified-Since`, `If-Match`,
|
|
83
|
-
`If-Unmodified-Since` or `Range` is never answered from the origin cache:
|
|
84
|
-
a stored entry is a full `200` representation and the handler owns
|
|
85
|
-
validators and ranges, so such requests always reach it (an asset route
|
|
86
|
-
keeps its own `304`, `412` and `206` answers).
|
|
87
|
-
- Never stored: results carrying `Set-Cookie`; routes declaring `secrets`;
|
|
88
|
-
results whose handler `Cache-Control` says `private` or `no-store`; bodies
|
|
89
|
-
larger than `maxBytes` (default 1 MiB).
|
|
90
|
-
- Key: route pattern, request path, query string and the values of the
|
|
91
|
-
declared `vary` request headers. The method is not part of the key so
|
|
92
|
-
`HEAD` shares the `GET` entry.
|
|
93
|
-
- Hits carry `Age` (RFC 9111). No non-standard headers are added.
|
|
94
|
-
- Concurrent misses for one key coalesce: the first request reaches the
|
|
95
|
-
handler, up to 64 others wait for its result, and any beyond that proceed
|
|
96
|
-
to the handler themselves. If the fill fails, waiters fall through to the
|
|
97
|
-
handler rather than receiving the error.
|
|
98
|
-
- Bounds: `maxEntries` per route configuration (default 10000) and 64 MiB of
|
|
99
|
-
bodies across the whole runtime; the least recently used entry is evicted
|
|
100
|
-
first. The store belongs to one runtime instance and is dropped on close and
|
|
101
|
-
reload, so a deploy never serves the previous code's output.
|
|
102
|
-
- Stored entries hold the handler's headers as they were after YAML
|
|
103
|
-
`response.headers` and this policy ran, before security headers and
|
|
104
|
-
compression. Those run again on every hit, so a hit is compressed and
|
|
105
|
-
hardened the same way as a miss. Bodies are stored uncompressed.
|
|
106
|
-
|
|
107
|
-
### `swr` at the origin: stale served once, next request refreshes
|
|
108
|
-
|
|
109
|
-
A policy has no handle to the route's handler, so it cannot revalidate in the
|
|
110
|
-
background. The origin-side approximation is: a request that finds an entry
|
|
111
|
-
past `originTtl` but within `staleWhileRevalidate` is answered from the stale
|
|
112
|
-
entry immediately, and the entry is flagged so the next request for that key
|
|
113
|
-
goes to the handler and replaces it. If that refresh fails the flag is
|
|
114
|
-
cleared and the stale entry may be served once more, as long as it is within
|
|
115
|
-
the window. Beyond `max-age + stale-while-revalidate` nothing is served from
|
|
116
|
-
memory. Clients and CDNs that honour RFC 5861 do their own background
|
|
117
|
-
revalidation from the emitted header.
|
|
118
|
-
|
|
119
|
-
### `sie` limitation
|
|
120
|
-
|
|
121
|
-
`stale-if-error` is header-only at the origin. Policies observe errors but
|
|
122
|
-
cannot replace a thrown error with a result (the runtime rethrows), so a
|
|
123
|
-
handler failure is not answered from a stale entry by this runtime. Downstream
|
|
124
|
-
caches honouring RFC 5861 still serve stale on a 5xx. Origin-side
|
|
125
|
-
stale-if-error needs the plugin API or a runtime change that lets a policy's
|
|
126
|
-
error hook return a result.
|
|
127
|
-
|
|
128
|
-
## Targets
|
|
129
|
-
|
|
130
|
-
| Target | Support | Note |
|
|
131
|
-
|---|---|---|
|
|
132
|
-
| node | native | headers and origin cache per runtime instance |
|
|
133
|
-
| vercel | native | headers; the origin cache is per function instance, so hit rates depend on instance reuse |
|
|
134
|
-
| aws | native | headers; the origin cache is per Lambda instance |
|
|
135
|
-
| cloudflare | refused | the Worker build has no policy runtime; activation fails with the route named |
|
|
136
|
-
|
|
137
|
-
## Diagnostics
|
|
138
|
-
|
|
139
|
-
`describe()` (in `urlcode test`, the readiness plan and plugin activation)
|
|
140
|
-
reports `strategy`, the emitted `cacheControl` string (or `explicit response
|
|
141
|
-
header` / `asset handler` when something else owns it), `cdnCacheControl`,
|
|
142
|
-
`originTtl`, `staleWhileRevalidate`, `staleIfError`, `vary` and whether the
|
|
143
|
-
origin cache is `on`.
|
|
144
|
-
|
|
145
|
-
Log events, through the runtime's `log`:
|
|
146
|
-
|
|
147
|
-
```json
|
|
148
|
-
{ "event": "cache", "route": "/feed", "outcome": "hit" }
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
`outcome` is `hit`, `stale`, `miss` or `store`. The route is the configured
|
|
152
|
-
pattern, never the request path or key.
|