@jimhoyd/urlcode 0.4.0-alpha.3 → 0.4.1

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 (65) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +20 -15
  3. package/ROADMAP.md +16 -11
  4. package/dist/BUILD-MANIFEST.json +16 -15
  5. package/dist/authoring.js +15 -1
  6. package/dist/capability-query.js +0 -1
  7. package/dist/catalog.js +0 -1
  8. package/dist/cli.js +23 -7
  9. package/dist/config.js +1 -1
  10. package/dist/explain.js +1 -1
  11. package/dist/http-response.js +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/init-with.js +36 -11
  14. package/dist/manifest.js +1 -1
  15. package/dist/mcp-authoring.js +2 -2
  16. package/dist/mcp.js +1 -1
  17. package/dist/policies/cache.js +2 -2
  18. package/dist/project-dependencies.js +305 -0
  19. package/dist/runtime.js +1 -1
  20. package/dist/trusted-functions.js +4 -5
  21. package/dist/types/authoring.d.ts +9 -1
  22. package/dist/types/capability-query.d.ts +0 -1
  23. package/dist/types/catalog.d.ts +0 -4
  24. package/dist/types/config.d.ts +1 -9
  25. package/dist/types/explain.d.ts +0 -1
  26. package/dist/types/http-response.d.ts +0 -1
  27. package/dist/types/index.d.ts +1 -0
  28. package/dist/types/init-with.d.ts +7 -13
  29. package/dist/types/manifest.d.ts +0 -1
  30. package/dist/types/project-dependencies.d.ts +78 -0
  31. package/dist/types/trusted-functions.d.ts +1 -4
  32. package/docs/AI-AUTHORING.md +5 -1
  33. package/docs/AWS.md +9 -0
  34. package/docs/CI-FOLLOWUP-2026-09-19.md +1 -1
  35. package/docs/CODEBASE-AUDIT-2026-09-20.md +6 -0
  36. package/docs/COMPOSING-A-SITE.md +278 -0
  37. package/docs/DEVELOPMENT-PIPELINE.md +208 -119
  38. package/docs/EXTENSIONS.md +36 -6
  39. package/docs/FRAMEWORK.md +45 -30
  40. package/docs/INSTALL.md +13 -8
  41. package/docs/MIDDLEWARE.md +10 -4
  42. package/docs/OPEN-DECISIONS.md +46 -6
  43. package/docs/READINESS.md +4 -3
  44. package/docs/README.md +3 -4
  45. package/docs/RELEASE-0.4.1.md +73 -0
  46. package/docs/RELEASE-SECURITY.md +27 -12
  47. package/docs/SPECIFICATION.md +5 -1
  48. package/docs/SPIKE-CORE-LAYERING.md +1 -1
  49. package/docs/STARTERS.md +17 -5
  50. package/docs/TOOLING.md +6 -4
  51. package/docs/VERCEL.md +10 -2
  52. package/docs/VERSION-ALIGNMENT.md +42 -8
  53. package/docs/archive/2026-09-19/ROADMAP.md +1 -0
  54. package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +1 -0
  55. package/docs/{SPIKE-LAMBDA-COMPILE.md → archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md} +168 -12
  56. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +2 -0
  57. package/docs/archive/README.md +1 -0
  58. package/docs/yaml/functions.md +10 -2
  59. package/docs/yaml/middleware.md +5 -3
  60. package/examples/cookbook/middleware/envelope.mjs +4 -2
  61. package/llms-full.txt +387 -44
  62. package/llms.txt +1 -0
  63. package/package.json +8 -5
  64. package/packaging/claude-plugin/.claude-plugin/plugin.json +1 -1
  65. package/recipes/middleware/middleware/envelope.mjs +4 -2
package/docs/INSTALL.md CHANGED
@@ -8,8 +8,13 @@ so the installed `urlcode` command runs `dist/cli.js` and needs no build tool.
8
8
 
9
9
  ## npm
10
10
 
11
+ The stable release target is `0.4.1`. Use these commands after publication;
12
+ `npm run release:status` in a checkout reports live registry availability. An
13
+ unversioned npm install selects the current `latest` channel, while `@alpha`
14
+ explicitly selects the separate prerelease channel.
15
+
11
16
  ```sh
12
- npm install --global @jimhoyd/urlcode
17
+ npm install --global @jimhoyd/urlcode@0.4.1
13
18
  urlcode --help
14
19
  ```
15
20
 
@@ -19,12 +24,12 @@ Which dependency list it belongs in depends on how the project uses URLCode:
19
24
  ```sh
20
25
  # Using URLCode as a tool: validate, test and build in CI, never imported by
21
26
  # the code that serves requests.
22
- npm install --save-dev @jimhoyd/urlcode
27
+ npm install --save-dev --save-exact @jimhoyd/urlcode@0.4.1
23
28
  npx urlcode validate
24
29
 
25
30
  # Embedding the runtime (see TYPESCRIPT.md): the application imports
26
31
  # @jimhoyd/urlcode at startup, so it must survive `npm ci --omit=dev`.
27
- npm install --save @jimhoyd/urlcode
32
+ npm install --save --save-exact @jimhoyd/urlcode@0.4.1
28
33
  ```
29
34
 
30
35
  A devDependency is absent from a production install, so an application that
@@ -65,7 +70,7 @@ It downloads the release tarball, verifies its SHA-256 against the release's
65
70
  `SHA256SUMS`, and installs with npm. Options:
66
71
 
67
72
  ```sh
68
- curl -fsSL .../install.sh | sh -s -- --version 0.3.0 --prefix "$HOME/.local"
73
+ curl -fsSL .../install.sh | sh -s -- --version 0.4.1 --prefix "$HOME/.local"
69
74
  ```
70
75
 
71
76
  `--prefix` avoids needing privileges for a global npm directory; add
@@ -76,12 +81,12 @@ for that moment: to inspect first, download it, read it, then run it.
76
81
 
77
82
  No image is published yet: the release job's GHCR step is gated behind the
78
83
  `PUBLISH_CONTAINER` repository variable and has not run, so there is nothing at
79
- `ghcr.io/jimhoyd-com/urlcode` to pull. Build it from a release checkout:
84
+ `ghcr.io/jimhoyd-com/urlcode` to pull. After the release tag exists, build it from that checkout:
80
85
 
81
86
  ```sh
82
- git clone --branch v0.3.0 https://github.com/jimhoyd-com/urlcode.git
83
- docker build -t urlcode:0.3.0 urlcode
84
- docker run --rm -p 127.0.0.1:3000:3000 -v "$PWD:/project:ro" urlcode:0.3.0 \
87
+ git clone --branch v0.4.1 https://github.com/jimhoyd-com/urlcode.git
88
+ docker build -t urlcode:0.4.1 urlcode
89
+ docker run --rm -p 127.0.0.1:3000:3000 -v "$PWD:/project:ro" urlcode:0.4.1 \
85
90
  serve --project /project --host 0.0.0.0
86
91
  ```
87
92
 
@@ -48,12 +48,18 @@ Every middleware and the function share the same request and context. Validated
48
48
  with a fresh `context.state` object for this chain. State never survives the
49
49
  request. Header edits are visible downstream; editing the request does not
50
50
  reroute it or change already validated inputs. Request bodies are single-use:
51
- reading one consumes it for downstream code. There is no `clone()` or streaming
52
- API; pass parsed data through `context.state` when needed.
51
+ reading one consumes it for downstream code. On a `sandbox: true` route there is
52
+ no `clone()` or streaming API at all, so pass parsed data through
53
+ `context.state`; a trusted route receives Node's own `Request`/`Response` and so
54
+ does have `clone()`, but passing parsed data through `context.state` keeps the
55
+ chain portable between the two modes.
53
56
 
54
57
  Function responses support the existing text/JSON guest API. To transform their
55
- body, read it and return a new `Response`. Native redirect/respond/page/static/
56
- download bodies are opaque and cannot be read through `text()` or `json()`.
58
+ body, read it and return a new `Response`. On a `sandbox: true` route, native
59
+ redirect/respond/page/static/download bodies are opaque and cannot be read
60
+ through `text()` or `json()`; a trusted chain receives them as an ordinary
61
+ `Response` and can read them, so wrapping `respond: {text: hello}` and returning
62
+ `HELLO` works there and fails in the guest.
57
63
  Returning the same native response preserves original bytes, including binary
58
64
  files, ranges and HEAD lengths. You may add headers, but cannot change its
59
65
  original status or existing native headers while preserving that body. To replace
@@ -33,9 +33,8 @@ keeps earlier discussions. Recommendations below are not accepted decisions.
33
33
  |---|---|---|
34
34
  | Where does work status live? | Several old plans repeated issues and continued calling delivered work unfinished. | Issues for actionable status, this short roadmap for sequence, archive for completed proposals. Preserve evidence gaps when archiving. |
35
35
  | Expand into business applications now? | No collection handler or proposed business suite is implemented; the model-backed benchmark evidence is missing. | Measure existing tasks and record repeated application plumbing before selecting a collection/CMS/forms project. Retired short-link products stay retired. [Proposal](SPIKE-BUSINESS-SUITE.md). |
36
- | Which provider execution model next? | AWS/Vercel still reject function/middleware despite the trusted default. | Decide demand first, then compare one Node deployment per project against one Lambda per route. Do not promise either today. [Proposal](SPIKE-LAMBDA-COMPILE.md). |
37
36
  | Fold extension schemas into retrieved context? | `urlcode extensions` and the MCP `get_extensions` query return the registered configuration and policy schemas, but `src/context.ts:113` reports `extensions` as names only, so an author writing `extensions.<name>.config` or `policies.extensions.<name>` must run the separate operator-authorized query first. | Decide from retrieval and task evidence, not preference: the existing small-task harness can measure whether folding schemas into bounded context improves authoring. Keep the token budget bounded and never auto-load a project-selected host file. This is a discovery improvement, not a defect in the existing query. |
38
- | Retire the UI primitive fallback? | Auth and admin use the kit when supplied, and retain tested primitive rendering without it. | Keep both until an explicit compatibility/deprecation decision; adoption is already implemented. |
37
+ | Keep the POST-plus-`request.body` sandbox advisory? | `src/readiness.ts` nudges any code-running route that accepts POST with a declared `request.body` and declares neither `sandbox: true` nor `sandboxReason`. It is advisory only: never fails `audit`, never changes `ready`. | The nudge keys on request *shape* while [AI authoring](AI-AUTHORING.md) tells authors to decide on *code* trust, so it can read as "untrusted input implies sandbox" -- the reasoning that guidance explicitly rejects. It still has value as a prompt to record a decision. Recommendation: keep the trigger, restate the message as a request to record the trust decision (`sandbox: true` or `sandboxReason`) rather than as a suggestion that this route may need isolation. Not changed here; #196 was a docs/tooling alignment pass. |
39
38
 
40
39
  The broader [AI benchmark proposal](SPIKE-AI-FRAMEWORK-BENCHMARK.md) also needs a
41
40
  chosen application, model-run budget and execution authorization. The existing
@@ -48,6 +47,10 @@ small-task harness can supply evidence without committing to that larger study.
48
47
  - Core `0.4.0-alpha.2` and current extension releases exist; publishing that
49
48
  already-shipped version is not a next step.
50
49
  - Auth/admin kit adoption and shared form helpers are implemented in their code.
50
+ - The UI primitive fallback is retired, which settles the question this table
51
+ carried. Auth and admin now render every screen through the kit and refuse
52
+ activation without it (`packages/auth/src/auth.ts`: "there is no
53
+ shared-primitive fallback"), so "keep both" no longer describes the code.
51
54
  - The template pins `0.4.0-alpha.2`. Its skill differences were read against that
52
55
  pin: omitted handlers and advice about the removed management API are stale,
53
56
  not intentional older-version behavior.
@@ -55,6 +58,44 @@ small-task harness can supply evidence without committing to that larger study.
55
58
  is not a schema validator for every example. Extending its coverage is tracked
56
59
  separately, not a reason to weaken review or bypass required checks.
57
60
 
61
+ ## Accepted: one Node deployment per project
62
+
63
+ **Decided 2026-09-19.** Projects that use `function` or `middleware` deploy as
64
+ **one trusted Node process** — a container or a VM running the project as it
65
+ runs locally. That is the supported execution model, and it needs no new work:
66
+ it is what the runtime already does.
67
+
68
+ **Per-route Lambda compilation is not pursued.** The alternative on the table
69
+ was a build step emitting one Lambda per `function` route
70
+ ([the proposal](archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md)). It is declined for now, on three
71
+ grounds the proposal itself states:
72
+
73
+ 1. It would replace the sandbox guarantee rather than preserve it, and lose the
74
+ fresh-per-invocation state that `sandbox: true` currently guarantees.
75
+ 2. It would make this project the author of generated IAM roles — a
76
+ security-critical output it has never owned.
77
+ 3. It would trade an honest refusal for a larger claim nobody has deployed.
78
+
79
+ Against that, a single Node deployment supports every route type today with no
80
+ compiler, no generated infrastructure and no second isolation story to document.
81
+
82
+ **What follows from this decision:**
83
+
84
+ - AWS and Vercel continue to refuse `function` and `middleware` at activation,
85
+ naming the route (`src/capabilities.ts`, `activateNativeOnly` in
86
+ `src/adapters.ts`). That refusal is now a **deliberate position**, not a gap
87
+ awaiting an adapter. Documentation should say so rather than implying the
88
+ support is coming.
89
+ - Serverless targets remain first-class for the declarative route types they can
90
+ actually serve; nothing about static or native-only deployment changes.
91
+ - [SPIKE-LAMBDA-COMPILE.md](archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md) is kept as the analysis
92
+ behind this decision, not as a plan. Reopen it only on evidence of real demand
93
+ for URLCode `function` routes specifically on AWS serverless — the proposal's
94
+ own §6 already scopes what a first attempt would be.
95
+
96
+ This decision is about the *execution model*, not about AWS. Deploying the Node
97
+ process to AWS (ECS, EC2, App Runner) is an operator choice this fully supports.
98
+
58
99
  ## Accepted: per-package release tags
59
100
 
60
101
  **Decided 2026-09-19.** Workspace packages under `packages/` release on
@@ -160,10 +201,9 @@ moves rather than trusting this line.
160
201
  The [archived monorepo plan](archive/2026-09-19/SPIKE-MONOREPO.md) records
161
202
  migration context.
162
203
  [Issue 172](https://github.com/jimhoyd-com/urlcode/issues/172), which tracked
163
- "consolidate middleware into core after moving it into the monorepo," is
164
- **moot and still open** — there is nothing left to consolidate. It should be
165
- closed with a pointer to this section. Migration starting is not a claim that
166
- it has landed.
204
+ "consolidate middleware into core after moving it into the monorepo," was
205
+ **closed on 2026-09-19** as moot — there was nothing left to consolidate.
206
+ Migration starting is not a claim that it has landed.
167
207
 
168
208
  ## Source review baseline
169
209
 
package/docs/READINESS.md CHANGED
@@ -10,9 +10,10 @@ urlcode audit --project ../my-links --expect-routes 2
10
10
  urlcode benchmark --project ../my-links --requests 1000 --concurrency 2 --max-p95-ms 50
11
11
  ```
12
12
 
13
- All three activate/validate the project with the same isolated runtime and use
14
- local environment loading like `test`. Pass an external `--policy` for explicitly
15
- authorized bindings. No destination redirects are followed, credentials are not
13
+ All three activate/validate the project with the same runtime that serves it --
14
+ each route in its own declared trust mode, trusted in-process unless it declares
15
+ `sandbox: true` -- and use local environment loading like `test`. Pass an
16
+ external `--policy` for explicitly authorized bindings. No destination redirects are followed, credentials are not
16
17
  printed, and no remote load-test target is accepted.
17
18
 
18
19
  ## Inventory and count reconciliation
package/docs/README.md CHANGED
@@ -39,8 +39,8 @@ boundary and the license. Use documentation pinned to your runtime revision;
39
39
 
40
40
  | Goal | Start here |
41
41
  |---|---|
42
- | Add accounts, sign-in and protected routes | [urlcode-auth](https://github.com/jimhoyd-com/urlcode-auth#readme) |
43
- | Manage users, sessions, roles and audit | [urlcode-admin](https://github.com/jimhoyd-com/urlcode-admin#readme) |
42
+ | Add accounts, sign-in and protected routes | [urlcode-auth](../packages/auth#readme), [auth security](../packages/auth/SECURITY.md) |
43
+ | Manage users, sessions, roles and audit | [urlcode-admin](../packages/admin#readme) |
44
44
  | Restyle every extension page and translate copy | [urlcode-ui](../packages/ui#readme), [ui contract](../packages/ui/CONTRACT.md) |
45
45
  | Write or install a versioned extension | [Extensions](EXTENSIONS.md), [example fixture](../examples/extensions/README.md) |
46
46
  | Follow implementation of the auth, admin and UI extensions | [Extension implementation sequence](archive/2026-09-19/EXTENSION-IMPLEMENTATION.md) |
@@ -59,7 +59,7 @@ boundary and the license. Use documentation pinned to your runtime revision;
59
59
  | Review security findings and gaps | [Internal security audit](SECURITY-AUDIT.md) |
60
60
  | Assess release readiness | [Evidence and open gates](RELEASE-READINESS.md) |
61
61
  | See unfinished work | [Roadmap](../ROADMAP.md) |
62
- | Read the design for compiling function routes into per-route Lambdas | [Lambda compile spike](SPIKE-LAMBDA-COMPILE.md) |
62
+ | Read why per-route Lambda compilation was declined | [Lambda compile spike (archived)](archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md) |
63
63
  | Verify a running deployment matches the project | [Deployment checks](DEPLOYMENT-CHECKS.md) |
64
64
  | Inspect target support | [Capabilities and normalized representation](CAPABILITIES.md) |
65
65
  | Deploy to Vercel, AWS Lambda or Cloudflare Workers | [Vercel](VERCEL.md), [AWS](AWS.md), [Cloudflare](CLOUDFLARE.md), [provider verification evidence](PROVIDER-VERIFICATION.md) |
@@ -79,7 +79,6 @@ in the guides above and the [specification](SPECIFICATION.md).
79
79
  - [Version alignment](VERSION-ALIGNMENT.md) and [release security](RELEASE-SECURITY.md)
80
80
  describe peer compatibility and publication.
81
81
  - Open proposals: [middleware layering](SPIKE-CORE-LAYERING.md),
82
- [Lambda compilation](SPIKE-LAMBDA-COMPILE.md),
83
82
  [agent benchmark](SPIKE-AI-FRAMEWORK-BENCHMARK.md), and
84
83
  [business suite](SPIKE-BUSINESS-SUITE.md). None is an implementation promise.
85
84
  - [Historical plans and reviews](archive/README.md) are archived separately.
@@ -0,0 +1,73 @@
1
+ # URLCode 0.4.1
2
+
3
+ Core, UI, auth and admin share this explicitly selected stable version. This does not enable permanent fixed versioning. Internal peer minimums advance to this version; install the coordinated set together.
4
+
5
+ ```sh
6
+ npm install --save-exact @jimhoyd/urlcode@0.4.1 @jimhoyd/urlcode-ui@0.4.1 @jimhoyd/urlcode-auth@0.4.1 @jimhoyd/urlcode-admin@0.4.1
7
+ ```
8
+
9
+ This coordinated release moves core, UI, auth and admin from `0.4.0-alpha.3` to stable `0.4.1`. It makes the reviewed monorepo release line available through npm `latest` and keeps the four packages' peer minimums aligned.
10
+
11
+ The runtime retains its existing trust model: project functions and middleware run trusted in Node by default; routes declaring `sandbox: true` retain QuickJS/WASM isolation. The stable label is a distribution decision, not an independent security assessment or hostile multi-tenant readiness claim.
12
+
13
+ Release preparation now supports an explicit exit from alpha. Publication promotes the exact signed candidate archives, pins their manifest digest in immutable tags, checks actual npm installability, and updates the standalone starter to the published core version. Historical alpha versions and tags remain unchanged.
14
+
15
+ ### admin-kit-only-console.md
16
+
17
+ **Breaking:** the console renders only through the urlcode-ui kit. `ui` is now a
18
+ required option of `adminExtension` and of `createAdministrationRuntime`'s
19
+ `admin` block, which is itself no longer optional.
20
+
21
+ `@jimhoyd/urlcode-ui` was already a required peer dependency, so nothing new has
22
+ to be installed. What changed is that the `ui` *extension* must now be supplied
23
+ and active: the primitive render path — the same `admin/*` templates rendered
24
+ through the shared primitives inside a console shell admin built itself — is
25
+ gone, along with the `RenderPath` seam, the `activeKit()` helper, the
26
+ `ScreenOptions.shell.sidebar` markup and `src/admin-presentation.ts`. The kit
27
+ builds the sidebar, page header and skip target from the `nav` items and account
28
+ `menu` admin supplies, so the console shell has one representation instead of
29
+ two. `ScreenOptions.preferences` is gone too: the kit layout now renders through
30
+ the same resolved presentation as the body, so the document's `lang` matches the
31
+ copy on the page.
32
+
33
+ Activation refuses up front, rather than failing per request in production, when
34
+
35
+ - `ui` is missing,
36
+ - `ui` is supplied but not active yet — declare `ui` before `admin` under
37
+ `extensions` in `urlcode.yaml`, since the runtime activates in declaration
38
+ order, and mount its assets route, or
39
+ - the kit was built without `adminUiTemplates`.
40
+
41
+ To migrate, build the extension with admin's templates and pass it:
42
+
43
+ ```js
44
+ const ui = createUiExtension({projectSha256, projectRoot, sources: [authCatalogue], extensions: [authUiTemplates, adminUiTemplates]});
45
+ adminExtension({service, csrfKey, projectSha256, ui});
46
+ ```
47
+
48
+ Scaffolding emits that wiring for you: `scaffold()`, `initAdministration` and
49
+ `urlcode init --with ui,auth,admin` compose the kit and register the admin
50
+ templates with it. `ui` is now required, and must come before `admin`; the
51
+ scaffold refuses otherwise before writing anything.
52
+
53
+ ### auth-kit-only-render.md
54
+
55
+ Breaking: the `ui` extension is now required. Every account screen renders through the `urlcode-ui` kit; the shared-primitive fallback is gone. `authExtension({ui, ...})` refuses activation when `ui` is absent or when the runtime has not activated it, naming the missing piece instead of failing per request. Declare `ui` before `auth` in `urlcode.yaml` (with its asset route) and list `ui.registration` before `authExtension` in the host: the runtime activates extensions in the order `urlcode.yaml` declares them. `@jimhoyd/urlcode-ui` was already a required peer dependency, so nothing new needs installing; what changes is that the extension must be supplied and active. `ScreenOptions.ui` is no longer optional and `screenObserver` no longer reports a render path.
56
+
57
+ Scaffolding composes the kit for you: `urlcode init --with ui,auth` and the standalone `initAuthentication` now write a project whose `urlcode.yaml` declares `ui` first and whose host passes it to `authExtension`. The scaffold refuses when `ui` is missing, or ordered after `auth`, before anything is written.
58
+
59
+ ### auth-store-startup-diagnostics.md
60
+
61
+ Report which startup phase an auth store worker reached when its 15-second bound elapses, and reject at once when the worker fails or exits before reporting readiness instead of waiting the bound out. The status and code are unchanged; the detail is attached as the error's cause for operator logs and never reaches a response.
62
+
63
+ ### pin-generated-site-dependencies.md
64
+
65
+ `urlcode-auth init` now writes a `package.json` that pins this package and each declared peer at the exact version installed beside it, instead of a manifest with no dependencies at all; `initAuthentication` returns those pins and names any peer it could not resolve. Nothing is installed: running `npm install` in the generated directory to produce a lockfile stays the operator's explicit step, and no upgrade command exists.
66
+
67
+ ### ui-scaffold-peer-registration.md
68
+
69
+ The scaffold wires kit-rendering peers into the host it generates. `scaffold()` reads the composed `names` and emits `createUiExtension({..., sources: [authCatalogue], extensions: [authUiTemplates, adminUiTemplates]})`, importing each peer it needs, so `urlcode init --with ui,auth,admin` produces a project that activates. Previously it always wrote `sources: []` and no `extensions`, which left auth and admin without their copy and templates. `ui` alone still registers nothing and imports no peer.
70
+
71
+ Name `ui` first: the runtime activates extensions in the order `urlcode.yaml` declares them, core writes that file in `--with` order, and auth and admin both refuse to activate before the kit is active.
72
+
73
+ Publish to the npm `latest` channel in core → UI → auth → admin order after exact-commit CI and candidate verification. Existing tags and the `alpha` channel stay unchanged; this stable release advances `latest`. Changesets prerelease mode is exited. Update the standalone starter's exact core pin after registry installability is verified. This preparation is not evidence of publication or an independent security assessment.
@@ -18,16 +18,18 @@ means a successful nightly or manual `ci.yml` run across every supported OS/Node
18
18
  combination; the compact push/PR matrix alone does not authorize publication. Package manifests,
19
19
  lockfile versions and tag names must agree. Published peer floors must exist and
20
20
  extension tests must resolve the published packages, not workspace source.
21
- The manual core candidate and tagged core release use the same preparation
22
- script with the digest-pinned Node image from Dockerfile. Locked dependencies,
21
+ The candidate builds all package archives once using the digest-pinned Node
22
+ image from Dockerfile; publishers promote those exact signed bytes. The builder
23
+ installs Git from Debian for repository-fixture tests; Git is a test dependency,
24
+ not an addition to the runtime image or npm package. Locked dependencies,
23
25
  verification, runtime audit, package installation tests and local drills precede
24
26
  packing. Build commands in that container receive no GitHub token.
25
27
 
26
28
  The candidate workflow stores artifacts without publishing. A tagged release
27
29
  publishes npm only when `PUBLISH_NPM=true` and core images only when
28
- `PUBLISH_CONTAINER=true`. Preparation retains original artifacts for 90 days
29
- before publication so reruns of the same run reuse them. Manual candidates retain
30
- their existing 30-day retention. Keep independent last-good artifacts and
30
+ `PUBLISH_CONTAINER=true`. Candidate and release artifacts are retained for 90 days, and each package
31
+ GitHub release stores the complete signed candidate bundle. Retries verify and
32
+ reuse original retained or durable bytes; missing originals fail closed. Keep independent last-good artifacts and
31
33
  rehearse deployment rollback; Actions retention is not an archival guarantee.
32
34
 
33
35
  ## Identity and provenance
@@ -39,9 +41,12 @@ requires a reviewed registry trust migration. Successful preparation does not
39
41
  prove the registry-side identity permits direct publication.
40
42
 
41
43
  `actions/attest` signs the candidate files with GitHub OIDC/Sigstore provenance.
42
- Core includes its package archive, dependency SBOM, build manifest, checksums,
43
- and, for a release, Homebrew formula. Extensions include their archive and
44
- checksums. The core manifest records source SHA, lockfile hash, Node and
44
+ The candidate bundle includes all four archives, dependency SBOM, build manifest,
45
+ train metadata, checksums and Homebrew formula. Each publisher retains that
46
+ bundle and publishes only its selected npm archive. New annotated version tags
47
+ pin the candidate run ID and signed-manifest SHA256. The run ID is also bound
48
+ into the manifest; the digest prevents another attempt of that run from
49
+ substituting different artifacts. The core manifest records source SHA, lockfile hash, Node and
45
50
  TypeScript versions and emitted-file hashes. `dist/` is built, never committed.
46
51
 
47
52
  Verify an artifact with `gh attestation verify <tarball> --repo
@@ -59,14 +64,18 @@ identical SHA-512 integrity. GitHub assets are compared and missing assets added
59
64
  existing unequal assets are never clobbered. Transient registry errors fail
60
65
  closed rather than count as an unpublished version.
61
66
 
62
- Alpha versions use npm/GHCR `alpha` and GitHub prerelease classification. New
63
- GitHub releases are not automatically promoted to GitHub `latest`. Mutable npm
67
+ Alpha versions use npm/GHCR `alpha` and GitHub prerelease classification.
68
+ Explicit stable versions use npm `latest` and normal GitHub releases. Only the
69
+ stable core release advances GitHub `latest`, so the core installer cannot pick
70
+ an extension release. Stable preparation removes Changesets prerelease mode;
71
+ existing alpha artifacts, tags and channel pointers remain unchanged. Mutable npm
64
72
  and container channels cannot regress to an older version. Existing core image
65
73
  versions are reused only with matching source labels; unlabeled historical
66
74
  images require a reviewed migration rather than an inferred identity.
67
75
 
68
- The coordinator creates one package tag at a time and waits for success before
69
- releasing dependents. Shared publication concurrency avoids cross-version races.
76
+ The coordinator verifies candidate availability and provenance before creating
77
+ any version tag, then creates one at a time and waits for successful publication
78
+ and consumer-facing registry installability before releasing dependents. Shared publication concurrency avoids cross-version races.
70
79
  The active immutable-tag rule blocks release tag updates/deletions with no
71
80
  bypass actors; its configuration is in `.github/rulesets/release-tags.json`. No automation needs permission to bypass main checks or approve
72
81
  its own PR. A repository-scoped GitHub App is the preferred eventual automation
@@ -79,3 +88,9 @@ publish path, OIDC trust for every package, tag protection, GHCR behavior and
79
88
  partial-failure recovery must be verified on an authorized release; local tests
80
89
  and workflow inspection cannot prove them. The release helpers do not alter
81
90
  historical npm channels, GitHub flags, tags or registry artifacts.
91
+
92
+ The coordinated `.3` release proved the existing OIDC identities, but exposed
93
+ missing-artifact retry behavior tracked in #223. New workflows fail closed or
94
+ recover verified durable bytes; old immutable tags retain their original
95
+ workflow code. The new promotion path still needs a future explicitly authorized
96
+ release rehearsal; implementing it does not publish a new version.
@@ -202,7 +202,11 @@ entry may likewise be a string, normalized to `{source: <string>}`. Only the
202
202
  long form exists after loading, so `routes`, `audit`, `explain`, revision hashes
203
203
  and the field reference describe the expansion.
204
204
 
205
- ES modules only (`.mjs` or `.js`, independent of Node package settings).
205
+ ES modules only. `.mjs` is loaded as ESM in both modes. A `sandbox: true` route
206
+ also reads `.js` as ESM independently of Node package settings, but a trusted
207
+ route imports `.js` through Node's own loader, so an ESM `.js` module needs the
208
+ nearest `package.json` to declare `"type": "module"`; under `"type": "commonjs"`
209
+ it fails activation. Prefer `.mjs` for modules that must work in either mode.
206
210
  [Build-time TypeScript authoring](TYPESCRIPT-AUTHORING.md) can produce these
207
211
  JavaScript modules in a separate output project; serving does not transpile them.
208
212
  The build never imports application code into Node, uses fixed compiler settings,
@@ -35,7 +35,7 @@ either is blocked on the other.
35
35
  Core's job is to stay the smallest thing that is still a complete product on
36
36
  its own: YAML routing (`redirect`/`respond`/`page`/`static`/`download`) plus
37
37
  `function`, the one primitive with an actual serverless story
38
- ([`SPIKE-LAMBDA-COMPILE.md`](SPIKE-LAMBDA-COMPILE.md) — compile each `function`
38
+ ([`SPIKE-LAMBDA-COMPILE.md`](archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md) — compile each `function`
39
39
  route to its own Lambda, the pattern `src/build-cloudflare.ts` already uses).
40
40
  That base alone serves static and dynamic content and deploys anywhere
41
41
  (`docs/FRAMEWORK.md` layer 1–3). Everything past that — accounts, admin,
package/docs/STARTERS.md CHANGED
@@ -36,18 +36,30 @@ on every push and pull request once the project is on GitHub.
36
36
  See [organization](ORGANIZATION.md) for choosing your own layout. Defaults allow
37
37
  GET/HEAD and use redirect 302; add configuration only when changing behavior.
38
38
 
39
+ `urlcode init` writes no `package.json`: the route project is route-only, and its
40
+ runtime may be installed globally, in a parent workspace or in a container. Add
41
+ `--manifest` to also write one pinning the runtime at exactly the version that
42
+ generated the project, then run `npm install` in it yourself to install that
43
+ version and produce a lockfile. The CLI never runs a package manager, and no
44
+ upgrade command exists — a pinned version changes when you edit the manifest.
45
+
39
46
  Initialization refuses an existing destination. Own the app in your own repository,
40
47
  keep secrets out of Git, and upgrade the runtime separately without regenerating
41
48
  application files. Add pages, downloads, more functions and business-specific
42
49
  features to this same project. Update tests and the expected route count as it grows.
43
50
  See [readiness](READINESS.md) and [security](FUNCTION-SECURITY.md).
44
51
 
45
- To start an extended site instead, install the extension packages from npm
46
- (`npm install @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin`, published as
47
- `0.1.0-alpha.x` prereleases) in the directory you run from and pass their names: `urlcode init ../my-site --with
48
- auth,admin` writes the same starter under `my-site/app/`, merges each package's
52
+ To start an extended site instead, install the compatible core, UI, auth and
53
+ admin set from npm in the directory you run from; the
54
+ [framework guide](FRAMEWORK.md#the-composition-contract) provides the exact
55
+ `0.4.1` command to use after publication. Pass UI first so its kit activates
56
+ before auth: `urlcode init ../my-site --with ui,auth,admin` writes the same starter under `my-site/app/`, merges each package's
49
57
  routes and declarations into it, and generates one `host.mjs` and README beside
50
- it. The contract each package fulfils is in [extensions](EXTENSIONS.md#scaffolding-with-init---with).
58
+ it, plus a `package.json` pinning the runtime, those packages and their declared
59
+ peers at the versions it just resolved, validated together against every declared
60
+ peer range. Installing them is your explicit `npm install` in that directory.
61
+ The contract each package fulfils is in [extensions](EXTENSIONS.md#scaffolding-with-init---with),
62
+ with `--no-manifest` and `--pin` in [recorded versions](EXTENSIONS.md#recorded-versions).
51
63
 
52
64
  Both paths carry an `AGENTS.md` for repository-aware assistants. `urlcode init`
53
65
  generates it from the installed runtime's capability catalog (the same source as
package/docs/TOOLING.md CHANGED
@@ -81,10 +81,12 @@ network. Keys always appear in this order:
81
81
  `--host-file`, `host` counts the operator module's extensions and plugins
82
82
  without activating them.
83
83
  - `routes`: path, methods and handler per route, sorted by path.
84
- - `constraints`: a fixed list that holds for every project (no guest network,
85
- no Node APIs, no regex routes, one handler per route, exact or `{param}`
86
- path segments, subtree mounts only for static and extension routes, no YAML
87
- interpolation, secrets by operator grant only), each with a value and a note.
84
+ - `constraints`: a fixed list that holds for every project (network and Node
85
+ built-ins available to trusted code and withdrawn by `sandbox: true`, no
86
+ regex routes, one handler per route, exact or `{param}` path segments,
87
+ subtree mounts only for static and extension routes, no YAML interpolation,
88
+ injected `env`/`secrets` by operator grant only), each with a value and a
89
+ note spelling out how it differs between the two trust modes.
88
90
  - `targets`: for each capability target (or the one `--target`), which of this
89
91
  project's used features are supported, conditional, refused or unknown.
90
92
  - `commands`: the exact `validate`, `test`, `audit --expect-routes N` (N is
package/docs/VERCEL.md CHANGED
@@ -7,7 +7,8 @@ that is the point of the project format.
7
7
  **This adapter serves native handlers only:** redirects, validated responses,
8
8
  pages, static assets and downloads. `function` and `middleware` routes are
9
9
  refused at activation, trusted or sandboxed alike, not per request, so a deployment cannot
10
- half-work. See [what is not supported](#what-this-adapter-does-not-do).
10
+ half-work, and that is a settled position rather than a pending limitation. See
11
+ [what is not supported](#what-this-adapter-does-not-do).
11
12
 
12
13
  A working project is in [`examples/vercel/`](../examples/vercel/).
13
14
 
@@ -71,13 +72,20 @@ and want it in generated URLs.
71
72
 
72
73
  | Not supported | Why |
73
74
  |---|---|
74
- | `function` routes | They need the self-hosted Node lifecycle; a `sandbox: true` route would additionally spawn worker threads and load the WASM engine on every cold start. Correctness is not the issue; predictable latency is, and it is unmeasured. |
75
+ | `function` routes | They need the self-hosted Node lifecycle; a `sandbox: true` route would additionally spawn worker threads and load the WASM engine on every cold start. Correctness is not the issue; the execution model is per-route compilation was considered and declined. |
75
76
  | Middleware | Runs in the same execution mode as the route's function, and is refused with it. |
76
77
  | `urlcode serve` operational endpoints | `/_urlcode/health` and `/_urlcode/ready` describe a long-lived process. Use Vercel's own observability. |
77
78
 
78
79
  Each refusal happens at activation with a message naming the route, so you find
79
80
  out on deploy rather than on a request.
80
81
 
82
+ `function` and `middleware` are not coming to this adapter. The supported answer
83
+ is to deploy the project as one trusted Node process — a container or a VM
84
+ running the project as it runs locally — which supports every route type today,
85
+ on any host you like including AWS (ECS, EC2, App Runner). See
86
+ [the decision](OPEN-DECISIONS.md#accepted-one-node-deployment-per-project) and
87
+ [the analysis behind it](archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md).
88
+
81
89
  ## Operating it
82
90
 
83
91
  Every instance activates the project independently: parsing YAML, snapshotting
@@ -26,18 +26,52 @@ must match its manifest. `npm run release:check` rejects stale lockfile versions
26
26
  Unreleased source changes do not require moving a published tag or pretending a
27
27
  new package has already shipped.
28
28
 
29
+ The `0.4.1` release is an explicit stable release decision for core, UI, auth
30
+ and admin. Publication moves each package's npm `latest` channel to `0.4.1`, in
31
+ core → UI → auth → admin order, after its release checks pass. A prepared
32
+ manifest or merged release PR does not prove registry publication: use
33
+ `npm run release:status` to inspect the live result before installing the set.
34
+ This alignment does not permanently couple package versions; subsequent
35
+ releases can still select only the packages that changed.
36
+
29
37
  Alpha releases publish under `alpha`; they never automatically move npm
30
- `latest`. Core's historical `latest` remains the stable 0.3.0 baseline until an
31
- explicit stable release decision. Extensions have historical alpha versions on
32
- `latest`; subsequent alpha publication does not keep that channel in lockstep.
33
- Different channel values alone are not drift. Test the install combination you
34
- recommend against peer ranges; a bare install may select an older channel.
35
- `release:status` reports each declared peer floor and whether its current
36
- `latest` and `alpha` satisfy the range.
38
+ `latest`. Stable publication does not move `alpha`, so the two channels can
39
+ legitimately show different versions. Test the install combination you recommend
40
+ against peer ranges. `release:status` reports each declared peer floor and
41
+ whether its current `latest` and `alpha` satisfy the range.
42
+
43
+ After all four `0.4.1` versions are published, install the aligned set with:
44
+
45
+ ```sh
46
+ npm install --save-exact @jimhoyd/urlcode@0.4.1 @jimhoyd/urlcode-ui@0.4.1 @jimhoyd/urlcode-auth@0.4.1 @jimhoyd/urlcode-admin@0.4.1
47
+ ```
48
+
49
+ Bare package names resolve npm's current `latest`; exact application pins and a
50
+ committed lockfile keep an existing application from changing on a new release.
51
+
52
+ ## Generated applications
53
+
54
+ A generated application records its own versions. `urlcode init --with` writes a
55
+ `package.json` pinning the running runtime, the named extensions and their
56
+ declared peers at the exact versions resolved at generation time, after checking
57
+ that set against every declared peer range; `urlcode init --manifest` does the
58
+ same for a route-only project with the runtime alone; `urlcode-auth init` pins
59
+ this package and its peers. Plain `urlcode init` stays route-only and writes no
60
+ manifest, for projects whose runtime is managed elsewhere. Generation never runs
61
+ a package manager: `package-lock.json` exists only after the operator runs
62
+ `npm install` in the generated directory, and a pin taken from a local path or
63
+ tarball reproduces only where that path exists.
64
+
65
+ No upgrade command exists. A generated project moves to new versions by an
66
+ operator editing its manifest and re-installing. The issue that asked for this
67
+ (#212) describes a future command that would choose a tested compatible set,
68
+ show the changes and require explicit alpha selection; nothing here implements
69
+ that, and the pins above are only the groundwork it would need.
37
70
 
38
71
  The standalone `urlcode-template` is an external exact-version consumer: after a
39
72
  runtime release, update its dependency and starter through its own reviewed PR.
40
- It is not automatically released by the monorepo coordinator. The retired
73
+ The coordinator prepares and checks that PR after registry installation succeeds;
74
+ the template is a consumer update, not a fifth npm package. The retired
41
75
  `urlcode-docs`, `urlcode-middleware`, `urlcode-dynamic-link` and `urlcode-short`
42
76
  repositories are not release targets.
43
77
 
@@ -6,6 +6,7 @@ current instructions. See the [current roadmap](../../../ROADMAP.md),
6
6
  Remaining acceptance work is not declared complete by archiving this record.
7
7
 
8
8
  <!-- trust-model-prose: historical-file -->
9
+ <!-- local-links: historical-file -->
9
10
  <!-- guidance-claims: ignore-file -->
10
11
 
11
12
  # Public roadmap
@@ -6,6 +6,7 @@ current instructions. See the [current roadmap](../../../ROADMAP.md),
6
6
  Remaining acceptance work is not declared complete by archiving this record.
7
7
 
8
8
  <!-- trust-model-prose: historical-file -->
9
+ <!-- local-links: historical-file -->
9
10
  <!-- guidance-claims: ignore-file -->
10
11
 
11
12
  # Review: the extension model, its precedents and its alignment