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

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 (191) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +24 -8
  2. package/.claude/skills/urlcode-operations/SKILL.md +10 -16
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/README.md +75 -62
  5. package/ROADMAP.md +61 -16
  6. package/SECURITY.md +23 -8
  7. package/dist/BUILD-MANIFEST.json +38 -43
  8. package/dist/agents-guide.js +18 -18
  9. package/dist/build-static.js +134 -0
  10. package/dist/capabilities.js +85 -31
  11. package/dist/capability-query.js +0 -1
  12. package/dist/cli.js +23 -31
  13. package/dist/compliance-rules/baseline.js +2 -10
  14. package/dist/compliance-rules/privacy.js +5 -16
  15. package/dist/compliance-rules/shared.js +0 -2
  16. package/dist/compliance.js +6 -8
  17. package/dist/config.js +15 -8
  18. package/dist/context.js +9 -10
  19. package/dist/examples.js +2 -2
  20. package/dist/explain-cli.js +3 -3
  21. package/dist/explain.js +5 -6
  22. package/dist/extensions.js +61 -2
  23. package/dist/function-sources.js +34 -2
  24. package/dist/function-worker.js +3 -1
  25. package/dist/functions.js +84 -13
  26. package/dist/guest-api.js +29 -3
  27. package/dist/index.js +3 -6
  28. package/dist/manifest.js +5 -7
  29. package/dist/mcp.js +1 -1
  30. package/dist/observability.js +1 -21
  31. package/dist/policies/cache.js +8 -3
  32. package/dist/policy.js +28 -9
  33. package/dist/project-tests.js +3 -3
  34. package/dist/readiness.js +32 -9
  35. package/dist/router.js +5 -7
  36. package/dist/runtime.js +77 -58
  37. package/dist/sandbox.js +48 -0
  38. package/dist/scaffold.js +0 -0
  39. package/dist/scripts/operational-drills.js +12 -54
  40. package/dist/server.js +3 -29
  41. package/dist/tooling.js +1 -1
  42. package/dist/trusted-functions.js +210 -0
  43. package/dist/types/build-static.d.ts +43 -0
  44. package/dist/types/capabilities.d.ts +14 -5
  45. package/dist/types/compliance-rules/shared.d.ts +0 -2
  46. package/dist/types/compliance.d.ts +0 -3
  47. package/dist/types/config.d.ts +2 -1
  48. package/dist/types/context.d.ts +2 -1
  49. package/dist/types/examples.d.ts +1 -1
  50. package/dist/types/extensions.d.ts +55 -0
  51. package/dist/types/function-sources.d.ts +4 -0
  52. package/dist/types/functions.d.ts +48 -5
  53. package/dist/types/guest-api.d.ts +1 -0
  54. package/dist/types/index.d.ts +3 -6
  55. package/dist/types/manifest.d.ts +1 -3
  56. package/dist/types/observability.d.ts +1 -14
  57. package/dist/types/project-tests.d.ts +1 -2
  58. package/dist/types/readiness.d.ts +10 -3
  59. package/dist/types/router.d.ts +2 -1
  60. package/dist/types/runtime.d.ts +0 -27
  61. package/dist/types/sandbox.d.ts +12 -0
  62. package/dist/types/scaffold.d.ts +0 -2
  63. package/dist/types/server.d.ts +1 -4
  64. package/dist/types/tooling.d.ts +3 -3
  65. package/dist/types/trusted-functions.d.ts +29 -0
  66. package/dist/types/types.d.ts +14 -7
  67. package/dist/types/verify-deployment.d.ts +2 -2
  68. package/dist/types.js +17 -4
  69. package/dist/typescript-authoring.js +55 -17
  70. package/dist/verify-deployment.js +3 -3
  71. package/docs/AI-AUTHORING.md +104 -9
  72. package/docs/AWS.md +4 -4
  73. package/docs/BEST-PRACTICES.md +3 -2
  74. package/docs/CAPABILITIES.md +29 -16
  75. package/docs/CAPACITY.md +128 -31
  76. package/docs/CLOUDFLARE.md +1 -2
  77. package/docs/COMPLIANCE.md +6 -9
  78. package/docs/DEPLOYMENT-CHECKS.md +1 -1
  79. package/docs/EXTENSIONS.md +183 -11
  80. package/docs/FRAMEWORK.md +35 -19
  81. package/docs/FUNCTION-SECURITY.md +129 -32
  82. package/docs/INSTALL.md +0 -5
  83. package/docs/LOAD-TESTING.md +4 -4
  84. package/docs/MIDDLEWARE.md +27 -16
  85. package/docs/MONITORING.md +2 -19
  86. package/docs/NEXT-PHASE-PLAN.md +12 -4
  87. package/docs/NEXT-STEPS.md +259 -40
  88. package/docs/OBSERVABILITY.md +7 -16
  89. package/docs/OPEN-DECISIONS.md +212 -0
  90. package/docs/OPERATIONAL-PROOF.md +26 -30
  91. package/docs/OPERATIONS.md +23 -32
  92. package/docs/POLICIES.md +15 -4
  93. package/docs/PRERENDER.md +2 -2
  94. package/docs/PROJECT-DIRECTION.md +34 -9
  95. package/docs/README.md +13 -11
  96. package/docs/RELEASE-READINESS.md +49 -29
  97. package/docs/RELEASE-SECURITY.md +89 -3
  98. package/docs/RESILIENCE.md +16 -15
  99. package/docs/ROUTING.md +8 -10
  100. package/docs/SANDBOX-REVIEW.md +19 -6
  101. package/docs/SCAFFOLDING.md +0 -2
  102. package/docs/SECURITY-AUDIT.md +40 -0
  103. package/docs/SPECIFICATION.md +60 -26
  104. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
  105. package/docs/SPIKE-BUSINESS-SUITE.md +8 -0
  106. package/docs/SPIKE-CORE-LAYERING.md +337 -0
  107. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
  108. package/docs/SPIKE-EXTENSION-MODEL.md +2 -2
  109. package/docs/SPIKE-EXTENSIONS.md +5 -0
  110. package/docs/SPIKE-LAMBDA-COMPILE.md +4 -2
  111. package/docs/SPIKE-MONOREPO.md +322 -0
  112. package/docs/STARTERS.md +1 -1
  113. package/docs/STATIC.md +94 -0
  114. package/docs/TOOLING.md +13 -9
  115. package/docs/TUNNELS.md +0 -3
  116. package/docs/TYPESCRIPT-AUTHORING.md +27 -12
  117. package/docs/TYPESCRIPT.md +25 -4
  118. package/docs/USABILITY-REVIEW.md +7 -1
  119. package/docs/VERCEL.md +4 -5
  120. package/docs/VERSION-ALIGNMENT.md +205 -0
  121. package/docs/YAML-GUIDE.md +0 -3
  122. package/docs/YAML-REFERENCE.md +16 -6
  123. package/docs/policies/cache.md +13 -0
  124. package/examples/assets/example.yaml +1 -1
  125. package/examples/aws/example.yaml +1 -1
  126. package/examples/cloudflare/example.yaml +1 -1
  127. package/examples/conditions/example.yaml +1 -1
  128. package/examples/cookbook/example.yaml +1 -1
  129. package/examples/cookbook/middleware/bucket.mjs +12 -2
  130. package/examples/cookbook/middleware/locale.mjs +7 -3
  131. package/examples/egress/example.yaml +1 -1
  132. package/examples/extensions/example.yaml +1 -1
  133. package/examples/prerender/README.md +2 -2
  134. package/examples/prerender/example.yaml +1 -1
  135. package/examples/provider-conformance/example.yaml +1 -1
  136. package/examples/vercel/example.yaml +1 -1
  137. package/llms-full.txt +594 -219
  138. package/llms.txt +23 -12
  139. package/package.json +10 -3
  140. package/packaging/claude-plugin/.claude-plugin/plugin.json +1 -1
  141. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +24 -8
  142. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +10 -16
  143. package/recipes/authenticated-json-api/recipe.yaml +1 -1
  144. package/recipes/contact-form/recipe.yaml +1 -1
  145. package/recipes/cors-api/recipe.yaml +1 -1
  146. package/recipes/health-page/recipe.yaml +1 -1
  147. package/recipes/json-api/recipe.yaml +1 -1
  148. package/recipes/middleware/middleware/bucket.mjs +12 -2
  149. package/recipes/middleware/middleware/locale.mjs +7 -3
  150. package/recipes/middleware/recipe.yaml +1 -1
  151. package/recipes/protected-download/recipe.yaml +1 -1
  152. package/recipes/redirect/recipe.yaml +1 -1
  153. package/recipes/static-plus-api/recipe.yaml +1 -1
  154. package/recipes/typescript/README.md +2 -1
  155. package/recipes/typescript/recipe.yaml +1 -1
  156. package/recipes/webhook-receiver/README.md +5 -1
  157. package/recipes/webhook-receiver/recipe.yaml +2 -1
  158. package/recipes/webhook-receiver/urlcode.yaml +7 -0
  159. package/schemas/recipe.schema.json +4 -3
  160. package/schemas/urlcode.schema.json +14 -41
  161. package/skills/urlcode/SKILL.md +29 -8
  162. package/starters/default/AGENTS.md +18 -18
  163. package/starters/default/urlcode.yaml +0 -1
  164. package/dist/link-api.js +0 -136
  165. package/dist/link-cli.js +0 -141
  166. package/dist/link-events.js +0 -76
  167. package/dist/link-records.js +0 -31
  168. package/dist/link-store-worker.js +0 -150
  169. package/dist/link-store.js +0 -250
  170. package/dist/management-policy.js +0 -40
  171. package/dist/sqlite-version.js +0 -6
  172. package/dist/types/link-api.d.ts +0 -30
  173. package/dist/types/link-cli.d.ts +0 -37
  174. package/dist/types/link-events.d.ts +0 -27
  175. package/dist/types/link-records.d.ts +0 -11
  176. package/dist/types/link-store-worker.d.ts +0 -1
  177. package/dist/types/link-store.d.ts +0 -130
  178. package/dist/types/management-policy.d.ts +0 -8
  179. package/dist/types/sqlite-version.d.ts +0 -1
  180. package/docs/DYNAMIC-LINKS.md +0 -61
  181. package/docs/MANAGEMENT-SECURITY.md +0 -82
  182. package/docs/links/cli.md +0 -110
  183. package/docs/links/limits.md +0 -175
  184. package/docs/links/management-api.md +0 -80
  185. package/docs/links/pools.md +0 -75
  186. package/docs/links/setup.md +0 -135
  187. package/docs/yaml/links.md +0 -30
  188. package/examples/live-links/README.md +0 -11
  189. package/examples/live-links/example.yaml +0 -21
  190. package/examples/live-links/tests/requests.json +0 -6
  191. 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,21 @@ 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`.
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`.
61
70
  - [urlcode-auth](https://github.com/jimhoyd-com/urlcode-auth): npm: @jimhoyd/urlcode-auth; accounts, sessions, MFA, roles, account page; its own llms.txt.
62
71
  - [urlcode-admin](https://github.com/jimhoyd-com/urlcode-admin): npm: @jimhoyd/urlcode-admin; users, sessions, roles, audit, cases; its own llms.txt.
63
72
  - [urlcode-ui](https://github.com/jimhoyd-com/urlcode-ui): npm: @jimhoyd/urlcode-ui; escaped templates, shadcn/ui partials, themes, translations; its own llms.txt.
73
+ - urlcode-dynamic-link (planned, not yet published): stored short links move here as a mount-based extension; core no longer has a native `link` handler.
64
74
  - [Plugins](docs/PLUGINS.md): host hook API in operator code, never named in YAML.
65
75
 
66
76
  ## Tooling and API
67
77
  - [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.
78
+ - [TypeScript](docs/TYPESCRIPT.md): declarations for `@jimhoyd/urlcode` and its `/plugins`, `/policies`, `/observability`, `/compliance`, `/prerender`, `/extensions`, `/sandbox`, `/aws`, `/vercel`, `/cloudflare` entries.
69
79
  - [Capabilities](docs/CAPABILITIES.md): per-target support; `urlcode capabilities --target NAME`.
70
80
 
71
81
  ## Operations
72
82
  - [Operations](docs/OPERATIONS.md), [install](docs/INSTALL.md), [deployment checks](docs/DEPLOYMENT-CHECKS.md), [CI action](docs/CI.md).
73
83
  - [Readiness](docs/READINESS.md), [capacity](docs/CAPACITY.md), [performance](docs/PERFORMANCE.md), [resilience](docs/RESILIENCE.md), [observability](docs/OBSERVABILITY.md).
74
84
  - [Vercel](docs/VERCEL.md), [AWS](docs/AWS.md), [Cloudflare](docs/CLOUDFLARE.md): adapters with local conformance tests; no provider deployment verified yet.
85
+ - [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
86
  - [Release readiness](docs/RELEASE-READINESS.md), [roadmap](ROADMAP.md): what is proven, what is planned.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jimhoyd/urlcode",
3
- "version": "0.4.0-alpha.1",
3
+ "version": "0.4.0-alpha.2",
4
4
  "description": "Portable runtime for programmable URL behavior",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -69,6 +69,11 @@
69
69
  "development": "./src/extensions.ts",
70
70
  "default": "./dist/extensions.js"
71
71
  },
72
+ "./sandbox": {
73
+ "types": "./dist/types/sandbox.d.ts",
74
+ "development": "./src/sandbox.ts",
75
+ "default": "./dist/sandbox.js"
76
+ },
72
77
  "./package.json": "./package.json"
73
78
  },
74
79
  "files": [
@@ -96,7 +101,7 @@
96
101
  "typecheck": "tsc -p tsconfig.json",
97
102
  "test": "node --conditions=development --test test/*.test.ts",
98
103
  "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",
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",
100
105
  "verify": "npm run lint && npm run typecheck && npm run check && npm run build && npm test",
101
106
  "benchmark": "node benchmarks/routing.ts",
102
107
  "test:package": "npm run build && node scripts/package-smoke.ts",
@@ -113,7 +118,9 @@
113
118
  "docs:plugin": "node scripts/generate-claude-plugin.ts",
114
119
  "docs:llms": "node scripts/build-llms-full.ts",
115
120
  "docs:cookbook-index": "node scripts/build-cookbook-index.ts",
121
+ "check:downstream-skills": "node scripts/check-downstream-skill-drift.ts",
116
122
  "benchmark:bulk": "node benchmarks/bulk.ts",
123
+ "benchmark:sandbox-vs-trusted": "node benchmarks/sandbox-vs-trusted.ts",
117
124
  "benchmark:agent": "node benchmarks/agent/run.ts",
118
125
  "sync:agents": "node scripts/sync-agent-lists.ts"
119
126
  },
@@ -138,7 +145,7 @@
138
145
  "globals": "17.12.0",
139
146
  "typescript-eslint": "8.70.0"
140
147
  },
141
- "homepage": "https://github.com/jimhoyd-com/urlcode-docs",
148
+ "homepage": "https://github.com/jimhoyd-com/urlcode#readme",
142
149
  "bugs": {
143
150
  "url": "https://github.com/jimhoyd-com/urlcode/issues"
144
151
  }
@@ -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.2",
5
5
  "author": {
6
6
  "name": "jimhoyd-com",
7
7
  "url": "https://github.com/jimhoyd-com"
@@ -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,6 +11,15 @@ 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
@@ -35,8 +44,9 @@ checkout you are working in — never from memory of another version.
35
44
  tests and the pinned runtime version. Preserve the user's organization,
36
45
  naming and unrelated routes.
37
46
  - 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.
47
+ `page`, `static`, `download`, `conditional`, `proxy` or an `extension` mount
48
+ — plus optional ordered middleware. Prefer a native handler when code is
49
+ unnecessary.
40
50
  - Declare each path placeholder as a required string. Paths match whole
41
51
  segments: no regex, no greedy captures, no wildcard handlers.
42
52
  - Bind typed inputs through `args` or context. There is no `${...}`
@@ -56,8 +66,14 @@ mistakes that recur:
56
66
  - No YAML anchors, aliases, template interpolation or remote includes.
57
67
  - No recursive includes or glob discovery; includes are explicit.
58
68
  - 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.
69
+ - `function`/`middleware` routes run trusted and unsandboxed by default: full
70
+ Node, npm, filesystem and `fetch` access, in-process, like any other project
71
+ 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
74
+ `sandbox: true` route gets a text/JSON `Request`/`Response` sandbox only:
75
+ **no** `fetch`, Node or npm APIs, filesystem, WebSocket, streaming or crypto
76
+ API.
61
77
  - No global middleware, Express compatibility or automatic auth.
62
78
  - `policies` accepts only `throttle`, `agents`, `security`, `compression` and
63
79
  `cache`, every key off unless declared; `hardened` is the only built-in
@@ -68,9 +84,9 @@ mistakes that recur:
68
84
  only and off unless declared; a declared route at the same path wins. Its
69
85
  generated routes count toward `--expect-routes`, and `site.sitemap` needs
70
86
  `--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.
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.
74
90
  - Infrastructure (proxy ranges, storage URLs, vendor rule identifiers) is an
75
91
  operator flag, never route YAML.
76
92
 
@@ -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,8 +69,8 @@ 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
 
@@ -84,12 +82,9 @@ already reviewed by the operator.
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.
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.
93
88
  - Sandbox concurrency, worker slots and execution deadlines are shared across
94
89
  every programmable route in a snapshot; there is no per-route fairness or
95
90
  reserved capacity, and awaiting a guest timer still occupies a slot.
@@ -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
@@ -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
@@ -3,7 +3,7 @@ description: Validate a JSON contact message in the sandbox and notify an operat
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
@@ -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
@@ -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
@@ -3,7 +3,7 @@ description: Bounded JSON request body echoed back by a sandboxed function.
3
3
  tags: [json, api, post, echo, function, body, validation, sandbox]
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
@@ -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
  }
@@ -1,5 +1,8 @@
1
1
  // Language redirect from Accept-Language, like Next.js i18n middleware. Only
2
2
  // languages listed in LOCALES are chosen; the native redirect is the default.
3
+ // `Response.redirect()`'s headers are immutable (a trusted route's real
4
+ // `Response` enforces the Fetch standard here, unlike the sandbox's guest
5
+ // API), so that branch builds its own `Response` with headers up front.
3
6
  export default async function locale(request, context, next) {
4
7
  const supported = (context.env.LOCALES || '').split(/\s+/).filter(Boolean);
5
8
  const ranked = (request.headers.get('accept-language') || '').split(',').map((part, index) => {
@@ -8,9 +11,10 @@ export default async function locale(request, context, next) {
8
11
  return {lang: tag.trim().toLowerCase().split('-')[0], q: q ? Number(q.slice(2)) || 0 : 1, index};
9
12
  }).filter(p => p.q > 0).sort((a, b) => b.q - a.q || a.index - b.index);
10
13
  const chosen = ranked.find(p => supported.includes(p.lang))?.lang;
11
- const response = chosen && chosen !== supported[0]
12
- ? Response.redirect(context.env.SITE + '/' + chosen + '/welcome', 302)
13
- : await next();
14
+ if (chosen && chosen !== supported[0]) {
15
+ return new Response(null, { status: 302, headers: { location: context.env.SITE + '/' + chosen + '/welcome', vary: 'accept-language' } });
16
+ }
17
+ const response = await next();
14
18
  response.headers.set('vary', 'accept-language');
15
19
  return response;
16
20
  }
@@ -3,7 +3,7 @@ description: Fourteen reusable middleware patterns around fifteen routes, mirror
3
3
  tags: [middleware, auth, bearer, basic, cors, tracing, request-id, errors, etag, caching, maintenance, negotiation, locale, referer, method-override, envelope, validation, debug, bucket, experiment]
4
4
  complexity: advanced
5
5
  capabilities: [bindings, download, enabled, function, methods, middleware, parameters, redirect, request.body, respond]
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: 15
8
8
  inputs:
9
9
  - name: credentials
@@ -3,7 +3,7 @@ description: Native attachment download that only the operator-installed auth ex
3
3
  tags: [download, attachment, protected, auth, authenticated, file, private, extension, content-disposition, "401"]
4
4
  complexity: advanced
5
5
  capabilities: [download, enabled, extension, methods, policies.extensions]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused}
6
+ targets: {self-hosted: conditional, aws: conditional, vercel: conditional, cloudflare: refused, static: refused}
7
7
  routes: 1
8
8
  services:
9
9
  - name: auth extension
@@ -3,7 +3,7 @@ description: Permanent redirect that forwards only an allowlisted query key.
3
3
  tags: [redirect, permanent, "301", query, passthrough, documentation, native]
4
4
  complexity: starter
5
5
  capabilities: [enabled, methods, redirect]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible}
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible, static: compatible}
7
7
  routes: 1
8
8
  inputs:
9
9
  - name: destination
@@ -3,7 +3,7 @@ description: A native static site (home page and an assets directory) next to on
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]
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: 3
8
8
  inputs:
9
9
  - name: pages
@@ -4,4 +4,5 @@ Run `urlcode build-typescript --project . --out ../hello-built` with a new outpu
4
4
  directory, then `urlcode validate --local --project ../hello-built` and
5
5
  `urlcode serve --project ../hello-built`. GET `/hello` returns JSON.
6
6
  TypeScript is transpiled ahead of time. The runtime executes only the emitted
7
- JavaScript in QuickJS/WASM. The build does not type-check or read tsconfig.json.
7
+ JavaScript, in QuickJS/WASM for a `sandbox: true` route and in-process for a
8
+ trusted one. The build does not type-check or read tsconfig.json.
@@ -3,7 +3,7 @@ description: Typed guest function transpiled ahead of time; only the emitted Jav
3
3
  tags: [typescript, function, build, typed, compile, sandbox]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, function, methods]
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
@@ -5,7 +5,11 @@ Run `urlcode validate --local --project .`, `urlcode test --project .` and
5
5
 
6
6
  `POST /webhook` accepts at most 64 KiB of `application/json`. The runtime
7
7
  rejects other methods (405), other content types (415), oversized bodies (413)
8
- and malformed JSON (400) before the sandbox runs. The function then requires an
8
+ and malformed JSON (400) before the sandbox runs. The route declares
9
+ `sandbox: true`: functions/middleware run trusted by default, but a
10
+ third-party sender's payload is exactly the input a project doesn't fully
11
+ trust, so this recipe isolates parsing it (docs/FUNCTION-SECURITY.md). The
12
+ function then requires an
9
13
  `X-Webhook-Event` header and a JSON object carrying a string `id`, and answers
10
14
  `202 {"received":true,...}`.
11
15
 
@@ -3,7 +3,7 @@ description: Accept a bounded JSON event that names its type in a header, check
3
3
  tags: [webhook, receiver, event, post, json, body, "202", callback, ingest, function]
4
4
  complexity: starter
5
5
  capabilities: [enabled, function, methods, request.body, 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: shape check
@@ -24,3 +24,4 @@ behavior:
24
24
  - a missing event header answers 400; a body that is not an object with a string id answers 422
25
25
  - other methods 405, other content types 415, oversized bodies 413 and malformed JSON 400 before the sandbox runs
26
26
  - the guest has no crypto or network API, so signatures are not verified here; put signed webhooks behind an operator extension or trusted host
27
+ - "the route declares sandbox: true, isolating the third-party payload from the trusted-by-default execution every other function/middleware route gets"
@@ -11,6 +11,13 @@ routes:
11
11
  format: json
12
12
  function:
13
13
  source: functions/receive.mjs
14
+ # A third-party sender's payload is exactly the kind of input this
15
+ # project does not fully trust (docs/AI-AUTHORING.md, "Deciding when a
16
+ # route needs sandbox: true"), even after body/content-type validation:
17
+ # isolate parsing it from the rest of the process.
18
+ sandbox: true
19
+ sandboxReason: >-
20
+ Third-party webhook payload; isolate parsing it even after body/content-type validation.
14
21
  response:
15
22
  headers:
16
23
  Cache-Control: no-store
@@ -45,12 +45,13 @@
45
45
  "description": "Derived: the capability preflight verdict per target. compatible means no issue; otherwise the strongest issue (refused, unknown, conditional).",
46
46
  "type": "object",
47
47
  "additionalProperties": false,
48
- "required": ["self-hosted", "aws", "vercel", "cloudflare"],
48
+ "required": ["self-hosted", "aws", "vercel", "cloudflare", "static"],
49
49
  "properties": {
50
50
  "self-hosted": {"$ref": "#/$defs/verdict"},
51
51
  "aws": {"$ref": "#/$defs/verdict"},
52
52
  "vercel": {"$ref": "#/$defs/verdict"},
53
- "cloudflare": {"$ref": "#/$defs/verdict"}
53
+ "cloudflare": {"$ref": "#/$defs/verdict"},
54
+ "static": {"$ref": "#/$defs/verdict"}
54
55
  }
55
56
  },
56
57
  "routes": {
@@ -81,7 +82,7 @@
81
82
  "additionalProperties": false,
82
83
  "required": ["kind", "description"],
83
84
  "properties": {
84
- "kind": {"enum": ["extension", "signals", "proxy", "env", "secret", "link-store", "origin"]},
85
+ "kind": {"enum": ["extension", "signals", "proxy", "env", "secret", "origin"]},
85
86
  "description": {"type": "string", "minLength": 1, "maxLength": 300}
86
87
  }
87
88
  }
@@ -22,11 +22,6 @@
22
22
  "uniqueItems": true,
23
23
  "maxItems": 256
24
24
  },
25
- "dynamicLinks": {
26
- "type": "boolean",
27
- "default": false,
28
- "description": "Entry-point opt-in for live stored-link routes. Does not enable guest storage or management access."
29
- },
30
25
  "policies": {
31
26
  "$ref": "#/$defs/policies"
32
27
  },
@@ -346,6 +341,16 @@
346
341
  "enabled": {
347
342
  "type": "boolean"
348
343
  },
344
+ "sandbox": {
345
+ "type": "boolean",
346
+ "default": false,
347
+ "description": "Run this route's function/middleware in the isolated QuickJS/WASM worker pool instead of the trusted, in-process default. false or absent (the default) means trusted, unsandboxed, direct host-process execution."
348
+ },
349
+ "sandboxReason": {
350
+ "type": "string",
351
+ "maxLength": 500,
352
+ "description": "Optional justification for this route's sandbox decision, whether sandbox is true or false: why it needs isolation, or why it is safe to trust. Never inferred or enforced; surfaced verbatim by explain/context/manifest."
353
+ },
349
354
  "expires": {
350
355
  "type": "string"
351
356
  },
@@ -882,37 +887,6 @@
882
887
  ]
883
888
  }
884
889
  },
885
- "link": {
886
- "type": "object",
887
- "properties": {
888
- "collection": {
889
- "type": "string",
890
- "pattern": "^[A-Za-z][A-Za-z0-9_-]{0,63}$"
891
- },
892
- "code": {
893
- "type": "object",
894
- "properties": {
895
- "from": {
896
- "const": "path"
897
- },
898
- "name": {
899
- "type": "string",
900
- "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
901
- }
902
- },
903
- "required": [
904
- "from",
905
- "name"
906
- ],
907
- "additionalProperties": false
908
- }
909
- },
910
- "required": [
911
- "collection",
912
- "code"
913
- ],
914
- "additionalProperties": false
915
- },
916
890
  "policies": {
917
891
  "$ref": "#/$defs/policies"
918
892
  },
@@ -964,6 +938,10 @@
964
938
  "$ref": "#/$defs/routeAuth"
965
939
  }
966
940
  ]
941
+ },
942
+ "cache": {
943
+ "description": "Short form for policies.cache: the same object, expanded to policies.cache before anything else reads the project. Refused alongside policies.cache; use one form.",
944
+ "$ref": "#/$defs/policyCache"
967
945
  }
968
946
  },
969
947
  "required": [],
@@ -999,11 +977,6 @@
999
977
  "respond"
1000
978
  ]
1001
979
  },
1002
- {
1003
- "required": [
1004
- "link"
1005
- ]
1006
- },
1007
980
  {
1008
981
  "required": [
1009
982
  "conditional"