@jimhoyd/urlcode 0.4.2 → 0.4.7
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.
- package/.claude/skills/urlcode-authoring/SKILL.md +57 -14
- package/.claude/skills/urlcode-operations/SKILL.md +4 -0
- package/README.md +42 -18
- package/SECURITY.md +5 -3
- package/dist/BUILD-MANIFEST.json +30 -27
- package/dist/agent-context.js +82 -0
- package/dist/agents-guide.js +42 -42
- package/dist/authoring.js +12 -2
- package/dist/body-schema.js +159 -0
- package/dist/build-cloudflare.js +2 -0
- package/dist/capabilities.js +1 -1
- package/dist/cli.js +28 -12
- package/dist/config.js +74 -6
- package/dist/context.js +4 -6
- package/dist/errors.js +3 -1
- package/dist/examples.js +1 -1
- package/dist/extensions.js +60 -2
- package/dist/http-policy.js +19 -4
- package/dist/http-response.js +2 -2
- package/dist/init-with.js +71 -9
- package/dist/mcp.js +15 -2
- package/dist/pattern-guard.js +32 -0
- package/dist/policies/security.js +0 -0
- package/dist/project-tests.js +35 -11
- package/dist/readiness.js +184 -32
- package/dist/recipes.js +1 -1
- package/dist/router.js +17 -0
- package/dist/runtime.js +26 -3
- package/dist/scaffold.js +0 -0
- package/dist/server.js +26 -3
- package/dist/site.js +0 -0
- package/dist/tooling.js +2 -1
- package/dist/types/agent-context.d.ts +44 -0
- package/dist/types/authoring.d.ts +3 -1
- package/dist/types/body-schema.d.ts +58 -0
- package/dist/types/config.d.ts +10 -2
- package/dist/types/context.d.ts +1 -1
- package/dist/types/errors.d.ts +9 -1
- package/dist/types/examples.d.ts +1 -1
- package/dist/types/extensions.d.ts +47 -2
- package/dist/types/http-policy.d.ts +3 -0
- package/dist/types/init-with.d.ts +10 -1
- package/dist/types/pattern-guard.d.ts +10 -0
- package/dist/types/project-tests.d.ts +9 -0
- package/dist/types/readiness.d.ts +68 -0
- package/dist/types/recipes.d.ts +1 -1
- package/dist/types/runtime.d.ts +4 -0
- package/dist/types/server.d.ts +9 -1
- package/dist/types/tooling.d.ts +1 -0
- package/dist/types/types.d.ts +13 -0
- package/dist/types.js +10 -2
- package/dist/typescript-authoring.js +5 -3
- package/dist/verify-deployment.js +16 -10
- package/examples/body-validation/README.md +16 -0
- package/examples/body-validation/example.yaml +17 -0
- package/examples/body-validation/tests/requests.json +15 -0
- package/examples/body-validation/urlcode.yaml +34 -0
- package/examples/coverage-waiver/README.md +8 -0
- package/examples/coverage-waiver/example.yaml +16 -0
- package/examples/coverage-waiver/functions/notes.mjs +2 -0
- package/examples/coverage-waiver/tests/requests.json +3 -0
- package/examples/coverage-waiver/urlcode.yaml +10 -0
- package/examples/data-dir/README.md +39 -0
- package/examples/data-dir/data/welcome.txt +1 -0
- package/examples/data-dir/example.yaml +22 -0
- package/examples/data-dir/functions/note.mjs +18 -0
- package/examples/data-dir/tests/requests.json +6 -0
- package/examples/data-dir/urlcode.yaml +6 -0
- package/examples/lifecycle/README.md +15 -0
- package/examples/lifecycle/example.yaml +19 -0
- package/examples/lifecycle/functions/notes.mjs +29 -0
- package/examples/lifecycle/tests/requests.json +12 -0
- package/examples/lifecycle/urlcode.yaml +29 -0
- package/examples/not-found/README.md +10 -0
- package/examples/not-found/example.yaml +17 -0
- package/examples/not-found/public/404.html +3 -0
- package/examples/not-found/public/index.html +3 -0
- package/examples/not-found/tests/requests.json +7 -0
- package/examples/not-found/urlcode.yaml +7 -0
- package/examples/shared-blocks/README.md +11 -0
- package/examples/shared-blocks/example.yaml +17 -0
- package/examples/shared-blocks/tests/requests.json +7 -0
- package/examples/shared-blocks/urlcode.yaml +37 -0
- package/llms-full.txt +364 -49
- package/llms.txt +44 -6
- package/package.json +15 -26
- package/recipes/static-page/README.md +9 -0
- package/recipes/static-page/public/index.html +11 -0
- package/recipes/static-page/recipe.yaml +21 -0
- package/recipes/static-page/tests/requests.json +22 -0
- package/recipes/static-page/urlcode.yaml +7 -0
- package/recipes/static-plus-api/README.md +6 -0
- package/recipes/static-plus-api/urlcode.yaml +4 -0
- package/recipes/store-crud/README.md +53 -0
- package/recipes/store-crud/recipe.yaml +31 -0
- package/recipes/store-crud/tests/requests.json +18 -0
- package/recipes/store-crud/urlcode.yaml +18 -0
- package/schemas/urlcode.schema.json +125 -60
- package/skills/urlcode/SKILL.md +47 -27
- package/starters/default/AGENTS.md +43 -43
- package/starters/page/README.md +14 -0
- package/starters/page/public/index.html +12 -0
- package/starters/page/tests/requests.json +17 -0
- package/starters/page/urlcode.yaml +6 -0
- package/.claude-plugin/marketplace.json +0 -18
- package/CONTRIBUTING.md +0 -148
- package/ROADMAP.md +0 -70
- package/docs/AI-AUTHORING.md +0 -339
- package/docs/ASSETS.md +0 -107
- package/docs/AUTH-BACKUP.md +0 -32
- package/docs/AWS.md +0 -86
- package/docs/BEST-PRACTICES.md +0 -276
- package/docs/BULK.md +0 -79
- package/docs/CAPABILITIES.md +0 -192
- package/docs/CAPACITY.md +0 -305
- package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
- package/docs/CI.md +0 -147
- package/docs/CLOUDFLARE.md +0 -109
- package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
- package/docs/COMPLIANCE.md +0 -239
- package/docs/COMPOSING-A-SITE.md +0 -287
- package/docs/CONDITIONS.md +0 -74
- package/docs/CONTAINER-PROMOTION.md +0 -74
- package/docs/DEPLOYMENT-CHECKS.md +0 -108
- package/docs/DEVELOPMENT-PIPELINE.md +0 -304
- package/docs/EGRESS.md +0 -125
- package/docs/EXTENSIONS.md +0 -403
- package/docs/FRAMEWORK.md +0 -217
- package/docs/FUNCTION-SECURITY.md +0 -251
- package/docs/HTTP.md +0 -129
- package/docs/INSTALL.md +0 -128
- package/docs/INTERCHANGE.md +0 -134
- package/docs/LOAD-TESTING.md +0 -91
- package/docs/LOCAL-DEVELOPMENT.md +0 -102
- package/docs/MIDDLEWARE-EXAMPLES.md +0 -75
- package/docs/MIDDLEWARE.md +0 -102
- package/docs/MONITORING.md +0 -115
- package/docs/OBSERVABILITY.md +0 -222
- package/docs/OPEN-DECISIONS.md +0 -149
- package/docs/OPERATIONAL-PROOF.md +0 -41
- package/docs/OPERATIONS.md +0 -201
- package/docs/ORGANIZATION.md +0 -135
- package/docs/PERFORMANCE.md +0 -72
- package/docs/PLUGINS.md +0 -271
- package/docs/POLICIES.md +0 -211
- package/docs/PRERENDER.md +0 -245
- package/docs/PROJECT-DIRECTION.md +0 -118
- package/docs/PROVIDER-VERIFICATION.md +0 -84
- package/docs/READINESS.md +0 -153
- package/docs/README.md +0 -93
- package/docs/RECIPES.md +0 -99
- package/docs/RELEASE-0.4.0-alpha.3.md +0 -50
- package/docs/RELEASE-0.4.1.md +0 -73
- package/docs/RELEASE-0.4.2.md +0 -30
- package/docs/RELEASE-READINESS.md +0 -146
- package/docs/RELEASE-SECURITY.md +0 -100
- package/docs/RESILIENCE.md +0 -161
- package/docs/ROUTING.md +0 -92
- package/docs/SANDBOX-REVIEW.md +0 -72
- package/docs/SCAFFOLDING.md +0 -70
- package/docs/SECURITY-AUDIT.md +0 -164
- package/docs/SITE.md +0 -150
- package/docs/SPECIFICATION.md +0 -359
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
- package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
- package/docs/SPIKE-CORE-LAYERING.md +0 -368
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -207
- package/docs/STANDARDS.md +0 -311
- package/docs/STARTERS.md +0 -83
- package/docs/STATIC.md +0 -105
- package/docs/TOOLING.md +0 -298
- package/docs/TUNNELS.md +0 -72
- package/docs/TYPESCRIPT-AUTHORING.md +0 -87
- package/docs/TYPESCRIPT.md +0 -123
- package/docs/VERCEL.md +0 -114
- package/docs/VERSION-ALIGNMENT.md +0 -88
- package/docs/YAML-GUIDE.md +0 -57
- package/docs/YAML-REFERENCE.md +0 -449
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
- package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
- package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
- package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
- package/docs/archive/2026-09-19/ROADMAP.md +0 -387
- package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
- package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
- package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
- package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
- package/docs/archive/2026-09-20/OPEN-DECISIONS-COMPLETED.md +0 -116
- package/docs/archive/README.md +0 -29
- package/docs/policies/agents.md +0 -182
- package/docs/policies/cache.md +0 -152
- package/docs/policies/compression.md +0 -169
- package/docs/policies/contract.md +0 -52
- package/docs/policies/hardened.md +0 -56
- package/docs/policies/interoperability.md +0 -169
- package/docs/policies/operations.md +0 -45
- package/docs/policies/security.md +0 -161
- package/docs/policies/throttle.md +0 -103
- package/docs/yaml/assets.md +0 -36
- package/docs/yaml/conditions.md +0 -20
- package/docs/yaml/functions.md +0 -168
- package/docs/yaml/middleware.md +0 -31
- package/docs/yaml/organization.md +0 -74
- package/docs/yaml/policies.md +0 -37
- package/docs/yaml/redirects.md +0 -64
- package/docs/yaml/responses.md +0 -57
- package/docs/yaml/site.md +0 -24
- package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -130
- 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?
|