@jimhoyd/urlcode 0.4.0-alpha.2 → 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 (115) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +17 -19
  2. package/.claude/skills/urlcode-operations/SKILL.md +9 -9
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CONTRIBUTING.md +20 -16
  5. package/README.md +44 -54
  6. package/ROADMAP.md +61 -376
  7. package/dist/BUILD-MANIFEST.json +15 -15
  8. package/dist/agents-guide.js +6 -6
  9. package/dist/build-static.js +2 -0
  10. package/dist/cli.js +3 -3
  11. package/dist/explain-cli.js +4 -2
  12. package/dist/explain.js +7 -1
  13. package/dist/extensions.js +1 -1
  14. package/dist/manifest.js +7 -1
  15. package/dist/mcp-authoring.js +2 -2
  16. package/dist/mcp.js +1 -1
  17. package/dist/policies.js +3 -1
  18. package/dist/prerender.js +4 -0
  19. package/dist/readiness.js +3 -0
  20. package/dist/route-diff.js +12 -5
  21. package/dist/types/explain.d.ts +6 -0
  22. package/dist/types/extensions.d.ts +1 -1
  23. package/dist/types/manifest.d.ts +5 -1
  24. package/dist/types/readiness.d.ts +2 -0
  25. package/dist/types/types.d.ts +8 -1
  26. package/dist/types.js +8 -1
  27. package/dist/typescript-authoring.js +18 -7
  28. package/docs/AI-AUTHORING.md +10 -6
  29. package/docs/ASSETS.md +2 -1
  30. package/docs/AUTH-BACKUP.md +32 -0
  31. package/docs/BEST-PRACTICES.md +17 -9
  32. package/docs/CAPABILITIES.md +1 -1
  33. package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
  34. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
  35. package/docs/CI.md +8 -3
  36. package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
  37. package/docs/DEVELOPMENT-PIPELINE.md +181 -0
  38. package/docs/EXTENSIONS.md +15 -5
  39. package/docs/FRAMEWORK.md +22 -18
  40. package/docs/FUNCTION-SECURITY.md +44 -0
  41. package/docs/LOCAL-DEVELOPMENT.md +3 -0
  42. package/docs/OPEN-DECISIONS.md +184 -212
  43. package/docs/OPERATIONAL-PROOF.md +3 -3
  44. package/docs/OPERATIONS.md +3 -3
  45. package/docs/POLICIES.md +13 -5
  46. package/docs/PRERENDER.md +23 -11
  47. package/docs/PROJECT-DIRECTION.md +3 -3
  48. package/docs/READINESS.md +6 -2
  49. package/docs/README.md +18 -41
  50. package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
  51. package/docs/RELEASE-READINESS.md +6 -6
  52. package/docs/RELEASE-SECURITY.md +68 -168
  53. package/docs/RESILIENCE.md +3 -3
  54. package/docs/ROUTING.md +3 -4
  55. package/docs/SECURITY-AUDIT.md +2 -2
  56. package/docs/SPECIFICATION.md +7 -5
  57. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
  58. package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
  59. package/docs/SPIKE-CORE-LAYERING.md +48 -17
  60. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
  61. package/docs/SPIKE-LAMBDA-COMPILE.md +8 -0
  62. package/docs/STATIC.md +14 -3
  63. package/docs/TOOLING.md +4 -3
  64. package/docs/TYPESCRIPT-AUTHORING.md +6 -1
  65. package/docs/VERSION-ALIGNMENT.md +46 -205
  66. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
  67. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +102 -0
  68. package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +15 -5
  69. package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +15 -3
  70. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
  71. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
  72. package/docs/archive/2026-09-19/ROADMAP.md +386 -0
  73. package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +10 -0
  74. package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
  75. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
  76. package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
  77. package/docs/archive/README.md +27 -0
  78. package/docs/policies/agents.md +1 -1
  79. package/docs/policies/compression.md +3 -2
  80. package/docs/policies/security.md +3 -2
  81. package/examples/assets/example.yaml +1 -1
  82. package/examples/cookbook/route-index.json +1 -1
  83. package/examples/cookbook/routes/middleware.yaml +1 -1
  84. package/examples/prerender/README.md +14 -6
  85. package/examples/prerender/functions/page.mjs +4 -2
  86. package/examples/prerender/middleware/template.mjs +1 -1
  87. package/examples/prerender/prerender.mjs +1 -1
  88. package/examples/prerender/urlcode.yaml +8 -4
  89. package/llms-full.txt +117 -45
  90. package/llms.txt +5 -4
  91. package/package.json +24 -4
  92. package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
  93. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +17 -19
  94. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +9 -9
  95. package/recipes/authenticated-json-api/README.md +4 -3
  96. package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
  97. package/recipes/authenticated-json-api/recipe.yaml +1 -1
  98. package/recipes/contact-form/functions/contact.mjs +2 -1
  99. package/recipes/contact-form/recipe.yaml +2 -2
  100. package/recipes/cors-api/README.md +2 -2
  101. package/recipes/cors-api/recipe.yaml +1 -1
  102. package/recipes/health-page/README.md +1 -1
  103. package/recipes/json-api/README.md +1 -1
  104. package/recipes/json-api/recipe.yaml +3 -3
  105. package/recipes/middleware/README.md +8 -4
  106. package/recipes/protected-download/README.md +1 -1
  107. package/recipes/protected-download/recipe.yaml +1 -1
  108. package/recipes/static-plus-api/README.md +2 -2
  109. package/recipes/static-plus-api/public/index.html +1 -1
  110. package/recipes/static-plus-api/recipe.yaml +1 -1
  111. package/recipes/static-plus-api/urlcode.yaml +1 -1
  112. package/recipes/typescript/recipe.yaml +4 -4
  113. package/skills/urlcode/SKILL.md +6 -6
  114. package/starters/default/AGENTS.md +6 -6
  115. package/docs/SPIKE-MONOREPO.md +0 -322
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@jimhoyd/urlcode",
3
- "version": "0.4.0-alpha.2",
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"
@@ -101,8 +104,10 @@
101
104
  "typecheck": "tsc -p tsconfig.json",
102
105
  "test": "node --conditions=development --test test/*.test.ts",
103
106
  "lint": "eslint .",
104
- "check": "node scripts/check.ts && 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",
105
- "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",
106
111
  "benchmark": "node benchmarks/routing.ts",
107
112
  "test:package": "npm run build && node scripts/package-smoke.ts",
108
113
  "dev": "node src/cli.ts dev --project starters/default",
@@ -122,7 +127,19 @@
122
127
  "benchmark:bulk": "node benchmarks/bulk.ts",
123
128
  "benchmark:sandbox-vs-trusted": "node benchmarks/sandbox-vs-trusted.ts",
124
129
  "benchmark:agent": "node benchmarks/agent/run.ts",
125
- "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"
126
143
  },
127
144
  "repository": {
128
145
  "type": "git",
@@ -138,11 +155,14 @@
138
155
  "yaml": "2.9.1"
139
156
  },
140
157
  "devDependencies": {
158
+ "@changesets/cli": "3.0.3",
141
159
  "@eslint/js": "10.0.1",
142
160
  "@types/mime-types": "3.0.1",
143
161
  "@types/node": "26.5.1",
162
+ "@types/semver": "7.8.0",
144
163
  "eslint": "10.10.0",
145
164
  "globals": "17.12.0",
165
+ "semver": "7.8.5",
146
166
  "typescript-eslint": "8.70.0"
147
167
  },
148
168
  "homepage": "https://github.com/jimhoyd-com/urlcode#readme",
@@ -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.2",
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
  }
@@ -26,17 +26,12 @@ Documentation, schema and runtime must come from the **same revision**. Read fro
26
26
  the project's installed runtime (`node_modules/@jimhoyd/urlcode/`) or the
27
27
  checkout you are working in — never from memory of another version.
28
28
 
29
- 1. `docs/AI-AUTHORING.md` the authoring contract and the **capability matrix**
30
- of what is available versus unavailable. Read this first and in full.
31
- 2. `schemas/urlcode.schema.json` the exact accepted structure.
32
- 3. `docs/YAML-REFERENCE.md` and `docs/SPECIFICATION.md` every field, and the
33
- implemented semantics, defaults and sandbox API.
34
- 4. `docs/YAML-GUIDE.md` and `examples/cookbook/` recipes and runnable files.
35
- 5. `docs/ROUTING.md`, `docs/HTTP.md`, `docs/MIDDLEWARE.md`, `docs/ASSETS.md` —
36
- matching precedence, methods, composition, MIME and ranges.
37
- 6. `docs/FUNCTION-SECURITY.md` — the sandbox and operator binding policy.
38
-
39
- `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.
40
35
 
41
36
  ## Workflow
42
37
 
@@ -52,8 +47,8 @@ checkout you are working in — never from memory of another version.
52
47
  - Bind typed inputs through `args` or context. There is no `${...}`
53
48
  interpolation anywhere in the format.
54
49
  - Create every referenced module, page and asset **before** validating. All
55
- paths resolve from the project root; functions and middleware use relative
56
- 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.
57
52
  - Write exact response fixtures for success and failure, covering every active
58
53
  method, middleware behavior, HEAD, and any range or cache semantics.
59
54
  - Follow `docs/BEST-PRACTICES.md` for layout and readability as the project grows.
@@ -69,14 +64,17 @@ mistakes that recur:
69
64
  - `function`/`middleware` routes run trusted and unsandboxed by default: full
70
65
  Node, npm, filesystem and `fetch` access, in-process, like any other project
71
66
  code. `sandbox: true` opts a route into isolation — reach for it when that
72
- route's own code warrants it (untrusted input, an unreviewed contribution, a
73
- particularly sensitive secret), not reflexively on every route. A
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
74
71
  `sandbox: true` route gets a text/JSON `Request`/`Response` sandbox only:
75
72
  **no** `fetch`, Node or npm APIs, filesystem, WebSocket, streaming or crypto
76
73
  API.
77
74
  - No global middleware, Express compatibility or automatic auth.
78
75
  - `policies` accepts only `throttle`, `agents`, `security`, `compression` and
79
- `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
80
78
  profile. Check the per-target table in `docs/POLICIES.md` before declaring
81
79
  one for a serverless or Cloudflare deployment — an unsupported policy refuses
82
80
  activation rather than degrading.
@@ -84,9 +82,9 @@ mistakes that recur:
84
82
  only and off unless declared; a declared route at the same path wins. Its
85
83
  generated routes count toward `--expect-routes`, and `site.sitemap` needs
86
84
  `--origin` on every command that activates the project.
87
- - There is no native `link` handler or `dynamicLinks` project flag. Stored
88
- short links are moving to a future `urlcode-dynamic-link` extension package,
89
- not yet published; report that as a gap, never invent a `link` field.
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.
90
88
  - Infrastructure (proxy ranges, storage URLs, vendor rule identifiers) is an
91
89
  operator flag, never route YAML.
92
90
 
@@ -74,20 +74,20 @@ by the operator.
74
74
 
75
75
  ## Hard limits — report these as gaps, never invent around them
76
76
 
77
- - No provider adapters, automatic TLS/DNS, distributed rate limiting, metrics
78
- exporters or durable event delivery are included; these remain the
79
- 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.
80
80
  - No orchestration, traffic switching or automated rollback; recovery is an
81
81
  explicit snapshot reload from a known-good artifact.
82
82
  - `verify-deployment` has no infrastructure access, uses no credential,
83
83
  follows no redirect and offers no `--insecure`. It cannot check anything a
84
84
  read-only HTTP probe cannot observe.
85
- - Core has no durable store and no private management API of its own; stored
86
- short links are moving to a future `urlcode-dynamic-link` extension
87
- package, not yet published.
88
- - Sandbox concurrency, worker slots and execution deadlines are shared across
89
- every programmable route in a snapshot; there is no per-route fairness or
90
- 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`.
91
91
  - `throttle` and `agents` policy counters are per instance, not distributed;
92
92
  they are a second layer behind the edge, never a replacement for it.
93
93
 
@@ -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
  }
@@ -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,5 +1,5 @@
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]
@@ -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.
@@ -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
 
@@ -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,6 +1,6 @@
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
6
  targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
@@ -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.
@@ -3,7 +3,7 @@
3
3
  `/downloads/report` serves `files/report.txt` as an attachment, but only after
4
4
  the operator-installed `auth` extension authorizes the request (`auth: true`,
5
5
  the short form of `policies.extensions.auth`). The file is
6
- served natively: no guest code runs, and the response is forced to `no-store`.
6
+ served natively: no project code runs at all, and the response is forced to `no-store`.
7
7
 
8
8
  Like the `authenticated-json-api` recipe, this project declares the extension
9
9
  and needs an operator host file outside the project plus the canonical origin
@@ -28,4 +28,4 @@ behavior:
28
28
  - GET /downloads/report without credentials answers 401
29
29
  - GET with the accepted credential answers 200, Content-Disposition attachment and Cache-Control no-store
30
30
  - HEAD mirrors both cases with an empty body
31
- - no guest code runs; the file is served natively and snapshotted at activation
31
+ - no project code runs; the file is served natively and snapshotted at activation
@@ -5,8 +5,8 @@ Run `urlcode validate --local --project .`, `urlcode test --project .` and
5
5
 
6
6
  `/` serves `public/index.html`, `/assets/*` serves everything under
7
7
  `public/assets` (with `index.html` for the directory itself), and `/api/info`
8
- is a sandboxed function returning JSON built from literal `args`. Pages and
9
- assets are native: no guest code runs for them, and they are snapshotted at
8
+ is a function returning JSON built from literal `args`. Pages and
9
+ assets are native: no project code runs for them at all, and they are snapshotted at
10
10
  activation, so new files need a reload. Edit the HTML, add files under
11
11
  `public/assets`, and change or extend `functions/info.mjs`.
12
12
 
@@ -3,6 +3,6 @@
3
3
  <head><meta charset="utf-8"><title>Example site</title><link rel="stylesheet" href="/assets/site.css"></head>
4
4
  <body>
5
5
  <h1>Example site</h1>
6
- <p>Static pages are served natively; <a href="/api/info">/api/info</a> is a sandboxed function.</p>
6
+ <p>Static pages are served natively; <a href="/api/info">/api/info</a> is a function route.</p>
7
7
  </body>
8
8
  </html>
@@ -1,5 +1,5 @@
1
1
  id: static-plus-api
2
- description: A native static site (home page and an assets directory) next to one sandboxed JSON endpoint.
2
+ description: A native static site (home page and an assets directory) next to one function-backed JSON endpoint.
3
3
  tags: [static, site, page, html, assets, css, api, json, function, cache-control, website]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, function, methods, page, response.headers, static]
@@ -12,7 +12,7 @@ routes:
12
12
  index: index.html
13
13
  cacheControl: public, max-age=3600
14
14
  /api/info:
15
- description: A small JSON endpoint next to the static site, computed in the sandbox.
15
+ description: A small JSON endpoint next to the static site, computed by a trusted function.
16
16
  function:
17
17
  source: functions/info.mjs
18
18
  args:
@@ -1,6 +1,6 @@
1
1
  id: typescript
2
- description: Typed guest function transpiled ahead of time; only the emitted JavaScript runs in the sandbox.
3
- tags: [typescript, function, build, typed, compile, sandbox]
2
+ description: "Typed function transpiled ahead of time; only the emitted JavaScript runs: in-process for a trusted route, in QuickJS/WASM where a route declares `sandbox: true`."
3
+ tags: [typescript, function, build, typed, compile]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, function, methods]
6
6
  targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
@@ -8,7 +8,7 @@ routes: 1
8
8
  inputs:
9
9
  - name: handler
10
10
  file: functions/hello.ts
11
- description: The typed guest; keep to the Request/Response sandbox API.
11
+ description: "The typed handler; keep to Request/Response, and to the sandbox subset if you add `sandbox: true`."
12
12
  files: [urlcode.yaml, functions/hello.ts, tests/requests.json, README.md]
13
13
  tests:
14
14
  fixtures: tests/requests.json
@@ -18,6 +18,6 @@ tests:
18
18
  - urlcode test --project ../hello-built
19
19
  - urlcode audit --project ../hello-built --expect-routes 1
20
20
  behavior:
21
- - after the build, GET /hello answers 200 JSON from the compiled guest
21
+ - after the build, GET /hello answers 200 JSON from the compiled module
22
22
  - the unbuilt project does not activate; .ts sources never run
23
23
  - POST answers 405
@@ -6,7 +6,7 @@ description: Work on a URLCode project, recognized by a urlcode.yaml file with v
6
6
  # URLCode authoring loop
7
7
 
8
8
  A URLCode project declares URL behavior in YAML; the installed runtime serves
9
- it. Your job is to change the declaration and the minimal guest code it names,
9
+ it. Your job is to change the declaration and the minimal application code it names,
10
10
  then prove the change with the runtime's own checks. Never reimplement what the
11
11
  runtime provides, and never invent fields.
12
12
 
@@ -68,14 +68,14 @@ need rather than reading them whole.
68
68
  2. Check supported extensions and their configuration before custom code. If a
69
69
  recipe from `recipes list` is close, `urlcode recipes add NAME --out DIR`
70
70
  and adapt the copy into the project's layout.
71
- 3. Only then write a function or middleware: one exported handler, relative
72
- ES-module imports inside the project only, inputs from validated `args`,
71
+ 3. Only then write a function or middleware: one exported handler, inputs from validated `args`,
73
72
  output as a `Response`. `function`/`middleware` routes run trusted and
74
73
  unsandboxed by default: full Node, npm, filesystem and `fetch` access, like
75
74
  any other project code. Add `sandbox: true` only when that route's own code
76
- warrants isolation (untrusted input, an unreviewed contribution, a
77
- particularly sensitive secret)a `sandbox: true` route then has no
78
- `fetch`, Node, npm, filesystem, WebSocket, streaming, crypto API or timers;
75
+ warrants isolation (unreviewed or third-party code, a secret whose blast
76
+ radius matters, complex logic never merely because it handles request
77
+ data, which is untrusted in both modes) a `sandbox: true` route then has no
78
+ `fetch`, Node, npm, filesystem, WebSocket, streaming or crypto APIs (bounded timers are supported);
79
79
  a need for those in a sandboxed route is a `proxy` route, a binding, or a
80
80
  report.
81
81
  4. Declare routing, validation, middleware chains, policies, static serving,
@@ -42,12 +42,12 @@ invitation to reimplement it.
42
42
 
43
43
  ## Functions and middleware are trusted by default; sandbox is opt-in
44
44
 
45
- A route's `function`/`middleware` code runs trusted, in-process, with full
46
- Node/filesystem/`fetch` access, receiving only the declared/granted `args`
47
- and `env`/`secrets`. Add `sandbox: true` when code warrants isolation
48
- (untrusted input, an unreviewed contribution, an especially sensitive
49
- secret): that route then gets a text/JSON subset only, no Node/filesystem/
50
- outside imports — use `proxy`/a binding instead, and say why in `description`.
45
+ A route's `function`/`middleware` runs trusted, in-process, with full
46
+ Node/filesystem/`fetch` access, given only declared `args`/`env`/`secrets`. Add
47
+ `sandbox: true` when that code warrants isolation (unreviewed code, a sensitive
48
+ secret, complex logic) — not merely for untrusted input, which both modes share.
49
+ A `sandbox: true` route gets a text/JSON subset only: use `proxy`/a binding, and
50
+ say why in `sandboxReason`.
51
51
 
52
52
  ## Checks that count as evidence
53
53