@jimhoyd/urlcode 0.4.2 → 0.4.7
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 +42 -18
- 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 +44 -6
- package/package.json +15 -26
- 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/docs/COMPOSING-A-SITE.md
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
# Composing a site from ui, auth and admin
|
|
2
|
-
|
|
3
|
-
One command produces a site that already has accounts, an administration
|
|
4
|
-
console and a presentation kit wired together:
|
|
5
|
-
|
|
6
|
-
```sh
|
|
7
|
-
npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin
|
|
8
|
-
urlcode init site --with ui,auth,admin
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
This page is the map of what you may then change, and with which tool. It
|
|
12
|
-
covers three different activities that are easy to confuse:
|
|
13
|
-
|
|
14
|
-
| You want to | Use | Code? |
|
|
15
|
-
|---|---|---|
|
|
16
|
-
| Change what an extension is configured to do | the `config` block in `app/urlcode.yaml` | none |
|
|
17
|
-
| Change how its screens look or read | files under `<site>/ui/` | none (templates are data) |
|
|
18
|
-
| Run your own logic at one of its lifecycle points | a project function named from that `config` | first-party project JavaScript |
|
|
19
|
-
| Add a capability none of the three provides | a new extension package | TypeScript against the runtime contract |
|
|
20
|
-
|
|
21
|
-
Work down that list, not up. The [declarative-first
|
|
22
|
-
principle](PROJECT-DIRECTION.md#design-principle-declarative-first) applies
|
|
23
|
-
here as much as it does to routes: reach for the next row only when the one
|
|
24
|
-
above it cannot express the requirement.
|
|
25
|
-
|
|
26
|
-
## What `--with ui,auth,admin` generates
|
|
27
|
-
|
|
28
|
-
- `site/app/` — the route project: `urlcode.yaml` with an `extensions` block
|
|
29
|
-
per package, and `routes/extensions.yaml` holding `/assets/ui/*`,
|
|
30
|
-
`/account/*`, `/private` and `/admin/*`.
|
|
31
|
-
- `site/host.mjs` — the operator host module, the one place that holds code.
|
|
32
|
-
It builds the kit with `createUiExtension`, passes the returned object into
|
|
33
|
-
`authExtension` and `adminExtension`, and lists `ui.registration` first.
|
|
34
|
-
- `site/ui/` — `copy/`, `templates/` and `extra.css`, the project's
|
|
35
|
-
presentation overrides, beside the host and **outside** `app/`.
|
|
36
|
-
- `site/operator-service.mjs`, `site/data/` — auth's operator service and its
|
|
37
|
-
private key material, mode `0600`.
|
|
38
|
-
- `site/README.md` — the merged next steps, environment table and the project
|
|
39
|
-
revision to review and pin.
|
|
40
|
-
|
|
41
|
-
Nothing about that wiring is manual any more. The generated host registers
|
|
42
|
-
`authCatalogue` as a copy source and both `authUiTemplates` and
|
|
43
|
-
`adminUiTemplates` as template namespaces, because auth and admin render only
|
|
44
|
-
through the kit and refuse to activate without their own templates present.
|
|
45
|
-
|
|
46
|
-
### Supported combinations
|
|
47
|
-
|
|
48
|
-
`--with` order is the activation order, and the kit must be active before
|
|
49
|
-
anything that renders through it.
|
|
50
|
-
|
|
51
|
-
| `--with` | Result |
|
|
52
|
-
|---|---|
|
|
53
|
-
| `ui` | Kit only; the host wires no peer catalogue or templates. |
|
|
54
|
-
| `ui,auth` | Accounts on `/account/*`, rendered through the kit. |
|
|
55
|
-
| `ui,auth,admin` | The full composition above. |
|
|
56
|
-
| `auth` or `auth,admin` | Refused: the scaffold names the missing `ui`. |
|
|
57
|
-
| `auth,admin,ui` | Refused: `ui` must come before the extensions it renders. |
|
|
58
|
-
| `admin` without `auth` | Refused: admin reuses auth's service, CSRF key and revision. |
|
|
59
|
-
|
|
60
|
-
Every refusal happens before anything is written, and leaves no directory
|
|
61
|
-
behind. There is no auth-without-ui or admin-without-ui configuration in this
|
|
62
|
-
revision: the UI primitive fallback was retired, so the kit is the only render
|
|
63
|
-
path (see [OPEN-DECISIONS.md](OPEN-DECISIONS.md)).
|
|
64
|
-
|
|
65
|
-
## Declarative configuration
|
|
66
|
-
|
|
67
|
-
Each package owns one `extensions.<name>` block. The block itself is core
|
|
68
|
-
schema (`version` plus `config`); what may go inside `config` is the
|
|
69
|
-
package's own JSON Schema, which you can print rather than guess:
|
|
70
|
-
|
|
71
|
-
```sh
|
|
72
|
-
urlcode extensions --project ./site/app --host-file "$PWD/site/host.mjs" --json
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
The same report is the MCP tool `get_extensions`, and it is the authoritative
|
|
76
|
-
answer for both the configuration schema and the per-route policy schema. The
|
|
77
|
-
generated site starts from something like this:
|
|
78
|
-
|
|
79
|
-
```yaml
|
|
80
|
-
version: "1"
|
|
81
|
-
extensions:
|
|
82
|
-
ui:
|
|
83
|
-
version: "1"
|
|
84
|
-
config:
|
|
85
|
-
theme:
|
|
86
|
-
name: My Site
|
|
87
|
-
languages: [en]
|
|
88
|
-
copy: ui/copy
|
|
89
|
-
templates: ui/templates
|
|
90
|
-
stylesheet: ui/extra.css
|
|
91
|
-
auth:
|
|
92
|
-
version: "1"
|
|
93
|
-
config:
|
|
94
|
-
registration: "off"
|
|
95
|
-
admin:
|
|
96
|
-
version: "1"
|
|
97
|
-
config: {}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Routes mount an extension, and policies require one:
|
|
101
|
-
|
|
102
|
-
```yaml
|
|
103
|
-
routes:
|
|
104
|
-
/assets/ui/*:
|
|
105
|
-
extension: ui
|
|
106
|
-
methods: [GET, HEAD]
|
|
107
|
-
/account/*:
|
|
108
|
-
extension: auth
|
|
109
|
-
methods: [GET, HEAD, POST]
|
|
110
|
-
/admin/*:
|
|
111
|
-
extension: admin
|
|
112
|
-
methods: [GET, HEAD, POST]
|
|
113
|
-
/private:
|
|
114
|
-
respond:
|
|
115
|
-
text: Signed in
|
|
116
|
-
policies:
|
|
117
|
-
extensions:
|
|
118
|
-
auth: {}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
See [EXTENSIONS.md](EXTENSIONS.md) for the `auth` route short form, extension
|
|
122
|
-
middleware, and the host-file trust boundary.
|
|
123
|
-
|
|
124
|
-
## Presentation overrides
|
|
125
|
-
|
|
126
|
-
The `ui` config's `copy`, `templates` and `stylesheet` paths point at the
|
|
127
|
-
project's own directories. Nothing here forks a package.
|
|
128
|
-
|
|
129
|
-
| Override | File | Effect |
|
|
130
|
-
|---|---|---|
|
|
131
|
-
| Wording and translation | `ui/copy/<locale>.json` | Replaces catalogue ids, including ids the auth and admin packages own. Listed in `languages`. |
|
|
132
|
-
| 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`. |
|
|
133
|
-
| Styling | `ui/extra.css` | Appended after the kit stylesheet; `{file, replace: true}` replaces it instead. |
|
|
134
|
-
| Colours, logo, favicon, radius, font | the `theme` block | Declarative; no file needed. |
|
|
135
|
-
|
|
136
|
-
A template is data in the kit's own language. It cannot add a script, change
|
|
137
|
-
what a form validates, or change what a page sends in headers — so an override
|
|
138
|
-
cannot weaken the screen it restyles. Stylesheets containing `@import`,
|
|
139
|
-
`script`, `javascript:` or `expression(` are refused.
|
|
140
|
-
|
|
141
|
-
Names, coverage and what the runtime will actually load:
|
|
142
|
-
|
|
143
|
-
```sh
|
|
144
|
-
npx urlcode-ui list --project ./site --extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin
|
|
145
|
-
npx urlcode-ui doctor --project ./site --extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin --copy ui/copy --templates ui/templates --stylesheet ui/extra.css
|
|
146
|
-
npx urlcode-ui eject auth/sign-in --out ./site/ui/templates --project ./site --extensions @jimhoyd/urlcode-auth
|
|
147
|
-
npx urlcode-ui copy --missing fr --project ./site --extensions @jimhoyd/urlcode-auth --copy ui/copy --languages en,fr
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
`eject` copies the shipped source so an override starts from what ships and
|
|
151
|
-
never overwrites an existing file. `ui/` lives outside `app/`, so editing copy
|
|
152
|
-
or templates does **not** change the project revision and does not require
|
|
153
|
-
re-pinning `PROJECT_SHA256`.
|
|
154
|
-
|
|
155
|
-
**Name the packages that ship the other namespaces.** `urlcode-ui` is this
|
|
156
|
-
kit alone until `--extensions` names them. Each package is resolved from
|
|
157
|
-
`--project` with Node package resolution and imported for the namespace it
|
|
158
|
-
exports; one that is not installed there is skipped with a note, so the
|
|
159
|
-
command still runs. The site's `host.mjs` is never read: it builds services
|
|
160
|
-
and reads secrets at its top level, and a read-only `list` or `doctor` must
|
|
161
|
-
not run it. With the packages named:
|
|
162
|
-
|
|
163
|
-
- `list` shows the `auth/*` and `admin/*` names beside the kit's own, each
|
|
164
|
-
with its origin, and `eject auth/sign-in` copies the shipped source.
|
|
165
|
-
- `doctor` reports an `expected` view model for an extension template, so its
|
|
166
|
-
`behind` flag tells you when an override of one has fallen behind what
|
|
167
|
-
ships. Its `extensions` field names the namespaces the report covers, so a
|
|
168
|
-
report built without a peer is visible as such.
|
|
169
|
-
- `preview auth/sign-in` renders the extension's own sample view model.
|
|
170
|
-
- `copy --missing` skeletons cover the auth ids the account screens use.
|
|
171
|
-
Admin-owned `adminUi.*` ids are deliberately not offered: admin composes
|
|
172
|
-
its catalogue onto the kit's presentation rather than registering it there,
|
|
173
|
-
and those translations do not currently reach the console
|
|
174
|
-
([#227](https://github.com/jimhoyd-com/urlcode/issues/227)).
|
|
175
|
-
|
|
176
|
-
`urlcode init <directory> --with ui,auth,admin` writes these commands into the
|
|
177
|
-
generated README with the flag already set. `@jimhoyd/urlcode-ui` depends on
|
|
178
|
-
neither peer; the operator names them.
|
|
179
|
-
|
|
180
|
-
Overrides of extension templates and of extension-owned catalogue ids reach
|
|
181
|
-
the rendered screens, which is what the regression test below asserts.
|
|
182
|
-
|
|
183
|
-
## Project functions: lifecycle hooks
|
|
184
|
-
|
|
185
|
-
A hook is your own function, named from the extension's `config`, that the
|
|
186
|
-
extension calls at a lifecycle point it defines. It uses the same source shape
|
|
187
|
-
a `function` route uses — a bare path, or `{source, export}` — resolved
|
|
188
|
-
relative to the route project.
|
|
189
|
-
|
|
190
|
-
```yaml
|
|
191
|
-
extensions:
|
|
192
|
-
auth:
|
|
193
|
-
version: "1"
|
|
194
|
-
config:
|
|
195
|
-
registration: open
|
|
196
|
-
hooks:
|
|
197
|
-
beforeRegister:
|
|
198
|
-
source: ./hooks/registration-rule.mjs
|
|
199
|
-
export: default
|
|
200
|
-
onSignUp: ./hooks/on-signup.mjs
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
`hooks` remains each package's own config, but core supplies the reference
|
|
204
|
-
schema, trusted loader and machine-readable hook contract. `get_extensions`
|
|
205
|
-
reports those contracts, so an agent can discover accepted names, purpose and
|
|
206
|
-
input/output shapes without guessing from prose.
|
|
207
|
-
|
|
208
|
-
Hooks are first-party project code and run **trusted and in-process**, the
|
|
209
|
-
same default `function` and `middleware` routes have
|
|
210
|
-
([FUNCTION-SECURITY.md](FUNCTION-SECURITY.md)). Extension hook contract v1 is
|
|
211
|
-
trusted-only; `sandbox: true` is rejected loudly at activation.
|
|
212
|
-
|
|
213
|
-
### `@jimhoyd/urlcode-ui`
|
|
214
|
-
|
|
215
|
-
| Hook | Input | Returns | Called |
|
|
216
|
-
|---|---|---|---|
|
|
217
|
-
| `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. |
|
|
218
|
-
|
|
219
|
-
For example, `transformView: ./hooks/ui-view.mjs` can add project-computed
|
|
220
|
-
navigation or labels to an auth/admin view without editing either package.
|
|
221
|
-
|
|
222
|
-
### `@jimhoyd/urlcode-auth`
|
|
223
|
-
|
|
224
|
-
| Hook | Input | Returns | Called |
|
|
225
|
-
|---|---|---|---|
|
|
226
|
-
| `beforeRegister` | `{email, profile?}` | `{allow: boolean, reason?}` | Before an account is created, on `POST /account/register` and on `POST /account/signup/begin`. |
|
|
227
|
-
| `onSignUp` | `{accountId, email}` | ignored | After a genuinely new account is created — on `/account/register`, and on `/account/signup/complete` only when that completion created an account rather than signing an existing one in. |
|
|
228
|
-
| `onDelete` | `{accountId, email}` | ignored | After the account owner's own deletion is scheduled. Not on an administrator-initiated deletion, and not on the background purge when the grace period ends. |
|
|
229
|
-
|
|
230
|
-
### `@jimhoyd/urlcode-admin`
|
|
231
|
-
|
|
232
|
-
| Hook | Input | Returns | Called |
|
|
233
|
-
|---|---|---|---|
|
|
234
|
-
| `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check. A veto means the auth service is never asked. |
|
|
235
|
-
| `onRegistrationApproved` | `{requestId, accountId, email, actorId, reason}` | ignored | After a registration request is approved. |
|
|
236
|
-
| `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked. |
|
|
237
|
-
|
|
238
|
-
### Verdicts and failure
|
|
239
|
-
|
|
240
|
-
- **A veto is explicit.** A pre-action hook allows only by returning
|
|
241
|
-
`allow: true`. `allow: false`, or no verdict at all, rejects the operation
|
|
242
|
-
with `403` and the hook's own `reason`, or a generic message when it gave
|
|
243
|
-
none. Nothing is written. A hook that *throws* has not returned a verdict:
|
|
244
|
-
the operation is still refused, but as a generic `500`, so return a verdict
|
|
245
|
-
rather than throwing when you mean to deny.
|
|
246
|
-
- **Broken hooks fail at activation, not at the first request.** A missing
|
|
247
|
-
module, a source path escaping the project, an export that is not a
|
|
248
|
-
function, or `sandbox: true` all throw while the extension activates, naming
|
|
249
|
-
the hook. The site does not start.
|
|
250
|
-
- **A post-action hook cannot undo anything.** `onSignUp`,
|
|
251
|
-
`onDelete`, `onRegistrationApproved` and `onAccountStatusChanged` run after
|
|
252
|
-
the operation has committed. Throwing from one replaces the success response
|
|
253
|
-
with a `500` while the account, approval or status change stands. There is
|
|
254
|
-
no retry and no rollback. Keep them non-throwing: catch your own errors and
|
|
255
|
-
queue the work instead of failing the request.
|
|
256
|
-
- **A hook's message is not a channel to the browser.** Only a pre-action
|
|
257
|
-
`reason` is shown. An uncaught error surfaces as a generic failure.
|
|
258
|
-
- **An edited hook needs a restart.** Activation re-imports the hook's entry
|
|
259
|
-
module, so a reload picks up an edit to that file — but modules it imports
|
|
260
|
-
stay on Node's module cache, exactly as for trusted route functions.
|
|
261
|
-
|
|
262
|
-
## TypeScript: implementing a new extension
|
|
263
|
-
|
|
264
|
-
Only write an extension when a capability is genuinely absent — not to
|
|
265
|
-
customize one of the three above. An extension is an operator-installed
|
|
266
|
-
package whose host object core activates; it is named in `host.mjs`, never
|
|
267
|
-
in YAML. The contract, the activation inputs, `ExtensionActivation.root`,
|
|
268
|
-
credential headers and the `projectSha256` pin are in
|
|
269
|
-
[EXTENSIONS.md](EXTENSIONS.md) and [TYPESCRIPT.md](TYPESCRIPT.md); the
|
|
270
|
-
`scaffold` export that makes a package work with `init --with` is in
|
|
271
|
-
[EXTENSIONS.md](EXTENSIONS.md#scaffolding-with-init---with).
|
|
272
|
-
|
|
273
|
-
If its screens should be themeable the same way auth's and admin's are, it
|
|
274
|
-
also exports a template namespace (and, if it ships English wording, a
|
|
275
|
-
catalogue) for a host to pass to `createUiExtension`. That is what makes
|
|
276
|
-
`ui/templates/<yourname>/<screen>.html` work in a consumer project without a
|
|
277
|
-
fork.
|
|
278
|
-
|
|
279
|
-
## What this page does not claim
|
|
280
|
-
|
|
281
|
-
The composition, the refusals and the override path are exercised by
|
|
282
|
-
`test/workspace-scaffold.integration.ts`, which runs `init --with` against the
|
|
283
|
-
built packages, drops a template and a copy catalogue into the generated
|
|
284
|
-
`ui/` directory and asserts both reach a rendered auth screen and a rendered
|
|
285
|
-
admin screen. That runs in-process against the generated host: no HTTP
|
|
286
|
-
listener, TLS proxy, browser or deployed site is exercised, and no published
|
|
287
|
-
npm tarball is checked against this checkout.
|
package/docs/CONDITIONS.md
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# Conditional routing
|
|
2
|
-
|
|
3
|
-
Conditions are exact string comparisons over a bounded, portable input subset.
|
|
4
|
-
A route's `match` is a conjunction: every declared query/header/cookie, host and
|
|
5
|
-
method must match. A mismatch returns 404 without trying a less-specific path.
|
|
6
|
-
Path precedence is unchanged; matching conditions is not authorization.
|
|
7
|
-
|
|
8
|
-
```yaml
|
|
9
|
-
version: "1"
|
|
10
|
-
routes:
|
|
11
|
-
/beta:
|
|
12
|
-
match:
|
|
13
|
-
headers: {x-beta-user: "true"}
|
|
14
|
-
redirect: {url: https://beta.example.com}
|
|
15
|
-
/campaign:
|
|
16
|
-
conditional:
|
|
17
|
-
cases:
|
|
18
|
-
- match: {query: {source: newsletter}}
|
|
19
|
-
redirect: {url: https://example.com/newsletter}
|
|
20
|
-
- match: {query: {source: partner}}
|
|
21
|
-
redirect: {url: https://example.com/partner}
|
|
22
|
-
fallback:
|
|
23
|
-
respond: {text: "Choose a campaign"}
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Use `conditional.cases` for multiple definitions at one path; duplicate YAML keys
|
|
27
|
-
remain errors. Each case has a nonempty match and exactly one redirect/respond
|
|
28
|
-
handler. The optional fallback has one of those handlers and no condition. No
|
|
29
|
-
nested cases or case-local middleware/bindings/policies. Shared parameters,
|
|
30
|
-
methods, response headers, middleware and policies belong to the route. A missing
|
|
31
|
-
fallback returns 404. Up to 16 cases per route; pairs must be provably disjoint:
|
|
32
|
-
at least one shared field must require different values. Ambiguous cases fail
|
|
33
|
-
activation instead of relying on order. Cases are considered before fallback.
|
|
34
|
-
|
|
35
|
-
Conditions admit query/header/cookie maps with 1–16 entries, names up to 128
|
|
36
|
-
characters and values up to 1,024. Header names normalize to lowercase; duplicate
|
|
37
|
-
case-insensitive names fail validation. Query strings are decoded once, have no
|
|
38
|
-
type conversion or default substitution, and duplicate examined values return
|
|
39
|
-
400. Examined duplicate header/cookie values return 400 when transport counts
|
|
40
|
-
are available. Cookies compare unquoted wire values, without percent decoding;
|
|
41
|
-
the Cookie header is bounded to 8 KiB. Missing values do not match empty strings.
|
|
42
|
-
Standard authentication and transport headers cannot be predicates. There are no regex, numeric,
|
|
43
|
-
geography, device, wildcard or arbitrary-code predicates.
|
|
44
|
-
|
|
45
|
-
`match.host` compares the canonical authority of the **operator-configured public
|
|
46
|
-
origin**, not client Host or forwarded headers. Configure the origin to use host
|
|
47
|
-
conditions; one runtime does not infer multiple trusted public hosts. Methods are
|
|
48
|
-
uppercase existing HTTP method tokens. A top-level match is checked before the
|
|
49
|
-
route's method/handler execution; it may mask a method rejection with 404.
|
|
50
|
-
|
|
51
|
-
Conditional results force `Cache-Control: no-store`; provider-specific CDN and
|
|
52
|
-
surrogate cache directives cannot enable caching. Cache policies must be
|
|
53
|
-
disabled or use no-store. This prevents one header/cookie branch from populating
|
|
54
|
-
a shared origin or downstream cache. Route conditions and case coverage require
|
|
55
|
-
explicit request fixtures; generated probes do not imply branch coverage.
|
|
56
|
-
|
|
57
|
-
Self-hosted, AWS and Vercel execute the shared matcher. Node-adapter tests are
|
|
58
|
-
local, not provider-deployment evidence; provider header coalescing limits still
|
|
59
|
-
apply. Cloudflare refuses conditions until an artifact lowering and conformance
|
|
60
|
-
tests exist. `urlcode capabilities` reports these distinctions. Operator plugins
|
|
61
|
-
remain trusted host code; conditions add no guest authority.
|
|
62
|
-
|
|
63
|
-
The [executable conditions project](../examples/conditions) includes explicit
|
|
64
|
-
branch, fallback, duplicate and trusted-origin requests. Run:
|
|
65
|
-
|
|
66
|
-
```sh
|
|
67
|
-
urlcode test --project examples/conditions --origin https://conditions.example.test
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Host names are canonical ASCII authorities (maximum 255 characters), with an
|
|
71
|
-
optional nondefault port. IPv6 literals and IDN Unicode host spellings are not
|
|
72
|
-
part of this initial condition syntax. Raw query comparisons remain separate
|
|
73
|
-
from typed route inputs: a parameter default does not make an absent condition
|
|
74
|
-
match, while all shared parameter validation still runs before a case handler.
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# Container image promotion (design, issue #233)
|
|
2
|
-
|
|
3
|
-
Status: design plus an inert helper. Nothing described under "Proposed" is
|
|
4
|
-
implemented, and no workflow was changed. This does not authorize enabling
|
|
5
|
-
`PUBLISH_CONTAINER`, publishing, or changing any existing tag. The point is
|
|
6
|
-
supply-chain integrity (tested bytes are shipped bytes), not speed: the sampled
|
|
7
|
-
CI image build took about 12 seconds, so build caching is deliberately out of scope.
|
|
8
|
-
|
|
9
|
-
## Current behavior (read from the workflows at 67a6996)
|
|
10
|
-
|
|
11
|
-
- `ci.yml` job `container` (a required check) runs `docker build -t urlcode:test .`
|
|
12
|
-
and smoke-tests it. That image is discarded; no digest is recorded.
|
|
13
|
-
- `candidate.yml` builds and signs the npm archives, SBOM and manifests. It builds no
|
|
14
|
-
image. `manifest.json` and the exact-file-set check in `validateCandidate` know nothing
|
|
15
|
-
about images.
|
|
16
|
-
- `release.yml`, only when `vars.PUBLISH_CONTAINER == 'true'`, runs a fresh
|
|
17
|
-
`docker build` of the tagged commit with `revision`/`version` labels and pushes
|
|
18
|
-
`:VERSION` and the channel tag.
|
|
19
|
-
- On retry, if `:VERSION` already exists it is accepted after checking only the
|
|
20
|
-
`revision` label. A label is asserted by whoever built the image, so a differently
|
|
21
|
-
built image with the same label passes. Bytes are not compared.
|
|
22
|
-
- Channel monotonicity is enforced by reading the `version` label of the existing
|
|
23
|
-
channel tag (semver `gte`). That guard is sound and should be kept.
|
|
24
|
-
- A rebuild is not byte-reproducible (it runs `npm ci`, embeds timestamps), so the image
|
|
25
|
-
the release publishes is never the image CI tested, even for the same commit.
|
|
26
|
-
|
|
27
|
-
Not verified: live GHCR contents, package visibility/permissions, whether
|
|
28
|
-
`PUBLISH_CONTAINER` is set, and whether any image was ever pushed.
|
|
29
|
-
|
|
30
|
-
## Proposed path (not implemented)
|
|
31
|
-
|
|
32
|
-
1. Candidate build. In `candidate.yml`, build the image once from the exact commit
|
|
33
|
-
(`docker buildx build --output type=oci,dest=candidate/image.oci.tar` with the
|
|
34
|
-
revision/version labels), run the same three smoke tests as `ci.yml` against that
|
|
35
|
-
archive, and record `image: { digest, sourceCommit, version }` in `manifest.json`.
|
|
36
|
-
Add `image.oci.tar` to the attested subjects so the existing `attestation verify` and
|
|
37
|
-
the manifest SHA-256 pinned in the annotated tag cover the image as well.
|
|
38
|
-
2. Manifest schema. Add the archive to the expected file set in `validateCandidate`
|
|
39
|
-
and validate the record with `candidateImage()` from `scripts/release-image.ts`.
|
|
40
|
-
Because the tag pins the manifest hash, the digest is immutable once tagged.
|
|
41
|
-
3. Promotion. `release.yml` verifies the bundle (already done by `restore`), then copies
|
|
42
|
-
the archive to the registry preserving the manifest digest (`skopeo copy` or
|
|
43
|
-
`crane push` of the OCI layout; not `docker load` then `docker push`, which can
|
|
44
|
-
change digests under some storage drivers) and pushes `:VERSION` and the channel tag
|
|
45
|
-
as pointers to that digest. It never runs `docker build`.
|
|
46
|
-
4. Retry identity. If `:VERSION` exists, resolve its digest with
|
|
47
|
-
`docker buildx imagetools inspect` and require `assertPromotedImage()` to pass. A
|
|
48
|
-
rebuilt image with matching labels but different bytes is refused. Channel tags keep
|
|
49
|
-
the existing `assertChannel` check via `assertImageChannel()`.
|
|
50
|
-
5. Attestation. Optionally attest the image digest with `actions/attest` using
|
|
51
|
-
`push-to-registry`; this needs `packages: write` and its GHCR behavior is unverified.
|
|
52
|
-
6. Self-hosting. The `Dockerfile` and `docker build` remain the documented way to build
|
|
53
|
-
from source. CI's `container` check stays as is to preserve the required check name.
|
|
54
|
-
|
|
55
|
-
## What the inert helper proves
|
|
56
|
-
|
|
57
|
-
`scripts/release-image.ts` and `test/release-image.test.ts` are pure functions and unit
|
|
58
|
-
tests. They pin the invariant (promoted digest equals tested digest; labels match;
|
|
59
|
-
channel never regresses; a same-label different-bytes image is rejected). No workflow
|
|
60
|
-
imports them, so the release path is unchanged.
|
|
61
|
-
|
|
62
|
-
## Could not establish
|
|
63
|
-
|
|
64
|
-
- Whether GHCR preserves the OCI digest through `skopeo`/`crane` from the runner, and
|
|
65
|
-
whether the org's package settings permit it.
|
|
66
|
-
- Behavior of image attestation and OIDC for GHCR.
|
|
67
|
-
- Whether a candidate-built OCI archive fits comfortably within the artifact size and
|
|
68
|
-
90-day retention model; recovery from GitHub release assets would need the archive
|
|
69
|
-
attached there too.
|
|
70
|
-
- Any real run: none was dispatched.
|
|
71
|
-
|
|
72
|
-
A first implementation should be a separate reviewed PR that lands the candidate-side
|
|
73
|
-
build behind an operator-controlled input, and leaves the publisher change until one
|
|
74
|
-
candidate has been produced and inspected.
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Verify a running deployment against the project
|
|
2
|
-
|
|
3
|
-
`urlcode verify-deployment` answers one question: does the deployment behind
|
|
4
|
-
`--target` behave the way this project declares? It starts the local snapshot
|
|
5
|
-
(as `validate` does), sends a bounded set of HTTP requests to the target and
|
|
6
|
-
compares what came back with what the snapshot says should come back. It reads
|
|
7
|
-
responses only. It has no infrastructure access, uses no credential, follows no
|
|
8
|
-
redirect and offers no `--insecure`.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
urlcode verify-deployment --project ../my-links --target https://links.example
|
|
12
|
-
urlcode verify-deployment --project ../my-links --target https://links.example \
|
|
13
|
-
--expect-routes 12 --compliance baseline --fail-on medium --timeout-ms 5000
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Options: `--origin` (the public origin the local snapshot generates absolute
|
|
17
|
-
URLs for; defaults to the target), `--expect-routes N`, `--expect-metrics`
|
|
18
|
-
(the operator chose to expose `/_urlcode/metrics` publicly), `--timeout-ms`
|
|
19
|
-
(per request, 100-120000, default 10000), `--fail-on high|medium|low|info|none`
|
|
20
|
-
(default `high`), the `audit` compliance flags (`--compliance`,
|
|
21
|
-
`--compliance-rules`, `--compliance-ignore`, `--compliance-warn`), and the
|
|
22
|
-
usual `--policy` binding the local snapshot needs to start.
|
|
23
|
-
|
|
24
|
-
## What it verifies
|
|
25
|
-
|
|
26
|
-
Every check yields findings `{check, severity, route?, message, expected?,
|
|
27
|
-
observed?}`. The report carries `checks` (assertions evaluated), `requests`
|
|
28
|
-
(HTTP requests sent), `findings`, `counts` by severity, `notes` (what was
|
|
29
|
-
deliberately not verified, such as compression delegated to a platform) and
|
|
30
|
-
`pass`.
|
|
31
|
-
|
|
32
|
-
| Check | What is compared | Severity |
|
|
33
|
-
|---|---|---|
|
|
34
|
-
| `probes` | `/_urlcode/health` and `/_urlcode/ready` answer 200 with `{status, version, routes}`; the deployed `version` equals the local snapshot's; the deployed route count equals the local count and `--expect-routes`; `/_urlcode/metrics` answers 404 unless `--expect-metrics` | high (metrics expected but absent: medium) |
|
|
35
|
-
| `fixtures` | `tests/requests.json` and the generated native cases, sent exactly as `urlcode test --target` would: status, expected headers and expected body | high |
|
|
36
|
-
| `security` | Per active literal route: every header of the effective security profile (`oshp`, `oshp-no-csp`, `set`, `unset`) with its exact value; a YAML `response.headers` value wins on a success, as it does in the runtime | high |
|
|
37
|
-
| `cache` | `Cache-Control` (and `CDN-Cache-Control`) equal the strategy's emitted value; skipped where YAML or an asset handler owns the header and on function routes, whose handlers may answer `private` | medium |
|
|
38
|
-
| `compression` | A second request with the declared `Accept-Encoding` on a response that is compressible (type, size at or above `minBytes`, no `no-transform`, no cookie, no secrets) carries `Content-Encoding`; on a delegated target it is noted, not checked | medium |
|
|
39
|
-
| `agents` | A `User-Agent` from the denied bundled list answers the configured status on every route with an enforcing agents policy; project list files and bare patterns are not probed | high |
|
|
40
|
-
| `throttle` | `RateLimit-Policy` carries the declared quota and window where the throttle is native | medium |
|
|
41
|
-
| `site` | Generated `robots.txt`, `sitemap.xml`, `favicon`, `security.txt` and `llms.txt` answer 200 with the expected content type; `robots.txt` and `security.txt` bodies equal the generated file (a `Sitemap:` line missing means the deployment was started without `--origin`) | medium |
|
|
42
|
-
| `methods` | A route that does not declare GET answers GET with 405 and `Allow`; a route with explicit `methods` answers OPTIONS with 405 and `Allow` | medium |
|
|
43
|
-
| `head` | On a `respond` route, HEAD answers 200 with GET's `Content-Length` and no body | medium |
|
|
44
|
-
| `errors` | An unmatched path (`/_urlcode-verify-<random>`) answers the runtime's 404 (`nosniff`, `no-store`) with the project-level security headers, which is also how a CDN error page or a different application shows itself | high |
|
|
45
|
-
| `transport` | On an https target, `Strict-Transport-Security` is present wherever the profile emits it (the deployment must have been started with `--origin https://...`); a certificate Node rejects, a refused connection or a timeout on the health probe ends the run; a redirect whose destination is plain `http:` is reported | high (http: destination: low) |
|
|
46
|
-
|
|
47
|
-
Bounds: fixtures plus generated cases, one to four requests per active literal
|
|
48
|
-
route (GET, then HEAD, OPTIONS, an encoded GET or a denied User-Agent where the
|
|
49
|
-
route calls for it), four probes. Concurrency is four for the route checks;
|
|
50
|
-
fixtures run one at a time, as `test` does. A run refuses to start above 10000
|
|
51
|
-
requests. Bodies are read up to 1 MiB and appear in the report only as the
|
|
52
|
-
first 200 bytes of a failing assertion. Requests count against the
|
|
53
|
-
deployment's throttle quotas: a quota smaller than the run turns later probes
|
|
54
|
-
into refusals, which the report shows as fixture and header findings.
|
|
55
|
-
|
|
56
|
-
## What it cannot verify
|
|
57
|
-
|
|
58
|
-
- Anything not visible in a response: process settings, worker counts,
|
|
59
|
-
request logging, binding files, the proxy or CDN configuration, certificates
|
|
60
|
-
beyond Node's default chain and host-name check, DNS.
|
|
61
|
-
- Routes with parameters, middleware or required inputs, except through the
|
|
62
|
-
fixtures the project supplies; the generated cases never invent business
|
|
63
|
-
data. Function bodies are verified only where a fixture asserts them.
|
|
64
|
-
- Agents policies built from project list files or bare patterns, compression
|
|
65
|
-
delegated to a platform, throttle refusals (the run never exhausts a quota
|
|
66
|
-
on purpose) and cache hits (the origin cache is invisible from outside).
|
|
67
|
-
- That the deployment stays this way: the report is a snapshot of one run.
|
|
68
|
-
|
|
69
|
-
A passing report says the target answered like the local snapshot during the
|
|
70
|
-
run. It is not a security assessment, a load test or a certification.
|
|
71
|
-
|
|
72
|
-
## Exit codes
|
|
73
|
-
|
|
74
|
-
`0` when no finding is at or above `--fail-on` (default `high`) and, when
|
|
75
|
-
`--compliance` was given, the compliance report passes or `--compliance-warn`
|
|
76
|
-
was set. `1` otherwise, and for an unreachable target, a rejected certificate,
|
|
77
|
-
a target that is not a bare HTTP(S) origin, or invalid options. The report is
|
|
78
|
-
printed as one JSON line after one `{"event":"finding",...}` line per finding.
|
|
79
|
-
|
|
80
|
-
## How it complements audit, compliance and benchmark
|
|
81
|
-
|
|
82
|
-
| Command | Runs against | Answers |
|
|
83
|
-
|---|---|---|
|
|
84
|
-
| `urlcode audit` | a local snapshot | are every route and method covered by passing checks, and do the counts reconcile |
|
|
85
|
-
| `urlcode audit --compliance` | declared configuration | does the configuration meet the rule set |
|
|
86
|
-
| `urlcode benchmark --target` | the deployment | how fast does it answer the workload |
|
|
87
|
-
| `urlcode verify-deployment` | the deployment | does it answer the way the project declares |
|
|
88
|
-
|
|
89
|
-
`audit` proves the project; `verify-deployment` proves the deployment is that
|
|
90
|
-
project. Neither replaces the [operational drills](OPERATIONAL-PROOF.md).
|
|
91
|
-
|
|
92
|
-
## A rollout gate
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
set -e
|
|
96
|
-
urlcode audit --project . --expect-routes 12 --compliance baseline
|
|
97
|
-
# deploy the candidate to the staging origin, then:
|
|
98
|
-
urlcode verify-deployment --project . --target https://staging.links.example \
|
|
99
|
-
--expect-routes 12 --fail-on medium
|
|
100
|
-
urlcode benchmark --project . --target https://staging.links.example --requests 500 --max-p95-ms 50
|
|
101
|
-
# switch traffic, then verify the production origin the same way:
|
|
102
|
-
urlcode verify-deployment --project . --target https://links.example --expect-routes 12
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
A version mismatch after the switch means traffic reaches a different
|
|
106
|
-
snapshot: an old instance still serving, a cache in front of the origin, or a
|
|
107
|
-
project revision that was never deployed. Roll back per
|
|
108
|
-
[Operations](OPERATIONS.md#deployment-and-rollback-procedure).
|