@jimhoyd/urlcode 0.4.0-alpha.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) 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 +59 -64
  6. package/ROADMAP.md +66 -376
  7. package/dist/BUILD-MANIFEST.json +26 -25
  8. package/dist/agents-guide.js +6 -6
  9. package/dist/authoring.js +15 -1
  10. package/dist/build-static.js +2 -0
  11. package/dist/capability-query.js +0 -1
  12. package/dist/catalog.js +0 -1
  13. package/dist/cli.js +25 -9
  14. package/dist/config.js +1 -1
  15. package/dist/explain-cli.js +4 -2
  16. package/dist/explain.js +8 -2
  17. package/dist/extensions.js +1 -1
  18. package/dist/http-response.js +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/init-with.js +36 -11
  21. package/dist/manifest.js +8 -2
  22. package/dist/mcp-authoring.js +4 -4
  23. package/dist/mcp.js +1 -1
  24. package/dist/policies/cache.js +2 -2
  25. package/dist/policies.js +3 -1
  26. package/dist/prerender.js +4 -0
  27. package/dist/project-dependencies.js +305 -0
  28. package/dist/readiness.js +3 -0
  29. package/dist/route-diff.js +12 -5
  30. package/dist/runtime.js +1 -1
  31. package/dist/trusted-functions.js +4 -5
  32. package/dist/types/authoring.d.ts +9 -1
  33. package/dist/types/capability-query.d.ts +0 -1
  34. package/dist/types/catalog.d.ts +0 -4
  35. package/dist/types/config.d.ts +1 -9
  36. package/dist/types/explain.d.ts +6 -1
  37. package/dist/types/extensions.d.ts +1 -1
  38. package/dist/types/http-response.d.ts +0 -1
  39. package/dist/types/index.d.ts +1 -0
  40. package/dist/types/init-with.d.ts +7 -13
  41. package/dist/types/manifest.d.ts +5 -2
  42. package/dist/types/project-dependencies.d.ts +78 -0
  43. package/dist/types/readiness.d.ts +2 -0
  44. package/dist/types/trusted-functions.d.ts +1 -4
  45. package/dist/types/types.d.ts +8 -1
  46. package/dist/types.js +8 -1
  47. package/dist/typescript-authoring.js +18 -7
  48. package/docs/AI-AUTHORING.md +15 -7
  49. package/docs/ASSETS.md +2 -1
  50. package/docs/AUTH-BACKUP.md +32 -0
  51. package/docs/AWS.md +9 -0
  52. package/docs/BEST-PRACTICES.md +17 -9
  53. package/docs/CAPABILITIES.md +1 -1
  54. package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
  55. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
  56. package/docs/CI.md +8 -3
  57. package/docs/CODEBASE-AUDIT-2026-09-20.md +284 -0
  58. package/docs/COMPOSING-A-SITE.md +278 -0
  59. package/docs/DEVELOPMENT-PIPELINE.md +270 -0
  60. package/docs/EXTENSIONS.md +51 -11
  61. package/docs/FRAMEWORK.md +67 -48
  62. package/docs/FUNCTION-SECURITY.md +44 -0
  63. package/docs/INSTALL.md +13 -8
  64. package/docs/LOCAL-DEVELOPMENT.md +3 -0
  65. package/docs/MIDDLEWARE.md +10 -4
  66. package/docs/OPEN-DECISIONS.md +224 -212
  67. package/docs/OPERATIONAL-PROOF.md +3 -3
  68. package/docs/OPERATIONS.md +3 -3
  69. package/docs/POLICIES.md +13 -5
  70. package/docs/PRERENDER.md +23 -11
  71. package/docs/PROJECT-DIRECTION.md +3 -3
  72. package/docs/READINESS.md +10 -5
  73. package/docs/README.md +20 -44
  74. package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
  75. package/docs/RELEASE-0.4.1.md +73 -0
  76. package/docs/RELEASE-READINESS.md +6 -6
  77. package/docs/RELEASE-SECURITY.md +96 -181
  78. package/docs/RESILIENCE.md +3 -3
  79. package/docs/ROUTING.md +3 -4
  80. package/docs/SECURITY-AUDIT.md +2 -2
  81. package/docs/SPECIFICATION.md +12 -6
  82. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
  83. package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
  84. package/docs/SPIKE-CORE-LAYERING.md +49 -18
  85. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
  86. package/docs/STARTERS.md +17 -5
  87. package/docs/STATIC.md +14 -3
  88. package/docs/TOOLING.md +10 -7
  89. package/docs/TYPESCRIPT-AUTHORING.md +6 -1
  90. package/docs/VERCEL.md +10 -2
  91. package/docs/VERSION-ALIGNMENT.md +76 -201
  92. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
  93. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +102 -0
  94. package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +15 -5
  95. package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +15 -3
  96. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
  97. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
  98. package/docs/archive/2026-09-19/ROADMAP.md +387 -0
  99. package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +11 -0
  100. package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
  101. package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +365 -0
  102. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +778 -0
  103. package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
  104. package/docs/archive/README.md +28 -0
  105. package/docs/policies/agents.md +1 -1
  106. package/docs/policies/compression.md +3 -2
  107. package/docs/policies/security.md +3 -2
  108. package/docs/yaml/functions.md +10 -2
  109. package/docs/yaml/middleware.md +5 -3
  110. package/examples/assets/example.yaml +1 -1
  111. package/examples/cookbook/middleware/envelope.mjs +4 -2
  112. package/examples/cookbook/route-index.json +1 -1
  113. package/examples/cookbook/routes/middleware.yaml +1 -1
  114. package/examples/prerender/README.md +14 -6
  115. package/examples/prerender/functions/page.mjs +4 -2
  116. package/examples/prerender/middleware/template.mjs +1 -1
  117. package/examples/prerender/prerender.mjs +1 -1
  118. package/examples/prerender/urlcode.yaml +8 -4
  119. package/llms-full.txt +503 -88
  120. package/llms.txt +6 -4
  121. package/package.json +27 -4
  122. package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
  123. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +17 -19
  124. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +9 -9
  125. package/recipes/authenticated-json-api/README.md +4 -3
  126. package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
  127. package/recipes/authenticated-json-api/recipe.yaml +1 -1
  128. package/recipes/contact-form/functions/contact.mjs +2 -1
  129. package/recipes/contact-form/recipe.yaml +2 -2
  130. package/recipes/cors-api/README.md +2 -2
  131. package/recipes/cors-api/recipe.yaml +1 -1
  132. package/recipes/health-page/README.md +1 -1
  133. package/recipes/json-api/README.md +1 -1
  134. package/recipes/json-api/recipe.yaml +3 -3
  135. package/recipes/middleware/README.md +8 -4
  136. package/recipes/middleware/middleware/envelope.mjs +4 -2
  137. package/recipes/protected-download/README.md +1 -1
  138. package/recipes/protected-download/recipe.yaml +1 -1
  139. package/recipes/static-plus-api/README.md +2 -2
  140. package/recipes/static-plus-api/public/index.html +1 -1
  141. package/recipes/static-plus-api/recipe.yaml +1 -1
  142. package/recipes/static-plus-api/urlcode.yaml +1 -1
  143. package/recipes/typescript/recipe.yaml +4 -4
  144. package/skills/urlcode/SKILL.md +6 -6
  145. package/starters/default/AGENTS.md +6 -6
  146. package/docs/SPIKE-LAMBDA-COMPILE.md +0 -201
  147. package/docs/SPIKE-MONOREPO.md +0 -322
package/docs/STARTERS.md CHANGED
@@ -36,18 +36,30 @@ on every push and pull request once the project is on GitHub.
36
36
  See [organization](ORGANIZATION.md) for choosing your own layout. Defaults allow
37
37
  GET/HEAD and use redirect 302; add configuration only when changing behavior.
38
38
 
39
+ `urlcode init` writes no `package.json`: the route project is route-only, and its
40
+ runtime may be installed globally, in a parent workspace or in a container. Add
41
+ `--manifest` to also write one pinning the runtime at exactly the version that
42
+ generated the project, then run `npm install` in it yourself to install that
43
+ version and produce a lockfile. The CLI never runs a package manager, and no
44
+ upgrade command exists — a pinned version changes when you edit the manifest.
45
+
39
46
  Initialization refuses an existing destination. Own the app in your own repository,
40
47
  keep secrets out of Git, and upgrade the runtime separately without regenerating
41
48
  application files. Add pages, downloads, more functions and business-specific
42
49
  features to this same project. Update tests and the expected route count as it grows.
43
50
  See [readiness](READINESS.md) and [security](FUNCTION-SECURITY.md).
44
51
 
45
- To start an extended site instead, install the extension packages from npm
46
- (`npm install @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin`, published as
47
- `0.1.0-alpha.x` prereleases) in the directory you run from and pass their names: `urlcode init ../my-site --with
48
- auth,admin` writes the same starter under `my-site/app/`, merges each package's
52
+ To start an extended site instead, install the compatible core, UI, auth and
53
+ admin set from npm in the directory you run from; the
54
+ [framework guide](FRAMEWORK.md#the-composition-contract) provides the exact
55
+ `0.4.1` command to use after publication. Pass UI first so its kit activates
56
+ before auth: `urlcode init ../my-site --with ui,auth,admin` writes the same starter under `my-site/app/`, merges each package's
49
57
  routes and declarations into it, and generates one `host.mjs` and README beside
50
- it. The contract each package fulfils is in [extensions](EXTENSIONS.md#scaffolding-with-init---with).
58
+ it, plus a `package.json` pinning the runtime, those packages and their declared
59
+ peers at the versions it just resolved, validated together against every declared
60
+ peer range. Installing them is your explicit `npm install` in that directory.
61
+ The contract each package fulfils is in [extensions](EXTENSIONS.md#scaffolding-with-init---with),
62
+ with `--no-manifest` and `--pin` in [recorded versions](EXTENSIONS.md#recorded-versions).
51
63
 
52
64
  Both paths carry an `AGENTS.md` for repository-aware assistants. `urlcode init`
53
65
  generates it from the installed runtime's capability catalog (the same source as
package/docs/STATIC.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Deploying to static hosting (S3 + CloudFront)
2
2
 
3
3
  The `static` target is the bottom rung of the [capability
4
- ladder](SPIKE-CORE-LAYERING.md#the-full-ladder-one-contract-one-vocabulary-per-level):
4
+ ladder](FRAMEWORK.md#the-ladder):
5
5
  the same `urlcode.yaml` a self-hosted server or a serverless adapter runs, with
6
6
  no server process at all. `urlcode build --target static` compiles a project
7
7
  ahead of time into plain files and two small JSON manifests meant for an S3
@@ -15,7 +15,7 @@ urlcode build --target static --project . --out dist --origin https://links.exam
15
15
 
16
16
  This target serves **redirects**, **declared responses** (`respond:`) and
17
17
  static files (`page`, `static`, `download`) — no path parameters, no query
18
- passthrough or mapping, GET/HEAD only, and no `enabled: false` or `expires`
18
+ passthrough or mapping, both GET and HEAD required, `respond.status: 200` only, and no `enabled: false` or `expires`
19
19
  (there is no server to answer a disabled or expired route with 404/410, so the
20
20
  build refuses one instead of silently serving it forever). Everything else is
21
21
  refused **at build time**, with the route pattern and the reason named:
@@ -33,7 +33,8 @@ refused **at build time**, with the route pattern and the reason named:
33
33
  | a redirect with a `{parameter}` in its path | S3's per-object redirect is keyed to one exact object, not a pattern |
34
34
  | a redirect with `query.pass`/`query.map` | S3's per-object redirect cannot compute a target per request |
35
35
  | a redirect with a `status` other than 301 | S3's per-object website redirect always answers 301 |
36
- | a route declaring methods other than GET/HEAD | static hosting only ever answers GET/HEAD |
36
+ | a route not admitting both GET and HEAD, or admitting other methods | the output cannot enforce a different method set |
37
+ | `respond.status` other than 200 | an uploaded response object cannot preserve a custom HTTP status |
37
38
  | a route with `enabled: false` or `expires` | no server to answer a disabled/expired route; remove the route instead |
38
39
 
39
40
  Run `urlcode capabilities --target static` for the full catalog.
@@ -92,3 +93,13 @@ object layout, the redirect manifest, and every refusal above. **It has not
92
93
  been deployed to S3 or fronted by CloudFront.** Bucket policy, CloudFront
93
94
  caching behavior, TLS/domain setup and the exact `aws s3` invocations above are
94
95
  unverified until a real deployment exercises them.
96
+
97
+
98
+ ## Building pages with middleware
99
+
100
+ Use [prerendering](PRERENDER.md) to execute functions and native middleware at
101
+ build time, then export the generated native page routes with this target.
102
+ Trusted Node execution is the build default; `sandbox: true` retains its
103
+ restricted imports and resource limits. Neither mode adds a request-time server
104
+ to the static output. Authentication, request-dependent headers and other
105
+ per-request middleware cannot be baked into a public file safely.
package/docs/TOOLING.md CHANGED
@@ -81,10 +81,12 @@ network. Keys always appear in this order:
81
81
  `--host-file`, `host` counts the operator module's extensions and plugins
82
82
  without activating them.
83
83
  - `routes`: path, methods and handler per route, sorted by path.
84
- - `constraints`: a fixed list that holds for every project (no guest network,
85
- no Node APIs, no regex routes, one handler per route, exact or `{param}`
86
- path segments, subtree mounts only for static and extension routes, no YAML
87
- interpolation, secrets by operator grant only), each with a value and a note.
84
+ - `constraints`: a fixed list that holds for every project (network and Node
85
+ built-ins available to trusted code and withdrawn by `sandbox: true`, no
86
+ regex routes, one handler per route, exact or `{param}` path segments,
87
+ subtree mounts only for static and extension routes, no YAML interpolation,
88
+ injected `env`/`secrets` by operator grant only), each with a value and a
89
+ note spelling out how it differs between the two trust modes.
88
90
  - `targets`: for each capability target (or the one `--target`), which of this
89
91
  project's used features are supported, conditional, refused or unknown.
90
92
  - `commands`: the exact `validate`, `test`, `audit --expect-routes N` (N is
@@ -137,8 +139,8 @@ assistant file-write, guest-execution, deployment or network authority.
137
139
 
138
140
  `urlcode explain [/route] [--project DIR] [--target T] [--host-file F] [--json]`
139
141
  prints what `explainRoute` returns: one route in detail, or without a path a
140
- one-line-per-route table (methods, handler, state, middleware count, policies,
141
- cache outcome and target support). `--target` narrows the support columns to
142
+ one-line-per-route table (methods, handler, state, execution mode, middleware
143
+ count, policies, cache outcome and target support). `--target` narrows the support columns to
142
144
  one deployment target; `--host-file` supplies the operator registry so
143
145
  extension requirements show their provider. An unknown route exits 1 and names
144
146
  the nearest patterns. Everything comes from the compiled configuration: no
@@ -148,7 +150,8 @@ request is evaluated, no function runs and no binding is read.
148
150
  `schemaVersion`, the `urlcode` version, the entry file and its includes, the
149
151
  `revision` (the same digest `inspectExtensionRevision` returns, so an operator
150
152
  pin can be checked against it), the config `configVersion`, every route (path,
151
- methods, handler, state, middleware, inputs, policy names, extension
153
+ methods, handler, state, execution mode (`sandbox`, with `sandboxReason` when
154
+ the route declares one), middleware, inputs, policy names, extension
152
155
  requirements, cache outcome, binding names, egress origins, capabilities and
153
156
  per-target support), the union of capabilities used, extension declarations
154
157
  (version, configuration keys, mounts and protected routes), recipe provenance
@@ -48,7 +48,12 @@ refused, and the source graph is limited to 128 modules, 1 MiB per source and
48
48
  source parser and byte limits before publication. For a trusted route, none of
49
49
  that applies: bare/npm specifiers, dynamic `import()`, `import.meta` and
50
50
  import attributes pass through unchanged (resolved by Node at serve time, not
51
- by this build), and there is no module-count or size ceiling. In both modes,
51
+ by this build), and there is no module-count or aggregate-size ceiling only
52
+ a generous 16 MiB per-source read cap that bounds authoring-time memory. A
53
+ module may be shared by trusted and sandboxed routes: it is emitted once, and
54
+ every module reachable from a `sandbox: true` route is still validated under
55
+ the sandbox rules and budgets above, which does not disqualify the trusted
56
+ route that also imports it. In both modes,
52
57
  no import extension inference occurs — relative imports of project modules
53
58
  still need an explicit `.ts`/`.js`/`.mjs` extension to be rewritten and
54
59
  followed. This does not execute the modules or replace normal route, policy,
package/docs/VERCEL.md CHANGED
@@ -7,7 +7,8 @@ that is the point of the project format.
7
7
  **This adapter serves native handlers only:** redirects, validated responses,
8
8
  pages, static assets and downloads. `function` and `middleware` routes are
9
9
  refused at activation, trusted or sandboxed alike, not per request, so a deployment cannot
10
- half-work. See [what is not supported](#what-this-adapter-does-not-do).
10
+ half-work, and that is a settled position rather than a pending limitation. See
11
+ [what is not supported](#what-this-adapter-does-not-do).
11
12
 
12
13
  A working project is in [`examples/vercel/`](../examples/vercel/).
13
14
 
@@ -71,13 +72,20 @@ and want it in generated URLs.
71
72
 
72
73
  | Not supported | Why |
73
74
  |---|---|
74
- | `function` routes | They need the self-hosted Node lifecycle; a `sandbox: true` route would additionally spawn worker threads and load the WASM engine on every cold start. Correctness is not the issue; predictable latency is, and it is unmeasured. |
75
+ | `function` routes | They need the self-hosted Node lifecycle; a `sandbox: true` route would additionally spawn worker threads and load the WASM engine on every cold start. Correctness is not the issue; the execution model is per-route compilation was considered and declined. |
75
76
  | Middleware | Runs in the same execution mode as the route's function, and is refused with it. |
76
77
  | `urlcode serve` operational endpoints | `/_urlcode/health` and `/_urlcode/ready` describe a long-lived process. Use Vercel's own observability. |
77
78
 
78
79
  Each refusal happens at activation with a message naming the route, so you find
79
80
  out on deploy rather than on a request.
80
81
 
82
+ `function` and `middleware` are not coming to this adapter. The supported answer
83
+ is to deploy the project as one trusted Node process — a container or a VM
84
+ running the project as it runs locally — which supports every route type today,
85
+ on any host you like including AWS (ECS, EC2, App Runner). See
86
+ [the decision](OPEN-DECISIONS.md#accepted-one-node-deployment-per-project) and
87
+ [the analysis behind it](archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md).
88
+
81
89
  ## Operating it
82
90
 
83
91
  Every instance activates the project independently: parsing YAML, snapshotting
@@ -1,205 +1,80 @@
1
- # Core version alignment across repositories
2
-
3
- Which core version each downstream package supports, how it says so, and the
4
- order in which a core behavior change reaches downstream repositories. This
5
- page is a mechanism and an invariant, not a schedule: it states no release
6
- cadence, no LTS line and no support window. Those are not decided.
7
-
8
- It exists because "the current core" had come to mean five different things at
9
- once, and one of them did not resolve. The `0.4.0-alpha.2` release closes that;
10
- the table below is where every repository lands, and it is the register to
11
- change whenever a version changes anywhere.
12
-
13
- | Repository | How it names core | Value (read from its own `package.json`/`peers.json`) |
14
- |---|---|---|
15
- | `urlcode` | source version | `0.4.0-alpha.2` |
16
- | `urlcode-auth`, `urlcode-admin` | peer range plus a reviewed SHA | `>=0.4.0-alpha.1 <0.5.0`; `peers.json` `urlcode` = `d5e86017e93b96ec24bfdbf840692b95fc323151` in both |
17
- | `urlcode-dynamic-link` | peer range | `>=0.4.0-alpha.1 <0.5.0` |
18
- | `urlcode-middleware` | peer range | `>=0.4.0-alpha.2 <0.5.0` |
19
- | `urlcode-short`, `urlcode-template`, `urlcode-docs` | exact dependency pin | `0.4.0-alpha.2` |
20
-
21
- Only `urlcode-middleware` requires `0.4.0-alpha.2` specifically: it uses the
22
- `middleware()` extension hook, `ExtensionActivation.root` and
23
- `RuntimeExtension.cacheSensitive`, none of which exist in `0.4.0-alpha.1`. The
24
- other extension packages work against either alpha and keep the wider floor,
25
- which is what the supported-floor definition below requires of them.
26
-
27
- The npm dist-tags for `@jimhoyd/urlcode` are `latest` = `0.3.0` and `alpha` =
28
- `0.4.0-alpha.2`. `latest` deliberately stays on the `0.3.0` Apache-2.0
29
- self-hosted baseline: the `0.4.0` line is a prerelease and must not become the
30
- default install. Every release workflow derives its dist-tag from the version
31
- rather than defaulting, so a prerelease can only publish under `alpha`.
32
-
33
- The sibling packages are `@jimhoyd/urlcode-ui` `0.1.0-alpha.5`,
34
- `@jimhoyd/urlcode-auth` and `@jimhoyd/urlcode-admin` `0.1.0-alpha.3`, and
35
- `@jimhoyd/urlcode-dynamic-link`, `@jimhoyd/urlcode-middleware` and
36
- `@jimhoyd/urlcode-short` `0.1.0-alpha.2`. For the extension line, `latest` and
37
- `alpha` point at the same version see the second invariant below.
38
-
39
- Every one of those is a new version in this release. Each package's previous
40
- release sat at the same version number as a source tree that had moved well
41
- past it 43 merged commits in `urlcode-auth`, 40 in `urlcode-ui`, 28 in
42
- `urlcode-admin` — so the published version number identified nothing. A
43
- version number that does not change when the source does is the same class of
44
- defect as a peer range that cannot resolve, and the rule is the same: change
45
- the version in the pull request that changes the source.
46
-
47
- ## The supported floor
48
-
49
- The supported core floor for an extension package is the **lowest core version
50
- published to npm that contains every core API the extension calls**. It is a
51
- property of the code, not of a calendar: raise it when the extension starts
52
- using a core API that older published cores do not have, and not otherwise.
53
-
54
- Everything below follows from that one definition.
55
-
56
- ## How a package expresses its core requirement
57
-
58
- Three forms are in use. They are not interchangeable.
59
-
60
- **Peer range (`peerDependencies`), for an extension package.** An extension is
61
- installed alongside core by the operator, so it must not carry its own copy;
62
- `peerDependencies` is the correct field. Write it as a floor plus the next
63
- breaking bound — `">=<floor> <0.5.0"` — where the floor is the supported floor
64
- above. `urlcode-auth` and `urlcode-admin` use this form.
65
-
66
- **Exact pin (`dependencies`), for an application or a starter.** A project that
67
- is deployed or cloned rather than composed — `urlcode-short`, `urlcode-template`,
68
- `urlcode-docs` — depends on one core version and pins it exactly. This is the
69
- right form when the repository's tests, generated files and documentation were
70
- all produced against one runtime and are only claimed to hold for that runtime.
71
-
72
- **Reviewed SHA (`peers.json`), for source CI on top of a range.** A reviewed
73
- commit is not a substitute for the peer range; it is an addition to it, used
74
- where the repository must build and test against an exact reviewed core
75
- checkout rather than whatever the registry resolves. `urlcode-auth` and
76
- `urlcode-admin` each keep one `peers.json` naming the reviewed core commit;
77
- each file states in its own `$comment` that published releases do not use those
78
- SHAs and resolve peers from the registry by the `package.json` range instead.
79
- Use a reviewed SHA when source CI needs reproducibility; do not use it to
80
- express what an installing operator will get.
81
-
82
- ## The invariant: a published peer range must be satisfiable
83
-
84
- **A package published to npm must never declare a peer range that no published
85
- core version satisfies.** At publish time, at least one version on the registry
86
- must fall inside the range. A range that points at an unpublished core is not a
87
- forward-looking declaration — it is an install failure for everyone who takes
88
- the package from the registry.
89
-
90
- The worked example came from this project. `@jimhoyd/urlcode-middleware`
91
- `0.1.0-alpha.1` was published declaring `peerDependencies`
92
- `{"@jimhoyd/urlcode": ">=0.4.0-alpha.2"}` at a time when the registry held only
93
- `0.3.0` and `0.4.0-alpha.1`, so nothing satisfied it. The range was correct —
94
- the package genuinely needs APIs that first appear in `0.4.0-alpha.2` — so the
95
- fix was never to widen the range. The publication order was wrong: the package
96
- was published before the core it requires, and it could be installed only from
97
- source against a vendored core tarball carried for exactly that reason.
98
-
99
- Publishing core `0.4.0-alpha.2` resolves it without any change to the already
100
- published package: the range becomes satisfiable the moment core is on the
101
- registry. The vendored tarball and the source-only install path go away with
102
- it.
103
-
104
- That is what the invariant prevents, and it is the only ordering rule that
105
- cannot be relaxed.
106
-
107
- ## The second invariant: `latest` must not fall below a sibling's floor
108
-
109
- **Where a package line publishes under a prerelease dist-tag, `latest` must
110
- still resolve to a version that satisfies every peer floor its siblings
111
- declare.** `latest` is what a plain `npm install <package>` resolves, so a
112
- `latest` left behind hands the installing operator a build that another
113
- package in the same install refuses.
114
-
115
- The worked example, again live in this project: `@jimhoyd/urlcode-auth`
116
- published `alpha` = `0.1.0-alpha.2` while leaving `latest` = `0.1.0-alpha.1`.
117
- `@jimhoyd/urlcode-admin` declares a peer floor of `>=0.1.0-alpha.2` on auth, so
118
- `npm install @jimhoyd/urlcode-auth` resolved a build below the floor admin
119
- requires. Nothing in the source is wrong; the dist-tag is.
120
-
121
- The fix is a registry operation rather than a source change:
1
+ # Package and channel alignment
2
+
3
+ The package manifests and root lockfile are the version authority. Read live
4
+ registry and Git tag state with `npm run release:status`; do not maintain a
5
+ second table of changing version numbers in documentation.
6
+
7
+ | Package | Manifest | Version owner | Release tag |
8
+ | --- | --- | --- | --- |
9
+ | `@jimhoyd/urlcode` | `package.json` | Explicit core release PR | `v<version>` |
10
+ | `@jimhoyd/urlcode-ui` | `packages/ui/package.json` | Changesets | `@jimhoyd/urlcode-ui@<version>` |
11
+ | `@jimhoyd/urlcode-auth` | `packages/auth/package.json` | Changesets | `@jimhoyd/urlcode-auth@<version>` |
12
+ | `@jimhoyd/urlcode-admin` | `packages/admin/package.json` | Changesets | `@jimhoyd/urlcode-admin@<version>` |
13
+
14
+ Development uses workspace source. Auth and admin's `file:../..` development
15
+ links resolve core to this checkout, enforced by `check-workspace-links.ts`.
16
+ Core never imports extension packages. Release verification instead installs the
17
+ published lower bound of each declared peer range and checks resolution. A peer
18
+ floor rises when code requires a newly introduced API, not just because a sibling
19
+ published another version. Preserve the declared upper bound during Changesets
20
+ versioning; `.changeset/config.json` limits unnecessary peer rewrites.
21
+
22
+ Publishable workspace changes carry Changesets; the release PR applies them and
23
+ updates versions, changelogs and the lockfile together. Core stays an explicit
24
+ entry in that PR until a separately reviewed workspace migration. Its CLI banner
25
+ must match its manifest. `npm run release:check` rejects stale lockfile versions.
26
+ Unreleased source changes do not require moving a published tag or pretending a
27
+ new package has already shipped.
28
+
29
+ The `0.4.1` release is an explicit stable release decision for core, UI, auth
30
+ and admin. Publication moves each package's npm `latest` channel to `0.4.1`, in
31
+ core UI → auth → admin order, after its release checks pass. A prepared
32
+ manifest or merged release PR does not prove registry publication: use
33
+ `npm run release:status` to inspect the live result before installing the set.
34
+ This alignment does not permanently couple package versions; subsequent
35
+ releases can still select only the packages that changed.
36
+
37
+ Alpha releases publish under `alpha`; they never automatically move npm
38
+ `latest`. Stable publication does not move `alpha`, so the two channels can
39
+ legitimately show different versions. Test the install combination you recommend
40
+ against peer ranges. `release:status` reports each declared peer floor and
41
+ whether its current `latest` and `alpha` satisfy the range.
42
+
43
+ After all four `0.4.1` versions are published, install the aligned set with:
122
44
 
123
45
  ```sh
124
- npm dist-tag add @jimhoyd/urlcode-auth@0.1.0-alpha.2 latest
46
+ npm install --save-exact @jimhoyd/urlcode@0.4.1 @jimhoyd/urlcode-ui@0.4.1 @jimhoyd/urlcode-auth@0.4.1 @jimhoyd/urlcode-admin@0.4.1
125
47
  ```
126
48
 
127
- Core is the deliberate exception. Its `latest` stays on `0.3.0` because no
128
- sibling declares a floor above it the extension packages name core through
129
- `peerDependencies`, which resolve by range and never by dist-tag.
130
-
131
- ## A deliberate older pin is a position, not drift
132
-
133
- `urlcode-template`, `urlcode-docs` and `urlcode-short` now all pin
134
- `0.4.0-alpha.2`, so no downstream repository is currently behind. An older pin
135
- remains a legitimate position, and the rule for it does not change: it is
136
- recorded where a reader will meet it. The repository's README says which core
137
- version it pins, and every statement about runtime behavior in that repository
138
- is read against that version. Where a statement is only true for the pinned version, it
139
- says so and names the version, rather than being silently corrected to match
140
- core's unreleased `main`.
141
-
142
- This matters most for the trust model. Core `0.4.0-alpha.2` runs `function` and
143
- `middleware` routes trusted and unsandboxed by default, with `sandbox: true` as
144
- a per-route opt-in ([decision record](SPIKE-DEFAULT-TRUST-MODEL.md)).
145
- `0.4.0-alpha.1` and earlier sandbox all such code unconditionally and have no
146
- `sandbox` field in the schema. This is why moving a pin to `0.4.0-alpha.2` is
147
- a behavior change even when no YAML changes: every `function`/`middleware`
148
- route that does not declare `sandbox` becomes trusted on upgrade. Review those
149
- routes before raising a pin, and add `sandbox: true` to the ones that handle
150
- input or code you would not trust with full Node/filesystem/network access.
151
- A repository still pinned to `0.4.0-alpha.1` or `0.3.0` that documents
152
- sandbox-by-default is **describing its pin correctly**. Its
153
- generated files field references, schemas, scaffolding output, vendored agent
154
- skills are likewise correct for that pin. Divergence from core's current
155
- `main` is not by itself staleness, and it is not a defect to be "fixed" by
156
- importing behavior the pinned runtime does not have. The same point is made
157
- about the advisory `npm run check:downstream-skills` report in
158
- [release readiness](RELEASE-READINESS.md).
159
-
160
- What is a defect: a repository that does not say which core version it pins, or
161
- that states pin-dependent behavior as an unconditional property of URLCode.
162
-
163
- ## Order of operations when core changes behavior
164
-
165
- A core behavior change reaches downstream repositories in this order. Each step
166
- depends on the one before it; skipping ahead is what produced the
167
- `urlcode-middleware` breakage above.
168
-
169
- 1. **Publish core.** The version carrying the change goes to npm first. Until it
170
- is on the registry, no downstream peer range may reference it (the invariant
171
- above).
172
- 2. **Raise downstream floors.** Extension packages move their peer range floor
173
- to the published version; applications and starters move their exact pin.
174
- A repository that chooses to stay on the older core does nothing here — that
175
- is a valid outcome, and step 3 does not apply to it.
176
- 3. **Regenerate downstream generated files.** Field references, schemas,
177
- scaffolding output and vendored agent skills are regenerated *after* the
178
- floor moves, against the version now pinned — never before, or they describe
179
- a runtime the repository does not depend on.
180
-
181
- Reviewed-SHA repositories take the same three steps, and additionally update
182
- `peers.json` to the reviewed commit for the new core when source CI is expected
183
- to build against it. The SHA and the published floor answer different questions
184
- and are updated independently.
185
-
186
- ## Open: two publishing conventions
187
-
188
- The repositories publish under two different conventions, and the maintainer has
189
- not settled which one the project uses. Both are recorded here neutrally; this
190
- page does not pick one.
191
-
192
- - **Private until release.** `urlcode-dynamic-link` and `urlcode-middleware`
193
- keep `"private": true` in their `package.json` on `main` and drop it in the
194
- release commit. Publication is an explicit, visible act in the release diff,
195
- and an accidental `npm publish` from `main` fails closed. Both packages are
196
- nonetheless published on npm, so the convention has been exercised.
197
- - **Publishable on main.** `urlcode-auth`, `urlcode-admin` and `urlcode-ui`
198
- carry no `private` field and declare `publishConfig.access = "public"`
199
- instead. The committed manifest is
200
- the manifest that publishes, so what is on the registry can be diffed against
201
- `main` without accounting for a release-only edit.
202
-
203
- The split is currently by repository, not by package kind, and nothing records
204
- why. Settling it is a maintainer decision; no `package.json` is changed on the
205
- strength of this page.
49
+ Bare package names resolve npm's current `latest`; exact application pins and a
50
+ committed lockfile keep an existing application from changing on a new release.
51
+
52
+ ## Generated applications
53
+
54
+ A generated application records its own versions. `urlcode init --with` writes a
55
+ `package.json` pinning the running runtime, the named extensions and their
56
+ declared peers at the exact versions resolved at generation time, after checking
57
+ that set against every declared peer range; `urlcode init --manifest` does the
58
+ same for a route-only project with the runtime alone; `urlcode-auth init` pins
59
+ this package and its peers. Plain `urlcode init` stays route-only and writes no
60
+ manifest, for projects whose runtime is managed elsewhere. Generation never runs
61
+ a package manager: `package-lock.json` exists only after the operator runs
62
+ `npm install` in the generated directory, and a pin taken from a local path or
63
+ tarball reproduces only where that path exists.
64
+
65
+ No upgrade command exists. A generated project moves to new versions by an
66
+ operator editing its manifest and re-installing. The issue that asked for this
67
+ (#212) describes a future command that would choose a tested compatible set,
68
+ show the changes and require explicit alpha selection; nothing here implements
69
+ that, and the pins above are only the groundwork it would need.
70
+
71
+ The standalone `urlcode-template` is an external exact-version consumer: after a
72
+ runtime release, update its dependency and starter through its own reviewed PR.
73
+ The coordinator prepares and checks that PR after registry installation succeeds;
74
+ the template is a consumer update, not a fifth npm package. The retired
75
+ `urlcode-docs`, `urlcode-middleware`, `urlcode-dynamic-link` and `urlcode-short`
76
+ repositories are not release targets.
77
+
78
+ See [the development pipeline](DEVELOPMENT-PIPELINE.md) for preparation,
79
+ publication order, immutable retries and credential scope, and
80
+ [release security](RELEASE-SECURITY.md) for provenance and limitations.
@@ -0,0 +1,68 @@
1
+ # Historical record
2
+
3
+ Archived 2026-09-19. This records an earlier implementation or proposal, not
4
+ current instructions. See the [current roadmap](../../../ROADMAP.md),
5
+ [current contract](../../SPECIFICATION.md) and [open decisions](../../OPEN-DECISIONS.md).
6
+ Remaining acceptance work is not declared complete by archiving this record.
7
+
8
+ <!-- trust-model-prose: historical-file -->
9
+ <!-- guidance-claims: ignore-file -->
10
+
11
+ # Extension implementation sequence
12
+
13
+ PR #54 records the auth, admin, UI and extension direction. Implementation is
14
+ incremental; the spikes are proposals, not the implemented project schema.
15
+ The implemented specification remains authoritative. This sequence incorporates
16
+ the architectural review rather than treating contradictory spike examples as
17
+ requirements.
18
+
19
+ ## Invariants
20
+
21
+ - Operators explicitly install trusted host extensions; project YAML never loads
22
+ host modules or chooses packages. Application code remains isolated in WASM.
23
+ - Authentication credentials stay in host processing. Guest Request headers and
24
+ header-derived parameters must not expose operator-declared credential headers,
25
+ including on public routes receiving a session cookie.
26
+ - Session and one-time-token changes have explicit concurrency semantics. Define
27
+ atomic operations before selecting a database abstraction; multi-record
28
+ operations cannot rely on a same-collection transaction promise.
29
+ - Passwords and bearer credentials use appropriate hashes. TOTP requires protected
30
+ recoverable key material and an operator key rotation/backup contract.
31
+ - Authentication forms and submission destinations belong to trusted extension
32
+ code. Project styling does not imply permission to replace security controls.
33
+ - Administrative authorization is enforced by the API, including subject-level
34
+ restrictions, self-promotion and last-administrator protections.
35
+ - Unsupported target capabilities fail explicitly. Live provider deployment
36
+ testing remains deferred and non-blocking; local tests do not prove deployment.
37
+
38
+ ## Delivery order
39
+
40
+ 1. **Credential boundary:** add an explicit host-plugin declaration for headers
41
+ withheld from guest requests and parameter resolution. Preserve originals for
42
+ host authentication, cache decisions and policy processing. Test functions,
43
+ middleware and routes without an authentication requirement.
44
+ 2. **Extension registration:** explicit operator registry, versioned configuration
45
+ validation, route ownership/collision rules and capability reporting. No
46
+ automatic project host-file execution. Define policy ordering before adding
47
+ an auth policy.
48
+ 3. **Session slice:** one supported login method, trusted login/logout forms,
49
+ protected page and JSON routes, session lookup/revocation, CSRF and no-store
50
+ behavior. Evaluate a maintained authentication implementation behind an
51
+ adapter before writing protocol engines. Define durable atomic store methods
52
+ and test concurrent redemption, expiry and interrupted operations.
53
+ 4. **Administrative API and CLI:** user lookup and session revocation first;
54
+ audit records and narrowly scoped permissions. Add role/recovery operations
55
+ only with object-level rules and concurrency tests.
56
+ 5. **UI and console:** extract components from real auth/admin flows; choose a
57
+ constrained renderer and asset ownership model; verify keyboard and assistive
58
+ technology behavior as well as automated accessibility checks.
59
+ 6. **Additional methods and targets:** passkeys, OIDC, MFA, recovery and imports
60
+ each receive state-machine, abuse-budget and portability tests. Preserve
61
+ verification of existing credential formats across target migrations.
62
+
63
+ ## Status
64
+
65
+ PR #54 is merged. The first implementation adds the operator-only
66
+ `Plugin.credentialHeaders` boundary, documented in [Plugins](../../PLUGINS.md).
67
+ The remaining items are pending; none of this document declares end-user auth,
68
+ admin routes, extension YAML, or a generic project store available today.