@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.
Files changed (115) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +17 -19
  2. package/.claude/skills/urlcode-operations/SKILL.md +9 -9
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CONTRIBUTING.md +20 -16
  5. package/README.md +44 -54
  6. package/ROADMAP.md +61 -376
  7. package/dist/BUILD-MANIFEST.json +15 -15
  8. package/dist/agents-guide.js +6 -6
  9. package/dist/build-static.js +2 -0
  10. package/dist/cli.js +3 -3
  11. package/dist/explain-cli.js +4 -2
  12. package/dist/explain.js +7 -1
  13. package/dist/extensions.js +1 -1
  14. package/dist/manifest.js +7 -1
  15. package/dist/mcp-authoring.js +2 -2
  16. package/dist/mcp.js +1 -1
  17. package/dist/policies.js +3 -1
  18. package/dist/prerender.js +4 -0
  19. package/dist/readiness.js +3 -0
  20. package/dist/route-diff.js +12 -5
  21. package/dist/types/explain.d.ts +6 -0
  22. package/dist/types/extensions.d.ts +1 -1
  23. package/dist/types/manifest.d.ts +5 -1
  24. package/dist/types/readiness.d.ts +2 -0
  25. package/dist/types/types.d.ts +8 -1
  26. package/dist/types.js +8 -1
  27. package/dist/typescript-authoring.js +18 -7
  28. package/docs/AI-AUTHORING.md +10 -6
  29. package/docs/ASSETS.md +2 -1
  30. package/docs/AUTH-BACKUP.md +32 -0
  31. package/docs/BEST-PRACTICES.md +17 -9
  32. package/docs/CAPABILITIES.md +1 -1
  33. package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
  34. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
  35. package/docs/CI.md +8 -3
  36. package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
  37. package/docs/DEVELOPMENT-PIPELINE.md +181 -0
  38. package/docs/EXTENSIONS.md +15 -5
  39. package/docs/FRAMEWORK.md +22 -18
  40. package/docs/FUNCTION-SECURITY.md +44 -0
  41. package/docs/LOCAL-DEVELOPMENT.md +3 -0
  42. package/docs/OPEN-DECISIONS.md +184 -212
  43. package/docs/OPERATIONAL-PROOF.md +3 -3
  44. package/docs/OPERATIONS.md +3 -3
  45. package/docs/POLICIES.md +13 -5
  46. package/docs/PRERENDER.md +23 -11
  47. package/docs/PROJECT-DIRECTION.md +3 -3
  48. package/docs/READINESS.md +6 -2
  49. package/docs/README.md +18 -41
  50. package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
  51. package/docs/RELEASE-READINESS.md +6 -6
  52. package/docs/RELEASE-SECURITY.md +68 -168
  53. package/docs/RESILIENCE.md +3 -3
  54. package/docs/ROUTING.md +3 -4
  55. package/docs/SECURITY-AUDIT.md +2 -2
  56. package/docs/SPECIFICATION.md +7 -5
  57. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
  58. package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
  59. package/docs/SPIKE-CORE-LAYERING.md +48 -17
  60. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
  61. package/docs/SPIKE-LAMBDA-COMPILE.md +8 -0
  62. package/docs/STATIC.md +14 -3
  63. package/docs/TOOLING.md +4 -3
  64. package/docs/TYPESCRIPT-AUTHORING.md +6 -1
  65. package/docs/VERSION-ALIGNMENT.md +46 -205
  66. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
  67. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +102 -0
  68. package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +15 -5
  69. package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +15 -3
  70. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
  71. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
  72. package/docs/archive/2026-09-19/ROADMAP.md +386 -0
  73. package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +10 -0
  74. package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
  75. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
  76. package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
  77. package/docs/archive/README.md +27 -0
  78. package/docs/policies/agents.md +1 -1
  79. package/docs/policies/compression.md +3 -2
  80. package/docs/policies/security.md +3 -2
  81. package/examples/assets/example.yaml +1 -1
  82. package/examples/cookbook/route-index.json +1 -1
  83. package/examples/cookbook/routes/middleware.yaml +1 -1
  84. package/examples/prerender/README.md +14 -6
  85. package/examples/prerender/functions/page.mjs +4 -2
  86. package/examples/prerender/middleware/template.mjs +1 -1
  87. package/examples/prerender/prerender.mjs +1 -1
  88. package/examples/prerender/urlcode.yaml +8 -4
  89. package/llms-full.txt +117 -45
  90. package/llms.txt +5 -4
  91. package/package.json +24 -4
  92. package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
  93. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +17 -19
  94. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +9 -9
  95. package/recipes/authenticated-json-api/README.md +4 -3
  96. package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
  97. package/recipes/authenticated-json-api/recipe.yaml +1 -1
  98. package/recipes/contact-form/functions/contact.mjs +2 -1
  99. package/recipes/contact-form/recipe.yaml +2 -2
  100. package/recipes/cors-api/README.md +2 -2
  101. package/recipes/cors-api/recipe.yaml +1 -1
  102. package/recipes/health-page/README.md +1 -1
  103. package/recipes/json-api/README.md +1 -1
  104. package/recipes/json-api/recipe.yaml +3 -3
  105. package/recipes/middleware/README.md +8 -4
  106. package/recipes/protected-download/README.md +1 -1
  107. package/recipes/protected-download/recipe.yaml +1 -1
  108. package/recipes/static-plus-api/README.md +2 -2
  109. package/recipes/static-plus-api/public/index.html +1 -1
  110. package/recipes/static-plus-api/recipe.yaml +1 -1
  111. package/recipes/static-plus-api/urlcode.yaml +1 -1
  112. package/recipes/typescript/recipe.yaml +4 -4
  113. package/skills/urlcode/SKILL.md +6 -6
  114. package/starters/default/AGENTS.md +6 -6
  115. 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.
@@ -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 the sandbox, for every result a route produces: `respond`
5
- bodies, function results, asset responses, cache hits and early denials.
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 the
4
- sandbox, to every result a route produces: redirects, `respond` bodies,
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 sandboxed function shows the two kinds of route side by side
17
+ - one trusted function shows the two kinds of route side by side
@@ -367,7 +367,7 @@
367
367
  "bindings",
368
368
  "auth"
369
369
  ],
370
- "description": "HTTP Basic authentication decoded inside the guest"
370
+ "description": "HTTP Basic authentication decoded in trusted middleware"
371
371
  },
372
372
  {
373
373
  "path": "/cors/data",
@@ -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 inside the guest
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
- native `page` routes and no sandbox on the request path.
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 runs function and middleware code per
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
- | Guest execution per request | yes | none |
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 that runs in Node, not guest code. The
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 QuickJS/WASM sandbox with no filesystem, network or host
3
- // code, at build time exactly as it would at request time.
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 guest code runs to serve this page.</footer>
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
- // guest execution on the request path.
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 sandbox on the request path.</p>
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. Guest code never reads the
36
- filesystem, so a larger site generates this project from host-prepared
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 47,012 tokens (estimate: characters / 4). -->
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 future `urlcode-dynamic-link` package (not yet
104
- published, so this one line is the exception to "nothing is roadmap" above)
105
- is expected to own them the same way `auth`/`admin` own their mounts.
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; the extension repositories'
122
- `pack-sources.mjs` still builds local tarballs from a reviewed checkout for
123
- operators who install only source they have read. Three files make an extended
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 wires the packages, once, in code the project cannot reach:
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 pages currently render through the shared primitives and a
176
- `presentation` (catalogue and `--ui-*` theme variables). The `ui` extension
177
- serves the template kit and its assets for pages an extension renders through
178
- `ui.kit`; moving the auth and admin screens onto the kit is a listed remaining
179
- item in the ui repository, so today one `presentation` restyles them and the
180
- `ui` block is optional.
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 never names code, packages, databases or credentials.** Extensions are
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. Declare `policies.extensions.auth`
219
- on the route; the runtime withholds `Cookie` and `Authorization` from guests.
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 (moving to a future `urlcode-dynamic-link` extension package, not yet published) |
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` surface it next to the
491
- route's `sandbox` boolean, so the trust decision has a reviewable trail
492
- without reading every route's source file:
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. Report a request for live stored links as a gap pending the future
574
- `urlcode-dynamic-link` extension package rather than inventing a `link` field.
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. Stored short links are moving to a future `urlcode-dynamic-link`
1259
- extension package (mount-based, like `auth`/`admin`), not yet published. A
1260
- project needing them declares an `extension` mount once that package exists;
1261
- there is no in-core replacement or deprecation shim for `link`/`dynamicLinks`.
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. [Provider conformance](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PROVIDER-VERIFICATION.md)
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. Stored short links are moving to a future
1543
- `urlcode-dynamic-link` extension package (mount-based, like `auth`/`admin`,
1544
- not yet published); a project needing them declares an `extension` mount once
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 sandboxed code.
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 the sandbox, so they can see the client address, keep counters
1906
- across requests and touch the transport, which route middleware by design
1907
- cannot ([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)).
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). `cache: {strategy, maxAge, ...}` expands to
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 are moving out of core this way too: a future
2481
- `urlcode-dynamic-link` package (mount-based, like `auth`/`admin`, not yet
2482
- published) will own the durable link store, its CLI and management API. Core
2483
- no longer has a native `link` handler or a `dynamicLinks` project flag.
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](https://github.com/jimhoyd-com/urlcode-auth): npm: @jimhoyd/urlcode-auth; accounts, sessions, MFA, roles, account page; its own llms.txt.
71
- - [urlcode-admin](https://github.com/jimhoyd-com/urlcode-admin): npm: @jimhoyd/urlcode-admin; users, sessions, roles, audit, cases; its own llms.txt.
72
- - [urlcode-ui](https://github.com/jimhoyd-com/urlcode-ui): npm: @jimhoyd/urlcode-ui; escaped templates, shadcn/ui partials, themes, translations; its own llms.txt.
73
- - urlcode-dynamic-link (planned, not yet published): stored short links move here as a mount-based extension; core no longer has a native `link` handler.
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