@jimhoyd/urlcode 0.4.0-alpha.2 → 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.
- package/.claude/skills/urlcode-authoring/SKILL.md +17 -19
- package/.claude/skills/urlcode-operations/SKILL.md +9 -9
- package/.claude-plugin/marketplace.json +1 -1
- package/CONTRIBUTING.md +20 -16
- package/README.md +44 -54
- package/ROADMAP.md +61 -376
- package/dist/BUILD-MANIFEST.json +15 -15
- package/dist/agents-guide.js +6 -6
- package/dist/build-static.js +2 -0
- package/dist/cli.js +3 -3
- package/dist/explain-cli.js +4 -2
- package/dist/explain.js +7 -1
- package/dist/extensions.js +1 -1
- package/dist/manifest.js +7 -1
- package/dist/mcp-authoring.js +2 -2
- package/dist/mcp.js +1 -1
- package/dist/policies.js +3 -1
- package/dist/prerender.js +4 -0
- package/dist/readiness.js +3 -0
- package/dist/route-diff.js +12 -5
- package/dist/types/explain.d.ts +6 -0
- package/dist/types/extensions.d.ts +1 -1
- package/dist/types/manifest.d.ts +5 -1
- package/dist/types/readiness.d.ts +2 -0
- package/dist/types/types.d.ts +8 -1
- package/dist/types.js +8 -1
- package/dist/typescript-authoring.js +18 -7
- package/docs/AI-AUTHORING.md +10 -6
- package/docs/ASSETS.md +2 -1
- package/docs/AUTH-BACKUP.md +32 -0
- package/docs/BEST-PRACTICES.md +17 -9
- package/docs/CAPABILITIES.md +1 -1
- package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
- package/docs/CI.md +8 -3
- package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
- package/docs/DEVELOPMENT-PIPELINE.md +181 -0
- package/docs/EXTENSIONS.md +15 -5
- package/docs/FRAMEWORK.md +22 -18
- package/docs/FUNCTION-SECURITY.md +44 -0
- package/docs/LOCAL-DEVELOPMENT.md +3 -0
- package/docs/OPEN-DECISIONS.md +184 -212
- package/docs/OPERATIONAL-PROOF.md +3 -3
- package/docs/OPERATIONS.md +3 -3
- package/docs/POLICIES.md +13 -5
- package/docs/PRERENDER.md +23 -11
- package/docs/PROJECT-DIRECTION.md +3 -3
- package/docs/READINESS.md +6 -2
- package/docs/README.md +18 -41
- package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
- package/docs/RELEASE-READINESS.md +6 -6
- package/docs/RELEASE-SECURITY.md +68 -168
- package/docs/RESILIENCE.md +3 -3
- package/docs/ROUTING.md +3 -4
- package/docs/SECURITY-AUDIT.md +2 -2
- package/docs/SPECIFICATION.md +7 -5
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
- package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
- package/docs/SPIKE-CORE-LAYERING.md +48 -17
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
- package/docs/SPIKE-LAMBDA-COMPILE.md +8 -0
- package/docs/STATIC.md +14 -3
- package/docs/TOOLING.md +4 -3
- package/docs/TYPESCRIPT-AUTHORING.md +6 -1
- package/docs/VERSION-ALIGNMENT.md +46 -205
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
- package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +102 -0
- package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +15 -5
- package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +15 -3
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
- package/docs/archive/2026-09-19/ROADMAP.md +386 -0
- package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +10 -0
- package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
- package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
- package/docs/archive/README.md +27 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/compression.md +3 -2
- package/docs/policies/security.md +3 -2
- package/examples/assets/example.yaml +1 -1
- package/examples/cookbook/route-index.json +1 -1
- package/examples/cookbook/routes/middleware.yaml +1 -1
- package/examples/prerender/README.md +14 -6
- package/examples/prerender/functions/page.mjs +4 -2
- package/examples/prerender/middleware/template.mjs +1 -1
- package/examples/prerender/prerender.mjs +1 -1
- package/examples/prerender/urlcode.yaml +8 -4
- package/llms-full.txt +117 -45
- package/llms.txt +5 -4
- package/package.json +24 -4
- package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +17 -19
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +9 -9
- package/recipes/authenticated-json-api/README.md +4 -3
- package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
- package/recipes/authenticated-json-api/recipe.yaml +1 -1
- package/recipes/contact-form/functions/contact.mjs +2 -1
- package/recipes/contact-form/recipe.yaml +2 -2
- package/recipes/cors-api/README.md +2 -2
- package/recipes/cors-api/recipe.yaml +1 -1
- package/recipes/health-page/README.md +1 -1
- package/recipes/json-api/README.md +1 -1
- package/recipes/json-api/recipe.yaml +3 -3
- package/recipes/middleware/README.md +8 -4
- package/recipes/protected-download/README.md +1 -1
- package/recipes/protected-download/recipe.yaml +1 -1
- package/recipes/static-plus-api/README.md +2 -2
- package/recipes/static-plus-api/public/index.html +1 -1
- package/recipes/static-plus-api/recipe.yaml +1 -1
- package/recipes/static-plus-api/urlcode.yaml +1 -1
- package/recipes/typescript/recipe.yaml +4 -4
- package/skills/urlcode/SKILL.md +6 -6
- package/starters/default/AGENTS.md +6 -6
- package/docs/SPIKE-MONOREPO.md +0 -322
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jimhoyd/urlcode",
|
|
3
|
-
"version": "0.4.0-alpha.
|
|
3
|
+
"version": "0.4.0-alpha.3",
|
|
4
4
|
"description": "Portable runtime for programmable URL behavior",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"packages/*"
|
|
8
|
+
],
|
|
6
9
|
"license": "Apache-2.0",
|
|
7
10
|
"engines": {
|
|
8
11
|
"node": ">=22.13.0"
|
|
@@ -101,8 +104,10 @@
|
|
|
101
104
|
"typecheck": "tsc -p tsconfig.json",
|
|
102
105
|
"test": "node --conditions=development --test test/*.test.ts",
|
|
103
106
|
"lint": "eslint .",
|
|
104
|
-
"check": "node scripts/check.ts && node scripts/check-trust-model-prose.ts && node scripts/check-guidance-claims.ts && node scripts/generate-yaml-reference.ts --check && node scripts/build-llms-full.ts --check && node scripts/build-cookbook-index.ts --check && node scripts/generate-claude-plugin.ts --check",
|
|
105
|
-
"verify": "npm run lint && npm run typecheck && npm run check && npm run build && npm test",
|
|
107
|
+
"check": "node scripts/check.ts && node scripts/check-trust-model-prose.ts && node scripts/check-guidance-claims.ts && node scripts/check-release-tags.ts && node scripts/check-workspace-links.ts && node scripts/generate-yaml-reference.ts --check && node scripts/build-llms-full.ts --check && node scripts/build-cookbook-index.ts --check && node scripts/generate-claude-plugin.ts --check && npm run release:check",
|
|
108
|
+
"verify": "npm run lint && npm run typecheck && npm run check && npm run build && npm test && npm run verify:workspaces",
|
|
109
|
+
"workspace:styles": "npm run styles --workspace @jimhoyd/urlcode-ui",
|
|
110
|
+
"verify:workspaces": "npm run verify --workspace @jimhoyd/urlcode-ui && npm run verify --workspace @jimhoyd/urlcode-auth && npm run verify --workspace @jimhoyd/urlcode-admin && npm run test:workspace-integration",
|
|
106
111
|
"benchmark": "node benchmarks/routing.ts",
|
|
107
112
|
"test:package": "npm run build && node scripts/package-smoke.ts",
|
|
108
113
|
"dev": "node src/cli.ts dev --project starters/default",
|
|
@@ -122,7 +127,19 @@
|
|
|
122
127
|
"benchmark:bulk": "node benchmarks/bulk.ts",
|
|
123
128
|
"benchmark:sandbox-vs-trusted": "node benchmarks/sandbox-vs-trusted.ts",
|
|
124
129
|
"benchmark:agent": "node benchmarks/agent/run.ts",
|
|
125
|
-
"sync:agents": "node scripts/sync-agent-lists.ts"
|
|
130
|
+
"sync:agents": "node scripts/sync-agent-lists.ts",
|
|
131
|
+
"check:docs": "node scripts/check-trust-model-prose.ts && node scripts/check-guidance-claims.ts && node scripts/generate-yaml-reference.ts --check && node scripts/build-llms-full.ts --check && node scripts/build-cookbook-index.ts --check && node scripts/generate-claude-plugin.ts --check",
|
|
132
|
+
"ci:plan": "node scripts/ci-plan.ts",
|
|
133
|
+
"test:package:built": "node scripts/package-smoke.ts",
|
|
134
|
+
"release:status": "node scripts/release.ts status",
|
|
135
|
+
"release:plan": "node scripts/release.ts plan",
|
|
136
|
+
"release:publish": "node scripts/release.ts publish",
|
|
137
|
+
"release:run": "node scripts/release-run.ts",
|
|
138
|
+
"release:peers": "node scripts/release.ts peers",
|
|
139
|
+
"release:check": "node scripts/release.ts check",
|
|
140
|
+
"ci:history": "node scripts/ci-history.ts",
|
|
141
|
+
"ci:report": "node scripts/ci-report.ts",
|
|
142
|
+
"test:workspace-integration": "node --test test/workspace-scaffold.integration.ts"
|
|
126
143
|
},
|
|
127
144
|
"repository": {
|
|
128
145
|
"type": "git",
|
|
@@ -138,11 +155,14 @@
|
|
|
138
155
|
"yaml": "2.9.1"
|
|
139
156
|
},
|
|
140
157
|
"devDependencies": {
|
|
158
|
+
"@changesets/cli": "3.0.3",
|
|
141
159
|
"@eslint/js": "10.0.1",
|
|
142
160
|
"@types/mime-types": "3.0.1",
|
|
143
161
|
"@types/node": "26.5.1",
|
|
162
|
+
"@types/semver": "7.8.0",
|
|
144
163
|
"eslint": "10.10.0",
|
|
145
164
|
"globals": "17.12.0",
|
|
165
|
+
"semver": "7.8.5",
|
|
146
166
|
"typescript-eslint": "8.70.0"
|
|
147
167
|
},
|
|
148
168
|
"homepage": "https://github.com/jimhoyd-com/urlcode#readme",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "urlcode",
|
|
3
3
|
"description": "Authoring and operating URLCode projects: the implemented YAML contract, capability limits, deployment and verification commands for the pinned runtime revision.",
|
|
4
|
-
"version": "0.4.0-alpha.
|
|
4
|
+
"version": "0.4.0-alpha.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "jimhoyd-com",
|
|
7
7
|
"url": "https://github.com/jimhoyd-com"
|
|
@@ -14,6 +14,6 @@
|
|
|
14
14
|
"routing",
|
|
15
15
|
"yaml",
|
|
16
16
|
"redirects",
|
|
17
|
-
"
|
|
17
|
+
"middleware"
|
|
18
18
|
]
|
|
19
19
|
}
|
|
@@ -26,17 +26,12 @@ Documentation, schema and runtime must come from the **same revision**. Read fro
|
|
|
26
26
|
the project's installed runtime (`node_modules/@jimhoyd/urlcode/`) or the
|
|
27
27
|
checkout you are working in — never from memory of another version.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
5. `docs/ROUTING.md`, `docs/HTTP.md`, `docs/MIDDLEWARE.md`, `docs/ASSETS.md` —
|
|
36
|
-
matching precedence, methods, composition, MIME and ranges.
|
|
37
|
-
6. `docs/FUNCTION-SECURITY.md` — the sandbox and operator binding policy.
|
|
38
|
-
|
|
39
|
-
`llms.txt` at the repository root is a compact index of all of the above.
|
|
29
|
+
Start with `urlcode context --project <dir> --budget 4000`, then retrieve the
|
|
30
|
+
capability, schema fragment, recipe or example relevant to the change. Use the
|
|
31
|
+
read-only MCP equivalents when available. `llms.txt` is the index; read the
|
|
32
|
+
matching task guide from `docs/` when a query needs more explanation.
|
|
33
|
+
`docs/SPECIFICATION.md` and `schemas/urlcode.schema.json` resolve contract
|
|
34
|
+
questions. Archived plans are historical, not valid YAML guidance.
|
|
40
35
|
|
|
41
36
|
## Workflow
|
|
42
37
|
|
|
@@ -52,8 +47,8 @@ checkout you are working in — never from memory of another version.
|
|
|
52
47
|
- Bind typed inputs through `args` or context. There is no `${...}`
|
|
53
48
|
interpolation anywhere in the format.
|
|
54
49
|
- Create every referenced module, page and asset **before** validating. All
|
|
55
|
-
paths resolve from the project root
|
|
56
|
-
|
|
50
|
+
source paths resolve from the project root. Trusted modules can import Node built-ins and npm packages;
|
|
51
|
+
only `sandbox: true` modules are restricted to the relative snapshotted graph.
|
|
57
52
|
- Write exact response fixtures for success and failure, covering every active
|
|
58
53
|
method, middleware behavior, HEAD, and any range or cache semantics.
|
|
59
54
|
- Follow `docs/BEST-PRACTICES.md` for layout and readability as the project grows.
|
|
@@ -69,14 +64,17 @@ mistakes that recur:
|
|
|
69
64
|
- `function`/`middleware` routes run trusted and unsandboxed by default: full
|
|
70
65
|
Node, npm, filesystem and `fetch` access, in-process, like any other project
|
|
71
66
|
code. `sandbox: true` opts a route into isolation — reach for it when that
|
|
72
|
-
route's own code warrants it (
|
|
73
|
-
|
|
67
|
+
route's own code warrants it (unreviewed or third-party code, a secret whose
|
|
68
|
+
blast radius matters, complex logic), not reflexively on every route and
|
|
69
|
+
never merely because it handles request data -- that is untrusted in both
|
|
70
|
+
modes and must be validated either way. A
|
|
74
71
|
`sandbox: true` route gets a text/JSON `Request`/`Response` sandbox only:
|
|
75
72
|
**no** `fetch`, Node or npm APIs, filesystem, WebSocket, streaming or crypto
|
|
76
73
|
API.
|
|
77
74
|
- No global middleware, Express compatibility or automatic auth.
|
|
78
75
|
- `policies` accepts only `throttle`, `agents`, `security`, `compression` and
|
|
79
|
-
`cache`,
|
|
76
|
+
`cache`, plus registered extension requirements under `extensions`;
|
|
77
|
+
the built-in policies are off unless declared; `hardened` is the only built-in
|
|
80
78
|
profile. Check the per-target table in `docs/POLICIES.md` before declaring
|
|
81
79
|
one for a serverless or Cloudflare deployment — an unsupported policy refuses
|
|
82
80
|
activation rather than degrading.
|
|
@@ -84,9 +82,9 @@ mistakes that recur:
|
|
|
84
82
|
only and off unless declared; a declared route at the same path wins. Its
|
|
85
83
|
generated routes count toward `--expect-routes`, and `site.sitemap` needs
|
|
86
84
|
`--origin` on every command that activates the project.
|
|
87
|
-
- There is no native `link` handler or `dynamicLinks` project flag
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
- There is no native `link` handler or `dynamicLinks` project flag, and no
|
|
86
|
+
supported extension package provides one; report stored short links as a gap,
|
|
87
|
+
never invent a `link` field.
|
|
90
88
|
- Infrastructure (proxy ranges, storage URLs, vendor rule identifiers) is an
|
|
91
89
|
operator flag, never route YAML.
|
|
92
90
|
|
|
@@ -74,20 +74,20 @@ by the operator.
|
|
|
74
74
|
|
|
75
75
|
## Hard limits — report these as gaps, never invent around them
|
|
76
76
|
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
operator
|
|
77
|
+
- Provider adapters exist with different capability limits; query
|
|
78
|
+
`urlcode capabilities --target NAME`. Automatic TLS/DNS, distributed rate
|
|
79
|
+
limiting, metrics exporters and durable delivery require operator infrastructure.
|
|
80
80
|
- No orchestration, traffic switching or automated rollback; recovery is an
|
|
81
81
|
explicit snapshot reload from a known-good artifact.
|
|
82
82
|
- `verify-deployment` has no infrastructure access, uses no credential,
|
|
83
83
|
follows no redirect and offers no `--insecure`. It cannot check anything a
|
|
84
84
|
read-only HTTP probe cannot observe.
|
|
85
|
-
- Core has no durable store and no private management API of its own
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
- Core has no durable store and no private management API of its own, and no
|
|
86
|
+
supported extension package provides stored short links.
|
|
87
|
+
- Only `sandbox: true` routes share the sandbox worker slots and forced
|
|
88
|
+
execution deadlines. Trusted routes run in Node under HTTP admission limits;
|
|
89
|
+
their cooperative timeout cannot stop blocking JavaScript. A guest timer still
|
|
90
|
+
occupies a sandbox slot. Size both modes from `docs/CAPACITY.md`.
|
|
91
91
|
- `throttle` and `agents` policy counters are per instance, not distributed;
|
|
92
92
|
they are a second layer behind the edge, never a replacement for it.
|
|
93
93
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Authenticated JSON API
|
|
2
2
|
|
|
3
|
-
`/api/profile` is a
|
|
3
|
+
`/api/profile` is a function behind `auth: true`, the route-level short
|
|
4
4
|
form that expands to `policies.extensions.auth: {}`. The project declares the
|
|
5
|
-
`auth` extension; it never chooses or loads the module that implements it. Authorization happens in trusted operator code before the
|
|
6
|
-
runs, and the
|
|
5
|
+
`auth` extension; it never chooses or loads the module that implements it. Authorization happens in trusted operator code before the route's function
|
|
6
|
+
runs, and the host strips `Authorization` and `Cookie` before dispatch -- for
|
|
7
|
+
trusted and `sandbox: true` routes alike.
|
|
7
8
|
|
|
8
9
|
This recipe does not activate on its own. Every command needs an operator host
|
|
9
10
|
file outside the project plus the canonical origin:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The auth extension has already authorized this request. Credentials never
|
|
2
|
-
// reach
|
|
2
|
+
// reach this function: the host strips Authorization and Cookie before
|
|
3
|
+
// dispatch, in both execution modes.
|
|
3
4
|
export default function profile() {
|
|
4
5
|
return Response.json({signedIn: true, profile: {name: 'Ada', plan: 'team'}});
|
|
5
6
|
}
|
|
@@ -28,7 +28,7 @@ tests:
|
|
|
28
28
|
- urlcode test --project . --host-file /operator/host.mjs --origin https://api.example.com
|
|
29
29
|
- urlcode audit --project . --expect-routes 1 --host-file /operator/host.mjs --origin https://api.example.com
|
|
30
30
|
behavior:
|
|
31
|
-
- GET /api/profile without credentials answers 401 from the extension before the
|
|
31
|
+
- GET /api/profile without credentials answers 401 from the extension before the function runs
|
|
32
32
|
- GET with the credential the extension accepts answers 200 JSON with Cache-Control no-store
|
|
33
33
|
- HEAD mirrors both cases with an empty body; POST answers 405
|
|
34
34
|
- the project declares the extension and the requirement; it never chooses or loads the implementing module
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Field checks the runtime does not do: the body limit and JSON syntax are
|
|
2
2
|
// enforced before this runs. The declared signal fires after a response with
|
|
3
3
|
// a fixed payload (route, method, status); the message itself never leaves
|
|
4
|
-
// the
|
|
4
|
+
// the signal, so a form store or mailer belongs behind the granted hook rather
|
|
5
|
+
// than an ad-hoc call from here.
|
|
5
6
|
const emailPattern = /^[^\s@]{1,64}@[^\s@]{1,255}$/;
|
|
6
7
|
export default async function contact(request) {
|
|
7
8
|
const body = await request.json();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
id: contact-form
|
|
2
|
-
description: Validate a JSON contact message in
|
|
2
|
+
description: Validate a JSON contact message in a function and notify an operator-granted hook with a fixed signal after the response.
|
|
3
3
|
tags: [contact, form, message, email, post, json, validation, signal, notify, hook, egress, "422"]
|
|
4
4
|
complexity: intermediate
|
|
5
5
|
capabilities: [enabled, function, methods, request.body, response.headers, signals]
|
|
@@ -30,4 +30,4 @@ behavior:
|
|
|
30
30
|
- POST /contact with a valid name, email and message answers 202 {"accepted":true}
|
|
31
31
|
- invalid fields answer 422 with one error line per field; a non-object body answers 422
|
|
32
32
|
- form-encoded bodies answer 415 and GET answers 405
|
|
33
|
-
- the signal carries route, method and status only, best effort; the message itself never
|
|
33
|
+
- the signal carries route, method and status only, best effort; the message itself is never included
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Run `urlcode validate --local --project .`, `urlcode test --project .` and
|
|
4
4
|
`urlcode audit --project . --expect-routes 1`.
|
|
5
5
|
|
|
6
|
-
`/api/items` returns JSON from a
|
|
6
|
+
`/api/items` returns JSON from a function (trusted, the default). `middleware/cors.mjs` runs
|
|
7
7
|
around it: it answers `OPTIONS` preflight itself with 204 and adds
|
|
8
8
|
`Access-Control-Allow-Origin` to responses for origins in its allowlist. Other
|
|
9
9
|
origins get `Vary: Origin` and no allow header, so browsers refuse them.
|
|
@@ -11,6 +11,6 @@ origins get `Vary: Origin` and no allow header, so browsers refuse them.
|
|
|
11
11
|
Edit `allowedOrigins` in the middleware and the function body. The route must
|
|
12
12
|
list `OPTIONS` in `methods`, or the runtime answers 405 before the middleware
|
|
13
13
|
runs. CORS is not a host policy in this contract; middleware is the supported
|
|
14
|
-
place for it, and
|
|
14
|
+
place for it, and middleware needs the self-hosted Node lifecycle, so serverless targets refuse this
|
|
15
15
|
project. A browser-facing API that also needs credentials must add
|
|
16
16
|
`Access-Control-Allow-Credentials` deliberately, never with a wildcard origin.
|
|
@@ -23,4 +23,4 @@ behavior:
|
|
|
23
23
|
- OPTIONS /api/items from https://app.example.com answers 204 with Access-Control-Allow-Origin, -Methods and -Headers
|
|
24
24
|
- GET from the allowed origin carries Access-Control-Allow-Origin; other origins get Vary Origin and no allow header
|
|
25
25
|
- the route lists OPTIONS in methods, otherwise the runtime answers 405 before the middleware runs
|
|
26
|
-
- CORS is middleware, not a host policy, so the route needs the
|
|
26
|
+
- CORS is middleware, not a host policy, so the route needs the self-hosted Node lifecycle
|
|
@@ -4,7 +4,7 @@ Run `urlcode validate --local --project .`, `urlcode test --project .` and
|
|
|
4
4
|
`urlcode audit --project . --expect-routes 2`.
|
|
5
5
|
|
|
6
6
|
`/health` answers `ok` as text and `/status` answers a small JSON document. Both
|
|
7
|
-
are native `respond` routes with `Cache-Control: no-store`, so no
|
|
7
|
+
are native `respond` routes with `Cache-Control: no-store`, so no project code runs
|
|
8
8
|
and every target supports them. Change `service` in `urlcode.yaml` to your
|
|
9
9
|
service name.
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# JSON echo API
|
|
2
2
|
|
|
3
3
|
Run `urlcode validate --local --project .` and `urlcode serve --project .`.
|
|
4
|
-
POST JSON with `Content-Type: application/json` to `/echo`. The
|
|
4
|
+
POST JSON with `Content-Type: application/json` to `/echo`. The function returns
|
|
5
5
|
`{"received": ...}`. Other methods are refused. Do not submit credentials to an
|
|
6
6
|
echo endpoint. This recipe grants no network or filesystem access.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: json-api
|
|
2
|
-
description: Bounded JSON request body echoed back by a
|
|
3
|
-
tags: [json, api, post, echo, function, body, validation
|
|
2
|
+
description: Bounded JSON request body echoed back by a function (trusted, the default).
|
|
3
|
+
tags: [json, api, post, echo, function, body, validation]
|
|
4
4
|
complexity: starter
|
|
5
5
|
capabilities: [enabled, function, methods, request.body]
|
|
6
6
|
targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
|
|
@@ -21,5 +21,5 @@ tests:
|
|
|
21
21
|
- urlcode audit --project . --expect-routes 1
|
|
22
22
|
behavior:
|
|
23
23
|
- POST /echo with application/json answers 200 {"received":<body>}
|
|
24
|
-
- malformed JSON answers 400 and another content type 415 before the
|
|
24
|
+
- malformed JSON answers 400 and another content type 415 before the function runs
|
|
25
25
|
- GET answers 405
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
Fourteen reusable middleware modules on fifteen routes (`auth.mjs` serves two),
|
|
4
4
|
covering what Express, Koa,
|
|
5
5
|
Hono, Next.js and edge runtimes usually ship as middleware, rewritten for
|
|
6
|
-
URLCode's
|
|
6
|
+
URLCode's middleware contract. These modules run trusted and in-process, the
|
|
7
|
+
default; a `sandbox: true` route sees a smaller guest API instead. Run `urlcode validate --local --project .`
|
|
7
8
|
and `urlcode serve --project .`, then keep the routes you need and delete the
|
|
8
9
|
rest; every module is independent.
|
|
9
10
|
|
|
10
11
|
| Route | Module | Pattern |
|
|
11
12
|
| --- | --- | --- |
|
|
12
13
|
| `/api/private` | `middleware/auth.mjs` (`bearer`) | Bearer token with constant-time comparison |
|
|
13
|
-
| `/admin/panel` | `middleware/auth.mjs` (`basic`) | HTTP Basic decoded in
|
|
14
|
+
| `/admin/panel` | `middleware/auth.mjs` (`basic`) | HTTP Basic decoded in trusted middleware |
|
|
14
15
|
| `/cors/data` | `middleware/cors.mjs` | Origin allowlist and preflight |
|
|
15
16
|
| `/traced` | `middleware/request-id.mjs` | Caller correlation id and Server-Timing |
|
|
16
17
|
| `/maintenance` | `middleware/maintenance.mjs` | 503 kill switch with bypass header |
|
|
@@ -28,7 +29,10 @@ rest; every module is independent.
|
|
|
28
29
|
Tokens, passwords and switches are literal `env` values so the recipe runs
|
|
29
30
|
without grants. Before deploying, move credentials to `{secret: name}` bindings
|
|
30
31
|
with an operator grant, replace the example.com destinations, and delete the
|
|
31
|
-
`/inspect` route or set `DEBUG` to `false`.
|
|
32
|
+
`/inspect` route or set `DEBUG` to `false`. These modules run trusted, so
|
|
33
|
+
`node:crypto` is available; the examples deliberately stay to request shaping
|
|
34
|
+
and are not a security boundary. Under `sandbox: true` there is no crypto API
|
|
35
|
+
at all, so none
|
|
32
36
|
of these modules can verify signatures or hash passwords; treat them as request
|
|
33
37
|
shaping, not as a security boundary. See the runtime's middleware documentation
|
|
34
|
-
for the full list of
|
|
38
|
+
for the full list of sandboxed-route limits.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`/downloads/report` serves `files/report.txt` as an attachment, but only after
|
|
4
4
|
the operator-installed `auth` extension authorizes the request (`auth: true`,
|
|
5
5
|
the short form of `policies.extensions.auth`). The file is
|
|
6
|
-
served natively: no
|
|
6
|
+
served natively: no project code runs at all, and the response is forced to `no-store`.
|
|
7
7
|
|
|
8
8
|
Like the `authenticated-json-api` recipe, this project declares the extension
|
|
9
9
|
and needs an operator host file outside the project plus the canonical origin
|
|
@@ -28,4 +28,4 @@ behavior:
|
|
|
28
28
|
- GET /downloads/report without credentials answers 401
|
|
29
29
|
- GET with the accepted credential answers 200, Content-Disposition attachment and Cache-Control no-store
|
|
30
30
|
- HEAD mirrors both cases with an empty body
|
|
31
|
-
- no
|
|
31
|
+
- no project code runs; the file is served natively and snapshotted at activation
|
|
@@ -5,8 +5,8 @@ Run `urlcode validate --local --project .`, `urlcode test --project .` and
|
|
|
5
5
|
|
|
6
6
|
`/` serves `public/index.html`, `/assets/*` serves everything under
|
|
7
7
|
`public/assets` (with `index.html` for the directory itself), and `/api/info`
|
|
8
|
-
is a
|
|
9
|
-
assets are native: no
|
|
8
|
+
is a function returning JSON built from literal `args`. Pages and
|
|
9
|
+
assets are native: no project code runs for them at all, and they are snapshotted at
|
|
10
10
|
activation, so new files need a reload. Edit the HTML, add files under
|
|
11
11
|
`public/assets`, and change or extend `functions/info.mjs`.
|
|
12
12
|
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
<head><meta charset="utf-8"><title>Example site</title><link rel="stylesheet" href="/assets/site.css"></head>
|
|
4
4
|
<body>
|
|
5
5
|
<h1>Example site</h1>
|
|
6
|
-
<p>Static pages are served natively; <a href="/api/info">/api/info</a> is a
|
|
6
|
+
<p>Static pages are served natively; <a href="/api/info">/api/info</a> is a function route.</p>
|
|
7
7
|
</body>
|
|
8
8
|
</html>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
id: static-plus-api
|
|
2
|
-
description: A native static site (home page and an assets directory) next to one
|
|
2
|
+
description: A native static site (home page and an assets directory) next to one function-backed JSON endpoint.
|
|
3
3
|
tags: [static, site, page, html, assets, css, api, json, function, cache-control, website]
|
|
4
4
|
complexity: intermediate
|
|
5
5
|
capabilities: [enabled, function, methods, page, response.headers, static]
|
|
@@ -12,7 +12,7 @@ routes:
|
|
|
12
12
|
index: index.html
|
|
13
13
|
cacheControl: public, max-age=3600
|
|
14
14
|
/api/info:
|
|
15
|
-
description: A small JSON endpoint next to the static site, computed
|
|
15
|
+
description: A small JSON endpoint next to the static site, computed by a trusted function.
|
|
16
16
|
function:
|
|
17
17
|
source: functions/info.mjs
|
|
18
18
|
args:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: typescript
|
|
2
|
-
description: Typed
|
|
3
|
-
tags: [typescript, function, build, typed, compile
|
|
2
|
+
description: "Typed function transpiled ahead of time; only the emitted JavaScript runs: in-process for a trusted route, in QuickJS/WASM where a route declares `sandbox: true`."
|
|
3
|
+
tags: [typescript, function, build, typed, compile]
|
|
4
4
|
complexity: intermediate
|
|
5
5
|
capabilities: [enabled, function, methods]
|
|
6
6
|
targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
|
|
@@ -8,7 +8,7 @@ routes: 1
|
|
|
8
8
|
inputs:
|
|
9
9
|
- name: handler
|
|
10
10
|
file: functions/hello.ts
|
|
11
|
-
description: The typed
|
|
11
|
+
description: "The typed handler; keep to Request/Response, and to the sandbox subset if you add `sandbox: true`."
|
|
12
12
|
files: [urlcode.yaml, functions/hello.ts, tests/requests.json, README.md]
|
|
13
13
|
tests:
|
|
14
14
|
fixtures: tests/requests.json
|
|
@@ -18,6 +18,6 @@ tests:
|
|
|
18
18
|
- urlcode test --project ../hello-built
|
|
19
19
|
- urlcode audit --project ../hello-built --expect-routes 1
|
|
20
20
|
behavior:
|
|
21
|
-
- after the build, GET /hello answers 200 JSON from the compiled
|
|
21
|
+
- after the build, GET /hello answers 200 JSON from the compiled module
|
|
22
22
|
- the unbuilt project does not activate; .ts sources never run
|
|
23
23
|
- POST answers 405
|
package/skills/urlcode/SKILL.md
CHANGED
|
@@ -6,7 +6,7 @@ description: Work on a URLCode project, recognized by a urlcode.yaml file with v
|
|
|
6
6
|
# URLCode authoring loop
|
|
7
7
|
|
|
8
8
|
A URLCode project declares URL behavior in YAML; the installed runtime serves
|
|
9
|
-
it. Your job is to change the declaration and the minimal
|
|
9
|
+
it. Your job is to change the declaration and the minimal application code it names,
|
|
10
10
|
then prove the change with the runtime's own checks. Never reimplement what the
|
|
11
11
|
runtime provides, and never invent fields.
|
|
12
12
|
|
|
@@ -68,14 +68,14 @@ need rather than reading them whole.
|
|
|
68
68
|
2. Check supported extensions and their configuration before custom code. If a
|
|
69
69
|
recipe from `recipes list` is close, `urlcode recipes add NAME --out DIR`
|
|
70
70
|
and adapt the copy into the project's layout.
|
|
71
|
-
3. Only then write a function or middleware: one exported handler,
|
|
72
|
-
ES-module imports inside the project only, inputs from validated `args`,
|
|
71
|
+
3. Only then write a function or middleware: one exported handler, inputs from validated `args`,
|
|
73
72
|
output as a `Response`. `function`/`middleware` routes run trusted and
|
|
74
73
|
unsandboxed by default: full Node, npm, filesystem and `fetch` access, like
|
|
75
74
|
any other project code. Add `sandbox: true` only when that route's own code
|
|
76
|
-
warrants isolation (
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
warrants isolation (unreviewed or third-party code, a secret whose blast
|
|
76
|
+
radius matters, complex logic — never merely because it handles request
|
|
77
|
+
data, which is untrusted in both modes) — a `sandbox: true` route then has no
|
|
78
|
+
`fetch`, Node, npm, filesystem, WebSocket, streaming or crypto APIs (bounded timers are supported);
|
|
79
79
|
a need for those in a sandboxed route is a `proxy` route, a binding, or a
|
|
80
80
|
report.
|
|
81
81
|
4. Declare routing, validation, middleware chains, policies, static serving,
|
|
@@ -42,12 +42,12 @@ invitation to reimplement it.
|
|
|
42
42
|
|
|
43
43
|
## Functions and middleware are trusted by default; sandbox is opt-in
|
|
44
44
|
|
|
45
|
-
A route's `function`/`middleware`
|
|
46
|
-
Node/filesystem/`fetch` access,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
A route's `function`/`middleware` runs trusted, in-process, with full
|
|
46
|
+
Node/filesystem/`fetch` access, given only declared `args`/`env`/`secrets`. Add
|
|
47
|
+
`sandbox: true` when that code warrants isolation (unreviewed code, a sensitive
|
|
48
|
+
secret, complex logic) — not merely for untrusted input, which both modes share.
|
|
49
|
+
A `sandbox: true` route gets a text/JSON subset only: use `proxy`/a binding, and
|
|
50
|
+
say why in `sandboxReason`.
|
|
51
51
|
|
|
52
52
|
## Checks that count as evidence
|
|
53
53
|
|