@jimhoyd/urlcode 0.4.1 → 0.4.6
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 +60 -7
- package/.claude/skills/urlcode-operations/SKILL.md +4 -0
- package/README.md +19 -15
- package/SECURITY.md +5 -3
- package/dist/BUILD-MANIFEST.json +31 -28
- package/dist/agent-context.js +82 -0
- package/dist/agents-guide.js +42 -42
- package/dist/authoring.js +12 -2
- package/dist/body-schema.js +159 -0
- package/dist/build-cloudflare.js +2 -0
- package/dist/capabilities.js +1 -1
- package/dist/cli.js +29 -12
- package/dist/config.js +74 -6
- package/dist/context.js +4 -6
- package/dist/errors.js +3 -1
- package/dist/examples.js +1 -1
- package/dist/extensions.js +138 -3
- package/dist/http-policy.js +19 -4
- package/dist/http-response.js +2 -2
- package/dist/init-with.js +71 -9
- package/dist/mcp.js +15 -2
- package/dist/pattern-guard.js +32 -0
- package/dist/policies/security.js +0 -0
- package/dist/policy.js +16 -0
- package/dist/project-tests.js +35 -11
- package/dist/readiness.js +188 -32
- package/dist/recipes.js +1 -1
- package/dist/router.js +17 -0
- package/dist/runtime.js +26 -3
- package/dist/scaffold.js +0 -0
- package/dist/server.js +26 -3
- package/dist/site.js +0 -0
- package/dist/tooling.js +3 -1
- package/dist/types/agent-context.d.ts +44 -0
- package/dist/types/authoring.d.ts +3 -1
- package/dist/types/body-schema.d.ts +58 -0
- package/dist/types/config.d.ts +10 -2
- package/dist/types/context.d.ts +1 -1
- package/dist/types/errors.d.ts +9 -1
- package/dist/types/examples.d.ts +1 -1
- package/dist/types/extensions.d.ts +105 -2
- package/dist/types/http-policy.d.ts +3 -0
- package/dist/types/init-with.d.ts +10 -1
- package/dist/types/pattern-guard.d.ts +10 -0
- package/dist/types/project-tests.d.ts +9 -0
- package/dist/types/readiness.d.ts +71 -0
- package/dist/types/recipes.d.ts +1 -1
- package/dist/types/runtime.d.ts +4 -0
- package/dist/types/server.d.ts +9 -1
- package/dist/types/tooling.d.ts +2 -0
- package/dist/types/types.d.ts +13 -0
- package/dist/types.js +10 -2
- package/dist/typescript-authoring.js +5 -3
- package/dist/verify-deployment.js +16 -10
- package/examples/body-validation/README.md +16 -0
- package/examples/body-validation/example.yaml +17 -0
- package/examples/body-validation/tests/requests.json +15 -0
- package/examples/body-validation/urlcode.yaml +34 -0
- package/examples/coverage-waiver/README.md +8 -0
- package/examples/coverage-waiver/example.yaml +16 -0
- package/examples/coverage-waiver/functions/notes.mjs +2 -0
- package/examples/coverage-waiver/tests/requests.json +3 -0
- package/examples/coverage-waiver/urlcode.yaml +10 -0
- package/examples/data-dir/README.md +39 -0
- package/examples/data-dir/data/welcome.txt +1 -0
- package/examples/data-dir/example.yaml +22 -0
- package/examples/data-dir/functions/note.mjs +18 -0
- package/examples/data-dir/tests/requests.json +6 -0
- package/examples/data-dir/urlcode.yaml +6 -0
- package/examples/lifecycle/README.md +15 -0
- package/examples/lifecycle/example.yaml +19 -0
- package/examples/lifecycle/functions/notes.mjs +29 -0
- package/examples/lifecycle/tests/requests.json +12 -0
- package/examples/lifecycle/urlcode.yaml +29 -0
- package/examples/not-found/README.md +10 -0
- package/examples/not-found/example.yaml +17 -0
- package/examples/not-found/public/404.html +3 -0
- package/examples/not-found/public/index.html +3 -0
- package/examples/not-found/tests/requests.json +7 -0
- package/examples/not-found/urlcode.yaml +7 -0
- package/examples/shared-blocks/README.md +11 -0
- package/examples/shared-blocks/example.yaml +17 -0
- package/examples/shared-blocks/tests/requests.json +7 -0
- package/examples/shared-blocks/urlcode.yaml +37 -0
- package/llms-full.txt +436 -149
- package/llms.txt +44 -6
- package/package.json +15 -22
- package/recipes/static-page/README.md +9 -0
- package/recipes/static-page/public/index.html +11 -0
- package/recipes/static-page/recipe.yaml +21 -0
- package/recipes/static-page/tests/requests.json +22 -0
- package/recipes/static-page/urlcode.yaml +7 -0
- package/recipes/static-plus-api/README.md +6 -0
- package/recipes/static-plus-api/urlcode.yaml +4 -0
- package/recipes/store-crud/README.md +53 -0
- package/recipes/store-crud/recipe.yaml +31 -0
- package/recipes/store-crud/tests/requests.json +18 -0
- package/recipes/store-crud/urlcode.yaml +18 -0
- package/schemas/urlcode.schema.json +125 -60
- package/skills/urlcode/SKILL.md +53 -26
- package/starters/default/AGENTS.md +43 -43
- package/starters/page/README.md +14 -0
- package/starters/page/public/index.html +12 -0
- package/starters/page/tests/requests.json +17 -0
- package/starters/page/urlcode.yaml +6 -0
- package/.claude-plugin/marketplace.json +0 -18
- package/CONTRIBUTING.md +0 -112
- package/ROADMAP.md +0 -66
- package/docs/AI-AUTHORING.md +0 -338
- package/docs/ASSETS.md +0 -107
- package/docs/AUTH-BACKUP.md +0 -32
- package/docs/AWS.md +0 -86
- package/docs/BEST-PRACTICES.md +0 -276
- package/docs/BULK.md +0 -79
- package/docs/CAPABILITIES.md +0 -192
- package/docs/CAPACITY.md +0 -305
- package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
- package/docs/CI.md +0 -147
- package/docs/CLOUDFLARE.md +0 -109
- package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
- package/docs/COMPLIANCE.md +0 -239
- package/docs/COMPOSING-A-SITE.md +0 -278
- package/docs/CONDITIONS.md +0 -74
- package/docs/DEPLOYMENT-CHECKS.md +0 -108
- package/docs/DEVELOPMENT-PIPELINE.md +0 -270
- package/docs/EGRESS.md +0 -125
- package/docs/EXTENSIONS.md +0 -438
- package/docs/FRAMEWORK.md +0 -217
- package/docs/FUNCTION-SECURITY.md +0 -254
- package/docs/HTTP.md +0 -129
- package/docs/INSTALL.md +0 -128
- package/docs/INTERCHANGE.md +0 -134
- package/docs/LOAD-TESTING.md +0 -91
- package/docs/LOCAL-DEVELOPMENT.md +0 -102
- package/docs/MIDDLEWARE-EXAMPLES.md +0 -75
- package/docs/MIDDLEWARE.md +0 -102
- package/docs/MONITORING.md +0 -115
- package/docs/OBSERVABILITY.md +0 -222
- package/docs/OPEN-DECISIONS.md +0 -224
- package/docs/OPERATIONAL-PROOF.md +0 -41
- package/docs/OPERATIONS.md +0 -201
- package/docs/ORGANIZATION.md +0 -135
- package/docs/PERFORMANCE.md +0 -72
- package/docs/PLUGINS.md +0 -271
- package/docs/POLICIES.md +0 -211
- package/docs/PRERENDER.md +0 -245
- package/docs/PROJECT-DIRECTION.md +0 -118
- package/docs/PROVIDER-VERIFICATION.md +0 -84
- package/docs/READINESS.md +0 -150
- package/docs/README.md +0 -87
- package/docs/RECIPES.md +0 -99
- package/docs/RELEASE-0.4.0-alpha.3.md +0 -50
- package/docs/RELEASE-0.4.1.md +0 -73
- package/docs/RELEASE-READINESS.md +0 -117
- package/docs/RELEASE-SECURITY.md +0 -96
- package/docs/RESILIENCE.md +0 -161
- package/docs/ROUTING.md +0 -92
- package/docs/SANDBOX-REVIEW.md +0 -72
- package/docs/SCAFFOLDING.md +0 -70
- package/docs/SECURITY-AUDIT.md +0 -164
- package/docs/SITE.md +0 -150
- package/docs/SPECIFICATION.md +0 -359
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
- package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
- package/docs/SPIKE-CORE-LAYERING.md +0 -368
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -211
- package/docs/STANDARDS.md +0 -311
- package/docs/STARTERS.md +0 -83
- package/docs/STATIC.md +0 -105
- package/docs/TOOLING.md +0 -298
- package/docs/TUNNELS.md +0 -72
- package/docs/TYPESCRIPT-AUTHORING.md +0 -87
- package/docs/TYPESCRIPT.md +0 -123
- package/docs/VERCEL.md +0 -114
- package/docs/VERSION-ALIGNMENT.md +0 -80
- package/docs/YAML-GUIDE.md +0 -57
- package/docs/YAML-REFERENCE.md +0 -449
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
- package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
- package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
- package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
- package/docs/archive/2026-09-19/ROADMAP.md +0 -387
- package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
- package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
- package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
- package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
- package/docs/archive/README.md +0 -28
- package/docs/policies/agents.md +0 -182
- package/docs/policies/cache.md +0 -152
- package/docs/policies/compression.md +0 -169
- package/docs/policies/contract.md +0 -52
- package/docs/policies/hardened.md +0 -56
- package/docs/policies/interoperability.md +0 -169
- package/docs/policies/operations.md +0 -45
- package/docs/policies/security.md +0 -161
- package/docs/policies/throttle.md +0 -103
- package/docs/yaml/assets.md +0 -36
- package/docs/yaml/conditions.md +0 -20
- package/docs/yaml/functions.md +0 -168
- package/docs/yaml/middleware.md +0 -31
- package/docs/yaml/organization.md +0 -74
- package/docs/yaml/policies.md +0 -37
- package/docs/yaml/redirects.md +0 -64
- package/docs/yaml/responses.md +0 -57
- package/docs/yaml/site.md +0 -24
- package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -120
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +0 -108
package/llms-full.txt
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
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: 16 documents, about
|
|
2
|
+
<!-- Consolidated URLCode authoring reference: 16 documents, about 57,753 tokens (estimate: characters / 4). -->
|
|
3
3
|
|
|
4
|
+
<!-- urlcode-current-version:start -->
|
|
4
5
|
# URLCode
|
|
5
6
|
|
|
6
7
|
> A portable runtime for programmable URL behavior, and the framework that grows
|
|
7
8
|
> from it: routes in YAML, functions and middleware, then accounts, administration and
|
|
8
9
|
> stored links as operator-installed extensions. Stable project format
|
|
9
|
-
> `version: "1"`. Core is Apache-2.0; this revision is `0.4.
|
|
10
|
-
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
|
|
10
|
+
> `version: "1"`. Core is Apache-2.0; this revision is `0.4.6`. `function`/`middleware`
|
|
11
|
+
> routes are trusted by default with `sandbox: true` as the per-route opt-in. The
|
|
12
|
+
> auth, admin and ui extension packages in this repository are versioned at the same
|
|
13
|
+
> revision; confirm what is published with `npm run release:status`.
|
|
14
|
+
<!-- urlcode-current-version:end -->
|
|
14
15
|
|
|
15
16
|
Use the schema and docs from the runtime revision you run. Do not assume Node
|
|
16
17
|
or fetch inside a `sandbox: true` function, regex routes, database access,
|
|
@@ -59,10 +60,12 @@ claim here is implemented in the linked repository; nothing is roadmap.
|
|
|
59
60
|
| `@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
61
|
| `@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
|
|
|
62
|
-
All four are Apache-2.0.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
All four are Apache-2.0. Each package's npm `latest` tag identifies its stable
|
|
64
|
+
version. The current publisher records the exact four-package stack tested
|
|
65
|
+
together on each new GitHub release and attaches the same information in signed
|
|
66
|
+
`train.json` metadata.
|
|
67
|
+
A stable npm channel is not an independent assessment: review, deployment
|
|
68
|
+
evidence and an accessibility assessment are still pending
|
|
66
69
|
([issue 58](https://github.com/jimhoyd-com/urlcode/issues/58)). Their status
|
|
67
70
|
files say exactly what is built: [auth](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/IMPLEMENTATION-STATUS.md),
|
|
68
71
|
[admin](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin/IMPLEMENTATION-STATUS.md),
|
|
@@ -115,11 +118,12 @@ exact requirement.
|
|
|
115
118
|
|
|
116
119
|
### The composition contract
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
An extended project starts from the current stable packages and one command.
|
|
122
|
+
`--save-exact` records the concrete versions selected from the independent
|
|
123
|
+
`latest` channels:
|
|
120
124
|
|
|
121
125
|
```sh
|
|
122
|
-
npm install --save-exact @jimhoyd/urlcode@
|
|
126
|
+
npm install --save-exact @jimhoyd/urlcode@latest @jimhoyd/urlcode-ui@latest @jimhoyd/urlcode-auth@latest @jimhoyd/urlcode-admin@latest
|
|
123
127
|
urlcode init my-site --with ui,auth,admin
|
|
124
128
|
```
|
|
125
129
|
|
|
@@ -178,12 +182,22 @@ export default {
|
|
|
178
182
|
};
|
|
179
183
|
```
|
|
180
184
|
|
|
181
|
-
Auth and admin
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
Auth and admin render every screen through `ui.kit`; their package-owned
|
|
186
|
+
templates and catalogues must be registered with that kit. Both refuse
|
|
187
|
+
activation when the UI extension is absent or has not activated first. See each
|
|
188
|
+
package README for the complete registration example; the scaffold generated by
|
|
189
|
+
`urlcode init --with ui,auth,admin` wires the same composition.
|
|
190
|
+
|
|
191
|
+
Treat that composition as one application with package ownership boundaries,
|
|
192
|
+
not as three separate user interfaces. Keep identity, session and recovery
|
|
193
|
+
behavior in auth and authorization, freshness, auditing and mutations in admin.
|
|
194
|
+
Apply product differences through the installed extensions' declared authoring
|
|
195
|
+
surfaces, in this order: configuration and theme, copy, a component or screen
|
|
196
|
+
template, stylesheet, then a supported hook. Create another extension only for
|
|
197
|
+
a reusable capability those surfaces cannot express. `urlcode extensions`
|
|
198
|
+
and the MCP `get_extensions` tool report those surfaces and their fast checks,
|
|
199
|
+
so people and agents can discover the supported path instead of replacing
|
|
200
|
+
package behavior.
|
|
187
201
|
|
|
188
202
|
```sh
|
|
189
203
|
urlcode serve --project /absolute/site --host-file /absolute/operator/host.mjs --origin https://site.example
|
|
@@ -220,6 +234,12 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
220
234
|
Function and middleware `source` fields do name project modules. Extensions are
|
|
221
235
|
logical names; the host file chooses the implementation. There is no
|
|
222
236
|
`--extension` flag, no `import` in YAML, no interpolation.
|
|
237
|
+
- **Build one product through declared authoring surfaces.** Inspect extension
|
|
238
|
+
authoring metadata before generating code. Prefer configuration/theme, copy,
|
|
239
|
+
the smallest template override, stylesheet and supported hooks, in that
|
|
240
|
+
order. Add an extension only for a reusable missing capability. Run the
|
|
241
|
+
reported fast checks while iterating and the full repository checks before
|
|
242
|
+
handoff.
|
|
223
243
|
- **One handler per route.** `redirect`, `respond`, `page`, `static`, `download`,
|
|
224
244
|
`function`, `proxy`, `conditional` or `extension`, plus optional
|
|
225
245
|
`middleware`. Paths are exact or single-segment `{param}`; `/*` only on
|
|
@@ -258,7 +278,7 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
258
278
|
| 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
279
|
| Add administration | [admin README](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin/README.md) |
|
|
260
280
|
| 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)
|
|
281
|
+
| Write an extension | [extensions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md) |
|
|
262
282
|
| 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) |
|
|
263
283
|
|
|
264
284
|
---
|
|
@@ -267,12 +287,29 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
267
287
|
|
|
268
288
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/AI-AUTHORING.md
|
|
269
289
|
|
|
270
|
-
Use this as project-authoring context. It describes the implemented source contract, including
|
|
290
|
+
Use this as project-authoring context. It describes the implemented source contract, including additions since 0.3.0,
|
|
271
291
|
not a general server framework. Runtime/schema/docs
|
|
272
292
|
must come from the same reviewed revision. The runtime is Apache-2.0; a
|
|
273
293
|
project you generate carries whatever license its owner chooses, so do not
|
|
274
294
|
add one to it automatically.
|
|
275
295
|
|
|
296
|
+
### First step: one bounded query
|
|
297
|
+
|
|
298
|
+
Make the first retrieval one bounded query: the MCP tool `get_context` when the
|
|
299
|
+
`urlcode` server is registered, otherwise `urlcode context --project DIR` (add
|
|
300
|
+
`--budget N` when context is scarce). Then retrieve only what the task needs:
|
|
301
|
+
`urlcode capabilities NAME` (MCP `get_capability`) for one capability's limits,
|
|
302
|
+
`get_schema` for one YAML fragment, `urlcode recipes search TEXT`
|
|
303
|
+
(`search_recipes`), `explain` for a route's effective behavior and, when the
|
|
304
|
+
operator supplies a host file, `get_extensions`. Context is a summary with the
|
|
305
|
+
constraints and exact commands, not a schema dump, and it never hides a
|
|
306
|
+
capability limit: ask `capabilities NAME` before promising a feature.
|
|
307
|
+
|
|
308
|
+
The complete catalogs (`urlcode capabilities`, `recipes list`), the compact
|
|
309
|
+
[llms.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms.txt) index and the generated [llms-full.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms-full.txt)
|
|
310
|
+
stay available as deliberate fallback and reference, not as the opening move.
|
|
311
|
+
The reading order below is for that reference use.
|
|
312
|
+
|
|
276
313
|
### Declarative-first default
|
|
277
314
|
|
|
278
315
|
> Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement.
|
|
@@ -316,18 +353,31 @@ as evidence, and the rules on grants and secrets. Assistants that load skills
|
|
|
316
353
|
find the same loop in `skills/urlcode/SKILL.md` inside the installed package; it
|
|
317
354
|
teaches how to retrieve the minimum reference through `urlcode capabilities`,
|
|
318
355
|
`urlcode recipes list|show` and `urlcode validate --local` rather than reading
|
|
319
|
-
the documentation whole.
|
|
356
|
+
the documentation whole. For a host-composed application, `get_extensions`
|
|
357
|
+
adds each extension's schemas, hooks, supported authoring surfaces and fast
|
|
358
|
+
checks. Agents should use those surfaces before generating replacement package
|
|
359
|
+
behavior. Neither file replaces the schema; both defer to it.
|
|
360
|
+
|
|
361
|
+
Treat core, installed extensions and product UI as one application with
|
|
362
|
+
different owners. Keep auth/admin security and workflow behavior package-owned;
|
|
363
|
+
keep branding, product navigation and the smallest necessary overrides in the
|
|
364
|
+
project. When a React frontend contains `components.json`, use the installed
|
|
365
|
+
official shadcn/ui skill for component discovery, composition, accessibility
|
|
366
|
+
and semantic Tailwind styling: start with `shadcn info --json`, then use its
|
|
367
|
+
documentation/search flow or configured MCP registry before generating a
|
|
368
|
+
component. The server template kit is shadcn-compatible but does not accept
|
|
369
|
+
React components. See the official [shadcn/ui skills guide](https://ui.shadcn.com/docs/skills).
|
|
320
370
|
|
|
321
371
|
### Authoring workflow
|
|
322
372
|
|
|
323
|
-
|
|
324
|
-
tokens, the runtime and schema version, what the project already uses, the
|
|
373
|
+
Start with the bounded query above. It prints the runtime and schema version, what the project already uses, the
|
|
325
374
|
constraints that hold for every project, which targets refuse this project's
|
|
326
375
|
features and the exact validate, test and audit commands with the intentional
|
|
327
376
|
route count filled in. It is derived from the compiled project and the
|
|
328
377
|
capability catalog, never from prose, so prefer it to re-reading the
|
|
329
378
|
documentation; add `--budget N` when context is scarce and `--json` for
|
|
330
|
-
tooling.
|
|
379
|
+
tooling. Its size grows with the project (about a thousand estimated tokens for
|
|
380
|
+
the starter, a few thousand for the cookbook), not with the framework.
|
|
331
381
|
|
|
332
382
|
- Inspect the existing entry point, included files, functions, tests and pinned
|
|
333
383
|
runtime. Preserve the user's organization and unrelated routes.
|
|
@@ -361,6 +411,26 @@ Use the intentional actual count, not always 2. Runtime checkout users can repla
|
|
|
361
411
|
External bindings require an already reviewed policy; add `--policy` where needed.
|
|
362
412
|
The benchmark operates locally; it is not a load test of an external deployment.
|
|
363
413
|
|
|
414
|
+
### Feedback from real authoring work
|
|
415
|
+
|
|
416
|
+
The authoring loop is also a source of roadmap evidence. After completing a
|
|
417
|
+
real task, an assistant should make a feedback **draft** only when a concrete
|
|
418
|
+
attempt exposes a capability gap, repeated framework workaround,
|
|
419
|
+
documentation/discovery gap, or suspected defect. One-off product logic is not
|
|
420
|
+
framework feedback.
|
|
421
|
+
|
|
422
|
+
The draft must name the installed runtime version and requested target, then
|
|
423
|
+
include a sanitized route/YAML fragment, the exact validation or test
|
|
424
|
+
observation, the smallest expected behavior, and a proposed fixture. It must
|
|
425
|
+
not include secrets, customer URLs, raw application source, or unsupported
|
|
426
|
+
claims inferred from a failed attempt.
|
|
427
|
+
|
|
428
|
+
Search [existing URLCode issues](https://github.com/jimhoyd-com/urlcode/issues)
|
|
429
|
+
before proposing a new one and identify a likely duplicate when found. An agent
|
|
430
|
+
may present a draft issue or comment for review, but must never create or update
|
|
431
|
+
a GitHub issue without the user's explicit approval. Feedback is evidence for a
|
|
432
|
+
maintainer to review; it is not a promise that the public contract will grow.
|
|
433
|
+
|
|
364
434
|
### Capability matrix: do not hallucinate these features
|
|
365
435
|
|
|
366
436
|
| Available | Unavailable or future |
|
|
@@ -406,11 +476,49 @@ declared route at the same path still wins. Count its generated routes in
|
|
|
406
476
|
`--expect-routes`. `site.sitemap` needs `--origin` at every command that
|
|
407
477
|
activates the project; see [site conventions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SITE.md).
|
|
408
478
|
|
|
479
|
+
### Built-in features by task
|
|
480
|
+
|
|
481
|
+
Before writing a function, check whether a declarative feature already covers the
|
|
482
|
+
need. Security headers are the usual miss: a project that declares nothing sends
|
|
483
|
+
only the runtime's defaults (`nosniff`, `no-store`, a request ID).
|
|
484
|
+
|
|
485
|
+
| I need | Declare | Reference |
|
|
486
|
+
|---|---|---|
|
|
487
|
+
| Security headers (CSP, HSTS, frame and referrer policy) | `policies.security: {headers: oshp}` or `policies.profile: hardened` | [security](https://github.com/jimhoyd-com/urlcode/blob/main/docs/policies/security.md) |
|
|
488
|
+
| Cache headers on a page, download or static mount | `cacheControl`: `no-cache` (default), `no-store`, `public, max-age=3600` or `public, max-age=31536000, immutable`; nothing else validates | [assets](https://github.com/jimhoyd-com/urlcode/blob/main/docs/yaml/assets.md) |
|
|
489
|
+
| A cache strategy on any route | `policies.cache` | [cache](https://github.com/jimhoyd-com/urlcode/blob/main/docs/policies/cache.md) |
|
|
490
|
+
| Body size, required body, content types, JSON syntax and shape | `request.body.maxBytes`, `required`, `contentTypes`, `format`, `schema` | [HTTP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md#body-schema-and-input-patterns) |
|
|
491
|
+
| A uuid path id or a bounded string pattern | parameter `schema: {type: string, format: uuid}` or `pattern` with `maxLength` | [HTTP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md#body-schema-and-input-patterns) |
|
|
492
|
+
| Method gating | `methods` (default GET/HEAD; 405 with `Allow`) | [HTTP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md) |
|
|
493
|
+
| Rate limits, bot and crawler denial, compression | `policies.throttle`, `agents`, `compression` | [policies](https://github.com/jimhoyd-com/urlcode/blob/main/docs/POLICIES.md) |
|
|
494
|
+
| Static JSON or text and fixed headers | `respond`, `response.headers` | [HTTP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md) |
|
|
495
|
+
| robots, sitemap, favicon, security.txt, llms.txt | top-level `site` | [site](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SITE.md) |
|
|
496
|
+
|
|
497
|
+
Which handler serves the response:
|
|
498
|
+
|
|
499
|
+
| The response is | Handler | Recipe |
|
|
500
|
+
|---|---|---|
|
|
501
|
+
| Fixed text or JSON | `respond` | `health-page` |
|
|
502
|
+
| A short HTML snippet | `respond` `text` plus `response.headers` `Content-Type: text/html; charset=utf-8` | [HTTP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md) |
|
|
503
|
+
| One HTML file | `page` | `static-page` |
|
|
504
|
+
| A directory of files | `static` | `static-plus-api` |
|
|
505
|
+
| An attachment | `download` | `protected-download` |
|
|
506
|
+
|
|
507
|
+
Data persistence has no native handler. The operator-installed `store` extension
|
|
508
|
+
serves declared collections as a CRUD API, and `urlcode recipes search "crud store
|
|
509
|
+
persist"` finds the `store-crud` recipe. It needs the operator to install
|
|
510
|
+
`@jimhoyd/urlcode-store` (on npm) and a host file. `init --with
|
|
511
|
+
ui,auth,store` scaffolds one from the published packages; a no-auth
|
|
512
|
+
`--with store` needs `--ack store:public-write`, which only a core release after the
|
|
513
|
+
store's first publication has, so say so rather than promising it. Report anything beyond that recipe (filtering, sorting, per-record
|
|
514
|
+
ownership, a database) as a gap. `urlcode context` lists the same built-ins so
|
|
515
|
+
they are visible before you write code.
|
|
516
|
+
|
|
409
517
|
### Agent skills
|
|
410
518
|
|
|
411
519
|
This repository ships two agent skills, each a thin trigger pointing at the
|
|
412
|
-
|
|
413
|
-
current rather than two:
|
|
520
|
+
version-matched CLI/schema and documentation bundle that are the actual source
|
|
521
|
+
of truth, so there is one place to keep current rather than two:
|
|
414
522
|
|
|
415
523
|
- [`urlcode-authoring`](https://github.com/jimhoyd-com/urlcode/blob/main/.claude/skills/urlcode-authoring/SKILL.md) — this
|
|
416
524
|
guide, the schema and the reference. It loads the capability limits and the
|
|
@@ -431,7 +539,10 @@ Three ways to get either, all pinned to a runtime revision:
|
|
|
431
539
|
- **npm.** The published package includes both skill directories. Copy the
|
|
432
540
|
one(s) you want into your project's `.claude/skills/` to pin guidance to the
|
|
433
541
|
same revision as the runtime you installed; a skill inside `node_modules` is
|
|
434
|
-
not discovered on its own.
|
|
542
|
+
not discovered on its own. The npm archive keeps `llms-full.txt` as its one
|
|
543
|
+
offline prose bundle instead of duplicating the repository's `docs/` tree;
|
|
544
|
+
use the CLI for structured queries and search that bundle by document heading
|
|
545
|
+
when more explanation is needed.
|
|
435
546
|
- **Plugin marketplace.** `.claude-plugin/marketplace.json` publishes the
|
|
436
547
|
`packaging/claude-plugin` distribution from this repository, carrying both
|
|
437
548
|
skills. Add the marketplace by its Git URL and install the `urlcode` plugin.
|
|
@@ -441,7 +552,7 @@ Three ways to get either, all pinned to a runtime revision:
|
|
|
441
552
|
|
|
442
553
|
`npm run docs:plugin` regenerates the plugin distribution from both skills;
|
|
443
554
|
`npm run check` fails if it is stale or if either skill names a documentation
|
|
444
|
-
path
|
|
555
|
+
path that does not exist in this revision.
|
|
445
556
|
### Bounded authoring tools
|
|
446
557
|
|
|
447
558
|
Before generating a common route by hand, search the bundled catalog:
|
|
@@ -536,13 +647,18 @@ under `advisories` with "consider whether this route needs `sandbox: true`".
|
|
|
536
647
|
This is a nudge to look, the same advisory spirit as the rest of `audit`'s
|
|
537
648
|
non-blocking findings — it never fails the check, never sets `ready: false`
|
|
538
649
|
and never infers the actual answer; setting `sandboxReason` (with `sandbox`
|
|
539
|
-
either `true` or `false`) or `sandbox: true` is enough to silence it.
|
|
650
|
+
either `true` or `false`) or `sandbox: true` is enough to silence it. The
|
|
651
|
+
advisory prints the exact line to add. Anything that touches the filesystem
|
|
652
|
+
(a persistent app writing files, for example) must be a trusted route,
|
|
653
|
+
because a sandbox has no filesystem: declare `sandboxReason` with the default
|
|
654
|
+
`sandbox: false` and say why it is trusted, as the `static-plus-api` recipe does.
|
|
540
655
|
|
|
541
656
|
The same judgment call applies to a project-level lifecycle hook an
|
|
542
657
|
extension invokes (`onSignUp`, `beforeRegister` and the like) — it is
|
|
543
|
-
first-party project code with the same trusted-by-default rule
|
|
544
|
-
`
|
|
545
|
-
|
|
658
|
+
first-party project code with the same trusted-by-default rule as a
|
|
659
|
+
`function`/`middleware` route. Extension hook contract v1 is trusted-only;
|
|
660
|
+
`sandbox: true` is rejected rather than silently ignored. See
|
|
661
|
+
[EXTENSIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md#project-level-lifecycle-hooks).
|
|
546
662
|
|
|
547
663
|
Guest TypeScript needs `build-typescript --project SOURCE --out NEW_DIRECTORY`
|
|
548
664
|
before serving. Only the emitted `.js`/`.mjs` executes, in QuickJS for a
|
|
@@ -561,7 +677,7 @@ origin grants pinned to the project revision. These are self-hosted features;
|
|
|
561
677
|
providers refuse them. Signals are bounded best effort with drops, no retries
|
|
562
678
|
or persistence. Never turn a user request into an implicit network grant.
|
|
563
679
|
|
|
564
|
-
Before using a feature, ask `urlcode capabilities <name>` for its constraints, grants and target support and `urlcode schema <path>` for only that YAML fragment (MCP: `get_capability`, `get_schema`), instead of guessing.
|
|
680
|
+
Before using a feature, ask `urlcode capabilities <name>` for its constraints, grants and target support and `urlcode schema <path>` for only that YAML fragment (MCP: `get_capability`, `get_schema`), instead of guessing. For an installed extension, use `urlcode extensions --host-file ... --json` or MCP `get_extensions`; its hook contracts include the accepted names, purpose and input/output schemas. Prefer extension configuration and UI copy/templates/theme/CSS, then a declared project hook, and only then a new extension or fork.
|
|
565
681
|
The [tooling SDK and stdio MCP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md) inspect, validate, explain and preview
|
|
566
682
|
without guest execution, environment reads or writes. Run `urlcode explain /route`
|
|
567
683
|
to check effective methods, policies and cache outcome, and `urlcode manifest`
|
|
@@ -610,10 +726,12 @@ programmatic compatibility analysis and provider verification limits.
|
|
|
610
726
|
|
|
611
727
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-GUIDE.md
|
|
612
728
|
|
|
613
|
-
|
|
729
|
+
<!-- urlcode-current-version:start -->
|
|
730
|
+
This guide targets URLCode 0.4.6. Start with the function example below,
|
|
614
731
|
then add only the fields your route needs. The authoritative machine-readable
|
|
615
732
|
shape is [JSON Schema](https://github.com/jimhoyd-com/urlcode/blob/main/schemas/urlcode.schema.json); semantic rules are in the
|
|
616
733
|
[specification](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPECIFICATION.md). Unsupported fields fail validation.
|
|
734
|
+
<!-- urlcode-current-version:end -->
|
|
617
735
|
|
|
618
736
|
### Run all the examples
|
|
619
737
|
|
|
@@ -693,6 +811,8 @@ schema-valid combinations activate successfully.
|
|
|
693
811
|
| `routes.*.enabled` | boolean | no | — |
|
|
694
812
|
| `routes.*.sandbox` | boolean | no | default: false |
|
|
695
813
|
| `routes.*.sandboxReason` | string | no | maxLength: 500 |
|
|
814
|
+
| `routes.*.coveredElsewhere` | object | no | minProperties: 1 |
|
|
815
|
+
| `routes.*.coveredElsewhere.*` | string | no | minLength: 1; maxLength: 500 |
|
|
696
816
|
| `routes.*.expires` | string | no | — |
|
|
697
817
|
| `routes.*.description` | string | no | maxLength: 1024 |
|
|
698
818
|
| `routes.*.parameters` | array | no | maxItems: 64 |
|
|
@@ -710,6 +830,8 @@ schema-valid combinations activate successfully.
|
|
|
710
830
|
| `routes.*.parameters[].schema.maxLength` | integer | no | minimum: 0; maximum: 8192 |
|
|
711
831
|
| `routes.*.parameters[].schema.minimum` | number | no | — |
|
|
712
832
|
| `routes.*.parameters[].schema.maximum` | number | no | — |
|
|
833
|
+
| `routes.*.parameters[].schema.pattern` | string | no | minLength: 1; maxLength: 128 |
|
|
834
|
+
| `routes.*.parameters[].schema.format` | string | no | enum: ["uuid"] |
|
|
713
835
|
| `routes.*.parameters[].schema.items` | object | no | unknown keys rejected |
|
|
714
836
|
| `routes.*.parameters[].schema.items.type` | string | yes | enum: ["string","integer","number","boolean"] |
|
|
715
837
|
| `routes.*.parameters[].schema.maxItems` | integer | no | minimum: 0; maximum: 100 |
|
|
@@ -770,6 +892,7 @@ schema-valid combinations activate successfully.
|
|
|
770
892
|
| `routes.*.request.body.contentTypes` | array | no | minItems: 1; maxItems: 16; uniqueItems: true |
|
|
771
893
|
| `routes.*.request.body.contentTypes[]` | string | no | pattern: "^[a-z0-9!#$&^_.+-]+/[a-z0-9!#$&^_.+-]+$" |
|
|
772
894
|
| `routes.*.request.body.format` | string | no | enum: ["text","json"] |
|
|
895
|
+
| `routes.*.request.body.schema` | object | no | — |
|
|
773
896
|
| `routes.*.response` | object | no | unknown keys rejected |
|
|
774
897
|
| `routes.*.response.headers` | object | no | maxProperties: 64 |
|
|
775
898
|
| `routes.*.response.headers.*` | one of the shapes below | no | — |
|
|
@@ -950,6 +1073,7 @@ schema-valid combinations activate successfully.
|
|
|
950
1073
|
| `routes.*.cache.maxBytes` | integer | no | minimum: 0; maximum: 16777216 |
|
|
951
1074
|
| `routes.*.cache.maxEntries` | integer | no | minimum: 1; maximum: 1000000 |
|
|
952
1075
|
| `routes.*.cache.force` | boolean | no | default: false |
|
|
1076
|
+
| `routes.*.use` | string | no | pattern: "^[a-z][a-z0-9-]{0,63}$" |
|
|
953
1077
|
| `includes` | array | no | maxItems: 256; uniqueItems: true |
|
|
954
1078
|
| `includes[]` | string | no | maxLength: 1024 |
|
|
955
1079
|
| `policies` | object | no | unknown keys rejected |
|
|
@@ -1114,10 +1238,27 @@ schema-valid combinations activate successfully.
|
|
|
1114
1238
|
| `site.securityTxt.encryption` | array | no | maxItems: 64 |
|
|
1115
1239
|
| `site.securityTxt.encryption[]` | string | no | minLength: 1; maxLength: 2048 |
|
|
1116
1240
|
| `site.llms` | string | no | minLength: 1; maxLength: 1024 |
|
|
1241
|
+
| `site.notFound` | string | no | minLength: 1; maxLength: 1024; pattern: "\\.[hH][tT][mM][lL]?$" |
|
|
1117
1242
|
| `extensions` | object | no | maxProperties: 16 |
|
|
1118
1243
|
| `extensions.*` | object | no | unknown keys rejected |
|
|
1119
1244
|
| `extensions.*.version` | constant | yes | const: "1" |
|
|
1120
1245
|
| `extensions.*.config` | object | yes | — |
|
|
1246
|
+
| `shared` | object | no | maxProperties: 32 |
|
|
1247
|
+
| `shared.*` | object | no | unknown keys rejected |
|
|
1248
|
+
| `shared.*.request` | object | no | unknown keys rejected |
|
|
1249
|
+
| `shared.*.request.body` | object | no | unknown keys rejected |
|
|
1250
|
+
| `shared.*.request.body.required` | boolean | no | — |
|
|
1251
|
+
| `shared.*.request.body.maxBytes` | integer | no | minimum: 0; maximum: 1048576 |
|
|
1252
|
+
| `shared.*.request.body.contentTypes` | array | no | minItems: 1; maxItems: 16; uniqueItems: true |
|
|
1253
|
+
| `shared.*.request.body.contentTypes[]` | string | no | pattern: "^[a-z0-9!#$&^_.+-]+/[a-z0-9!#$&^_.+-]+$" |
|
|
1254
|
+
| `shared.*.request.body.format` | string | no | enum: ["text","json"] |
|
|
1255
|
+
| `shared.*.request.body.schema` | object | no | — |
|
|
1256
|
+
| `shared.*.response` | object | no | unknown keys rejected |
|
|
1257
|
+
| `shared.*.response.headers` | object | no | maxProperties: 64 |
|
|
1258
|
+
| `shared.*.response.headers.*` | one of the shapes below | no | — |
|
|
1259
|
+
| `shared.*.response.headers.* (option 1)` | string | no | maxLength: 4096 |
|
|
1260
|
+
| `shared.*.response.headers.* (option 2)` | array | no | minItems: 1; maxItems: 16 |
|
|
1261
|
+
| `shared.*.response.headers.* (option 2)[]` | string | no | maxLength: 4096 |
|
|
1121
1262
|
|
|
1122
1263
|
---
|
|
1123
1264
|
|
|
@@ -1126,7 +1267,7 @@ schema-valid combinations activate successfully.
|
|
|
1126
1267
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPECIFICATION.md
|
|
1127
1268
|
|
|
1128
1269
|
This document and [JSON Schema](https://github.com/jimhoyd-com/urlcode/blob/main/schemas/urlcode.schema.json) describe
|
|
1129
|
-
the source contract, including
|
|
1270
|
+
the source contract, including additions since 0.3.0.
|
|
1130
1271
|
`version: "1"` remains the project-format contract. Unsupported fields
|
|
1131
1272
|
are rejected rather than interpreted as future behavior.
|
|
1132
1273
|
|
|
@@ -1231,6 +1372,31 @@ Routes accept `request.body` validation and `response.headers` overrides. The
|
|
|
1231
1372
|
See [HTTP configuration](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md) for the exact supported fields, precedence,
|
|
1232
1373
|
security restrictions and examples.
|
|
1233
1374
|
|
|
1375
|
+
### Shared blocks
|
|
1376
|
+
|
|
1377
|
+
An optional top-level `shared` map (entry `urlcode.yaml` only; at most 32 names
|
|
1378
|
+
matching `^[a-z][a-z0-9-]{0,63}$`) holds named `request` and `response.headers`
|
|
1379
|
+
blocks. A route selects one with `use: <name>`. This is the reuse mechanism;
|
|
1380
|
+
anchors, aliases and merge keys stay rejected. Rules:
|
|
1381
|
+
|
|
1382
|
+
- A route's own `request` (or `response`) key replaces the shared block's key as
|
|
1383
|
+
a whole. There is no deep merge, so a reviewer never reconstructs a header set
|
|
1384
|
+
from two places.
|
|
1385
|
+
- Resolution happens at load time, before the route hash, `audit`, `routes` and
|
|
1386
|
+
every compiler run. The resolved route carries no `use`, and changing a shared
|
|
1387
|
+
block changes the hash of exactly the routes that select it.
|
|
1388
|
+
- An unknown name fails validation, as does a shared block whose
|
|
1389
|
+
`response.headers` name a header the runtime owns (the same set a route may
|
|
1390
|
+
not set). Included files may `use` names from the entry file but may not
|
|
1391
|
+
declare `shared`. Per-route rules, such as the asset-handler header limits,
|
|
1392
|
+
still apply to the resolved route.
|
|
1393
|
+
- Only `request` and `response.headers` are shareable. `sandbox`,
|
|
1394
|
+
`sandboxReason`, `policies` and other route defaults are deliberately left
|
|
1395
|
+
out for now, so each trust decision stays on its route.
|
|
1396
|
+
- No cross-file or remote reuse of blocks.
|
|
1397
|
+
|
|
1398
|
+
See the [shared-blocks example](https://github.com/jimhoyd-com/urlcode/blob/main/examples/shared-blocks/README.md).
|
|
1399
|
+
|
|
1234
1400
|
### Policies
|
|
1235
1401
|
|
|
1236
1402
|
Optional top-level `policies` and `profiles` keys, and `routes.<path>.policies`,
|
|
@@ -1253,8 +1419,11 @@ Duplicate scalar query/header inputs return 400. Required missing inputs return
|
|
|
1253
1419
|
400; defaults apply only to absent inputs. Empty strings are present values.
|
|
1254
1420
|
|
|
1255
1421
|
Supported validation: `type`, scalar `enum`, `default`, string `minLength`/
|
|
1256
|
-
`maxLength`,
|
|
1257
|
-
|
|
1422
|
+
`maxLength`, string `format: uuid` and a bounded `pattern` ([restrictions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md#body-schema-and-input-patterns)),
|
|
1423
|
+
numeric `minimum`/`maximum`, query array `items` and `maxItems`. JSON bodies are
|
|
1424
|
+
validated by `request.body.schema`, not by a parameter (a failure answers 422, as JSON
|
|
1425
|
+
when the client explicitly accepts `application/json` and otherwise as text; see
|
|
1426
|
+
[HTTP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md#body-schema-and-input-patterns)). Cookies, nested inputs and
|
|
1258
1427
|
OpenAPI `style`/`explode` fields are not implemented. This uses a documented
|
|
1259
1428
|
OpenAPI-like input subset; it is not an OpenAPI document or full JSON Schema
|
|
1260
1429
|
input vocabulary. String limits are at most 8,192 characters.
|
|
@@ -1327,6 +1496,16 @@ entry may likewise be a string, normalized to `{source: <string>}`. Only the
|
|
|
1327
1496
|
long form exists after loading, so `routes`, `audit`, `explain`, revision hashes
|
|
1328
1497
|
and the field reference describe the expansion.
|
|
1329
1498
|
|
|
1499
|
+
Auto-binding belongs to the string form only. The long form (`source`, and
|
|
1500
|
+
therefore any `export:`) is taken as written: URLCode does not add `args` or
|
|
1501
|
+
path `parameters` to it, so a route like `/api/todos/{id}` with
|
|
1502
|
+
`function: {source: ..., export: get}` must declare the `id` parameter under
|
|
1503
|
+
`parameters` and map it under `args` by hand, as the example above does. A
|
|
1504
|
+
function has one `source` and one `export` per route; to serve several methods
|
|
1505
|
+
on one path, branch on `request.method` inside the function, or declare one
|
|
1506
|
+
route per method where the path allows it. A `methods:` map of per-method
|
|
1507
|
+
functions is not implemented.
|
|
1508
|
+
|
|
1330
1509
|
ES modules only. `.mjs` is loaded as ESM in both modes. A `sandbox: true` route
|
|
1331
1510
|
also reads `.js` as ESM independently of Node package settings, but a trusted
|
|
1332
1511
|
route imports `.js` through Node's own loader, so an ESM `.js` module needs the
|
|
@@ -1645,6 +1824,7 @@ status or default `Cache-Control: no-store` on functions/redirects.
|
|
|
1645
1824
|
| `request.body.maxBytes` | 0–1048576; tighter per-route budget, enforced while reading fixed/chunked bodies; 413 on overflow |
|
|
1646
1825
|
| `request.body.contentTypes` | Exact lowercase MIME essences for nonempty bodies; parameters ignored; mismatch/missing type returns 415 |
|
|
1647
1826
|
| `request.body.format` | `text`: validate UTF-8; `json`: validate UTF-8, JSON media type and JSON syntax; malformed input returns 400 |
|
|
1827
|
+
| `request.body.schema` | Requires `format: json`. A JSON Schema subset checked after parsing; a body that breaks it returns 422 (see below) |
|
|
1648
1828
|
|
|
1649
1829
|
The operator request limit remains an upper bound; YAML cannot raise it. A route
|
|
1650
1830
|
without body policy keeps the existing server limit. A configured body policy
|
|
@@ -1684,12 +1864,76 @@ cache and disposition on its own handler. Asset metadata cannot be overridden by
|
|
|
1684
1864
|
`response.headers`. On functions/declared responses, Content-Type may be configured;
|
|
1685
1865
|
JSON declarations require a JSON type. No response header secret interpolation.
|
|
1686
1866
|
|
|
1867
|
+
#### Body schema and input patterns
|
|
1868
|
+
|
|
1869
|
+
`request.body.schema` accepts `type` (`object`, `array`, `string`, `integer`,
|
|
1870
|
+
`number`, `boolean`, `null`), `properties`, `required`, `additionalProperties`
|
|
1871
|
+
(true or false), `items`, scalar `enum`, `minLength`/`maxLength`, `pattern`,
|
|
1872
|
+
`format: uuid`, `minimum`/`maximum` and `minItems`/`maxItems`. Anything else,
|
|
1873
|
+
including `$ref`, `oneOf` and `default`, fails activation. A schema is limited to
|
|
1874
|
+
6 levels, 128 nodes and 64 properties per object. It is checked by the runtime
|
|
1875
|
+
itself, so it behaves the same on every host and is not compiled from author
|
|
1876
|
+
code.
|
|
1877
|
+
|
|
1878
|
+
A failing body answers **422** as `text/plain`: `Request body failed validation`
|
|
1879
|
+
then one line per failure, at most 8, each naming only a path the schema
|
|
1880
|
+
declared (`/title must be a string`). Array positions print as `[]`. Nothing the
|
|
1881
|
+
client sent is echoed, in line with the fixed-words rule for runtime errors.
|
|
1882
|
+
Malformed JSON stays 400 and a wrong media type 415.
|
|
1883
|
+
|
|
1884
|
+
A client that sends `Accept: application/json` gets the same failures as
|
|
1885
|
+
`application/json` instead (the server and the Cloudflare Worker agree):
|
|
1886
|
+
|
|
1887
|
+
```json
|
|
1888
|
+
{"error":"body_validation_failed","message":"Request body failed validation","issues":[{"pointer":"/title","keyword":"maxLength","message":"must be at most 8 characters","expected":8}]}
|
|
1889
|
+
```
|
|
1890
|
+
|
|
1891
|
+
Each issue carries `pointer` (RFC 6901, built only from names the schema
|
|
1892
|
+
declared; array positions are `/[]`, not an index; the root is `""`), `keyword`
|
|
1893
|
+
(`type`, `enum`, `required`, `additionalProperties`, `minLength`, `maxLength`,
|
|
1894
|
+
`format`, `pattern`, `minimum`, `maximum`, `minItems` or `maxItems`), the fixed
|
|
1895
|
+
`message`, and where the schema states one, `expected` (the type, bound, format
|
|
1896
|
+
or, for `enum`, up to 16 short declared values) or `property` (the missing name
|
|
1897
|
+
from `required`). The offending value is never included, because it may hold a
|
|
1898
|
+
secret. At most 8 issues are listed and the body is capped at 4096 bytes;
|
|
1899
|
+
when trailing issues are dropped to fit, `"truncated":true` is added.
|
|
1900
|
+
|
|
1901
|
+
Negotiation is deliberately conservative: JSON is sent only when the Accept
|
|
1902
|
+
header names `application/json` explicitly with `q` above 0 and no higher `q` for
|
|
1903
|
+
an explicit `text/plain`. A missing header, `*/*`, `application/*`, browsers'
|
|
1904
|
+
default Accept and a malformed `q` keep the plain-text answer. The status is 422
|
|
1905
|
+
either way and the same checks run before any function or sandbox code.
|
|
1906
|
+
|
|
1907
|
+
Parameter schemas (path, query, header) also accept `format: uuid` and `pattern`
|
|
1908
|
+
on string inputs, rejecting a mismatch with 400. `pattern` runs on every request
|
|
1909
|
+
in the host process, so it is restricted: 1 to 128 characters, `maxLength` of at
|
|
1910
|
+
most 128 on the same schema, no group repeated by `*`, `+` or `{n,}`, no
|
|
1911
|
+
lookaround, no backreference and at most three unbounded quantifiers. That
|
|
1912
|
+
restriction is conservative, not a proof of linear time. It is what stands
|
|
1913
|
+
between an author regex and a backtracking stall, so prefer `format` or `enum`
|
|
1914
|
+
when either fits.
|
|
1915
|
+
|
|
1687
1916
|
`respond` is an additional native handler (exactly one handler per route):
|
|
1688
1917
|
|
|
1689
1918
|
- `status`: 200–599, default 200; 206 and 304 are reserved for native asset semantics.
|
|
1690
1919
|
- `text`: literal UTF-8 body, default content type text/plain.
|
|
1691
1920
|
- `json`: any JSON-compatible YAML value, serialized with application/json.
|
|
1692
1921
|
- Omit both for an empty body; declaring both fails. Body limit is 1 MiB.
|
|
1922
|
+
- A short HTML answer is `text` plus a declared content type. There is no
|
|
1923
|
+
`respond.html`; use the `page` handler for anything larger than a snippet:
|
|
1924
|
+
|
|
1925
|
+
```yaml
|
|
1926
|
+
/:
|
|
1927
|
+
respond:
|
|
1928
|
+
text: "<!doctype html><h1>Hello</h1>"
|
|
1929
|
+
response:
|
|
1930
|
+
headers:
|
|
1931
|
+
Content-Type: text/html; charset=utf-8
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
The body is served verbatim and the default `nosniff` and `no-store` still
|
|
1935
|
+
apply. Under the `oshp` security profile the CSP (`default-src 'self'`) blocks
|
|
1936
|
+
inline `<script>` and `<style>`, so keep the snippet to markup.
|
|
1693
1937
|
- Status 204/205 cannot declare a nonempty body. HEAD always suppresses the body.
|
|
1694
1938
|
|
|
1695
1939
|
Functions still return their own Response/status/body. YAML header policy does
|
|
@@ -1959,10 +2203,8 @@ compiled and the request path is unchanged. Each policy has its own page:
|
|
|
1959
2203
|
| `compression` | [compression](https://github.com/jimhoyd-com/urlcode/blob/main/docs/policies/compression.md) | `Accept-Encoding` negotiation; assets precompressed at snapshot time |
|
|
1960
2204
|
| `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 |
|
|
1961
2205
|
|
|
1962
|
-
|
|
1963
|
-
[
|
|
1964
|
-
declarative block cannot express pass host [plugins](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PLUGINS.md) in code; plugins
|
|
1965
|
-
are never named in YAML.
|
|
2206
|
+
Operators who need behavior the declarative block cannot express pass host
|
|
2207
|
+
[plugins](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PLUGINS.md) in code; plugins are never named in YAML.
|
|
1966
2208
|
|
|
1967
2209
|
### Declaring policies
|
|
1968
2210
|
|
|
@@ -2181,6 +2423,7 @@ site:
|
|
|
2181
2423
|
policy: [https://example.com/security-policy]
|
|
2182
2424
|
preferredLanguages: [en, fr]
|
|
2183
2425
|
llms: llms.txt
|
|
2426
|
+
notFound: public/404.html
|
|
2184
2427
|
routes:
|
|
2185
2428
|
/: {page: {file: public/index.html}}
|
|
2186
2429
|
```
|
|
@@ -2290,9 +2533,30 @@ refused).
|
|
|
2290
2533
|
A project-relative text file served as a `page` route with
|
|
2291
2534
|
`text/plain; charset=utf-8` and the default `no-cache`.
|
|
2292
2535
|
|
|
2536
|
+
#### `notFound` → `/404.html`
|
|
2537
|
+
|
|
2538
|
+
A project-relative `.html`/`.htm` file that answers a request matching no
|
|
2539
|
+
route. It becomes a `page` route at `/404.html` (`text/html; charset=utf-8`,
|
|
2540
|
+
`Cache-Control: no-store`); the runtime serves that page with status **404**
|
|
2541
|
+
for an unmatched `GET` or `HEAD`, with the same project security headers and
|
|
2542
|
+
`nosniff` as the built-in 404. Other methods keep the plain-text `Not found`.
|
|
2543
|
+
A route that matches but is disabled, mismatched by `match:` or denied by a
|
|
2544
|
+
policy keeps its own answer; only "no route matched" uses the page.
|
|
2545
|
+
|
|
2546
|
+
`/404.html` is also reachable directly and answers 200 (the same as on static
|
|
2547
|
+
hosting, which is why the path is fixed). It is one generated route in the
|
|
2548
|
+
`routes`/`audit` counts and `explain` reports it as `site.notFound`; the
|
|
2549
|
+
sitemap leaves it out. A route you declare at `/404.html` wins and is served
|
|
2550
|
+
as the not-found page instead. The page answers every unknown URL, so it cannot depend on the path requested.
|
|
2551
|
+
|
|
2552
|
+
`urlcode build --target static` writes it as the object `404.html` (see
|
|
2553
|
+
[static hosting](https://github.com/jimhoyd-com/urlcode/blob/main/docs/STATIC.md)); point the host's error document at that key.
|
|
2554
|
+
Cloudflare refuses it like any `page` route (no asset binding); the Worker
|
|
2555
|
+
has no per-request fallback page, so use the platform's own 404 asset there.
|
|
2556
|
+
|
|
2293
2557
|
### Per-target support
|
|
2294
2558
|
|
|
2295
|
-
| Target | `robots`, `sitemap`, `securityTxt` (`respond`) | `favicon`, `llms` (`page`) |
|
|
2559
|
+
| Target | `robots`, `sitemap`, `securityTxt` (`respond`) | `favicon`, `llms`, `notFound` (`page`) |
|
|
2296
2560
|
| --- | --- | --- |
|
|
2297
2561
|
| self-hosted, Vercel, AWS | served | served |
|
|
2298
2562
|
| Cloudflare | compiled into the artifact (`build --origin` for absolute URLs) | refused at build time like any `page` route: the target has no asset binding; serve them from the platform's static assets |
|
|
@@ -2531,6 +2795,10 @@ and management API. That package has since been retired and unpublished, so no
|
|
|
2531
2795
|
supported stored-link extension ships today. Core no longer has a native `link`
|
|
2532
2796
|
handler or a `dynamicLinks` project flag.
|
|
2533
2797
|
|
|
2798
|
+
The `store` extension is the data-owning counterpart: it serves declared,
|
|
2799
|
+
bounded collections as a CRUD API from an operator-owned directory. See
|
|
2800
|
+
[data store](https://github.com/jimhoyd-com/urlcode/blob/main/docs/STORE.md).
|
|
2801
|
+
|
|
2534
2802
|
A project declares versioned configuration and exclusive route mounts:
|
|
2535
2803
|
|
|
2536
2804
|
```yaml
|
|
@@ -2702,29 +2970,18 @@ Node adapter conformance is not a live-provider deployment claim.
|
|
|
2702
2970
|
|
|
2703
2971
|
### Project-level lifecycle hooks
|
|
2704
2972
|
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
**The pattern.** An extension with lifecycle events an author judges worth
|
|
2719
|
-
exposing should let the project name its own function in the extension's own
|
|
2720
|
-
`config`, using the same source shape `function`/`middleware` routes already
|
|
2721
|
-
use (a string path, or `{source, export, args}` — `schemas/urlcode.schema.json`),
|
|
2722
|
-
and add its own `sandbox` boolean next to it (below). The extension's own
|
|
2723
|
-
`activate()` reads that config, and its own runtime dispatch — not a new core
|
|
2724
|
-
primitive, an ordinary call the extension package makes with the request
|
|
2725
|
-
context it already has — invokes the named function at the lifecycle point
|
|
2726
|
-
it defines, with a typed input and a typed verdict the extension's own
|
|
2727
|
-
schema documents. For example, an auth-style extension might declare:
|
|
2973
|
+
Extensions expose project customization points through the core hook primitive.
|
|
2974
|
+
Each registration publishes `hooks`, a machine-readable list containing the
|
|
2975
|
+
hook name, whether it is a value-transforming `filter` or side-effect `action`,
|
|
2976
|
+
its description and its input/output JSON Schemas. The extension embeds
|
|
2977
|
+
`extensionHooksSchema(contracts)` in its configuration schema and calls
|
|
2978
|
+
`loadExtensionHooks(config.hooks, contracts, context)` during activation.
|
|
2979
|
+
Core then enforces the common source/export shape, project-root confinement,
|
|
2980
|
+
known names, eager module/export validation, input/output schemas and reload
|
|
2981
|
+
cache busting. Hook entry bytes participate in the project revision, so editing
|
|
2982
|
+
a hook invalidates the operator's extension pin.
|
|
2983
|
+
|
|
2984
|
+
Projects select those declared hooks in the extension's own configuration:
|
|
2728
2985
|
|
|
2729
2986
|
```yaml
|
|
2730
2987
|
extensions:
|
|
@@ -2740,76 +2997,59 @@ extensions:
|
|
|
2740
2997
|
```
|
|
2741
2998
|
|
|
2742
2999
|
with `beforeRegister` called before an account is created, given a typed
|
|
2743
|
-
`{email,
|
|
3000
|
+
`{email, profile?}` input and returning a typed verdict (`{allow: true}`
|
|
2744
3001
|
or `{allow: false, reason}`), and `onSignUp` called after, for side effects
|
|
2745
|
-
such as provisioning a workspace.
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
project code
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
a
|
|
2763
|
-
security
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
sandboxed `function`/`middleware` route — the identical module-allowlist
|
|
2792
|
-
walk, memory/stack limits, two-layer deadline enforcement, `maxBytes` and
|
|
2793
|
-
response-shape validation, with no separate or weaker engine for
|
|
2794
|
-
extensions. It takes an explicit list of `{source, export}` entries
|
|
2795
|
-
(resolve a hook's `source` string with the re-exported `functionFile()`,
|
|
2796
|
-
the same resolution/validation a native route's `source` gets) instead of
|
|
2797
|
-
anything route/YAML-shaped, and `execute({entry, chain}, request, context)`
|
|
2798
|
-
in place of a `FunctionRoute`. There is no "trusted" mode exported
|
|
2799
|
-
alongside it — `SandboxPool` is only ever the isolated path; see
|
|
2800
|
-
[FUNCTION-SECURITY.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md) and
|
|
2801
|
-
[TYPESCRIPT.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TYPESCRIPT.md) for the full contract.
|
|
2802
|
-
|
|
2803
|
-
An extension honoring a hook's `sandbox: true` is expected to actually
|
|
2804
|
-
isolate that invocation through `SandboxPool` now that the primitive exists
|
|
2805
|
-
(or document plainly that it does not yet, rather than accepting the field
|
|
2806
|
-
and silently running it trusted) — say which, in the extension's own docs,
|
|
2807
|
-
so an author reading them is not misled about what opt-in exists.
|
|
3002
|
+
such as provisioning a workspace. Hook names and lifecycle timing remain the
|
|
3003
|
+
extension's domain, while their declaration, loading and discovery are shared.
|
|
3004
|
+
|
|
3005
|
+
Hooks are first-party project code and run trusted in-process by default, with
|
|
3006
|
+
full Node access, like trusted `function` and `middleware` routes. Contract v1
|
|
3007
|
+
does not define an arbitrary-value sandbox hook protocol. A hook reference with
|
|
3008
|
+
`sandbox: true` is rejected during activation rather than silently run trusted.
|
|
3009
|
+
Only the entry module is refreshed during reactivation; its imported dependencies
|
|
3010
|
+
remain in Node's module cache until restart.
|
|
3011
|
+
|
|
3012
|
+
The UI extension exposes `transformView`, a synchronous filter called before a
|
|
3013
|
+
named kit template renders. It receives `{template, view}` and returns the view
|
|
3014
|
+
model to render. Use copy, templates, theme and CSS for ordinary presentation
|
|
3015
|
+
changes; use this hook for project-specific computed view data that those
|
|
3016
|
+
declarative layers cannot express. It also exposes `transformPage`, called
|
|
3017
|
+
before the shared layout renders. It receives the editable title, layout,
|
|
3018
|
+
navigation, account menu and flash message and returns those page fields. This
|
|
3019
|
+
lets a product join auth/admin screens to its own shell without replacing their
|
|
3020
|
+
security or workflow behavior. Both filters are synchronous and trusted.
|
|
3021
|
+
|
|
3022
|
+
### Building an extension
|
|
3023
|
+
|
|
3024
|
+
An extension package should export a registration factory and, when it supports
|
|
3025
|
+
`urlcode init --with`, a side-effect-free `scaffold` function. The registration:
|
|
3026
|
+
|
|
3027
|
+
1. Declares its logical name, contract version, supported targets, exact project
|
|
3028
|
+
revision pin and strict configuration/policy schemas.
|
|
3029
|
+
2. Publishes every project hook through `hooks` and reuses
|
|
3030
|
+
`extensionHooksSchema` plus `loadExtensionHooks`; it does not implement its
|
|
3031
|
+
own path resolver or dynamic-import cache.
|
|
3032
|
+
3. Publishes an `authoring` contract listing its supported project-owned
|
|
3033
|
+
configuration, theme/copy, component/template, stylesheet and hook surfaces,
|
|
3034
|
+
plus focused `fastChecks`. Keep descriptions concrete enough that an agent
|
|
3035
|
+
can choose a supported surface instead of copying package behavior.
|
|
3036
|
+
4. Activates all configuration, files, services and hooks before serving a
|
|
3037
|
+
request. Invalid or stale configuration fails activation.
|
|
3038
|
+
5. Returns `handle` for mounts and optionally `authorize`/`middleware` for route
|
|
3039
|
+
policies. It closes resources it owns.
|
|
3040
|
+
6. Keeps credentials, storage and provider setup in the operator host. Project
|
|
3041
|
+
YAML contains logical configuration and project-relative hook references.
|
|
3042
|
+
|
|
3043
|
+
Consumers install the package, declare its YAML block and mounts/policies, and
|
|
3044
|
+
register it in `host.mjs`. They modify it through declared configuration,
|
|
3045
|
+
presentation layers and hooks. A fork is reserved for changing behavior the
|
|
3046
|
+
extension has not exposed; that is evidence for a new declarative field or hook.
|
|
3047
|
+
See [Composing a site](https://github.com/jimhoyd-com/urlcode/blob/main/docs/COMPOSING-A-SITE.md) for the complete ui/auth/admin example.
|
|
2808
3048
|
|
|
2809
3049
|
### Discovering schemas
|
|
2810
3050
|
|
|
2811
3051
|
Each registration carries the JSON Schemas that validate its `config` block and
|
|
2812
|
-
its per-route policy requirements. `urlcode extensions` prints them together with
|
|
3052
|
+
its per-route policy requirements, plus its hook and authoring contracts. `urlcode extensions` prints them together with
|
|
2813
3053
|
the project's own declarations so an author can see what a mount accepts:
|
|
2814
3054
|
|
|
2815
3055
|
```sh
|
|
@@ -2818,6 +3058,8 @@ urlcode extensions --project ./site --host-file /absolute/operator/host.mjs [--j
|
|
|
2818
3058
|
|
|
2819
3059
|
For every registration in the host file it reports the name, contract version,
|
|
2820
3060
|
targets, credential headers, configuration schema, policy schema (if any),
|
|
3061
|
+
declared hook names, kinds, descriptions and input/output schemas, supported
|
|
3062
|
+
authoring surfaces and their fast checks,
|
|
2821
3063
|
whether the project declares it, whether its `projectSha256` matches the current
|
|
2822
3064
|
revision, the routes that mount it and the routes whose policies require it.
|
|
2823
3065
|
Declared names the host does not register are listed as unregistered. The command
|
|
@@ -2890,7 +3132,27 @@ in memory (core zeroes `Uint8Array` contents after writing or on failure). The
|
|
|
2890
3132
|
types are exported from `@jimhoyd/urlcode` for packages that want to typecheck
|
|
2891
3133
|
against them.
|
|
2892
3134
|
|
|
2893
|
-
|
|
3135
|
+
`--with` is an unordered set. Core sorts the requested names before calling
|
|
3136
|
+
each `scaffold` (so `names` is the same for every spelling), then orders the
|
|
3137
|
+
results from the optional declarative fields on `ScaffoldResult`:
|
|
3138
|
+
|
|
3139
|
+
- `provides`: capability names the extension offers (for example `ui.kit`);
|
|
3140
|
+
a capability must not equal an extension name.
|
|
3141
|
+
- `requires`: extensions or capabilities that must be in the set and are
|
|
3142
|
+
placed before this extension. A missing one refuses, naming both.
|
|
3143
|
+
- `after`: the same ordering, without requiring presence.
|
|
3144
|
+
- `conflicts`: extensions or capabilities that must not be in the set.
|
|
3145
|
+
- Risky-scaffold acknowledgements are one generic channel, not a flag per risk. The operator repeats `--ack <extension>:<id>` (both parts lowercase letters, digits and hyphens, for example `store:public-write`); core validates the syntax, de-duplicates and sorts the values and hands them to every scaffold as the opaque `ScaffoldRequest.acknowledgements` (always present, possibly empty). An extension reads only ids qualified with its own name. To require one it throws an `Error` whose message states the risk and that carries `acknowledgement: '<name>:<id>'`; core appends the exact re-run command (the same `--with`, `--pin` and `--no-manifest`, plus every `--ack` already given and the new one), so an agent meets the acknowledgement only when it reaches that risk and never needs it in advance. A scaffold that used one lists it in `ScaffoldResult.acknowledged`; core refuses any `--ack` that no scaffold listed (a typo, an extension not in `--with`, or an unneeded value), before anything is written, and refuses a scaffold that lists an id it was not given or that is not its own. Acknowledgements are never written to project YAML; they are visible in command history, and the extension should state the resulting model in its generated README. `routeNotes` (single-line strings) are written as comments above that extension's routes; core renders them and infers no policy. Core adds no per-extension flag or result field: a new risk needs only a new id in the extension that owns it. `--allow-public-write` was a store-specific predecessor of this channel; it was never in a published core release and is removed rather than aliased (see [store](https://github.com/jimhoyd-com/urlcode/blob/main/docs/STORE.md)).
|
|
3146
|
+
|
|
3147
|
+
Core topologically orders by these, taking the lexically smallest ready
|
|
3148
|
+
extension first, so every permutation of the same set produces the same host,
|
|
3149
|
+
`urlcode.yaml` activation order and README. A cycle or a missing requirement or
|
|
3150
|
+
conflict refuses before anything is written, naming the extensions involved.
|
|
3151
|
+
Core never adds an extension (auth or ui) and never infers security policy
|
|
3152
|
+
from the set. Host setup should be self-contained (own identifiers, such as
|
|
3153
|
+
`storeProjectSha256`) unless it declares `requires` for what it references.
|
|
3154
|
+
|
|
3155
|
+
Assembly rules, in the resolved order:
|
|
2894
3156
|
|
|
2895
3157
|
- Every package is resolved and every `scaffold` is called before anything is
|
|
2896
3158
|
written. A name that is not installed refuses with the `npm install` command;
|
|
@@ -2904,7 +3166,7 @@ Assembly rules, in `--with` order:
|
|
|
2904
3166
|
naming both sources.
|
|
2905
3167
|
- `host.mjs` is all `hostImports`, then all `hostSetup` lines, then an
|
|
2906
3168
|
`extensions` array of every `hostEntries` item, then `close()` running the
|
|
2907
|
-
`hostClose` statements in reverse
|
|
3169
|
+
`hostClose` statements in reverse resolved order so later entries release
|
|
2908
3170
|
before what they built on. Setup lines share one module scope: admin's entry
|
|
2909
3171
|
references the `service`, `csrfKey` and `projectSha256` identifiers that
|
|
2910
3172
|
auth's setup defines, which is why `names` carries the full list.
|
|
@@ -3007,8 +3269,10 @@ through the kit and refuse to activate without their own templates present.
|
|
|
3007
3269
|
|
|
3008
3270
|
#### Supported combinations
|
|
3009
3271
|
|
|
3010
|
-
`--with`
|
|
3011
|
-
|
|
3272
|
+
`--with` is an unordered set. Core derives the activation order from each
|
|
3273
|
+
extension's declared requirements, so the kit is active before anything that
|
|
3274
|
+
renders through it, whatever order you name them in. A missing requirement
|
|
3275
|
+
(for example admin without auth) refuses before anything is written.
|
|
3012
3276
|
|
|
3013
3277
|
| `--with` | Result |
|
|
3014
3278
|
|---|---|
|
|
@@ -3018,6 +3282,8 @@ anything that renders through it.
|
|
|
3018
3282
|
| `auth` or `auth,admin` | Refused: the scaffold names the missing `ui`. |
|
|
3019
3283
|
| `auth,admin,ui` | Refused: `ui` must come before the extensions it renders. |
|
|
3020
3284
|
| `admin` without `auth` | Refused: admin reuses auth's service, CSRF key and revision. |
|
|
3285
|
+
| `ui,auth,store` | Todo API and CRUD screen, both protected by `auth: true`. |
|
|
3286
|
+
| `store` or `ui,store` | Refused: the writable mount would be public. Add `auth`, or re-run the printed command with `--ack store:public-write` for a documented public-write scaffold; core rejects any `--ack` no scaffold consumed, such as one with auth composed or `store` absent. |
|
|
3021
3287
|
|
|
3022
3288
|
Every refusal happens before anything is written, and leaves no directory
|
|
3023
3289
|
behind. There is no auth-without-ui or admin-without-ui configuration in this
|
|
@@ -3088,12 +3354,20 @@ middleware, and the host-file trust boundary.
|
|
|
3088
3354
|
The `ui` config's `copy`, `templates` and `stylesheet` paths point at the
|
|
3089
3355
|
project's own directories. Nothing here forks a package.
|
|
3090
3356
|
|
|
3357
|
+
Treat the result as one application. Auth and admin keep ownership of sessions,
|
|
3358
|
+
CSRF, permissions, validation and mutations; the project owns its brand,
|
|
3359
|
+
product navigation and the smallest presentation differences it needs. Inspect
|
|
3360
|
+
`urlcode extensions --host-file ... --json` (MCP: `get_extensions`) and follow
|
|
3361
|
+
each registration's `authoring.surfaces` before copying package code.
|
|
3362
|
+
|
|
3091
3363
|
| Override | File | Effect |
|
|
3092
3364
|
|---|---|---|
|
|
3093
3365
|
| Wording and translation | `ui/copy/<locale>.json` | Replaces catalogue ids, including ids the auth and admin packages own. Listed in `languages`. |
|
|
3094
3366
|
| 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
3367
|
| Styling | `ui/extra.css` | Appended after the kit stylesheet; `{file, replace: true}` replaces it instead. |
|
|
3096
3368
|
| Colours, logo, favicon, radius, font | the `theme` block | Declarative; no file needed. |
|
|
3369
|
+
| Computed view data | `extensions.ui.config.hooks.transformView` | Adds project data before a named template renders. |
|
|
3370
|
+
| Product shell and navigation | `extensions.ui.config.hooks.transformPage` | Changes title, layout, navigation, account menu or flash before the shared layout renders. |
|
|
3097
3371
|
|
|
3098
3372
|
A template is data in the kit's own language. It cannot add a script, change
|
|
3099
3373
|
what a form validates, or change what a page sends in headers — so an override
|
|
@@ -3139,6 +3413,13 @@ not run it. With the packages named:
|
|
|
3139
3413
|
generated README with the flag already set. `@jimhoyd/urlcode-ui` depends on
|
|
3140
3414
|
neither peer; the operator names them.
|
|
3141
3415
|
|
|
3416
|
+
Run the extension's published `fastChecks` while editing. Theme and copy changes
|
|
3417
|
+
need no framework build. Template and CSS checks load only the UI kit and named
|
|
3418
|
+
namespaces; the full runtime validation and request suite remain the final
|
|
3419
|
+
evidence. A React product frontend with `components.json` should also install
|
|
3420
|
+
and follow the official shadcn/ui skill. The server template kit uses compatible
|
|
3421
|
+
tokens but does not accept React components.
|
|
3422
|
+
|
|
3142
3423
|
Overrides of extension templates and of extension-owned catalogue ids reach
|
|
3143
3424
|
the rendered screens, which is what the regression test below asserts.
|
|
3144
3425
|
|
|
@@ -3162,15 +3443,24 @@ extensions:
|
|
|
3162
3443
|
onSignUp: ./hooks/on-signup.mjs
|
|
3163
3444
|
```
|
|
3164
3445
|
|
|
3165
|
-
`hooks`
|
|
3166
|
-
|
|
3167
|
-
|
|
3446
|
+
`hooks` remains each package's own config, but core supplies the reference
|
|
3447
|
+
schema, trusted loader and machine-readable hook contract. `get_extensions`
|
|
3448
|
+
reports those contracts, so an agent can discover accepted names, purpose and
|
|
3449
|
+
input/output shapes without guessing from prose.
|
|
3168
3450
|
|
|
3169
3451
|
Hooks are first-party project code and run **trusted and in-process**, the
|
|
3170
3452
|
same default `function` and `middleware` routes have
|
|
3171
|
-
([FUNCTION-SECURITY.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md)).
|
|
3172
|
-
|
|
3173
|
-
|
|
3453
|
+
([FUNCTION-SECURITY.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md)). Extension hook contract v1 is
|
|
3454
|
+
trusted-only; `sandbox: true` is rejected loudly at activation.
|
|
3455
|
+
|
|
3456
|
+
#### `@jimhoyd/urlcode-ui`
|
|
3457
|
+
|
|
3458
|
+
| Hook | Input | Returns | Called |
|
|
3459
|
+
|---|---|---|---|
|
|
3460
|
+
| `transformView` | `{template, view}` | the view object to render | Synchronously before each public `ui.kit.render()` or `ui.kit.page()` call. Use it only when theme, copy, templates and CSS cannot express the change. |
|
|
3461
|
+
|
|
3462
|
+
For example, `transformView: ./hooks/ui-view.mjs` can add project-computed
|
|
3463
|
+
navigation or labels to an auth/admin view without editing either package.
|
|
3174
3464
|
|
|
3175
3465
|
#### `@jimhoyd/urlcode-auth`
|
|
3176
3466
|
|
|
@@ -3338,14 +3628,11 @@ machinery either way.
|
|
|
3338
3628
|
|
|
3339
3629
|
This engine — worker spawning, the module-allowlist walk, the two-layer
|
|
3340
3630
|
deadline, `maxBytes` and response-shape validation — is one implementation
|
|
3341
|
-
shared by route dispatch and by `@jimhoyd/urlcode/sandbox`'s `SandboxPool
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
`sandbox: true`
|
|
3345
|
-
|
|
3346
|
-
through that entry point; there is no separate, weaker sandbox for extensions
|
|
3347
|
-
to reach for, and no "trusted" mode exported there — trusted execution needs
|
|
3348
|
-
no primitive at all (docs/SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
3631
|
+
shared by route dispatch and by `@jimhoyd/urlcode/sandbox`'s `SandboxPool`.
|
|
3632
|
+
That public HTTP-shaped primitive remains available to extension authors, but
|
|
3633
|
+
project extension hooks use arbitrary typed values and contract v1 runs them
|
|
3634
|
+
trusted in-process; it rejects `sandbox: true` rather than claiming HTTP sandbox
|
|
3635
|
+
semantics apply to them. See [extensions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md#project-level-lifecycle-hooks).
|
|
3349
3636
|
|
|
3350
3637
|
### What the trusted default can and can't do
|
|
3351
3638
|
|