@jimhoyd/urlcode 0.4.0-alpha.1 → 0.4.0-alpha.3

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.
Files changed (232) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +36 -22
  2. package/.claude/skills/urlcode-operations/SKILL.md +16 -22
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CONTRIBUTING.md +20 -16
  5. package/README.md +83 -80
  6. package/ROADMAP.md +61 -331
  7. package/SECURITY.md +23 -8
  8. package/dist/BUILD-MANIFEST.json +42 -47
  9. package/dist/agents-guide.js +18 -18
  10. package/dist/build-static.js +136 -0
  11. package/dist/capabilities.js +85 -31
  12. package/dist/capability-query.js +0 -1
  13. package/dist/cli.js +24 -32
  14. package/dist/compliance-rules/baseline.js +2 -10
  15. package/dist/compliance-rules/privacy.js +5 -16
  16. package/dist/compliance-rules/shared.js +0 -2
  17. package/dist/compliance.js +6 -8
  18. package/dist/config.js +15 -8
  19. package/dist/context.js +9 -10
  20. package/dist/examples.js +2 -2
  21. package/dist/explain-cli.js +7 -5
  22. package/dist/explain.js +10 -5
  23. package/dist/extensions.js +61 -2
  24. package/dist/function-sources.js +34 -2
  25. package/dist/function-worker.js +3 -1
  26. package/dist/functions.js +84 -13
  27. package/dist/guest-api.js +29 -3
  28. package/dist/index.js +3 -6
  29. package/dist/manifest.js +11 -7
  30. package/dist/mcp-authoring.js +2 -2
  31. package/dist/mcp.js +1 -1
  32. package/dist/observability.js +1 -21
  33. package/dist/policies/cache.js +8 -3
  34. package/dist/policies.js +3 -1
  35. package/dist/policy.js +28 -9
  36. package/dist/prerender.js +4 -0
  37. package/dist/project-tests.js +3 -3
  38. package/dist/readiness.js +35 -9
  39. package/dist/route-diff.js +12 -5
  40. package/dist/router.js +5 -7
  41. package/dist/runtime.js +77 -58
  42. package/dist/sandbox.js +48 -0
  43. package/dist/scaffold.js +0 -0
  44. package/dist/scripts/operational-drills.js +12 -54
  45. package/dist/server.js +3 -29
  46. package/dist/tooling.js +1 -1
  47. package/dist/trusted-functions.js +210 -0
  48. package/dist/types/build-static.d.ts +43 -0
  49. package/dist/types/capabilities.d.ts +14 -5
  50. package/dist/types/compliance-rules/shared.d.ts +0 -2
  51. package/dist/types/compliance.d.ts +0 -3
  52. package/dist/types/config.d.ts +2 -1
  53. package/dist/types/context.d.ts +2 -1
  54. package/dist/types/examples.d.ts +1 -1
  55. package/dist/types/explain.d.ts +6 -0
  56. package/dist/types/extensions.d.ts +55 -0
  57. package/dist/types/function-sources.d.ts +4 -0
  58. package/dist/types/functions.d.ts +48 -5
  59. package/dist/types/guest-api.d.ts +1 -0
  60. package/dist/types/index.d.ts +3 -6
  61. package/dist/types/manifest.d.ts +5 -3
  62. package/dist/types/observability.d.ts +1 -14
  63. package/dist/types/project-tests.d.ts +1 -2
  64. package/dist/types/readiness.d.ts +12 -3
  65. package/dist/types/router.d.ts +2 -1
  66. package/dist/types/runtime.d.ts +0 -27
  67. package/dist/types/sandbox.d.ts +12 -0
  68. package/dist/types/scaffold.d.ts +0 -2
  69. package/dist/types/server.d.ts +1 -4
  70. package/dist/types/tooling.d.ts +3 -3
  71. package/dist/types/trusted-functions.d.ts +29 -0
  72. package/dist/types/types.d.ts +22 -8
  73. package/dist/types/verify-deployment.d.ts +2 -2
  74. package/dist/types.js +25 -5
  75. package/dist/typescript-authoring.js +67 -18
  76. package/dist/verify-deployment.js +3 -3
  77. package/docs/AI-AUTHORING.md +109 -10
  78. package/docs/ASSETS.md +2 -1
  79. package/docs/AUTH-BACKUP.md +32 -0
  80. package/docs/AWS.md +4 -4
  81. package/docs/BEST-PRACTICES.md +17 -8
  82. package/docs/CAPABILITIES.md +30 -17
  83. package/docs/CAPACITY.md +128 -31
  84. package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
  85. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
  86. package/docs/CI.md +8 -3
  87. package/docs/CLOUDFLARE.md +1 -2
  88. package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
  89. package/docs/COMPLIANCE.md +6 -9
  90. package/docs/DEPLOYMENT-CHECKS.md +1 -1
  91. package/docs/DEVELOPMENT-PIPELINE.md +181 -0
  92. package/docs/EXTENSIONS.md +193 -11
  93. package/docs/FRAMEWORK.md +53 -33
  94. package/docs/FUNCTION-SECURITY.md +173 -32
  95. package/docs/INSTALL.md +0 -5
  96. package/docs/LOAD-TESTING.md +4 -4
  97. package/docs/LOCAL-DEVELOPMENT.md +3 -0
  98. package/docs/MIDDLEWARE.md +27 -16
  99. package/docs/MONITORING.md +2 -19
  100. package/docs/OBSERVABILITY.md +7 -16
  101. package/docs/OPEN-DECISIONS.md +184 -0
  102. package/docs/OPERATIONAL-PROOF.md +26 -30
  103. package/docs/OPERATIONS.md +23 -32
  104. package/docs/POLICIES.md +27 -8
  105. package/docs/PRERENDER.md +25 -13
  106. package/docs/PROJECT-DIRECTION.md +35 -10
  107. package/docs/READINESS.md +6 -2
  108. package/docs/README.md +20 -41
  109. package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
  110. package/docs/RELEASE-READINESS.md +50 -30
  111. package/docs/RELEASE-SECURITY.md +72 -86
  112. package/docs/RESILIENCE.md +16 -15
  113. package/docs/ROUTING.md +7 -10
  114. package/docs/SANDBOX-REVIEW.md +19 -6
  115. package/docs/SCAFFOLDING.md +0 -2
  116. package/docs/SECURITY-AUDIT.md +42 -2
  117. package/docs/SPECIFICATION.md +63 -27
  118. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +288 -0
  119. package/docs/SPIKE-BUSINESS-SUITE.md +16 -0
  120. package/docs/SPIKE-CORE-LAYERING.md +368 -0
  121. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +211 -0
  122. package/docs/SPIKE-LAMBDA-COMPILE.md +12 -2
  123. package/docs/STARTERS.md +1 -1
  124. package/docs/STATIC.md +105 -0
  125. package/docs/TOOLING.md +17 -12
  126. package/docs/TUNNELS.md +0 -3
  127. package/docs/TYPESCRIPT-AUTHORING.md +32 -12
  128. package/docs/TYPESCRIPT.md +25 -4
  129. package/docs/VERCEL.md +4 -5
  130. package/docs/VERSION-ALIGNMENT.md +46 -0
  131. package/docs/YAML-GUIDE.md +0 -3
  132. package/docs/YAML-REFERENCE.md +16 -6
  133. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
  134. package/docs/{MANAGEMENT-SECURITY.md → archive/2026-09-19/MANAGEMENT-SECURITY.md} +20 -0
  135. package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +26 -8
  136. package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +274 -43
  137. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
  138. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
  139. package/docs/archive/2026-09-19/ROADMAP.md +386 -0
  140. package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +12 -2
  141. package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +28 -12
  142. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
  143. package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +18 -2
  144. package/docs/archive/README.md +27 -0
  145. package/docs/policies/agents.md +1 -1
  146. package/docs/policies/cache.md +13 -0
  147. package/docs/policies/compression.md +3 -2
  148. package/docs/policies/security.md +3 -2
  149. package/examples/assets/example.yaml +2 -2
  150. package/examples/aws/example.yaml +1 -1
  151. package/examples/cloudflare/example.yaml +1 -1
  152. package/examples/conditions/example.yaml +1 -1
  153. package/examples/cookbook/example.yaml +1 -1
  154. package/examples/cookbook/middleware/bucket.mjs +12 -2
  155. package/examples/cookbook/middleware/locale.mjs +7 -3
  156. package/examples/cookbook/route-index.json +1 -1
  157. package/examples/cookbook/routes/middleware.yaml +1 -1
  158. package/examples/egress/example.yaml +1 -1
  159. package/examples/extensions/example.yaml +1 -1
  160. package/examples/prerender/README.md +14 -6
  161. package/examples/prerender/example.yaml +1 -1
  162. package/examples/prerender/functions/page.mjs +4 -2
  163. package/examples/prerender/middleware/template.mjs +1 -1
  164. package/examples/prerender/prerender.mjs +1 -1
  165. package/examples/prerender/urlcode.yaml +8 -4
  166. package/examples/provider-conformance/example.yaml +1 -1
  167. package/examples/vercel/example.yaml +1 -1
  168. package/llms-full.txt +686 -239
  169. package/llms.txt +27 -15
  170. package/package.json +32 -5
  171. package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
  172. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +36 -22
  173. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +16 -22
  174. package/recipes/authenticated-json-api/README.md +4 -3
  175. package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
  176. package/recipes/authenticated-json-api/recipe.yaml +2 -2
  177. package/recipes/contact-form/functions/contact.mjs +2 -1
  178. package/recipes/contact-form/recipe.yaml +3 -3
  179. package/recipes/cors-api/README.md +2 -2
  180. package/recipes/cors-api/recipe.yaml +2 -2
  181. package/recipes/health-page/README.md +1 -1
  182. package/recipes/health-page/recipe.yaml +1 -1
  183. package/recipes/json-api/README.md +1 -1
  184. package/recipes/json-api/recipe.yaml +4 -4
  185. package/recipes/middleware/README.md +8 -4
  186. package/recipes/middleware/middleware/bucket.mjs +12 -2
  187. package/recipes/middleware/middleware/locale.mjs +7 -3
  188. package/recipes/middleware/recipe.yaml +1 -1
  189. package/recipes/protected-download/README.md +1 -1
  190. package/recipes/protected-download/recipe.yaml +2 -2
  191. package/recipes/redirect/recipe.yaml +1 -1
  192. package/recipes/static-plus-api/README.md +2 -2
  193. package/recipes/static-plus-api/public/index.html +1 -1
  194. package/recipes/static-plus-api/recipe.yaml +2 -2
  195. package/recipes/static-plus-api/urlcode.yaml +1 -1
  196. package/recipes/typescript/README.md +2 -1
  197. package/recipes/typescript/recipe.yaml +5 -5
  198. package/recipes/webhook-receiver/README.md +5 -1
  199. package/recipes/webhook-receiver/recipe.yaml +2 -1
  200. package/recipes/webhook-receiver/urlcode.yaml +7 -0
  201. package/schemas/recipe.schema.json +4 -3
  202. package/schemas/urlcode.schema.json +14 -41
  203. package/skills/urlcode/SKILL.md +32 -11
  204. package/starters/default/AGENTS.md +18 -18
  205. package/starters/default/urlcode.yaml +0 -1
  206. package/dist/link-api.js +0 -136
  207. package/dist/link-cli.js +0 -141
  208. package/dist/link-events.js +0 -76
  209. package/dist/link-records.js +0 -31
  210. package/dist/link-store-worker.js +0 -150
  211. package/dist/link-store.js +0 -250
  212. package/dist/management-policy.js +0 -40
  213. package/dist/sqlite-version.js +0 -6
  214. package/dist/types/link-api.d.ts +0 -30
  215. package/dist/types/link-cli.d.ts +0 -37
  216. package/dist/types/link-events.d.ts +0 -27
  217. package/dist/types/link-records.d.ts +0 -11
  218. package/dist/types/link-store-worker.d.ts +0 -1
  219. package/dist/types/link-store.d.ts +0 -130
  220. package/dist/types/management-policy.d.ts +0 -8
  221. package/dist/types/sqlite-version.d.ts +0 -1
  222. package/docs/DYNAMIC-LINKS.md +0 -61
  223. package/docs/links/cli.md +0 -110
  224. package/docs/links/limits.md +0 -175
  225. package/docs/links/management-api.md +0 -80
  226. package/docs/links/pools.md +0 -75
  227. package/docs/links/setup.md +0 -135
  228. package/docs/yaml/links.md +0 -30
  229. package/examples/live-links/README.md +0 -11
  230. package/examples/live-links/example.yaml +0 -21
  231. package/examples/live-links/tests/requests.json +0 -6
  232. package/examples/live-links/urlcode.yaml +0 -16
package/llms.txt CHANGED
@@ -1,20 +1,31 @@
1
1
  # URLCode
2
2
 
3
3
  > A portable runtime for programmable URL behavior, and the framework that grows
4
- > from it: routes in YAML, isolated functions, live links, then accounts and
5
- > administration as operator-installed extensions. Stable project format
6
- > `version: "1"`. Core is Apache-2.0; `0.4.0-alpha.1` is the current alpha on top of
4
+ > from it: routes in YAML, functions and middleware, then accounts, administration and
5
+ > stored links as operator-installed extensions. Stable project format
6
+ > `version: "1"`. Core is Apache-2.0; this revision is `0.4.0-alpha.2`, which makes
7
+ > `function`/`middleware` routes trusted by default with `sandbox: true` as the
8
+ > per-route opt-in; `0.4.0-alpha.1` is the newest alpha published to npm, on top of
7
9
  > the `0.3.0` release. The auth, admin and ui extension packages are on npm as
8
- > `0.1.0-alpha.1`, source-complete, review pending.
10
+ > `0.1.0-alpha.x`, source-complete, review pending.
9
11
 
10
12
  Use the schema and docs from the runtime revision you run. Do not assume Node
11
- or fetch in functions, regex routes, database access, global middleware, YAML
12
- interpolation, or packages named in YAML. Secrets need external revision-pinned
13
+ or fetch inside a `sandbox: true` function, regex routes, database access,
14
+ global middleware, YAML interpolation, or packages named in YAML. Secrets need external revision-pinned
13
15
  grants. Unsupported features fail with the route named; nothing degrades silently.
14
16
 
15
17
  Agents that explicitly want the complete consolidated reference in one fetch should read
16
18
  [llms-full.txt](llms-full.txt), generated from the documents below (about 50k tokens, estimated).
17
19
 
20
+ ## Declarative-first default
21
+
22
+ > Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement.
23
+
24
+ Check the installed version's primitives, YAML configuration, policies, supported
25
+ extensions and recipes/templates before writing a custom function or middleware.
26
+ Keep necessary custom code focused and report the capability gap; never invent
27
+ fields or bypass target limits or operator grants. See [the design principle](docs/PROJECT-DIRECTION.md#design-principle-declarative-first).
28
+
18
29
  ## Authoring
19
30
  - [AI authoring contract](docs/AI-AUTHORING.md): workflow, capability matrix, checks.
20
31
  - [Authoring skill](.claude/skills/urlcode-authoring/SKILL.md): loadable authoring skill shipped with this revision; see [distribution](docs/AI-AUTHORING.md#agent-skills).
@@ -24,12 +35,11 @@ Agents that explicitly want the complete consolidated reference in one fetch sho
24
35
  - [Field reference](docs/YAML-REFERENCE.md): generated exhaustive field inventory.
25
36
  - [Semantics](docs/SPECIFICATION.md): validation, defaults and sandbox API.
26
37
  - [Runnable cookbook](examples/cookbook/README.md): 25 routes with HTTP fixtures.
27
- - [Dynamic links](docs/DYNAMIC-LINKS.md): optional SQLite, live mutations, management API and limits.
28
38
  - [Route matching](docs/ROUTING.md): precedence, non-greedy parameters, updates.
29
39
  - [Middleware](docs/MIDDLEWARE.md): next(), state, ordering and native body limits.
30
40
  - [Policies](docs/POLICIES.md): optional host-enforced `policies`/`profiles` keys, all off by default: `throttle`, `agents`, `security`, `compression`, `cache`; merge rules and per-target support.
31
41
  - [Plugins](docs/PLUGINS.md): host hook API operators pass in code; never named in YAML.
32
- - [TypeScript](docs/TYPESCRIPT.md): the package ships declarations for every export (`urlcode`, `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender`, `@jimhoyd/urlcode/aws`, `@jimhoyd/urlcode/vercel`, `@jimhoyd/urlcode/cloudflare`); the runtime source is TypeScript, `dist/` is its stripped JavaScript.
42
+ - [TypeScript](docs/TYPESCRIPT.md): the package ships declarations for every export (`urlcode`, `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender`, `@jimhoyd/urlcode/sandbox`, `@jimhoyd/urlcode/aws`, `@jimhoyd/urlcode/vercel`, `@jimhoyd/urlcode/cloudflare`); the runtime source is TypeScript, `dist/` is its stripped JavaScript.
33
43
  - [HTTP](docs/HTTP.md): methods, request bodies and response headers.
34
44
  - [Assets](docs/ASSETS.md): pages, MIME, downloads, cache and ranges.
35
45
  - [Site conventions](docs/SITE.md): optional top-level `site` key, all off by default: `robots`, `sitemap`, `favicon`, `securityTxt`, `llms` generate native routes; declared routes win; absolute URLs need `--origin`.
@@ -46,10 +56,9 @@ Agents that explicitly want the complete consolidated reference in one fetch sho
46
56
  ## Routes and handlers
47
57
  - [Routing](docs/ROUTING.md): exact and `{param}` paths, precedence, `/*` only on static and extension mounts.
48
58
  - [HTTP](docs/HTTP.md): methods, validated inputs, bodies, response headers, cookies.
49
- - [Function security](docs/FUNCTION-SECURITY.md): QuickJS/WASM sandbox, Request/Response subset, operator grants.
59
+ - [Function security](docs/FUNCTION-SECURITY.md): trusted and unsandboxed by default, `sandbox: true` QuickJS/WASM opt-in, Request/Response subset, operator grants.
50
60
  - [Middleware](docs/MIDDLEWARE.md) and [examples](docs/MIDDLEWARE-EXAMPLES.md): `next()`, state, ordering; also `urlcode recipes add middleware`.
51
61
  - [Assets](docs/ASSETS.md): pages, static, downloads, MIME, ranges. [Prerender](docs/PRERENDER.md): render once, no request-time code.
52
- - [Dynamic links](docs/DYNAMIC-LINKS.md): `link` handler, SQLite store, CLI, private management API.
53
62
  - [Conditions](docs/CONDITIONS.md): exact predicates, disjoint redirect/respond cases, no-store.
54
63
  - [Egress](docs/EGRESS.md): bounded HTTPS proxy and best-effort signals behind operator grants; self-hosted only.
55
64
  - [Policies](docs/POLICIES.md): `throttle`, `agents`, `security`, `compression`, `cache`; all off unless declared. [Site](docs/SITE.md): robots, sitemap, favicon, security.txt, llms.txt.
@@ -57,19 +66,22 @@ Agents that explicitly want the complete consolidated reference in one fetch sho
57
66
  - [Interchange](docs/INTERCHANGE.md), [bulk import](docs/BULK.md), [recipes](docs/RECIPES.md) (`urlcode recipes search`, `examples search`), [TypeScript guests](docs/TYPESCRIPT-AUTHORING.md).
58
67
 
59
68
  ## Extensions (accounts, administration, presentation)
60
- - [Extensions](docs/EXTENSIONS.md): `extensions.<name>` blocks, `extension` mounts, `policies.extensions`, the operator host file, `@jimhoyd/urlcode/extensions`.
61
- - [urlcode-auth](https://github.com/jimhoyd-com/urlcode-auth): npm: @jimhoyd/urlcode-auth; accounts, sessions, MFA, roles, account page; its own llms.txt.
62
- - [urlcode-admin](https://github.com/jimhoyd-com/urlcode-admin): npm: @jimhoyd/urlcode-admin; users, sessions, roles, audit, cases; its own llms.txt.
63
- - [urlcode-ui](https://github.com/jimhoyd-com/urlcode-ui): npm: @jimhoyd/urlcode-ui; escaped templates, shadcn/ui partials, themes, translations; its own llms.txt.
69
+ - [Extensions](docs/EXTENSIONS.md): `extensions.<name>` blocks, `extension` mounts, `policies.extensions`, the operator host file, `@jimhoyd/urlcode/extensions`. Project-level lifecycle hooks run trusted via plain `import()`, or sandboxed via `@jimhoyd/urlcode/sandbox`'s `SandboxPool`.
70
+ - [urlcode-auth](packages/auth): npm: @jimhoyd/urlcode-auth; accounts, sessions, MFA, roles, account page; its own llms.txt. Lives in this repository as a workspace package.
71
+ - [urlcode-admin](packages/admin): npm: @jimhoyd/urlcode-admin; users, sessions, roles, audit, cases; its own llms.txt. Lives in this repository as a workspace package.
72
+ - [urlcode-ui](packages/ui): npm: @jimhoyd/urlcode-ui; escaped templates, shadcn/ui partials, themes, translations; its own llms.txt. Lives in this repository as a workspace package, not in a separate repository.
73
+ - urlcode-middleware (retired): per-route middleware briefly lived here as an extension-seam alternative; the package was unpublished at 0.1.0-alpha.2 and its repository deleted. This is not a gap — per-route middleware is native to core via the `middleware:` array (docs/MIDDLEWARE.md); use that.
74
+ - urlcode-dynamic-link (retired): stored short links briefly lived here as a mount-based extension; the package was unpublished and its repository deleted. Core has no native `link` handler and no supported extension provides stored links — report them as a gap.
64
75
  - [Plugins](docs/PLUGINS.md): host hook API in operator code, never named in YAML.
65
76
 
66
77
  ## Tooling and API
67
78
  - [Tooling SDK and MCP](docs/TOOLING.md): `urlcode mcp`, read-only inspection, validation, conversion previews.
68
- - [TypeScript](docs/TYPESCRIPT.md): declarations for `@jimhoyd/urlcode` and its `/plugins`, `/policies`, `/observability`, `/compliance`, `/prerender`, `/extensions`, `/aws`, `/vercel`, `/cloudflare` entries.
79
+ - [TypeScript](docs/TYPESCRIPT.md): declarations for `@jimhoyd/urlcode` and its `/plugins`, `/policies`, `/observability`, `/compliance`, `/prerender`, `/extensions`, `/sandbox`, `/aws`, `/vercel`, `/cloudflare` entries.
69
80
  - [Capabilities](docs/CAPABILITIES.md): per-target support; `urlcode capabilities --target NAME`.
70
81
 
71
82
  ## Operations
72
83
  - [Operations](docs/OPERATIONS.md), [install](docs/INSTALL.md), [deployment checks](docs/DEPLOYMENT-CHECKS.md), [CI action](docs/CI.md).
73
84
  - [Readiness](docs/READINESS.md), [capacity](docs/CAPACITY.md), [performance](docs/PERFORMANCE.md), [resilience](docs/RESILIENCE.md), [observability](docs/OBSERVABILITY.md).
74
85
  - [Vercel](docs/VERCEL.md), [AWS](docs/AWS.md), [Cloudflare](docs/CLOUDFLARE.md): adapters with local conformance tests; no provider deployment verified yet.
86
+ - [Static hosting](docs/STATIC.md): `urlcode build --target static` compiles redirects/pages/static/downloads to S3 + CloudFront objects and redirect metadata; no server, so function/middleware/extension/proxy/signals/conditions/parameters/bindings/policies are all refused; GitHub Pages is explicitly out of scope.
75
87
  - [Release readiness](docs/RELEASE-READINESS.md), [roadmap](ROADMAP.md): what is proven, what is planned.
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@jimhoyd/urlcode",
3
- "version": "0.4.0-alpha.1",
3
+ "version": "0.4.0-alpha.3",
4
4
  "description": "Portable runtime for programmable URL behavior",
5
5
  "type": "module",
6
+ "workspaces": [
7
+ "packages/*"
8
+ ],
6
9
  "license": "Apache-2.0",
7
10
  "engines": {
8
11
  "node": ">=22.13.0"
@@ -69,6 +72,11 @@
69
72
  "development": "./src/extensions.ts",
70
73
  "default": "./dist/extensions.js"
71
74
  },
75
+ "./sandbox": {
76
+ "types": "./dist/types/sandbox.d.ts",
77
+ "development": "./src/sandbox.ts",
78
+ "default": "./dist/sandbox.js"
79
+ },
72
80
  "./package.json": "./package.json"
73
81
  },
74
82
  "files": [
@@ -96,8 +104,10 @@
96
104
  "typecheck": "tsc -p tsconfig.json",
97
105
  "test": "node --conditions=development --test test/*.test.ts",
98
106
  "lint": "eslint .",
99
- "check": "node scripts/check.ts && node scripts/generate-yaml-reference.ts --check && node scripts/build-llms-full.ts --check && node scripts/build-cookbook-index.ts --check && node scripts/generate-claude-plugin.ts --check",
100
- "verify": "npm run lint && npm run typecheck && npm run check && npm run build && npm test",
107
+ "check": "node scripts/check.ts && node scripts/check-trust-model-prose.ts && node scripts/check-guidance-claims.ts && node scripts/check-release-tags.ts && node scripts/check-workspace-links.ts && node scripts/generate-yaml-reference.ts --check && node scripts/build-llms-full.ts --check && node scripts/build-cookbook-index.ts --check && node scripts/generate-claude-plugin.ts --check && npm run release:check",
108
+ "verify": "npm run lint && npm run typecheck && npm run check && npm run build && npm test && npm run verify:workspaces",
109
+ "workspace:styles": "npm run styles --workspace @jimhoyd/urlcode-ui",
110
+ "verify:workspaces": "npm run verify --workspace @jimhoyd/urlcode-ui && npm run verify --workspace @jimhoyd/urlcode-auth && npm run verify --workspace @jimhoyd/urlcode-admin && npm run test:workspace-integration",
101
111
  "benchmark": "node benchmarks/routing.ts",
102
112
  "test:package": "npm run build && node scripts/package-smoke.ts",
103
113
  "dev": "node src/cli.ts dev --project starters/default",
@@ -113,9 +123,23 @@
113
123
  "docs:plugin": "node scripts/generate-claude-plugin.ts",
114
124
  "docs:llms": "node scripts/build-llms-full.ts",
115
125
  "docs:cookbook-index": "node scripts/build-cookbook-index.ts",
126
+ "check:downstream-skills": "node scripts/check-downstream-skill-drift.ts",
116
127
  "benchmark:bulk": "node benchmarks/bulk.ts",
128
+ "benchmark:sandbox-vs-trusted": "node benchmarks/sandbox-vs-trusted.ts",
117
129
  "benchmark:agent": "node benchmarks/agent/run.ts",
118
- "sync:agents": "node scripts/sync-agent-lists.ts"
130
+ "sync:agents": "node scripts/sync-agent-lists.ts",
131
+ "check:docs": "node scripts/check-trust-model-prose.ts && node scripts/check-guidance-claims.ts && node scripts/generate-yaml-reference.ts --check && node scripts/build-llms-full.ts --check && node scripts/build-cookbook-index.ts --check && node scripts/generate-claude-plugin.ts --check",
132
+ "ci:plan": "node scripts/ci-plan.ts",
133
+ "test:package:built": "node scripts/package-smoke.ts",
134
+ "release:status": "node scripts/release.ts status",
135
+ "release:plan": "node scripts/release.ts plan",
136
+ "release:publish": "node scripts/release.ts publish",
137
+ "release:run": "node scripts/release-run.ts",
138
+ "release:peers": "node scripts/release.ts peers",
139
+ "release:check": "node scripts/release.ts check",
140
+ "ci:history": "node scripts/ci-history.ts",
141
+ "ci:report": "node scripts/ci-report.ts",
142
+ "test:workspace-integration": "node --test test/workspace-scaffold.integration.ts"
119
143
  },
120
144
  "repository": {
121
145
  "type": "git",
@@ -131,14 +155,17 @@
131
155
  "yaml": "2.9.1"
132
156
  },
133
157
  "devDependencies": {
158
+ "@changesets/cli": "3.0.3",
134
159
  "@eslint/js": "10.0.1",
135
160
  "@types/mime-types": "3.0.1",
136
161
  "@types/node": "26.5.1",
162
+ "@types/semver": "7.8.0",
137
163
  "eslint": "10.10.0",
138
164
  "globals": "17.12.0",
165
+ "semver": "7.8.5",
139
166
  "typescript-eslint": "8.70.0"
140
167
  },
141
- "homepage": "https://github.com/jimhoyd-com/urlcode-docs",
168
+ "homepage": "https://github.com/jimhoyd-com/urlcode#readme",
142
169
  "bugs": {
143
170
  "url": "https://github.com/jimhoyd-com/urlcode/issues"
144
171
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "urlcode",
3
3
  "description": "Authoring and operating URLCode projects: the implemented YAML contract, capability limits, deployment and verification commands for the pinned runtime revision.",
4
- "version": "0.4.0-alpha.1",
4
+ "version": "0.4.0-alpha.3",
5
5
  "author": {
6
6
  "name": "jimhoyd-com",
7
7
  "url": "https://github.com/jimhoyd-com"
@@ -14,6 +14,6 @@
14
14
  "routing",
15
15
  "yaml",
16
16
  "redirects",
17
- "short-links"
17
+ "middleware"
18
18
  ]
19
19
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: urlcode-authoring
3
- description: Author or modify a URLCode project — write and edit urlcode.yaml routes, function and middleware modules, pages, static assets, downloads and stored links, then validate and test them. Use whenever a urlcode.yaml file is present or referenced, when the user mentions URLCode, @jimhoyd/urlcode, urlcode routes/handlers/policies/site keys, or asks for redirects, short links or request functions in a URLCode project. Loads the implemented capability matrix so unsupported features are reported as gaps instead of invented.
3
+ description: Author or modify a URLCode project — write and edit urlcode.yaml routes, function and middleware modules, pages, static assets and downloads, then validate and test them. Use whenever a urlcode.yaml file is present or referenced, when the user mentions URLCode, @jimhoyd/urlcode, urlcode routes/handlers/policies/site keys, or asks for redirects or request functions in a URLCode project. Loads the implemented capability matrix so unsupported features are reported as gaps instead of invented.
4
4
  ---
5
5
 
6
6
  # Authoring URLCode projects
@@ -11,23 +11,27 @@ validates. Features outside that contract do not silently degrade — they fail
11
11
  validation. So the cost of guessing is a broken project, and the whole job here
12
12
  is to author only what the pinned revision implements and then prove it.
13
13
 
14
+ ## Declarative-first default
15
+
16
+ > Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement.
17
+
18
+ Check the installed version's primitives, YAML configuration, policies, supported
19
+ extensions and recipes/templates before writing a custom function or middleware.
20
+ Keep necessary custom code focused and report the capability gap; never invent
21
+ fields or bypass target limits or operator grants. See `docs/PROJECT-DIRECTION.md` in the installed runtime.
22
+
14
23
  ## Read the contract before writing YAML
15
24
 
16
25
  Documentation, schema and runtime must come from the **same revision**. Read from
17
26
  the project's installed runtime (`node_modules/@jimhoyd/urlcode/`) or the
18
27
  checkout you are working in — never from memory of another version.
19
28
 
20
- 1. `docs/AI-AUTHORING.md` the authoring contract and the **capability matrix**
21
- of what is available versus unavailable. Read this first and in full.
22
- 2. `schemas/urlcode.schema.json` the exact accepted structure.
23
- 3. `docs/YAML-REFERENCE.md` and `docs/SPECIFICATION.md` every field, and the
24
- implemented semantics, defaults and sandbox API.
25
- 4. `docs/YAML-GUIDE.md` and `examples/cookbook/` recipes and runnable files.
26
- 5. `docs/ROUTING.md`, `docs/HTTP.md`, `docs/MIDDLEWARE.md`, `docs/ASSETS.md` —
27
- matching precedence, methods, composition, MIME and ranges.
28
- 6. `docs/FUNCTION-SECURITY.md` — the sandbox and operator binding policy.
29
-
30
- `llms.txt` at the repository root is a compact index of all of the above.
29
+ Start with `urlcode context --project <dir> --budget 4000`, then retrieve the
30
+ capability, schema fragment, recipe or example relevant to the change. Use the
31
+ read-only MCP equivalents when available. `llms.txt` is the index; read the
32
+ matching task guide from `docs/` when a query needs more explanation.
33
+ `docs/SPECIFICATION.md` and `schemas/urlcode.schema.json` resolve contract
34
+ questions. Archived plans are historical, not valid YAML guidance.
31
35
 
32
36
  ## Workflow
33
37
 
@@ -35,15 +39,16 @@ checkout you are working in — never from memory of another version.
35
39
  tests and the pinned runtime version. Preserve the user's organization,
36
40
  naming and unrelated routes.
37
41
  - Choose exactly one handler per route — `function`, `redirect`, `respond`,
38
- `page`, `static`, `download` or `link` plus optional ordered middleware.
39
- Prefer a native handler when code is unnecessary.
42
+ `page`, `static`, `download`, `conditional`, `proxy` or an `extension` mount
43
+ — plus optional ordered middleware. Prefer a native handler when code is
44
+ unnecessary.
40
45
  - Declare each path placeholder as a required string. Paths match whole
41
46
  segments: no regex, no greedy captures, no wildcard handlers.
42
47
  - Bind typed inputs through `args` or context. There is no `${...}`
43
48
  interpolation anywhere in the format.
44
49
  - Create every referenced module, page and asset **before** validating. All
45
- paths resolve from the project root; functions and middleware use relative
46
- ES-module imports only.
50
+ source paths resolve from the project root. Trusted modules can import Node built-ins and npm packages;
51
+ only `sandbox: true` modules are restricted to the relative snapshotted graph.
47
52
  - Write exact response fixtures for success and failure, covering every active
48
53
  method, middleware behavior, HEAD, and any range or cache semantics.
49
54
  - Follow `docs/BEST-PRACTICES.md` for layout and readability as the project grows.
@@ -56,11 +61,20 @@ mistakes that recur:
56
61
  - No YAML anchors, aliases, template interpolation or remote includes.
57
62
  - No recursive includes or glob discovery; includes are explicit.
58
63
  - No regex, optional or greedy route segments, and no host-based routing.
59
- - The sandbox is text/JSON `Request`/`Response` only: **no** `fetch`, Node or
60
- npm APIs, filesystem, WebSocket, streaming or crypto API.
64
+ - `function`/`middleware` routes run trusted and unsandboxed by default: full
65
+ Node, npm, filesystem and `fetch` access, in-process, like any other project
66
+ code. `sandbox: true` opts a route into isolation — reach for it when that
67
+ route's own code warrants it (unreviewed or third-party code, a secret whose
68
+ blast radius matters, complex logic), not reflexively on every route and
69
+ never merely because it handles request data -- that is untrusted in both
70
+ modes and must be validated either way. A
71
+ `sandbox: true` route gets a text/JSON `Request`/`Response` sandbox only:
72
+ **no** `fetch`, Node or npm APIs, filesystem, WebSocket, streaming or crypto
73
+ API.
61
74
  - No global middleware, Express compatibility or automatic auth.
62
75
  - `policies` accepts only `throttle`, `agents`, `security`, `compression` and
63
- `cache`, every key off unless declared; `hardened` is the only built-in
76
+ `cache`, plus registered extension requirements under `extensions`;
77
+ the built-in policies are off unless declared; `hardened` is the only built-in
64
78
  profile. Check the per-target table in `docs/POLICIES.md` before declaring
65
79
  one for a serverless or Cloudflare deployment — an unsupported policy refuses
66
80
  activation rather than degrading.
@@ -68,9 +82,9 @@ mistakes that recur:
68
82
  only and off unless declared; a declared route at the same path wins. Its
69
83
  generated routes count toward `--expect-routes`, and `site.sitemap` needs
70
84
  `--origin` on every command that activates the project.
71
- - `dynamicLinks: true` belongs in the entry `urlcode.yaml` only, and only for
72
- live `link` handlers not for includes, and not for parameterized redirects
73
- or functions.
85
+ - There is no native `link` handler or `dynamicLinks` project flag, and no
86
+ supported extension package provides one; report stored short links as a gap,
87
+ never invent a `link` field.
74
88
  - Infrastructure (proxy ranges, storage URLs, vendor rule identifiers) is an
75
89
  operator flag, never route YAML.
76
90
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: urlcode-operations
3
- description: Deploy, verify, monitor and operate a URLCode project — process/container deployment, release readiness, verifying a live deployment against the project, capacity/audit/benchmark, observability, DDoS/overload resilience, and private management (grants, live-link credentials). Use when the user asks to deploy, check readiness, verify a running deployment, size/benchmark a project, monitor it, plan for overload, or manage live links/bindings. Reports operational limits and unimplemented capabilities as gaps instead of inventing mitigations.
3
+ description: Deploy, verify, monitor and operate a URLCode project — process/container deployment, release readiness, verifying a live deployment against the project, capacity/audit/benchmark, observability, DDoS/overload resilience, and operator binding grants. Use when the user asks to deploy, check readiness, verify a running deployment, size/benchmark a project, monitor it, plan for overload, or manage bindings. Reports operational limits and unimplemented capabilities as gaps instead of inventing mitigations.
4
4
  ---
5
5
 
6
6
  # Operating a URLCode deployment
@@ -30,9 +30,7 @@ another version.
30
30
  overload and DDoS; what layer each defense belongs to.
31
31
  6. `docs/MONITORING.md` and `docs/OBSERVABILITY.md` — health/ready probes,
32
32
  logs, metrics format, what is and is not exported.
33
- 7. `docs/MANAGEMENT-SECURITY.md` — the private management API: credential
34
- policy shape, scope, loopback-only binding.
35
- 8. `docs/POLICIES.md` and `docs/FUNCTION-SECURITY.md` — per-target policy
33
+ 7. `docs/POLICIES.md` and `docs/FUNCTION-SECURITY.md` — per-target policy
36
34
  support and the operator binding-grant process, needed whenever a
37
35
  deployment or verification step touches either.
38
36
 
@@ -71,28 +69,25 @@ urlcode verify-deployment --project ./my-links --target https://links.example \
71
69
  Run the actual commands and report actual results, never "should work" or
72
70
  "should be reachable". `verify-deployment` needs a real target; do not
73
71
  simulate its output. In a runtime checkout, substitute `node src/cli.ts` for
74
- `urlcode`. Pass `--policy`/`--link-store` where a snapshot needs bindings
75
- already reviewed by the operator.
72
+ `urlcode`. Pass `--policy` where a snapshot needs bindings already reviewed
73
+ by the operator.
76
74
 
77
75
  ## Hard limits — report these as gaps, never invent around them
78
76
 
79
- - No provider adapters, automatic TLS/DNS, distributed rate limiting, metrics
80
- exporters or durable event delivery are included; these remain the
81
- operator's own infrastructure.
77
+ - Provider adapters exist with different capability limits; query
78
+ `urlcode capabilities --target NAME`. Automatic TLS/DNS, distributed rate
79
+ limiting, metrics exporters and durable delivery require operator infrastructure.
82
80
  - No orchestration, traffic switching or automated rollback; recovery is an
83
81
  explicit snapshot reload from a known-good artifact.
84
82
  - `verify-deployment` has no infrastructure access, uses no credential,
85
83
  follows no redirect and offers no `--insecure`. It cannot check anything a
86
84
  read-only HTTP probe cannot observe.
87
- - The private management API binds only `127.0.0.1`/`::1`; it is never meant
88
- to be exposed through a public proxy or container port mapping, and browser
89
- Origin requests are rejected regardless.
90
- - A management credentials policy is operator-owned, outside the application,
91
- never in YAML or Git, at most 64 KiB, mode 600, at most 128 credentials with
92
- explicit collection/action allowlists no wildcards.
93
- - Sandbox concurrency, worker slots and execution deadlines are shared across
94
- every programmable route in a snapshot; there is no per-route fairness or
95
- reserved capacity, and awaiting a guest timer still occupies a slot.
85
+ - Core has no durable store and no private management API of its own, and no
86
+ supported extension package provides stored short links.
87
+ - Only `sandbox: true` routes share the sandbox worker slots and forced
88
+ execution deadlines. Trusted routes run in Node under HTTP admission limits;
89
+ their cooperative timeout cannot stop blocking JavaScript. A guest timer still
90
+ occupies a sandbox slot. Size both modes from `docs/CAPACITY.md`.
96
91
  - `throttle` and `agents` policy counters are per instance, not distributed;
97
92
  they are a second layer behind the edge, never a replacement for it.
98
93
 
@@ -102,10 +97,9 @@ responsibility that covers it instead of inventing a flag.
102
97
 
103
98
  ## Boundaries
104
99
 
105
- - Never generate or approve an operator binding grant, or a management
106
- credentials policy, on the user's behalf. Both are the operator's own
107
- reviewed decision; produce the shape and let them fill in and store the
108
- real secret.
100
+ - Never generate or approve an operator binding grant on the user's behalf.
101
+ That is the operator's own reviewed decision; produce the shape and let
102
+ them fill in and store the real secret.
109
103
  - Keep every credential, token and policy file out of source, examples and
110
104
  Git. A synthetic example value is fine; a real one is never committed.
111
105
  - Do not deploy, expose a service, rotate a credential, or run
@@ -1,9 +1,10 @@
1
1
  # Authenticated JSON API
2
2
 
3
- `/api/profile` is a sandboxed function behind `auth: true`, the route-level short
3
+ `/api/profile` is a function behind `auth: true`, the route-level short
4
4
  form that expands to `policies.extensions.auth: {}`. The project declares the
5
- `auth` extension; it never chooses or loads the module that implements it. Authorization happens in trusted operator code before the guest
6
- runs, and the runtime withholds `Authorization` and `Cookie` from the sandbox.
5
+ `auth` extension; it never chooses or loads the module that implements it. Authorization happens in trusted operator code before the route's function
6
+ runs, and the host strips `Authorization` and `Cookie` before dispatch -- for
7
+ trusted and `sandbox: true` routes alike.
7
8
 
8
9
  This recipe does not activate on its own. Every command needs an operator host
9
10
  file outside the project plus the canonical origin:
@@ -1,5 +1,6 @@
1
1
  // The auth extension has already authorized this request. Credentials never
2
- // reach guest code: Authorization and Cookie are withheld from the sandbox.
2
+ // reach this function: the host strips Authorization and Cookie before
3
+ // dispatch, in both execution modes.
3
4
  export default function profile() {
4
5
  return Response.json({signedIn: true, profile: {name: 'Ada', plan: 'team'}});
5
6
  }
@@ -3,7 +3,7 @@ description: JSON endpoint protected by the operator-installed auth extension th
3
3
  tags: [auth, authenticated, protected, signed-in, bearer, json, api, function, extension, "401", private]
4
4
  complexity: advanced
5
5
  capabilities: [enabled, extension, function, methods, policies.extensions]
6
- targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
6
+ targets: {self-hosted: conditional, aws: refused, vercel: refused, cloudflare: refused, static: refused}
7
7
  routes: 1
8
8
  services:
9
9
  - name: auth extension
@@ -28,7 +28,7 @@ tests:
28
28
  - urlcode test --project . --host-file /operator/host.mjs --origin https://api.example.com
29
29
  - urlcode audit --project . --expect-routes 1 --host-file /operator/host.mjs --origin https://api.example.com
30
30
  behavior:
31
- - GET /api/profile without credentials answers 401 from the extension before the guest runs
31
+ - GET /api/profile without credentials answers 401 from the extension before the function runs
32
32
  - GET with the credential the extension accepts answers 200 JSON with Cache-Control no-store
33
33
  - HEAD mirrors both cases with an empty body; POST answers 405
34
34
  - the project declares the extension and the requirement; it never chooses or loads the implementing module
@@ -1,7 +1,8 @@
1
1
  // Field checks the runtime does not do: the body limit and JSON syntax are
2
2
  // enforced before this runs. The declared signal fires after a response with
3
3
  // a fixed payload (route, method, status); the message itself never leaves
4
- // the sandbox, so a form store or mailer must sit behind the granted hook.
4
+ // the signal, so a form store or mailer belongs behind the granted hook rather
5
+ // than an ad-hoc call from here.
5
6
  const emailPattern = /^[^\s@]{1,64}@[^\s@]{1,255}$/;
6
7
  export default async function contact(request) {
7
8
  const body = await request.json();
@@ -1,9 +1,9 @@
1
1
  id: contact-form
2
- description: Validate a JSON contact message in the sandbox and notify an operator-granted hook with a fixed signal after the response.
2
+ description: Validate a JSON contact message in a function and notify an operator-granted hook with a fixed signal after the response.
3
3
  tags: [contact, form, message, email, post, json, validation, signal, notify, hook, egress, "422"]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, function, methods, request.body, response.headers, signals]
6
- targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
7
7
  routes: 1
8
8
  services:
9
9
  - name: hook endpoint
@@ -30,4 +30,4 @@ behavior:
30
30
  - POST /contact with a valid name, email and message answers 202 {"accepted":true}
31
31
  - invalid fields answer 422 with one error line per field; a non-object body answers 422
32
32
  - form-encoded bodies answer 415 and GET answers 405
33
- - the signal carries route, method and status only, best effort; the message itself never leaves the sandbox
33
+ - the signal carries route, method and status only, best effort; the message itself is never included
@@ -3,7 +3,7 @@
3
3
  Run `urlcode validate --local --project .`, `urlcode test --project .` and
4
4
  `urlcode audit --project . --expect-routes 1`.
5
5
 
6
- `/api/items` returns JSON from a sandboxed function. `middleware/cors.mjs` runs
6
+ `/api/items` returns JSON from a function (trusted, the default). `middleware/cors.mjs` runs
7
7
  around it: it answers `OPTIONS` preflight itself with 204 and adds
8
8
  `Access-Control-Allow-Origin` to responses for origins in its allowlist. Other
9
9
  origins get `Vary: Origin` and no allow header, so browsers refuse them.
@@ -11,6 +11,6 @@ origins get `Vary: Origin` and no allow header, so browsers refuse them.
11
11
  Edit `allowedOrigins` in the middleware and the function body. The route must
12
12
  list `OPTIONS` in `methods`, or the runtime answers 405 before the middleware
13
13
  runs. CORS is not a host policy in this contract; middleware is the supported
14
- place for it, and it needs the sandbox, so serverless targets refuse this
14
+ place for it, and middleware needs the self-hosted Node lifecycle, so serverless targets refuse this
15
15
  project. A browser-facing API that also needs credentials must add
16
16
  `Access-Control-Allow-Credentials` deliberately, never with a wildcard origin.
@@ -3,7 +3,7 @@ description: JSON endpoint for one allowed browser origin; middleware answers pr
3
3
  tags: [cors, preflight, options, browser, origin, api, json, middleware, access-control, vary]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, function, methods, middleware, response.headers]
6
- targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
7
7
  routes: 1
8
8
  inputs:
9
9
  - name: allowedOrigins
@@ -23,4 +23,4 @@ behavior:
23
23
  - OPTIONS /api/items from https://app.example.com answers 204 with Access-Control-Allow-Origin, -Methods and -Headers
24
24
  - GET from the allowed origin carries Access-Control-Allow-Origin; other origins get Vary Origin and no allow header
25
25
  - the route lists OPTIONS in methods, otherwise the runtime answers 405 before the middleware runs
26
- - CORS is middleware, not a host policy, so the route needs the sandbox
26
+ - CORS is middleware, not a host policy, so the route needs the self-hosted Node lifecycle
@@ -4,7 +4,7 @@ Run `urlcode validate --local --project .`, `urlcode test --project .` and
4
4
  `urlcode audit --project . --expect-routes 2`.
5
5
 
6
6
  `/health` answers `ok` as text and `/status` answers a small JSON document. Both
7
- are native `respond` routes with `Cache-Control: no-store`, so no sandbox runs
7
+ are native `respond` routes with `Cache-Control: no-store`, so no project code runs
8
8
  and every target supports them. Change `service` in `urlcode.yaml` to your
9
9
  service name.
10
10
 
@@ -3,7 +3,7 @@ description: Plain-text liveness and JSON readiness answers served natively with
3
3
  tags: [health, status, readiness, liveness, uptime, probe, monitoring, respond, native, json]
4
4
  complexity: starter
5
5
  capabilities: [enabled, methods, respond, response.headers]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible}
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible, static: refused}
7
7
  routes: 2
8
8
  inputs:
9
9
  - name: service
@@ -1,6 +1,6 @@
1
1
  # JSON echo API
2
2
 
3
3
  Run `urlcode validate --local --project .` and `urlcode serve --project .`.
4
- POST JSON with `Content-Type: application/json` to `/echo`. The sandbox returns
4
+ POST JSON with `Content-Type: application/json` to `/echo`. The function returns
5
5
  `{"received": ...}`. Other methods are refused. Do not submit credentials to an
6
6
  echo endpoint. This recipe grants no network or filesystem access.
@@ -1,9 +1,9 @@
1
1
  id: json-api
2
- description: Bounded JSON request body echoed back by a sandboxed function.
3
- tags: [json, api, post, echo, function, body, validation, sandbox]
2
+ description: Bounded JSON request body echoed back by a function (trusted, the default).
3
+ tags: [json, api, post, echo, function, body, validation]
4
4
  complexity: starter
5
5
  capabilities: [enabled, function, methods, request.body]
6
- targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
7
7
  routes: 1
8
8
  inputs:
9
9
  - name: handler
@@ -21,5 +21,5 @@ tests:
21
21
  - urlcode audit --project . --expect-routes 1
22
22
  behavior:
23
23
  - POST /echo with application/json answers 200 {"received":<body>}
24
- - malformed JSON answers 400 and another content type 415 before the sandbox runs
24
+ - malformed JSON answers 400 and another content type 415 before the function runs
25
25
  - GET answers 405
@@ -3,14 +3,15 @@
3
3
  Fourteen reusable middleware modules on fifteen routes (`auth.mjs` serves two),
4
4
  covering what Express, Koa,
5
5
  Hono, Next.js and edge runtimes usually ship as middleware, rewritten for
6
- URLCode's small sandboxed guest API. Run `urlcode validate --local --project .`
6
+ URLCode's middleware contract. These modules run trusted and in-process, the
7
+ default; a `sandbox: true` route sees a smaller guest API instead. Run `urlcode validate --local --project .`
7
8
  and `urlcode serve --project .`, then keep the routes you need and delete the
8
9
  rest; every module is independent.
9
10
 
10
11
  | Route | Module | Pattern |
11
12
  | --- | --- | --- |
12
13
  | `/api/private` | `middleware/auth.mjs` (`bearer`) | Bearer token with constant-time comparison |
13
- | `/admin/panel` | `middleware/auth.mjs` (`basic`) | HTTP Basic decoded in the guest |
14
+ | `/admin/panel` | `middleware/auth.mjs` (`basic`) | HTTP Basic decoded in trusted middleware |
14
15
  | `/cors/data` | `middleware/cors.mjs` | Origin allowlist and preflight |
15
16
  | `/traced` | `middleware/request-id.mjs` | Caller correlation id and Server-Timing |
16
17
  | `/maintenance` | `middleware/maintenance.mjs` | 503 kill switch with bypass header |
@@ -28,7 +29,10 @@ rest; every module is independent.
28
29
  Tokens, passwords and switches are literal `env` values so the recipe runs
29
30
  without grants. Before deploying, move credentials to `{secret: name}` bindings
30
31
  with an operator grant, replace the example.com destinations, and delete the
31
- `/inspect` route or set `DEBUG` to `false`. The guest exposes no crypto, so none
32
+ `/inspect` route or set `DEBUG` to `false`. These modules run trusted, so
33
+ `node:crypto` is available; the examples deliberately stay to request shaping
34
+ and are not a security boundary. Under `sandbox: true` there is no crypto API
35
+ at all, so none
32
36
  of these modules can verify signatures or hash passwords; treat them as request
33
37
  shaping, not as a security boundary. See the runtime's middleware documentation
34
- for the full list of guest limits.
38
+ for the full list of sandboxed-route limits.
@@ -1,5 +1,9 @@
1
1
  // Sticky A/B bucketing through a cookie, as on Vercel or Cloudflare edge examples.
2
2
  // Bucket b gets a different destination; everyone keeps their bucket for a week.
3
+ // `Response.redirect()`'s headers are immutable (per the Fetch standard a
4
+ // trusted route's real `Response` enforces this, unlike the sandbox's guest
5
+ // API), so build that branch's headers up front instead of mutating the
6
+ // result afterward.
3
7
  function cookie(request, name) {
4
8
  for (const part of (request.headers.get('cookie') || '').split(';')) {
5
9
  const [key, ...rest] = part.trim().split('=');
@@ -12,8 +16,14 @@ export default async function bucket(request, context, next) {
12
16
  const fresh = assigned !== 'a' && assigned !== 'b';
13
17
  if (fresh) assigned = Math.random() < 0.5 ? 'a' : 'b';
14
18
  context.state.bucket = assigned;
15
- const response = assigned === 'b' ? Response.redirect(context.env.VARIANT_URL, 302) : await next();
19
+ const setCookie = 'bucket=' + assigned + '; Path=/; Max-Age=604800; SameSite=Lax';
20
+ if (assigned === 'b') {
21
+ const headers = new Headers({ location: context.env.VARIANT_URL, vary: 'cookie' });
22
+ if (fresh) headers.append('set-cookie', setCookie);
23
+ return new Response(null, { status: 302, headers });
24
+ }
25
+ const response = await next();
16
26
  response.headers.set('vary', 'cookie');
17
- if (fresh) response.headers.append('set-cookie', 'bucket=' + assigned + '; Path=/; Max-Age=604800; SameSite=Lax');
27
+ if (fresh) response.headers.append('set-cookie', setCookie);
18
28
  return response;
19
29
  }