@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
@@ -3,88 +3,83 @@
3
3
  "files": {
4
4
  "dist/adapters.js": "a7153ec52f2ed7e0cdd8ed7d433504815f402ec53b2b1c3d38d0cf0e2fcf6509",
5
5
  "dist/agent-lists.js": "35ab484198897501d011cb6cc00b5a6787192e19df29c97646e8c118fcc19eef",
6
- "dist/agents-guide.js": "9cecc60690fe91e1f09a5298273c1d918428f114b5abb11f40f37f341668309c",
6
+ "dist/agents-guide.js": "a76e9818ec995a349085a53e8badd3ef49734275557b50bc7dcbe995ce3d0e9f",
7
7
  "dist/assets.js": "0833b093d2fe457ce68281110dc07d47914992753607741c13b339d92c76cf5c",
8
8
  "dist/authoring-files.js": "ad32814f9ae9c549c6247396f982aa700d13a293cc0a589609eccc68f8965550",
9
9
  "dist/authoring.js": "ec5240e8686c124f4601229b45f89ce112d9eeb9d35e3059fe55e93056c45209",
10
10
  "dist/aws.js": "7d8e3a97b4f6dbefbd68da058f05192469d1efcaed5458b284863ebe462e060e",
11
11
  "dist/build-cloudflare.js": "af4f49ad45287d7c4105bb84b1592b5b8e76c930f7159505338298f7c5fd5651",
12
+ "dist/build-static.js": "a2d2fcef3099805392da9140096d9843226bfb582dc046acf9a77f37b8a0d528",
12
13
  "dist/bulk.js": "aac88d422bd9a7a341a421fb4e29ff5580b28d39250b7f1f5315ba20ae92da8a",
13
- "dist/capabilities.js": "c1e28c7de676c3b43c154d6f767749a1b34df78a64c58b5c23ad95339f964608",
14
- "dist/capability-query.js": "850525d542aeeaeb4f89397ac57c6f89daad3fe3fa02ecb0332ea9f75d140b32",
14
+ "dist/capabilities.js": "28dba05b3851b8b12b3c1b809af87b732f84d601c737afe9225abe429886e691",
15
+ "dist/capability-query.js": "ba148b27c6a60f8c895f13f26468b16c18ae0a5cddea26b602965c8efbba90ae",
15
16
  "dist/catalog.js": "ab1c01c9296cc277d2bd4fd682aeb053656a7d9f91c61974f4b78feea0211de8",
16
- "dist/cli.js": "4cba007ed3666fdfdf9fc676de1e346ab8196e3907dc2541eeb3e59842d9891a",
17
+ "dist/cli.js": "f6097c246c6b9006df58e3463648013ec8837ab3296a8c294809313503be167a",
17
18
  "dist/client-address.js": "9d0d01466aab23124330605c5a0e0f981f87c897858acfad909f546a63df165b",
18
19
  "dist/cloudflare.js": "17b43b0a6b6a0ef8a893415c989e4ae3976d97cc966adbf897b785f9adc472e3",
19
- "dist/compliance-rules/baseline.js": "0a0c03d932f30797c4837c78c6b64ca6bb88f2ca947eb7f6adf5fdcdd280616d",
20
- "dist/compliance-rules/privacy.js": "0e4d12106cf777299a7d30451f53c7b996f202fe53d7e1047b69f5df2a74a53d",
21
- "dist/compliance-rules/shared.js": "595a7ee3a5af8c52197c5c9cbf09f4289a234e52b7a198afdd498be9be8d28a1",
20
+ "dist/compliance-rules/baseline.js": "6296dea6bcb9f9bb80ed6ed5ab2f456e7f74e6de2be5d2d98ca8c983fb4df3ab",
21
+ "dist/compliance-rules/privacy.js": "4cfb1c07d7df698c362dfc8761cd3bba893769e92d7fb0a3901e1c2d25e801f1",
22
+ "dist/compliance-rules/shared.js": "86fd6db6a93375689acc64975cbc394ce9452dea21ae5941e743f99822515995",
22
23
  "dist/compliance-rules/strict.js": "73ee13c2f893acf20cc3d44bf054e58c1474dec52eafe0cf2ce7f4eb96a3bc53",
23
- "dist/compliance.js": "e956dc89b0b729fed7031297b8d7554141110334e670edf5cedc3d052a8fb3e4",
24
+ "dist/compliance.js": "29a18da3cefea94802ac7d817d1ab4e3674abd5c3ac4ef893cb8b01c6a23fb61",
24
25
  "dist/conditions.js": "ff25e97da550a3bdd80af55522045669438aa31a77d2bc83ad602bcefdee0475",
25
26
  "dist/config-worker.js": "bc2170d60c35f8d5de227cbfc4c5067dc61d7cbc5b98aed88e68a11797072da5",
26
- "dist/config.js": "8efc2cfc24e9a958770d73aa8464d18c81d7a9a1d2398398cd9cc2776e639127",
27
- "dist/context.js": "a6b0278b4fd478175f43b1ef5ae640ce6e0782c906c6fd2c7d1aebaf5a31c918",
27
+ "dist/config.js": "0cd82b4c407848f9c66e804a6dd4bf9a8a8503264e51584456578073e0f01921",
28
+ "dist/context.js": "549a53e4738b9c20a1ec55614027f758026fb5860bf4ac962ce22d7029e48df0",
28
29
  "dist/ecosystem-cli.js": "1e71bacd53d6fa8c7857cfcce3c3012ce054771a434b78d1f39e892e32642edd",
29
30
  "dist/egress.js": "2ae29fe2cb4590fd2f715abe6817db47fed2f50dd31f37946ecf42eafbb0dd9e",
30
31
  "dist/errors.js": "a908e66496afebbdd632a8c159a6e14d23f8f38798a0b4342ca122f2511c6cde",
31
- "dist/examples.js": "8de9917a82fb04e44a3182d57c6a95cc730feb8b22a37d54b9b3c4177aa83111",
32
- "dist/explain-cli.js": "c5414e9627f428f0d8efa4e4f3f89951ea10a6b3fe1d7f633e48f71a792406d2",
33
- "dist/explain.js": "5dd271b699c9b852fbaa4db271d9a370fe98fca43b1d0ac018de0c0b047bf7b8",
34
- "dist/extensions.js": "7402ff86017fca83a0e08a50b402bc428056566d63511aa84d609e252dc456e8",
35
- "dist/function-sources.js": "f6e8bdd9d4ef728b44c6e1f08c601b11fc4ad3e0c104dacf4fea0b71874ea4e7",
36
- "dist/function-worker.js": "3f5bad4f1b5cb812924c2dcc73ece6f816cf6aac26cd471fd1030dcfbf009c3b",
37
- "dist/functions.js": "6b1e5d4a3f8db17d4e5df261b98ed15f5f0c5e5507a6fc0f472e9f94ed2b6090",
38
- "dist/guest-api.js": "bc2501496a6ef9fd20dddb606fbc6fd557d534dd075e7ad38406248b230013ed",
32
+ "dist/examples.js": "89ae2b764a688fe52acfb9b94d3d262d7742401ebd0ef10c9ebfc76edfa5631c",
33
+ "dist/explain-cli.js": "dd342b21a96fc97a11488ac3528793ba0f5d4744b74a1d99085e142c4737b69a",
34
+ "dist/explain.js": "9450d2699cbe65e5c078c443ba7a6cba95c748e75d455e05264083b3c93c92a2",
35
+ "dist/extensions.js": "fed2b394466a7d406f9edeaedb71d9da81e50e3150eb7d01f2e7659104afeac7",
36
+ "dist/function-sources.js": "16fad4abc81c7ee07b6cbcef2d23a9fc50e97a17cb4dde1db53469bbb555c96f",
37
+ "dist/function-worker.js": "35771790ada4e1b36d447d4967a5e6cf0b543f32944e4c904f5fbf8998e824a5",
38
+ "dist/functions.js": "0f09d1bd27eb655ceb5900a4a5e2b760d2028b02cb637a6a89a4eb5940828008",
39
+ "dist/guest-api.js": "49aec9ed72733ad119e63bcfcee18158dcc8ceb951e1823b8947f6407ff24afc",
39
40
  "dist/header-validation.js": "465181dbb08ff05f52defdd29fda025c0c64589bf319fa87fa6d3ab4b68216d5",
40
41
  "dist/http-policy.js": "4cb187642269078ddf3460a3f1813dc57878a616a6f8132ef8f8d3b4b91ebb56",
41
42
  "dist/http-response.js": "5fc3bb9eb932a3fd2fbc72c361416a5f1439b8bf45b2be37baca1c6446abf40c",
42
- "dist/index.js": "53648d88b0db7d9168d2f212ea4a28b7fd3ead6238c99c0b8d7d41536274d6d4",
43
+ "dist/index.js": "7e9f6ec8a125e2dfd4230b2ecd17ac513c0153d778a57bbcee7a1a9cd9681f81",
43
44
  "dist/init-with.js": "8371f568867ebdc6ab8e9aa762234ae2aa015865f46aa901254528e20d69b791",
44
45
  "dist/interchange-cli.js": "35bd70ba8077af5c3e39404ff0e5d28707632090a59a90645141252449fd3f04",
45
46
  "dist/interchange.js": "26789420af33344d9a08c00fe6b2708aa71994224df527b3075295be2151611e",
46
- "dist/link-api.js": "0d1f2fb5ef8cd6fda7b071d3e08c53eb4b1c01d4003167af2f400949b70dea4f",
47
- "dist/link-cli.js": "c4e2121a3e66f33c640832a7cb362a6a1db8b6d91d55a4239ce38ba0ced67b22",
48
- "dist/link-events.js": "502a6038a538fa43094a9ba9c7546f504b4a1832a1377f26ca8cb69c2326670e",
49
- "dist/link-records.js": "ef67a58cd107db0c3ac1c0ffd7b32817553606cf69bb3002da4ca0138c272f69",
50
- "dist/link-store-worker.js": "0080452bbada2154b923449cd14596579bde94ad5f8dab94ecc84aa0d59c089b",
51
- "dist/link-store.js": "526cdbe1b917cc7a74fb6edbbf13717d823ace5b5d6a5f89bc91438056a10b37",
52
47
  "dist/logging.js": "8ec57fea26983aa071b05fe15d680d6550b03488101163ed9666da674118cf31",
53
- "dist/management-policy.js": "bd5563da52c53ace71b01daed0d2a492baeb3f26f5ec5ee1b0981c2b88df9ffa",
54
- "dist/manifest.js": "d4f58f936febbabf09ec1706baae7c1cb186dcd8a3d29b5725dad5b6eaf43a3f",
48
+ "dist/manifest.js": "977f0f1cbe4466f7fbd9f2a0745e016cb9ebae640bc264fba04b4d02f4dbaca9",
55
49
  "dist/match.js": "53ebcc2cda529a8d07fb83f8cb69a1446036ffc1641af3c4bd45a55851bded28",
56
50
  "dist/mcp-authoring.js": "5030f52ef9c7ebc7b97305fcce687eb0a0d6164e34fda87f8278135a1e6f3d65",
57
- "dist/mcp.js": "ac96c5a094d7cc88cf16d1d1e3c23f45773729fe28855f921581badb2b95eb89",
58
- "dist/observability.js": "13a22b9e34cf8da3739856eeada8522fb6c43765386cae07c6aa0981447fcb83",
51
+ "dist/mcp.js": "e2d70f63481dc40aae60f7c27128e6a09ec370b69fd68aee8890bcc990f4f304",
52
+ "dist/observability.js": "f4b1ab496f051fe3ef2ed36b2b2e469c63a8940700e2df852e6ff2d9cccdd5ae",
59
53
  "dist/operator-host.js": "e3dac9d43a83beb775202b4ab9eeeba7ac63cebfc5dcc669be38d825be587bd0",
60
54
  "dist/plugins.js": "a61aa933cd9e1b41020b86fae78aa3a7f45f5313124aa11b50cb57898722385e",
61
55
  "dist/policies/agents.js": "c1dbc1e90339f472eeeb2372f7f4637f59a5d210797ea680ac8120a02aa20c10",
62
- "dist/policies/cache.js": "ef16d7e592526c29f865b2693faa2c8afbea41c6526687563dffcb13a77fc1f7",
56
+ "dist/policies/cache.js": "03f79b91694bb78a98de4473384e5110e1ae1c3c83d46f929495d03af04d9da1",
63
57
  "dist/policies/compression.js": "800f3ae3b1a4c1fe58c3d9908364ea9d78f655ff50c7f7589f584378e7f194b0",
64
58
  "dist/policies/security.js": "2b3bfdf82140156fea4b162adc28ee43ccd85c402fb200680dbb8d8706417420",
65
59
  "dist/policies/throttle.js": "bbb0cb1c24cf70d0a1ac32fc0618efb6c44e5b5d42564842f9c585ff7e501603",
66
60
  "dist/policies.js": "23fa019b7bf1cdedcc5f7772d2f157e4b381a554bce9e84594c2be4a0f8f76b8",
67
- "dist/policy.js": "8285ed144ba42fd34e926e526cd4dcf60d5ab3668adf4f0b797f074ebd5243ce",
61
+ "dist/policy.js": "33c527008fafe4af18243ba668d069517c20659150f9f1d1a5a1d076e8724f89",
68
62
  "dist/prerender.js": "0386ea2411e00b9563b5349133f1d723947b01f837fa92d4c61a699c9cfb5cdb",
69
- "dist/project-tests.js": "225761d36bbe7e185c49c10c0334e579caf7d98647c9941faa4b0ea6406522fa",
63
+ "dist/project-tests.js": "445eaa139d2f1a2534369cfdeef081ef518de03218a467e7c5c2059caf840270",
70
64
  "dist/provider-verification.js": "ffcf9f05e8cb5f71ea46a97e279e8a613fdfdf9435ab02bd57af11e2680a9107",
71
65
  "dist/proxy.js": "34107e59ac0dbbea090efa3cf5badf3226c5302ba25e8f8ff9b2b5596ed80e50",
72
- "dist/readiness.js": "d419b09a83dc8e6790be7c91ba4e1a2a5cf9b141577c98bd24e99a3609553649",
66
+ "dist/readiness.js": "6ce69a005d7c45cf0ef97fa5eaba34279d43ebf28124eca18359d9db4f599808",
73
67
  "dist/recipes.js": "11853f23e63c3c65c5bed03a278cde0349d28c9fa23266c9907a2df318bb5fc3",
74
68
  "dist/route-diff.js": "eebac076a886ddfb2bf5b97ea09d81f888e7bb0063d482eddb8e5fef9acea107",
75
- "dist/router.js": "4315585232561c1737e579a2cbd990596d337b37565dd449bcb1bbaa6abbfb13",
76
- "dist/runtime.js": "f6f1bfa320a427423d66be845de3368552b1f4cca4a28136b913ac26fb9d6169",
77
- "dist/scaffold.js": "9dd30112e070ec554bd7cae1ca0a8b03642632be57411ba76a72972f4ba49b3e",
69
+ "dist/router.js": "cf831025d5ed6778d1d6a0e541177799794e76766c82084ded81d3e87317ca1e",
70
+ "dist/runtime.js": "0470dcab0774d2da3336e9ce76a4853757bf96081e52bd741f6b44c1bdd4161f",
71
+ "dist/sandbox.js": "c88c5aa5dd95b4f55f1ba62da7969ad66dbd97c30f72e5b5f2817a2098ac1c65",
72
+ "dist/scaffold.js": "3ef243aceca7ddf2780aa2c8c04ae17edd07682d3a418fd5d65ba00f5c762f14",
78
73
  "dist/schema-query.js": "bfd1844acd8d54ac361115191fcfbaf828a67e223b9ec6e9c22f158761af149b",
79
- "dist/server.js": "d8c78d1695b3c11328f3f983281fba78c2aca6a66cf755b39ecf4ec2ec4b6f29",
74
+ "dist/server.js": "d878bc8f0d6a05f8fd25997cace3150e2ff4d44ee9c8f4b5530e29601e164be1",
80
75
  "dist/signals.js": "b55e54efc8fb6703e08f2a1e808ec8e011bda67738db8ceadf1b248f695795be",
81
76
  "dist/site.js": "c40a06a6f9e2d6d3bea94727783c115fed4309bb0190787f58434eed64cbde84",
82
- "dist/sqlite-version.js": "a3a08d99064bd0f35847e288027e00a516efdd61c675bfbbe7726e3aa900f0d1",
83
- "dist/tooling.js": "eaf9aa49df17b5ab3bc538976b2849fac52b5ca779b6e814fd7ca592ecd757b7",
84
- "dist/types.js": "8579202bf322e542969bbd9e5b98774963b12ac69bdf362eebfcb068de2d5421",
85
- "dist/typescript-authoring.js": "156c617c2cbf992c299dbbbd5398500f0f30d1c90df8630509036da4ffd7cae9",
77
+ "dist/tooling.js": "7409db98422325cd4181d073d645ea57debca0e54cb151123474b85f4d4ed7dc",
78
+ "dist/trusted-functions.js": "befd256848be786f97e372c6af0e05ac08a0c93baed38bf2470ea49bc00dd3e4",
79
+ "dist/types.js": "a1213d033b6a347c63e94685ab9056d8cd7a301d9c83150142afe0f3ab2c6536",
80
+ "dist/typescript-authoring.js": "27829098603029ab565174e540fb1d5ef68c1a923272b7186f64a362f5244872",
86
81
  "dist/vercel.js": "a0da20c095d0c7fa8f579ed4dd5951e7a34191f8a72a57c5e782c9eab39f6f08",
87
- "dist/verify-deployment.js": "cac5d10ea18ae5f699df0eb0a5229837297c7627b47e65140f053820cae2414d",
88
- "dist/scripts/operational-drills.js": "b7e9448279c59ee9de62023055a9ff5c7a60bcceed4c33d023edc612f5575af9"
82
+ "dist/verify-deployment.js": "0c2e68f3cf258a364728c801411df5284343bc01b75a64fe275ff84e397e998b",
83
+ "dist/scripts/operational-drills.js": "c26c406d28013cc554b25a5b6bf8baf4e71d5e67e50d866fae546f538a192317"
89
84
  }
90
85
  }
@@ -16,7 +16,7 @@ export function renderMcpConfig(project = '.') {
16
16
  assert(/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(project) && !project.split('/').includes('..'), 'MCP project path must be a relative path without ..');
17
17
  return JSON.stringify({ mcpServers: { urlcode: { command: 'urlcode', args: ['mcp', '--project', project] } } }, null, 2) + '\n';
18
18
  }
19
- const handlerNames = ['redirect','respond','page','static','download','function','link','proxy','conditional','extension'];
19
+ const handlerNames = ['redirect','respond','page','static','download','function','proxy','conditional','extension'];
20
20
 
21
21
  /**
22
22
  * The application-level AGENTS.md written by `urlcode init`. Built from the
@@ -48,11 +48,11 @@ authentication; the runtime provides them. Read this file before changing anythi
48
48
  3. Run \`urlcode recipes list\` and \`urlcode recipes show NAME\` before writing a
49
49
  route from scratch. If a recipe covers the need, add it with
50
50
  \`urlcode recipes add NAME --out DIR\` and adapt the copy.
51
- 4. Prefer YAML over code. Prefer native handlers over functions.
51
+ 4. Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement. Check supported extensions and recipes first; explain any capability gap.
52
52
 
53
53
  ## Ask the runtime through MCP first
54
54
 
55
- \`${mcpConfigFile}\` registers the read-only \`urlcode mcp\` server. When it is
55
+ When present, \`${mcpConfigFile}\` registers the read-only \`urlcode mcp\` server. When it is
56
56
  available, prefer its tools over reading documents: \`get_context\`,
57
57
  \`get_capability\`, \`get_schema\`, \`search_recipes\`, \`explain\`, \`get_manifest\`.
58
58
  The CLI equivalents are the fallback: \`urlcode context\`, \`urlcode capabilities NAME\`,
@@ -62,23 +62,24 @@ The CLI equivalents are the fallback: \`urlcode context\`, \`urlcode capabilitie
62
62
  ## What the runtime provides (this version)
63
63
 
64
64
  - Handlers, exactly one per route: ${handlers.map(name => `\`${name}\``).join(', ')}.
65
- - Ordered \`middleware\` around any handler, declared in YAML, run in the sandbox.
65
+ - Ordered \`middleware\` around any handler, declared in YAML, trusted by default.
66
66
  - Validated inputs: \`parameters\`, \`request.body\` and \`methods\` on the route;
67
67
  functions receive validated \`args\`, never raw user input.
68
68
  - Policies, host-enforced and off by default: ${policies.map(name => `\`${name}\``).join(', ')}.
69
69
  - Site conventions under \`site\`, each generating one native route: ${site.join(', ')}.
70
70
  - Bindings: named \`env\` and \`secrets\` references resolved by the operator, never values in YAML.
71
71
 
72
- Never recreate any of these in a function. If a requirement seems to need one
73
- of them and it is missing, that is a report, not an invitation to reimplement.
72
+ Never recreate any of these in a function; a missing one is a report, not an
73
+ invitation to reimplement it.
74
74
 
75
- ## Functions and middleware are sandboxed
75
+ ## Functions and middleware are trusted by default; sandbox is opt-in
76
76
 
77
- Guest code runs in an isolated JavaScript engine with a fresh heap per call.
78
- It sees a text/JSON \`Request\`/\`Response\` subset, validated \`args\` and granted
79
- \`env\`. There is no \`fetch\`, no Node API, no filesystem, no timers and no
80
- imports outside the project. Do not write code that needs them; declare a
81
- \`proxy\` route or a binding instead and say why.
77
+ A route's \`function\`/\`middleware\` code runs trusted, in-process, with full
78
+ Node/filesystem/\`fetch\` access, receiving only the declared/granted \`args\`
79
+ and \`env\`/\`secrets\`. Add \`sandbox: true\` when code warrants isolation
80
+ (untrusted input, an unreviewed contribution, an especially sensitive
81
+ secret): that route then gets a text/JSON subset only, no Node/filesystem/
82
+ outside imports — use \`proxy\`/a binding instead, and say why in \`description\`.
82
83
 
83
84
  ## Checks that count as evidence
84
85
 
@@ -88,10 +89,9 @@ urlcode test
88
89
  urlcode audit --expect-routes ${routes}
89
90
  \`\`\`
90
91
 
91
- Run all three after every change. Update the expected route count deliberately
92
- when you add or remove a route, and add fixtures to \`tests/requests.json\` for
93
- every new route (positive and negative cases, every active method, HEAD).
94
- Without a global install, invoke \`node /path/to/urlcode/src/cli.ts\` instead of \`urlcode\`.
92
+ Run all three after every change, updating the route count deliberately and
93
+ adding \`tests/requests.json\` fixtures for every new route (positive/negative,
94
+ every active method, HEAD). No global install: use \`node /path/to/urlcode/src/cli.ts\`.
95
95
 
96
96
  ## Rules
97
97
 
@@ -101,8 +101,8 @@ Without a global install, invoke \`node /path/to/urlcode/src/cli.ts\` instead of
101
101
  stop; the operator grants it outside this project, pinned to the revision.
102
102
  - Secrets stay out of the project: no keys, tokens or credentials in YAML,
103
103
  functions, fixtures, \`.env\` files that are not ignored, or commit messages.
104
- - Authentication is host processing: declare \`auth\` on the route, never build
105
- login forms, sessions or password checks in functions.
104
+ - Protect a route with \`auth: true\`/\`auth: { role: admin }\` where an \`auth\`
105
+ extension is declared; \`cache\` likewise expands to \`policies.cache\`.
106
106
  - Validation, tests and the audit are the evidence. Local checks are not a
107
107
  deployment, a soak test or a security review; do not claim otherwise.
108
108
 
@@ -0,0 +1,134 @@
1
+ import { analyzeProjectCapabilities, analyzeCompiledCapabilities, assertTargetCompatibility } from './capabilities.js';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import { join, dirname } from 'node:path';
4
+ import { loadDocument } from './config.js';
5
+ import { compileRoutes } from './router.js';
6
+ import { compileAssets } from './assets.js';
7
+ import { assert, ConfigError } from './errors.js';
8
+ import { applySite } from './site.js';
9
+ import { buildManifest, renderManifest, manifestPath } from './manifest.js';
10
+
11
+
12
+
13
+
14
+
15
+ // The manifests below are this target's own build output, not a published
16
+ // contract: their shape may change with any release.
17
+ const FORMAT = 1;
18
+
19
+ /** One S3 per-object website redirect: a zero-byte object at `key` carrying
20
+ * `x-amz-website-redirect-location: location`, which S3 always answers with a
21
+ * 301 regardless of the object's own metadata. */
22
+
23
+ /** One served file: the bytes are written under `out/objects/<key>`; the
24
+ * metadata here is what a deploy step sets as the S3 object's own headers. */
25
+
26
+
27
+
28
+
29
+ // S3 static-website object keys never start with a slash; the bucket's own
30
+ // "Index document" suffix (conventionally index.html) is what answers a
31
+ // request for a folder path or the site root, exactly the same convention
32
+ // used here for pattern '/'.
33
+ function objectKey(pattern ) {
34
+ const trimmed = pattern.slice(1);
35
+ return trimmed === '' ? 'index.html' : trimmed;
36
+ }
37
+
38
+ export async function buildStatic(project , { out = 'dist/static', origin, log = () => {} } = {}) {
39
+ const loaded = await loadDocument(project);
40
+ // Generated site routes are built like declared ones; the ones that need
41
+ // an origin get it from --origin, exactly as the server does.
42
+ await applySite(loaded, { origin, log });
43
+ assertTargetCompatibility(analyzeProjectCapabilities(loaded, 'static'));
44
+ const compiled = await compileRoutes(loaded, {}, {}, undefined);
45
+ const routes = [...compiled.exact.values(), ...[...compiled.byLength.values()].flat(), ...compiled.mounts];
46
+
47
+ assertTargetCompatibility(analyzeCompiledCapabilities(loaded.document, compiled, 'static'));
48
+ assert(routes.length, 'No routes to build');
49
+
50
+ // Capability analysis already refused function/middleware/extension,
51
+ // parameters, request.body, response.headers, bindings, proxy/signals,
52
+ // conditional/conditions and every policy for this target, so every
53
+ // surviving route is a plain redirect/respond/page/static/download with no
54
+ // path placeholders. What is left below are shapes capability analysis
55
+ // cannot see because they are configuration, not capability, distinctions:
56
+ // a redirect that needs request-time logic (query passthrough/mapping, a
57
+ // non-301 status), and a route that needs request-time lifecycle checks
58
+ // (`enabled: false`, `expires`) or a method other than GET/HEAD.
59
+ const defaultMethods = new Set(['GET', 'HEAD']);
60
+ for (const route of routes) {
61
+ assert(route.enabled !== false, `${route.pattern}: static hosting has no server to answer a disabled route with 404; remove the route instead of disabling it`);
62
+ assert(route.expiresAt === undefined, `${route.pattern}: static hosting has no server to answer an expired route with 410; remove the route when it expires`);
63
+ assert(route.methods.every(method => defaultMethods.has(method)), `${route.pattern}: static hosting only ever answers GET/HEAD; this target refuses declared methods ${route.methods.join(', ')}`);
64
+ if (route.redirect) {
65
+ assert(!route.names.length, `${route.pattern}: static hosting cannot redirect a path pattern; only an exact literal path can carry an S3 per-object redirect`);
66
+ const query = route.redirect.query;
67
+ assert(!query?.pass?.length && !(query?.map && Object.keys(query.map).length), `${route.pattern}: static hosting cannot pass or map query parameters into a redirect target; this needs request-time logic S3 does not have`);
68
+ assert(!route.redirect.status || route.redirect.status === 301, `${route.pattern}: S3's per-object website redirect always answers 301; this route declares ${route.redirect.status}, which would silently change on this target`);
69
+ }
70
+ }
71
+
72
+ await compileAssets(loaded.root, routes);
73
+
74
+ const objectsDir = join(out, 'objects');
75
+ await mkdir(objectsDir, { recursive: true });
76
+ const redirects = [];
77
+ const objects = [];
78
+ const writeObject = async (key , body ) => {
79
+ const target = join(objectsDir, ...key.split('/'));
80
+ await mkdir(dirname(target), { recursive: true });
81
+ await writeFile(target, body);
82
+ };
83
+
84
+ for (const route of routes) {
85
+ if (route.redirect) {
86
+ const key = objectKey(route.pattern);
87
+ redirects.push({ key, location: route.redirect.url, status: 301 });
88
+ await writeObject(key, new Uint8Array());
89
+ continue;
90
+ }
91
+ if (route.static) {
92
+ const asset = route.asset;
93
+ assert(asset instanceof Map, `${route.pattern}: static mount has no compiled asset snapshot`);
94
+ const prefix = route.prefix .slice(1);
95
+ for (const [name, file] of asset) {
96
+ const key = prefix + name;
97
+ objects.push({ key, contentType: file.type, cacheControl: file.cache });
98
+ await writeObject(key, file.body);
99
+ }
100
+ continue;
101
+ }
102
+ if (route.page || route.download) {
103
+ const asset = route.asset;
104
+ assert(asset && !(asset instanceof Map), `${route.pattern}: route has no compiled asset snapshot`);
105
+ const key = objectKey(route.pattern);
106
+ objects.push({ key, contentType: asset.type, cacheControl: asset.cache, ...(asset.attachment ? { contentDisposition: asset.attachment } : {}) });
107
+ await writeObject(key, asset.body);
108
+ continue;
109
+ }
110
+ if (route.reply) {
111
+ const key = objectKey(route.pattern);
112
+ const contentType = route.reply.headers.find(([name]) => name === 'content-type')?.[1] ?? 'application/octet-stream';
113
+ objects.push({ key, contentType });
114
+ await writeObject(key, route.reply.body);
115
+ continue;
116
+ }
117
+ // Unreachable: capability analysis already refuses every other handler for this target.
118
+ throw new ConfigError(`${route.pattern}: static target has no compiler for this route`);
119
+ }
120
+
121
+ const redirectManifest = { format: FORMAT, redirects };
122
+ const objectManifest = { format: FORMAT, objects };
123
+ await writeFile(join(out, 'redirects.json'), JSON.stringify(redirectManifest, null, 2) + '\n');
124
+ await writeFile(join(out, 'objects.json'), JSON.stringify(objectManifest, null, 2) + '\n');
125
+ // The semantic manifest travels with the artifact so a reviewer can read
126
+ // what was built without the project checkout (docs/TOOLING.md).
127
+ const manifest = manifestPath(out);
128
+ await writeFile(manifest, renderManifest(await buildManifest(project, origin === undefined ? {} : { origin })));
129
+ return { out, format: FORMAT, version: loaded.version, routes: routes.length, redirects: redirects.length, files: objects.length, manifest };
130
+ }
131
+
132
+ // Exported for tests that need the exact key a route compiles to without
133
+ // duplicating the mapping rule.
134
+ export const staticObjectKey = objectKey;
@@ -1,13 +1,19 @@
1
- import { hasExtensionPolicy } from './extensions.js';
1
+ import { hasExtensionPolicy, effectiveExtensionPolicies } from './extensions.js';
2
+
2
3
  import { ConfigError } from './errors.js';
3
4
  import { effectivePolicies, registry } from './policies.js';
4
5
 
5
6
 
6
- export const capabilityTargets = ['self-hosted', 'cloudflare', 'aws', 'vercel'] ;
7
+ export const capabilityTargets = ['self-hosted', 'cloudflare', 'aws', 'vercel', 'static'] ;
7
8
 
9
+ /** `static` never reaches the policy-module handshake (src/types.ts `TargetName`): it refuses every
10
+ * runtime policy outright, so it needs no entry in that exhaustive per-target record. */
11
+
8
12
 
9
- export const capabilityNames = ['extension','policies.extensions','proxy', 'signals', 'conditional', 'conditions', 'redirect', 'respond', 'page', 'static', 'download', 'function', 'middleware', 'link', 'dynamicLinks', 'parameters', 'methods', 'enabled', 'expires', 'request.body', 'response.headers', 'bindings', 'policies.agents', 'policies.security', 'policies.cache', 'policies.compression', 'policies.throttle'] ;
13
+ export const capabilityNames = ['extension','policies.extensions','proxy', 'signals', 'conditional', 'conditions', 'redirect', 'respond', 'page', 'static', 'download', 'function', 'middleware', 'parameters', 'methods', 'enabled', 'expires', 'request.body', 'response.headers', 'bindings', 'policies.agents', 'policies.security', 'policies.cache', 'policies.compression', 'policies.throttle'] ;
10
14
 
15
+ /** The resolved operator registration set, when known (loaded via --host-file, same as `inspectExtensions`). Keyed by extension name. */
16
+
11
17
 
12
18
 
13
19
 
@@ -23,17 +29,55 @@ export const capabilityNames = ['extension','policies.extensions','proxy', 'sign
23
29
  export function normalizeCapabilityTarget(target ) {
24
30
  if (target === 'node') return 'self-hosted';
25
31
  if ((capabilityTargets ).includes(target)) return target ;
26
- throw new ConfigError('Unknown capability target; use self-hosted, cloudflare, aws or vercel');
32
+ throw new ConfigError('Unknown capability target; use self-hosted, cloudflare, aws, vercel or static');
27
33
  }
28
- const internalTarget = (target ) => target === 'self-hosted' ? 'node' : target;
34
+ const internalTarget = (target ) => target === 'self-hosted' ? 'node' : target;
29
35
  const deployment = (target ) => target === 'self-hosted' ? 'local-runtime' : 'unverified';
30
36
  const policyNames = Object.keys(registry) ;
31
37
 
38
+ // static hosting (S3 + CloudFront) has no server at all, so no capability
39
+ // needing request-time evaluation can be represented; only capabilities a
40
+ // build can lower ahead of time (redirect/respond/page/static/download,
41
+ // methods/enabled/expires) survive.
42
+ const staticRefusals = {
43
+ extension: 'no server, so no operator extension registry to delegate to',
44
+ 'policies.extensions': 'no server, so no operator extension registry to delegate to',
45
+ proxy: 'no server, so no bounded egress at request time',
46
+ signals: 'no server, so no fire-and-forget egress after a reply',
47
+ conditional: 'no server, so request-time condition matching is not possible',
48
+ conditions: 'no server, so request-time condition matching is not possible',
49
+ function: 'no server, so no dynamic execution',
50
+ middleware: 'no server, so no middleware execution',
51
+ parameters: 'no server, so no request-time parameter validation',
52
+ 'request.body': 'no server, so there is no request body to read or validate',
53
+ 'response.headers': 'no server, so response headers cannot be added per request; set them via S3 object metadata or a CloudFront response headers policy instead',
54
+ bindings: 'no server, so env/secret bindings cannot be resolved per request',
55
+ };
56
+
32
57
  // Policy modules remain the authority for configuration-dependent support.
33
- function decision(capability , target , policies ) {
34
- if(capability==='extension'||capability==='policies.extensions')return target==='cloudflare'?{support:'refused',reason:'Operator extensions have no Worker artifact lowering'}:{support:'native',reason:'Requires an operator registry and exact revision pin at activation'};
58
+ // `extension`/`policies.extensions` cannot get a target-independent answer:
59
+ // each registered extension declares its own `targets` (src/extensions.ts),
60
+ // so refusal on aws/vercel/self-hosted depends on that extension, not on the
61
+ // capability name. `extensionNames` names the specific extension(s) this
62
+ // requirement involves; `extensions` is the resolved registration set, when
63
+ // the caller has one (loaded via --host-file, same as `inspectExtensions`).
64
+ function decision(capability , target , policies , extensionNames , extensions ) {
65
+ if(capability==='extension'||capability==='policies.extensions'){
66
+ if(target==='cloudflare')return {support:'refused',reason:'Operator extensions have no Worker artifact lowering'};
67
+ if(target==='static')return {support:'refused',reason:staticRefusals[capability] };
68
+ if(!extensions)return {support:'conditional',reason:'Depends on the specific registered extension\'s declared targets; resolve with --host-file (CLI) or the runtime\'s extensions option'};
69
+ const names=extensionNames?.length?extensionNames:[...extensions.keys()];
70
+ if(!names.length)return {support:'unknown',reason:'No extension registration to check'};
71
+ const checked=names.map(name=>{const registration=extensions.get(name);return {name,support:(registration?(registration.targets.includes(internalTarget(target))?'native':'refused'):'unknown') };});
72
+ const unknown=checked.filter(item=>item.support==='unknown').map(item=>item.name);
73
+ if(unknown.length)return {support:'unknown',reason:`Not registered by the host file: ${unknown.join(', ')}`};
74
+ const refused=checked.filter(item=>item.support==='refused').map(item=>item.name);
75
+ if(refused.length)return {support:'refused',reason:`Refused by the extension's own declared targets: ${refused.join(', ')}`};
76
+ return {support:'native',reason:'Registered extension declares support for this target'};
77
+ }
35
78
  const policy = policyNames.find(name => capability === `policies.${name}`);
36
79
  if (policy) {
80
+ if (target === 'static') return { support: 'refused', reason: 'no server, so runtime policies are not enforced for static hosting' };
37
81
  if (policy === 'throttle' && !policies && (target === 'aws' || target === 'vercel')) {
38
82
  return { support: 'conditional', reason: 'Only partition: route is implemented; counters are per instance' };
39
83
  }
@@ -44,19 +88,24 @@ function decision(capability , target , policies
44
88
  : support === 'unknown' ? 'No implementation evidence for this target'
45
89
  : 'Implemented by the existing policy module; configuration validation still applies' };
46
90
  }
47
- if (target !== 'self-hosted') {
91
+ if (target === 'static') {
92
+ const reason = staticRefusals[capability];
93
+ if (reason) return { support: 'refused', reason };
94
+ // redirect/respond/page/static/download/methods/enabled/expires fall through to the target's own success reason below.
95
+ } else if (target !== 'self-hosted') {
48
96
  const reason = ['proxy','signals'].includes(capability) ? 'bounded egress currently requires the self-hosted Node lifecycle'
49
97
  : target === 'cloudflare' && ['conditional','conditions'].includes(capability) ? 'conditional routing has no Worker artifact lowering yet'
50
- : capability === 'function' ? 'isolated functions need worker threads and the WASM engine'
51
- : capability === 'middleware' ? 'declares middleware that needs the sandbox'
52
- : capability === 'link' || capability === 'dynamicLinks' ? 'stored live links need a durable writable store'
98
+ : capability === 'function' ? 'functions need the self-hosted Node lifecycle, whether trusted (in-process) or sandboxed (worker threads and the WASM engine)'
99
+ : capability === 'middleware' ? 'middleware needs the self-hosted Node lifecycle, whether trusted (in-process) or sandboxed (worker threads and the WASM engine)'
53
100
  : target === 'cloudflare' && ['page', 'static', 'download'].includes(capability) ? 'assets need a static-asset binding'
54
101
  : target === 'cloudflare' && capability === 'bindings' ? 'env and secret bindings would have to be baked into the artifact'
55
102
  : undefined;
56
103
  if (reason) return { support: 'refused', reason };
57
104
  }
58
- return { support: target === 'cloudflare' ? 'compiled' : 'native', reason: target === 'self-hosted'
105
+ return { support: target === 'cloudflare' || target === 'static' ? 'compiled' : 'native', reason: target === 'self-hosted'
59
106
  ? 'Implemented and tested in the local runtime'
107
+ : target === 'static'
108
+ ? 'Compiled ahead of time into S3/CloudFront redirect metadata or static objects; provider deployment unverified'
60
109
  : 'Implemented with local adapter tests; provider deployment unverified; transport normalization limits apply' };
61
110
  }
62
111
 
@@ -83,7 +132,7 @@ export function routeCapabilities(route , document
83
132
  if (branch.respond && !result.includes('respond')) result.push('respond');
84
133
  }
85
134
  }
86
- for (const name of ['redirect', 'respond', 'page', 'static', 'download', 'function', 'link'] ) if (route[name]) result.push(name);
135
+ for (const name of ['redirect', 'respond', 'page', 'static', 'download', 'function'] ) if (route[name]) result.push(name);
87
136
  if (route.middleware?.length) result.push('middleware');
88
137
  if (route.parameters?.length) result.push('parameters');
89
138
  // Defaults are still semantics required by every route.
@@ -97,32 +146,42 @@ export function routeCapabilities(route , document
97
146
  return result;
98
147
  }
99
148
 
100
- function analyze(document , routes , requestedTarget ) {
149
+ function analyze(document , routes , requestedTarget , registrations ) {
101
150
  const target = normalizeCapabilityTarget(requestedTarget);
151
+ const extensions = registrations ? new Map(registrations.map(registration => [registration.name, registration])) : undefined;
102
152
  const requirements = [];
103
- if (Object.keys(document.extensions??{}).length) requirements.push({path:'(project)',capability:'extension',...decision('extension',target)});
104
- if (document.dynamicLinks) requirements.push({ path: '(project)', capability: 'dynamicLinks', ...decision('dynamicLinks', target) });
153
+ if (Object.keys(document.extensions??{}).length) requirements.push({path:'(project)',capability:'extension',...decision('extension',target,undefined,Object.keys(document.extensions??{}),extensions)});
105
154
  for (const [path, route] of routes) {
106
155
  const policies = effectivePolicies(document, route);
107
- for (const capability of routeCapabilities(route, document)) requirements.push({ path, capability, ...decision(capability, target, policies) });
156
+ for (const capability of routeCapabilities(route, document)) {
157
+ const extensionNames = capability==='extension' ? (route.extension?[route.extension]:[])
158
+ : capability==='policies.extensions' ? Object.keys(effectiveExtensionPolicies(document,route)) : undefined;
159
+ requirements.push({ path, capability, ...decision(capability, target, policies, extensionNames, extensions) });
160
+ }
108
161
  }
109
162
  const issues = requirements.filter(item => item.support === 'refused' || item.support === 'unknown' || item.support === 'conditional');
110
163
  return { target, deployment: deployment(target), compatible: issues.length === 0, requirements, issues };
111
164
  }
112
165
 
113
- /** Preflight only: call after schema validation/site expansion, before resolving bindings. */
114
- export function analyzeProjectCapabilities(loaded , target ) {
115
- return analyze(loaded.document, Object.entries(loaded.routes), target);
166
+ /**
167
+ * Preflight only: call after schema validation/site expansion, before resolving bindings.
168
+ * `registrations` is the resolved operator extension set, when known (the same
169
+ * shape --host-file loads for `inspectExtensions`); without it, `extension`/
170
+ * `policies.extensions` requirements report `conditional`/`unknown` rather
171
+ * than a blanket `native` that ignores the specific extension's own targets.
172
+ */
173
+ export function analyzeProjectCapabilities(loaded , target , registrations ) {
174
+ return analyze(loaded.document, Object.entries(loaded.routes), target, registrations);
116
175
  }
117
176
 
118
177
  /** Analyze normalized route semantics without exporting validators, resources or resolved secrets. */
119
- export function analyzeCompiledCapabilities(document , compiled , target ) {
178
+ export function analyzeCompiledCapabilities(document , compiled , target , registrations ) {
120
179
  function* entries() {
121
180
  for (const route of compiled.exact.values()) yield [route.pattern, route];
122
181
  for (const bucket of compiled.byLength.values()) for (const route of bucket) yield [route.pattern, route];
123
182
  for (const route of compiled.mounts) yield [route.pattern, route];
124
183
  }
125
- return analyze(document, entries(), target);
184
+ return analyze(document, entries(), target, registrations);
126
185
  }
127
186
 
128
187
  export function assertTargetCompatibility(report ) {
@@ -178,15 +237,10 @@ export const capabilityDetails = {
178
237
  constraints: ['`directory` required, project-relative, 1 to 1024 characters; `index` must be a .html name', 'Refused on Cloudflare: assets need a static-asset binding'], grants: [] },
179
238
  download: { kind: 'handler', summary: 'Serve a project file as an attachment.', schema: ['download'],
180
239
  constraints: ['`file` required, project-relative, 1 to 1024 characters; `filename` at most 255 characters', 'Refused on Cloudflare: assets need a static-asset binding'], grants: [] },
181
- function: { kind: 'handler', summary: 'Sandboxed project function producing the reply.', schema: ['function'],
182
- constraints: ['`source` at most 1024 characters, project-relative; `export` defaults to the default export', '`args` are literals, `{from: path|query|header}` inputs or `{env}` references', 'Self-hosted only: needs worker threads and the WASM engine; no network or filesystem in the guest'], grants: [] },
183
- middleware: { kind: 'middleware', summary: 'Sandboxed modules run before the handler.', schema: ['middleware'],
184
- constraints: ['At most 16 entries, each with a project-relative `source` and optional `export`', 'Self-hosted only: needs the sandbox'], grants: [] },
185
- link: { kind: 'handler', summary: 'Live stored short link resolved from an operator collection.', schema: ['link', 'dynamicLinks'],
186
- constraints: ['`collection` matches ^[A-Za-z][A-Za-z0-9_-]{0,63}$; `code` comes from a path parameter', 'Entry urlcode.yaml must set `dynamicLinks: true`', 'Self-hosted only: needs a durable writable store'],
187
- grants: ['--link-store collection=/absolute/file binding owned by the operator'] },
188
- dynamicLinks: { kind: 'project', summary: 'Entry-point opt-in for live stored-link routes.', schema: ['dynamicLinks'],
189
- constraints: ['Entry urlcode.yaml only; ignored defaults to false', 'Does not enable guest storage or management access'], grants: ['--link-store binding when any link route is declared'] },
240
+ function: { kind: 'handler', summary: 'Project function producing the reply; trusted and unsandboxed by default, sandboxed opt-in.', schema: ['function'],
241
+ constraints: ['`source` at most 1024 characters, project-relative; `export` defaults to the default export', '`args` are literals, `{from: path|query|header}` inputs or `{env}` references', 'Self-hosted only', 'Trusted by default (`sandbox` false or absent): runs in-process with full Node network/filesystem access, like any other project code', 'Route-level `sandbox: true` runs the whole `function`/`middleware` chain isolated instead: worker threads, the WASM engine, no network or filesystem in the guest (docs/FUNCTION-SECURITY.md)'], grants: [] },
242
+ middleware: { kind: 'middleware', summary: 'Modules run before the handler; trusted and unsandboxed by default, sandboxed opt-in.', schema: ['middleware'],
243
+ constraints: ['At most 16 entries, each with a project-relative `source` and optional `export`', 'Self-hosted only', 'Same trusted-by-default / `sandbox: true` opt-in as `function`, applied uniformly to the whole route'], grants: [] },
190
244
  parameters: { kind: 'request', summary: 'Validated path, query and header inputs.', schema: ['parameters'],
191
245
  constraints: ['Names match ^[A-Za-z_][A-Za-z0-9_-]*$ and `in` is path, query or header', 'Schema types: string, integer, number, boolean, array; length bounds up to 8192'], grants: [] },
192
246
  methods: { kind: 'routing', summary: 'Allowed HTTP methods; defaults to GET and HEAD.', schema: ['methods'],
@@ -35,7 +35,6 @@ function usage(base ,files ,name )
35
35
  let document ;try{document=parse(readFileSync(file,'utf8'));}catch{continue;}
36
36
  if(!object(document))continue;
37
37
  const routes =[];
38
- if(name==='dynamicLinks'&&document.dynamicLinks===true)routes.push('(project)');
39
38
  if(name==='extension'&&object(document.extensions)&&Object.keys(document.extensions).length)routes.push('(project)');
40
39
  for(const [path,route] of Object.entries(object(document.routes)?document.routes:{})) {
41
40
  if(!object(route))continue;