@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
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
# Historical record
|
|
2
|
+
|
|
3
|
+
Archived 2026-09-19. This records an earlier implementation or proposal, not
|
|
4
|
+
current instructions. See the [current roadmap](../../../ROADMAP.md),
|
|
5
|
+
[current contract](../../SPECIFICATION.md) and [open decisions](../../OPEN-DECISIONS.md).
|
|
6
|
+
Remaining acceptance work is not declared complete by archiving this record.
|
|
7
|
+
|
|
8
|
+
<!-- trust-model-prose: historical-file -->
|
|
9
|
+
<!-- guidance-claims: ignore-file -->
|
|
10
|
+
|
|
1
11
|
# Usability review: URLCode against the tools people reach for instead
|
|
2
12
|
|
|
3
13
|
Status: an honest assessment written 2026-09-18 against the source in the four
|
|
@@ -10,12 +20,12 @@ that would matter most. It does not change any contract by itself.
|
|
|
10
20
|
> execution mode. Where the table below credits functions and middleware with
|
|
11
21
|
> isolation, that now describes a route declaring `sandbox: true`; the default
|
|
12
22
|
> is trusted, in-process execution with full Node, npm and filesystem access.
|
|
13
|
-
> See [SPIKE-DEFAULT-TRUST-MODEL.md](SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
23
|
+
> See [SPIKE-DEFAULT-TRUST-MODEL.md](../../SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
14
24
|
|
|
15
25
|
## 1. Who the competitors are, by rung
|
|
16
26
|
|
|
17
27
|
URLCode is not one product; it competes with a different tool on each rung
|
|
18
|
-
of [the ladder](FRAMEWORK.md#the-ladder).
|
|
28
|
+
of [the ladder](../../FRAMEWORK.md#the-ladder).
|
|
19
29
|
|
|
20
30
|
| Rung | What people use today | Where URLCode is better | Where it is worse today |
|
|
21
31
|
|---|---|---|---|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Historical records
|
|
2
|
+
|
|
3
|
+
These documents retain earlier reasoning and evidence. They are not authoring
|
|
4
|
+
instructions or an active backlog. Read [the current roadmap](../../ROADMAP.md),
|
|
5
|
+
[open decisions](../OPEN-DECISIONS.md) and [implemented contract](../SPECIFICATION.md)
|
|
6
|
+
first. An old example can describe a removed feature or an earlier trust model.
|
|
7
|
+
|
|
8
|
+
Archived on 2026-09-19 after checking the current code:
|
|
9
|
+
|
|
10
|
+
| Record | Why archived | Current owner of remaining work |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| [Monorepo spike](2026-09-19/SPIKE-MONOREPO.md) | The migration it proposed is complete: ui, auth and admin are workspace packages and all three have been released from this repository | [Development pipeline](../DEVELOPMENT-PIPELINE.md) and [release security](../RELEASE-SECURITY.md) |
|
|
13
|
+
| [Previous roadmap](2026-09-19/ROADMAP.md) | Release chronology mixed with current planning | Current roadmap and release readiness |
|
|
14
|
+
| [Detailed phases](2026-09-19/NEXT-STEPS.md) | Most authoring/tooling/UI work delivered; repeated stale backlog | Current roadmap, issues and open decisions |
|
|
15
|
+
| [Previous decisions](2026-09-19/OPEN-DECISIONS.md) | Mixed settled releases, obsolete repository counts and live questions | Current open decisions |
|
|
16
|
+
| [Next-phase implementation](2026-09-19/NEXT-PHASE-PLAN.md) | Delivered source snapshot for alpha.1 | Specification and provider evidence |
|
|
17
|
+
| [Extension delivery sequence](2026-09-19/EXTENSION-IMPLEMENTATION.md) | Registration, auth/admin and UI now implemented | Package status files and core issue 58 |
|
|
18
|
+
| [Policy/extension exploration](2026-09-19/SPIKE-EXTENSIONS.md) | Main design delivered; obsolete link APIs | Policies, plugins and extensions guides |
|
|
19
|
+
| [Extension model review](2026-09-19/SPIKE-EXTENSION-MODEL.md) | Historical design rationale | Current extension contract |
|
|
20
|
+
| [Usability review](2026-09-19/USABILITY-REVIEW.md) | Predates trusted execution and retired packages | New benchmark observations |
|
|
21
|
+
| [Management security](2026-09-19/MANAGEMENT-SECURITY.md) | Describes the removed core link-management API | No current core replacement |
|
|
22
|
+
|
|
23
|
+
Open proposals stay outside the archive with their status stated at the top.
|
|
24
|
+
The accepted trust-by-default decision remains directly linked from the current
|
|
25
|
+
security contract; its `SPIKE-` filename is retained for existing references.
|
|
26
|
+
Security audits and operational evidence remain available: “implemented” never
|
|
27
|
+
means their unperformed review, deployment or recovery work is complete.
|
package/docs/policies/agents.md
CHANGED
|
@@ -65,7 +65,7 @@ Matching is string matching. A client can claim any `User-Agent`, and the
|
|
|
65
65
|
genuine search crawlers publish the way to check a claim (reverse DNS for
|
|
66
66
|
Googlebot, bingbot and Applebot; the IETF `web-bot-auth` HTTP Message
|
|
67
67
|
Signature drafts for newer agents). That verification needs network calls and
|
|
68
|
-
vendor data, so it belongs in a [host plugin](../SPIKE-EXTENSIONS.md) that
|
|
68
|
+
vendor data, so it belongs in a [host plugin](../archive/2026-09-19/SPIKE-EXTENSIONS.md) that
|
|
69
69
|
runs after this policy, not in the runtime.
|
|
70
70
|
|
|
71
71
|
## Bundled lists
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Compression policy
|
|
2
2
|
|
|
3
3
|
`policies.compression` negotiates a content coding (RFC 9110 §12.5.3) on the
|
|
4
|
-
host, outside
|
|
5
|
-
bodies, function results, asset
|
|
4
|
+
host, outside function/middleware execution — trusted or sandboxed alike — for
|
|
5
|
+
every result a route produces: `respond` bodies, function results, asset
|
|
6
|
+
responses, cache hits and early denials.
|
|
6
7
|
It runs last in the response phase, after the cache store and the security
|
|
7
8
|
headers, so every header it reads is final. Asset snapshots are compressed
|
|
8
9
|
once at load and served by reference (the NGINX `gzip_static` / Caddy
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Security headers policy
|
|
2
2
|
|
|
3
|
-
`policies.security` adds response security headers on the host, outside
|
|
4
|
-
|
|
3
|
+
`policies.security` adds response security headers on the host, outside
|
|
4
|
+
function/middleware execution -- trusted or sandboxed alike -- to every result
|
|
5
|
+
a route produces: redirects, `respond` bodies,
|
|
5
6
|
function results, asset responses, cache hits and early denials from the
|
|
6
7
|
`agents` and `throttle` policies. Values are the OWASP Secure Headers
|
|
7
8
|
Project best-practice recommendations (OSHP 2024 best practices,
|
|
@@ -14,4 +14,4 @@ tests:
|
|
|
14
14
|
- node src/cli.ts audit --project examples/assets --expect-routes 5
|
|
15
15
|
behavior:
|
|
16
16
|
- native page, static and download routes serve snapshotted files with explicit caching
|
|
17
|
-
- one
|
|
17
|
+
- one trusted function shows the two kinds of route side by side
|
|
@@ -12,7 +12,7 @@ routes:
|
|
|
12
12
|
export: bearer
|
|
13
13
|
respond: {json: {private: true}}
|
|
14
14
|
/admin/panel:
|
|
15
|
-
description: HTTP Basic authentication decoded
|
|
15
|
+
description: HTTP Basic authentication decoded in trusted middleware
|
|
16
16
|
env:
|
|
17
17
|
ADMIN_USER: {value: admin}
|
|
18
18
|
ADMIN_PASSWORD: {value: cookbook-password}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# Prerender a dynamic project into a native static one
|
|
2
2
|
|
|
3
3
|
Three pages rendered by a function and one shared template middleware, then
|
|
4
|
-
rendered once at build time into a project that serves the same bytes with
|
|
5
|
-
|
|
4
|
+
rendered once at build time into a project that serves the same bytes with no
|
|
5
|
+
code on the request path at all.
|
|
6
|
+
|
|
7
|
+
The source routes run trusted and in-process, which is the default for
|
|
8
|
+
`function`/`middleware` and the right choice here: the page code is reviewed
|
|
9
|
+
first-party code that reads nothing but the literal arguments in
|
|
10
|
+
`urlcode.yaml`. Prerendering does not depend on that — a route declaring
|
|
11
|
+
`sandbox: true` prerenders the same way — and either way the generated project
|
|
12
|
+
runs no code at all.
|
|
6
13
|
|
|
7
14
|
From the runtime checkout:
|
|
8
15
|
|
|
@@ -15,17 +22,18 @@ node src/cli.ts audit --project /absolute/out --expect-routes 3
|
|
|
15
22
|
```
|
|
16
23
|
|
|
17
24
|
The same three URLs answer identically before and after. The difference is what
|
|
18
|
-
runs to serve them: the source project
|
|
19
|
-
request, the generated project reads a prevalidated byte buffer
|
|
25
|
+
runs to serve them: the source project executes the function and its middleware
|
|
26
|
+
per request, the generated project reads a prevalidated byte buffer and runs no
|
|
27
|
+
project code at all.
|
|
20
28
|
|
|
21
29
|
| | Source project | Generated project |
|
|
22
30
|
|---|---|---|
|
|
23
31
|
| Handlers | 3 × `function` | 3 × `page` |
|
|
24
32
|
| Middleware | shared template | none |
|
|
25
|
-
|
|
|
33
|
+
| Project code per request | function + middleware | none |
|
|
26
34
|
| Content | reviewed literals in YAML | rendered HTML files |
|
|
27
35
|
|
|
28
|
-
`prerender.mjs` is operator build tooling
|
|
36
|
+
`prerender.mjs` is operator build tooling, not a route handler. The
|
|
29
37
|
orchestration lives in the runtime's build helper:
|
|
30
38
|
|
|
31
39
|
```js
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// One page's content, supplied as reviewed literal arguments in urlcode.yaml.
|
|
2
|
-
// This runs in the
|
|
3
|
-
//
|
|
2
|
+
// This runs trusted and in-process -- the default for a function route -- at
|
|
3
|
+
// build time exactly as it would at request time. It reads nothing but its
|
|
4
|
+
// own args, so there is nothing here for `sandbox: true` to isolate; adding it
|
|
5
|
+
// would cost worker-pool capacity for no gain.
|
|
4
6
|
export function page(request, {args}) {
|
|
5
7
|
return new Response(args.body, {headers: {'content-type': 'text/html; charset=utf-8'}});
|
|
6
8
|
}
|
|
@@ -22,7 +22,7 @@ export async function template(request, context, next) {
|
|
|
22
22
|
<h1>${title}</h1>
|
|
23
23
|
${content}
|
|
24
24
|
</main>
|
|
25
|
-
<footer>Prerendered at build time. No
|
|
25
|
+
<footer>Prerendered at build time. No project code runs to serve this page.</footer>
|
|
26
26
|
</body>
|
|
27
27
|
</html>
|
|
28
28
|
`, {status: response.status, headers: {'content-type': 'text/html; charset=utf-8'}});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Prerender recipe: render this project's function/middleware routes once, at
|
|
3
3
|
// build time, into a native page project that serves the same bytes with no
|
|
4
|
-
//
|
|
4
|
+
// project code on the request path.
|
|
5
5
|
//
|
|
6
6
|
// The orchestration that is easy to get wrong — runtime lifecycle, response
|
|
7
7
|
// validation, budgets, byte fidelity and output-path safety — lives in the
|
|
@@ -2,6 +2,10 @@ version: "1"
|
|
|
2
2
|
# The dynamic source of the site. Every page is a function response wrapped by
|
|
3
3
|
# one shared template middleware, so `dev` serves it live while you edit. The
|
|
4
4
|
# prerender recipe renders these routes once and emits a native page project.
|
|
5
|
+
# No route declares `sandbox: true`: this is reviewed first-party code taking
|
|
6
|
+
# literal arguments from this file, which is the trusted default's case, not an
|
|
7
|
+
# exception to it (docs/AI-AUTHORING.md, "Deciding when a route needs
|
|
8
|
+
# `sandbox: true`").
|
|
5
9
|
routes:
|
|
6
10
|
/:
|
|
7
11
|
description: Home page rendered by a function and the shared template
|
|
@@ -16,7 +20,7 @@ routes:
|
|
|
16
20
|
body: |
|
|
17
21
|
<p>This page is generated by a function and wrapped by shared template
|
|
18
22
|
middleware. Running the prerender recipe turns it into a native page
|
|
19
|
-
route that serves the same bytes with no
|
|
23
|
+
route that serves the same bytes with no code on the request path.</p>
|
|
20
24
|
<ul>
|
|
21
25
|
<li><a href="/guide">Guide</a></li>
|
|
22
26
|
<li><a href="/about">About</a></li>
|
|
@@ -32,9 +36,9 @@ routes:
|
|
|
32
36
|
args:
|
|
33
37
|
title: Guide
|
|
34
38
|
body: |
|
|
35
|
-
<p>Content is reviewed literal input in YAML
|
|
36
|
-
|
|
37
|
-
content before rendering.</p>
|
|
39
|
+
<p>Content is reviewed literal input in YAML rather than anything this
|
|
40
|
+
code goes and reads, so a larger site generates this project from
|
|
41
|
+
host-prepared content before rendering.</p>
|
|
38
42
|
/about:
|
|
39
43
|
description: A third page proving the template is shared, not copied
|
|
40
44
|
middleware:
|
package/llms-full.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- Generated by scripts/build-llms-full.ts (npm run docs:llms). Do not edit; edit the source documents. -->
|
|
2
|
-
<!-- Consolidated URLCode authoring reference: 15 documents, about
|
|
2
|
+
<!-- Consolidated URLCode authoring reference: 15 documents, about 48,160 tokens (estimate: characters / 4). -->
|
|
3
3
|
|
|
4
4
|
# URLCode
|
|
5
5
|
|
|
@@ -100,9 +100,9 @@ Each rung's YAML is valid on every rung above it.
|
|
|
100
100
|
project copy, template and stylesheet overrides for kit-rendered pages.
|
|
101
101
|
|
|
102
102
|
Stored short links previously sat here as a native `link` route; that handler
|
|
103
|
-
was removed from core. A
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
was removed from core. A `urlcode-dynamic-link` package owned them the same way
|
|
104
|
+
`auth`/`admin` own their mounts, but it has been retired and unpublished; no
|
|
105
|
+
package occupies this rung today.
|
|
106
106
|
|
|
107
107
|
Rungs 1 to 3 need only the core package. Rungs 4 to 6 need the extension
|
|
108
108
|
packages, installed from npm as `0.1.0-alpha.x` prereleases, and a Node host
|
|
@@ -118,9 +118,9 @@ npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/
|
|
|
118
118
|
urlcode init my-site --with auth,admin
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
Installing from npm is the normal path;
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
Installing from npm is the normal path; `scripts/pack-sources.mjs` still builds
|
|
122
|
+
local tarballs from a reviewed checkout for operators who install only source
|
|
123
|
+
they have read — one revision now covers core and every extension. Three files make an extended
|
|
124
124
|
project. Nothing else is discovered by convention.
|
|
125
125
|
|
|
126
126
|
```
|
|
@@ -152,7 +152,8 @@ routes:
|
|
|
152
152
|
auth: {}
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
The operator host
|
|
155
|
+
The operator host explicitly registers the packages. Registration is an
|
|
156
|
+
activation boundary; it does not isolate trusted application code from the host:
|
|
156
157
|
|
|
157
158
|
```js
|
|
158
159
|
import { createUiExtension } from '@jimhoyd/urlcode-ui/host';
|
|
@@ -172,12 +173,12 @@ export default {
|
|
|
172
173
|
};
|
|
173
174
|
```
|
|
174
175
|
|
|
175
|
-
Auth and admin
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
Auth and admin already render their screens through `ui.kit` when the host
|
|
177
|
+
supplies the UI extension; their package-owned templates and catalogues must be
|
|
178
|
+
registered with that kit. Without it, both retain shared primitive rendering
|
|
179
|
+
through `presentation`. The example above uses that primitive fallback. The UI
|
|
180
|
+
block is optional, and a migration to the kit is not unfinished framework work.
|
|
181
|
+
See each package README for its complete kit registration example.
|
|
181
182
|
|
|
182
183
|
```sh
|
|
183
184
|
urlcode serve --project /absolute/site --host-file /absolute/operator/host.mjs --origin https://site.example
|
|
@@ -199,7 +200,8 @@ or mounts changes the revision and needs an explicit operator reapproval.
|
|
|
199
200
|
These are the facts that keep generated projects valid. The full matrix is in
|
|
200
201
|
[AI authoring](https://github.com/jimhoyd-com/urlcode/blob/main/docs/AI-AUTHORING.md); this is the short list.
|
|
201
202
|
|
|
202
|
-
- **YAML
|
|
203
|
+
- **Extension YAML names logical extensions, not host packages or credentials.**
|
|
204
|
+
Function and middleware `source` fields do name project modules. Extensions are
|
|
203
205
|
logical names; the host file chooses the implementation. There is no
|
|
204
206
|
`--extension` flag, no `import` in YAML, no interpolation.
|
|
205
207
|
- **One handler per route.** `redirect`, `respond`, `page`, `static`, `download`,
|
|
@@ -210,13 +212,15 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
210
212
|
It runs in-process with full Node access unless the route declares
|
|
211
213
|
`sandbox: true`, which isolates it to a text/JSON `Request`/`Response`
|
|
212
214
|
subset, validated `args` and granted `env`, with no `fetch`, Node,
|
|
213
|
-
filesystem or timers. Either way, `args`/`env`/`secrets` are exactly what
|
|
215
|
+
filesystem or general network access; bounded timers are available. Either way, `args`/`env`/`secrets` are exactly what
|
|
214
216
|
the route declares and an operator grants — trust changes where code runs,
|
|
215
217
|
not what it is handed. See docs/SPIKE-DEFAULT-TRUST-MODEL.md and
|
|
216
218
|
docs/FUNCTION-SECURITY.md.
|
|
217
219
|
- **Authentication is host processing.** Do not build login forms, session
|
|
218
|
-
cookies or password checks in functions.
|
|
219
|
-
|
|
220
|
+
cookies or password checks in functions. With the auth extension declared,
|
|
221
|
+
prefer `auth: true` or `auth: {role: admin}`; these expand to
|
|
222
|
+
`policies.extensions.auth`. The runtime filters credential headers passed to
|
|
223
|
+
application handlers. This is not a security boundary against trusted Node code.
|
|
220
224
|
- **Everything is validated before it runs.** `urlcode validate --local`,
|
|
221
225
|
`urlcode test`, `urlcode audit --expect-routes N`. Unsupported features fail
|
|
222
226
|
with the route named; nothing degrades silently.
|
|
@@ -238,7 +242,7 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
238
242
|
| Add accounts | [auth README](https://github.com/jimhoyd-com/urlcode-auth#readme), [auth security](https://github.com/jimhoyd-com/urlcode-auth/blob/main/SECURITY.md) |
|
|
239
243
|
| Add administration | [admin README](https://github.com/jimhoyd-com/urlcode-admin#readme) |
|
|
240
244
|
| Restyle every page | [ui README](https://github.com/jimhoyd-com/urlcode-ui#readme), [ui contract](https://github.com/jimhoyd-com/urlcode-ui/blob/main/CONTRACT.md) |
|
|
241
|
-
| Write an extension | [extensions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md), [extension model review](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPIKE-EXTENSION-MODEL.md) |
|
|
245
|
+
| Write an extension | [extensions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md), [extension model review](https://github.com/jimhoyd-com/urlcode/blob/main/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md) |
|
|
242
246
|
| Run it | [operations](https://github.com/jimhoyd-com/urlcode/blob/main/docs/OPERATIONS.md), [install](https://github.com/jimhoyd-com/urlcode/blob/main/docs/INSTALL.md), [deployment checks](https://github.com/jimhoyd-com/urlcode/blob/main/docs/DEPLOYMENT-CHECKS.md) |
|
|
243
247
|
|
|
244
248
|
---
|
|
@@ -353,7 +357,7 @@ The benchmark operates locally; it is not a load test of an external deployment.
|
|
|
353
357
|
| Parameter validation and JSON body syntax checks | Full OpenAPI or JSON Schema validation of request bodies |
|
|
354
358
|
| Local test/audit/benchmark | Route-local YAML tests, managed monitoring, production load certification |
|
|
355
359
|
| Local/self-hosted runtime; limited AWS/Vercel/Cloudflare implementations with local tests | Verified provider deployments or full cross-provider parity |
|
|
356
|
-
| File authoring and snapshot reload | General guest storage broker; stored short links (
|
|
360
|
+
| File authoring and snapshot reload | General guest storage broker; stored short links (no supported package; the `urlcode-dynamic-link` extension was retired) |
|
|
357
361
|
| Optional host `policies` (`throttle`, `agents`, `security`, `compression`, `cache`) and reusable `profiles` | Plugins named in YAML, shared multi-instance counters, CORS, verified-bot checks |
|
|
358
362
|
| Optional top-level `site` (`robots`, `sitemap`, `favicon`, `securityTxt`, `llms`) generating native routes | Per-route `noindex` field, sitemap index files, `humans.txt`, signed `security.txt` |
|
|
359
363
|
|
|
@@ -487,9 +491,12 @@ Put that justification where tooling can see it, not only in a source
|
|
|
487
491
|
comment: an optional `sandboxReason` string on the route (up to 500
|
|
488
492
|
characters, `schemas/urlcode.schema.json`) records why a route needs
|
|
489
493
|
isolation, or why it is safe to trust, regardless of whether `sandbox` is
|
|
490
|
-
`true` or `false`. `urlcode explain`/`context
|
|
491
|
-
|
|
492
|
-
|
|
494
|
+
`true` or `false`. `urlcode explain`/`context`, the manifest and the
|
|
495
|
+
`routes` inventory all surface it next to the route's `sandbox` boolean —
|
|
496
|
+
per route, not per handler, so a native handler that runs `middleware`
|
|
497
|
+
reports its execution mode too, and `routes --compare` shows a flip between
|
|
498
|
+
trusted and sandboxed execution as a changed route. The trust decision has a
|
|
499
|
+
reviewable trail without reading every route's source file:
|
|
493
500
|
|
|
494
501
|
```yaml
|
|
495
502
|
routes:
|
|
@@ -570,8 +577,9 @@ trust (adding `sandbox: true` without saying why, or relying on the trusted
|
|
|
570
577
|
default for code that plainly needed isolation) to work around them.
|
|
571
578
|
|
|
572
579
|
There is no native `link` handler or `dynamicLinks` project flag; both were
|
|
573
|
-
removed.
|
|
574
|
-
|
|
580
|
+
removed. The `urlcode-dynamic-link` extension package that briefly owned them
|
|
581
|
+
has been retired and unpublished, so there is no supported replacement. Report a
|
|
582
|
+
request for live stored links as a gap rather than inventing a `link` field.
|
|
575
583
|
|
|
576
584
|
See [capabilities and normalized route representation](https://github.com/jimhoyd-com/urlcode/blob/main/docs/CAPABILITIES.md) for the target catalog,
|
|
577
585
|
programmatic compatibility analysis and provider verification limits.
|
|
@@ -1255,10 +1263,10 @@ keys preserve repeated values. Headers are forwarded only through explicit maps.
|
|
|
1255
1263
|
### Stored links
|
|
1256
1264
|
|
|
1257
1265
|
Core has no native `link` handler or `dynamicLinks` project flag; both were
|
|
1258
|
-
removed.
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1266
|
+
removed. The `urlcode-dynamic-link` extension package that replaced them has
|
|
1267
|
+
been retired and unpublished, so a project needing stored short links owns that
|
|
1268
|
+
storage itself; there is no in-core replacement or deprecation shim for
|
|
1269
|
+
`link`/`dynamicLinks`.
|
|
1262
1270
|
|
|
1263
1271
|
### Functions
|
|
1264
1272
|
|
|
@@ -1426,7 +1434,9 @@ are local Git-owned examples; they grant no capabilities. [Build-time TypeScript
|
|
|
1426
1434
|
is separate from runtime execution.
|
|
1427
1435
|
|
|
1428
1436
|
The [tooling SDK and optional local MCP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md) inspect and validate without
|
|
1429
|
-
executing handlers or reading binding values.
|
|
1437
|
+
executing handlers or reading binding values. MCP defaults to inspection; the
|
|
1438
|
+
explicit `--allow-authoring` option enables bounded project writes, separately
|
|
1439
|
+
from host-file loading and grants. [Provider conformance](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PROVIDER-VERIFICATION.md)
|
|
1430
1440
|
distinguishes local adapter replay from actual deployment observations; no real
|
|
1431
1441
|
provider deployment is implied by CI. [Proxy and signal egress](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EGRESS.md) requires
|
|
1432
1442
|
external revision-pinned operator grants and bounded host-owned transport;
|
|
@@ -1539,10 +1549,9 @@ any per-visitor session record. Git owns route behavior and code; user-created r
|
|
|
1539
1549
|
their own persistence, backup and export lifecycle.
|
|
1540
1550
|
|
|
1541
1551
|
Core has no native handler for this today: the `link` handler that implemented
|
|
1542
|
-
it was removed.
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
that package exists.
|
|
1552
|
+
it was removed. The `urlcode-dynamic-link` extension package that replaced it
|
|
1553
|
+
has been retired and unpublished, so a project needing stored short links owns
|
|
1554
|
+
that storage itself.
|
|
1546
1555
|
|
|
1547
1556
|
Functions still cannot access databases, the filesystem or network directly.
|
|
1548
1557
|
General application state and realtime sessions remain future work.
|
|
@@ -1790,7 +1799,8 @@ Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/ASSETS.md
|
|
|
1790
1799
|
|
|
1791
1800
|
Native file handlers are implemented in 0.1.0-alpha.3. They work in the local
|
|
1792
1801
|
Node runtime and self-hosted process/container. Provider adapters remain planned.
|
|
1793
|
-
They do not run user functions or expose filesystem APIs to
|
|
1802
|
+
They do not run user functions or expose filesystem APIs to guest code, trusted
|
|
1803
|
+
or sandboxed.
|
|
1794
1804
|
|
|
1795
1805
|
```yaml
|
|
1796
1806
|
version: "1"
|
|
@@ -1902,9 +1912,10 @@ Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/POLICIES.md
|
|
|
1902
1912
|
Policies are host-enforced behavior declared in YAML around a route: a
|
|
1903
1913
|
per-client request budget, a User-Agent deny list, a security-header profile,
|
|
1904
1914
|
content-coding negotiation and an HTTP caching strategy. They run in the host
|
|
1905
|
-
process, outside
|
|
1906
|
-
|
|
1907
|
-
|
|
1915
|
+
process, outside function/middleware execution — trusted or sandboxed alike —
|
|
1916
|
+
so they can see the client address, keep counters across requests and touch
|
|
1917
|
+
the transport, which route middleware by design cannot
|
|
1918
|
+
([middleware](https://github.com/jimhoyd-com/urlcode/blob/main/docs/MIDDLEWARE.md), [function security](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md)).
|
|
1908
1919
|
|
|
1909
1920
|
Everything here is optional and off by default. A project with no `policies`
|
|
1910
1921
|
key and no `profiles` key behaves exactly as before: no policy module is
|
|
@@ -1919,7 +1930,7 @@ compiled and the request path is unchanged. Each policy has its own page:
|
|
|
1919
1930
|
| `cache` | [cache](https://github.com/jimhoyd-com/urlcode/blob/main/docs/policies/cache.md) | Named strategies from RFC 9111/5861/8246/9213 plus an origin memory cache |
|
|
1920
1931
|
|
|
1921
1932
|
The design and the reasoning behind each choice are in the
|
|
1922
|
-
[extensions spike](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPIKE-EXTENSIONS.md). Operators who need behavior the
|
|
1933
|
+
[extensions spike](https://github.com/jimhoyd-com/urlcode/blob/main/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md). Operators who need behavior the
|
|
1923
1934
|
declarative block cannot express pass host [plugins](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PLUGINS.md) in code; plugins
|
|
1924
1935
|
are never named in YAML.
|
|
1925
1936
|
|
|
@@ -1941,7 +1952,14 @@ routes:
|
|
|
1941
1952
|
`policies` at the top level sets project defaults; `routes.<path>.policies`
|
|
1942
1953
|
adjusts them for one route. Two route-level short forms exist. `auth`
|
|
1943
1954
|
expands to `policies.extensions.auth` when the project declares an auth
|
|
1944
|
-
[extension](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md)
|
|
1955
|
+
[extension](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md), carrying the same keys minus `required`;
|
|
1956
|
+
`required: false` documents the intent and emits no policy. It accepts
|
|
1957
|
+
`required`, `role`, `permission`, `verified`, `freshWithinSeconds` and
|
|
1958
|
+
`onDeny` and nothing else — `role` is singular, and there is no `roles`. Like
|
|
1959
|
+
`cache` below, it is refused rather than silently ignored in three cases: when
|
|
1960
|
+
the project declares no `extensions.auth`, when the route also sets
|
|
1961
|
+
`policies.extensions.auth` (use one form), and when the route sets
|
|
1962
|
+
`policies.extensions: false`. `cache: {strategy, maxAge, ...}` expands to
|
|
1945
1963
|
`policies.cache` the same way — the compiler merges it into that route's
|
|
1946
1964
|
`policies` before anything else reads the project, so `routes`, `audit` and
|
|
1947
1965
|
`explain` see only the expanded long form, and it is refused alongside a
|
|
@@ -2477,10 +2495,11 @@ and admin implementations live in `urlcode-auth` and `urlcode-admin`; the runtim
|
|
|
2477
2495
|
supplies only the generic integration contract. No project file can import a host
|
|
2478
2496
|
extension or choose its npm package.
|
|
2479
2497
|
|
|
2480
|
-
Stored short links
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
no longer has a native `link`
|
|
2498
|
+
Stored short links moved out of core this way too: a `urlcode-dynamic-link`
|
|
2499
|
+
package (mount-based, like `auth`/`admin`) owned the durable link store, its CLI
|
|
2500
|
+
and management API. That package has since been retired and unpublished, so no
|
|
2501
|
+
supported stored-link extension ships today. Core no longer has a native `link`
|
|
2502
|
+
handler or a `dynamicLinks` project flag.
|
|
2484
2503
|
|
|
2485
2504
|
A project declares versioned configuration and exclusive route mounts:
|
|
2486
2505
|
|
|
@@ -2727,7 +2746,16 @@ each half of a hook's `sandbox: true` opt-in has its own answer:
|
|
|
2727
2746
|
or provided: this is ordinary first-party project code, and the
|
|
2728
2747
|
extension's own `activate()` already has `ExtensionActivation.root` to
|
|
2729
2748
|
resolve the hook's `source` against and can `import()` it directly, the
|
|
2730
|
-
same way any trusted `function`/`middleware` route does.
|
|
2749
|
+
same way any trusted `function`/`middleware` route does. Do that import
|
|
2750
|
+
with a per-activation cache-busting query, the way core's own trusted
|
|
2751
|
+
route activation does (`src/trusted-functions.ts`): Node's ESM loader
|
|
2752
|
+
caches a resolved module forever by URL, so a plain `import()` of the
|
|
2753
|
+
unchanged file URL makes a second activation in the same process keep
|
|
2754
|
+
serving the hook code that was on disk at the first one
|
|
2755
|
+
(jimhoyd-com/urlcode#198). Only the hook's **entry** module is refreshed
|
|
2756
|
+
this way — modules the hook itself imports stay on Node's module cache,
|
|
2757
|
+
the same limitation the trusted route path has, so a change to a hook's
|
|
2758
|
+
own dependency still needs a process restart.
|
|
2731
2759
|
- **Sandboxed (`sandbox: true`).** `@jimhoyd/urlcode/sandbox` exports
|
|
2732
2760
|
`SandboxPool`, the same QuickJS/worker-thread engine that backs a
|
|
2733
2761
|
sandboxed `function`/`middleware` route — the identical module-allowlist
|
|
@@ -2914,6 +2942,35 @@ API is all it has, so its binding grant *is* effectively its whole reach into
|
|
|
2914
2942
|
the environment. Trusted code's reach is not bounded that way; treat the
|
|
2915
2943
|
grant as scoping `context`, not as scoping the process.
|
|
2916
2944
|
|
|
2945
|
+
### Migrating to the trusted default
|
|
2946
|
+
|
|
2947
|
+
If you are upgrading a project from a release before this change shipped:
|
|
2948
|
+
**every existing `function` and `middleware` route silently changes execution
|
|
2949
|
+
mode**, from sandboxed to trusted, unless it already has (or you add)
|
|
2950
|
+
`sandbox: true`. This is a real behavior change on upgrade, not a
|
|
2951
|
+
documentation update — a route that used to run with no filesystem or network
|
|
2952
|
+
access will, after the upgrade, run with full Node access unless you opt it
|
|
2953
|
+
back into the sandbox.
|
|
2954
|
+
|
|
2955
|
+
Before upgrading:
|
|
2956
|
+
|
|
2957
|
+
- List every `function` and `middleware` route in the project.
|
|
2958
|
+
- For each one, decide whether you fully trust that code to run in-process
|
|
2959
|
+
with full Node/filesystem/network access — the same trust you would extend
|
|
2960
|
+
to any other code you deploy to that server.
|
|
2961
|
+
- Add `sandbox: true` explicitly to any route whose code you do not fully
|
|
2962
|
+
trust, that processes input from a source you don't control, or that handles
|
|
2963
|
+
a secret binding you want isolated — before you upgrade, not after.
|
|
2964
|
+
- Routes you do want running trusted need no change; that is now the default.
|
|
2965
|
+
|
|
2966
|
+
The change moves the sandbox from an unconditional guarantee to an explicit,
|
|
2967
|
+
per-route choice, mainly for performance: the previous blanket sandbox capped
|
|
2968
|
+
concurrency at two workers with no queue shared across every function route on
|
|
2969
|
+
the server, which does not scale to real concurrent traffic. It also brings
|
|
2970
|
+
first-party code in line with how the rest of the Node ecosystem treats
|
|
2971
|
+
deployed application code. The sandbox itself is unchanged for routes that opt
|
|
2972
|
+
into it; only the default for routes that declare neither option has changed.
|
|
2973
|
+
|
|
2917
2974
|
### What "sandboxed" (`sandbox: true`) still guarantees
|
|
2918
2975
|
|
|
2919
2976
|
- Function sources are parsed/snapshotted without importing them into Node.
|
|
@@ -2990,6 +3047,21 @@ it is handed *through `context`*. It does not change what the code can go get
|
|
|
2990
3047
|
on its own once it is running; see "binding grants are unaffected" above for
|
|
2991
3048
|
that distinction.
|
|
2992
3049
|
|
|
3050
|
+
### Trusted code, not trusted requests
|
|
3051
|
+
|
|
3052
|
+
"Trusted" describes the code's authorship — first-party project code you
|
|
3053
|
+
reviewed and deployed — not the requests it handles. Every request, in either
|
|
3054
|
+
mode, still carries client-controlled path, query, header and body data that is
|
|
3055
|
+
exactly as adversarial as it always was. Running trusted means that code
|
|
3056
|
+
executes with full Node access if it mishandles that input; it does not mean
|
|
3057
|
+
the input itself became safe to trust. Declare `parameters` and `request.body`
|
|
3058
|
+
validation in YAML, check `args` and any other request data again inside
|
|
3059
|
+
function/middleware code, and implement your own authentication and
|
|
3060
|
+
authorization — no route, sandboxed or trusted, adds automatic auth.
|
|
3061
|
+
`sandbox: true` narrows what a bug or an unreviewed dependency in the *code*
|
|
3062
|
+
can do with that same request data; it is not a substitute for validating or
|
|
3063
|
+
authenticating the request itself.
|
|
3064
|
+
|
|
2993
3065
|
### Granting selected bindings
|
|
2994
3066
|
|
|
2995
3067
|
An application may request a named binding in YAML, but only an operator can
|
package/llms.txt
CHANGED
|
@@ -67,10 +67,11 @@ fields or bypass target limits or operator grants. See [the design principle](do
|
|
|
67
67
|
|
|
68
68
|
## Extensions (accounts, administration, presentation)
|
|
69
69
|
- [Extensions](docs/EXTENSIONS.md): `extensions.<name>` blocks, `extension` mounts, `policies.extensions`, the operator host file, `@jimhoyd/urlcode/extensions`. Project-level lifecycle hooks run trusted via plain `import()`, or sandboxed via `@jimhoyd/urlcode/sandbox`'s `SandboxPool`.
|
|
70
|
-
- [urlcode-auth](
|
|
71
|
-
- [urlcode-admin](
|
|
72
|
-
- [urlcode-ui](
|
|
73
|
-
- urlcode-
|
|
70
|
+
- [urlcode-auth](packages/auth): npm: @jimhoyd/urlcode-auth; accounts, sessions, MFA, roles, account page; its own llms.txt. Lives in this repository as a workspace package.
|
|
71
|
+
- [urlcode-admin](packages/admin): npm: @jimhoyd/urlcode-admin; users, sessions, roles, audit, cases; its own llms.txt. Lives in this repository as a workspace package.
|
|
72
|
+
- [urlcode-ui](packages/ui): npm: @jimhoyd/urlcode-ui; escaped templates, shadcn/ui partials, themes, translations; its own llms.txt. Lives in this repository as a workspace package, not in a separate repository.
|
|
73
|
+
- urlcode-middleware (retired): per-route middleware briefly lived here as an extension-seam alternative; the package was unpublished at 0.1.0-alpha.2 and its repository deleted. This is not a gap — per-route middleware is native to core via the `middleware:` array (docs/MIDDLEWARE.md); use that.
|
|
74
|
+
- urlcode-dynamic-link (retired): stored short links briefly lived here as a mount-based extension; the package was unpublished and its repository deleted. Core has no native `link` handler and no supported extension provides stored links — report them as a gap.
|
|
74
75
|
- [Plugins](docs/PLUGINS.md): host hook API in operator code, never named in YAML.
|
|
75
76
|
|
|
76
77
|
## Tooling and API
|