@jimhoyd/urlcode 0.3.0 → 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 (380) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +122 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +108 -0
  3. package/.claude-plugin/marketplace.json +18 -0
  4. package/CONTRIBUTING.md +30 -2
  5. package/README.md +195 -255
  6. package/ROADMAP.md +143 -15
  7. package/SECURITY.md +31 -9
  8. package/dist/BUILD-MANIFEST.json +72 -47
  9. package/dist/adapters.js +4 -23
  10. package/dist/agent-lists.js +1 -1
  11. package/dist/agents-guide.js +113 -0
  12. package/dist/authoring-files.js +60 -0
  13. package/dist/authoring.js +11 -1
  14. package/dist/aws.js +4 -3
  15. package/dist/build-cloudflare.js +11 -24
  16. package/dist/build-static.js +134 -0
  17. package/dist/bulk.js +37 -0
  18. package/dist/capabilities.js +262 -0
  19. package/dist/capability-query.js +71 -0
  20. package/dist/catalog.js +105 -0
  21. package/dist/cli.js +165 -34
  22. package/dist/client-address.js +1 -1
  23. package/dist/compliance-rules/baseline.js +9 -17
  24. package/dist/compliance-rules/privacy.js +7 -18
  25. package/dist/compliance-rules/shared.js +0 -2
  26. package/dist/compliance-rules/strict.js +5 -5
  27. package/dist/compliance.js +6 -8
  28. package/dist/conditions.js +88 -0
  29. package/dist/config.js +69 -6
  30. package/dist/context.js +155 -0
  31. package/dist/ecosystem-cli.js +88 -0
  32. package/dist/egress.js +98 -0
  33. package/dist/examples.js +92 -0
  34. package/dist/explain-cli.js +64 -0
  35. package/dist/explain.js +131 -0
  36. package/dist/extensions.js +231 -0
  37. package/dist/function-sources.js +49 -5
  38. package/dist/function-worker.js +3 -1
  39. package/dist/functions.js +84 -13
  40. package/dist/guest-api.js +29 -3
  41. package/dist/index.js +40 -6
  42. package/dist/init-with.js +165 -0
  43. package/dist/interchange-cli.js +42 -0
  44. package/dist/interchange.js +189 -0
  45. package/dist/manifest.js +109 -0
  46. package/dist/match.js +2 -2
  47. package/dist/mcp-authoring.js +147 -0
  48. package/dist/mcp.js +97 -0
  49. package/dist/observability.js +7 -21
  50. package/dist/operator-host.js +29 -0
  51. package/dist/plugins.js +12 -0
  52. package/dist/policies/agents.js +2 -2
  53. package/dist/policies/cache.js +8 -3
  54. package/dist/policies/compression.js +2 -1
  55. package/dist/policies/security.js +0 -0
  56. package/dist/policies.js +1 -1
  57. package/dist/policy.js +56 -15
  58. package/dist/prerender.js +100 -41
  59. package/dist/project-tests.js +3 -3
  60. package/dist/provider-verification.js +92 -0
  61. package/dist/proxy.js +44 -0
  62. package/dist/readiness.js +34 -11
  63. package/dist/recipes.js +41 -0
  64. package/dist/route-diff.js +106 -0
  65. package/dist/router.js +45 -7
  66. package/dist/runtime.js +164 -64
  67. package/dist/sandbox.js +48 -0
  68. package/dist/scaffold.js +0 -0
  69. package/dist/schema-query.js +62 -0
  70. package/dist/scripts/operational-drills.js +12 -54
  71. package/dist/server.js +3 -29
  72. package/dist/signals.js +24 -0
  73. package/dist/site.js +0 -0
  74. package/dist/tooling.js +96 -0
  75. package/dist/trusted-functions.js +210 -0
  76. package/dist/types/adapters.d.ts +7 -4
  77. package/dist/types/agent-lists.d.ts +0 -1
  78. package/dist/types/agents-guide.d.ts +17 -0
  79. package/dist/types/authoring-files.d.ts +10 -0
  80. package/dist/types/aws.d.ts +3 -1
  81. package/dist/types/build-cloudflare.d.ts +1 -0
  82. package/dist/types/build-static.d.ts +43 -0
  83. package/dist/types/bulk.d.ts +27 -0
  84. package/dist/types/capabilities.d.ts +64 -0
  85. package/dist/types/capability-query.d.ts +24 -0
  86. package/dist/types/catalog.d.ts +65 -0
  87. package/dist/types/client-address.d.ts +0 -1
  88. package/dist/types/compliance-rules/baseline.d.ts +1 -9
  89. package/dist/types/compliance-rules/privacy.d.ts +1 -4
  90. package/dist/types/compliance-rules/shared.d.ts +0 -2
  91. package/dist/types/compliance-rules/strict.d.ts +0 -5
  92. package/dist/types/compliance.d.ts +0 -3
  93. package/dist/types/conditions.d.ts +19 -0
  94. package/dist/types/config.d.ts +21 -2
  95. package/dist/types/context.d.ts +66 -0
  96. package/dist/types/ecosystem-cli.d.ts +17 -0
  97. package/dist/types/egress.d.ts +46 -0
  98. package/dist/types/examples.d.ts +50 -0
  99. package/dist/types/explain-cli.d.ts +11 -0
  100. package/dist/types/explain.d.ts +95 -0
  101. package/dist/types/extensions.d.ts +177 -0
  102. package/dist/types/function-sources.d.ts +9 -0
  103. package/dist/types/functions.d.ts +48 -5
  104. package/dist/types/guest-api.d.ts +1 -0
  105. package/dist/types/index.d.ts +36 -6
  106. package/dist/types/init-with.d.ts +30 -0
  107. package/dist/types/interchange-cli.d.ts +16 -0
  108. package/dist/types/interchange.d.ts +42 -0
  109. package/dist/types/manifest.d.ts +79 -0
  110. package/dist/types/match.d.ts +1 -0
  111. package/dist/types/mcp-authoring.d.ts +92 -0
  112. package/dist/types/mcp.d.ts +12 -0
  113. package/dist/types/observability.d.ts +3 -14
  114. package/dist/types/operator-host.d.ts +8 -0
  115. package/dist/types/plugins.d.ts +2 -0
  116. package/dist/types/policies/agents.d.ts +0 -2
  117. package/dist/types/policies/compression.d.ts +2 -0
  118. package/dist/types/policies/security.d.ts +0 -1
  119. package/dist/types/policy.d.ts +15 -4
  120. package/dist/types/project-tests.d.ts +3 -2
  121. package/dist/types/provider-verification.d.ts +53 -0
  122. package/dist/types/proxy.d.ts +21 -0
  123. package/dist/types/readiness.d.ts +10 -3
  124. package/dist/types/recipes.d.ts +30 -0
  125. package/dist/types/route-diff.d.ts +27 -0
  126. package/dist/types/router.d.ts +2 -1
  127. package/dist/types/runtime.d.ts +11 -27
  128. package/dist/types/sandbox.d.ts +12 -0
  129. package/dist/types/scaffold.d.ts +0 -2
  130. package/dist/types/schema-query.d.ts +12 -0
  131. package/dist/types/server.d.ts +1 -4
  132. package/dist/types/signals.d.ts +25 -0
  133. package/dist/types/site.d.ts +0 -1
  134. package/dist/types/tooling.d.ts +115 -0
  135. package/dist/types/trusted-functions.d.ts +29 -0
  136. package/dist/types/types.d.ts +71 -7
  137. package/dist/types/typescript-authoring.d.ts +12 -0
  138. package/dist/types/vercel.d.ts +3 -1
  139. package/dist/types/verify-deployment.d.ts +47 -0
  140. package/dist/types.js +37 -5
  141. package/dist/typescript-authoring.js +142 -0
  142. package/dist/vercel.js +4 -3
  143. package/dist/verify-deployment.js +270 -0
  144. package/docs/AI-AUTHORING.md +232 -15
  145. package/docs/AWS.md +4 -4
  146. package/docs/BEST-PRACTICES.md +3 -2
  147. package/docs/BULK.md +79 -0
  148. package/docs/CAPABILITIES.md +192 -0
  149. package/docs/CAPACITY.md +129 -32
  150. package/docs/CI.md +142 -0
  151. package/docs/CLOUDFLARE.md +1 -2
  152. package/docs/COMPLIANCE.md +6 -9
  153. package/docs/CONDITIONS.md +74 -0
  154. package/docs/DEPLOYMENT-CHECKS.md +108 -0
  155. package/docs/EGRESS.md +125 -0
  156. package/docs/EXTENSIONS.md +398 -0
  157. package/docs/FRAMEWORK.md +198 -0
  158. package/docs/FUNCTION-SECURITY.md +129 -32
  159. package/docs/INSTALL.md +45 -12
  160. package/docs/INTERCHANGE.md +134 -0
  161. package/docs/LOAD-TESTING.md +4 -4
  162. package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
  163. package/docs/MIDDLEWARE.md +29 -16
  164. package/docs/MONITORING.md +2 -19
  165. package/docs/NEXT-PHASE-PLAN.md +98 -0
  166. package/docs/NEXT-STEPS.md +634 -0
  167. package/docs/OBSERVABILITY.md +11 -18
  168. package/docs/OPEN-DECISIONS.md +212 -0
  169. package/docs/OPERATIONAL-PROOF.md +30 -31
  170. package/docs/OPERATIONS.md +29 -35
  171. package/docs/PLUGINS.md +37 -0
  172. package/docs/POLICIES.md +23 -309
  173. package/docs/PRERENDER.md +41 -1
  174. package/docs/PROJECT-DIRECTION.md +75 -8
  175. package/docs/PROVIDER-VERIFICATION.md +84 -0
  176. package/docs/READINESS.md +21 -1
  177. package/docs/README.md +87 -34
  178. package/docs/RECIPES.md +99 -0
  179. package/docs/RELEASE-READINESS.md +57 -35
  180. package/docs/RELEASE-SECURITY.md +116 -7
  181. package/docs/RESILIENCE.md +16 -15
  182. package/docs/ROUTING.md +8 -10
  183. package/docs/SANDBOX-REVIEW.md +19 -6
  184. package/docs/SCAFFOLDING.md +0 -2
  185. package/docs/SECURITY-AUDIT.md +41 -1
  186. package/docs/SPECIFICATION.md +150 -29
  187. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
  188. package/docs/SPIKE-BUSINESS-SUITE.md +1021 -0
  189. package/docs/SPIKE-CORE-LAYERING.md +337 -0
  190. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
  191. package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
  192. package/docs/SPIKE-EXTENSIONS.md +6 -0
  193. package/docs/SPIKE-LAMBDA-COMPILE.md +201 -0
  194. package/docs/SPIKE-MONOREPO.md +322 -0
  195. package/docs/STANDARDS.md +150 -142
  196. package/docs/STARTERS.md +21 -1
  197. package/docs/STATIC.md +94 -0
  198. package/docs/TOOLING.md +295 -0
  199. package/docs/TUNNELS.md +0 -3
  200. package/docs/TYPESCRIPT-AUTHORING.md +82 -0
  201. package/docs/TYPESCRIPT.md +25 -4
  202. package/docs/USABILITY-REVIEW.md +129 -0
  203. package/docs/VERCEL.md +4 -5
  204. package/docs/VERSION-ALIGNMENT.md +205 -0
  205. package/docs/YAML-GUIDE.md +15 -479
  206. package/docs/YAML-REFERENCE.md +143 -22
  207. package/docs/policies/agents.md +1 -1
  208. package/docs/policies/cache.md +13 -0
  209. package/docs/policies/contract.md +52 -0
  210. package/docs/policies/hardened.md +56 -0
  211. package/docs/policies/interoperability.md +169 -0
  212. package/docs/policies/operations.md +45 -0
  213. package/docs/yaml/assets.md +36 -0
  214. package/docs/yaml/conditions.md +20 -0
  215. package/docs/yaml/functions.md +160 -0
  216. package/docs/yaml/middleware.md +29 -0
  217. package/docs/yaml/organization.md +74 -0
  218. package/docs/yaml/policies.md +37 -0
  219. package/docs/yaml/redirects.md +64 -0
  220. package/docs/yaml/responses.md +57 -0
  221. package/docs/yaml/site.md +24 -0
  222. package/examples/assets/example.yaml +17 -0
  223. package/examples/aws/example.yaml +20 -0
  224. package/examples/cloudflare/example.yaml +19 -0
  225. package/examples/compliance/example.yaml +11 -0
  226. package/examples/conditions/README.md +12 -0
  227. package/examples/conditions/example.yaml +19 -0
  228. package/examples/conditions/tests/requests.json +13 -0
  229. package/examples/conditions/urlcode.yaml +24 -0
  230. package/examples/cookbook/README.md +8 -4
  231. package/examples/cookbook/example.yaml +17 -0
  232. package/examples/cookbook/functions/catalog.mjs +3 -0
  233. package/examples/cookbook/functions/fail.mjs +4 -0
  234. package/examples/cookbook/functions/items.mjs +3 -0
  235. package/examples/cookbook/functions/profile.mjs +3 -0
  236. package/examples/cookbook/functions/resource.mjs +3 -0
  237. package/examples/cookbook/functions/status.mjs +3 -0
  238. package/examples/cookbook/middleware/auth.mjs +48 -0
  239. package/examples/cookbook/middleware/body.mjs +15 -0
  240. package/examples/cookbook/middleware/bucket.mjs +29 -0
  241. package/examples/cookbook/middleware/cors.mjs +21 -0
  242. package/examples/cookbook/middleware/debug.mjs +13 -0
  243. package/examples/cookbook/middleware/envelope.mjs +11 -0
  244. package/examples/cookbook/middleware/errors.mjs +11 -0
  245. package/examples/cookbook/middleware/etag.mjs +18 -0
  246. package/examples/cookbook/middleware/locale.mjs +20 -0
  247. package/examples/cookbook/middleware/maintenance.mjs +10 -0
  248. package/examples/cookbook/middleware/methods.mjs +15 -0
  249. package/examples/cookbook/middleware/negotiate.mjs +20 -0
  250. package/examples/cookbook/middleware/referer.mjs +12 -0
  251. package/examples/cookbook/middleware/request-id.mjs +16 -0
  252. package/examples/cookbook/route-index.json +676 -0
  253. package/examples/cookbook/routes/middleware.yaml +126 -0
  254. package/examples/cookbook/tests/requests.json +526 -0
  255. package/examples/cookbook/urlcode.yaml +1 -0
  256. package/examples/egress/README.md +22 -0
  257. package/examples/egress/example.yaml +19 -0
  258. package/examples/egress/urlcode.yaml +19 -0
  259. package/examples/extensions/README.md +7 -0
  260. package/examples/extensions/example.yaml +21 -0
  261. package/examples/extensions/urlcode.yaml +25 -0
  262. package/examples/monitoring/example.yaml +8 -0
  263. package/examples/prerender/README.md +2 -2
  264. package/examples/prerender/example.yaml +16 -0
  265. package/examples/provider-conformance/README.md +12 -0
  266. package/examples/provider-conformance/example.yaml +14 -0
  267. package/examples/provider-conformance/urlcode.yaml +34 -0
  268. package/examples/tunnel/example.yaml +8 -0
  269. package/examples/vercel/example.yaml +19 -0
  270. package/llms-full.txt +3084 -0
  271. package/llms.txt +61 -21
  272. package/package.json +36 -7
  273. package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
  274. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +122 -0
  275. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +108 -0
  276. package/recipes/authenticated-json-api/README.md +51 -0
  277. package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
  278. package/recipes/authenticated-json-api/recipe.yaml +34 -0
  279. package/recipes/authenticated-json-api/tests/requests.json +39 -0
  280. package/recipes/authenticated-json-api/urlcode.yaml +12 -0
  281. package/recipes/contact-form/README.md +25 -0
  282. package/recipes/contact-form/functions/contact.mjs +17 -0
  283. package/recipes/contact-form/recipe.yaml +33 -0
  284. package/recipes/contact-form/tests/requests.json +47 -0
  285. package/recipes/contact-form/urlcode.yaml +18 -0
  286. package/recipes/cors-api/README.md +16 -0
  287. package/recipes/cors-api/functions/items.mjs +3 -0
  288. package/recipes/cors-api/middleware/cors.mjs +21 -0
  289. package/recipes/cors-api/recipe.yaml +26 -0
  290. package/recipes/cors-api/tests/requests.json +65 -0
  291. package/recipes/cors-api/urlcode.yaml +12 -0
  292. package/recipes/health-page/README.md +13 -0
  293. package/recipes/health-page/recipe.yaml +23 -0
  294. package/recipes/health-page/tests/requests.json +36 -0
  295. package/recipes/health-page/urlcode.yaml +19 -0
  296. package/recipes/json-api/README.md +6 -0
  297. package/recipes/json-api/functions/echo.mjs +3 -0
  298. package/recipes/json-api/recipe.yaml +25 -0
  299. package/recipes/json-api/tests/requests.json +34 -0
  300. package/recipes/json-api/urlcode.yaml +12 -0
  301. package/recipes/middleware/README.md +34 -0
  302. package/recipes/middleware/functions/catalog.mjs +3 -0
  303. package/recipes/middleware/functions/fail.mjs +4 -0
  304. package/recipes/middleware/functions/items.mjs +3 -0
  305. package/recipes/middleware/functions/profile.mjs +3 -0
  306. package/recipes/middleware/functions/resource.mjs +3 -0
  307. package/recipes/middleware/functions/status.mjs +3 -0
  308. package/recipes/middleware/middleware/auth.mjs +48 -0
  309. package/recipes/middleware/middleware/body.mjs +15 -0
  310. package/recipes/middleware/middleware/bucket.mjs +29 -0
  311. package/recipes/middleware/middleware/cors.mjs +21 -0
  312. package/recipes/middleware/middleware/debug.mjs +13 -0
  313. package/recipes/middleware/middleware/envelope.mjs +11 -0
  314. package/recipes/middleware/middleware/errors.mjs +11 -0
  315. package/recipes/middleware/middleware/etag.mjs +18 -0
  316. package/recipes/middleware/middleware/locale.mjs +20 -0
  317. package/recipes/middleware/middleware/maintenance.mjs +10 -0
  318. package/recipes/middleware/middleware/methods.mjs +15 -0
  319. package/recipes/middleware/middleware/negotiate.mjs +20 -0
  320. package/recipes/middleware/middleware/referer.mjs +12 -0
  321. package/recipes/middleware/middleware/request-id.mjs +16 -0
  322. package/recipes/middleware/public/guide.txt +1 -0
  323. package/recipes/middleware/recipe.yaml +50 -0
  324. package/recipes/middleware/tests/requests.json +528 -0
  325. package/recipes/middleware/urlcode.yaml +127 -0
  326. package/recipes/protected-download/README.md +22 -0
  327. package/recipes/protected-download/files/report.txt +1 -0
  328. package/recipes/protected-download/recipe.yaml +31 -0
  329. package/recipes/protected-download/tests/requests.json +32 -0
  330. package/recipes/protected-download/urlcode.yaml +15 -0
  331. package/recipes/redirect/README.md +7 -0
  332. package/recipes/redirect/recipe.yaml +25 -0
  333. package/recipes/redirect/tests/requests.json +19 -0
  334. package/recipes/redirect/urlcode.yaml +9 -0
  335. package/recipes/static-plus-api/README.md +15 -0
  336. package/recipes/static-plus-api/functions/info.mjs +3 -0
  337. package/recipes/static-plus-api/public/assets/index.html +3 -0
  338. package/recipes/static-plus-api/public/assets/site.css +1 -0
  339. package/recipes/static-plus-api/public/index.html +8 -0
  340. package/recipes/static-plus-api/recipe.yaml +29 -0
  341. package/recipes/static-plus-api/tests/requests.json +56 -0
  342. package/recipes/static-plus-api/urlcode.yaml +23 -0
  343. package/recipes/typescript/README.md +8 -0
  344. package/recipes/typescript/functions/hello.ts +5 -0
  345. package/recipes/typescript/recipe.yaml +23 -0
  346. package/recipes/typescript/tests/requests.json +18 -0
  347. package/recipes/typescript/urlcode.yaml +5 -0
  348. package/recipes/webhook-receiver/README.md +20 -0
  349. package/recipes/webhook-receiver/functions/receive.mjs +16 -0
  350. package/recipes/webhook-receiver/recipe.yaml +27 -0
  351. package/recipes/webhook-receiver/tests/requests.json +59 -0
  352. package/recipes/webhook-receiver/urlcode.yaml +23 -0
  353. package/schemas/recipe.schema.json +139 -0
  354. package/schemas/urlcode.schema.json +659 -110
  355. package/skills/urlcode/SKILL.md +119 -0
  356. package/starters/default/.github/workflows/urlcode.yml +23 -0
  357. package/starters/default/.mcp.json +12 -0
  358. package/starters/default/AGENTS.md +79 -0
  359. package/starters/default/urlcode.yaml +0 -1
  360. package/dist/link-api.js +0 -136
  361. package/dist/link-cli.js +0 -141
  362. package/dist/link-events.js +0 -76
  363. package/dist/link-records.js +0 -31
  364. package/dist/link-store-worker.js +0 -150
  365. package/dist/link-store.js +0 -250
  366. package/dist/management-policy.js +0 -41
  367. package/dist/sqlite-version.js +0 -6
  368. package/dist/types/link-api.d.ts +0 -30
  369. package/dist/types/link-cli.d.ts +0 -36
  370. package/dist/types/link-events.d.ts +0 -27
  371. package/dist/types/link-records.d.ts +0 -11
  372. package/dist/types/link-store-worker.d.ts +0 -1
  373. package/dist/types/link-store.d.ts +0 -130
  374. package/dist/types/management-policy.d.ts +0 -9
  375. package/dist/types/sqlite-version.d.ts +0 -1
  376. package/docs/DYNAMIC-LINKS.md +0 -561
  377. package/docs/MANAGEMENT-SECURITY.md +0 -82
  378. package/examples/live-links/README.md +0 -11
  379. package/examples/live-links/tests/requests.json +0 -6
  380. package/examples/live-links/urlcode.yaml +0 -16
@@ -0,0 +1,337 @@
1
+ # Spike: layering `link` and `middleware` out of core
2
+
3
+ Status: proposal, nothing implemented. No code in this repository does any of
4
+ this, and nothing here is committed scope. This combines two extraction ideas
5
+ into one spike because they share a mechanism and a sequence, not because
6
+ either is blocked on the other.
7
+
8
+ > **Update:** this spike's `link` extraction (Phase 1 below) has since been
9
+ > implemented — core no longer has a native `link`/`LinkStore`/`dynamicLinks`
10
+ > API; it now lives in the separate
11
+ > [`urlcode-dynamic-link`](https://github.com/jimhoyd-com/urlcode-dynamic-link)
12
+ > package. Present-tense references to `link` as a core feature elsewhere in
13
+ > this document describe the pre-extraction state this spike was analyzing,
14
+ > not current core.
15
+
16
+ ## The shape this is aiming at
17
+
18
+ Core's job is to stay the smallest thing that is still a complete product on
19
+ its own: YAML routing (`redirect`/`respond`/`page`/`static`/`download`) plus
20
+ `function`, the one primitive with an actual serverless story
21
+ ([`SPIKE-LAMBDA-COMPILE.md`](SPIKE-LAMBDA-COMPILE.md) — compile each `function`
22
+ route to its own Lambda, the pattern `src/build-cloudflare.ts` already uses).
23
+ That base alone serves static and dynamic content and deploys anywhere
24
+ (`docs/FRAMEWORK.md` layer 1–3). Everything past that — accounts, admin,
25
+ stored links, UI kit — is an extension a project opts into. `docs/FRAMEWORK.md`
26
+ already describes this as four composed packages; `auth` is the existing proof
27
+ that "basic project to enterprise in minutes" works by adding declarations,
28
+ not by forking core.
29
+
30
+ `link` and `middleware` are the two pieces of today's core that don't fit that
31
+ story: `link` owns durable state no other core feature needs, and
32
+ `middleware` is guest code that only some projects want. Extracting both
33
+ narrows core to exactly YAML + function, matching the vision above.
34
+
35
+ ## Why one spike, not two independent ones
36
+
37
+ Both extractions land on the same extension mechanism, already implemented in
38
+ `src/extensions.ts`, and reusing one proof point for both keeps the design
39
+ honest instead of inventing two different shapes:
40
+
41
+ - **Mount ownership** (`route.extension`, `RuntimeExtension.activate` →
42
+ `ExtensionInstance.handle`): an extension owns an exclusive `/prefix/*` and
43
+ answers every request under it. This is the shape `link` needs — it already
44
+ behaves like a self-contained mount (`docs/DYNAMIC-LINKS.md`), and
45
+ `auth`/`admin` already use it for `/account/*` and `/admin/*`
46
+ (`docs/FRAMEWORK.md:10-15`).
47
+ - **Wrap without owning** (`ExtensionInstance.authorize`, called when a route
48
+ declares `policies.extensions.<name>` without an `extension:` mount): the
49
+ extension is asked to authorize a request that some *other* handler will
50
+ still serve, and can short-circuit with a `HandlerResult` or return nothing
51
+ (`src/extensions.ts:19`, `docs/EXTENSIONS.md:74-78`: "Its instance handles
52
+ bounded requests and, when used in policies, authorizes requests"). This is
53
+ already exactly how `auth` guards a `redirect`/`page`/`function` route today
54
+ without taking it over.
55
+
56
+ **Resolved — `ExtensionInstance.middleware` now exists.** This section
57
+ previously identified a real gap: `authorize()` can only gate (run before the
58
+ handler, either proceed or short-circuit) and never sees the handler's actual
59
+ response, so it could not express what native `middleware:` does today — wrap
60
+ the handler with `next()`, running code before *and* after it and
61
+ inspecting/mutating the returned `HandlerResult`. That gap is closed: a third,
62
+ additive `ExtensionInstance` capability, `middleware(config, request, next)`,
63
+ is attached the same way as `authorize` (`policies.extensions.<name>`, same
64
+ validated `config`) but with wrap semantics, chainable across multiple
65
+ extensions declared on one route, and composable with `authorize` on the same
66
+ route without either mechanism special-casing the other (see
67
+ [EXTENSIONS.md#wrapping-a-route-extension-middleware](EXTENSIONS.md#wrapping-a-route-extension-middleware)
68
+ and `src/extensions.ts`/`src/runtime.ts`). `urlcode-middleware` now has a real
69
+ contract to build the extraction against, rather than only mount ownership and
70
+ the gate-only `authorize()`.
71
+
72
+ **Resolved — `ExtensionActivation.root` now exists.** Building
73
+ `urlcode-middleware` against `ExtensionInstance.middleware` surfaced a second
74
+ gap: resolving a project-relative `source` (`middleware/headers.mjs`, the same
75
+ shape core's own native `middleware:` entries use) needs the project's
76
+ resolved directory, and `ExtensionActivation` (`{origin, target,
77
+ projectSha256, mounts}`) didn't carry one. `process.cwd()` is not a
78
+ substitute — `--project`/`--host-file` are independent, arbitrary paths, a
79
+ server can be started from any working directory, and the JS API can load a
80
+ project programmatically with no relationship to `cwd()` at all. `root` is
81
+ now a field on `ExtensionActivation`, set from the same resolved path
82
+ (`loadDocument()`'s `realpath`) that `router.ts`'s `functionFile()` already
83
+ resolves native `function`/`middleware` sources against, so an extension
84
+ resolves project-relative paths the identical way core does.
85
+
86
+ **Superseded by `docs/SPIKE-DEFAULT-TRUST-MODEL.md` — read that first.** This
87
+ section originally argued `middleware` should stay sandboxed like `function`
88
+ was under the old blanket-untrusted default. The maintainer has since decided
89
+ first-party `function`/`middleware` code is **trusted by default**, with
90
+ sandboxing an explicit per-declaration opt-in (`sandbox: true`), and
91
+ confirmed the same rule applies uniformly to `middleware` — no special case
92
+ for its wider per-request blast radius. So: `urlcode-middleware` runs trusted
93
+ in-process by default, same as `function`, with the sandboxed path available
94
+ for whichever specific `middleware:` wrap a developer judges needs it.
95
+ `authorize()` is still a distinct mechanism reserved for vetted,
96
+ operator-installed, revision-pinned extensions like `auth` — that split is
97
+ unchanged — but the reason `middleware` doesn't use `authorize()` is now
98
+ about mount-vs-wrap shape and contract ownership, not about needing its own
99
+ guest sandbox by default the way this section originally argued.
100
+
101
+ ## Sequence: `link` first, `middleware` second — and why it isn't arbitrary
102
+
103
+ 1. **`link` → `@jimhoyd/urlcode-dynamic-link`.** Already scoped in detail in
104
+ a sibling session's reviewed plan (Phase 1: remove `link`/`dynamicLinks`
105
+ from `src/types.ts`, `src/router.ts`, `src/runtime.ts`
106
+ (`src/runtime.ts:269-282`), schema and ~13 test files; Phase 2: the new
107
+ package, blocked on that repo existing/being attached). It is ready to
108
+ execute pending approval and needs nothing from the middleware work to
109
+ proceed.
110
+ 2. **`middleware` → extension.** Depends on Phase 1's incidental fix, not on
111
+ Phase 2: today `src/capabilities.ts` gives a blanket native/refused
112
+ answer for `extension`/`policies.extensions` without consulting the
113
+ specific registered extension's own `targets`
114
+ (`RuntimeExtension.targets`, `src/extensions.ts:31`) — wrong for any
115
+ self-hosted-only extension. `link`'s Phase 1c makes capability analysis
116
+ extension-target-aware. A `middleware` extension needs that same fix to
117
+ correctly report itself refused on Cloudflare/Vercel/AWS the way today's
118
+ native `middleware` handler is refused there — so it should land after,
119
+ reusing that work rather than duplicating it.
120
+
121
+ Sequence matters for that one dependency; nothing else forces an order.
122
+ `middleware` extraction now also depends on `docs/SPIKE-DEFAULT-TRUST-MODEL.md`
123
+ landing first (trusted-by-default needs to exist as a real execution path
124
+ before `urlcode-middleware` can be built against it) — recommended order is
125
+ `link` → default-trust-model → `middleware`, not `link` → `middleware`.
126
+
127
+ ## Cross-repo dependency
128
+
129
+ Neither extraction is core-only in effect, even though Phase 1 of each is
130
+ core-only in *scope*. `docs/FRAMEWORK.md:10-15` lists three other repos —
131
+ `urlcode-auth`, `urlcode-admin`, `urlcode-ui` — that already implement
132
+ `RuntimeExtension`/`ExtensionInstance` against core's contract
133
+ (`src/extensions.ts`). None of them are attached to this session, so the
134
+ following is reasoned from the documented contract, not verified against
135
+ their actual source; it needs confirming against those repos (via `add_repo`)
136
+ before anything here is treated as settled.
137
+
138
+ - **Core is upstream of every extension repo, never the reverse.** `auth`,
139
+ `admin`, `ui`, and the future `dynamic-link` and `middleware` extensions
140
+ each pin to a core contract version; core does not import or depend on any
141
+ of them (`AGENTS.md`: "Core never imports them"). So the dependency
142
+ direction for both extractions is: land the core contract change and cut a
143
+ release, *then* update/ship the consuming extension repos against it — not
144
+ the other way around.
145
+ - **The capability-analysis fix (`link`'s Phase 1c) is additive, not a
146
+ `RuntimeExtension` contract change.** It changes what core *reports* about
147
+ an extension's declared `targets`, not the shape an extension implements.
148
+ On paper this needs no changes in `auth`/`admin`/`ui` — but that assumption
149
+ should be checked against their actual `targets` declarations once those
150
+ repos are available, since a repo currently relying on the old blanket
151
+ native/refused answer could see a new, more accurate `refused` result it
152
+ wasn't expecting.
153
+ - **`middleware` does not reuse `authorize()`, so it does not force a change
154
+ to that hook** — it needs its own contract surface (a sandboxed-execution
155
+ extension shape, closer to how `function` is wired in `runtime.ts` than to
156
+ how `auth` is wired). `auth`/`admin`/`ui` are unaffected by `middleware`'s
157
+ extraction unless a project happens to combine `middleware` with one of
158
+ them, which is a project-level composition question, not a contract change
159
+ those three repos need to absorb.
160
+ - **A new `urlcode-dynamic-link` and a new `urlcode-middleware` repo** both
161
+ need to exist or be attached before their Phase 2 work can be written or
162
+ verified, matching the constraint already flagged for `link`.
163
+
164
+ ## Repo governance for the two new repos (decided)
165
+
166
+ Both `urlcode-dynamic-link` and `urlcode-middleware` follow `GOVERNANCE.md`
167
+ and `AGENTS.md` as written, with one explicit decision recorded here per
168
+ AGENTS.md's "do not publish packages without an explicit decision":
169
+
170
+ - **License: Apache-2.0**, same as core, no separate CLA/DCO — matching
171
+ `GOVERNANCE.md`'s "Licensing and participation" section exactly. No new
172
+ licensing terms for either repo.
173
+ - **Repo settings mirror core's ruleset** (`GOVERNANCE.md` "Changes and
174
+ responsibility"): `main` protected against force-push/deletion, requires an
175
+ up-to-date branch, passing CI and a PR, squash merges, no ruleset bypass for
176
+ admins or automation, CODEOWNERS recording ownership. CI/release workflow
177
+ shape copied from core's `release.yml` (candidate build → audit → pack →
178
+ attest → publish via trusted publisher, no long-lived npm token), per the
179
+ pattern `docs/NEXT-STEPS.md` §2.1 already used for `auth`/`admin`/`ui`.
180
+ CodeQL required on main, secret scanning and push protection on, same as
181
+ core.
182
+ - **Published public from the start** — both the GitHub repo and the npm
183
+ package (`@jimhoyd/urlcode-dynamic-link`, `@jimhoyd/urlcode-middleware`) are
184
+ public, not the "`private: true` until reviewed" alpha pattern
185
+ `auth`/`admin`/`ui` used at their first release. This is a deliberate
186
+ departure from that precedent, not an oversight — record the same alpha
187
+ caveat in each README/status file (source complete, independent review and
188
+ deployment evidence pending) so "public" doesn't read as "reviewed."
189
+ - Naming matches convention: repo `urlcode-<name>` ↔ package
190
+ `@jimhoyd/urlcode-<name>`, consistent with `urlcode-auth`/`-admin`/`-ui`.
191
+ - Still outside this session's scope to execute: creating the two GitHub
192
+ repos, setting their branch protection/CODEOWNERS, and the actual npm
193
+ publish are maintainer actions, not something done from within this repo's
194
+ checkout.
195
+
196
+ ## Performance considerations
197
+
198
+ Both extractions keep everything in the same Node process — extensions are
199
+ loaded and activated in-process via a host file (`src/extensions.ts`), not a
200
+ network hop or separate deployment unit — so neither is a "distributed
201
+ system tax." The real costs are narrower and different for each:
202
+
203
+ - **`link`** moving from a native `runtime.ts` branch (`src/runtime.ts:269-282`)
204
+ to an extension mount means every stored-link lookup now also passes through
205
+ `extensionResponse()` (`src/extensions.ts:164-172`): a header-count/byte-size
206
+ check (≤256 headers, ≤16 KiB), a 1 MiB body-size assert, and a `Cache-Control`
207
+ rewrite. That's small, bounded, per-request work — but `link` is the
208
+ project's most latency-sensitive path (a redirect lookup), and
209
+ `docs/CAPACITY.md:200` already warns "do not extrapolate in-memory redirect
210
+ benchmark numbers to database lookups" for the *native* handler today. The
211
+ extension path adds a fixed increment on top of that existing SQLite-bound
212
+ latency; worth a benchmark comparison (native vs. extension-mounted `link`)
213
+ before calling this cost-neutral rather than assuming it from the code shape.
214
+ - **`middleware`'s performance story changes under `SPIKE-DEFAULT-TRUST-MODEL.md`.**
215
+ Under the old blanket-sandboxed default, this section argued middleware's
216
+ cost was worse than `function`'s because it runs on every request through
217
+ every route it's attached to, not once per matched route — paying the
218
+ worker-thread/fresh-heap tax repeatedly on the hot path. That cost is now
219
+ the *opt-in* path, not the default: trusted-by-default execution runs
220
+ `middleware` as ordinary in-process code with no worker pool ceiling, the
221
+ same throughput profile `authorize()` already has (see the concurrency
222
+ discussion in this conversation — thousands of concurrent trusted
223
+ executions is realistic, bounded by normal Node concurrency and the
224
+ instance's HTTP admission cap, not a fixed worker-slot count). The sandbox
225
+ cost described above still applies, in full, to whichever specific
226
+ `middleware:` wrap a developer explicitly opts into `sandbox: true` — and
227
+ the same mitigation still matters there: native declarative primitives
228
+ (`docs/NEXT-STEPS.md`'s Phase 4.2 `auth: { required: true, roles: [...] }`
229
+ direction) reduce how much logic ever needs to reach for the sandboxed
230
+ opt-in at all, for the cases where a developer does judge it warranted.
231
+
232
+ ## Other core pieces considered and set aside
233
+
234
+ Checked against the same test used for `link`/`middleware` — does it own
235
+ state or behavior nothing else in core needs, and is it optional rather than
236
+ part of the smallest complete product:
237
+
238
+ - **`proxy`** — explicitly *not* a candidate. The sibling session's plan for
239
+ `link` calls this out directly: unlike `link`, `proxy` is a shared egress
240
+ primitive future extensions are expected to build on, so extracting it
241
+ would create a dependency extensions have on an extension, which core's
242
+ "extensions never depend on each other" shape doesn't support today.
243
+ - **`policies`** (`throttle`, `agents`, security headers, compression,
244
+ cache) — these are declarative YAML behavior applied by core to every
245
+ route, not guest code or durable external state; `throttle`/`agents`
246
+ counters are already scoped as "per instance, not distributed"
247
+ (`docs/OPERATIONS.md`), which is a limitation to document, not a reason to
248
+ extract. A bare project (no extensions at all) still needs security
249
+ headers and basic rate limiting, so these stay part of the smallest
250
+ complete product.
251
+ - **`conditional`, `static`, `download`, `page`, `respond`, `redirect`** —
252
+ these *are* the YAML-routing half of "YAML + function"; extracting any of
253
+ them would shrink core below the "complete product on its own" bar rather
254
+ than trim it.
255
+ - **Management API / operator grants / credential policy** — foundation that
256
+ extensions themselves depend on (`docs/MANAGEMENT-SECURITY.md`,
257
+ `docs/FUNCTION-SECURITY.md`); moving it out would mean extracting the thing
258
+ the extraction pattern relies on.
259
+
260
+ Nothing else in core matches the `link`/`middleware` shape today. If a third
261
+ candidate is going to be found, `docs/REPETITION-LOG.md`'s discipline (`docs/NEXT-STEPS.md`
262
+ Phase 6 — extract from observed repetition, not speculation) is the more
263
+ defensible way to find it than continuing to eyeball the handler list.
264
+
265
+ Recommendation: before either Phase 2 begins, attach `urlcode-auth`,
266
+ `urlcode-admin` and `urlcode-ui` to a session and confirm (a) their actual
267
+ `targets` declarations against the Phase 1c capability-analysis change, and
268
+ (b) whether any of them implement `authorize()` beyond `auth`. That turns the
269
+ bullets above from reasoned-from-docs into verified, and gives real basis for
270
+ sequencing core's release against theirs (e.g. a core minor version that adds
271
+ extension-target-aware capability reporting without breaking the contract,
272
+ versus a core change that requires those repos to update in lockstep).
273
+
274
+ ## Open questions before either is built
275
+
276
+ - `link`: hard break vs. deprecation window (pre-1.0, `0.4.0-alpha.1`;
277
+ sibling session's plan recommends a hard break, flagged explicitly since it
278
+ breaks any project with `dynamicLinks: true` until the new package ships).
279
+ - `middleware`: how much of today's `middleware:` guest code is genuinely
280
+ custom logic versus a common pattern (auth gating, headers, CORS, rate
281
+ limiting) that a native declarative primitive could cover with no guest
282
+ execution at all? This sizing decides how much the sandbox's per-request
283
+ cost actually matters in practice, and should come from an inventory of
284
+ real usage, not a guess, before `urlcode-middleware`'s guest-execution
285
+ contract is designed.
286
+ - Both: this repo stays core-only per `AGENTS.md` ("the auth, admin and ui
287
+ extensions live in their own repositories... Core never imports them");
288
+ neither extraction's Phase 2 can be written here.
289
+
290
+ ## The full ladder: one contract, one vocabulary per level
291
+
292
+ `link` and `middleware` shrink core by moving pieces *out*; there's a
293
+ complementary, additive move that extends the ladder *below* core instead of
294
+ touching it: a `static` compile target, alongside the existing
295
+ `node`/`aws`/`vercel`/`cloudflare` targets in `src/capabilities.ts`. Same
296
+ `urlcode.yaml`, same routing vocabulary — the difference between levels is
297
+ only which capabilities a given target can serve, exactly the mechanism that
298
+ already exists (Cloudflare already refuses `function`/`link`/`middleware`
299
+ today; `static` would additionally refuse `function`, keeping only
300
+ `redirect`/`respond`/`page`/`static`/`download`). No new syntax, no second
301
+ schema, no fork of the contract — a project written once reads as:
302
+
303
+ ```
304
+ static hosting (S3, CloudFront) → routing + static assets only, no server
305
+ node/aws/vercel (serverless) → + function, the dynamic primitive
306
+ extensions (auth/admin/link/…) → + accounts, admin, stored links, middleware
307
+ ```
308
+
309
+ This is the same YAML at every level; the only thing that changes is which
310
+ handlers a target accepts, reported the same way `urlcode capabilities
311
+ --target <name>` already reports it. That's the point being made here: the
312
+ progression isn't three different products, it's one contract with graduated
313
+ vocabulary, so a project can start at "static site" and grow into "function"
314
+ and then "extensions" without a rewrite — just fewer refusals as the target
315
+ gets more capable.
316
+
317
+ This is additive, not part of the `link`/`middleware` extraction: it doesn't
318
+ touch core's code, doesn't shrink core's self-definition ("YAML + function"
319
+ stays true for the `node`/`aws`/`vercel` targets), and needs nothing from
320
+ either extraction to be built. It reuses `build-cloudflare.ts`'s pattern
321
+ (compile YAML to the target's native format) for S3/CloudFront redirect
322
+ rules and object routing.
323
+
324
+ **One real gap, not glossed over:** GitHub Pages has no server-side rewrite
325
+ layer, so `redirect` routes can't compile to true HTTP redirects there — only
326
+ a meta-refresh/JS fallback or a static 404-page trick, both lower fidelity
327
+ than what the same route does on every other target. If `static` ships,
328
+ GitHub Pages needs either an explicit fidelity caveat in its target
329
+ description or exclusion from the `static` target's claimed support, not a
330
+ silent "same behavior everywhere" promise the platform can't keep.
331
+
332
+ ## Non-goals
333
+
334
+ This spike does not decide `link`'s Phase 1 breaking-change policy, does not
335
+ design the `middleware` extension's config schema, and does not touch
336
+ `function` — `function` stays in core as the serverless-capable primitive
337
+ that makes YAML + function a complete product on its own.
@@ -0,0 +1,209 @@
1
+ # Decision: first-party `function`/`middleware` code is trusted by default
2
+
3
+ Status: **decided by the maintainer and implemented in `0.4.0-alpha.2`**
4
+ (`sandbox`/`sandboxReason` in `schemas/urlcode.schema.json`; see the
5
+ `0.4.0-alpha.2` entry in [the roadmap](../ROADMAP.md) and the resulting contract
6
+ in [function security](FUNCTION-SECURITY.md)). `0.4.0-alpha.2` is not yet
7
+ published to npm. This overturns an
8
+ explicit, previously-stated project rule — see "What this reverses" below —
9
+ so treat it as a deliberate, recorded policy change, not a code cleanup. The
10
+ body below is preserved as written at decision time; where it calls downstream
11
+ packages unbuilt, that was true then, and the notes mark what exists now.
12
+
13
+ ## The decision
14
+
15
+ Project-authored `function` and `middleware` code runs **trusted and
16
+ unsandboxed by default** (direct host-process execution, no worker thread, no
17
+ QuickJS/WASM, no fresh-heap-per-call cost). Sandboxing becomes **opt-in**,
18
+ declared per route by the developer when they judge a specific piece of code
19
+ needs it (e.g. code processing input from an untrusted third party, a
20
+ plugin/contribution the project owner hasn't personally reviewed, or genuinely
21
+ adversarial-input-facing logic).
22
+
23
+ Rationale on record: the maintainer's own reasoning is that the current
24
+ blanket sandbox is a major performance ceiling (2 workers, no queue, shared
25
+ across every programmable route — `docs/CAPACITY.md`) that does not scale to
26
+ real concurrent traffic "out of the box," and that treating all first-party
27
+ code as equally untrusted regardless of the developer's own judgment is the
28
+ wrong default for a framework whose primary author is often the same person
29
+ deploying it. The alternative (sandboxed-by-default, opt-in-to-trusted) was
30
+ raised and explicitly rejected in favor of this direction.
31
+
32
+ Industry comparison supporting this call: mainstream frameworks that serve
33
+ AI-agent-generated backend code today (Express, Next.js API routes, Django,
34
+ Rails) run that code directly in the host process at serve time, unsandboxed,
35
+ identically to hand-written code — there is no mainstream precedent for
36
+ production request-handling code running in an isolated engine. Where the
37
+ industry *does* sandbox AI-generated code (E2B, Modal sandboxes, OpenAI Code
38
+ Interpreter, Anthropic's own code execution tool) is at generation/dev-time,
39
+ while the agent is writing and iterating — not at serve-time once code is
40
+ reviewed and deployed. URLCode's current design is the unusual one: it keeps
41
+ the isolation boundary at production serve-time rather than following the
42
+ "sandbox generation, trust deployment" pattern the rest of the ecosystem
43
+ uses. This decision brings first-party `function`/`middleware` code in line
44
+ with that mainstream pattern by default, while keeping the stricter,
45
+ less-common serve-time isolation available as an explicit opt-in for code
46
+ that specifically warrants it.
47
+
48
+ ## What this reverses — read before touching anything else
49
+
50
+ This is not additive; it contradicts explicit, multiple-file statements that
51
+ currently describe the opposite as an unconditional guarantee. All of the
52
+ following assert or depend on "guest code is always sandboxed, regardless of
53
+ origin, even your own repository," and need to be rewritten, not just
54
+ extended, once this ships:
55
+
56
+ - `AGENTS.md:9-11` — "Treat all application code as untrusted... never add a
57
+ host-code execution fallback."
58
+ - `docs/FUNCTION-SECURITY.md:3-6` — "Application code is untrusted even when
59
+ it came from your own Git repository... There is no `unsafe`, `trusted` or
60
+ automatic host-execution fallback." (This line directly anticipated and
61
+ rejected exactly this proposal; it cannot be left standing as written.)
62
+ - `SECURITY.md:3-5` — "Application functions are untrusted by default...
63
+ There is no unrestricted host-execution fallback."
64
+ - `docs/SANDBOX-REVIEW.md:5-6` — "No unrestricted Node execution fallback is
65
+ permitted."
66
+ - `docs/SECURITY-AUDIT.md` and `docs/PROJECT-DIRECTION.md:25-27` — the
67
+ "not a general Node web framework" framing and residual-risk reasoning
68
+ assume any guest code could be adversarial.
69
+ - `README.md`'s function description states the sandbox as an unconditional
70
+ runtime property, not a mode a route opts into.
71
+ - `docs/AI-AUTHORING.md` treats `FUNCTION-SECURITY.md` as required reading
72
+ with no first-party exemption in the authoring contract.
73
+
74
+ None of these can be quietly left in place once `trusted` is the default —
75
+ an operator or a security reviewer reading them after this ships would be
76
+ reading claims the runtime no longer makes.
77
+
78
+ ## What has to actually change (not just docs)
79
+
80
+ There is no execution branch for this today — sandboxing is hardwired, not a
81
+ config path. Per the earlier sweep:
82
+
83
+ 1. **Schema/types**: a per-route (or per-function-declaration) field, e.g.
84
+ `sandbox: true`, defaulting to `false`/absent = trusted. Needs a home in
85
+ `schemas/urlcode.schema.json` and `src/types.ts` (`RouteConfig`, whatever
86
+ shape `function`/`middleware` declarations take).
87
+ 2. **Execution path**: `src/functions.ts`/`function-worker.ts` currently has
88
+ no branch that skips `FunctionPool`'s worker/QuickJS dispatch and calls a
89
+ guest export directly in-process — that in-process path needs to be built
90
+ from scratch as the new default, with the existing sandboxed path kept
91
+ fully intact and reachable via the opt-in flag.
92
+ 3. **Trust-declaration integrity**: since this is opt-out rather than
93
+ opt-in, the risk shifts from "can code fake being trusted" (the sandboxed
94
+ default's concern) to "does everyone correctly opt untrusted-input-facing
95
+ code INTO the sandbox." That's a documentation/authoring-guidance problem,
96
+ not an enforcement one — `urlcode audit`/`validate` cannot know a
97
+ developer's intent, so the authoring docs (`docs/AI-AUTHORING.md`, the
98
+ generated project `AGENTS.md`, the `urlcode-authoring` skill) need to
99
+ clearly teach **when** a project should reach for `sandbox: true` — e.g.
100
+ code parsing third-party webhook payloads it doesn't fully trust, a
101
+ contributed function nobody on the team reviewed, anything handling a
102
+ secret binding it can't fully vet. Reasonable defaults in generated
103
+ scaffolding (recipes, starter templates) should still model this judgment
104
+ correctly rather than silently omitting it everywhere.
105
+ 4. **Test suite**: `test/sandbox.test.ts`, `test/sandbox-pool.test.ts`,
106
+ `test/egress.test.ts`, `test/middleware.test.ts` and related isolation
107
+ suites (~60-90 tests) stay valid for the `sandbox: true` path unchanged;
108
+ new tests are needed for the trusted default path (it can reach Node APIs,
109
+ the module graph, etc., on purpose) plus tests confirming the two paths
110
+ don't cross-contaminate (a trusted-path failure can't be mistaken for a
111
+ sandboxed one, and vice versa).
112
+ 5. **Capacity docs**: `docs/CAPACITY.md`'s worker/deadline/heap numbers stay
113
+ as the sandboxed-path limits; the trusted path needs its own documented
114
+ capacity model (ordinary Node concurrency, the existing HTTP admission cap
115
+ `--max-in-flight`, no worker-pool ceiling) — see the concurrency
116
+ discussion earlier in this conversation for the concrete numbers.
117
+ 6. **Extension-model consistency**: `auth`/`admin`/`ui` already run trusted
118
+ via a *different* mechanism (`authorize()`/`handle()`, operator-installed,
119
+ revision-pinned packages outside the project). This decision does not
120
+ merge that model with first-party `function`/`middleware` trust — they
121
+ remain two separate trust paths that happen to both be unsandboxed, for
122
+ different reasons (operator-vetted package vs. developer's own judgment
123
+ call). Keep that distinction explicit in the docs rewrite so "trusted"
124
+ doesn't become one undifferentiated concept.
125
+
126
+ ## Cross-repo impact
127
+
128
+ Same caveat as `docs/SPIKE-CORE-LAYERING.md`: `urlcode-auth`, `urlcode-admin`,
129
+ `urlcode-ui` are not attached to this session, so the following is reasoned
130
+ from the documented contract, not verified against their source — confirm
131
+ with `add_repo` before treating it as settled.
132
+
133
+ - **`auth`/`admin`/`ui` themselves: contract unaffected.** They run through
134
+ `authorize()`/`handle()` — a separate mechanism from `function`/`middleware`
135
+ guest execution, unchanged by this decision. No `RuntimeExtension` code
136
+ changes needed on their side.
137
+ - **But their own docs/examples likely need an audit anyway.** If any of the
138
+ three repos' READMEs, threat-model docs, or example projects describe core's
139
+ sandbox as an unconditional guarantee (language inherited from today's
140
+ `AGENTS.md`/`FUNCTION-SECURITY.md` wording, which this decision replaces),
141
+ those statements become inaccurate the moment core ships the new default.
142
+ Anywhere one of these repos ships an example `urlcode.yaml` with `function:`/
143
+ `middleware:` routes (onboarding docs, a demo, a conformance fixture), it's
144
+ now running trusted by default unless that example explicitly opts into
145
+ `sandbox: true` — worth checking whether any existing example implicitly
146
+ depends on sandboxing (e.g., a demo built to showcase "safe to run
147
+ arbitrary/third-party code") and would silently stop meaning what it claims.
148
+ - **This is a behavior change, not just a schema addition — versioning must
149
+ reflect that.** A project upgrading core with zero YAML changes gets a
150
+ materially different execution model for every existing `function`/
151
+ `middleware` route it already has. That needs a major/minor version bump
152
+ with an explicit changelog entry and migration note (not a patch release),
153
+ and `peers.json` in `auth`/`admin`/`ui` (`docs/SPIKE-CORE-LAYERING.md`'s
154
+ §2.2 reference) should pin deliberately to a core version that includes
155
+ this change, not inherit it silently on a routine bump.
156
+ - **`urlcode-dynamic-link` (built and published since; was planned when this was written): unaffected.** It's a
157
+ mount-based extension like `auth`, not a `function`/`middleware` consumer —
158
+ nothing here changes its design.
159
+ - **`urlcode-middleware` (built and published since; was planned when this
160
+ was written): same rule applies — decided.** First-party middleware is trusted by default, exactly like
161
+ `function`; `sandbox: true` is the same opt-in a developer reaches for
162
+ when a specific `middleware:` wrap genuinely warrants it (e.g. it's
163
+ processing input from a source the developer doesn't fully trust). This
164
+ was flagged as an open fork (middleware's wider blast radius — it wraps
165
+ every request through a route, not one operation) and the maintainer has
166
+ resolved it: one uniform default across `function` and `middleware`, not a
167
+ special case. `docs/SPIKE-CORE-LAYERING.md`'s middleware section, which
168
+ still describes middleware as sandboxed-by-default, is superseded by this
169
+ and needs updating to match.
170
+ - **Extension-authored project-level lifecycle hooks: same rule, no special
171
+ case.** `docs/EXTENSIONS.md`'s "Project-level lifecycle hooks" section
172
+ settles this explicitly: a hook a project names in an extension's own YAML
173
+ config (`urlcode-auth`'s `onSignUp`/`beforeRegister`/`onDelete` and the
174
+ like) is first-party project code in the same category as any
175
+ `function`/`middleware` route, trusted and in-process by default, with the
176
+ same per-hook `sandbox: true` opt-in. No hardwired always-sandboxed case
177
+ for lifecycle hooks specifically. Trusted execution of such a hook needs no
178
+ core primitive — an extension's own `activate()` already has
179
+ `ExtensionActivation.root` and can `import()` the project's module
180
+ directly. The isolated half of that opt-in previously had no equivalent:
181
+ core's trusted/sandboxed dispatch was wired to route dispatch only, not
182
+ exposed to extensions. `@jimhoyd/urlcode/sandbox`'s `SandboxPool` (see
183
+ [FUNCTION-SECURITY.md](FUNCTION-SECURITY.md), [TYPESCRIPT.md](TYPESCRIPT.md))
184
+ closes that: the same worker/QuickJS engine `FunctionPool` already used for
185
+ route dispatch, generalized to explicit `{source, export}` entries/targets
186
+ instead of `FunctionRoute`, with no second engine and no "trusted" mode
187
+ exported alongside it.
188
+
189
+ ## Recommended sequencing
190
+
191
+ This is independent of, but touches the same files as, the `link`/
192
+ `middleware` extraction in `docs/SPIKE-CORE-LAYERING.md`. Recommend landing
193
+ this trust-model change first, since it changes what "keep middleware
194
+ sandboxed" in that spike even means (middleware's default execution mode
195
+ changes too) — building the extraction against the old assumption first
196
+ would mean redoing it once this ships. `docs/SPIKE-CORE-LAYERING.md`'s
197
+ middleware section will need a follow-up pass once this decision's schema
198
+ shape exists.
199
+
200
+ ## Not decided here
201
+
202
+ - The exact field name/shape (`sandbox: true` vs. `trust: sandboxed` vs.
203
+ something else) — a naming/schema-design pass, not a policy question.
204
+ - Whether `link`'s extraction or the `static` target need any changes as a
205
+ result — on current understanding, no (neither touches guest-code
206
+ execution), but worth re-checking once the schema shape is settled.
207
+ - The actual rewritten wording for `AGENTS.md`/`SECURITY.md`/
208
+ `FUNCTION-SECURITY.md` — drafting that is a deliberate writing pass, not
209
+ something to improvise inline here.