@jimhoyd/urlcode 0.4.0-alpha.2 → 0.4.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +44 -54
- package/ROADMAP.md +61 -376
- package/dist/BUILD-MANIFEST.json +15 -15
- package/dist/agents-guide.js +6 -6
- package/dist/build-static.js +2 -0
- package/dist/cli.js +3 -3
- package/dist/explain-cli.js +4 -2
- package/dist/explain.js +7 -1
- package/dist/extensions.js +1 -1
- package/dist/manifest.js +7 -1
- package/dist/mcp-authoring.js +2 -2
- package/dist/mcp.js +1 -1
- package/dist/policies.js +3 -1
- package/dist/prerender.js +4 -0
- package/dist/readiness.js +3 -0
- package/dist/route-diff.js +12 -5
- package/dist/types/explain.d.ts +6 -0
- package/dist/types/extensions.d.ts +1 -1
- package/dist/types/manifest.d.ts +5 -1
- package/dist/types/readiness.d.ts +2 -0
- 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 +10 -6
- package/docs/ASSETS.md +2 -1
- package/docs/AUTH-BACKUP.md +32 -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 +278 -0
- package/docs/DEVELOPMENT-PIPELINE.md +181 -0
- package/docs/EXTENSIONS.md +15 -5
- package/docs/FRAMEWORK.md +22 -18
- package/docs/FUNCTION-SECURITY.md +44 -0
- package/docs/LOCAL-DEVELOPMENT.md +3 -0
- package/docs/OPEN-DECISIONS.md +184 -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 +6 -2
- package/docs/README.md +18 -41
- package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
- package/docs/RELEASE-READINESS.md +6 -6
- package/docs/RELEASE-SECURITY.md +68 -168
- package/docs/RESILIENCE.md +3 -3
- package/docs/ROUTING.md +3 -4
- package/docs/SECURITY-AUDIT.md +2 -2
- package/docs/SPECIFICATION.md +7 -5
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
- package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
- package/docs/SPIKE-CORE-LAYERING.md +48 -17
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
- package/docs/SPIKE-LAMBDA-COMPILE.md +8 -0
- package/docs/STATIC.md +14 -3
- package/docs/TOOLING.md +4 -3
- package/docs/TYPESCRIPT-AUTHORING.md +6 -1
- package/docs/VERSION-ALIGNMENT.md +46 -205
- 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 +386 -0
- package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +10 -0
- package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
- package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
- package/docs/archive/README.md +27 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/compression.md +3 -2
- package/docs/policies/security.md +3 -2
- package/examples/assets/example.yaml +1 -1
- 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 +117 -45
- package/llms.txt +5 -4
- package/package.json +24 -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/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-MONOREPO.md +0 -322
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
# Spike: layering `link` and `middleware` out of core
|
|
2
2
|
|
|
3
|
+
> Review update, 2026-09-19: Current baseline: **both extractions this spike
|
|
4
|
+
> proposes are over, and neither ended in a shipped separate package.** Link
|
|
5
|
+
> extraction completed and the resulting package was then retired. Middleware
|
|
6
|
+
> extraction never completed: core still implements native middleware, and the
|
|
7
|
+
> separate middleware extension — which added a different API with per-entry
|
|
8
|
+
> sandboxing — has itself been unpublished at `0.1.0-alpha.2` and its
|
|
9
|
+
> repository deleted. The extraction direction below is superseded, and it is
|
|
10
|
+
> not an instruction to remove core middleware: core's native `middleware:`
|
|
11
|
+
> array is now the only implementation of per-route middleware there is.
|
|
12
|
+
>
|
|
13
|
+
> An earlier version of this note said "monorepo work is starting now:
|
|
14
|
+
> middleware moves in as a separate package first, and folds into core
|
|
15
|
+
> afterward." Monorepo work is still starting, for core, auth, admin and UI —
|
|
16
|
+
> but the middleware clause is void. There is no package to move in and
|
|
17
|
+
> nothing to fold in afterward.
|
|
18
|
+
|
|
19
|
+
|
|
3
20
|
Status: proposal, nothing implemented. No code in this repository does any of
|
|
4
21
|
this, and nothing here is committed scope. This combines two extraction ideas
|
|
5
22
|
into one spike because they share a mechanism and a sequence, not because
|
|
6
23
|
either is blocked on the other.
|
|
7
24
|
|
|
8
|
-
> **Update:** this spike's `link` extraction (Phase 1 below)
|
|
9
|
-
>
|
|
10
|
-
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
> this document describe the pre-extraction state this
|
|
14
|
-
> not current core.
|
|
25
|
+
> **Update:** this spike's `link` extraction (Phase 1 below) was implemented —
|
|
26
|
+
> core no longer has a native `link`/`LinkStore`/`dynamicLinks` API — and the
|
|
27
|
+
> separate `urlcode-dynamic-link` package that received it has since been
|
|
28
|
+
> retired, unpublished from npm and its repository deleted (September 2026).
|
|
29
|
+
> Nothing owns stored links today. Present-tense references to `link` as a core
|
|
30
|
+
> feature elsewhere in this document describe the pre-extraction state this
|
|
31
|
+
> spike was analyzing, not current core.
|
|
15
32
|
|
|
16
33
|
## The shape this is aiming at
|
|
17
34
|
|
|
@@ -41,7 +58,8 @@ honest instead of inventing two different shapes:
|
|
|
41
58
|
- **Mount ownership** (`route.extension`, `RuntimeExtension.activate` →
|
|
42
59
|
`ExtensionInstance.handle`): an extension owns an exclusive `/prefix/*` and
|
|
43
60
|
answers every request under it. This is the shape `link` needs — it already
|
|
44
|
-
behaves like a self-contained mount (
|
|
61
|
+
behaves like a self-contained mount (documented in the since-retired
|
|
62
|
+
`urlcode-dynamic-link` package), and
|
|
45
63
|
`auth`/`admin` already use it for `/account/*` and `/admin/*`
|
|
46
64
|
(`docs/FRAMEWORK.md:10-15`).
|
|
47
65
|
- **Wrap without owning** (`ExtensionInstance.authorize`, called when a route
|
|
@@ -161,7 +179,15 @@ before anything here is treated as settled.
|
|
|
161
179
|
need to exist or be attached before their Phase 2 work can be written or
|
|
162
180
|
verified, matching the constraint already flagged for `link`.
|
|
163
181
|
|
|
164
|
-
## Repo governance for the two new repos (decided)
|
|
182
|
+
## Repo governance for the two new repos (decided — both repos since deleted)
|
|
183
|
+
|
|
184
|
+
> **Historical, 2026-09-19.** Both repositories this section governs were
|
|
185
|
+
> created, released once, and then deleted; both packages are unpublished. The
|
|
186
|
+
> decisions below were applied while they existed and are kept as the recorded
|
|
187
|
+
> template for any future extension repository — not as a description of
|
|
188
|
+
> anything live. Note in particular that the "published public from the start"
|
|
189
|
+
> departure recorded below is the one whose consequences are worth reading
|
|
190
|
+
> back: both repos that took it are gone within days of their first release.
|
|
165
191
|
|
|
166
192
|
Both `urlcode-dynamic-link` and `urlcode-middleware` follow `GOVERNANCE.md`
|
|
167
193
|
and `AGENTS.md` as written, with one explicit decision recorded here per
|
|
@@ -176,7 +202,8 @@ AGENTS.md's "do not publish packages without an explicit decision":
|
|
|
176
202
|
admins or automation, CODEOWNERS recording ownership. CI/release workflow
|
|
177
203
|
shape copied from core's `release.yml` (candidate build → audit → pack →
|
|
178
204
|
attest → publish via trusted publisher, no long-lived npm token), per the
|
|
179
|
-
pattern `docs/NEXT-STEPS.md`
|
|
205
|
+
pattern §2.1 of the archived plan (`docs/archive/2026-09-19/NEXT-STEPS.md`)
|
|
206
|
+
already used for `auth`/`admin`/`ui`.
|
|
180
207
|
CodeQL required on main, secret scanning and push protection on, same as
|
|
181
208
|
core.
|
|
182
209
|
- **Published public from the start** — both the GitHub repo and the npm
|
|
@@ -225,9 +252,10 @@ system tax." The real costs are narrower and different for each:
|
|
|
225
252
|
cost described above still applies, in full, to whichever specific
|
|
226
253
|
`middleware:` wrap a developer explicitly opts into `sandbox: true` — and
|
|
227
254
|
the same mitigation still matters there: native declarative primitives
|
|
228
|
-
(
|
|
229
|
-
|
|
230
|
-
opt-in at all, for the cases where a
|
|
255
|
+
(the Phase 4.2 `auth: { required: true, role: ... }` direction in the
|
|
256
|
+
archived plan, `docs/archive/2026-09-19/NEXT-STEPS.md`) reduce how much logic
|
|
257
|
+
ever needs to reach for the sandboxed opt-in at all, for the cases where a
|
|
258
|
+
developer does judge it warranted.
|
|
231
259
|
|
|
232
260
|
## Other core pieces considered and set aside
|
|
233
261
|
|
|
@@ -253,14 +281,17 @@ part of the smallest complete product:
|
|
|
253
281
|
them would shrink core below the "complete product on its own" bar rather
|
|
254
282
|
than trim it.
|
|
255
283
|
- **Management API / operator grants / credential policy** — foundation that
|
|
256
|
-
extensions themselves depend on (`docs/MANAGEMENT-SECURITY.md`,
|
|
284
|
+
extensions themselves depend on (`docs/archive/2026-09-19/MANAGEMENT-SECURITY.md`,
|
|
257
285
|
`docs/FUNCTION-SECURITY.md`); moving it out would mean extracting the thing
|
|
258
286
|
the extraction pattern relies on.
|
|
259
287
|
|
|
260
288
|
Nothing else in core matches the `link`/`middleware` shape today. If a third
|
|
261
|
-
candidate is going to be found,
|
|
262
|
-
|
|
263
|
-
defensible way to find it than
|
|
289
|
+
candidate is going to be found, the repetition-log discipline of Phase 6 in the
|
|
290
|
+
archived plan (`docs/archive/2026-09-19/NEXT-STEPS.md`) — extract from observed
|
|
291
|
+
repetition, not speculation — is the more defensible way to find it than
|
|
292
|
+
continuing to eyeball the handler list. That plan records Phase 6 as never
|
|
293
|
+
started, and no `docs/REPETITION-LOG.md` was ever written, so the log itself
|
|
294
|
+
would have to be started before it could inform the decision.
|
|
264
295
|
|
|
265
296
|
Recommendation: before either Phase 2 begins, attach `urlcode-auth`,
|
|
266
297
|
`urlcode-admin` and `urlcode-ui` to a session and confirm (a) their actual
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
Status: **decided by the maintainer and implemented in `0.4.0-alpha.2`**
|
|
4
4
|
(`sandbox`/`sandboxReason` in `schemas/urlcode.schema.json`; see the
|
|
5
5
|
`0.4.0-alpha.2` entry in [the roadmap](../ROADMAP.md) and the resulting contract
|
|
6
|
-
in [function security](FUNCTION-SECURITY.md)).
|
|
7
|
-
published to npm. This overturns an
|
|
6
|
+
in [function security](FUNCTION-SECURITY.md)). The release is published; this file retains the decision rationale. This overturns an
|
|
8
7
|
explicit, previously-stated project rule — see "What this reverses" below —
|
|
9
8
|
so treat it as a deliberate, recorded policy change, not a code cleanup. The
|
|
10
9
|
body below is preserved as written at decision time; where it calls downstream
|
|
@@ -153,11 +152,14 @@ with `add_repo` before treating it as settled.
|
|
|
153
152
|
and `peers.json` in `auth`/`admin`/`ui` (`docs/SPIKE-CORE-LAYERING.md`'s
|
|
154
153
|
§2.2 reference) should pin deliberately to a core version that includes
|
|
155
154
|
this change, not inherit it silently on a routine bump.
|
|
156
|
-
- **`urlcode-dynamic-link` (built and published
|
|
155
|
+
- **`urlcode-dynamic-link` (built and published after this was written, then retired, unpublished and deleted in September 2026): moot.** It was a
|
|
157
156
|
mount-based extension like `auth`, not a `function`/`middleware` consumer —
|
|
158
157
|
nothing here changes its design.
|
|
159
|
-
- **`urlcode-middleware` (built and published since
|
|
160
|
-
|
|
158
|
+
- **`urlcode-middleware` (built and published since, then retired, unpublished
|
|
159
|
+
and deleted in September 2026): the rule outlived the package — decided.**
|
|
160
|
+
The decision below was made for that package but is not about it: it governs
|
|
161
|
+
core's own native `middleware:` array, which is where per-route middleware
|
|
162
|
+
lives now that the extension package is gone. First-party middleware is trusted by default, exactly like
|
|
161
163
|
`function`; `sandbox: true` is the same opt-in a developer reaches for
|
|
162
164
|
when a specific `middleware:` wrap genuinely warrants it (e.g. it's
|
|
163
165
|
processing input from a source the developer doesn't fully trust). This
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Spike: compiling `function` routes into their own Lambdas
|
|
2
2
|
|
|
3
|
+
> Review update, 2026-09-19: Current baseline: trusted Node execution is the default, so fresh invocation
|
|
4
|
+
> state is a guarantee of `sandbox: true` only. AWS/Vercel still reject functions
|
|
5
|
+
> and middleware in `src/capabilities.ts`; changing the default did not implement
|
|
6
|
+
> a provider adapter for them. Re-evaluate the proposed per-route lowering against
|
|
7
|
+
> a single trusted Node deployment per project before choosing either. Removed
|
|
8
|
+
> link APIs in the analysis below are historical.
|
|
9
|
+
|
|
10
|
+
|
|
3
11
|
Status: proposal, nothing implemented. No code in this repository does any of
|
|
4
12
|
this, and nothing here is committed scope.
|
|
5
13
|
|
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
|
@@ -137,8 +137,8 @@ assistant file-write, guest-execution, deployment or network authority.
|
|
|
137
137
|
|
|
138
138
|
`urlcode explain [/route] [--project DIR] [--target T] [--host-file F] [--json]`
|
|
139
139
|
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
|
|
140
|
+
one-line-per-route table (methods, handler, state, execution mode, middleware
|
|
141
|
+
count, policies, cache outcome and target support). `--target` narrows the support columns to
|
|
142
142
|
one deployment target; `--host-file` supplies the operator registry so
|
|
143
143
|
extension requirements show their provider. An unknown route exits 1 and names
|
|
144
144
|
the nearest patterns. Everything comes from the compiled configuration: no
|
|
@@ -148,7 +148,8 @@ request is evaluated, no function runs and no binding is read.
|
|
|
148
148
|
`schemaVersion`, the `urlcode` version, the entry file and its includes, the
|
|
149
149
|
`revision` (the same digest `inspectExtensionRevision` returns, so an operator
|
|
150
150
|
pin can be checked against it), the config `configVersion`, every route (path,
|
|
151
|
-
methods, handler, state,
|
|
151
|
+
methods, handler, state, execution mode (`sandbox`, with `sandboxReason` when
|
|
152
|
+
the route declares one), middleware, inputs, policy names, extension
|
|
152
153
|
requirements, cache outcome, binding names, egress origins, capabilities and
|
|
153
154
|
per-target support), the union of capabilities used, extension declarations
|
|
154
155
|
(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,
|
|
@@ -1,205 +1,46 @@
|
|
|
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
|
-
|
|
45
|
-
|
|
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:
|
|
122
|
-
|
|
123
|
-
```sh
|
|
124
|
-
npm dist-tag add @jimhoyd/urlcode-auth@0.1.0-alpha.2 latest
|
|
125
|
-
```
|
|
126
|
-
|
|
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.
|
|
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
|
+
Alpha releases publish under `alpha`; they never automatically move npm
|
|
30
|
+
`latest`. Core's historical `latest` remains the stable 0.3.0 baseline until an
|
|
31
|
+
explicit stable release decision. Extensions have historical alpha versions on
|
|
32
|
+
`latest`; subsequent alpha publication does not keep that channel in lockstep.
|
|
33
|
+
Different channel values alone are not drift. Test the install combination you
|
|
34
|
+
recommend against peer ranges; a bare install may select an older channel.
|
|
35
|
+
`release:status` reports each declared peer floor and whether its current
|
|
36
|
+
`latest` and `alpha` satisfy the range.
|
|
37
|
+
|
|
38
|
+
The standalone `urlcode-template` is an external exact-version consumer: after a
|
|
39
|
+
runtime release, update its dependency and starter through its own reviewed PR.
|
|
40
|
+
It is not automatically released by the monorepo coordinator. The retired
|
|
41
|
+
`urlcode-docs`, `urlcode-middleware`, `urlcode-dynamic-link` and `urlcode-short`
|
|
42
|
+
repositories are not release targets.
|
|
43
|
+
|
|
44
|
+
See [the development pipeline](DEVELOPMENT-PIPELINE.md) for preparation,
|
|
45
|
+
publication order, immutable retries and credential scope, and
|
|
46
|
+
[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.
|