@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,322 @@
1
+ # CI and monorepo release audit — 2026-09-19
2
+
3
+ Status: historical findings and recommendation. The accompanying implementation
4
+ and current commands are documented in [the development pipeline](DEVELOPMENT-PIPELINE.md).
5
+ The proposed helper table below records the audit design, not the installed command list. Source inspected:
6
+ `3a3d9adf6f0ec53faa078c93dfb977aa5ac416f7`; GitHub settings, runs, tags,
7
+ releases and npm dist-tags were read on September 19. Concurrent PRs may change
8
+ this snapshot. This review does not authorize publishing or changing protections.
9
+
10
+ ## Recommendation
11
+
12
+ Keep npm workspaces and Changesets, independent package versions, and the
13
+ existing public package names. Add an explicit change classifier for CI and one
14
+ release coordinator for all four packages. Preserve the existing required gate
15
+ and CodeQL policy. Stop using tag pushes as the place to discover whether a
16
+ release is buildable. Prepare and validate an immutable release plan first;
17
+ create tags from that plan once; resume partial publication without moving them.
18
+
19
+ Do this incrementally. A repository layout migration, Nx, Turborepo, remote
20
+ caching, or fewer security assertions is not a prerequisite for faster PRs.
21
+
22
+ ## Measured bottleneck
23
+
24
+ [PR #181](https://github.com/jimhoyd-com/urlcode/pull/181) changed only
25
+ `AGENTS.md` (9 additions, 4 deletions). Its
26
+ [verify run](https://github.com/jimhoyd-com/urlcode/actions/runs/35475753912)
27
+ took **9m08s** from creation to completion.
28
+
29
+ | Job | Execution time |
30
+ | --- | ---: |
31
+ | verify, Linux / Node 22 | 8m24s, after 39s from workflow creation to job start |
32
+ | verify, Linux / Node 24 | 5m49s |
33
+ | verify, Linux / Node 26 | 5m36s |
34
+ | build-fidelity | 27s |
35
+ | action | 25s |
36
+ | container | 22s |
37
+ | audit | 19s |
38
+ | verify-complete | 2s |
39
+
40
+ Within Node 22, `npm run verify` took 7m33s. Its core tests took 281.7s,
41
+ auth tests 92.9s, admin tests 23.6s, and UI tests 2.7s. These suites run
42
+ **serially**. Lint, typecheck and source checks took roughly 29s combined.
43
+ Package verification subsequently took 28s; drills took 7s. This is chiefly
44
+ execution time, not queueing, in this example.
45
+
46
+ The same pattern appears in
47
+ [PR #180's run](https://github.com/jimhoyd-com/urlcode/actions/runs/35475563183):
48
+ 8m38s overall, with Node 22 the longest job. These are sampled runs, not a
49
+ long-term percentile study or a demonstrated performance gain.
50
+
51
+ Conversely, the
52
+ [UI alpha.6 release](https://github.com/jimhoyd-com/urlcode/actions/runs/35475220590)
53
+ took 19m23s: approximately 11m24s before the job started, then 7m37s in the
54
+ whole-repository verify step. Both queue pressure and unnecessarily broad
55
+ release verification matter there; the API does not establish the queue's cause.
56
+
57
+ The workflow already avoids branch-push plus PR duplication and cancels stale
58
+ PR runs. Its three PR matrix legs repeat lint, types, generated-file checks,
59
+ builds, every package's tests, package smoke tests and drills. Main expands this
60
+ to nine OS/Node combinations. `test:package` builds again after `verify` has
61
+ built; candidate/core release explicitly build before calling `verify`, which
62
+ builds again, and then `test:package`, which builds a third time.
63
+
64
+ ## What the checks buy
65
+
66
+ | Check | Purpose | Proposed placement |
67
+ | --- | --- | --- |
68
+ | Documentation and generated resources | Prevent stale references, guidance and authoring resources | Every PR; standalone fast lane for prose changes |
69
+ | Lint and typecheck | Source and contract errors | Once per relevant change on a canonical Node version |
70
+ | Core and extension regressions | Behavioral, integration and isolation invariants | Affected packages and downstream consumers on PRs; full validation for releases |
71
+ | Supported Node versions | Detect runtime compatibility failures | Retain all three versions for runtime changes initially; avoid repeating static checks |
72
+ | Windows/macOS | Path, process, filesystem and platform differences | Targeted PR coverage plus full main coverage initially |
73
+ | Package install smoke | Prove shipped archives and declarations work | Relevant package/CLI/starter changes and releases; lightweight pack inventory for shipped-doc changes |
74
+ | Container | Validate the shipped execution environment | Keep existing required job; early optimization can leave this cheap job alone |
75
+ | Action smoke | Exercise the consumer-facing composite action | Runtime/action/starter/build changes |
76
+ | Build fidelity | Detect nondeterministic emitted files and packs | Build/package/toolchain changes and releases |
77
+ | Runtime dependency audit | Detect known advisories | Keep initially: measured cost is small; also run on a schedule and before release |
78
+ | CodeQL | Static security analysis | Preserve current enforced policy; not the observed critical path |
79
+ | Operational drills | Local lifecycle/recovery regression signal | Relevant runtime changes, main and release validation |
80
+ | Real-model evals | Authoring quality regression | Existing weekly/manual workflow; no need to put it on ordinary PRs |
81
+
82
+ Do not remove timeout, worker replacement, sandbox or authentication tests to
83
+ save time. Profile and change their scheduling or fixtures while preserving the
84
+ behavior they establish. The longest sampled core subtests included explain
85
+ versus runtime agreement (~35s), a slim-image installer test (~21s), and sitemap
86
+ limits (~21s). Investigate repeated CLI startups and repeated project loading;
87
+ these measurements do not yet establish which internal operation dominates.
88
+ Auth's ~93s warrants separate profiling, including password hashing, without
89
+ changing production security parameters to speed up tests.
90
+
91
+ ## PR workflow shape
92
+
93
+ Always start the workflow. A small, tested classifier compares the complete PR
94
+ diff against its base, including renamed/deleted paths, and emits an explicit
95
+ job plan. Missing history, unknown paths, classifier errors and workflow/shared
96
+ toolchain changes select full verification. Do not infer safety just from a
97
+ `.md` extension: starter, recipe and executable authoring inputs need their
98
+ own categories. Core changes affect all consumers; UI affects auth/admin; auth
99
+ affects admin; admin-only changes need not retest all of core.
100
+
101
+ For a prose-only PR, run guidance and generated-resource checks, local link and
102
+ reference checks, and applicable package file-inclusion assertions. Extract this
103
+ from today's `check` rather than calling the full source/test syntax walk.
104
+ Changes to generator code, manifests, lockfiles, schemas, executable examples or
105
+ CI configuration must leave the prose-only lane.
106
+
107
+ For code PRs, run static checks once, build required outputs/styles in dependency
108
+ order, and schedule core/UI/auth/admin tests independently. Start by preserving
109
+ the current Node coverage. Pilot two balanced core-test shards on the slowest
110
+ leg, measure runner-minutes and wall time, and increase only if the gains justify
111
+ the extra jobs. Keep the full local `npm run verify` entry point.
112
+
113
+ The `verify-complete` gate must know which jobs the classifier required. It must
114
+ reject failure, cancellation, missing results and unexpected skips; accept a
115
+ skip only when the validated plan explicitly marks that job unnecessary. Test
116
+ the gate's failure paths. The present gate rejects every skipped dependency, so
117
+ adding `if:` conditions without updating its contract will break merging.
118
+
119
+ Do not add workflow-level `paths-ignore` to a required workflow: GitHub documents
120
+ that such skipped workflows can leave required checks pending. Use job selection
121
+ inside an always-triggered workflow instead.
122
+ [GitHub documentation](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/skip-workflow-runs)
123
+
124
+ The live main ruleset requires `container` and `verify-complete`, and separately
125
+ enforces CodeQL findings. It has no bypass actors. Its strict up-to-date setting
126
+ is **false**, contrary to CONTRIBUTING's claim that an up-to-date branch is
127
+ required. Preserve protections in the first implementation; reconcile that
128
+ documentation. A later policy change needs a deliberate review, not an implicit
129
+ side effect of renaming jobs.
130
+
131
+ Windows failures are real: the
132
+ [main run at cc582f2](https://github.com/jimhoyd-com/urlcode/actions/runs/35475923267)
133
+ failed on `spawnSync npm.cmd EINVAL` in UI packaging. PR
134
+ [#182](https://github.com/jimhoyd-com/urlcode/pull/182) already addresses this.
135
+ Linux-only PR tests let this reach main. Add a focused Windows packaging/process
136
+ smoke for relevant changes before considering a smaller main matrix. Keep the
137
+ existing full main matrix during rollout; moving exhaustive coverage to nightly
138
+ is a later tradeoff, and release candidates must still pass full validation.
139
+
140
+ Initial goals: prose PRs under 90 seconds excluding runner queueing; ordinary
141
+ code PRs under 5 minutes. These are targets, not measured promises. Report queue
142
+ time separately from execution, and compare p50/p95 over at least 20 runs per
143
+ change class before deciding the optimization succeeded.
144
+
145
+ ## Release and tag findings
146
+
147
+ 1. **Tags have actually moved across source revisions.** Release history for
148
+ `v0.4.0-alpha.1` records five distinct commits: `b5cd619`, `871dd87`,
149
+ `bb0f9e6`, `84e45ea`, then successful `8dabc7e`. The sampled history also
150
+ shows multiple commits for `v0.3.0`, `v0.2.0`, and `v0.1.0`. This establishes
151
+ reuse of tag names across commits, not who changed them or whether every
152
+ failed attempt published an artifact. The live ruleset inventory contained
153
+ only a branch ruleset, not tag protection.
154
+ 2. **An ancestor of main is not necessarily a validated release commit.** All
155
+ four release workflows check main ancestry, but do not require the exact
156
+ commit's main CI result. UI alpha.6 published from `8fa7f8b` while that
157
+ commit's main verify run failed on Windows. Release validation was Linux-only.
158
+ 3. **Core alphas are classified as normal GitHub releases.** Both alpha.1 and
159
+ alpha.2 had `prerelease: false`; GitHub's latest-release endpoint returned
160
+ alpha.2. Core's release creation omits the prerelease flag. npm correctly
161
+ separates its `alpha` channel; this is a distinct GitHub-channel defect.
162
+ 4. **Core container publication always updates `latest`.** `release.yml` tags
163
+ and pushes `latest` even for prereleases when `PUBLISH_CONTAINER` is enabled.
164
+ The code path is confirmed; this audit did not establish whether an alpha
165
+ actually overwrote the live GHCR tag.
166
+ 5. **Candidate and release have already diverged.** Candidate reads Dockerfile
167
+ into two shell variables, swallowing `AS build` into the image variable and
168
+ failing the digest regex. The same parse was fixed only in release. Running
169
+ candidate's parser locally against the current Dockerfile reproduced failure.
170
+ The last listed candidate successes predate this snapshot; no new candidate
171
+ was dispatched during this audit. Existing release tests check release's parser,
172
+ not both paths.
173
+ 6. **Existence is treated as sufficient for retry.** Publish steps skip an
174
+ existing npm version without checking its integrity against the candidate;
175
+ GitHub release uploads use `--clobber`. The fidelity job tests repeated packing
176
+ in one checkout, not equality to a prior publication. Extension builds use
177
+ floating Node 22 and registry-installed peers, so same-commit reproducibility
178
+ across days is not established. Preserve and compare the original artifact.
179
+ 7. **Concurrency is scoped to a tag.** Two different versions of one package can
180
+ publish concurrently; there is no shared release-train order or monotonic
181
+ channel guard. A late old release could update a channel after a newer one.
182
+ 8. **Changesets is only partially in charge.** Version 3.0.3 is installed and
183
+ pre-mode is enabled, with independent versions. Core is outside its workspace
184
+ package set. The tag checker checks trigger disjointness, not remote tag SHA,
185
+ registry integrity, missing changesets, peer compatibility or channel state.
186
+ PR [#183](https://github.com/jimhoyd-com/urlcode/pull/183) already tackles a
187
+ Changesets peer-range rewrite and stale channel documentation; build on it.
188
+ 9. **Release documentation contains competing snapshots.** VERSION-ALIGNMENT
189
+ still describes old repository pins, and the Changesets README says only UI
190
+ is covered and publishing is not wired up, despite a successful monorepo UI
191
+ release. RELEASE-SECURITY describes candidate/release as sharing a path that
192
+ has demonstrably diverged. Generate the live inventory; retain policy in prose.
193
+
194
+ The actual npm channels at audit time were:
195
+
196
+ | Package | `latest` | `alpha` |
197
+ | --- | --- | --- |
198
+ | core | 0.3.0 | 0.4.0-alpha.2 |
199
+ | ui | 0.1.0-alpha.5 | 0.1.0-alpha.6 |
200
+ | auth | 0.1.0-alpha.3 | 0.1.0-alpha.3 |
201
+ | admin | 0.1.0-alpha.3 | 0.1.0-alpha.3 |
202
+
203
+ Different channel values are not inherently drift. Historical extension `latest`
204
+ values are already alphas; do not silently move them. Define channel intent,
205
+ test the documented install combinations against peer ranges, and report
206
+ deviations. An old core release tag also should not follow current main:
207
+ unreleased development is normal; silently relabeling a published version is not.
208
+
209
+ ## One release process for four independently versioned packages
210
+
211
+ Use Changesets for reviewed release intent and changelogs, with `fixed` and
212
+ `linked` remaining empty. Require a changeset or a reviewed no-release reason
213
+ for changes to publishable behavior. A routine feature PR need not manually
214
+ bump versions: an accumulated release PR owns version changes, lockfile updates,
215
+ peer-range changes and changelogs together. This replaces VERSION-ALIGNMENT's
216
+ instruction to bump on every source-changing PR once the new flow is implemented.
217
+ [Changesets configuration](https://github.com/changesets/changesets/blob/main/docs/config-file-options.md)
218
+
219
+ Near term, keep root core where it is and include it explicitly in the common
220
+ release inventory and plan, with its existing version update reviewed in the
221
+ same release PR. Do not pretend `changeset version` covers it. Longer term,
222
+ evaluate moving core to `packages/core` with a private tooling root so Changesets
223
+ can own all four uniformly. That is a separate migration with CLI/container/
224
+ starter/package tests, not part of the urgent CI fix. Preserve existing tag
225
+ formats during this transition; any future new core tag format needs an explicit
226
+ compatibility decision for install.sh, Homebrew, action consumers and old links.
227
+
228
+ Proposed lifecycle:
229
+
230
+ 1. A protected release PR produces versions and a plan listing package name,
231
+ directory, version, tag, channel and dependency order. Manifests supply the
232
+ versions; the plan does not become a second hand-maintained version database.
233
+ 2. After merge, select and freeze its exact main SHA. Require that SHA's main
234
+ checks and release-specific validation; never substitute the newest passing
235
+ run from another commit. A canceled/missing full check must be run for the
236
+ selected SHA. Run without publication credentials during preparation.
237
+ 3. Build once with locked tooling, pack the proposed packages, and install the
238
+ actual tarballs together in a clean consumer project without development
239
+ export conditions. Separately test declared published peer floors. Where a
240
+ new floor belongs to this release, test its candidate tarball before publishing
241
+ and confirm registry resolution after its predecessor publishes.
242
+ 4. Sign and retain artifacts and a manifest binding source SHA, package versions,
243
+ dependency versions, lock hash and artifact hashes. Exercise this same prepare
244
+ path for a manual candidate; candidate must not be a second copied implementation.
245
+ 5. Create each expected tag once at that SHA. Refuse a remote tag at a different
246
+ SHA. Publish the prepared bytes in dependency order: core/UI before auth, auth
247
+ before admin, including only packages that need a release. Serialize publication
248
+ across the release train. Keep write/OIDC permissions confined to publication.
249
+ 6. On retry, reconcile each artifact with npm integrity, tags, GitHub assets and
250
+ container digests. Identical means complete; a mismatch stops. A registry
251
+ timeout is not proof a version is absent. Never clobber a different asset or
252
+ move a version tag. Reuse retained bytes; if unavailable, require a rebuild
253
+ that matches recorded hashes. A source fix needs a new version and release PR.
254
+ 7. Mark prereleases consistently on GitHub/npm/GHCR, and update mutable channels
255
+ only according to explicit policy with a guard against regressions. Summarize
256
+ partial success clearly; separate registries cannot form an atomic transaction.
257
+
258
+ Protect release tag namespaces against update/deletion, with a narrowly defined
259
+ creation path. This is a proposed strengthening, not a settings change made by
260
+ the audit. Avoid a token-created-tag event chain: coordinate preparation and
261
+ publication explicitly in workflow jobs/dispatch, rather than depending on
262
+ another push workflow being triggered by a workflow-created tag.
263
+
264
+ Initially retain the existing four trusted-publisher workflow filenames as thin
265
+ wrappers around shared checked-in helpers. Consolidating to a new filename is a
266
+ registry trust migration: verify each package's npm publisher identity and direct
267
+ publish permission first. A dry run cannot prove the OIDC exchange works.
268
+ [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/)
269
+
270
+ ## Small helper surface
271
+
272
+ These names describe proposed commands, not commands implemented by this audit.
273
+
274
+ | Command | Contract |
275
+ | --- | --- |
276
+ | `npm run ci:plan -- --base SHA --head SHA` | Print change class, affected package closure, required jobs and why; deterministic and fail closed |
277
+ | `npm run ci:report -- --run ID` | Read GitHub job/step timings, separate queue and execution, show failed checks and run links |
278
+ | `npm run release:status` | Read manifests, Changesets state, Git tags, GitHub releases, npm versions/channels/integrity and peer compatibility; no mutations |
279
+ | `npm run release:plan -- --sha SHA` | Produce the exact package/version/tag/channel/order plan, including root core |
280
+ | `npm run release:prepare -- --plan FILE` | Validate, build, pack, smoke-test and write immutable artifact manifest; no publication |
281
+ | `npm run release:publish -- --plan FILE` | CI-only mutation path; validate commit/checks/tags/artifacts, publish or resume idempotently |
282
+
283
+ Use structured JSON outputs plus a short human summary. Share the version/tag/
284
+ channel parser, manifest inventory, Docker image parser and integrity comparison
285
+ between candidate and release. Test those contracts by execution, not only by
286
+ regex checks that particular shell snippets exist in YAML. Add workflow syntax
287
+ validation and fixtures for wrong tag, wrong SHA, mismatched version, prerelease,
288
+ partial publish, existing unequal artifact, registry outage and peer-floor failure.
289
+
290
+ ## Rollout order and acceptance
291
+
292
+ 1. **Release correctness:** shared candidate parser, correct GitHub/GHCR alpha
293
+ handling, read-only status/preflight, immutable retry checks and exact-SHA
294
+ main-CI gate. Coordinate with #183; preserve the current publisher identities.
295
+ 2. **Immediate PR speed:** prose lane and tested classifier/gate. Keep existing
296
+ required check names, audit, CodeQL and container. Demonstrate a docs-only PR
297
+ finishing quickly and a deliberately failed required job blocking the gate.
298
+ 3. **Code throughput:** split static/core/workspace checks, remove redundant
299
+ builds, profile and shard the slow suites, add targeted Windows coverage.
300
+ Verify equivalent test coverage and measure queue/cost as well as latency.
301
+ 4. **Release coordination:** one Changesets release PR and package inventory,
302
+ one preparation path, serialized dependency-aware publication and immutable
303
+ reconciliation. Prove partial-failure recovery on a future authorized release.
304
+ 5. **Optional structural work:** core workspace migration and reconsideration of
305
+ the nine-leg main matrix after measured coverage/performance evidence.
306
+
307
+ This audit made no release, tag, registry-channel or branch-protection changes.
308
+ It inspected live evidence and reproduced the candidate parser failure locally;
309
+ it did not execute a new full matrix, deploy containers, change npm trust, or
310
+ prove end-to-end recovery. Passing CI is not an independent security assessment.
311
+
312
+ ## Implementation follow-through
313
+
314
+ The user subsequently authorized implementation. The accompanying PR installs
315
+ the conservative prose lane, separate core/workspace jobs, shared release
316
+ helpers, live inventory and an explicit sequential tag coordinator. Root core
317
+ remains outside Changesets; whole-train preparation before any tag and finer
318
+ package selection are tracked in [#185](https://github.com/jimhoyd-com/urlcode/issues/185).
319
+ The user explicitly approved the immutable-tag rule, now active as
320
+ [23712319](https://github.com/jimhoyd-com/urlcode/rules/23712319), without bypass.
321
+ The two historical core alpha release flags were corrected and GitHub latest
322
+ restored to stable v0.3.0. No tags or artifact bytes were rewritten.
package/docs/CI.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Checking a project on GitHub
2
2
 
3
+ For this repository's own PR checks and releases, see the
4
+ [development pipeline](DEVELOPMENT-PIPELINE.md). This page describes the action
5
+ used by applications built with URLCode.
6
+
3
7
  `jimhoyd-com/urlcode/action` is a composite GitHub Action for a URLCode
4
8
  *project*: a repository with a `urlcode.yaml`. It runs the same local checks
5
9
  you run by hand and, on pull requests, keeps one comment up to date with the
@@ -81,8 +85,9 @@ On `pull_request` events the action fetches the base commit, checks it out into
81
85
  a temporary git worktree, runs `urlcode routes` there and on the head with the
82
86
  head's runtime, and renders `routes --compare` as Markdown: one table each for
83
87
  added, removed and changed routes, or "No route changes". A changed route lists
84
- each differing field (handler, methods, state, middleware count, policies,
85
- generated marker or policy description) with its before and after value.
88
+ each differing field (handler, methods, state, `sandbox`, `sandboxReason`,
89
+ middleware count, policies, generated marker or policy description) with its
90
+ before and after value.
86
91
 
87
92
  `action/comment.mjs` then finds the pull request's comments for the marker
88
93
  `<!-- urlcode-route-diff project="<project>" -->` and updates that comment,
@@ -135,7 +140,7 @@ urlcode routes --project . --compare /tmp/base.json --format markdown
135
140
 
136
141
  Or `make validate`, `make test` and `make audit ARGS='--expect-routes 2'`
137
142
  from the starter Makefile. The runtime repository exercises the action on
138
- every pull request against `examples/cookbook` (`.github/workflows/ci.yml`,
143
+ full-lane pull requests against `examples/cookbook` (`.github/workflows/ci.yml`,
139
144
  job `action`) with the packed tarball as `runtime`, and `test/action.test.ts`
140
145
  checks that `action.yml` is a composite action with the inputs above and that
141
146
  every third-party action it or the starter workflow uses is pinned to a
@@ -0,0 +1,278 @@
1
+ # Codebase, tooling and documentation audit — 2026-09-20
2
+
3
+ Reviewed commit: `bca8ac7` (core 0.4.0-alpha.2, auth 0.1.0-alpha.5,
4
+ admin 0.1.0-alpha.4, UI 0.1.0-alpha.6). Local environment: macOS,
5
+ Node 26.8.2. Date is UTC. This report records findings; it does not change
6
+ runtime behavior or remove supported features.
7
+
8
+ The trusted default is implemented correctly in the dispatch paths inspected:
9
+ only a route declaring `sandbox: true` enters the QuickJS pool. Ordinary
10
+ project functions and middleware run in Node. Host access by trusted code is
11
+ intentional, not an audit finding. Binding grants scope injected context;
12
+ they do not confine trusted code's independent filesystem, environment or
13
+ network access. Opt-in sandbox isolation and provider refusals remain necessary.
14
+
15
+ ## Scope and method
16
+
17
+ Inventoried 966 tracked files. Reviewed the implemented specification,
18
+ contribution/security instructions, runtime activation/dispatch, module loading,
19
+ TypeScript authoring, inspection/manifests/diffs, extension hooks, workspace
20
+ contracts, package/build/check scripts, CI/release configuration, starter,
21
+ example/recipe coverage and documentation indexes. Used targeted source review,
22
+ the complete local verification suite and synthetic reproductions. This is a
23
+ repository-wide engineering audit, not a claim that every line or execution
24
+ path received independent security review.
25
+
26
+ Scanned tracked non-archive/non-spike Markdown for local links and YAML fences:
27
+ 64 YAML fences, 49 route/project-shaped candidates. Added only an omitted
28
+ format version or the documented route wrapper for schema checks. Classified
29
+ partial illustrations separately from complete examples. Semantic constraints,
30
+ external services and prose fragments require more than schema validation.
31
+
32
+ Searched existing repository issues before filing. Five new issues and new
33
+ evidence on existing issue 168 retain all actionable findings below.
34
+
35
+ ## Findings, ordered by practical impact
36
+
37
+ ### 1. Inspection hides middleware execution-mode changes — P2
38
+
39
+ [Issue 199](https://github.com/jimhoyd-com/urlcode/issues/199).
40
+
41
+ For a `respond` route with `middleware: [mw.mjs]`, switching `sandbox` from
42
+ true to false produces identical manifest route records and an empty route
43
+ diff. This changes execution from QuickJS to full Node without appearing in
44
+ those review surfaces. The overall manifest digest can change; it does not
45
+ explain the change to reviewers.
46
+
47
+ `src/explain.ts` adds sandbox information only inside the function-handler
48
+ case. `src/manifest.ts` carries that handler object forward, while
49
+ `src/readiness.ts` and `src/route-diff.ts` omit execution mode from inventory.
50
+ Context already exposes it at route level.
51
+
52
+ Reproduction: create the same middleware/native route in both modes, call
53
+ `buildManifest`, obtain `createRuntime(...).testPlan()`, then call `diffRoutes`.
54
+ Observed diff: `{"added":[],"removed":[],"changed":[]}`. Add route-level
55
+ execution mode/reason to explain, manifest and inventory, with compatibility
56
+ handling for older inventory files and regressions for both handler kinds.
57
+
58
+ ### 2. Auth/admin hook reactivation serves stale entry code — P2
59
+
60
+ [Issue 198](https://github.com/jimhoyd-com/urlcode/issues/198).
61
+
62
+ Both `packages/auth/src/lifecycle-hooks.ts` and
63
+ `packages/admin/src/admin-hooks.ts` import an unchanged file URL. Loading a
64
+ hook, editing its entry file, then loading it again in the same process returns
65
+ the old hook. A synthetic decision changed on disk from allow/v1 to deny/v2;
66
+ both loaders still returned allow/v1 after reactivation.
67
+
68
+ Native trusted routes already give entry modules a new URL per activation.
69
+ Apply an explicit hook reload policy and test it, or clearly require process
70
+ restart for hook changes. This reproduction exercised the loaders directly,
71
+ not the end-to-end development watcher and account flow. Transitive Node module
72
+ caching is a separate, already documented limitation. Keep trusted execution
73
+ as the default and reject unsupported sandbox hooks explicitly.
74
+
75
+ ### 3. TypeScript authoring rejects valid mixed-trust module reuse — P2
76
+
77
+ [Issue 197](https://github.com/jimhoyd-com/urlcode/issues/197).
78
+
79
+ `src/typescript-authoring.ts:34` assigns each source one trust mode and rejects
80
+ reuse from a different mode. Two routes sharing a pure `f.ts`, one trusted and
81
+ one sandboxed, fail with “Module is imported by both a sandboxed and a trusted
82
+ route”. The equivalent JavaScript project activates and both routes return
83
+ 200/ok. Shared transitive helpers have the same restriction.
84
+
85
+ Separate emission deduplication from sandbox reachability validation. A helper
86
+ reachable by a sandboxed route must still satisfy all sandbox import/source
87
+ budgets; trusted execution must not be downgraded or unnecessarily restricted.
88
+ Also reconcile the documented unlimited trusted source size with the builder's
89
+ 16 MiB per-file authoring cap.
90
+
91
+ ### 4. Live guidance still contradicts trust by default — P2
92
+
93
+ [Issue 196](https://github.com/jimhoyd-com/urlcode/issues/196).
94
+
95
+ - `docs/yaml/functions.md:57` denies Node/npm/network/filesystem APIs immediately
96
+ after an ordinary trusted route example.
97
+ - `docs/yaml/middleware.md:26` describes a mandatory shared sandbox.
98
+ - `docs/MIDDLEWARE.md:49` applies guest clone/body restrictions without scoping
99
+ them to sandboxed execution. Reproduced: trusted middleware reads a native
100
+ response body and returns its uppercase text; the sandboxed version rejects
101
+ that operation.
102
+ - `docs/SPECIFICATION.md` says `.js` ESM behavior is independent of package
103
+ settings. Reproduced: ESM `f.js` under `type: commonjs` fails trusted activation
104
+ and works in the sandbox. Ordinary Node resolution is the intended default;
105
+ the documentation should explain `.mjs` and package type accurately.
106
+ - `README.md` and `docs/FRAMEWORK.md` overstate binding grants as the only way
107
+ secrets reach code. Scope that statement to injected bindings.
108
+ - `docs/READINESS.md` calls the activated runtime isolated; the first test in
109
+ `test/typescript-authoring.test.ts` claims QuickJS execution but omits sandbox.
110
+
111
+ The prose guards pass this revision despite these contradictions. Strengthen
112
+ specific regression fixtures and fix the guidance; do not impose old sandbox
113
+ restrictions on trusted code to make the prose true.
114
+
115
+ The POST/body-based sandbox advisory is a related cleanup candidate. It is
116
+ non-blocking and documented, but suggests isolation from request shape rather
117
+ than evidence about code trust. Review whether to remove or reword it while
118
+ retaining explicit `sandboxReason` information. This is a recommendation, not
119
+ a discovered isolation failure.
120
+
121
+ ### 5. Copyable documentation examples fail validation — P2
122
+
123
+ [New evidence on issue 168](https://github.com/jimhoyd-com/urlcode/issues/168#issuecomment-5747024306).
124
+
125
+ - `docs/yaml/policies.md:18` uses `page.source`; the field is `page.file`.
126
+ - `docs/policies/compression.md:35` uses `secret: api-key`, rejected by the
127
+ external binding-name grammar; an identifier such as `api_key` is admitted.
128
+ - `docs/AI-AUTHORING.md:249` omits the leading slash on `webhooks/stripe`.
129
+ This also illustrates why schema checks alone cannot replace semantic route
130
+ validation. The same section still lists the retired `link` handler.
131
+
132
+ The first two are complete fenced projects and fail `validateDocument`.
133
+ The partial profile illustration in `docs/POLICIES.md:169` was excluded from
134
+ findings because it deliberately omits a handler. Add classified executable
135
+ snippets: complete projects, context-dependent fragments and intentionally
136
+ invalid examples. Existing cookbook tests do not validate every prose example.
137
+
138
+ ### 6. Monorepo ownership, navigation and release prose need cleanup — P3
139
+
140
+ [Issue 200](https://github.com/jimhoyd-com/urlcode/issues/200).
141
+
142
+ - Package AGENTS files still direct findings to former package repositories.
143
+ - Framework/status pages retain former source homes, old alpha combinations,
144
+ private cross-repository credential instructions and peer-pin language.
145
+ - Two admin README links target nonexistent package-local release workflows.
146
+ - Documentation navigation duplicates cookbook entries with conflicting 25/40
147
+ counts; llms.txt repeats semantics and TypeScript entries.
148
+ - Hook comments claim core lacks a sandbox primitive although SandboxPool is
149
+ exported. The packages still explicitly reject sandboxed hooks; update the
150
+ reason and tracked work rather than claiming they already support them.
151
+
152
+ Use local links and one canonical version/ownership inventory for live material;
153
+ keep dated release evidence clearly historical. Consolidate the duplicate
154
+ SQLite preflight predicates through development tooling without removing auth's
155
+ runtime gate. Shorten repeated issue-history narration in implementation comments
156
+ where a contract explanation and issue link are sufficient.
157
+
158
+ Do not remove the supported UI primitive fallback, opt-in sandbox path, provider
159
+ compatibility refusals, licenses or regression fixtures. No dead production module
160
+ was established with enough evidence to recommend deleting it in this audit.
161
+
162
+ ## Verification evidence
163
+
164
+ The final sequential `npm run verify` passed, including lint, strict typecheck,
165
+ syntax/JSON/catalog checks, generated-document checks, workspace links, release
166
+ checks, build, core tests and all workspace suites:
167
+
168
+ | Suite | Passed | Skipped | Failed/cancelled |
169
+ |---|---:|---:|---:|
170
+ | Core | 518 | 1 | 0 |
171
+ | UI | 57 | 0 | 0 |
172
+ | Auth | 207 | 0 | 0 |
173
+ | Admin | 68 | 0 | 0 |
174
+ | Workspace scaffold integration | 1 | 0 | 0 |
175
+
176
+ Total: 851 passed, one intentional HTTPS-deployment test skip. The integration
177
+ test checks scaffold composition; it is not a live provider deployment.
178
+
179
+ `npm run test:package` passed installation of the packed archive and
180
+ starter/cookbook/authoring-consumer checks. Separate CLI HTTP fixture runs passed
181
+ for the starter, assets, cookbook, Vercel, AWS, Cloudflare, conditions (with its
182
+ documented public origin) and prerender source. The cookbook count audit passed
183
+ with 40 routes. Extension, fake-egress, monitoring, provider-conformance and
184
+ tunnel examples are also exercised by their dedicated core tests.
185
+
186
+ `npm audit --omit=dev --json`
187
+ reported zero known runtime advisories at audit time. Neither substitutes for
188
+ source review or establishes absence of unknown vulnerabilities.
189
+
190
+ The initial restricted-environment run could not run local HTTP tests. A first
191
+ unrestricted run had five test-file cancellations; those files passed alone.
192
+ An overlapping build also caused transient missing-declaration errors in a
193
+ workspace run. The final sequential full run passed without cancellations.
194
+ These intermediate results are not reported as confirmed product defects.
195
+
196
+ ## Remaining evidence boundaries
197
+
198
+ No provider account was deployed, no release published, no production system
199
+ probed, and no container/Windows/Linux/Node 22 or 24 run was performed locally.
200
+ Live email/OIDC/passkey services, accessibility/browser/device assessment,
201
+ operational recovery/soak evidence and independent sandbox review remain separate.
202
+ Existing issues 58, 173, 174 and 185 already retain relevant acceptance,
203
+ model-benchmark, schema-discovery and release/CI-evidence work; this audit does
204
+ not close those gates.
205
+
206
+ ## Follow-up: dead-code reachability
207
+
208
+ [Issue 203](https://github.com/jimhoyd-com/urlcode/issues/203) records a dedicated
209
+ unused-code pass requested after the initial audit. Runtime source remains the
210
+ same as the reviewed revision; the intervening commit only added this report.
211
+
212
+ A conservative relative-reference graph rooted at package exports and CLI entry
213
+ points reached all 157 tracked production TypeScript modules. Every direct
214
+ runtime dependency has a production source reference, and every root script
215
+ has a named reference elsewhere in the repository. No whole production file,
216
+ runtime dependency or root script was established as removable.
217
+
218
+ A TypeScript symbol/reference pass excluded 501 symbols exposed by public
219
+ package entrypoints. Candidates were then checked with repository-wide search
220
+ and manual inspection, including local uses, CLI imports, namespace dispatch,
221
+ worker URLs and dynamically loaded agent-list code. The confirmed small removals
222
+ are:
223
+
224
+ | Declaration/plumbing | Evidence | Proposed cleanup |
225
+ |---|---|---|
226
+ | `src/capability-query.ts:47`, `capabilityNameList()` | Declaration only; no caller or public entry export | Delete the unused wrapper |
227
+ | `src/catalog.ts:39`, `metadataFiles` | Declaration only; no reader or public entry export | Delete the unused constant |
228
+ | `src/trusted-functions.ts`, `log` option/property | Assigned but never read | Remove this unused executor plumbing, preserving runtime observer/logging behavior |
229
+ | `src/mcp-authoring.ts:57`, `expandHandler(path, handler)` | `path` is never read | Remove the argument and update callers |
230
+ | `src/policies/cache.ts:201`, `revalidate(state, req, result)` | `state` is never read | Remove the argument and update callers |
231
+
232
+ Several live implementation helpers are unnecessarily exported: admin's
233
+ `activeKit`, core's `routeState`, `forbiddenHeaders`, `normalizeRoute`,
234
+ `manifestFileName`, and local scaffold/render/name helpers in `init-with.ts`.
235
+ These are candidates for removing export modifiers, not deleting their bodies.
236
+ Check declaration dependencies before changing exported types.
237
+
238
+ An additional TypeScript check with `--noUnusedLocals --noUnusedParameters`
239
+ reported six unused parameters: the two production helpers above and four test
240
+ callbacks. It reported no unused local declarations. This stricter exploratory
241
+ check is separate from the normal passing typecheck.
242
+
243
+ Public APIs with no internal callers, types used in public signatures, registry
244
+ policy hooks, dynamic imports, supported UI fallback rendering and opt-in sandbox
245
+ execution are not dead code. This reachability analysis does not prove that
246
+ every branch executes. No production code was removed by this follow-up.
247
+
248
+ ## Follow-up: unnecessary files and distribution weight
249
+
250
+ [File-level evidence on issue 200](https://github.com/jimhoyd-com/urlcode/issues/200#issuecomment-5747116642).
251
+
252
+ One package-local script is obsolete: `packages/admin/scripts/peer-revisions.mjs`.
253
+ It has no workflow/package-script caller and reads the deleted admin `peers.json`;
254
+ executing it fails with ENOENT. Its only other named reference is historical
255
+ monorepo prose. Remove it; workspace linking replaced its cross-repository
256
+ revision-output mechanism. The earlier root-script scan did not cover this
257
+ package-local leftover.
258
+
259
+ The three package `CODE_OF_CONDUCT.md` files are byte-identical to the root copy
260
+ (1,062 bytes each). Consolidate their links to the root policy before deleting
261
+ the duplicates. Package governance also repeats repository-wide controls and
262
+ can link to the root while retaining any package-specific information. Package
263
+ licenses and attribution notices serve a different purpose and must stay.
264
+
265
+ A dry-run npm package inventory includes 11 archived documentation files
266
+ (179,666 bytes) and six design-spike files (184,792 bytes): about 356 KiB
267
+ uncompressed combined. The audit report also ships because package.json includes
268
+ all of docs. Consider excluding maintainer/history/design records from npm
269
+ while retaining them in Git and keeping their references navigable. These
270
+ figures are content sizes, not estimates of compressed download savings.
271
+
272
+ The duplicate Claude plugin skills are intentional, checked distribution copies;
273
+ cookbook/recipe copies make each project independently usable; upstream UI
274
+ snapshots preserve attribution/provenance; the benchmark baseline is consumed
275
+ by its gate. None is established as unwanted. No tracked build output,
276
+ node_modules, tarballs, logs, backup files or TypeScript build-info files were
277
+ found. Ignored local dependencies/build products are regenerable development
278
+ output, not tracked repository clutter. No files were deleted in this audit.