@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
@@ -0,0 +1,181 @@
1
+ # Development and release pipeline
2
+
3
+ The repository uses npm workspaces, independent package versions, and Changesets
4
+ for UI, auth and admin. Core remains at the repository root and is explicitly
5
+ included in the shared release inventory. Moving it is not required to use the
6
+ same release checks and coordinator.
7
+
8
+ ## Pull requests
9
+
10
+ Every PR starts `verify`. A complete Git diff selects one of two lanes:
11
+
12
+ - **Prose:** root project Markdown, `docs/**/*.md`, `llms.txt` and
13
+ `llms-full.txt` changes run guidance/generated-resource checks, runtime audit,
14
+ and the required container job. CodeQL retains its repository policy.
15
+ - **Full:** all other changes, mixed changes, empty/unavailable diffs and main
16
+ pushes run static checks once and core and workspace suites separately. Both
17
+ suites retain Linux on Node 22/24/26. Main adds Windows/macOS on Node 24.
18
+ PRs add those platform legs for runtime, CLI, SQLite, fixture, dependency,
19
+ workflow and unknown changes; known UI presentation-only changes omit them. Package, action,
20
+ cookbook, reproducibility and operational checks retain their coverage.
21
+
22
+ The prose allowlist is deliberately narrow. Package documentation, skills,
23
+ starters, examples, schemas, manifests and workflow changes select full checks.
24
+ A rename from source into docs also selects full checks. No required workflow
25
+ uses `paths-ignore`.
26
+
27
+ `verify-complete` accepts only the results specified by the successful plan.
28
+ Failed, canceled, missing or unexpectedly skipped work fails the gate. Required
29
+ check names (`verify-complete`, `container`) and CodeQL enforcement are preserved.
30
+ The repository ruleset currently does not require a branch to be up to date;
31
+ release publication separately requires verification of the exact main commit.
32
+
33
+ ```sh
34
+ npm run check:docs # prose checks without the runtime suite
35
+ npm run ci:plan -- BASE_SHA HEAD_SHA
36
+ npm run ci:report -- RUN_ID # read GitHub job/step durations
37
+ npm run ci:history -- 100 2026-09-19 # group historical timing samples
38
+ npm run verify # full local validation remains available
39
+ npm run test:package # builds and installs a real archive
40
+ ```
41
+
42
+ CI uses `test:package:built` only after building in that same job. Core tests and
43
+ workspace tests run in separate jobs to shorten their serial critical path;
44
+ this increases job setup overhead and needs monitoring for runner queue pressure.
45
+ After building all three extensions, the workspace job also runs the real
46
+ `init --with auth,admin,ui` scaffold integration. Missing workspace outputs fail
47
+ instead of silently skipping an absent external checkout.
48
+ The [audit](CI-RELEASE-AUDIT-2026-09-19.md) records the previous timings.
49
+ The [follow-up measurements](CI-FOLLOWUP-2026-09-19.md) record the first compact
50
+ main result and explain why the new lanes still need 20 organic runs each.
51
+
52
+ ## Version and release ownership
53
+
54
+ A feature PR records release intent in a Changeset for a changed workspace
55
+ package. Review dependency/peer changes explicitly. Keep pre-mode enabled until
56
+ an explicit decision to leave alpha. Do not force all packages to one version.
57
+ Core version bumps remain explicit in the release PR, including its CLI banner.
58
+ `release:check` verifies every manifest against its lockfile entry and checks the
59
+ alpha-mode policy. Existing CLI tests catch core banner/version disagreement.
60
+
61
+ A release PR collects version/changelog and lockfile changes together. Ordinary
62
+ unreleased development does not move existing version tags or npm versions.
63
+ Use the exact release commit after its full platform checks have passed. Routine
64
+ main builds use five OS/Node combinations per suite (ten jobs total), rather
65
+ than the full nine per suite (eighteen). Nightly runs at 07:17 UTC and manual
66
+ runs retain all three operating systems on all three Node versions. Before a
67
+ release, run `gh workflow run ci.yml --ref main` and wait for that exact commit's
68
+ full run to succeed. A successful compact main run alone cannot authorize a
69
+ release. Main pushes do not cancel scheduled/manual verification.
70
+
71
+ Inspect release state:
72
+
73
+ ```sh
74
+ npm run release:status # registry channels, peer compatibility, remote tag SHAs
75
+ npm run release:plan # read-only JSON inventory, including root core
76
+ npm run release:run # read-only ordered proposal at HEAD
77
+ ```
78
+
79
+ All three commands require network access; none publish. `release:run` selects
80
+ unpublished manifest versions and releases already tagged at HEAD (for resuming
81
+ partial completion). Its order is core, UI, auth, admin, skipping other already
82
+ published versions. Plan fields come from package manifests, not copied versions
83
+ in another config file.
84
+
85
+ Once the release itself is authorized, from a clean checkout of that exact SHA:
86
+
87
+ ```sh
88
+ npm run release:run -- --execute
89
+ ```
90
+
91
+ This opt-in command creates missing tags through GitHub, refuses an existing tag
92
+ at a different commit, and waits for each package's tag-triggered release before
93
+ starting the next. It stops on failure or missing npm publication. Do not push
94
+ all release tags at once: publication workflows share one concurrency group and
95
+ GitHub may replace pending runs. The coordinator intentionally starts one at a
96
+ time. It never merges a PR, bypasses checks, force-pushes or publishes locally.
97
+
98
+ The coordinator needs `gh` authentication with repository Contents write and
99
+ Actions read; reading checks also needs Checks read. It is intended for an
100
+ authorized maintainer session or a repository-scoped GitHub App. A fine-grained
101
+ PAT can serve a short-lived maintainer script with those permissions, but must
102
+ have no ruleset bypass. The workflow `GITHUB_TOKEN` should not be used to create
103
+ these trigger tags: its push events do not start another ordinary push workflow.
104
+ For long-lived automation prefer a GitHub App; do not add a bypass to `main`.
105
+ [GitHub App guidance](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/deciding-when-to-build-a-github-app)
106
+
107
+ ## Publication and recovery
108
+
109
+ The four workflow filenames remain unchanged because npm trusted publishing
110
+ names them. They call shared helpers for identity, preflight, peer installation,
111
+ retry handling and publication. npm authentication remains OIDC; no npm token
112
+ is introduced. Core candidate and release share `prepare-core-release.sh`.
113
+ For an authorized release, first dispatch the manual `candidate.yml` workflow on
114
+ the selected main commit. It extends the core candidate with UI/auth/admin
115
+ archives and verifies all four together in an isolated temporary consumer:
116
+ peer compatibility, installed versions, public imports and real scaffold
117
+ composition. Its signed `train.json` records the proposed archives and integrity.
118
+ A candidate does not publish, validate live providers or prove registry OIDC;
119
+ release workflows still prepare and retain their own immutable retry artifacts.
120
+ Extensions share `prepare-extension-release.sh` and test published peer floors.
121
+ Auth/admin build in the workspace for packaging, then build and run their suites
122
+ in a temporary copy outside the monorepo against exact registry peer floors.
123
+ This preserves #184’s isolation fix; npm `--prefix` is not an isolation boundary.
124
+
125
+ Preflight checks the checkout SHA, main ancestry, a successful exact-SHA full
126
+ `ci.yml` nightly or explicit manual run, CodeQL, remote tag SHA, npm
127
+ channel monotonicity and published peer floors. When a full run was canceled,
128
+ run `verify` manually at the selected tag/ref, then rerun the failed release;
129
+ never substitute another commit's passing run or move the tag.
130
+
131
+ Prepared artifacts are retained for 90 days before publication. A rerun of the
132
+ same workflow run restores those original bytes and skips preparation. npm
133
+ versions already present must have identical SHA-512 integrity; existing GitHub
134
+ assets must match byte for byte. Different bytes stop the release. An absent or
135
+ expired artifact requires reconstruction that still passes these comparisons;
136
+ if it cannot, diagnose and create a new version rather than overwrite history.
137
+
138
+ GitHub release classification follows the manifest's prerelease status. New
139
+ GitHub releases use `--latest=false`; stable latest promotion is a separate
140
+ maintainer decision, avoiding accidental promotion by a package-level release.
141
+ GHCR updates the derived channel (`alpha` for alphas, `latest` for stable) and
142
+ preserves existing version images only when their source label matches. An
143
+ existing image/channel without the required labels fails closed and needs a
144
+ reviewed migration; this change does not silently relabel old images.
145
+
146
+ Partial npm/GitHub/GHCR success is possible; those systems cannot be updated
147
+ atomically. Rerun the original failed run, check its summary and then rerun the
148
+ coordinator. A changed source commit requires a new version and tag. Failed
149
+ OIDC configuration needs correction on npm, not tag deletion. A green dry run
150
+ cannot prove registry-side OIDC trust; each package's first authorized publish
151
+ must verify it.
152
+
153
+ Historical tags, GitHub release flags and npm channels are not retroactively
154
+ rewritten by these scripts. Use `release:status` to inspect them. The active [Immutable release tags rule](https://github.com/jimhoyd-com/urlcode/rules/23712319)
155
+ prohibits update/deletion of `v*` and `@jimhoyd/urlcode-*@*` tags, permits
156
+ creation, and has no bypass actors. Its reviewed configuration is tracked in
157
+ `.github/rulesets/release-tags.json`. Main protection is unchanged.
158
+
159
+ During the September 19 alignment, GitHub releases `v0.4.0-alpha.1` and
160
+ `v0.4.0-alpha.2` were explicitly marked as prereleases and GitHub latest was
161
+ restored to `v0.3.0`, matching npm. Their tags and artifact bytes were unchanged.
162
+ Further performance and release-train validation is tracked in
163
+ [issue #185](https://github.com/jimhoyd-com/urlcode/issues/185).
164
+
165
+ ### Windows fixture cleanup
166
+
167
+ Auth/admin tests register resources with their package-local `test/cleanup.ts`.
168
+ Cleanup runs in reverse acquisition order: close servers and SQLite services
169
+ before deleting temporary directories, including services reopened by a test.
170
+ Every registered callback is attempted even if another closer throws, and the
171
+ combined error fails the test. Register each closer as soon as its resource opens.
172
+ The suites use a five-minute test-file timeout so a stuck fixture is diagnosed
173
+ before the CI job limit. Node applies this limit to whole test files too; the
174
+ large auth-core file legitimately exceeds two minutes on Windows Node 22.
175
+ Windows regression coverage runs on Node 24 for platform-sensitive PRs and
176
+ main; nightly/manual runs cover Node 22/24/26.
177
+
178
+ Failed auth service initialization also waits for its SQLite worker to terminate
179
+ before rejecting. Callers can clean up or retry after a rejected open without
180
+ racing a remaining database handle; configuration identity failures still fail
181
+ closed with the same error code.
@@ -6,10 +6,11 @@ and admin implementations live in `urlcode-auth` and `urlcode-admin`; the runtim
6
6
  supplies only the generic integration contract. No project file can import a host
7
7
  extension or choose its npm package.
8
8
 
9
- Stored short links are moving out of core this way too: a future
10
- `urlcode-dynamic-link` package (mount-based, like `auth`/`admin`, not yet
11
- published) will own the durable link store, its CLI and management API. Core
12
- no longer has a native `link` handler or a `dynamicLinks` project flag.
9
+ Stored short links moved out of core this way too: a `urlcode-dynamic-link`
10
+ package (mount-based, like `auth`/`admin`) owned the durable link store, its CLI
11
+ and management API. That package has since been retired and unpublished, so no
12
+ supported stored-link extension ships today. Core no longer has a native `link`
13
+ handler or a `dynamicLinks` project flag.
13
14
 
14
15
  A project declares versioned configuration and exclusive route mounts:
15
16
 
@@ -256,7 +257,16 @@ each half of a hook's `sandbox: true` opt-in has its own answer:
256
257
  or provided: this is ordinary first-party project code, and the
257
258
  extension's own `activate()` already has `ExtensionActivation.root` to
258
259
  resolve the hook's `source` against and can `import()` it directly, the
259
- same way any trusted `function`/`middleware` route does.
260
+ same way any trusted `function`/`middleware` route does. Do that import
261
+ with a per-activation cache-busting query, the way core's own trusted
262
+ route activation does (`src/trusted-functions.ts`): Node's ESM loader
263
+ caches a resolved module forever by URL, so a plain `import()` of the
264
+ unchanged file URL makes a second activation in the same process keep
265
+ serving the hook code that was on disk at the first one
266
+ (jimhoyd-com/urlcode#198). Only the hook's **entry** module is refreshed
267
+ this way — modules the hook itself imports stay on Node's module cache,
268
+ the same limitation the trusted route path has, so a change to a hook's
269
+ own dependency still needs a process restart.
260
270
  - **Sandboxed (`sandbox: true`).** `@jimhoyd/urlcode/sandbox` exports
261
271
  `SandboxPool`, the same QuickJS/worker-thread engine that backs a
262
272
  sandboxed `function`/`middleware` route — the identical module-allowlist
package/docs/FRAMEWORK.md CHANGED
@@ -56,9 +56,9 @@ Each rung's YAML is valid on every rung above it.
56
56
  project copy, template and stylesheet overrides for kit-rendered pages.
57
57
 
58
58
  Stored short links previously sat here as a native `link` route; that handler
59
- was removed from core. A future `urlcode-dynamic-link` package (not yet
60
- published, so this one line is the exception to "nothing is roadmap" above)
61
- is expected to own them the same way `auth`/`admin` own their mounts.
59
+ was removed from core. A `urlcode-dynamic-link` package owned them the same way
60
+ `auth`/`admin` own their mounts, but it has been retired and unpublished; no
61
+ package occupies this rung today.
62
62
 
63
63
  Rungs 1 to 3 need only the core package. Rungs 4 to 6 need the extension
64
64
  packages, installed from npm as `0.1.0-alpha.x` prereleases, and a Node host
@@ -74,9 +74,9 @@ npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/
74
74
  urlcode init my-site --with auth,admin
75
75
  ```
76
76
 
77
- Installing from npm is the normal path; the extension repositories'
78
- `pack-sources.mjs` still builds local tarballs from a reviewed checkout for
79
- operators who install only source they have read. Three files make an extended
77
+ Installing from npm is the normal path; `scripts/pack-sources.mjs` still builds
78
+ local tarballs from a reviewed checkout for operators who install only source
79
+ they have read one revision now covers core and every extension. Three files make an extended
80
80
  project. Nothing else is discovered by convention.
81
81
 
82
82
  ```
@@ -108,7 +108,8 @@ routes:
108
108
  auth: {}
109
109
  ```
110
110
 
111
- The operator host wires the packages, once, in code the project cannot reach:
111
+ The operator host explicitly registers the packages. Registration is an
112
+ activation boundary; it does not isolate trusted application code from the host:
112
113
 
113
114
  ```js
114
115
  import { createUiExtension } from '@jimhoyd/urlcode-ui/host';
@@ -128,12 +129,12 @@ export default {
128
129
  };
129
130
  ```
130
131
 
131
- Auth and admin pages currently render through the shared primitives and a
132
- `presentation` (catalogue and `--ui-*` theme variables). The `ui` extension
133
- serves the template kit and its assets for pages an extension renders through
134
- `ui.kit`; moving the auth and admin screens onto the kit is a listed remaining
135
- item in the ui repository, so today one `presentation` restyles them and the
136
- `ui` block is optional.
132
+ Auth and admin already render their screens through `ui.kit` when the host
133
+ supplies the UI extension; their package-owned templates and catalogues must be
134
+ registered with that kit. Without it, both retain shared primitive rendering
135
+ through `presentation`. The example above uses that primitive fallback. The UI
136
+ block is optional, and a migration to the kit is not unfinished framework work.
137
+ See each package README for its complete kit registration example.
137
138
 
138
139
  ```sh
139
140
  urlcode serve --project /absolute/site --host-file /absolute/operator/host.mjs --origin https://site.example
@@ -155,7 +156,8 @@ or mounts changes the revision and needs an explicit operator reapproval.
155
156
  These are the facts that keep generated projects valid. The full matrix is in
156
157
  [AI authoring](AI-AUTHORING.md); this is the short list.
157
158
 
158
- - **YAML never names code, packages, databases or credentials.** Extensions are
159
+ - **Extension YAML names logical extensions, not host packages or credentials.**
160
+ Function and middleware `source` fields do name project modules. Extensions are
159
161
  logical names; the host file chooses the implementation. There is no
160
162
  `--extension` flag, no `import` in YAML, no interpolation.
161
163
  - **One handler per route.** `redirect`, `respond`, `page`, `static`, `download`,
@@ -166,13 +168,15 @@ These are the facts that keep generated projects valid. The full matrix is in
166
168
  It runs in-process with full Node access unless the route declares
167
169
  `sandbox: true`, which isolates it to a text/JSON `Request`/`Response`
168
170
  subset, validated `args` and granted `env`, with no `fetch`, Node,
169
- filesystem or timers. Either way, `args`/`env`/`secrets` are exactly what
171
+ filesystem or general network access; bounded timers are available. Either way, `args`/`env`/`secrets` are exactly what
170
172
  the route declares and an operator grants — trust changes where code runs,
171
173
  not what it is handed. See docs/SPIKE-DEFAULT-TRUST-MODEL.md and
172
174
  docs/FUNCTION-SECURITY.md.
173
175
  - **Authentication is host processing.** Do not build login forms, session
174
- cookies or password checks in functions. Declare `policies.extensions.auth`
175
- on the route; the runtime withholds `Cookie` and `Authorization` from guests.
176
+ cookies or password checks in functions. With the auth extension declared,
177
+ prefer `auth: true` or `auth: {role: admin}`; these expand to
178
+ `policies.extensions.auth`. The runtime filters credential headers passed to
179
+ application handlers. This is not a security boundary against trusted Node code.
176
180
  - **Everything is validated before it runs.** `urlcode validate --local`,
177
181
  `urlcode test`, `urlcode audit --expect-routes N`. Unsupported features fail
178
182
  with the route named; nothing degrades silently.
@@ -194,5 +198,5 @@ These are the facts that keep generated projects valid. The full matrix is in
194
198
  | 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) |
195
199
  | Add administration | [admin README](https://github.com/jimhoyd-com/urlcode-admin#readme) |
196
200
  | 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) |
197
- | Write an extension | [extensions](EXTENSIONS.md), [extension model review](SPIKE-EXTENSION-MODEL.md) |
201
+ | Write an extension | [extensions](EXTENSIONS.md), [extension model review](archive/2026-09-19/SPIKE-EXTENSION-MODEL.md) |
198
202
  | Run it | [operations](OPERATIONS.md), [install](INSTALL.md), [deployment checks](DEPLOYMENT-CHECKS.md) |
@@ -40,6 +40,35 @@ API is all it has, so its binding grant *is* effectively its whole reach into
40
40
  the environment. Trusted code's reach is not bounded that way; treat the
41
41
  grant as scoping `context`, not as scoping the process.
42
42
 
43
+ ## Migrating to the trusted default
44
+
45
+ If you are upgrading a project from a release before this change shipped:
46
+ **every existing `function` and `middleware` route silently changes execution
47
+ mode**, from sandboxed to trusted, unless it already has (or you add)
48
+ `sandbox: true`. This is a real behavior change on upgrade, not a
49
+ documentation update — a route that used to run with no filesystem or network
50
+ access will, after the upgrade, run with full Node access unless you opt it
51
+ back into the sandbox.
52
+
53
+ Before upgrading:
54
+
55
+ - List every `function` and `middleware` route in the project.
56
+ - For each one, decide whether you fully trust that code to run in-process
57
+ with full Node/filesystem/network access — the same trust you would extend
58
+ to any other code you deploy to that server.
59
+ - Add `sandbox: true` explicitly to any route whose code you do not fully
60
+ trust, that processes input from a source you don't control, or that handles
61
+ a secret binding you want isolated — before you upgrade, not after.
62
+ - Routes you do want running trusted need no change; that is now the default.
63
+
64
+ The change moves the sandbox from an unconditional guarantee to an explicit,
65
+ per-route choice, mainly for performance: the previous blanket sandbox capped
66
+ concurrency at two workers with no queue shared across every function route on
67
+ the server, which does not scale to real concurrent traffic. It also brings
68
+ first-party code in line with how the rest of the Node ecosystem treats
69
+ deployed application code. The sandbox itself is unchanged for routes that opt
70
+ into it; only the default for routes that declare neither option has changed.
71
+
43
72
  ## What "sandboxed" (`sandbox: true`) still guarantees
44
73
 
45
74
  - Function sources are parsed/snapshotted without importing them into Node.
@@ -116,6 +145,21 @@ it is handed *through `context`*. It does not change what the code can go get
116
145
  on its own once it is running; see "binding grants are unaffected" above for
117
146
  that distinction.
118
147
 
148
+ ## Trusted code, not trusted requests
149
+
150
+ "Trusted" describes the code's authorship — first-party project code you
151
+ reviewed and deployed — not the requests it handles. Every request, in either
152
+ mode, still carries client-controlled path, query, header and body data that is
153
+ exactly as adversarial as it always was. Running trusted means that code
154
+ executes with full Node access if it mishandles that input; it does not mean
155
+ the input itself became safe to trust. Declare `parameters` and `request.body`
156
+ validation in YAML, check `args` and any other request data again inside
157
+ function/middleware code, and implement your own authentication and
158
+ authorization — no route, sandboxed or trusted, adds automatic auth.
159
+ `sandbox: true` narrows what a bug or an unreviewed dependency in the *code*
160
+ can do with that same request data; it is not a substitute for validating or
161
+ authenticating the request itself.
162
+
119
163
  ## Granting selected bindings
120
164
 
121
165
  An application may request a named binding in YAML, but only an operator can
@@ -10,6 +10,9 @@ ship, together with the declarations; `dist` is never committed. Make is an opti
10
10
  shortcut layer; npm and the CLI work on Windows, macOS and Linux. No global
11
11
  package install, hosting account, database or Docker is needed for the local loop.
12
12
 
13
+ For the repository's prose-only checks, CI selection and release helper commands,
14
+ see [the development pipeline](DEVELOPMENT-PIPELINE.md).
15
+
13
16
  ## Try the runtime
14
17
 
15
18
  From the runtime checkout, `make dev` installs locked dependencies if needed and