@jimhoyd/urlcode 0.4.2 → 0.4.6

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 (214) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +57 -14
  2. package/.claude/skills/urlcode-operations/SKILL.md +4 -0
  3. package/README.md +19 -15
  4. package/SECURITY.md +5 -3
  5. package/dist/BUILD-MANIFEST.json +30 -27
  6. package/dist/agent-context.js +82 -0
  7. package/dist/agents-guide.js +42 -42
  8. package/dist/authoring.js +12 -2
  9. package/dist/body-schema.js +159 -0
  10. package/dist/build-cloudflare.js +2 -0
  11. package/dist/capabilities.js +1 -1
  12. package/dist/cli.js +28 -12
  13. package/dist/config.js +74 -6
  14. package/dist/context.js +4 -6
  15. package/dist/errors.js +3 -1
  16. package/dist/examples.js +1 -1
  17. package/dist/extensions.js +60 -2
  18. package/dist/http-policy.js +19 -4
  19. package/dist/http-response.js +2 -2
  20. package/dist/init-with.js +71 -9
  21. package/dist/mcp.js +15 -2
  22. package/dist/pattern-guard.js +32 -0
  23. package/dist/policies/security.js +0 -0
  24. package/dist/project-tests.js +35 -11
  25. package/dist/readiness.js +184 -32
  26. package/dist/recipes.js +1 -1
  27. package/dist/router.js +17 -0
  28. package/dist/runtime.js +26 -3
  29. package/dist/scaffold.js +0 -0
  30. package/dist/server.js +26 -3
  31. package/dist/site.js +0 -0
  32. package/dist/tooling.js +2 -1
  33. package/dist/types/agent-context.d.ts +44 -0
  34. package/dist/types/authoring.d.ts +3 -1
  35. package/dist/types/body-schema.d.ts +58 -0
  36. package/dist/types/config.d.ts +10 -2
  37. package/dist/types/context.d.ts +1 -1
  38. package/dist/types/errors.d.ts +9 -1
  39. package/dist/types/examples.d.ts +1 -1
  40. package/dist/types/extensions.d.ts +47 -2
  41. package/dist/types/http-policy.d.ts +3 -0
  42. package/dist/types/init-with.d.ts +10 -1
  43. package/dist/types/pattern-guard.d.ts +10 -0
  44. package/dist/types/project-tests.d.ts +9 -0
  45. package/dist/types/readiness.d.ts +68 -0
  46. package/dist/types/recipes.d.ts +1 -1
  47. package/dist/types/runtime.d.ts +4 -0
  48. package/dist/types/server.d.ts +9 -1
  49. package/dist/types/tooling.d.ts +1 -0
  50. package/dist/types/types.d.ts +13 -0
  51. package/dist/types.js +10 -2
  52. package/dist/typescript-authoring.js +5 -3
  53. package/dist/verify-deployment.js +16 -10
  54. package/examples/body-validation/README.md +16 -0
  55. package/examples/body-validation/example.yaml +17 -0
  56. package/examples/body-validation/tests/requests.json +15 -0
  57. package/examples/body-validation/urlcode.yaml +34 -0
  58. package/examples/coverage-waiver/README.md +8 -0
  59. package/examples/coverage-waiver/example.yaml +16 -0
  60. package/examples/coverage-waiver/functions/notes.mjs +2 -0
  61. package/examples/coverage-waiver/tests/requests.json +3 -0
  62. package/examples/coverage-waiver/urlcode.yaml +10 -0
  63. package/examples/data-dir/README.md +39 -0
  64. package/examples/data-dir/data/welcome.txt +1 -0
  65. package/examples/data-dir/example.yaml +22 -0
  66. package/examples/data-dir/functions/note.mjs +18 -0
  67. package/examples/data-dir/tests/requests.json +6 -0
  68. package/examples/data-dir/urlcode.yaml +6 -0
  69. package/examples/lifecycle/README.md +15 -0
  70. package/examples/lifecycle/example.yaml +19 -0
  71. package/examples/lifecycle/functions/notes.mjs +29 -0
  72. package/examples/lifecycle/tests/requests.json +12 -0
  73. package/examples/lifecycle/urlcode.yaml +29 -0
  74. package/examples/not-found/README.md +10 -0
  75. package/examples/not-found/example.yaml +17 -0
  76. package/examples/not-found/public/404.html +3 -0
  77. package/examples/not-found/public/index.html +3 -0
  78. package/examples/not-found/tests/requests.json +7 -0
  79. package/examples/not-found/urlcode.yaml +7 -0
  80. package/examples/shared-blocks/README.md +11 -0
  81. package/examples/shared-blocks/example.yaml +17 -0
  82. package/examples/shared-blocks/tests/requests.json +7 -0
  83. package/examples/shared-blocks/urlcode.yaml +37 -0
  84. package/llms-full.txt +364 -49
  85. package/llms.txt +43 -5
  86. package/package.json +15 -22
  87. package/recipes/static-page/README.md +9 -0
  88. package/recipes/static-page/public/index.html +11 -0
  89. package/recipes/static-page/recipe.yaml +21 -0
  90. package/recipes/static-page/tests/requests.json +22 -0
  91. package/recipes/static-page/urlcode.yaml +7 -0
  92. package/recipes/static-plus-api/README.md +6 -0
  93. package/recipes/static-plus-api/urlcode.yaml +4 -0
  94. package/recipes/store-crud/README.md +53 -0
  95. package/recipes/store-crud/recipe.yaml +31 -0
  96. package/recipes/store-crud/tests/requests.json +18 -0
  97. package/recipes/store-crud/urlcode.yaml +18 -0
  98. package/schemas/urlcode.schema.json +125 -60
  99. package/skills/urlcode/SKILL.md +47 -27
  100. package/starters/default/AGENTS.md +43 -43
  101. package/starters/page/README.md +14 -0
  102. package/starters/page/public/index.html +12 -0
  103. package/starters/page/tests/requests.json +17 -0
  104. package/starters/page/urlcode.yaml +6 -0
  105. package/.claude-plugin/marketplace.json +0 -18
  106. package/CONTRIBUTING.md +0 -148
  107. package/ROADMAP.md +0 -70
  108. package/docs/AI-AUTHORING.md +0 -339
  109. package/docs/ASSETS.md +0 -107
  110. package/docs/AUTH-BACKUP.md +0 -32
  111. package/docs/AWS.md +0 -86
  112. package/docs/BEST-PRACTICES.md +0 -276
  113. package/docs/BULK.md +0 -79
  114. package/docs/CAPABILITIES.md +0 -192
  115. package/docs/CAPACITY.md +0 -305
  116. package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
  117. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
  118. package/docs/CI.md +0 -147
  119. package/docs/CLOUDFLARE.md +0 -109
  120. package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
  121. package/docs/COMPLIANCE.md +0 -239
  122. package/docs/COMPOSING-A-SITE.md +0 -287
  123. package/docs/CONDITIONS.md +0 -74
  124. package/docs/CONTAINER-PROMOTION.md +0 -74
  125. package/docs/DEPLOYMENT-CHECKS.md +0 -108
  126. package/docs/DEVELOPMENT-PIPELINE.md +0 -304
  127. package/docs/EGRESS.md +0 -125
  128. package/docs/EXTENSIONS.md +0 -403
  129. package/docs/FRAMEWORK.md +0 -217
  130. package/docs/FUNCTION-SECURITY.md +0 -251
  131. package/docs/HTTP.md +0 -129
  132. package/docs/INSTALL.md +0 -128
  133. package/docs/INTERCHANGE.md +0 -134
  134. package/docs/LOAD-TESTING.md +0 -91
  135. package/docs/LOCAL-DEVELOPMENT.md +0 -102
  136. package/docs/MIDDLEWARE-EXAMPLES.md +0 -75
  137. package/docs/MIDDLEWARE.md +0 -102
  138. package/docs/MONITORING.md +0 -115
  139. package/docs/OBSERVABILITY.md +0 -222
  140. package/docs/OPEN-DECISIONS.md +0 -149
  141. package/docs/OPERATIONAL-PROOF.md +0 -41
  142. package/docs/OPERATIONS.md +0 -201
  143. package/docs/ORGANIZATION.md +0 -135
  144. package/docs/PERFORMANCE.md +0 -72
  145. package/docs/PLUGINS.md +0 -271
  146. package/docs/POLICIES.md +0 -211
  147. package/docs/PRERENDER.md +0 -245
  148. package/docs/PROJECT-DIRECTION.md +0 -118
  149. package/docs/PROVIDER-VERIFICATION.md +0 -84
  150. package/docs/READINESS.md +0 -153
  151. package/docs/README.md +0 -93
  152. package/docs/RECIPES.md +0 -99
  153. package/docs/RELEASE-0.4.0-alpha.3.md +0 -50
  154. package/docs/RELEASE-0.4.1.md +0 -73
  155. package/docs/RELEASE-0.4.2.md +0 -30
  156. package/docs/RELEASE-READINESS.md +0 -146
  157. package/docs/RELEASE-SECURITY.md +0 -100
  158. package/docs/RESILIENCE.md +0 -161
  159. package/docs/ROUTING.md +0 -92
  160. package/docs/SANDBOX-REVIEW.md +0 -72
  161. package/docs/SCAFFOLDING.md +0 -70
  162. package/docs/SECURITY-AUDIT.md +0 -164
  163. package/docs/SITE.md +0 -150
  164. package/docs/SPECIFICATION.md +0 -359
  165. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
  166. package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
  167. package/docs/SPIKE-CORE-LAYERING.md +0 -368
  168. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -207
  169. package/docs/STANDARDS.md +0 -311
  170. package/docs/STARTERS.md +0 -83
  171. package/docs/STATIC.md +0 -105
  172. package/docs/TOOLING.md +0 -298
  173. package/docs/TUNNELS.md +0 -72
  174. package/docs/TYPESCRIPT-AUTHORING.md +0 -87
  175. package/docs/TYPESCRIPT.md +0 -123
  176. package/docs/VERCEL.md +0 -114
  177. package/docs/VERSION-ALIGNMENT.md +0 -88
  178. package/docs/YAML-GUIDE.md +0 -57
  179. package/docs/YAML-REFERENCE.md +0 -449
  180. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
  181. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
  182. package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
  183. package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
  184. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
  185. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
  186. package/docs/archive/2026-09-19/ROADMAP.md +0 -387
  187. package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
  188. package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
  189. package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
  190. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
  191. package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
  192. package/docs/archive/2026-09-20/OPEN-DECISIONS-COMPLETED.md +0 -116
  193. package/docs/archive/README.md +0 -29
  194. package/docs/policies/agents.md +0 -182
  195. package/docs/policies/cache.md +0 -152
  196. package/docs/policies/compression.md +0 -169
  197. package/docs/policies/contract.md +0 -52
  198. package/docs/policies/hardened.md +0 -56
  199. package/docs/policies/interoperability.md +0 -169
  200. package/docs/policies/operations.md +0 -45
  201. package/docs/policies/security.md +0 -161
  202. package/docs/policies/throttle.md +0 -103
  203. package/docs/yaml/assets.md +0 -36
  204. package/docs/yaml/conditions.md +0 -20
  205. package/docs/yaml/functions.md +0 -168
  206. package/docs/yaml/middleware.md +0 -31
  207. package/docs/yaml/organization.md +0 -74
  208. package/docs/yaml/policies.md +0 -37
  209. package/docs/yaml/redirects.md +0 -64
  210. package/docs/yaml/responses.md +0 -57
  211. package/docs/yaml/site.md +0 -24
  212. package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
  213. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -130
  214. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +0 -108
@@ -1,365 +0,0 @@
1
- # Historical record
2
-
3
- Archived 2026-09-19, when the proposal was **declined**. This records an earlier
4
- proposal, not current instructions. The decision it lost to is
5
- [one Node deployment per project](../../OPEN-DECISIONS.md#accepted-one-node-deployment-per-project);
6
- see also the [current roadmap](../../../ROADMAP.md) and
7
- [current contract](../../SPECIFICATION.md). Remaining acceptance work is not
8
- declared complete by archiving this record.
9
-
10
- <!-- trust-model-prose: historical-file -->
11
- <!-- guidance-claims: ignore-file -->
12
-
13
- # Spike: compiling `function` routes into their own Lambdas
14
-
15
- > Review update, 2026-09-19: Current baseline: trusted Node execution is the default, so fresh invocation
16
- > state is a guarantee of `sandbox: true` only. AWS/Vercel still reject functions
17
- > and middleware in `src/capabilities.ts`; changing the default did not implement
18
- > a provider adapter for them. The per-route lowering was weighed against a
19
- > single trusted Node deployment per project, and **the Node deployment was
20
- > chosen** (2026-09-19). Removed link APIs in the analysis below are historical.
21
-
22
-
23
- Status: **declined 2026-09-19 — kept as the analysis behind that decision, not
24
- as a plan.** Nothing here is implemented and nothing here is committed scope.
25
- The maintainer chose the alternative this document weighs itself against: a
26
- project that uses `function` or `middleware` deploys as **one trusted Node
27
- process**, and AWS/Vercel keep refusing those routes at activation as a
28
- deliberate position rather than a gap awaiting an adapter. See
29
- [open decisions](../../OPEN-DECISIONS.md#accepted-one-node-deployment-per-project).
30
-
31
- Read on for why, and for what a first attempt would look like if real demand for
32
- `function` routes on AWS serverless ever appears and reopens the question.
33
-
34
- AWS already deploys today. `createLambdaHandler` (`src/aws.ts`) runs a project
35
- as **one** Lambda behind a Function URL or an API Gateway HTTP API, reading the
36
- same `urlcode.yaml` that runs locally — see [AWS](../../AWS.md). What it cannot serve
37
- is `function`, `middleware` and `link`, which
38
- `activateNativeOnly` (`src/adapters.ts`) refuses for the whole deployment at
39
- activation rather than letting individual routes fail per request.
40
-
41
- This spike asks one question: **is the refusal of `function` a fact about
42
- Lambda, or a fact about the adapter?** It argues the second, sketches the
43
- lowering that follows, and is deliberate about what that lowering costs.
44
-
45
- Section 0 says all of that again without the jargon; sections 1 onward are the
46
- argument in full.
47
-
48
- ## 0. In plain terms
49
-
50
- This section is the whole spike in ordinary language. Everything after it is the
51
- same argument stated precisely; if the two ever disagree, the precise version
52
- wins.
53
-
54
- ### What a `function` route is
55
-
56
- Most of a URLCode project is description, not code: "this URL redirects there",
57
- "this URL returns that page", "this URL serves that file". You write those in
58
- YAML and never write a program. A **`function` route** is the escape hatch for
59
- the cases YAML cannot describe — you write a small piece of JavaScript, and the
60
- runtime calls it when a request arrives. `middleware` is the same idea, except
61
- it runs on the way *to* other routes rather than answering by itself.
62
-
63
- ### Where that code runs today
64
-
65
- Two ways, and the project picks per route:
66
-
67
- - **Trusted (the default).** The code runs directly in the same Node process as
68
- everything else, at full speed, with the same access the process has. This is
69
- the default because the person writing the route is usually the same person
70
- deploying it. See [the decision](../../SPIKE-DEFAULT-TRUST-MODEL.md).
71
- - **Sandboxed (`sandbox: true`, opt in per route).** The code runs inside a
72
- small, separate JavaScript engine (QuickJS, compiled to WebAssembly) on a
73
- worker thread. Inside there it has no filesystem, no network, no environment
74
- variables and a completely fresh memory space on every single call. It can
75
- only reach the outside world through *bindings* an operator granted by name.
76
- See [function security](../../FUNCTION-SECURITY.md).
77
-
78
- Think of the sandbox as a sealed room inside your own house. Nothing gets in or
79
- out except through a hatch you deliberately opened, and the room is wiped clean
80
- between visitors.
81
-
82
- ### What happens on AWS today
83
-
84
- Nothing, for these routes. If a project contains any `function` or `middleware`
85
- route, the AWS adapter refuses **the entire deployment at startup** and names
86
- what it cannot serve. Everything else — redirects, pages, static files,
87
- downloads — deploys fine as a single Lambda.
88
-
89
- That refusal is not a bug or an oversight. It is the runtime declining to
90
- pretend. The alternative would be accepting the deployment and then failing
91
- individual requests in production, which is worse.
92
-
93
- ### Why it refuses
94
-
95
- The sealed room is expensive to build. Every time AWS starts a fresh copy of
96
- your Lambda (a "cold start"), that copy would have to spin up worker threads and
97
- boot a WebAssembly engine *before* it could answer the first request. One
98
- process trying to serve every route in a project, rebuilding all that machinery
99
- on every cold start, is a bad trade.
100
-
101
- The key observation of this spike: that reasoning is about **one process serving
102
- every route**. It is not actually about AWS. Change the unit of deployment and
103
- the objection evaporates.
104
-
105
- ### The idea
106
-
107
- Stop shipping one Lambda for the whole project. Instead, at build time, emit
108
- **one Lambda per `function` route** — plus the existing single Lambda for all
109
- the ordinary declarative routes.
110
-
111
- Then you do not need the sealed room at all, because each piece of code already
112
- has an entire AWS process to itself. The process *is* the wall. This is the same
113
- move URLCode already makes for Cloudflare: when a platform will not run what the
114
- adapter needs, compile the project ahead of time instead of adapting at runtime.
115
-
116
- ### The catch — three of them
117
-
118
- 1. **It is a different kind of safety, not more of the same safety.** A Lambda
119
- is a genuinely separate box, which is good. But out of the box that box has
120
- working network access, a writable temp disk, ambient environment variables,
121
- and an AWS identity (an "IAM role") that can reach real infrastructure. The
122
- sealed room had none of that. So swapping one for the other is a **trade**,
123
- not an upgrade — and the documentation would have to say exactly which
124
- guarantee changed rather than announcing "functions work on AWS now". One
125
- guarantee is simply lost: the sandbox promises fresh memory on every call,
126
- and AWS reuses warm containers.
127
- 2. **URLCode would start generating security-critical infrastructure.** To keep
128
- each route's permissions tight, the build would have to write out an IAM role
129
- per route. A generated permissions file is not an implementation detail you
130
- can wave away with "don't rely on this" — if it is too generous, it is a
131
- security hole the project authored.
132
- 3. **It would be an unverified claim wearing a capability's clothes.** Right now
133
- the project's position on AWS is honest: it reports `deployment: 'unverified'`
134
- and tells you to deploy the example yourself. Shipping a compiler that emits
135
- cloud infrastructure nobody on the project has ever actually deployed would
136
- trade an honest refusal for a bigger unproven promise.
137
-
138
- ### The alternative already sitting on the table — and now chosen
139
-
140
- Run the project as **one ordinary Node deployment** — a container or a VM. That
141
- supports `function` and `middleware` today, with no compiler, no generated
142
- infrastructure and no new security surface. It costs you the serverless
143
- operating model: something is always running, and you scale it yourself.
144
-
145
- So the real decision is not "can this be built" — it probably can. It is:
146
-
147
- > Is there enough demand for running URLCode `function` routes specifically on
148
- > AWS serverless to justify this project owning an infrastructure compiler and a
149
- > second, weaker isolation story?
150
-
151
- **That question was answered on 2026-09-19: no.** Nobody has produced the demand
152
- evidence, and the plain Node deployment does the job today, so the Node
153
- deployment is the supported model and the compiler is not being built. The
154
- refusal on AWS and Vercel stays, and is now a position rather than a gap. See
155
- [open decisions](../../OPEN-DECISIONS.md#accepted-one-node-deployment-per-project).
156
- If that demand ever shows up, §6 below already scopes what a first attempt
157
- would be.
158
-
159
- ## 1. Where the refusal actually comes from
160
-
161
- `src/capabilities.ts` gives the reason:
162
-
163
- ```
164
- capability === 'function' ? 'isolated functions need worker threads and the WASM engine'
165
- ```
166
-
167
- That is true of the runtime's *own* mechanism. Isolation for guest code is
168
- QuickJS inside WebAssembly, driven from worker threads, with the boundaries
169
- [function security](../../FUNCTION-SECURITY.md) lists: no `process`, no filesystem,
170
- no sockets, no `fetch`, a fresh guest heap per invocation, and bindings denied
171
- unless an operator granted them by exact name.
172
-
173
- A single Lambda cannot host that engine cheaply, because every cold start pays
174
- worker startup and WASM instantiation before the first request. So the adapter
175
- refuses — correctly, for the shape it is.
176
-
177
- But nothing in that sentence is about Lambda. It is about *one process serving
178
- every route*. Change the deployment unit and the sentence stops applying.
179
-
180
- ## 2. The lowering
181
-
182
- Cloudflare already establishes the pattern: where a platform forbids what the
183
- adapter needs, URLCode **compiles ahead of time** instead of adapting at
184
- runtime. `urlcode build --target cloudflare` (`src/build-cloudflare.ts`) emits
185
- an artifact the Worker reads, and refuses at build time anything it cannot
186
- serve, with the route named — see [Cloudflare](../../CLOUDFLARE.md).
187
-
188
- The same move for AWS: a build step emits **one Lambda per `function` route**,
189
- plus the existing native-handler Lambda for everything else.
190
-
191
- ```
192
- urlcode build --target aws --project . --out dist
193
-
194
- dist/
195
- routes/ the native handler Lambda (redirect, respond, page, static, download)
196
- fn/<route-id>/ one directory per function route
197
- template.yaml the generated stack
198
- ```
199
-
200
- The guest source becomes the Lambda's handler. There is no QuickJS in the
201
- request path, because the request never crosses a guest boundary inside a
202
- process — the process *is* the boundary.
203
-
204
- This is not a smaller change than it looks. Three things follow from it.
205
-
206
- ## 3. What changes, stated plainly
207
-
208
- ### 3.1 The isolation guarantee is replaced, not preserved
209
-
210
- This is the claim most likely to be made too early, so it goes first.
211
-
212
- > **Plainly:** both are real walls, but they are different walls, and neither
213
- > one is strictly stronger. Read the table as a trade, not a scorecard.
214
-
215
- QuickJS-WASM and a Lambda are both real isolation. They are **not the same
216
- isolation**, and neither strictly contains the other:
217
-
218
- | | QuickJS-WASM | per-route Lambda |
219
- | --- | --- | --- |
220
- | Network | unavailable unless a binding is granted | available by default; must be removed |
221
- | Filesystem | unavailable | a writable `/tmp`, and the deployment package |
222
- | Environment | not exposed to the guest | ambient unless scrubbed |
223
- | Blast radius of an escape | the guest heap | the function's IAM role |
224
- | Per-invocation state | fresh heap, guaranteed | a warm container may be reused |
225
-
226
- The two rows that matter most are the last two. A guest that escapes QuickJS
227
- reaches a heap. A guest that misbehaves in a Lambda reaches **whatever that
228
- Lambda's execution role can reach** — so the compiler would have to emit a role
229
- per route that grants exactly the route's declared bindings and nothing else,
230
- and that emitted role becomes a security-critical generated artifact.
231
- Warm-container reuse is the other: the runtime currently *guarantees* fresh
232
- state per invocation, and Lambda does not.
233
-
234
- The honest framing, and the one the docs would have to carry: per-route Lambdas
235
- are **a substitute for the sandbox, not the sandbox**. Anything that says
236
- "functions now work on AWS" without saying which guarantee changed is a claim
237
- this project should not make.
238
-
239
- ### 3.2 `middleware` is the hard part, not `function`
240
-
241
- > **Plainly:** a `function` is a dead end — it answers and the request stops
242
- > there, so giving it its own Lambda is easy. Middleware is a queue of steps
243
- > every request walks through on its way somewhere else, and a queue split
244
- > across separate Lambdas has to be either copied into each one or wired up as
245
- > a chain of calls. Both hurt, so this spike does not attempt middleware.
246
-
247
- `function` lowers cleanly because it is a leaf. `middleware` is a per-request
248
- chain, and there are only two ways to lower it, both with a real cost:
249
-
250
- - **Inline** the chain into each function Lambda at build time. Cheap at
251
- runtime; duplicates the middleware into every function's package, and a
252
- middleware change rebuilds every function.
253
- - **Orchestrate** — a hop per middleware. Composable; adds a Lambda invocation
254
- of latency and cost to every request, on the hot path.
255
-
256
- Neither is obviously right, which is exactly why this spike scopes middleware
257
- out rather than picking one under time pressure.
258
-
259
- ### 3.3 `link` does not fall out of this at all
260
-
261
- Stored live links need a durable writable store that instances share. That is
262
- the same refusal before and after this change. DynamoDB is the natural lowering,
263
- but it is a store implementation with its own export and restore discipline, not
264
- something a compile step produces. (Note: the native `link` handler this
265
- section describes was later removed from core; see
266
- `docs/SPIKE-CORE-LAYERING.md`.)
267
-
268
- ## 4. Emitting infrastructure is a new kind of output
269
-
270
- > **Plainly:** up to now URLCode has only ever produced things that run *inside*
271
- > a server it was given. Emitting a CloudFormation stack means it starts
272
- > producing the cloud account's own configuration — including permissions — and
273
- > becomes responsible for that being correct as AWS changes underneath it.
274
-
275
- `examples/aws/template.yaml` is hand-written today. Generating a stack means
276
- this project starts owning a surface it has never owned:
277
-
278
- - The generated template is only as correct as the provider's current
279
- behaviour, which changes without asking.
280
- - A generated IAM role is a security artifact (3.1), so "the template is
281
- internal, don't rely on it" is a weaker disclaimer here than it was for the
282
- Cloudflare artifact.
283
- - The gap simply *moves* unless the emitted stack is checked against the
284
- project. `urlcode verify-deployment --target <url>` already probes a running
285
- deployment; it would need to cover the multi-Lambda shape, or the build gains
286
- a new unverified claim while retiring an honest refusal.
287
-
288
- That last point is the one worth holding onto. The project's current position on
289
- AWS is **honest**: the capability catalog reports `deployment: 'unverified'` for
290
- every target that is not self-hosted, and [AWS](../../AWS.md) tells a reader to treat
291
- the limits as unverified until they deploy the example themselves. A compiler
292
- that emits infrastructure nobody has deployed would be a larger unverified claim
293
- wearing the clothes of a capability.
294
-
295
- ## 5. The capability model already has the right shape
296
-
297
- `src/capabilities.ts` (from the target-capability centralization) is where this
298
- lands with no new concept:
299
-
300
- ```
301
- function / aws: refused → compiled
302
- ```
303
-
304
- `compiled` already exists as a `CapabilitySupport` value and already means what
305
- is needed here — Cloudflare uses it. `deployment` stays `'unverified'` until
306
- something is actually deployed. The catalog would tell the truth about the new
307
- lowering without anything else in the model changing, and `urlcode capabilities
308
- --target aws` would report it.
309
-
310
- ## 6. Proposed scope for a first spike
311
-
312
- Narrow, so that the isolation story stays clean and the win is real.
313
- **Plainly: build the smallest version that proves the idea, and resist every
314
- tempting extra.**
315
-
316
- **In:** `function` routes, Function URL only, one Lambda per function route,
317
- a generated role per route carrying exactly that route's granted bindings,
318
- and the existing native-handler Lambda unchanged for everything else.
319
-
320
- **Out:** `middleware` (3.2), `link` (3.3), API Gateway, VPC, custom domains,
321
- warm-start tuning, and any claim about cost.
322
-
323
- **Done looks like:** a project in `examples/` that builds, a generated template
324
- a reader can inspect, `urlcode capabilities --target aws` reporting `function`
325
- as `compiled`, and a written comparison of the two isolation models that a
326
- reviewer can disagree with.
327
-
328
- **Not done by that:** a deployment. Everything above can pass without anyone
329
- having run it on AWS, and the spike should say so rather than imply otherwise.
330
-
331
- ## 7. Prior art worth reading before building this
332
-
333
- - **Cloudflare target in this repository** — the closest precedent, and the one
334
- that establishes compile-not-adapt as a thing URLCode already does.
335
- - **SST, Serverless Framework, AWS CDK** — all generate per-function
336
- infrastructure from a declaration. The interesting question is not how they
337
- emit it but how they keep the emitted stack honest as the provider moves.
338
- - **Deno Deploy and Vercel functions** — isolate-per-request models, the closest
339
- commercial thing to the guarantee QuickJS-WASM gives today.
340
-
341
- ## 8. Open questions
342
-
343
- These are the things a reviewer should press on. In plain terms: *is losing the
344
- fresh-state guarantee acceptable at all; should URLCode write the permissions or
345
- merely describe them; does splitting into many Lambdas quietly weaken the
346
- policies a project already declares; and is one Lambda per route even the right
347
- size of piece?*
348
-
349
- - Is warm-container reuse acceptable at all, given the runtime currently
350
- *guarantees* fresh per-invocation state? If not, this lowering is wrong for
351
- any route that relies on that guarantee, and there is no build-time way to
352
- tell which ones do.
353
- - Does the generated IAM role belong in URLCode's output, or should the build
354
- emit a *description* of the permissions each route needs and leave the role to
355
- the operator — closer to how [function security](../../FUNCTION-SECURITY.md) already
356
- keeps grants operator-controlled and outside the checkout?
357
- - Does a per-route Lambda change what `policies` can promise? `throttle` on AWS
358
- is already `conditional` — "counters are per instance" — and more instances
359
- make that weaker, not stronger.
360
- - Is one Lambda per route the right granularity, or one per *project* with a
361
- route parameter, which keeps deployment small but reintroduces a shared
362
- process?
363
- - What happens to the 6 MB Lambda response limit ([AWS](../../AWS.md)) for a function
364
- route that returns a large body — refuse at build time, as the Cloudflare
365
- target refuses what it cannot serve?