@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.
- package/.claude/skills/urlcode-authoring/SKILL.md +17 -19
- package/.claude/skills/urlcode-operations/SKILL.md +9 -9
- package/.claude-plugin/marketplace.json +1 -1
- package/CONTRIBUTING.md +20 -16
- package/README.md +59 -64
- package/ROADMAP.md +66 -376
- package/dist/BUILD-MANIFEST.json +26 -25
- package/dist/agents-guide.js +6 -6
- package/dist/authoring.js +15 -1
- package/dist/build-static.js +2 -0
- package/dist/capability-query.js +0 -1
- package/dist/catalog.js +0 -1
- package/dist/cli.js +25 -9
- package/dist/config.js +1 -1
- package/dist/explain-cli.js +4 -2
- package/dist/explain.js +8 -2
- package/dist/extensions.js +1 -1
- package/dist/http-response.js +1 -1
- package/dist/index.js +1 -0
- package/dist/init-with.js +36 -11
- package/dist/manifest.js +8 -2
- package/dist/mcp-authoring.js +4 -4
- package/dist/mcp.js +1 -1
- package/dist/policies/cache.js +2 -2
- package/dist/policies.js +3 -1
- package/dist/prerender.js +4 -0
- package/dist/project-dependencies.js +305 -0
- package/dist/readiness.js +3 -0
- package/dist/route-diff.js +12 -5
- package/dist/runtime.js +1 -1
- package/dist/trusted-functions.js +4 -5
- package/dist/types/authoring.d.ts +9 -1
- package/dist/types/capability-query.d.ts +0 -1
- package/dist/types/catalog.d.ts +0 -4
- package/dist/types/config.d.ts +1 -9
- package/dist/types/explain.d.ts +6 -1
- package/dist/types/extensions.d.ts +1 -1
- package/dist/types/http-response.d.ts +0 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/init-with.d.ts +7 -13
- package/dist/types/manifest.d.ts +5 -2
- package/dist/types/project-dependencies.d.ts +78 -0
- package/dist/types/readiness.d.ts +2 -0
- package/dist/types/trusted-functions.d.ts +1 -4
- package/dist/types/types.d.ts +8 -1
- package/dist/types.js +8 -1
- package/dist/typescript-authoring.js +18 -7
- package/docs/AI-AUTHORING.md +15 -7
- package/docs/ASSETS.md +2 -1
- package/docs/AUTH-BACKUP.md +32 -0
- package/docs/AWS.md +9 -0
- package/docs/BEST-PRACTICES.md +17 -9
- package/docs/CAPABILITIES.md +1 -1
- package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
- package/docs/CI.md +8 -3
- package/docs/CODEBASE-AUDIT-2026-09-20.md +284 -0
- package/docs/COMPOSING-A-SITE.md +278 -0
- package/docs/DEVELOPMENT-PIPELINE.md +270 -0
- package/docs/EXTENSIONS.md +51 -11
- package/docs/FRAMEWORK.md +67 -48
- package/docs/FUNCTION-SECURITY.md +44 -0
- package/docs/INSTALL.md +13 -8
- package/docs/LOCAL-DEVELOPMENT.md +3 -0
- package/docs/MIDDLEWARE.md +10 -4
- package/docs/OPEN-DECISIONS.md +224 -212
- package/docs/OPERATIONAL-PROOF.md +3 -3
- package/docs/OPERATIONS.md +3 -3
- package/docs/POLICIES.md +13 -5
- package/docs/PRERENDER.md +23 -11
- package/docs/PROJECT-DIRECTION.md +3 -3
- package/docs/READINESS.md +10 -5
- package/docs/README.md +20 -44
- package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
- package/docs/RELEASE-0.4.1.md +73 -0
- package/docs/RELEASE-READINESS.md +6 -6
- package/docs/RELEASE-SECURITY.md +96 -181
- package/docs/RESILIENCE.md +3 -3
- package/docs/ROUTING.md +3 -4
- package/docs/SECURITY-AUDIT.md +2 -2
- package/docs/SPECIFICATION.md +12 -6
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
- package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
- package/docs/SPIKE-CORE-LAYERING.md +49 -18
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
- package/docs/STARTERS.md +17 -5
- package/docs/STATIC.md +14 -3
- package/docs/TOOLING.md +10 -7
- package/docs/TYPESCRIPT-AUTHORING.md +6 -1
- package/docs/VERCEL.md +10 -2
- package/docs/VERSION-ALIGNMENT.md +76 -201
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
- package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +102 -0
- package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +15 -5
- package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +15 -3
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
- package/docs/archive/2026-09-19/ROADMAP.md +387 -0
- package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +11 -0
- package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
- package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +365 -0
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +778 -0
- package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
- package/docs/archive/README.md +28 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/compression.md +3 -2
- package/docs/policies/security.md +3 -2
- package/docs/yaml/functions.md +10 -2
- package/docs/yaml/middleware.md +5 -3
- package/examples/assets/example.yaml +1 -1
- package/examples/cookbook/middleware/envelope.mjs +4 -2
- package/examples/cookbook/route-index.json +1 -1
- package/examples/cookbook/routes/middleware.yaml +1 -1
- package/examples/prerender/README.md +14 -6
- package/examples/prerender/functions/page.mjs +4 -2
- package/examples/prerender/middleware/template.mjs +1 -1
- package/examples/prerender/prerender.mjs +1 -1
- package/examples/prerender/urlcode.yaml +8 -4
- package/llms-full.txt +503 -88
- package/llms.txt +6 -4
- package/package.json +27 -4
- package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +17 -19
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +9 -9
- package/recipes/authenticated-json-api/README.md +4 -3
- package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
- package/recipes/authenticated-json-api/recipe.yaml +1 -1
- package/recipes/contact-form/functions/contact.mjs +2 -1
- package/recipes/contact-form/recipe.yaml +2 -2
- package/recipes/cors-api/README.md +2 -2
- package/recipes/cors-api/recipe.yaml +1 -1
- package/recipes/health-page/README.md +1 -1
- package/recipes/json-api/README.md +1 -1
- package/recipes/json-api/recipe.yaml +3 -3
- package/recipes/middleware/README.md +8 -4
- package/recipes/middleware/middleware/envelope.mjs +4 -2
- package/recipes/protected-download/README.md +1 -1
- package/recipes/protected-download/recipe.yaml +1 -1
- package/recipes/static-plus-api/README.md +2 -2
- package/recipes/static-plus-api/public/index.html +1 -1
- package/recipes/static-plus-api/recipe.yaml +1 -1
- package/recipes/static-plus-api/urlcode.yaml +1 -1
- package/recipes/typescript/recipe.yaml +4 -4
- package/skills/urlcode/SKILL.md +6 -6
- package/starters/default/AGENTS.md +6 -6
- package/docs/SPIKE-LAMBDA-COMPILE.md +0 -201
- 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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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.
|
|
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](
|
|
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
|
|
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
|
|
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 (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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;
|
|
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
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
`
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
[release
|
|
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.
|