@jimhoyd/urlcode 0.4.2 → 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 +57 -14
- package/.claude/skills/urlcode-operations/SKILL.md +4 -0
- package/README.md +19 -15
- package/SECURITY.md +5 -3
- package/dist/BUILD-MANIFEST.json +30 -27
- 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 +28 -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 +60 -2
- 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/project-tests.js +35 -11
- package/dist/readiness.js +184 -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 +2 -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 +47 -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 +68 -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 +1 -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 +364 -49
- package/llms.txt +43 -5
- 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 +47 -27
- 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 -148
- package/ROADMAP.md +0 -70
- package/docs/AI-AUTHORING.md +0 -339
- 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 -287
- package/docs/CONDITIONS.md +0 -74
- package/docs/CONTAINER-PROMOTION.md +0 -74
- package/docs/DEPLOYMENT-CHECKS.md +0 -108
- package/docs/DEVELOPMENT-PIPELINE.md +0 -304
- package/docs/EGRESS.md +0 -125
- package/docs/EXTENSIONS.md +0 -403
- package/docs/FRAMEWORK.md +0 -217
- package/docs/FUNCTION-SECURITY.md +0 -251
- 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 -149
- 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 -153
- package/docs/README.md +0 -93
- 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-0.4.2.md +0 -30
- package/docs/RELEASE-READINESS.md +0 -146
- package/docs/RELEASE-SECURITY.md +0 -100
- 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 -207
- 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 -88
- 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/2026-09-20/OPEN-DECISIONS-COMPLETED.md +0 -116
- package/docs/archive/README.md +0 -29
- 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 -130
- 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,7 +647,11 @@ 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
|
|
@@ -611,10 +726,12 @@ programmatic compatibility analysis and provider verification limits.
|
|
|
611
726
|
|
|
612
727
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-GUIDE.md
|
|
613
728
|
|
|
614
|
-
|
|
729
|
+
<!-- urlcode-current-version:start -->
|
|
730
|
+
This guide targets URLCode 0.4.6. Start with the function example below,
|
|
615
731
|
then add only the fields your route needs. The authoritative machine-readable
|
|
616
732
|
shape is [JSON Schema](https://github.com/jimhoyd-com/urlcode/blob/main/schemas/urlcode.schema.json); semantic rules are in the
|
|
617
733
|
[specification](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPECIFICATION.md). Unsupported fields fail validation.
|
|
734
|
+
<!-- urlcode-current-version:end -->
|
|
618
735
|
|
|
619
736
|
### Run all the examples
|
|
620
737
|
|
|
@@ -694,6 +811,8 @@ schema-valid combinations activate successfully.
|
|
|
694
811
|
| `routes.*.enabled` | boolean | no | — |
|
|
695
812
|
| `routes.*.sandbox` | boolean | no | default: false |
|
|
696
813
|
| `routes.*.sandboxReason` | string | no | maxLength: 500 |
|
|
814
|
+
| `routes.*.coveredElsewhere` | object | no | minProperties: 1 |
|
|
815
|
+
| `routes.*.coveredElsewhere.*` | string | no | minLength: 1; maxLength: 500 |
|
|
697
816
|
| `routes.*.expires` | string | no | — |
|
|
698
817
|
| `routes.*.description` | string | no | maxLength: 1024 |
|
|
699
818
|
| `routes.*.parameters` | array | no | maxItems: 64 |
|
|
@@ -711,6 +830,8 @@ schema-valid combinations activate successfully.
|
|
|
711
830
|
| `routes.*.parameters[].schema.maxLength` | integer | no | minimum: 0; maximum: 8192 |
|
|
712
831
|
| `routes.*.parameters[].schema.minimum` | number | no | — |
|
|
713
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"] |
|
|
714
835
|
| `routes.*.parameters[].schema.items` | object | no | unknown keys rejected |
|
|
715
836
|
| `routes.*.parameters[].schema.items.type` | string | yes | enum: ["string","integer","number","boolean"] |
|
|
716
837
|
| `routes.*.parameters[].schema.maxItems` | integer | no | minimum: 0; maximum: 100 |
|
|
@@ -771,6 +892,7 @@ schema-valid combinations activate successfully.
|
|
|
771
892
|
| `routes.*.request.body.contentTypes` | array | no | minItems: 1; maxItems: 16; uniqueItems: true |
|
|
772
893
|
| `routes.*.request.body.contentTypes[]` | string | no | pattern: "^[a-z0-9!#$&^_.+-]+/[a-z0-9!#$&^_.+-]+$" |
|
|
773
894
|
| `routes.*.request.body.format` | string | no | enum: ["text","json"] |
|
|
895
|
+
| `routes.*.request.body.schema` | object | no | — |
|
|
774
896
|
| `routes.*.response` | object | no | unknown keys rejected |
|
|
775
897
|
| `routes.*.response.headers` | object | no | maxProperties: 64 |
|
|
776
898
|
| `routes.*.response.headers.*` | one of the shapes below | no | — |
|
|
@@ -951,6 +1073,7 @@ schema-valid combinations activate successfully.
|
|
|
951
1073
|
| `routes.*.cache.maxBytes` | integer | no | minimum: 0; maximum: 16777216 |
|
|
952
1074
|
| `routes.*.cache.maxEntries` | integer | no | minimum: 1; maximum: 1000000 |
|
|
953
1075
|
| `routes.*.cache.force` | boolean | no | default: false |
|
|
1076
|
+
| `routes.*.use` | string | no | pattern: "^[a-z][a-z0-9-]{0,63}$" |
|
|
954
1077
|
| `includes` | array | no | maxItems: 256; uniqueItems: true |
|
|
955
1078
|
| `includes[]` | string | no | maxLength: 1024 |
|
|
956
1079
|
| `policies` | object | no | unknown keys rejected |
|
|
@@ -1115,10 +1238,27 @@ schema-valid combinations activate successfully.
|
|
|
1115
1238
|
| `site.securityTxt.encryption` | array | no | maxItems: 64 |
|
|
1116
1239
|
| `site.securityTxt.encryption[]` | string | no | minLength: 1; maxLength: 2048 |
|
|
1117
1240
|
| `site.llms` | string | no | minLength: 1; maxLength: 1024 |
|
|
1241
|
+
| `site.notFound` | string | no | minLength: 1; maxLength: 1024; pattern: "\\.[hH][tT][mM][lL]?$" |
|
|
1118
1242
|
| `extensions` | object | no | maxProperties: 16 |
|
|
1119
1243
|
| `extensions.*` | object | no | unknown keys rejected |
|
|
1120
1244
|
| `extensions.*.version` | constant | yes | const: "1" |
|
|
1121
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 |
|
|
1122
1262
|
|
|
1123
1263
|
---
|
|
1124
1264
|
|
|
@@ -1127,7 +1267,7 @@ schema-valid combinations activate successfully.
|
|
|
1127
1267
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPECIFICATION.md
|
|
1128
1268
|
|
|
1129
1269
|
This document and [JSON Schema](https://github.com/jimhoyd-com/urlcode/blob/main/schemas/urlcode.schema.json) describe
|
|
1130
|
-
the source contract, including
|
|
1270
|
+
the source contract, including additions since 0.3.0.
|
|
1131
1271
|
`version: "1"` remains the project-format contract. Unsupported fields
|
|
1132
1272
|
are rejected rather than interpreted as future behavior.
|
|
1133
1273
|
|
|
@@ -1232,6 +1372,31 @@ Routes accept `request.body` validation and `response.headers` overrides. The
|
|
|
1232
1372
|
See [HTTP configuration](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md) for the exact supported fields, precedence,
|
|
1233
1373
|
security restrictions and examples.
|
|
1234
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
|
+
|
|
1235
1400
|
### Policies
|
|
1236
1401
|
|
|
1237
1402
|
Optional top-level `policies` and `profiles` keys, and `routes.<path>.policies`,
|
|
@@ -1254,8 +1419,11 @@ Duplicate scalar query/header inputs return 400. Required missing inputs return
|
|
|
1254
1419
|
400; defaults apply only to absent inputs. Empty strings are present values.
|
|
1255
1420
|
|
|
1256
1421
|
Supported validation: `type`, scalar `enum`, `default`, string `minLength`/
|
|
1257
|
-
`maxLength`,
|
|
1258
|
-
|
|
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
|
|
1259
1427
|
OpenAPI `style`/`explode` fields are not implemented. This uses a documented
|
|
1260
1428
|
OpenAPI-like input subset; it is not an OpenAPI document or full JSON Schema
|
|
1261
1429
|
input vocabulary. String limits are at most 8,192 characters.
|
|
@@ -1328,6 +1496,16 @@ entry may likewise be a string, normalized to `{source: <string>}`. Only the
|
|
|
1328
1496
|
long form exists after loading, so `routes`, `audit`, `explain`, revision hashes
|
|
1329
1497
|
and the field reference describe the expansion.
|
|
1330
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
|
+
|
|
1331
1509
|
ES modules only. `.mjs` is loaded as ESM in both modes. A `sandbox: true` route
|
|
1332
1510
|
also reads `.js` as ESM independently of Node package settings, but a trusted
|
|
1333
1511
|
route imports `.js` through Node's own loader, so an ESM `.js` module needs the
|
|
@@ -1646,6 +1824,7 @@ status or default `Cache-Control: no-store` on functions/redirects.
|
|
|
1646
1824
|
| `request.body.maxBytes` | 0–1048576; tighter per-route budget, enforced while reading fixed/chunked bodies; 413 on overflow |
|
|
1647
1825
|
| `request.body.contentTypes` | Exact lowercase MIME essences for nonempty bodies; parameters ignored; mismatch/missing type returns 415 |
|
|
1648
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) |
|
|
1649
1828
|
|
|
1650
1829
|
The operator request limit remains an upper bound; YAML cannot raise it. A route
|
|
1651
1830
|
without body policy keeps the existing server limit. A configured body policy
|
|
@@ -1685,12 +1864,76 @@ cache and disposition on its own handler. Asset metadata cannot be overridden by
|
|
|
1685
1864
|
`response.headers`. On functions/declared responses, Content-Type may be configured;
|
|
1686
1865
|
JSON declarations require a JSON type. No response header secret interpolation.
|
|
1687
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
|
+
|
|
1688
1916
|
`respond` is an additional native handler (exactly one handler per route):
|
|
1689
1917
|
|
|
1690
1918
|
- `status`: 200–599, default 200; 206 and 304 are reserved for native asset semantics.
|
|
1691
1919
|
- `text`: literal UTF-8 body, default content type text/plain.
|
|
1692
1920
|
- `json`: any JSON-compatible YAML value, serialized with application/json.
|
|
1693
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.
|
|
1694
1937
|
- Status 204/205 cannot declare a nonempty body. HEAD always suppresses the body.
|
|
1695
1938
|
|
|
1696
1939
|
Functions still return their own Response/status/body. YAML header policy does
|
|
@@ -1960,10 +2203,8 @@ compiled and the request path is unchanged. Each policy has its own page:
|
|
|
1960
2203
|
| `compression` | [compression](https://github.com/jimhoyd-com/urlcode/blob/main/docs/policies/compression.md) | `Accept-Encoding` negotiation; assets precompressed at snapshot time |
|
|
1961
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 |
|
|
1962
2205
|
|
|
1963
|
-
|
|
1964
|
-
[
|
|
1965
|
-
declarative block cannot express pass host [plugins](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PLUGINS.md) in code; plugins
|
|
1966
|
-
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.
|
|
1967
2208
|
|
|
1968
2209
|
### Declaring policies
|
|
1969
2210
|
|
|
@@ -2182,6 +2423,7 @@ site:
|
|
|
2182
2423
|
policy: [https://example.com/security-policy]
|
|
2183
2424
|
preferredLanguages: [en, fr]
|
|
2184
2425
|
llms: llms.txt
|
|
2426
|
+
notFound: public/404.html
|
|
2185
2427
|
routes:
|
|
2186
2428
|
/: {page: {file: public/index.html}}
|
|
2187
2429
|
```
|
|
@@ -2291,9 +2533,30 @@ refused).
|
|
|
2291
2533
|
A project-relative text file served as a `page` route with
|
|
2292
2534
|
`text/plain; charset=utf-8` and the default `no-cache`.
|
|
2293
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
|
+
|
|
2294
2557
|
### Per-target support
|
|
2295
2558
|
|
|
2296
|
-
| Target | `robots`, `sitemap`, `securityTxt` (`respond`) | `favicon`, `llms` (`page`) |
|
|
2559
|
+
| Target | `robots`, `sitemap`, `securityTxt` (`respond`) | `favicon`, `llms`, `notFound` (`page`) |
|
|
2297
2560
|
| --- | --- | --- |
|
|
2298
2561
|
| self-hosted, Vercel, AWS | served | served |
|
|
2299
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 |
|
|
@@ -2532,6 +2795,10 @@ and management API. That package has since been retired and unpublished, so no
|
|
|
2532
2795
|
supported stored-link extension ships today. Core no longer has a native `link`
|
|
2533
2796
|
handler or a `dynamicLinks` project flag.
|
|
2534
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
|
+
|
|
2535
2802
|
A project declares versioned configuration and exclusive route mounts:
|
|
2536
2803
|
|
|
2537
2804
|
```yaml
|
|
@@ -2746,7 +3013,11 @@ The UI extension exposes `transformView`, a synchronous filter called before a
|
|
|
2746
3013
|
named kit template renders. It receives `{template, view}` and returns the view
|
|
2747
3014
|
model to render. Use copy, templates, theme and CSS for ordinary presentation
|
|
2748
3015
|
changes; use this hook for project-specific computed view data that those
|
|
2749
|
-
declarative layers cannot express.
|
|
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.
|
|
2750
3021
|
|
|
2751
3022
|
### Building an extension
|
|
2752
3023
|
|
|
@@ -2758,11 +3029,15 @@ An extension package should export a registration factory and, when it supports
|
|
|
2758
3029
|
2. Publishes every project hook through `hooks` and reuses
|
|
2759
3030
|
`extensionHooksSchema` plus `loadExtensionHooks`; it does not implement its
|
|
2760
3031
|
own path resolver or dynamic-import cache.
|
|
2761
|
-
3.
|
|
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
|
|
2762
3037
|
request. Invalid or stale configuration fails activation.
|
|
2763
|
-
|
|
3038
|
+
5. Returns `handle` for mounts and optionally `authorize`/`middleware` for route
|
|
2764
3039
|
policies. It closes resources it owns.
|
|
2765
|
-
|
|
3040
|
+
6. Keeps credentials, storage and provider setup in the operator host. Project
|
|
2766
3041
|
YAML contains logical configuration and project-relative hook references.
|
|
2767
3042
|
|
|
2768
3043
|
Consumers install the package, declare its YAML block and mounts/policies, and
|
|
@@ -2774,7 +3049,7 @@ See [Composing a site](https://github.com/jimhoyd-com/urlcode/blob/main/docs/COM
|
|
|
2774
3049
|
### Discovering schemas
|
|
2775
3050
|
|
|
2776
3051
|
Each registration carries the JSON Schemas that validate its `config` block and
|
|
2777
|
-
its per-route policy requirements, plus its hook contracts. `urlcode extensions` prints them together with
|
|
3052
|
+
its per-route policy requirements, plus its hook and authoring contracts. `urlcode extensions` prints them together with
|
|
2778
3053
|
the project's own declarations so an author can see what a mount accepts:
|
|
2779
3054
|
|
|
2780
3055
|
```sh
|
|
@@ -2783,7 +3058,8 @@ urlcode extensions --project ./site --host-file /absolute/operator/host.mjs [--j
|
|
|
2783
3058
|
|
|
2784
3059
|
For every registration in the host file it reports the name, contract version,
|
|
2785
3060
|
targets, credential headers, configuration schema, policy schema (if any),
|
|
2786
|
-
declared hook names, kinds, descriptions and input/output schemas,
|
|
3061
|
+
declared hook names, kinds, descriptions and input/output schemas, supported
|
|
3062
|
+
authoring surfaces and their fast checks,
|
|
2787
3063
|
whether the project declares it, whether its `projectSha256` matches the current
|
|
2788
3064
|
revision, the routes that mount it and the routes whose policies require it.
|
|
2789
3065
|
Declared names the host does not register are listed as unregistered. The command
|
|
@@ -2856,7 +3132,27 @@ in memory (core zeroes `Uint8Array` contents after writing or on failure). The
|
|
|
2856
3132
|
types are exported from `@jimhoyd/urlcode` for packages that want to typecheck
|
|
2857
3133
|
against them.
|
|
2858
3134
|
|
|
2859
|
-
|
|
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:
|
|
2860
3156
|
|
|
2861
3157
|
- Every package is resolved and every `scaffold` is called before anything is
|
|
2862
3158
|
written. A name that is not installed refuses with the `npm install` command;
|
|
@@ -2870,7 +3166,7 @@ Assembly rules, in `--with` order:
|
|
|
2870
3166
|
naming both sources.
|
|
2871
3167
|
- `host.mjs` is all `hostImports`, then all `hostSetup` lines, then an
|
|
2872
3168
|
`extensions` array of every `hostEntries` item, then `close()` running the
|
|
2873
|
-
`hostClose` statements in reverse
|
|
3169
|
+
`hostClose` statements in reverse resolved order so later entries release
|
|
2874
3170
|
before what they built on. Setup lines share one module scope: admin's entry
|
|
2875
3171
|
references the `service`, `csrfKey` and `projectSha256` identifiers that
|
|
2876
3172
|
auth's setup defines, which is why `names` carries the full list.
|
|
@@ -2973,8 +3269,10 @@ through the kit and refuse to activate without their own templates present.
|
|
|
2973
3269
|
|
|
2974
3270
|
#### Supported combinations
|
|
2975
3271
|
|
|
2976
|
-
`--with`
|
|
2977
|
-
|
|
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.
|
|
2978
3276
|
|
|
2979
3277
|
| `--with` | Result |
|
|
2980
3278
|
|---|---|
|
|
@@ -2984,6 +3282,8 @@ anything that renders through it.
|
|
|
2984
3282
|
| `auth` or `auth,admin` | Refused: the scaffold names the missing `ui`. |
|
|
2985
3283
|
| `auth,admin,ui` | Refused: `ui` must come before the extensions it renders. |
|
|
2986
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. |
|
|
2987
3287
|
|
|
2988
3288
|
Every refusal happens before anything is written, and leaves no directory
|
|
2989
3289
|
behind. There is no auth-without-ui or admin-without-ui configuration in this
|
|
@@ -3054,12 +3354,20 @@ middleware, and the host-file trust boundary.
|
|
|
3054
3354
|
The `ui` config's `copy`, `templates` and `stylesheet` paths point at the
|
|
3055
3355
|
project's own directories. Nothing here forks a package.
|
|
3056
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
|
+
|
|
3057
3363
|
| Override | File | Effect |
|
|
3058
3364
|
|---|---|---|
|
|
3059
3365
|
| Wording and translation | `ui/copy/<locale>.json` | Replaces catalogue ids, including ids the auth and admin packages own. Listed in `languages`. |
|
|
3060
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`. |
|
|
3061
3367
|
| Styling | `ui/extra.css` | Appended after the kit stylesheet; `{file, replace: true}` replaces it instead. |
|
|
3062
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. |
|
|
3063
3371
|
|
|
3064
3372
|
A template is data in the kit's own language. It cannot add a script, change
|
|
3065
3373
|
what a form validates, or change what a page sends in headers — so an override
|
|
@@ -3105,6 +3413,13 @@ not run it. With the packages named:
|
|
|
3105
3413
|
generated README with the flag already set. `@jimhoyd/urlcode-ui` depends on
|
|
3106
3414
|
neither peer; the operator names them.
|
|
3107
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
|
+
|
|
3108
3423
|
Overrides of extension templates and of extension-owned catalogue ids reach
|
|
3109
3424
|
the rendered screens, which is what the regression test below asserts.
|
|
3110
3425
|
|