@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/llms-full.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- Generated by scripts/build-llms-full.ts (npm run docs:llms). Do not edit; edit the source documents. -->
|
|
2
|
-
<!-- Consolidated URLCode authoring reference:
|
|
2
|
+
<!-- Consolidated URLCode authoring reference: 16 documents, about 52,663 tokens (estimate: characters / 4). -->
|
|
3
3
|
|
|
4
4
|
# URLCode
|
|
5
5
|
|
|
@@ -36,7 +36,8 @@ Agents that explicitly want the complete consolidated reference in one fetch sho
|
|
|
36
36
|
12. [Conditional routing](#conditional-routing)
|
|
37
37
|
13. [Bounded proxy and webhook transport](#bounded-proxy-and-webhook-transport)
|
|
38
38
|
14. [Operator-installed extensions](#operator-installed-extensions)
|
|
39
|
-
15. [
|
|
39
|
+
15. [Composing a site from ui, auth and admin](#composing-a-site-from-ui-auth-and-admin)
|
|
40
|
+
16. [Function execution: trusted by default, sandboxed opt-in](#function-execution-trusted-by-default-sandboxed-opt-in)
|
|
40
41
|
|
|
41
42
|
---
|
|
42
43
|
|
|
@@ -51,26 +52,26 @@ claim here is implemented in the linked repository; nothing is roadmap.
|
|
|
51
52
|
|
|
52
53
|
### Four packages, one project shape
|
|
53
54
|
|
|
54
|
-
| Package |
|
|
55
|
+
| Package | Source | What it adds | How a project declares it |
|
|
55
56
|
|---|---|---|---|
|
|
56
|
-
| `@jimhoyd/urlcode` |
|
|
57
|
-
| `@jimhoyd/urlcode-ui` | [
|
|
58
|
-
| `@jimhoyd/urlcode-auth` | [
|
|
59
|
-
| `@jimhoyd/urlcode-admin` | [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
endorsement: the source is complete, but independent review, deployment
|
|
66
|
-
evidence and an accessibility assessment are still pending
|
|
57
|
+
| `@jimhoyd/urlcode` | this repository | The runtime: YAML routes, functions and middleware (trusted by default, `sandbox: true` opt-in), pages and assets, policies, site conventions, CLI, provider adapters, the extension contract | `urlcode.yaml` with `version: "1"` |
|
|
58
|
+
| `@jimhoyd/urlcode-ui` | [`packages/ui`](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui) | Shared presentation: escaped templates, shadcn/ui partials, one stylesheet with light and dark, themes, translations, the `ui` extension that serves the kit's assets | `extensions.ui` plus an asset mount route |
|
|
59
|
+
| `@jimhoyd/urlcode-auth` | [`packages/auth`](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth) | Accounts: password, passkeys, OpenID Connect, email codes, TOTP, recovery, sessions, roles, registration modes, account page, operator CLI | `extensions.auth` plus an `/account/*` mount and `policies.extensions.auth` on protected routes |
|
|
60
|
+
| `@jimhoyd/urlcode-admin` | [`packages/admin`](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin) | Administration: users, sessions, roles, audit, registration approval, two-person cases, support impersonation, health | `extensions.admin` plus an `/admin/*` mount |
|
|
61
|
+
|
|
62
|
+
All four are Apache-2.0. The `0.4.1` release line aligns their stable versions;
|
|
63
|
+
check `npm run release:status` for publication progress. A stable npm channel is
|
|
64
|
+
not an independent assessment: review, deployment evidence and an accessibility
|
|
65
|
+
assessment are still pending
|
|
67
66
|
([issue 58](https://github.com/jimhoyd-com/urlcode/issues/58)). Their status
|
|
68
|
-
files say exactly what is built: [auth](https://github.com/jimhoyd-com/urlcode
|
|
69
|
-
[admin](https://github.com/jimhoyd-com/urlcode
|
|
70
|
-
[ui](https://github.com/jimhoyd-com/urlcode
|
|
71
|
-
|
|
72
|
-
in
|
|
73
|
-
|
|
67
|
+
files say exactly what is built: [auth](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/IMPLEMENTATION-STATUS.md),
|
|
68
|
+
[admin](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin/IMPLEMENTATION-STATUS.md),
|
|
69
|
+
[ui](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui/IMPLEMENTATION-STATUS.md).
|
|
70
|
+
The current version of each package is its own manifest, and the peer ranges it
|
|
71
|
+
declares are in that manifest too; do not read a version number out of this
|
|
72
|
+
page. How versions, channels and release tags line up is recorded in
|
|
73
|
+
[package and channel alignment](https://github.com/jimhoyd-com/urlcode/blob/main/docs/VERSION-ALIGNMENT.md), and `npm run
|
|
74
|
+
release:status` reports the live registry and tag state.
|
|
74
75
|
|
|
75
76
|
### The ladder
|
|
76
77
|
|
|
@@ -87,8 +88,10 @@ Each rung's YAML is valid on every rung above it.
|
|
|
87
88
|
3. **Functions and middleware.** `function` routes and ordered `middleware`
|
|
88
89
|
in JavaScript, trusted and in-process by default; a route declaring
|
|
89
90
|
`sandbox: true` runs isolated instead (QuickJS inside WebAssembly, fresh
|
|
90
|
-
heap per call, no Node, filesystem or network).
|
|
91
|
-
only
|
|
91
|
+
heap per call, no Node, filesystem or network). The `env`/`secrets` the
|
|
92
|
+
runtime injects into a function come only from an operator grant pinned to
|
|
93
|
+
the project revision; the grant governs that injected context, not the
|
|
94
|
+
ambient Node environment trusted in-process code can reach on its own.
|
|
92
95
|
4. **Accounts.** The `auth` extension: sign-in, registration, MFA, account
|
|
93
96
|
page and protected routes. The operator installs it in a host file outside
|
|
94
97
|
the project; YAML only declares the mount and configuration.
|
|
@@ -100,27 +103,29 @@ Each rung's YAML is valid on every rung above it.
|
|
|
100
103
|
project copy, template and stylesheet overrides for kit-rendered pages.
|
|
101
104
|
|
|
102
105
|
Stored short links previously sat here as a native `link` route; that handler
|
|
103
|
-
was removed from core. A
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
was removed from core. A `urlcode-dynamic-link` package owned them the same way
|
|
107
|
+
`auth`/`admin` own their mounts, but it has been retired and unpublished; no
|
|
108
|
+
package occupies this rung today.
|
|
106
109
|
|
|
107
110
|
Rungs 1 to 3 need only the core package. Rungs 4 to 6 need the extension
|
|
108
|
-
packages
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
packages installed from npm and a Node host with a
|
|
112
|
+
patched SQLite build; see each package's README ([auth](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/README.md),
|
|
113
|
+
[admin](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin/README.md), [ui](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui/README.md)) for the
|
|
114
|
+
exact requirement.
|
|
111
115
|
|
|
112
116
|
### The composition contract
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
After the aligned `0.4.1` packages are published, an extended project starts
|
|
119
|
+
with the packages and one command:
|
|
115
120
|
|
|
116
121
|
```sh
|
|
117
|
-
npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin
|
|
118
|
-
urlcode init my-site --with auth,admin
|
|
122
|
+
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
|
|
123
|
+
urlcode init my-site --with ui,auth,admin
|
|
119
124
|
```
|
|
120
125
|
|
|
121
|
-
Installing from npm is the normal path;
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
Installing from npm is the normal path; `scripts/pack-sources.mjs` still builds
|
|
127
|
+
local tarballs from a reviewed checkout for operators who install only source
|
|
128
|
+
they have read — one revision now covers core and every extension. Three files make an extended
|
|
124
129
|
project. Nothing else is discovered by convention.
|
|
125
130
|
|
|
126
131
|
```
|
|
@@ -152,7 +157,8 @@ routes:
|
|
|
152
157
|
auth: {}
|
|
153
158
|
```
|
|
154
159
|
|
|
155
|
-
The operator host
|
|
160
|
+
The operator host explicitly registers the packages. Registration is an
|
|
161
|
+
activation boundary; it does not isolate trusted application code from the host:
|
|
156
162
|
|
|
157
163
|
```js
|
|
158
164
|
import { createUiExtension } from '@jimhoyd/urlcode-ui/host';
|
|
@@ -172,18 +178,18 @@ export default {
|
|
|
172
178
|
};
|
|
173
179
|
```
|
|
174
180
|
|
|
175
|
-
Auth and admin
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
Auth and admin already render their screens through `ui.kit` when the host
|
|
182
|
+
supplies the UI extension; their package-owned templates and catalogues must be
|
|
183
|
+
registered with that kit. Without it, both retain shared primitive rendering
|
|
184
|
+
through `presentation`. The example above uses that primitive fallback. The UI
|
|
185
|
+
block is optional, and a migration to the kit is not unfinished framework work.
|
|
186
|
+
See each package README for its complete kit registration example.
|
|
181
187
|
|
|
182
188
|
```sh
|
|
183
189
|
urlcode serve --project /absolute/site --host-file /absolute/operator/host.mjs --origin https://site.example
|
|
184
190
|
```
|
|
185
191
|
|
|
186
|
-
`urlcode init <dir> --with auth,admin` writes this layout in one step: it
|
|
192
|
+
`urlcode init <dir> --with ui,auth,admin` writes this layout in one step: it
|
|
187
193
|
resolves each installed `@jimhoyd/urlcode-<name>` from the current directory,
|
|
188
194
|
calls its `scaffold` export and merges the fragments into `app/urlcode.yaml`,
|
|
189
195
|
one `host.mjs` and one `README.md`, refusing before writing anything when a
|
|
@@ -194,12 +200,24 @@ administrator from JSON on stdin. `inspectExtensionRevision(project)` prints
|
|
|
194
200
|
the SHA-256 that `projectSha256` must carry; changing extension YAML, policies
|
|
195
201
|
or mounts changes the revision and needs an explicit operator reapproval.
|
|
196
202
|
|
|
203
|
+
The presentation tooling composes the same way, by naming packages rather than
|
|
204
|
+
depending on them. `urlcode-ui` is the kit alone until
|
|
205
|
+
`--extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin` names the packages
|
|
206
|
+
that ship the other namespaces: each is resolved from `--project` with Node
|
|
207
|
+
package resolution and imported for the namespace it exports, so `list`,
|
|
208
|
+
`doctor`, `eject`, `preview` and `copy --missing` cover the `auth/*` and
|
|
209
|
+
`admin/*` templates and copy the host registers, and a project override of an
|
|
210
|
+
extension template is checked against the shipped view model. A package that is
|
|
211
|
+
not installed is skipped. `urlcode init --with` writes the commands with the
|
|
212
|
+
flag already set; `@jimhoyd/urlcode-ui` still depends on neither peer.
|
|
213
|
+
|
|
197
214
|
### Rules an agent must follow
|
|
198
215
|
|
|
199
216
|
These are the facts that keep generated projects valid. The full matrix is in
|
|
200
217
|
[AI authoring](https://github.com/jimhoyd-com/urlcode/blob/main/docs/AI-AUTHORING.md); this is the short list.
|
|
201
218
|
|
|
202
|
-
- **YAML
|
|
219
|
+
- **Extension YAML names logical extensions, not host packages or credentials.**
|
|
220
|
+
Function and middleware `source` fields do name project modules. Extensions are
|
|
203
221
|
logical names; the host file chooses the implementation. There is no
|
|
204
222
|
`--extension` flag, no `import` in YAML, no interpolation.
|
|
205
223
|
- **One handler per route.** `redirect`, `respond`, `page`, `static`, `download`,
|
|
@@ -210,13 +228,15 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
210
228
|
It runs in-process with full Node access unless the route declares
|
|
211
229
|
`sandbox: true`, which isolates it to a text/JSON `Request`/`Response`
|
|
212
230
|
subset, validated `args` and granted `env`, with no `fetch`, Node,
|
|
213
|
-
filesystem or timers. Either way, `args`/`env`/`secrets` are exactly what
|
|
231
|
+
filesystem or general network access; bounded timers are available. Either way, `args`/`env`/`secrets` are exactly what
|
|
214
232
|
the route declares and an operator grants — trust changes where code runs,
|
|
215
233
|
not what it is handed. See docs/SPIKE-DEFAULT-TRUST-MODEL.md and
|
|
216
234
|
docs/FUNCTION-SECURITY.md.
|
|
217
235
|
- **Authentication is host processing.** Do not build login forms, session
|
|
218
|
-
cookies or password checks in functions.
|
|
219
|
-
|
|
236
|
+
cookies or password checks in functions. With the auth extension declared,
|
|
237
|
+
prefer `auth: true` or `auth: {role: admin}`; these expand to
|
|
238
|
+
`policies.extensions.auth`. The runtime filters credential headers passed to
|
|
239
|
+
application handlers. This is not a security boundary against trusted Node code.
|
|
220
240
|
- **Everything is validated before it runs.** `urlcode validate --local`,
|
|
221
241
|
`urlcode test`, `urlcode audit --expect-routes N`. Unsupported features fail
|
|
222
242
|
with the route named; nothing degrades silently.
|
|
@@ -235,10 +255,10 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
235
255
|
| Need | Read |
|
|
236
256
|
|---|---|
|
|
237
257
|
| Write or change routes | [YAML guide](https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-GUIDE.md), [field reference](https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-REFERENCE.md), [cookbook](https://github.com/jimhoyd-com/urlcode/blob/main/examples/cookbook/README.md) |
|
|
238
|
-
| Add accounts | [auth README](https://github.com/jimhoyd-com/urlcode
|
|
239
|
-
| Add administration | [admin README](https://github.com/jimhoyd-com/urlcode
|
|
240
|
-
| Restyle every page | [ui README](https://github.com/jimhoyd-com/urlcode
|
|
241
|
-
| Write an extension | [extensions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md), [extension model review](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPIKE-EXTENSION-MODEL.md) |
|
|
258
|
+
| Add accounts | [auth README](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/README.md), [auth security](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/SECURITY.md) |
|
|
259
|
+
| Add administration | [admin README](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin/README.md) |
|
|
260
|
+
| Restyle every page | [ui README](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui/README.md), [ui contract](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui/CONTRACT.md) |
|
|
261
|
+
| Write an extension | [extensions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md), [extension model review](https://github.com/jimhoyd-com/urlcode/blob/main/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md) |
|
|
242
262
|
| Run it | [operations](https://github.com/jimhoyd-com/urlcode/blob/main/docs/OPERATIONS.md), [install](https://github.com/jimhoyd-com/urlcode/blob/main/docs/INSTALL.md), [deployment checks](https://github.com/jimhoyd-com/urlcode/blob/main/docs/DEPLOYMENT-CHECKS.md) |
|
|
243
263
|
|
|
244
264
|
---
|
|
@@ -272,7 +292,11 @@ fields or bypass target limits or operator grants. See [the design principle](ht
|
|
|
272
292
|
6. [Readiness](https://github.com/jimhoyd-com/urlcode/blob/main/docs/READINESS.md), [capacity](https://github.com/jimhoyd-com/urlcode/blob/main/docs/CAPACITY.md), [DDoS/recovery](https://github.com/jimhoyd-com/urlcode/blob/main/docs/RESILIENCE.md).
|
|
273
293
|
7. [The framework](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FRAMEWORK.md) for accounts, administration and presentation:
|
|
274
294
|
`extensions.<name>` blocks and `extension` mounts are the only YAML those
|
|
275
|
-
packages need
|
|
295
|
+
packages need. [Composing a site](https://github.com/jimhoyd-com/urlcode/blob/main/docs/COMPOSING-A-SITE.md) is the map of what a
|
|
296
|
+
consumer may then change: the `config` each package accepts, the
|
|
297
|
+
presentation overrides under `ui/`, the project functions its lifecycle
|
|
298
|
+
hooks call, and when a requirement instead needs a new extension in
|
|
299
|
+
TypeScript.
|
|
276
300
|
|
|
277
301
|
The root [llms.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms.txt) is a compact discovery index; the generated
|
|
278
302
|
[llms-full.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms-full.txt) concatenates the authoring documents above in
|
|
@@ -353,7 +377,7 @@ The benchmark operates locally; it is not a load test of an external deployment.
|
|
|
353
377
|
| Parameter validation and JSON body syntax checks | Full OpenAPI or JSON Schema validation of request bodies |
|
|
354
378
|
| Local test/audit/benchmark | Route-local YAML tests, managed monitoring, production load certification |
|
|
355
379
|
| Local/self-hosted runtime; limited AWS/Vercel/Cloudflare implementations with local tests | Verified provider deployments or full cross-provider parity |
|
|
356
|
-
| File authoring and snapshot reload | General guest storage broker; stored short links (
|
|
380
|
+
| File authoring and snapshot reload | General guest storage broker; stored short links (no supported package; the `urlcode-dynamic-link` extension was retired) |
|
|
357
381
|
| Optional host `policies` (`throttle`, `agents`, `security`, `compression`, `cache`) and reusable `profiles` | Plugins named in YAML, shared multi-instance counters, CORS, verified-bot checks |
|
|
358
382
|
| Optional top-level `site` (`robots`, `sitemap`, `favicon`, `securityTxt`, `llms`) generating native routes | Per-route `noindex` field, sitemap index files, `humans.txt`, signed `security.txt` |
|
|
359
383
|
|
|
@@ -487,9 +511,12 @@ Put that justification where tooling can see it, not only in a source
|
|
|
487
511
|
comment: an optional `sandboxReason` string on the route (up to 500
|
|
488
512
|
characters, `schemas/urlcode.schema.json`) records why a route needs
|
|
489
513
|
isolation, or why it is safe to trust, regardless of whether `sandbox` is
|
|
490
|
-
`true` or `false`. `urlcode explain`/`context
|
|
491
|
-
|
|
492
|
-
|
|
514
|
+
`true` or `false`. `urlcode explain`/`context`, the manifest and the
|
|
515
|
+
`routes` inventory all surface it next to the route's `sandbox` boolean —
|
|
516
|
+
per route, not per handler, so a native handler that runs `middleware`
|
|
517
|
+
reports its execution mode too, and `routes --compare` shows a flip between
|
|
518
|
+
trusted and sandboxed execution as a changed route. The trust decision has a
|
|
519
|
+
reviewable trail without reading every route's source file:
|
|
493
520
|
|
|
494
521
|
```yaml
|
|
495
522
|
routes:
|
|
@@ -570,8 +597,9 @@ trust (adding `sandbox: true` without saying why, or relying on the trusted
|
|
|
570
597
|
default for code that plainly needed isolation) to work around them.
|
|
571
598
|
|
|
572
599
|
There is no native `link` handler or `dynamicLinks` project flag; both were
|
|
573
|
-
removed.
|
|
574
|
-
|
|
600
|
+
removed. The `urlcode-dynamic-link` extension package that briefly owned them
|
|
601
|
+
has been retired and unpublished, so there is no supported replacement. Report a
|
|
602
|
+
request for live stored links as a gap rather than inventing a `link` field.
|
|
575
603
|
|
|
576
604
|
See [capabilities and normalized route representation](https://github.com/jimhoyd-com/urlcode/blob/main/docs/CAPABILITIES.md) for the target catalog,
|
|
577
605
|
programmatic compatibility analysis and provider verification limits.
|
|
@@ -1255,10 +1283,10 @@ keys preserve repeated values. Headers are forwarded only through explicit maps.
|
|
|
1255
1283
|
### Stored links
|
|
1256
1284
|
|
|
1257
1285
|
Core has no native `link` handler or `dynamicLinks` project flag; both were
|
|
1258
|
-
removed.
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1286
|
+
removed. The `urlcode-dynamic-link` extension package that replaced them has
|
|
1287
|
+
been retired and unpublished, so a project needing stored short links owns that
|
|
1288
|
+
storage itself; there is no in-core replacement or deprecation shim for
|
|
1289
|
+
`link`/`dynamicLinks`.
|
|
1262
1290
|
|
|
1263
1291
|
### Functions
|
|
1264
1292
|
|
|
@@ -1299,7 +1327,11 @@ entry may likewise be a string, normalized to `{source: <string>}`. Only the
|
|
|
1299
1327
|
long form exists after loading, so `routes`, `audit`, `explain`, revision hashes
|
|
1300
1328
|
and the field reference describe the expansion.
|
|
1301
1329
|
|
|
1302
|
-
ES modules only
|
|
1330
|
+
ES modules only. `.mjs` is loaded as ESM in both modes. A `sandbox: true` route
|
|
1331
|
+
also reads `.js` as ESM independently of Node package settings, but a trusted
|
|
1332
|
+
route imports `.js` through Node's own loader, so an ESM `.js` module needs the
|
|
1333
|
+
nearest `package.json` to declare `"type": "module"`; under `"type": "commonjs"`
|
|
1334
|
+
it fails activation. Prefer `.mjs` for modules that must work in either mode.
|
|
1303
1335
|
[Build-time TypeScript authoring](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TYPESCRIPT-AUTHORING.md) can produce these
|
|
1304
1336
|
JavaScript modules in a separate output project; serving does not transpile them.
|
|
1305
1337
|
The build never imports application code into Node, uses fixed compiler settings,
|
|
@@ -1426,7 +1458,9 @@ are local Git-owned examples; they grant no capabilities. [Build-time TypeScript
|
|
|
1426
1458
|
is separate from runtime execution.
|
|
1427
1459
|
|
|
1428
1460
|
The [tooling SDK and optional local MCP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md) inspect and validate without
|
|
1429
|
-
executing handlers or reading binding values.
|
|
1461
|
+
executing handlers or reading binding values. MCP defaults to inspection; the
|
|
1462
|
+
explicit `--allow-authoring` option enables bounded project writes, separately
|
|
1463
|
+
from host-file loading and grants. [Provider conformance](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PROVIDER-VERIFICATION.md)
|
|
1430
1464
|
distinguishes local adapter replay from actual deployment observations; no real
|
|
1431
1465
|
provider deployment is implied by CI. [Proxy and signal egress](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EGRESS.md) requires
|
|
1432
1466
|
external revision-pinned operator grants and bounded host-owned transport;
|
|
@@ -1539,10 +1573,9 @@ any per-visitor session record. Git owns route behavior and code; user-created r
|
|
|
1539
1573
|
their own persistence, backup and export lifecycle.
|
|
1540
1574
|
|
|
1541
1575
|
Core has no native handler for this today: the `link` handler that implemented
|
|
1542
|
-
it was removed.
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
that package exists.
|
|
1576
|
+
it was removed. The `urlcode-dynamic-link` extension package that replaced it
|
|
1577
|
+
has been retired and unpublished, so a project needing stored short links owns
|
|
1578
|
+
that storage itself.
|
|
1546
1579
|
|
|
1547
1580
|
Functions still cannot access databases, the filesystem or network directly.
|
|
1548
1581
|
General application state and realtime sessions remain future work.
|
|
@@ -1735,12 +1768,18 @@ Every middleware and the function share the same request and context. Validated
|
|
|
1735
1768
|
with a fresh `context.state` object for this chain. State never survives the
|
|
1736
1769
|
request. Header edits are visible downstream; editing the request does not
|
|
1737
1770
|
reroute it or change already validated inputs. Request bodies are single-use:
|
|
1738
|
-
reading one consumes it for downstream code.
|
|
1739
|
-
API
|
|
1771
|
+
reading one consumes it for downstream code. On a `sandbox: true` route there is
|
|
1772
|
+
no `clone()` or streaming API at all, so pass parsed data through
|
|
1773
|
+
`context.state`; a trusted route receives Node's own `Request`/`Response` and so
|
|
1774
|
+
does have `clone()`, but passing parsed data through `context.state` keeps the
|
|
1775
|
+
chain portable between the two modes.
|
|
1740
1776
|
|
|
1741
1777
|
Function responses support the existing text/JSON guest API. To transform their
|
|
1742
|
-
body, read it and return a new `Response`.
|
|
1743
|
-
download bodies are opaque and cannot be read
|
|
1778
|
+
body, read it and return a new `Response`. On a `sandbox: true` route, native
|
|
1779
|
+
redirect/respond/page/static/download bodies are opaque and cannot be read
|
|
1780
|
+
through `text()` or `json()`; a trusted chain receives them as an ordinary
|
|
1781
|
+
`Response` and can read them, so wrapping `respond: {text: hello}` and returning
|
|
1782
|
+
`HELLO` works there and fails in the guest.
|
|
1744
1783
|
Returning the same native response preserves original bytes, including binary
|
|
1745
1784
|
files, ranges and HEAD lengths. You may add headers, but cannot change its
|
|
1746
1785
|
original status or existing native headers while preserving that body. To replace
|
|
@@ -1790,7 +1829,8 @@ Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/ASSETS.md
|
|
|
1790
1829
|
|
|
1791
1830
|
Native file handlers are implemented in 0.1.0-alpha.3. They work in the local
|
|
1792
1831
|
Node runtime and self-hosted process/container. Provider adapters remain planned.
|
|
1793
|
-
They do not run user functions or expose filesystem APIs to
|
|
1832
|
+
They do not run user functions or expose filesystem APIs to guest code, trusted
|
|
1833
|
+
or sandboxed.
|
|
1794
1834
|
|
|
1795
1835
|
```yaml
|
|
1796
1836
|
version: "1"
|
|
@@ -1902,9 +1942,10 @@ Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/POLICIES.md
|
|
|
1902
1942
|
Policies are host-enforced behavior declared in YAML around a route: a
|
|
1903
1943
|
per-client request budget, a User-Agent deny list, a security-header profile,
|
|
1904
1944
|
content-coding negotiation and an HTTP caching strategy. They run in the host
|
|
1905
|
-
process, outside
|
|
1906
|
-
|
|
1907
|
-
|
|
1945
|
+
process, outside function/middleware execution — trusted or sandboxed alike —
|
|
1946
|
+
so they can see the client address, keep counters across requests and touch
|
|
1947
|
+
the transport, which route middleware by design cannot
|
|
1948
|
+
([middleware](https://github.com/jimhoyd-com/urlcode/blob/main/docs/MIDDLEWARE.md), [function security](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md)).
|
|
1908
1949
|
|
|
1909
1950
|
Everything here is optional and off by default. A project with no `policies`
|
|
1910
1951
|
key and no `profiles` key behaves exactly as before: no policy module is
|
|
@@ -1919,7 +1960,7 @@ compiled and the request path is unchanged. Each policy has its own page:
|
|
|
1919
1960
|
| `cache` | [cache](https://github.com/jimhoyd-com/urlcode/blob/main/docs/policies/cache.md) | Named strategies from RFC 9111/5861/8246/9213 plus an origin memory cache |
|
|
1920
1961
|
|
|
1921
1962
|
The design and the reasoning behind each choice are in the
|
|
1922
|
-
[extensions spike](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPIKE-EXTENSIONS.md). Operators who need behavior the
|
|
1963
|
+
[extensions spike](https://github.com/jimhoyd-com/urlcode/blob/main/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md). Operators who need behavior the
|
|
1923
1964
|
declarative block cannot express pass host [plugins](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PLUGINS.md) in code; plugins
|
|
1924
1965
|
are never named in YAML.
|
|
1925
1966
|
|
|
@@ -1941,7 +1982,14 @@ routes:
|
|
|
1941
1982
|
`policies` at the top level sets project defaults; `routes.<path>.policies`
|
|
1942
1983
|
adjusts them for one route. Two route-level short forms exist. `auth`
|
|
1943
1984
|
expands to `policies.extensions.auth` when the project declares an auth
|
|
1944
|
-
[extension](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md)
|
|
1985
|
+
[extension](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md), carrying the same keys minus `required`;
|
|
1986
|
+
`required: false` documents the intent and emits no policy. It accepts
|
|
1987
|
+
`required`, `role`, `permission`, `verified`, `freshWithinSeconds` and
|
|
1988
|
+
`onDeny` and nothing else — `role` is singular, and there is no `roles`. Like
|
|
1989
|
+
`cache` below, it is refused rather than silently ignored in three cases: when
|
|
1990
|
+
the project declares no `extensions.auth`, when the route also sets
|
|
1991
|
+
`policies.extensions.auth` (use one form), and when the route sets
|
|
1992
|
+
`policies.extensions: false`. `cache: {strategy, maxAge, ...}` expands to
|
|
1945
1993
|
`policies.cache` the same way — the compiler merges it into that route's
|
|
1946
1994
|
`policies` before anything else reads the project, so `routes`, `audit` and
|
|
1947
1995
|
`explain` see only the expanded long form, and it is refused alongside a
|
|
@@ -2477,10 +2525,11 @@ and admin implementations live in `urlcode-auth` and `urlcode-admin`; the runtim
|
|
|
2477
2525
|
supplies only the generic integration contract. No project file can import a host
|
|
2478
2526
|
extension or choose its npm package.
|
|
2479
2527
|
|
|
2480
|
-
Stored short links
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
no longer has a native `link`
|
|
2528
|
+
Stored short links moved out of core this way too: a `urlcode-dynamic-link`
|
|
2529
|
+
package (mount-based, like `auth`/`admin`) owned the durable link store, its CLI
|
|
2530
|
+
and management API. That package has since been retired and unpublished, so no
|
|
2531
|
+
supported stored-link extension ships today. Core no longer has a native `link`
|
|
2532
|
+
handler or a `dynamicLinks` project flag.
|
|
2484
2533
|
|
|
2485
2534
|
A project declares versioned configuration and exclusive route mounts:
|
|
2486
2535
|
|
|
@@ -2663,7 +2712,7 @@ project customizes an extension's *look* through `urlcode-ui`'s
|
|
|
2663
2712
|
standard, or every extension author (and every project depending on one)
|
|
2664
2713
|
either reinvents it or forks the extension. `urlcode-auth` and
|
|
2665
2714
|
`urlcode-admin` independently hit this gap (auth/#35, admin/#32); auth's own
|
|
2666
|
-
[SPIKE-AUTH.md](https://github.com/jimhoyd-com/urlcode
|
|
2715
|
+
[SPIKE-AUTH.md](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/docs/SPIKE-AUTH.md)
|
|
2667
2716
|
already scoped a shape for `onSignUp`/`beforeRegister`/`onDelete`.
|
|
2668
2717
|
|
|
2669
2718
|
**The pattern.** An extension with lifecycle events an author judges worth
|
|
@@ -2727,7 +2776,16 @@ each half of a hook's `sandbox: true` opt-in has its own answer:
|
|
|
2727
2776
|
or provided: this is ordinary first-party project code, and the
|
|
2728
2777
|
extension's own `activate()` already has `ExtensionActivation.root` to
|
|
2729
2778
|
resolve the hook's `source` against and can `import()` it directly, the
|
|
2730
|
-
same way any trusted `function`/`middleware` route does.
|
|
2779
|
+
same way any trusted `function`/`middleware` route does. Do that import
|
|
2780
|
+
with a per-activation cache-busting query, the way core's own trusted
|
|
2781
|
+
route activation does (`src/trusted-functions.ts`): Node's ESM loader
|
|
2782
|
+
caches a resolved module forever by URL, so a plain `import()` of the
|
|
2783
|
+
unchanged file URL makes a second activation in the same process keep
|
|
2784
|
+
serving the hook code that was on disk at the first one
|
|
2785
|
+
(jimhoyd-com/urlcode#198). Only the hook's **entry** module is refreshed
|
|
2786
|
+
this way — modules the hook itself imports stay on Node's module cache,
|
|
2787
|
+
the same limitation the trusted route path has, so a change to a hook's
|
|
2788
|
+
own dependency still needs a process restart.
|
|
2731
2789
|
- **Sandboxed (`sandbox: true`).** `@jimhoyd/urlcode/sandbox` exports
|
|
2732
2790
|
`SandboxPool`, the same QuickJS/worker-thread engine that backs a
|
|
2733
2791
|
sandboxed `function`/`middleware` route — the identical module-allowlist
|
|
@@ -2797,16 +2855,15 @@ boundary, not a JavaScript sandbox or an independent security review.
|
|
|
2797
2855
|
|
|
2798
2856
|
### Scaffolding with `init --with`
|
|
2799
2857
|
|
|
2800
|
-
`urlcode init <directory> --with auth,admin` produces the layered site the
|
|
2858
|
+
`urlcode init <directory> --with ui,auth,admin` produces the layered site the
|
|
2801
2859
|
[framework page](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FRAMEWORK.md#the-composition-contract) describes in one
|
|
2802
2860
|
command: the starter under `<directory>/app/`, one `host.mjs`, one `README.md`,
|
|
2803
2861
|
and each extension's own operator files. Core never bundles or imports the
|
|
2804
2862
|
extension packages at build time; at run time it resolves
|
|
2805
2863
|
`@jimhoyd/urlcode-<name>` for each name with Node's package resolution from
|
|
2806
|
-
the invoking directory
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
the package and calls its
|
|
2864
|
+
the invoking directory. Install a compatible package set there, as shown in
|
|
2865
|
+
[the framework guide](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FRAMEWORK.md#the-composition-contract), before using
|
|
2866
|
+
`--with ui,auth`; UI must activate before auth. Core imports the package and calls its
|
|
2810
2867
|
`scaffold` export with this request:
|
|
2811
2868
|
|
|
2812
2869
|
```ts
|
|
@@ -2862,6 +2919,37 @@ Assembly rules, in `--with` order:
|
|
|
2862
2919
|
review the project and pin it explicitly; the host is generated to require
|
|
2863
2920
|
the pin, never to compute it.
|
|
2864
2921
|
|
|
2922
|
+
#### Recorded versions
|
|
2923
|
+
|
|
2924
|
+
`init --with` also writes `<directory>/package.json`: a private manifest that
|
|
2925
|
+
pins, at exactly the version that was resolved, the running runtime, every
|
|
2926
|
+
extension named in `--with`, and every package those extensions declare in
|
|
2927
|
+
`peerDependencies` (so `@jimhoyd/urlcode-ui` is pinned for an `auth,admin` site
|
|
2928
|
+
although nobody named it). Before anything is written, the whole set is checked
|
|
2929
|
+
against every declared peer range; an incompatible combination or a missing
|
|
2930
|
+
required peer refuses and names it, leaving no directory behind.
|
|
2931
|
+
|
|
2932
|
+
Nothing is installed. The generated site has no `node_modules` and no
|
|
2933
|
+
`package-lock.json` until you run `npm install` in it yourself, which the
|
|
2934
|
+
command and the generated README both state as the next step. Reproducibility
|
|
2935
|
+
comes from that install, not from generation.
|
|
2936
|
+
|
|
2937
|
+
- `--no-manifest` generates the site without a `package.json`, for a site whose
|
|
2938
|
+
dependencies are managed elsewhere. Plain `urlcode init` is unchanged and
|
|
2939
|
+
still writes no manifest; add `--manifest` to pin the runtime for a
|
|
2940
|
+
route-only project too.
|
|
2941
|
+
- `--pin <package>=<specifier>` records a specifier instead of the resolved
|
|
2942
|
+
version, for local tarball or offline development
|
|
2943
|
+
(`--pin @jimhoyd/urlcode-auth=file:/abs/urlcode-auth-0.1.0-alpha.6.tgz`). A
|
|
2944
|
+
package installed from a local path or tarball is detected from npm's own
|
|
2945
|
+
install record and pinned by that path without any flag; the README says so,
|
|
2946
|
+
because such a pin only reproduces where that path exists.
|
|
2947
|
+
|
|
2948
|
+
There is no upgrade command. Moving a generated project to newer versions today
|
|
2949
|
+
means editing its `package.json` and re-running `npm install` yourself; nothing
|
|
2950
|
+
in this runtime selects a newer tested set, shows the change, or updates a
|
|
2951
|
+
lockfile for you.
|
|
2952
|
+
|
|
2865
2953
|
Serving the result is the usual explicit host binding:
|
|
2866
2954
|
|
|
2867
2955
|
```sh
|
|
@@ -2870,6 +2958,289 @@ urlcode validate --project app --host-file "$PWD/host.mjs" --origin https://site
|
|
|
2870
2958
|
|
|
2871
2959
|
---
|
|
2872
2960
|
|
|
2961
|
+
# Composing a site from ui, auth and admin
|
|
2962
|
+
|
|
2963
|
+
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/COMPOSING-A-SITE.md
|
|
2964
|
+
|
|
2965
|
+
One command produces a site that already has accounts, an administration
|
|
2966
|
+
console and a presentation kit wired together:
|
|
2967
|
+
|
|
2968
|
+
```sh
|
|
2969
|
+
npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin
|
|
2970
|
+
urlcode init site --with ui,auth,admin
|
|
2971
|
+
```
|
|
2972
|
+
|
|
2973
|
+
This page is the map of what you may then change, and with which tool. It
|
|
2974
|
+
covers three different activities that are easy to confuse:
|
|
2975
|
+
|
|
2976
|
+
| You want to | Use | Code? |
|
|
2977
|
+
|---|---|---|
|
|
2978
|
+
| Change what an extension is configured to do | the `config` block in `app/urlcode.yaml` | none |
|
|
2979
|
+
| Change how its screens look or read | files under `<site>/ui/` | none (templates are data) |
|
|
2980
|
+
| Run your own logic at one of its lifecycle points | a project function named from that `config` | first-party project JavaScript |
|
|
2981
|
+
| Add a capability none of the three provides | a new extension package | TypeScript against the runtime contract |
|
|
2982
|
+
|
|
2983
|
+
Work down that list, not up. The [declarative-first
|
|
2984
|
+
principle](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PROJECT-DIRECTION.md#design-principle-declarative-first) applies
|
|
2985
|
+
here as much as it does to routes: reach for the next row only when the one
|
|
2986
|
+
above it cannot express the requirement.
|
|
2987
|
+
|
|
2988
|
+
### What `--with ui,auth,admin` generates
|
|
2989
|
+
|
|
2990
|
+
- `site/app/` — the route project: `urlcode.yaml` with an `extensions` block
|
|
2991
|
+
per package, and `routes/extensions.yaml` holding `/assets/ui/*`,
|
|
2992
|
+
`/account/*`, `/private` and `/admin/*`.
|
|
2993
|
+
- `site/host.mjs` — the operator host module, the one place that holds code.
|
|
2994
|
+
It builds the kit with `createUiExtension`, passes the returned object into
|
|
2995
|
+
`authExtension` and `adminExtension`, and lists `ui.registration` first.
|
|
2996
|
+
- `site/ui/` — `copy/`, `templates/` and `extra.css`, the project's
|
|
2997
|
+
presentation overrides, beside the host and **outside** `app/`.
|
|
2998
|
+
- `site/operator-service.mjs`, `site/data/` — auth's operator service and its
|
|
2999
|
+
private key material, mode `0600`.
|
|
3000
|
+
- `site/README.md` — the merged next steps, environment table and the project
|
|
3001
|
+
revision to review and pin.
|
|
3002
|
+
|
|
3003
|
+
Nothing about that wiring is manual any more. The generated host registers
|
|
3004
|
+
`authCatalogue` as a copy source and both `authUiTemplates` and
|
|
3005
|
+
`adminUiTemplates` as template namespaces, because auth and admin render only
|
|
3006
|
+
through the kit and refuse to activate without their own templates present.
|
|
3007
|
+
|
|
3008
|
+
#### Supported combinations
|
|
3009
|
+
|
|
3010
|
+
`--with` order is the activation order, and the kit must be active before
|
|
3011
|
+
anything that renders through it.
|
|
3012
|
+
|
|
3013
|
+
| `--with` | Result |
|
|
3014
|
+
|---|---|
|
|
3015
|
+
| `ui` | Kit only; the host wires no peer catalogue or templates. |
|
|
3016
|
+
| `ui,auth` | Accounts on `/account/*`, rendered through the kit. |
|
|
3017
|
+
| `ui,auth,admin` | The full composition above. |
|
|
3018
|
+
| `auth` or `auth,admin` | Refused: the scaffold names the missing `ui`. |
|
|
3019
|
+
| `auth,admin,ui` | Refused: `ui` must come before the extensions it renders. |
|
|
3020
|
+
| `admin` without `auth` | Refused: admin reuses auth's service, CSRF key and revision. |
|
|
3021
|
+
|
|
3022
|
+
Every refusal happens before anything is written, and leaves no directory
|
|
3023
|
+
behind. There is no auth-without-ui or admin-without-ui configuration in this
|
|
3024
|
+
revision: the UI primitive fallback was retired, so the kit is the only render
|
|
3025
|
+
path (see [OPEN-DECISIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/OPEN-DECISIONS.md)).
|
|
3026
|
+
|
|
3027
|
+
### Declarative configuration
|
|
3028
|
+
|
|
3029
|
+
Each package owns one `extensions.<name>` block. The block itself is core
|
|
3030
|
+
schema (`version` plus `config`); what may go inside `config` is the
|
|
3031
|
+
package's own JSON Schema, which you can print rather than guess:
|
|
3032
|
+
|
|
3033
|
+
```sh
|
|
3034
|
+
urlcode extensions --project ./site/app --host-file "$PWD/site/host.mjs" --json
|
|
3035
|
+
```
|
|
3036
|
+
|
|
3037
|
+
The same report is the MCP tool `get_extensions`, and it is the authoritative
|
|
3038
|
+
answer for both the configuration schema and the per-route policy schema. The
|
|
3039
|
+
generated site starts from something like this:
|
|
3040
|
+
|
|
3041
|
+
```yaml
|
|
3042
|
+
version: "1"
|
|
3043
|
+
extensions:
|
|
3044
|
+
ui:
|
|
3045
|
+
version: "1"
|
|
3046
|
+
config:
|
|
3047
|
+
theme:
|
|
3048
|
+
name: My Site
|
|
3049
|
+
languages: [en]
|
|
3050
|
+
copy: ui/copy
|
|
3051
|
+
templates: ui/templates
|
|
3052
|
+
stylesheet: ui/extra.css
|
|
3053
|
+
auth:
|
|
3054
|
+
version: "1"
|
|
3055
|
+
config:
|
|
3056
|
+
registration: "off"
|
|
3057
|
+
admin:
|
|
3058
|
+
version: "1"
|
|
3059
|
+
config: {}
|
|
3060
|
+
```
|
|
3061
|
+
|
|
3062
|
+
Routes mount an extension, and policies require one:
|
|
3063
|
+
|
|
3064
|
+
```yaml
|
|
3065
|
+
routes:
|
|
3066
|
+
/assets/ui/*:
|
|
3067
|
+
extension: ui
|
|
3068
|
+
methods: [GET, HEAD]
|
|
3069
|
+
/account/*:
|
|
3070
|
+
extension: auth
|
|
3071
|
+
methods: [GET, HEAD, POST]
|
|
3072
|
+
/admin/*:
|
|
3073
|
+
extension: admin
|
|
3074
|
+
methods: [GET, HEAD, POST]
|
|
3075
|
+
/private:
|
|
3076
|
+
respond:
|
|
3077
|
+
text: Signed in
|
|
3078
|
+
policies:
|
|
3079
|
+
extensions:
|
|
3080
|
+
auth: {}
|
|
3081
|
+
```
|
|
3082
|
+
|
|
3083
|
+
See [EXTENSIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md) for the `auth` route short form, extension
|
|
3084
|
+
middleware, and the host-file trust boundary.
|
|
3085
|
+
|
|
3086
|
+
### Presentation overrides
|
|
3087
|
+
|
|
3088
|
+
The `ui` config's `copy`, `templates` and `stylesheet` paths point at the
|
|
3089
|
+
project's own directories. Nothing here forks a package.
|
|
3090
|
+
|
|
3091
|
+
| Override | File | Effect |
|
|
3092
|
+
|---|---|---|
|
|
3093
|
+
| Wording and translation | `ui/copy/<locale>.json` | Replaces catalogue ids, including ids the auth and admin packages own. Listed in `languages`. |
|
|
3094
|
+
| A whole screen | `ui/templates/<name>.html` | Shadows a kit or extension template of that name, for example `ui/templates/auth/sign-in.html` or `ui/templates/admin/dashboard.html`. |
|
|
3095
|
+
| Styling | `ui/extra.css` | Appended after the kit stylesheet; `{file, replace: true}` replaces it instead. |
|
|
3096
|
+
| Colours, logo, favicon, radius, font | the `theme` block | Declarative; no file needed. |
|
|
3097
|
+
|
|
3098
|
+
A template is data in the kit's own language. It cannot add a script, change
|
|
3099
|
+
what a form validates, or change what a page sends in headers — so an override
|
|
3100
|
+
cannot weaken the screen it restyles. Stylesheets containing `@import`,
|
|
3101
|
+
`script`, `javascript:` or `expression(` are refused.
|
|
3102
|
+
|
|
3103
|
+
Names, coverage and what the runtime will actually load:
|
|
3104
|
+
|
|
3105
|
+
```sh
|
|
3106
|
+
npx urlcode-ui list --project ./site --extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin
|
|
3107
|
+
npx urlcode-ui doctor --project ./site --extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin --copy ui/copy --templates ui/templates --stylesheet ui/extra.css
|
|
3108
|
+
npx urlcode-ui eject auth/sign-in --out ./site/ui/templates --project ./site --extensions @jimhoyd/urlcode-auth
|
|
3109
|
+
npx urlcode-ui copy --missing fr --project ./site --extensions @jimhoyd/urlcode-auth --copy ui/copy --languages en,fr
|
|
3110
|
+
```
|
|
3111
|
+
|
|
3112
|
+
`eject` copies the shipped source so an override starts from what ships and
|
|
3113
|
+
never overwrites an existing file. `ui/` lives outside `app/`, so editing copy
|
|
3114
|
+
or templates does **not** change the project revision and does not require
|
|
3115
|
+
re-pinning `PROJECT_SHA256`.
|
|
3116
|
+
|
|
3117
|
+
**Name the packages that ship the other namespaces.** `urlcode-ui` is this
|
|
3118
|
+
kit alone until `--extensions` names them. Each package is resolved from
|
|
3119
|
+
`--project` with Node package resolution and imported for the namespace it
|
|
3120
|
+
exports; one that is not installed there is skipped with a note, so the
|
|
3121
|
+
command still runs. The site's `host.mjs` is never read: it builds services
|
|
3122
|
+
and reads secrets at its top level, and a read-only `list` or `doctor` must
|
|
3123
|
+
not run it. With the packages named:
|
|
3124
|
+
|
|
3125
|
+
- `list` shows the `auth/*` and `admin/*` names beside the kit's own, each
|
|
3126
|
+
with its origin, and `eject auth/sign-in` copies the shipped source.
|
|
3127
|
+
- `doctor` reports an `expected` view model for an extension template, so its
|
|
3128
|
+
`behind` flag tells you when an override of one has fallen behind what
|
|
3129
|
+
ships. Its `extensions` field names the namespaces the report covers, so a
|
|
3130
|
+
report built without a peer is visible as such.
|
|
3131
|
+
- `preview auth/sign-in` renders the extension's own sample view model.
|
|
3132
|
+
- `copy --missing` skeletons cover the auth ids the account screens use.
|
|
3133
|
+
Admin-owned `adminUi.*` ids are deliberately not offered: admin composes
|
|
3134
|
+
its catalogue onto the kit's presentation rather than registering it there,
|
|
3135
|
+
and those translations do not currently reach the console
|
|
3136
|
+
([#227](https://github.com/jimhoyd-com/urlcode/issues/227)).
|
|
3137
|
+
|
|
3138
|
+
`urlcode init <directory> --with ui,auth,admin` writes these commands into the
|
|
3139
|
+
generated README with the flag already set. `@jimhoyd/urlcode-ui` depends on
|
|
3140
|
+
neither peer; the operator names them.
|
|
3141
|
+
|
|
3142
|
+
Overrides of extension templates and of extension-owned catalogue ids reach
|
|
3143
|
+
the rendered screens, which is what the regression test below asserts.
|
|
3144
|
+
|
|
3145
|
+
### Project functions: lifecycle hooks
|
|
3146
|
+
|
|
3147
|
+
A hook is your own function, named from the extension's `config`, that the
|
|
3148
|
+
extension calls at a lifecycle point it defines. It uses the same source shape
|
|
3149
|
+
a `function` route uses — a bare path, or `{source, export}` — resolved
|
|
3150
|
+
relative to the route project.
|
|
3151
|
+
|
|
3152
|
+
```yaml
|
|
3153
|
+
extensions:
|
|
3154
|
+
auth:
|
|
3155
|
+
version: "1"
|
|
3156
|
+
config:
|
|
3157
|
+
registration: open
|
|
3158
|
+
hooks:
|
|
3159
|
+
beforeRegister:
|
|
3160
|
+
source: ./hooks/registration-rule.mjs
|
|
3161
|
+
export: default
|
|
3162
|
+
onSignUp: ./hooks/on-signup.mjs
|
|
3163
|
+
```
|
|
3164
|
+
|
|
3165
|
+
`hooks` is not a core schema key. It is each package's own config, validated
|
|
3166
|
+
by that package's schema before `activate()`, which is why `get_extensions` is
|
|
3167
|
+
the place to check what a given version accepts.
|
|
3168
|
+
|
|
3169
|
+
Hooks are first-party project code and run **trusted and in-process**, the
|
|
3170
|
+
same default `function` and `middleware` routes have
|
|
3171
|
+
([FUNCTION-SECURITY.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md)). Neither package implements
|
|
3172
|
+
sandboxed hook execution yet; `sandbox: true` on a hook is rejected loudly at
|
|
3173
|
+
activation rather than accepted and silently run trusted.
|
|
3174
|
+
|
|
3175
|
+
#### `@jimhoyd/urlcode-auth`
|
|
3176
|
+
|
|
3177
|
+
| Hook | Input | Returns | Called |
|
|
3178
|
+
|---|---|---|---|
|
|
3179
|
+
| `beforeRegister` | `{email, profile?}` | `{allow: boolean, reason?}` | Before an account is created, on `POST /account/register` and on `POST /account/signup/begin`. |
|
|
3180
|
+
| `onSignUp` | `{accountId, email}` | ignored | After a genuinely new account is created — on `/account/register`, and on `/account/signup/complete` only when that completion created an account rather than signing an existing one in. |
|
|
3181
|
+
| `onDelete` | `{accountId, email}` | ignored | After the account owner's own deletion is scheduled. Not on an administrator-initiated deletion, and not on the background purge when the grace period ends. |
|
|
3182
|
+
|
|
3183
|
+
#### `@jimhoyd/urlcode-admin`
|
|
3184
|
+
|
|
3185
|
+
| Hook | Input | Returns | Called |
|
|
3186
|
+
|---|---|---|---|
|
|
3187
|
+
| `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check. A veto means the auth service is never asked. |
|
|
3188
|
+
| `onRegistrationApproved` | `{requestId, accountId, email, actorId, reason}` | ignored | After a registration request is approved. |
|
|
3189
|
+
| `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked. |
|
|
3190
|
+
|
|
3191
|
+
#### Verdicts and failure
|
|
3192
|
+
|
|
3193
|
+
- **A veto is explicit.** A pre-action hook allows only by returning
|
|
3194
|
+
`allow: true`. `allow: false`, or no verdict at all, rejects the operation
|
|
3195
|
+
with `403` and the hook's own `reason`, or a generic message when it gave
|
|
3196
|
+
none. Nothing is written. A hook that *throws* has not returned a verdict:
|
|
3197
|
+
the operation is still refused, but as a generic `500`, so return a verdict
|
|
3198
|
+
rather than throwing when you mean to deny.
|
|
3199
|
+
- **Broken hooks fail at activation, not at the first request.** A missing
|
|
3200
|
+
module, a source path escaping the project, an export that is not a
|
|
3201
|
+
function, or `sandbox: true` all throw while the extension activates, naming
|
|
3202
|
+
the hook. The site does not start.
|
|
3203
|
+
- **A post-action hook cannot undo anything.** `onSignUp`,
|
|
3204
|
+
`onDelete`, `onRegistrationApproved` and `onAccountStatusChanged` run after
|
|
3205
|
+
the operation has committed. Throwing from one replaces the success response
|
|
3206
|
+
with a `500` while the account, approval or status change stands. There is
|
|
3207
|
+
no retry and no rollback. Keep them non-throwing: catch your own errors and
|
|
3208
|
+
queue the work instead of failing the request.
|
|
3209
|
+
- **A hook's message is not a channel to the browser.** Only a pre-action
|
|
3210
|
+
`reason` is shown. An uncaught error surfaces as a generic failure.
|
|
3211
|
+
- **An edited hook needs a restart.** Activation re-imports the hook's entry
|
|
3212
|
+
module, so a reload picks up an edit to that file — but modules it imports
|
|
3213
|
+
stay on Node's module cache, exactly as for trusted route functions.
|
|
3214
|
+
|
|
3215
|
+
### TypeScript: implementing a new extension
|
|
3216
|
+
|
|
3217
|
+
Only write an extension when a capability is genuinely absent — not to
|
|
3218
|
+
customize one of the three above. An extension is an operator-installed
|
|
3219
|
+
package whose host object core activates; it is named in `host.mjs`, never
|
|
3220
|
+
in YAML. The contract, the activation inputs, `ExtensionActivation.root`,
|
|
3221
|
+
credential headers and the `projectSha256` pin are in
|
|
3222
|
+
[EXTENSIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md) and [TYPESCRIPT.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TYPESCRIPT.md); the
|
|
3223
|
+
`scaffold` export that makes a package work with `init --with` is in
|
|
3224
|
+
[EXTENSIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md#scaffolding-with-init---with).
|
|
3225
|
+
|
|
3226
|
+
If its screens should be themeable the same way auth's and admin's are, it
|
|
3227
|
+
also exports a template namespace (and, if it ships English wording, a
|
|
3228
|
+
catalogue) for a host to pass to `createUiExtension`. That is what makes
|
|
3229
|
+
`ui/templates/<yourname>/<screen>.html` work in a consumer project without a
|
|
3230
|
+
fork.
|
|
3231
|
+
|
|
3232
|
+
### What this page does not claim
|
|
3233
|
+
|
|
3234
|
+
The composition, the refusals and the override path are exercised by
|
|
3235
|
+
`test/workspace-scaffold.integration.ts`, which runs `init --with` against the
|
|
3236
|
+
built packages, drops a template and a copy catalogue into the generated
|
|
3237
|
+
`ui/` directory and asserts both reach a rendered auth screen and a rendered
|
|
3238
|
+
admin screen. That runs in-process against the generated host: no HTTP
|
|
3239
|
+
listener, TLS proxy, browser or deployed site is exercised, and no published
|
|
3240
|
+
npm tarball is checked against this checkout.
|
|
3241
|
+
|
|
3242
|
+
---
|
|
3243
|
+
|
|
2873
3244
|
# Function execution: trusted by default, sandboxed opt-in
|
|
2874
3245
|
|
|
2875
3246
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md
|
|
@@ -2914,6 +3285,35 @@ API is all it has, so its binding grant *is* effectively its whole reach into
|
|
|
2914
3285
|
the environment. Trusted code's reach is not bounded that way; treat the
|
|
2915
3286
|
grant as scoping `context`, not as scoping the process.
|
|
2916
3287
|
|
|
3288
|
+
### Migrating to the trusted default
|
|
3289
|
+
|
|
3290
|
+
If you are upgrading a project from a release before this change shipped:
|
|
3291
|
+
**every existing `function` and `middleware` route silently changes execution
|
|
3292
|
+
mode**, from sandboxed to trusted, unless it already has (or you add)
|
|
3293
|
+
`sandbox: true`. This is a real behavior change on upgrade, not a
|
|
3294
|
+
documentation update — a route that used to run with no filesystem or network
|
|
3295
|
+
access will, after the upgrade, run with full Node access unless you opt it
|
|
3296
|
+
back into the sandbox.
|
|
3297
|
+
|
|
3298
|
+
Before upgrading:
|
|
3299
|
+
|
|
3300
|
+
- List every `function` and `middleware` route in the project.
|
|
3301
|
+
- For each one, decide whether you fully trust that code to run in-process
|
|
3302
|
+
with full Node/filesystem/network access — the same trust you would extend
|
|
3303
|
+
to any other code you deploy to that server.
|
|
3304
|
+
- Add `sandbox: true` explicitly to any route whose code you do not fully
|
|
3305
|
+
trust, that processes input from a source you don't control, or that handles
|
|
3306
|
+
a secret binding you want isolated — before you upgrade, not after.
|
|
3307
|
+
- Routes you do want running trusted need no change; that is now the default.
|
|
3308
|
+
|
|
3309
|
+
The change moves the sandbox from an unconditional guarantee to an explicit,
|
|
3310
|
+
per-route choice, mainly for performance: the previous blanket sandbox capped
|
|
3311
|
+
concurrency at two workers with no queue shared across every function route on
|
|
3312
|
+
the server, which does not scale to real concurrent traffic. It also brings
|
|
3313
|
+
first-party code in line with how the rest of the Node ecosystem treats
|
|
3314
|
+
deployed application code. The sandbox itself is unchanged for routes that opt
|
|
3315
|
+
into it; only the default for routes that declare neither option has changed.
|
|
3316
|
+
|
|
2917
3317
|
### What "sandboxed" (`sandbox: true`) still guarantees
|
|
2918
3318
|
|
|
2919
3319
|
- Function sources are parsed/snapshotted without importing them into Node.
|
|
@@ -2990,6 +3390,21 @@ it is handed *through `context`*. It does not change what the code can go get
|
|
|
2990
3390
|
on its own once it is running; see "binding grants are unaffected" above for
|
|
2991
3391
|
that distinction.
|
|
2992
3392
|
|
|
3393
|
+
### Trusted code, not trusted requests
|
|
3394
|
+
|
|
3395
|
+
"Trusted" describes the code's authorship — first-party project code you
|
|
3396
|
+
reviewed and deployed — not the requests it handles. Every request, in either
|
|
3397
|
+
mode, still carries client-controlled path, query, header and body data that is
|
|
3398
|
+
exactly as adversarial as it always was. Running trusted means that code
|
|
3399
|
+
executes with full Node access if it mishandles that input; it does not mean
|
|
3400
|
+
the input itself became safe to trust. Declare `parameters` and `request.body`
|
|
3401
|
+
validation in YAML, check `args` and any other request data again inside
|
|
3402
|
+
function/middleware code, and implement your own authentication and
|
|
3403
|
+
authorization — no route, sandboxed or trusted, adds automatic auth.
|
|
3404
|
+
`sandbox: true` narrows what a bug or an unreviewed dependency in the *code*
|
|
3405
|
+
can do with that same request data; it is not a substitute for validating or
|
|
3406
|
+
authenticating the request itself.
|
|
3407
|
+
|
|
2993
3408
|
### Granting selected bindings
|
|
2994
3409
|
|
|
2995
3410
|
An application may request a named binding in YAML, but only an operator can
|