@jimhoyd/urlcode 0.4.0-alpha.1 → 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 (232) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +36 -22
  2. package/.claude/skills/urlcode-operations/SKILL.md +16 -22
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CONTRIBUTING.md +20 -16
  5. package/README.md +83 -80
  6. package/ROADMAP.md +61 -331
  7. package/SECURITY.md +23 -8
  8. package/dist/BUILD-MANIFEST.json +42 -47
  9. package/dist/agents-guide.js +18 -18
  10. package/dist/build-static.js +136 -0
  11. package/dist/capabilities.js +85 -31
  12. package/dist/capability-query.js +0 -1
  13. package/dist/cli.js +24 -32
  14. package/dist/compliance-rules/baseline.js +2 -10
  15. package/dist/compliance-rules/privacy.js +5 -16
  16. package/dist/compliance-rules/shared.js +0 -2
  17. package/dist/compliance.js +6 -8
  18. package/dist/config.js +15 -8
  19. package/dist/context.js +9 -10
  20. package/dist/examples.js +2 -2
  21. package/dist/explain-cli.js +7 -5
  22. package/dist/explain.js +10 -5
  23. package/dist/extensions.js +61 -2
  24. package/dist/function-sources.js +34 -2
  25. package/dist/function-worker.js +3 -1
  26. package/dist/functions.js +84 -13
  27. package/dist/guest-api.js +29 -3
  28. package/dist/index.js +3 -6
  29. package/dist/manifest.js +11 -7
  30. package/dist/mcp-authoring.js +2 -2
  31. package/dist/mcp.js +1 -1
  32. package/dist/observability.js +1 -21
  33. package/dist/policies/cache.js +8 -3
  34. package/dist/policies.js +3 -1
  35. package/dist/policy.js +28 -9
  36. package/dist/prerender.js +4 -0
  37. package/dist/project-tests.js +3 -3
  38. package/dist/readiness.js +35 -9
  39. package/dist/route-diff.js +12 -5
  40. package/dist/router.js +5 -7
  41. package/dist/runtime.js +77 -58
  42. package/dist/sandbox.js +48 -0
  43. package/dist/scaffold.js +0 -0
  44. package/dist/scripts/operational-drills.js +12 -54
  45. package/dist/server.js +3 -29
  46. package/dist/tooling.js +1 -1
  47. package/dist/trusted-functions.js +210 -0
  48. package/dist/types/build-static.d.ts +43 -0
  49. package/dist/types/capabilities.d.ts +14 -5
  50. package/dist/types/compliance-rules/shared.d.ts +0 -2
  51. package/dist/types/compliance.d.ts +0 -3
  52. package/dist/types/config.d.ts +2 -1
  53. package/dist/types/context.d.ts +2 -1
  54. package/dist/types/examples.d.ts +1 -1
  55. package/dist/types/explain.d.ts +6 -0
  56. package/dist/types/extensions.d.ts +55 -0
  57. package/dist/types/function-sources.d.ts +4 -0
  58. package/dist/types/functions.d.ts +48 -5
  59. package/dist/types/guest-api.d.ts +1 -0
  60. package/dist/types/index.d.ts +3 -6
  61. package/dist/types/manifest.d.ts +5 -3
  62. package/dist/types/observability.d.ts +1 -14
  63. package/dist/types/project-tests.d.ts +1 -2
  64. package/dist/types/readiness.d.ts +12 -3
  65. package/dist/types/router.d.ts +2 -1
  66. package/dist/types/runtime.d.ts +0 -27
  67. package/dist/types/sandbox.d.ts +12 -0
  68. package/dist/types/scaffold.d.ts +0 -2
  69. package/dist/types/server.d.ts +1 -4
  70. package/dist/types/tooling.d.ts +3 -3
  71. package/dist/types/trusted-functions.d.ts +29 -0
  72. package/dist/types/types.d.ts +22 -8
  73. package/dist/types/verify-deployment.d.ts +2 -2
  74. package/dist/types.js +25 -5
  75. package/dist/typescript-authoring.js +67 -18
  76. package/dist/verify-deployment.js +3 -3
  77. package/docs/AI-AUTHORING.md +109 -10
  78. package/docs/ASSETS.md +2 -1
  79. package/docs/AUTH-BACKUP.md +32 -0
  80. package/docs/AWS.md +4 -4
  81. package/docs/BEST-PRACTICES.md +17 -8
  82. package/docs/CAPABILITIES.md +30 -17
  83. package/docs/CAPACITY.md +128 -31
  84. package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
  85. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
  86. package/docs/CI.md +8 -3
  87. package/docs/CLOUDFLARE.md +1 -2
  88. package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
  89. package/docs/COMPLIANCE.md +6 -9
  90. package/docs/DEPLOYMENT-CHECKS.md +1 -1
  91. package/docs/DEVELOPMENT-PIPELINE.md +181 -0
  92. package/docs/EXTENSIONS.md +193 -11
  93. package/docs/FRAMEWORK.md +53 -33
  94. package/docs/FUNCTION-SECURITY.md +173 -32
  95. package/docs/INSTALL.md +0 -5
  96. package/docs/LOAD-TESTING.md +4 -4
  97. package/docs/LOCAL-DEVELOPMENT.md +3 -0
  98. package/docs/MIDDLEWARE.md +27 -16
  99. package/docs/MONITORING.md +2 -19
  100. package/docs/OBSERVABILITY.md +7 -16
  101. package/docs/OPEN-DECISIONS.md +184 -0
  102. package/docs/OPERATIONAL-PROOF.md +26 -30
  103. package/docs/OPERATIONS.md +23 -32
  104. package/docs/POLICIES.md +27 -8
  105. package/docs/PRERENDER.md +25 -13
  106. package/docs/PROJECT-DIRECTION.md +35 -10
  107. package/docs/READINESS.md +6 -2
  108. package/docs/README.md +20 -41
  109. package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
  110. package/docs/RELEASE-READINESS.md +50 -30
  111. package/docs/RELEASE-SECURITY.md +72 -86
  112. package/docs/RESILIENCE.md +16 -15
  113. package/docs/ROUTING.md +7 -10
  114. package/docs/SANDBOX-REVIEW.md +19 -6
  115. package/docs/SCAFFOLDING.md +0 -2
  116. package/docs/SECURITY-AUDIT.md +42 -2
  117. package/docs/SPECIFICATION.md +63 -27
  118. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +288 -0
  119. package/docs/SPIKE-BUSINESS-SUITE.md +16 -0
  120. package/docs/SPIKE-CORE-LAYERING.md +368 -0
  121. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +211 -0
  122. package/docs/SPIKE-LAMBDA-COMPILE.md +12 -2
  123. package/docs/STARTERS.md +1 -1
  124. package/docs/STATIC.md +105 -0
  125. package/docs/TOOLING.md +17 -12
  126. package/docs/TUNNELS.md +0 -3
  127. package/docs/TYPESCRIPT-AUTHORING.md +32 -12
  128. package/docs/TYPESCRIPT.md +25 -4
  129. package/docs/VERCEL.md +4 -5
  130. package/docs/VERSION-ALIGNMENT.md +46 -0
  131. package/docs/YAML-GUIDE.md +0 -3
  132. package/docs/YAML-REFERENCE.md +16 -6
  133. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
  134. package/docs/{MANAGEMENT-SECURITY.md → archive/2026-09-19/MANAGEMENT-SECURITY.md} +20 -0
  135. package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +26 -8
  136. package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +274 -43
  137. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
  138. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
  139. package/docs/archive/2026-09-19/ROADMAP.md +386 -0
  140. package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +12 -2
  141. package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +28 -12
  142. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
  143. package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +18 -2
  144. package/docs/archive/README.md +27 -0
  145. package/docs/policies/agents.md +1 -1
  146. package/docs/policies/cache.md +13 -0
  147. package/docs/policies/compression.md +3 -2
  148. package/docs/policies/security.md +3 -2
  149. package/examples/assets/example.yaml +2 -2
  150. package/examples/aws/example.yaml +1 -1
  151. package/examples/cloudflare/example.yaml +1 -1
  152. package/examples/conditions/example.yaml +1 -1
  153. package/examples/cookbook/example.yaml +1 -1
  154. package/examples/cookbook/middleware/bucket.mjs +12 -2
  155. package/examples/cookbook/middleware/locale.mjs +7 -3
  156. package/examples/cookbook/route-index.json +1 -1
  157. package/examples/cookbook/routes/middleware.yaml +1 -1
  158. package/examples/egress/example.yaml +1 -1
  159. package/examples/extensions/example.yaml +1 -1
  160. package/examples/prerender/README.md +14 -6
  161. package/examples/prerender/example.yaml +1 -1
  162. package/examples/prerender/functions/page.mjs +4 -2
  163. package/examples/prerender/middleware/template.mjs +1 -1
  164. package/examples/prerender/prerender.mjs +1 -1
  165. package/examples/prerender/urlcode.yaml +8 -4
  166. package/examples/provider-conformance/example.yaml +1 -1
  167. package/examples/vercel/example.yaml +1 -1
  168. package/llms-full.txt +686 -239
  169. package/llms.txt +27 -15
  170. package/package.json +32 -5
  171. package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
  172. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +36 -22
  173. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +16 -22
  174. package/recipes/authenticated-json-api/README.md +4 -3
  175. package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
  176. package/recipes/authenticated-json-api/recipe.yaml +2 -2
  177. package/recipes/contact-form/functions/contact.mjs +2 -1
  178. package/recipes/contact-form/recipe.yaml +3 -3
  179. package/recipes/cors-api/README.md +2 -2
  180. package/recipes/cors-api/recipe.yaml +2 -2
  181. package/recipes/health-page/README.md +1 -1
  182. package/recipes/health-page/recipe.yaml +1 -1
  183. package/recipes/json-api/README.md +1 -1
  184. package/recipes/json-api/recipe.yaml +4 -4
  185. package/recipes/middleware/README.md +8 -4
  186. package/recipes/middleware/middleware/bucket.mjs +12 -2
  187. package/recipes/middleware/middleware/locale.mjs +7 -3
  188. package/recipes/middleware/recipe.yaml +1 -1
  189. package/recipes/protected-download/README.md +1 -1
  190. package/recipes/protected-download/recipe.yaml +2 -2
  191. package/recipes/redirect/recipe.yaml +1 -1
  192. package/recipes/static-plus-api/README.md +2 -2
  193. package/recipes/static-plus-api/public/index.html +1 -1
  194. package/recipes/static-plus-api/recipe.yaml +2 -2
  195. package/recipes/static-plus-api/urlcode.yaml +1 -1
  196. package/recipes/typescript/README.md +2 -1
  197. package/recipes/typescript/recipe.yaml +5 -5
  198. package/recipes/webhook-receiver/README.md +5 -1
  199. package/recipes/webhook-receiver/recipe.yaml +2 -1
  200. package/recipes/webhook-receiver/urlcode.yaml +7 -0
  201. package/schemas/recipe.schema.json +4 -3
  202. package/schemas/urlcode.schema.json +14 -41
  203. package/skills/urlcode/SKILL.md +32 -11
  204. package/starters/default/AGENTS.md +18 -18
  205. package/starters/default/urlcode.yaml +0 -1
  206. package/dist/link-api.js +0 -136
  207. package/dist/link-cli.js +0 -141
  208. package/dist/link-events.js +0 -76
  209. package/dist/link-records.js +0 -31
  210. package/dist/link-store-worker.js +0 -150
  211. package/dist/link-store.js +0 -250
  212. package/dist/management-policy.js +0 -40
  213. package/dist/sqlite-version.js +0 -6
  214. package/dist/types/link-api.d.ts +0 -30
  215. package/dist/types/link-cli.d.ts +0 -37
  216. package/dist/types/link-events.d.ts +0 -27
  217. package/dist/types/link-records.d.ts +0 -11
  218. package/dist/types/link-store-worker.d.ts +0 -1
  219. package/dist/types/link-store.d.ts +0 -130
  220. package/dist/types/management-policy.d.ts +0 -8
  221. package/dist/types/sqlite-version.d.ts +0 -1
  222. package/docs/DYNAMIC-LINKS.md +0 -61
  223. package/docs/links/cli.md +0 -110
  224. package/docs/links/limits.md +0 -175
  225. package/docs/links/management-api.md +0 -80
  226. package/docs/links/pools.md +0 -75
  227. package/docs/links/setup.md +0 -135
  228. package/docs/yaml/links.md +0 -30
  229. package/examples/live-links/README.md +0 -11
  230. package/examples/live-links/example.yaml +0 -21
  231. package/examples/live-links/tests/requests.json +0 -6
  232. package/examples/live-links/urlcode.yaml +0 -16
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
@@ -21,8 +21,7 @@ route pattern and the reason named:
21
21
 
22
22
  | Handler | Why it is refused |
23
23
  | --- | --- |
24
- | `function`, `middleware` | need worker threads and the QuickJS WASM engine |
25
- | `link` | needs a durable writable store the platform does not provide here |
24
+ | `function`, `middleware` | need the self-hosted Node lifecycle, and worker threads and the QuickJS WASM engine when a route declares `sandbox: true` |
26
25
  | `page`, `static`, `download` | need a platform static-asset binding, not an inline copy |
27
26
  | `env`, `secrets` | would have to be baked into the artifact |
28
27
 
@@ -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.
@@ -49,11 +49,11 @@ A **project** rule runs once with:
49
49
  |---|---|
50
50
  | `document` | The parsed and validated `urlcode.yaml` (includes are merged into `routes`) |
51
51
  | `routes` | Route configuration by pattern, as written in YAML |
52
- | `plan` | `testPlan()`: `inventory[]` (`path`, `handler`, `methods`, `middleware`, `policies`, `state`), `policies` (the per-route describe map), `dynamicLinks` |
52
+ | `plan` | `testPlan()`: `inventory[]` (`path`, `handler`, `methods`, `middleware`, `policies`, `state`), `policies` (the per-route describe map) |
53
53
  | `policies` | `effectivePolicies(document, route)` by pattern: the merged configuration of every policy on each route |
54
54
  | `origin` | The declared public origin, or `null` |
55
55
  | `target` | `node` unless the caller states another |
56
- | `host` | `{ requestLog, linkEvents, includeCode }` as declared for the deployment; `null` where undeclared |
56
+ | `host` | `{ requestLog }` as declared for the deployment; `null` where undeclared |
57
57
 
58
58
  A **route** rule runs once per inventory entry and additionally receives
59
59
  `route` (the inventory entry), `config` (that route's YAML), `policy` (the
@@ -86,7 +86,6 @@ cache and compression secrets handling in `src/policies/cache.ts` and
86
86
  | `rfc6585/throttle-functions` | [RFC 6585](https://www.rfc-editor.org/rfc/rfc6585) §4 | medium | Every active function or middleware route has an effective `policies.throttle` | Declare a throttle on the route or the project |
87
87
  | `rfc9309/robots` | [RFC 9309](https://www.rfc-editor.org/rfc/rfc9309) | low | Some active route declares `policies.agents`, or an active `/robots.txt` `respond` route exists | Add a `/robots.txt` route or an agents deny list |
88
88
  | `rfc9110/expired-routes` | [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110) §15.5.11 | info | Lists routes past `expires` that still answer 410 | Remove them once the 410 window has served its purpose |
89
- | `ops/management-private` | [Management security](MANAGEMENT-SECURITY.md) | info | Reminder when `dynamicLinks: true`: the management API and `/_urlcode` probes belong on a private bind | Run `links api` privately with an auth file; keep probes internal |
90
89
 
91
90
  ### `strict` (baseline plus)
92
91
 
@@ -102,15 +101,13 @@ cache and compression secrets handling in `src/policies/cache.ts` and
102
101
 
103
102
  These rules check deployment settings, so `audit` takes `--request-log` to
104
103
  declare the level the deployment uses (the audit process itself always logs
105
- nothing). The CLI cannot enable link events, so it declares `linkEvents:
106
- false`; embedders pass `host` themselves. The references are the runtime's
104
+ nothing); embedders pass `host` themselves. The references are the runtime's
107
105
  own [logging guarantees](MONITORING.md): records carry no URL, query, header,
108
106
  body or binding, and `detailed` adds only the method and route pattern.
109
107
 
110
108
  | Rule | Standard | Severity | Checks | Remediation |
111
109
  |---|---|---|---|---|
112
110
  | `privacy/request-log-minimal` | [Monitoring](MONITORING.md), Log records | medium (`info` when undeclared) | `host.requestLog` is `minimal` | Use the default log unless per-route rates are required |
113
- | `privacy/link-events-off` | [Monitoring](MONITORING.md), The link event channel | medium; `high` with `includeCode` (`info` when undeclared and `dynamicLinks` is on) | The link event channel is off, or on without `includeCode` | Enable it only for a declared purpose; never disclose codes |
114
111
  | `privacy/detailed-log-parameters` | [Monitoring](MONITORING.md), Log records | low | With `detailed` logging, no active route takes parameters (records name the pattern and method, never values) | Keep `minimal` on parameterised deployments |
115
112
 
116
113
  ## Writing custom rules
@@ -159,9 +156,9 @@ Without any compliance flag the audit report is unchanged apart from
159
156
  "findings": [{ "rule": "rfc6585/throttle-functions", "severity": "medium", "route": "/hello/{name}",
160
157
  "message": "…", "remediation": "…", "standard": { "name": "RFC 6585 …", "reference": "…", "section": "…" } }],
161
158
  "counts": { "high": 0, "medium": 4, "low": 11, "info": 1 }, "pass": true,
162
- "evidence": { "routes": 21, "active": 19, "dynamicLinks": false, "policies": ["agents", "cache", "security", "throttle"],
159
+ "evidence": { "routes": 21, "active": 19, "policies": ["agents", "cache", "security", "throttle"],
163
160
  "files": ["urlcode.yaml", "routes/code.yaml"], "origin": null, "target": "node",
164
- "host": { "requestLog": "minimal", "linkEvents": false, "includeCode": null },
161
+ "host": { "requestLog": "minimal" },
165
162
  "scope": "declared configuration and runtime facts; not a deployment or certification" } }
166
163
  ```
167
164
 
@@ -190,7 +187,7 @@ const report = await runCompliance(runtime, {
190
187
  rules: [], override: {}, disable: [], // as a rules module would export them
191
188
  ignore: ['rfc9110/expired-routes'],
192
189
  origin: 'https://links.example',
193
- host: { requestLog: 'minimal', linkEvents: false }, // what the deployment is configured with
190
+ host: { requestLog: 'minimal' }, // what the deployment is configured with
194
191
  });
195
192
  await runtime.close();
196
193
  ```
@@ -19,7 +19,7 @@ URLs for; defaults to the target), `--expect-routes N`, `--expect-metrics`
19
19
  (per request, 100-120000, default 10000), `--fail-on high|medium|low|info|none`
20
20
  (default `high`), the `audit` compliance flags (`--compliance`,
21
21
  `--compliance-rules`, `--compliance-ignore`, `--compliance-warn`), and the
22
- usual `--policy` and `--link-store` bindings the local snapshot needs to start.
22
+ usual `--policy` binding the local snapshot needs to start.
23
23
 
24
24
  ## What it verifies
25
25
 
@@ -0,0 +1,181 @@
1
+ # Development and release pipeline
2
+
3
+ The repository uses npm workspaces, independent package versions, and Changesets
4
+ for UI, auth and admin. Core remains at the repository root and is explicitly
5
+ included in the shared release inventory. Moving it is not required to use the
6
+ same release checks and coordinator.
7
+
8
+ ## Pull requests
9
+
10
+ Every PR starts `verify`. A complete Git diff selects one of two lanes:
11
+
12
+ - **Prose:** root project Markdown, `docs/**/*.md`, `llms.txt` and
13
+ `llms-full.txt` changes run guidance/generated-resource checks, runtime audit,
14
+ and the required container job. CodeQL retains its repository policy.
15
+ - **Full:** all other changes, mixed changes, empty/unavailable diffs and main
16
+ pushes run static checks once and core and workspace suites separately. Both
17
+ suites retain Linux on Node 22/24/26. Main adds Windows/macOS on Node 24.
18
+ PRs add those platform legs for runtime, CLI, SQLite, fixture, dependency,
19
+ workflow and unknown changes; known UI presentation-only changes omit them. Package, action,
20
+ cookbook, reproducibility and operational checks retain their coverage.
21
+
22
+ The prose allowlist is deliberately narrow. Package documentation, skills,
23
+ starters, examples, schemas, manifests and workflow changes select full checks.
24
+ A rename from source into docs also selects full checks. No required workflow
25
+ uses `paths-ignore`.
26
+
27
+ `verify-complete` accepts only the results specified by the successful plan.
28
+ Failed, canceled, missing or unexpectedly skipped work fails the gate. Required
29
+ check names (`verify-complete`, `container`) and CodeQL enforcement are preserved.
30
+ The repository ruleset currently does not require a branch to be up to date;
31
+ release publication separately requires verification of the exact main commit.
32
+
33
+ ```sh
34
+ npm run check:docs # prose checks without the runtime suite
35
+ npm run ci:plan -- BASE_SHA HEAD_SHA
36
+ npm run ci:report -- RUN_ID # read GitHub job/step durations
37
+ npm run ci:history -- 100 2026-09-19 # group historical timing samples
38
+ npm run verify # full local validation remains available
39
+ npm run test:package # builds and installs a real archive
40
+ ```
41
+
42
+ CI uses `test:package:built` only after building in that same job. Core tests and
43
+ workspace tests run in separate jobs to shorten their serial critical path;
44
+ this increases job setup overhead and needs monitoring for runner queue pressure.
45
+ After building all three extensions, the workspace job also runs the real
46
+ `init --with auth,admin,ui` scaffold integration. Missing workspace outputs fail
47
+ instead of silently skipping an absent external checkout.
48
+ The [audit](CI-RELEASE-AUDIT-2026-09-19.md) records the previous timings.
49
+ The [follow-up measurements](CI-FOLLOWUP-2026-09-19.md) record the first compact
50
+ main result and explain why the new lanes still need 20 organic runs each.
51
+
52
+ ## Version and release ownership
53
+
54
+ A feature PR records release intent in a Changeset for a changed workspace
55
+ package. Review dependency/peer changes explicitly. Keep pre-mode enabled until
56
+ an explicit decision to leave alpha. Do not force all packages to one version.
57
+ Core version bumps remain explicit in the release PR, including its CLI banner.
58
+ `release:check` verifies every manifest against its lockfile entry and checks the
59
+ alpha-mode policy. Existing CLI tests catch core banner/version disagreement.
60
+
61
+ A release PR collects version/changelog and lockfile changes together. Ordinary
62
+ unreleased development does not move existing version tags or npm versions.
63
+ Use the exact release commit after its full platform checks have passed. Routine
64
+ main builds use five OS/Node combinations per suite (ten jobs total), rather
65
+ than the full nine per suite (eighteen). Nightly runs at 07:17 UTC and manual
66
+ runs retain all three operating systems on all three Node versions. Before a
67
+ release, run `gh workflow run ci.yml --ref main` and wait for that exact commit's
68
+ full run to succeed. A successful compact main run alone cannot authorize a
69
+ release. Main pushes do not cancel scheduled/manual verification.
70
+
71
+ Inspect release state:
72
+
73
+ ```sh
74
+ npm run release:status # registry channels, peer compatibility, remote tag SHAs
75
+ npm run release:plan # read-only JSON inventory, including root core
76
+ npm run release:run # read-only ordered proposal at HEAD
77
+ ```
78
+
79
+ All three commands require network access; none publish. `release:run` selects
80
+ unpublished manifest versions and releases already tagged at HEAD (for resuming
81
+ partial completion). Its order is core, UI, auth, admin, skipping other already
82
+ published versions. Plan fields come from package manifests, not copied versions
83
+ in another config file.
84
+
85
+ Once the release itself is authorized, from a clean checkout of that exact SHA:
86
+
87
+ ```sh
88
+ npm run release:run -- --execute
89
+ ```
90
+
91
+ This opt-in command creates missing tags through GitHub, refuses an existing tag
92
+ at a different commit, and waits for each package's tag-triggered release before
93
+ starting the next. It stops on failure or missing npm publication. Do not push
94
+ all release tags at once: publication workflows share one concurrency group and
95
+ GitHub may replace pending runs. The coordinator intentionally starts one at a
96
+ time. It never merges a PR, bypasses checks, force-pushes or publishes locally.
97
+
98
+ The coordinator needs `gh` authentication with repository Contents write and
99
+ Actions read; reading checks also needs Checks read. It is intended for an
100
+ authorized maintainer session or a repository-scoped GitHub App. A fine-grained
101
+ PAT can serve a short-lived maintainer script with those permissions, but must
102
+ have no ruleset bypass. The workflow `GITHUB_TOKEN` should not be used to create
103
+ these trigger tags: its push events do not start another ordinary push workflow.
104
+ For long-lived automation prefer a GitHub App; do not add a bypass to `main`.
105
+ [GitHub App guidance](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/deciding-when-to-build-a-github-app)
106
+
107
+ ## Publication and recovery
108
+
109
+ The four workflow filenames remain unchanged because npm trusted publishing
110
+ names them. They call shared helpers for identity, preflight, peer installation,
111
+ retry handling and publication. npm authentication remains OIDC; no npm token
112
+ is introduced. Core candidate and release share `prepare-core-release.sh`.
113
+ For an authorized release, first dispatch the manual `candidate.yml` workflow on
114
+ the selected main commit. It extends the core candidate with UI/auth/admin
115
+ archives and verifies all four together in an isolated temporary consumer:
116
+ peer compatibility, installed versions, public imports and real scaffold
117
+ composition. Its signed `train.json` records the proposed archives and integrity.
118
+ A candidate does not publish, validate live providers or prove registry OIDC;
119
+ release workflows still prepare and retain their own immutable retry artifacts.
120
+ Extensions share `prepare-extension-release.sh` and test published peer floors.
121
+ Auth/admin build in the workspace for packaging, then build and run their suites
122
+ in a temporary copy outside the monorepo against exact registry peer floors.
123
+ This preserves #184’s isolation fix; npm `--prefix` is not an isolation boundary.
124
+
125
+ Preflight checks the checkout SHA, main ancestry, a successful exact-SHA full
126
+ `ci.yml` nightly or explicit manual run, CodeQL, remote tag SHA, npm
127
+ channel monotonicity and published peer floors. When a full run was canceled,
128
+ run `verify` manually at the selected tag/ref, then rerun the failed release;
129
+ never substitute another commit's passing run or move the tag.
130
+
131
+ Prepared artifacts are retained for 90 days before publication. A rerun of the
132
+ same workflow run restores those original bytes and skips preparation. npm
133
+ versions already present must have identical SHA-512 integrity; existing GitHub
134
+ assets must match byte for byte. Different bytes stop the release. An absent or
135
+ expired artifact requires reconstruction that still passes these comparisons;
136
+ if it cannot, diagnose and create a new version rather than overwrite history.
137
+
138
+ GitHub release classification follows the manifest's prerelease status. New
139
+ GitHub releases use `--latest=false`; stable latest promotion is a separate
140
+ maintainer decision, avoiding accidental promotion by a package-level release.
141
+ GHCR updates the derived channel (`alpha` for alphas, `latest` for stable) and
142
+ preserves existing version images only when their source label matches. An
143
+ existing image/channel without the required labels fails closed and needs a
144
+ reviewed migration; this change does not silently relabel old images.
145
+
146
+ Partial npm/GitHub/GHCR success is possible; those systems cannot be updated
147
+ atomically. Rerun the original failed run, check its summary and then rerun the
148
+ coordinator. A changed source commit requires a new version and tag. Failed
149
+ OIDC configuration needs correction on npm, not tag deletion. A green dry run
150
+ cannot prove registry-side OIDC trust; each package's first authorized publish
151
+ must verify it.
152
+
153
+ Historical tags, GitHub release flags and npm channels are not retroactively
154
+ rewritten by these scripts. Use `release:status` to inspect them. The active [Immutable release tags rule](https://github.com/jimhoyd-com/urlcode/rules/23712319)
155
+ prohibits update/deletion of `v*` and `@jimhoyd/urlcode-*@*` tags, permits
156
+ creation, and has no bypass actors. Its reviewed configuration is tracked in
157
+ `.github/rulesets/release-tags.json`. Main protection is unchanged.
158
+
159
+ During the September 19 alignment, GitHub releases `v0.4.0-alpha.1` and
160
+ `v0.4.0-alpha.2` were explicitly marked as prereleases and GitHub latest was
161
+ restored to `v0.3.0`, matching npm. Their tags and artifact bytes were unchanged.
162
+ Further performance and release-train validation is tracked in
163
+ [issue #185](https://github.com/jimhoyd-com/urlcode/issues/185).
164
+
165
+ ### Windows fixture cleanup
166
+
167
+ Auth/admin tests register resources with their package-local `test/cleanup.ts`.
168
+ Cleanup runs in reverse acquisition order: close servers and SQLite services
169
+ before deleting temporary directories, including services reopened by a test.
170
+ Every registered callback is attempted even if another closer throws, and the
171
+ combined error fails the test. Register each closer as soon as its resource opens.
172
+ The suites use a five-minute test-file timeout so a stuck fixture is diagnosed
173
+ before the CI job limit. Node applies this limit to whole test files too; the
174
+ large auth-core file legitimately exceeds two minutes on Windows Node 22.
175
+ Windows regression coverage runs on Node 24 for platform-sensitive PRs and
176
+ main; nightly/manual runs cover Node 22/24/26.
177
+
178
+ Failed auth service initialization also waits for its SQLite worker to terminate
179
+ before rejecting. Callers can clean up or retry after a rejected open without
180
+ racing a remaining database handle; configuration identity failures still fail
181
+ closed with the same error code.