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

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