@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
@@ -5,24 +5,51 @@ for UI, auth and admin. Core remains at the repository root and is explicitly
5
5
  included in the shared release inventory. Moving it is not required to use the
6
6
  same release checks and coordinator.
7
7
 
8
- ## Pull requests
8
+ ## Pull requests and main pushes
9
9
 
10
- Every PR starts `verify`. A complete Git diff selects one of two lanes:
10
+ Every PR and every push to main starts `verify`. A complete Git diff selects one
11
+ of two lanes:
11
12
 
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.
13
+ - **Prose:** root project Markdown, `docs/**/*.md`, `llms.txt`, `llms-full.txt`,
14
+ `benchmarks/agent/README.md`, `benchmarks/results/README.md` and each
15
+ package's `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and `GOVERNANCE.md` run
16
+ guidance/generated-resource checks, runtime audit, and the required container
17
+ job. CodeQL retains its repository policy.
18
+ - **Full:** all other changes, mixed changes, and empty, unclassifiable or
19
+ unavailable diffs run static checks once and core and workspace suites
20
+ separately. Both suites retain Linux on Node 22/24/26. Main adds
21
+ Windows/macOS on Node 24. PRs add those platform legs for runtime, CLI,
22
+ SQLite, fixture, dependency, workflow and unknown changes; known UI
23
+ presentation-only changes omit them. Package, action, cookbook,
24
+ reproducibility and operational checks retain their coverage.
21
25
 
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
+ A pull request is classified against its merge base; a push to main is
27
+ classified tip to tip from the event's `before`/`after` SHAs, so a force-push or
28
+ rewritten history is measured by what actually moved. Classification fails
29
+ closed: a missing, malformed or all-zero SHA (branch creation or deletion), and
30
+ history this checkout cannot read, select full verification. Scheduled and
31
+ manually dispatched runs are never classified from paths at all, so exact-SHA
32
+ release coverage cannot silently become a docs-only run.
33
+
34
+ The prose allowlist is deliberately narrow, and it is a list of reviewed,
35
+ non-executable contributor prose rather than "every Markdown file". Skills,
36
+ starters, recipes, examples, schemas, manifests, workflows, benchmark prompts,
37
+ tasks, answers and acceptance notes, and any package document that ships inside
38
+ a published tarball or is read by an agent surface (`README.md`, `SECURITY.md`,
39
+ `CONTRACT.md`, `THREAT-MODEL.md`, `IMPLEMENTATION-STATUS.md`, `AGENTS.md`,
40
+ `CHANGELOG.md`) select full checks. Anything feeding a generator stays in the
41
+ code lane. A rename from source into docs also selects full checks, because the
42
+ diff is read without rename detection and shows both paths. Every prose path is
43
+ still covered by the always-run `docs` job, which walks all authored Markdown.
44
+ No required workflow uses `paths-ignore`.
45
+
46
+ The always-run `docs` job runs `npm run check:docs`; in the full lane the
47
+ `static` job runs `npm run check:code`, which is the rest of `npm run check`.
48
+ The two together are exactly `npm run check`, which stays complete for local
49
+ use. This removes a duplicated dependency install plus seven repeated checks on
50
+ the same commit, not meaningful wall time: the sampled documentation checking
51
+ was about two seconds. Job names, lane selection and `verify-complete`
52
+ dependencies are unchanged.
26
53
 
27
54
  `verify-complete` accepts only the results specified by the successful plan.
28
55
  Failed, canceled, missing or unexpectedly skipped work fails the gate. Required
@@ -32,7 +59,8 @@ release publication separately requires verification of the exact main commit.
32
59
 
33
60
  ```sh
34
61
  npm run check:docs # prose checks without the runtime suite
35
- npm run ci:plan -- BASE_SHA HEAD_SHA
62
+ npm run check:code # everything in `check` except the prose checks
63
+ npm run ci:plan -- BASE_SHA HEAD_SHA # previews as a pull request outside Actions
36
64
  npm run ci:report -- RUN_ID # read GitHub job/step durations
37
65
  npm run ci:history -- 100 2026-09-19 # group historical timing samples
38
66
  npm run verify # full local validation remains available
@@ -43,123 +71,184 @@ CI uses `test:package:built` only after building in that same job. Core tests an
43
71
  workspace tests run in separate jobs to shorten their serial critical path;
44
72
  this increases job setup overhead and needs monitoring for runner queue pressure.
45
73
  After building all three extensions, the workspace job also runs the real
46
- `init --with auth,admin,ui` scaffold integration. Missing workspace outputs fail
74
+ `init --with ui,auth,admin` scaffold integration. Missing workspace outputs fail
47
75
  instead of silently skipping an absent external checkout.
48
76
  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:
77
+ The [follow-up measurements](CI-FOLLOWUP-2026-09-19.md) record early compact-main observations; issue #185 contains the later decision
78
+ and current sample sizes.
79
+
80
+ ## Version preparation and release ownership
81
+
82
+ Core remains at the repository root. Independent extension versions remain
83
+ supported; a coordinated version is an explicit maintainer choice, not a
84
+ permanent fixed-version policy. An explicitly selected stable version exits
85
+ Changesets alpha pre-mode; subsequent stable patches stay out of pre-mode. Feature PRs record workspace release intent in
86
+ Changesets; core release notes remain an explicit maintainer responsibility.
87
+
88
+ `release:check` checks manifest/lock versions and peer ranges, CLI and MCP
89
+ versions, generated plugin metadata, local peer compatibility, and channel policy.
90
+ The preparation helper updates these together, adds release notes and extension
91
+ changelogs, and records the version decision. Pending Changesets must be
92
+ explicitly consumed; they are archived under `.changeset/pre/` and their summaries
93
+ included in the release notes. Review the resulting diff and peer minimums.
72
94
 
73
95
  ```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
96
+ # Example only: choose the next intended version before executing.
97
+ npm run release:prepare -- --version 0.4.1 --consume-changesets
98
+ # Apply local edits on a clean non-main branch; no remote writes or publication:
99
+ npm run release:prepare -- --version 0.4.1 --consume-changesets --execute
77
100
  ```
78
101
 
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.
102
+ An optional `--notes PATH` adds reviewed maintainer notes. Dry runs do not change
103
+ files. Preparation rejects downgrades, reused local tags, dirty checkouts and
104
+ stale plans. A stable target removes `.changeset/pre.json`, publishes to npm
105
+ `latest`, and leaves the historical `alpha` pointer unchanged. Alpha targets
106
+ require existing alpha mode; the helper never silently re-enters prerelease mode.
107
+ It does not invoke a permanent Changesets fixed-version policy.
84
108
 
85
- Once the release itself is authorized, from a clean checkout of that exact SHA:
109
+ ## One-command release and resume
110
+
111
+ Inspect without writing:
112
+
113
+ ```sh
114
+ npm run release:status # registry channels, peer compatibility, tag SHAs
115
+ npm run release:plan # manifest-derived inventory
116
+ npm run release:run # ordered states at this checkout: pending/resume/unchanged
117
+ npm run release:run -- --version 0.4.1 --consume-changesets
118
+ ```
119
+
120
+ For an explicitly authorized coordinated release:
121
+
122
+ ```sh
123
+ npm run release:run -- --version 0.4.1 --consume-changesets --execute
124
+ ```
125
+
126
+ `--execute` authorizes the entire sequence: create the release branch/PR, wait
127
+ for checks and merge, run the release gates, create version tags, publish, verify
128
+ an installed consumer, and create/check/merge the starter update. It never
129
+ approves a review or bypasses a required check. A required human review still
130
+ blocks merging. No write or publication occurs without `--execute`.
131
+
132
+ The coordinator works in a temporary clone and prints its location. It prepares
133
+ `codex/release-VERSION`, or resumes the existing PR/branch after checking its
134
+ versions and receipt. It checks out the actual merged SHA and reinstalls that
135
+ commit's locked dependencies. Repeating the command discovers existing PRs,
136
+ gates, tags and workflow state rather than creating another version. Temporary
137
+ release clones are retained for diagnosis and can be removed after completion.
138
+
139
+ For an independently prepared release PR already merged to main, use a clean
140
+ checkout of its exact commit:
86
141
 
87
142
  ```sh
88
143
  npm run release:run -- --execute
89
144
  ```
90
145
 
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`.
146
+ The coordinator creates `codex/release-validation/SHA` at the already-merged
147
+ commit when gates are missing. An existing validation branch must name that
148
+ exact SHA; it is never moved. This lets main advance without changing the release
149
+ being tested. The branches remain as audit/resume references. It dispatches full
150
+ `ci.yml` and `candidate.yml`, reuses existing successful runs, and waits for
151
+ running ones. A failed gate stops with its run ID; diagnose it and rerun that
152
+ exact run before resuming. Compact PR/main checks cannot replace the full
153
+ OS/Node matrix or CodeQL on the selected commit.
154
+
155
+ Before creating any version tags, it downloads and verifies the candidate bundle.
156
+ A green run with missing artifacts does not authorize tags. New version tags are
157
+ annotated with the source commit, chosen candidate run ID and the SHA256 of its
158
+ signed manifest. The manifest binds every package and supporting asset by hash. Every package in a
159
+ resumed train must select that same candidate. Neither a later candidate of the
160
+ same source nor a newer main commit can silently replace the chosen bytes.
161
+ A successful rerun of the same candidate ID cannot substitute a changed bundle: its
162
+ manifest must still match the immutable checksum in the release tag.
163
+ After tags exist, rerun package publishers rather than the pinned candidate run.
164
+ If a later attempt of that candidate run fails, the coordinator stops even when
165
+ an earlier attempt succeeded; it does not infer which attempt should be trusted.
166
+
167
+ Publication is sequential: core → UI → auth → admin, skipping unchanged published
168
+ versions. Each publisher must succeed and its version must be readable through
169
+ npm's abbreviated install metadata, with a downloadable SHA512-verified tarball,
170
+ before dependents begin. Bounded retries handle propagation, transport failures,
171
+ 429 and server errors; authentication and integrity failures stop immediately.
172
+ Afterward, an external consumer with a fresh npm cache installs the four exact
173
+ registry versions, checks its peer tree and imports, and generates the combined
174
+ extension scaffold in dependency order (`ui,auth,admin`). The candidate archive
175
+ smoke uses the same scaffold check before any package is published.
176
+
177
+ The standalone starter helper updates the exact core pin, lockfile, matching
178
+ schema/docs links and guide from the installed published core package, then runs
179
+ validation/tests/audit/benchmark,
180
+ and opens a resumable PR. The coordinator waits for checks and merges it, checking
181
+ for a newer template pin immediately before merge. `--skip-template` explicitly
182
+ leaves this follow-up to the maintainer. To run only that follow-up:
183
+
184
+ ```sh
185
+ npm run release:template -- --version 0.4.1 --execute
186
+ ```
187
+
188
+ That standalone helper opens a PR but does not merge it. All helpers stop on
189
+ errors; rerun after diagnosis. A failed publisher is retried at most once per
190
+ coordinator invocation and must pass the original-byte recovery checks below.
191
+
192
+ The maintainer identity needs repository Contents, Actions and Pull requests
193
+ write, plus Checks read, on the affected repositories. GitHub App installations
194
+ should be scoped to URLCode and its template. No ruleset bypass or long-lived
195
+ npm token is needed. The workflow `GITHUB_TOKEN` must not create the triggering
196
+ version tags because its push events do not start ordinary push workflows.
105
197
  [GitHub App guidance](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/deciding-when-to-build-a-github-app)
106
198
 
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
199
+ ## Build once, publish verified bytes
200
+
201
+ The four publisher filenames remain unchanged for npm trusted-publisher identity.
202
+ The candidate builds in the digest-pinned environment, runs verification,
203
+ packaging and local operational checks, packs all four packages, and tests an
204
+ isolated combined consumer. The signed bundle contains all four archives,
205
+ SBOM, Homebrew formula, source/build manifest, train identity and checksums.
206
+ The manifest binds it to the candidate run as well as the commit.
207
+
208
+ Publishers verify the selected candidate's workflow provenance, exact source SHA,
209
+ run identity, manifest/package identities and hashes. They publish the selected
210
+ package's existing archive without rebuilding it. Auth/admin still run isolated
211
+ compatibility tests against their actual published peer floors; temporary test
212
+ builds do not replace the promoted archive. This preserves the distinction
213
+ between workspace compatibility and registry compatibility.
214
+
215
+ Each package's GitHub release stores the complete signed bundle for durable
216
+ recovery. Supporting sibling archives are candidate evidence: an independent
217
+ package release does not imply every sibling archive was published to npm.
218
+ Candidate and release Actions artifacts retain 90 days; retention is not an
219
+ archival guarantee. Keep independent last-good copies for deployment rollback.
220
+
221
+ ## Recovery, immutable tags and channels
222
+
223
+ A retry restores the original retained bundle, or recovers the complete verified
224
+ bundle from that package's GitHub release. Missing, incomplete or unverifiable
225
+ originals stop the retry. It never rebuilds archives or substitutes a new
226
+ candidate. If publication stopped before a complete durable release existed and
227
+ the retained artifact is gone, a new version may be required.
228
+
229
+ Existing npm versions must match SHA512 integrity; existing GitHub assets must
230
+ match byte for byte. Partial npm/GitHub/GHCR success is possible and cannot be
231
+ made atomic. Fix registry identity/settings where appropriate and resume the
232
+ original run. A source change requires a new version and tag. Never delete,
233
+ recreate, move or force-push version tags.
234
+
235
+ These recovery changes apply to releases made with the new workflows. They cannot
236
+ change the immutable workflow source at `0.4.0-alpha.3` or repair that historical
237
+ run by rerunning it. The missing-artifact behavior observed there is recorded in
238
+ [issue #223](https://github.com/jimhoyd-com/urlcode/issues/223).
239
+
240
+ npm uses OIDC with pinned npm 11.5.1. Alpha versions use npm/GHCR `alpha`, and
241
+ GitHub prerelease classification with `--latest=false`. Existing `latest`
242
+ pointers are not promoted by this flow. Core GHCR publication remains conditional
243
+ on `PUBLISH_CONTAINER=true`; its existing version/channel identity guards remain.
244
+ Historical GHCR verification is still a separate follow-up.
245
+
246
+ The [Immutable release tags rule](https://github.com/jimhoyd-com/urlcode/rules/23712319)
247
+ blocks updates/deletions of `v*` and `@jimhoyd/urlcode-*@*`, permits creation, and
248
+ has no bypass actors. Main protection is unchanged. Source and artifact checks
249
+ are not an independent security assessment, provider deployment or recovery
250
+ proof. The next explicitly authorized new release must exercise the complete
251
+ new promotion/recovery path. Progress and remaining work are recorded in
163
252
  [issue #185](https://github.com/jimhoyd-com/urlcode/issues/185).
164
253
 
165
254
  ### Windows fixture cleanup
@@ -193,7 +193,7 @@ project customizes an extension's *look* through `urlcode-ui`'s
193
193
  standard, or every extension author (and every project depending on one)
194
194
  either reinvents it or forks the extension. `urlcode-auth` and
195
195
  `urlcode-admin` independently hit this gap (auth/#35, admin/#32); auth's own
196
- [SPIKE-AUTH.md](https://github.com/jimhoyd-com/urlcode-auth/blob/main/docs/SPIKE-AUTH.md)
196
+ [SPIKE-AUTH.md](../packages/auth/docs/SPIKE-AUTH.md)
197
197
  already scoped a shape for `onSignUp`/`beforeRegister`/`onDelete`.
198
198
 
199
199
  **The pattern.** An extension with lifecycle events an author judges worth
@@ -336,16 +336,15 @@ boundary, not a JavaScript sandbox or an independent security review.
336
336
 
337
337
  ## Scaffolding with `init --with`
338
338
 
339
- `urlcode init <directory> --with auth,admin` produces the layered site the
339
+ `urlcode init <directory> --with ui,auth,admin` produces the layered site the
340
340
  [framework page](FRAMEWORK.md#the-composition-contract) describes in one
341
341
  command: the starter under `<directory>/app/`, one `host.mjs`, one `README.md`,
342
342
  and each extension's own operator files. Core never bundles or imports the
343
343
  extension packages at build time; at run time it resolves
344
344
  `@jimhoyd/urlcode-<name>` for each name with Node's package resolution from
345
- the invoking directory (so `npm install @jimhoyd/urlcode-auth` in that
346
- directory, from npm where the packages are published as `0.1.0-alpha.x`
347
- prereleases, is the normal path and what makes `--with auth` work), imports
348
- the package and calls its
345
+ the invoking directory. Install a compatible package set there, as shown in
346
+ [the framework guide](FRAMEWORK.md#the-composition-contract), before using
347
+ `--with ui,auth`; UI must activate before auth. Core imports the package and calls its
349
348
  `scaffold` export with this request:
350
349
 
351
350
  ```ts
@@ -401,6 +400,37 @@ Assembly rules, in `--with` order:
401
400
  review the project and pin it explicitly; the host is generated to require
402
401
  the pin, never to compute it.
403
402
 
403
+ ### Recorded versions
404
+
405
+ `init --with` also writes `<directory>/package.json`: a private manifest that
406
+ pins, at exactly the version that was resolved, the running runtime, every
407
+ extension named in `--with`, and every package those extensions declare in
408
+ `peerDependencies` (so `@jimhoyd/urlcode-ui` is pinned for an `auth,admin` site
409
+ although nobody named it). Before anything is written, the whole set is checked
410
+ against every declared peer range; an incompatible combination or a missing
411
+ required peer refuses and names it, leaving no directory behind.
412
+
413
+ Nothing is installed. The generated site has no `node_modules` and no
414
+ `package-lock.json` until you run `npm install` in it yourself, which the
415
+ command and the generated README both state as the next step. Reproducibility
416
+ comes from that install, not from generation.
417
+
418
+ - `--no-manifest` generates the site without a `package.json`, for a site whose
419
+ dependencies are managed elsewhere. Plain `urlcode init` is unchanged and
420
+ still writes no manifest; add `--manifest` to pin the runtime for a
421
+ route-only project too.
422
+ - `--pin <package>=<specifier>` records a specifier instead of the resolved
423
+ version, for local tarball or offline development
424
+ (`--pin @jimhoyd/urlcode-auth=file:/abs/urlcode-auth-0.1.0-alpha.6.tgz`). A
425
+ package installed from a local path or tarball is detected from npm's own
426
+ install record and pinned by that path without any flag; the README says so,
427
+ because such a pin only reproduces where that path exists.
428
+
429
+ There is no upgrade command. Moving a generated project to newer versions today
430
+ means editing its `package.json` and re-running `npm install` yourself; nothing
431
+ in this runtime selects a newer tested set, shows the change, or updates a
432
+ lockfile for you.
433
+
404
434
  Serving the result is the usual explicit host binding:
405
435
 
406
436
  ```sh
package/docs/FRAMEWORK.md CHANGED
@@ -7,26 +7,26 @@ claim here is implemented in the linked repository; nothing is roadmap.
7
7
 
8
8
  ## Four packages, one project shape
9
9
 
10
- | Package | Repository | What it adds | How a project declares it |
10
+ | Package | Source | What it adds | How a project declares it |
11
11
  |---|---|---|---|
12
- | `@jimhoyd/urlcode` | [urlcode](https://github.com/jimhoyd-com/urlcode) | The runtime: YAML routes, functions and middleware (trusted by default, `sandbox: true` opt-in), pages and assets, policies, site conventions, CLI, provider adapters, the extension contract | `urlcode.yaml` with `version: "1"` |
13
- | `@jimhoyd/urlcode-ui` | [urlcode-ui](https://github.com/jimhoyd-com/urlcode-ui) | Shared presentation: escaped templates, shadcn/ui partials, one stylesheet with light and dark, themes, translations, the `ui` extension that serves the kit's assets | `extensions.ui` plus an asset mount route |
14
- | `@jimhoyd/urlcode-auth` | [urlcode-auth](https://github.com/jimhoyd-com/urlcode-auth) | Accounts: password, passkeys, OpenID Connect, email codes, TOTP, recovery, sessions, roles, registration modes, account page, operator CLI | `extensions.auth` plus an `/account/*` mount and `policies.extensions.auth` on protected routes |
15
- | `@jimhoyd/urlcode-admin` | [urlcode-admin](https://github.com/jimhoyd-com/urlcode-admin) | Administration: users, sessions, roles, audit, registration approval, two-person cases, support impersonation, health | `extensions.admin` plus an `/admin/*` mount |
16
-
17
- The core is Apache-2.0 and released. The three extension packages are
18
- Apache-2.0 and published to npm as alphas (`@jimhoyd/urlcode-ui@0.1.0-alpha.4`,
19
- `@jimhoyd/urlcode-auth@0.1.0-alpha.2`, `@jimhoyd/urlcode-admin@0.1.0-alpha.2`,
20
- on core `0.4.0-alpha.1`). An alpha on npm is a distribution channel, not an
21
- endorsement: the source is complete, but independent review, deployment
22
- evidence and an accessibility assessment are still pending
12
+ | `@jimhoyd/urlcode` | this repository | The runtime: YAML routes, functions and middleware (trusted by default, `sandbox: true` opt-in), pages and assets, policies, site conventions, CLI, provider adapters, the extension contract | `urlcode.yaml` with `version: "1"` |
13
+ | `@jimhoyd/urlcode-ui` | [`packages/ui`](../packages/ui) | Shared presentation: escaped templates, shadcn/ui partials, one stylesheet with light and dark, themes, translations, the `ui` extension that serves the kit's assets | `extensions.ui` plus an asset mount route |
14
+ | `@jimhoyd/urlcode-auth` | [`packages/auth`](../packages/auth) | Accounts: password, passkeys, OpenID Connect, email codes, TOTP, recovery, sessions, roles, registration modes, account page, operator CLI | `extensions.auth` plus an `/account/*` mount and `policies.extensions.auth` on protected routes |
15
+ | `@jimhoyd/urlcode-admin` | [`packages/admin`](../packages/admin) | Administration: users, sessions, roles, audit, registration approval, two-person cases, support impersonation, health | `extensions.admin` plus an `/admin/*` mount |
16
+
17
+ All four are Apache-2.0. The `0.4.1` release line aligns their stable versions;
18
+ check `npm run release:status` for publication progress. A stable npm channel is
19
+ not an independent assessment: review, deployment evidence and an accessibility
20
+ assessment are still pending
23
21
  ([issue 58](https://github.com/jimhoyd-com/urlcode/issues/58)). Their status
24
- files say exactly what is built: [auth](https://github.com/jimhoyd-com/urlcode-auth/blob/main/IMPLEMENTATION-STATUS.md),
25
- [admin](https://github.com/jimhoyd-com/urlcode-admin/blob/main/IMPLEMENTATION-STATUS.md),
26
- [ui](https://github.com/jimhoyd-com/urlcode-ui/blob/main/IMPLEMENTATION-STATUS.md).
27
- Which core version each package supports, how it declares that, and the order
28
- in which a core change reaches the downstream repositories are recorded in
29
- [core version alignment](VERSION-ALIGNMENT.md).
22
+ files say exactly what is built: [auth](../packages/auth/IMPLEMENTATION-STATUS.md),
23
+ [admin](../packages/admin/IMPLEMENTATION-STATUS.md),
24
+ [ui](../packages/ui/IMPLEMENTATION-STATUS.md).
25
+ The current version of each package is its own manifest, and the peer ranges it
26
+ declares are in that manifest too; do not read a version number out of this
27
+ page. How versions, channels and release tags line up is recorded in
28
+ [package and channel alignment](VERSION-ALIGNMENT.md), and `npm run
29
+ release:status` reports the live registry and tag state.
30
30
 
31
31
  ## The ladder
32
32
 
@@ -43,8 +43,10 @@ Each rung's YAML is valid on every rung above it.
43
43
  3. **Functions and middleware.** `function` routes and ordered `middleware`
44
44
  in JavaScript, trusted and in-process by default; a route declaring
45
45
  `sandbox: true` runs isolated instead (QuickJS inside WebAssembly, fresh
46
- heap per call, no Node, filesystem or network). Secrets reach a function
47
- only through an operator grant pinned to the project revision.
46
+ heap per call, no Node, filesystem or network). The `env`/`secrets` the
47
+ runtime injects into a function come only from an operator grant pinned to
48
+ the project revision; the grant governs that injected context, not the
49
+ ambient Node environment trusted in-process code can reach on its own.
48
50
  4. **Accounts.** The `auth` extension: sign-in, registration, MFA, account
49
51
  page and protected routes. The operator installs it in a host file outside
50
52
  the project; YAML only declares the mount and configuration.
@@ -61,17 +63,19 @@ was removed from core. A `urlcode-dynamic-link` package owned them the same way
61
63
  package occupies this rung today.
62
64
 
63
65
  Rungs 1 to 3 need only the core package. Rungs 4 to 6 need the extension
64
- packages, installed from npm as `0.1.0-alpha.x` prereleases, and a Node host
65
- with a patched SQLite build; see each repository's README for the exact
66
- requirement.
66
+ packages installed from npm and a Node host with a
67
+ patched SQLite build; see each package's README ([auth](../packages/auth/README.md),
68
+ [admin](../packages/admin/README.md), [ui](../packages/ui/README.md)) for the
69
+ exact requirement.
67
70
 
68
71
  ## The composition contract
69
72
 
70
- An extended project starts with the packages and one command:
73
+ After the aligned `0.4.1` packages are published, an extended project starts
74
+ with the packages and one command:
71
75
 
72
76
  ```sh
73
- npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin
74
- urlcode init my-site --with auth,admin
77
+ 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
78
+ urlcode init my-site --with ui,auth,admin
75
79
  ```
76
80
 
77
81
  Installing from npm is the normal path; `scripts/pack-sources.mjs` still builds
@@ -140,7 +144,7 @@ See each package README for its complete kit registration example.
140
144
  urlcode serve --project /absolute/site --host-file /absolute/operator/host.mjs --origin https://site.example
141
145
  ```
142
146
 
143
- `urlcode init <dir> --with auth,admin` writes this layout in one step: it
147
+ `urlcode init <dir> --with ui,auth,admin` writes this layout in one step: it
144
148
  resolves each installed `@jimhoyd/urlcode-<name>` from the current directory,
145
149
  calls its `scaffold` export and merges the fragments into `app/urlcode.yaml`,
146
150
  one `host.mjs` and one `README.md`, refusing before writing anything when a
@@ -151,6 +155,17 @@ administrator from JSON on stdin. `inspectExtensionRevision(project)` prints
151
155
  the SHA-256 that `projectSha256` must carry; changing extension YAML, policies
152
156
  or mounts changes the revision and needs an explicit operator reapproval.
153
157
 
158
+ The presentation tooling composes the same way, by naming packages rather than
159
+ depending on them. `urlcode-ui` is the kit alone until
160
+ `--extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin` names the packages
161
+ that ship the other namespaces: each is resolved from `--project` with Node
162
+ package resolution and imported for the namespace it exports, so `list`,
163
+ `doctor`, `eject`, `preview` and `copy --missing` cover the `auth/*` and
164
+ `admin/*` templates and copy the host registers, and a project override of an
165
+ extension template is checked against the shipped view model. A package that is
166
+ not installed is skipped. `urlcode init --with` writes the commands with the
167
+ flag already set; `@jimhoyd/urlcode-ui` still depends on neither peer.
168
+
154
169
  ## Rules an agent must follow
155
170
 
156
171
  These are the facts that keep generated projects valid. The full matrix is in
@@ -195,8 +210,8 @@ These are the facts that keep generated projects valid. The full matrix is in
195
210
  | Need | Read |
196
211
  |---|---|
197
212
  | Write or change routes | [YAML guide](YAML-GUIDE.md), [field reference](YAML-REFERENCE.md), [cookbook](../examples/cookbook/README.md) |
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) |
199
- | Add administration | [admin README](https://github.com/jimhoyd-com/urlcode-admin#readme) |
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) |
213
+ | Add accounts | [auth README](../packages/auth/README.md), [auth security](../packages/auth/SECURITY.md) |
214
+ | Add administration | [admin README](../packages/admin/README.md) |
215
+ | Restyle every page | [ui README](../packages/ui/README.md), [ui contract](../packages/ui/CONTRACT.md) |
201
216
  | Write an extension | [extensions](EXTENSIONS.md), [extension model review](archive/2026-09-19/SPIKE-EXTENSION-MODEL.md) |
202
217
  | Run it | [operations](OPERATIONS.md), [install](INSTALL.md), [deployment checks](DEPLOYMENT-CHECKS.md) |