@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/urlcode-authoring/SKILL.md +106 -0
- package/.claude/skills/urlcode-operations/SKILL.md +114 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/CONTRIBUTING.md +30 -2
- package/README.md +157 -230
- package/ROADMAP.md +85 -2
- package/SECURITY.md +8 -1
- package/dist/BUILD-MANIFEST.json +62 -32
- package/dist/adapters.js +4 -23
- package/dist/agent-lists.js +1 -1
- package/dist/agents-guide.js +113 -0
- package/dist/authoring-files.js +60 -0
- package/dist/authoring.js +11 -1
- package/dist/aws.js +4 -3
- package/dist/build-cloudflare.js +11 -24
- package/dist/bulk.js +37 -0
- package/dist/capabilities.js +208 -0
- package/dist/capability-query.js +72 -0
- package/dist/catalog.js +105 -0
- package/dist/cli.js +154 -15
- package/dist/client-address.js +1 -1
- package/dist/compliance-rules/baseline.js +8 -8
- package/dist/compliance-rules/privacy.js +3 -3
- package/dist/compliance-rules/strict.js +5 -5
- package/dist/conditions.js +88 -0
- package/dist/config.js +60 -4
- package/dist/context.js +156 -0
- package/dist/ecosystem-cli.js +88 -0
- package/dist/egress.js +98 -0
- package/dist/examples.js +92 -0
- package/dist/explain-cli.js +64 -0
- package/dist/explain.js +132 -0
- package/dist/extensions.js +172 -0
- package/dist/function-sources.js +15 -3
- package/dist/index.js +37 -0
- package/dist/init-with.js +165 -0
- package/dist/interchange-cli.js +42 -0
- package/dist/interchange.js +189 -0
- package/dist/link-cli.js +1 -1
- package/dist/management-policy.js +0 -1
- package/dist/manifest.js +111 -0
- package/dist/match.js +2 -2
- package/dist/mcp-authoring.js +147 -0
- package/dist/mcp.js +97 -0
- package/dist/observability.js +6 -0
- package/dist/operator-host.js +29 -0
- package/dist/plugins.js +12 -0
- package/dist/policies/agents.js +2 -2
- package/dist/policies/compression.js +2 -1
- package/dist/policies/security.js +0 -0
- package/dist/policies.js +1 -1
- package/dist/policy.js +29 -7
- package/dist/prerender.js +100 -41
- package/dist/project-tests.js +3 -3
- package/dist/provider-verification.js +92 -0
- package/dist/proxy.js +44 -0
- package/dist/readiness.js +5 -5
- package/dist/recipes.js +41 -0
- package/dist/route-diff.js +106 -0
- package/dist/router.js +42 -2
- package/dist/runtime.js +95 -14
- package/dist/schema-query.js +62 -0
- package/dist/signals.js +24 -0
- package/dist/site.js +0 -0
- package/dist/tooling.js +96 -0
- package/dist/types/adapters.d.ts +7 -4
- package/dist/types/agent-lists.d.ts +0 -1
- package/dist/types/agents-guide.d.ts +17 -0
- package/dist/types/authoring-files.d.ts +10 -0
- package/dist/types/aws.d.ts +3 -1
- package/dist/types/build-cloudflare.d.ts +1 -0
- package/dist/types/bulk.d.ts +27 -0
- package/dist/types/capabilities.d.ts +55 -0
- package/dist/types/capability-query.d.ts +24 -0
- package/dist/types/catalog.d.ts +65 -0
- package/dist/types/client-address.d.ts +0 -1
- package/dist/types/compliance-rules/baseline.d.ts +1 -9
- package/dist/types/compliance-rules/privacy.d.ts +1 -4
- package/dist/types/compliance-rules/strict.d.ts +0 -5
- package/dist/types/conditions.d.ts +19 -0
- package/dist/types/config.d.ts +20 -2
- package/dist/types/context.d.ts +65 -0
- package/dist/types/ecosystem-cli.d.ts +17 -0
- package/dist/types/egress.d.ts +46 -0
- package/dist/types/examples.d.ts +50 -0
- package/dist/types/explain-cli.d.ts +11 -0
- package/dist/types/explain.d.ts +95 -0
- package/dist/types/extensions.d.ts +122 -0
- package/dist/types/function-sources.d.ts +5 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/init-with.d.ts +30 -0
- package/dist/types/interchange-cli.d.ts +16 -0
- package/dist/types/interchange.d.ts +42 -0
- package/dist/types/link-cli.d.ts +2 -1
- package/dist/types/management-policy.d.ts +0 -1
- package/dist/types/manifest.d.ts +81 -0
- package/dist/types/match.d.ts +1 -0
- package/dist/types/mcp-authoring.d.ts +92 -0
- package/dist/types/mcp.d.ts +12 -0
- package/dist/types/observability.d.ts +2 -0
- package/dist/types/operator-host.d.ts +8 -0
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/policies/agents.d.ts +0 -2
- package/dist/types/policies/compression.d.ts +2 -0
- package/dist/types/policies/security.d.ts +0 -1
- package/dist/types/policy.d.ts +15 -4
- package/dist/types/project-tests.d.ts +3 -1
- package/dist/types/provider-verification.d.ts +53 -0
- package/dist/types/proxy.d.ts +21 -0
- package/dist/types/readiness.d.ts +1 -1
- package/dist/types/recipes.d.ts +30 -0
- package/dist/types/route-diff.d.ts +27 -0
- package/dist/types/runtime.d.ts +11 -0
- package/dist/types/schema-query.d.ts +12 -0
- package/dist/types/signals.d.ts +25 -0
- package/dist/types/site.d.ts +0 -1
- package/dist/types/tooling.d.ts +115 -0
- package/dist/types/types.d.ts +57 -0
- package/dist/types/typescript-authoring.d.ts +12 -0
- package/dist/types/vercel.d.ts +3 -1
- package/dist/types/verify-deployment.d.ts +47 -0
- package/dist/types.js +21 -2
- package/dist/typescript-authoring.js +104 -0
- package/dist/vercel.js +4 -3
- package/dist/verify-deployment.js +270 -0
- package/docs/AI-AUTHORING.md +130 -8
- package/docs/BULK.md +79 -0
- package/docs/CAPABILITIES.md +179 -0
- package/docs/CAPACITY.md +1 -1
- package/docs/CI.md +142 -0
- package/docs/CONDITIONS.md +74 -0
- package/docs/DEPLOYMENT-CHECKS.md +108 -0
- package/docs/DYNAMIC-LINKS.md +18 -518
- package/docs/EGRESS.md +125 -0
- package/docs/EXTENSIONS.md +226 -0
- package/docs/FRAMEWORK.md +182 -0
- package/docs/INSTALL.md +45 -7
- package/docs/INTERCHANGE.md +134 -0
- package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
- package/docs/MIDDLEWARE.md +2 -0
- package/docs/NEXT-PHASE-PLAN.md +90 -0
- package/docs/NEXT-STEPS.md +415 -0
- package/docs/OBSERVABILITY.md +4 -2
- package/docs/OPERATIONAL-PROOF.md +4 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/PLUGINS.md +37 -0
- package/docs/POLICIES.md +12 -309
- package/docs/PRERENDER.md +40 -0
- package/docs/PROJECT-DIRECTION.md +42 -0
- package/docs/PROVIDER-VERIFICATION.md +84 -0
- package/docs/READINESS.md +21 -1
- package/docs/README.md +82 -31
- package/docs/RECIPES.md +99 -0
- package/docs/RELEASE-READINESS.md +11 -9
- package/docs/RELEASE-SECURITY.md +27 -4
- package/docs/SECURITY-AUDIT.md +1 -1
- package/docs/SPECIFICATION.md +95 -8
- package/docs/SPIKE-BUSINESS-SUITE.md +1013 -0
- package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
- package/docs/SPIKE-EXTENSIONS.md +1 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +199 -0
- package/docs/STANDARDS.md +150 -142
- package/docs/STARTERS.md +21 -1
- package/docs/TOOLING.md +291 -0
- package/docs/TYPESCRIPT-AUTHORING.md +67 -0
- package/docs/TYPESCRIPT.md +1 -1
- package/docs/USABILITY-REVIEW.md +123 -0
- package/docs/YAML-GUIDE.md +18 -479
- package/docs/YAML-REFERENCE.md +127 -16
- package/docs/links/cli.md +110 -0
- package/docs/links/limits.md +175 -0
- package/docs/links/management-api.md +80 -0
- package/docs/links/pools.md +75 -0
- package/docs/links/setup.md +135 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/contract.md +52 -0
- package/docs/policies/hardened.md +56 -0
- package/docs/policies/interoperability.md +169 -0
- package/docs/policies/operations.md +45 -0
- package/docs/yaml/assets.md +36 -0
- package/docs/yaml/conditions.md +20 -0
- package/docs/yaml/functions.md +160 -0
- package/docs/yaml/links.md +30 -0
- package/docs/yaml/middleware.md +29 -0
- package/docs/yaml/organization.md +74 -0
- package/docs/yaml/policies.md +37 -0
- package/docs/yaml/redirects.md +64 -0
- package/docs/yaml/responses.md +57 -0
- package/docs/yaml/site.md +24 -0
- package/examples/assets/example.yaml +17 -0
- package/examples/aws/example.yaml +20 -0
- package/examples/cloudflare/example.yaml +19 -0
- package/examples/compliance/example.yaml +11 -0
- package/examples/conditions/README.md +12 -0
- package/examples/conditions/example.yaml +19 -0
- package/examples/conditions/tests/requests.json +13 -0
- package/examples/conditions/urlcode.yaml +24 -0
- package/examples/cookbook/README.md +8 -4
- package/examples/cookbook/example.yaml +17 -0
- package/examples/cookbook/functions/catalog.mjs +3 -0
- package/examples/cookbook/functions/fail.mjs +4 -0
- package/examples/cookbook/functions/items.mjs +3 -0
- package/examples/cookbook/functions/profile.mjs +3 -0
- package/examples/cookbook/functions/resource.mjs +3 -0
- package/examples/cookbook/functions/status.mjs +3 -0
- package/examples/cookbook/middleware/auth.mjs +48 -0
- package/examples/cookbook/middleware/body.mjs +15 -0
- package/examples/cookbook/middleware/bucket.mjs +19 -0
- package/examples/cookbook/middleware/cors.mjs +21 -0
- package/examples/cookbook/middleware/debug.mjs +13 -0
- package/examples/cookbook/middleware/envelope.mjs +11 -0
- package/examples/cookbook/middleware/errors.mjs +11 -0
- package/examples/cookbook/middleware/etag.mjs +18 -0
- package/examples/cookbook/middleware/locale.mjs +16 -0
- package/examples/cookbook/middleware/maintenance.mjs +10 -0
- package/examples/cookbook/middleware/methods.mjs +15 -0
- package/examples/cookbook/middleware/negotiate.mjs +20 -0
- package/examples/cookbook/middleware/referer.mjs +12 -0
- package/examples/cookbook/middleware/request-id.mjs +16 -0
- package/examples/cookbook/route-index.json +676 -0
- package/examples/cookbook/routes/middleware.yaml +126 -0
- package/examples/cookbook/tests/requests.json +526 -0
- package/examples/cookbook/urlcode.yaml +1 -0
- package/examples/egress/README.md +22 -0
- package/examples/egress/example.yaml +19 -0
- package/examples/egress/urlcode.yaml +19 -0
- package/examples/extensions/README.md +7 -0
- package/examples/extensions/example.yaml +21 -0
- package/examples/extensions/urlcode.yaml +25 -0
- package/examples/live-links/example.yaml +21 -0
- package/examples/monitoring/example.yaml +8 -0
- package/examples/prerender/example.yaml +16 -0
- package/examples/provider-conformance/README.md +12 -0
- package/examples/provider-conformance/example.yaml +14 -0
- package/examples/provider-conformance/urlcode.yaml +34 -0
- package/examples/tunnel/example.yaml +8 -0
- package/examples/vercel/example.yaml +19 -0
- package/llms-full.txt +2709 -0
- package/llms.txt +48 -19
- package/package.json +29 -7
- package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +106 -0
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +114 -0
- package/recipes/authenticated-json-api/README.md +51 -0
- package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
- package/recipes/authenticated-json-api/recipe.yaml +34 -0
- package/recipes/authenticated-json-api/tests/requests.json +39 -0
- package/recipes/authenticated-json-api/urlcode.yaml +12 -0
- package/recipes/contact-form/README.md +25 -0
- package/recipes/contact-form/functions/contact.mjs +17 -0
- package/recipes/contact-form/recipe.yaml +33 -0
- package/recipes/contact-form/tests/requests.json +47 -0
- package/recipes/contact-form/urlcode.yaml +18 -0
- package/recipes/cors-api/README.md +16 -0
- package/recipes/cors-api/functions/items.mjs +3 -0
- package/recipes/cors-api/middleware/cors.mjs +21 -0
- package/recipes/cors-api/recipe.yaml +26 -0
- package/recipes/cors-api/tests/requests.json +65 -0
- package/recipes/cors-api/urlcode.yaml +12 -0
- package/recipes/health-page/README.md +13 -0
- package/recipes/health-page/recipe.yaml +23 -0
- package/recipes/health-page/tests/requests.json +36 -0
- package/recipes/health-page/urlcode.yaml +19 -0
- package/recipes/json-api/README.md +6 -0
- package/recipes/json-api/functions/echo.mjs +3 -0
- package/recipes/json-api/recipe.yaml +25 -0
- package/recipes/json-api/tests/requests.json +34 -0
- package/recipes/json-api/urlcode.yaml +12 -0
- package/recipes/middleware/README.md +34 -0
- package/recipes/middleware/functions/catalog.mjs +3 -0
- package/recipes/middleware/functions/fail.mjs +4 -0
- package/recipes/middleware/functions/items.mjs +3 -0
- package/recipes/middleware/functions/profile.mjs +3 -0
- package/recipes/middleware/functions/resource.mjs +3 -0
- package/recipes/middleware/functions/status.mjs +3 -0
- package/recipes/middleware/middleware/auth.mjs +48 -0
- package/recipes/middleware/middleware/body.mjs +15 -0
- package/recipes/middleware/middleware/bucket.mjs +19 -0
- package/recipes/middleware/middleware/cors.mjs +21 -0
- package/recipes/middleware/middleware/debug.mjs +13 -0
- package/recipes/middleware/middleware/envelope.mjs +11 -0
- package/recipes/middleware/middleware/errors.mjs +11 -0
- package/recipes/middleware/middleware/etag.mjs +18 -0
- package/recipes/middleware/middleware/locale.mjs +16 -0
- package/recipes/middleware/middleware/maintenance.mjs +10 -0
- package/recipes/middleware/middleware/methods.mjs +15 -0
- package/recipes/middleware/middleware/negotiate.mjs +20 -0
- package/recipes/middleware/middleware/referer.mjs +12 -0
- package/recipes/middleware/middleware/request-id.mjs +16 -0
- package/recipes/middleware/public/guide.txt +1 -0
- package/recipes/middleware/recipe.yaml +50 -0
- package/recipes/middleware/tests/requests.json +528 -0
- package/recipes/middleware/urlcode.yaml +127 -0
- package/recipes/protected-download/README.md +22 -0
- package/recipes/protected-download/files/report.txt +1 -0
- package/recipes/protected-download/recipe.yaml +31 -0
- package/recipes/protected-download/tests/requests.json +32 -0
- package/recipes/protected-download/urlcode.yaml +15 -0
- package/recipes/redirect/README.md +7 -0
- package/recipes/redirect/recipe.yaml +25 -0
- package/recipes/redirect/tests/requests.json +19 -0
- package/recipes/redirect/urlcode.yaml +9 -0
- package/recipes/static-plus-api/README.md +15 -0
- package/recipes/static-plus-api/functions/info.mjs +3 -0
- package/recipes/static-plus-api/public/assets/index.html +3 -0
- package/recipes/static-plus-api/public/assets/site.css +1 -0
- package/recipes/static-plus-api/public/index.html +8 -0
- package/recipes/static-plus-api/recipe.yaml +29 -0
- package/recipes/static-plus-api/tests/requests.json +56 -0
- package/recipes/static-plus-api/urlcode.yaml +23 -0
- package/recipes/typescript/README.md +7 -0
- package/recipes/typescript/functions/hello.ts +5 -0
- package/recipes/typescript/recipe.yaml +23 -0
- package/recipes/typescript/tests/requests.json +18 -0
- package/recipes/typescript/urlcode.yaml +5 -0
- package/recipes/webhook-receiver/README.md +16 -0
- package/recipes/webhook-receiver/functions/receive.mjs +16 -0
- package/recipes/webhook-receiver/recipe.yaml +26 -0
- package/recipes/webhook-receiver/tests/requests.json +59 -0
- package/recipes/webhook-receiver/urlcode.yaml +16 -0
- package/schemas/recipe.schema.json +138 -0
- package/schemas/urlcode.schema.json +656 -80
- package/skills/urlcode/SKILL.md +98 -0
- package/starters/default/.github/workflows/urlcode.yml +23 -0
- package/starters/default/.mcp.json +12 -0
- package/starters/default/AGENTS.md +79 -0
package/docs/BULK.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Bulk redirect projects
|
|
2
|
+
|
|
3
|
+
Bulk import converts CSV, JSON or YAML redirect rows into an ordinary Git-owned
|
|
4
|
+
URLCode project. It delegates semantics and row diagnostics to the strict
|
|
5
|
+
[redirect interchange](INTERCHANGE.md) converter. Only literal paths and
|
|
6
|
+
absolute HTTP(S) redirect destinations are accepted; unsupported behavior and
|
|
7
|
+
duplicate paths fail rather than being discarded or overwritten.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
urlcode bulk-import csv redirects.csv --out ./imported --dry-run
|
|
11
|
+
urlcode bulk-import csv redirects.csv --out ./imported
|
|
12
|
+
urlcode validate --local --project ./imported
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
CSV requires the header `path,url,status`; status can be empty for 302. JSON and
|
|
16
|
+
YAML are arrays of `{path, url, status?}` rows. Input is limited to 32 MiB and
|
|
17
|
+
100,000 routes. This command is for data import, not arbitrary provider config.
|
|
18
|
+
Use explicit provider interchange commands when migrating provider files.
|
|
19
|
+
|
|
20
|
+
The SDK provides
|
|
21
|
+
`importBulkProject(text, format, output, {dryRun, source})`. `format` is `csv`,
|
|
22
|
+
`json` or `yaml`. The report includes `ok`, `routeCount`, source/row diagnostics,
|
|
23
|
+
a SHA-256 fingerprint of the exact input text, and a bounded output-file plan.
|
|
24
|
+
Invalid input returns `ok: false` without writing files. Filesystem/output
|
|
25
|
+
refusals throw. `source` is a caller-supplied provenance label, not a file to read.
|
|
26
|
+
|
|
27
|
+
Routes are sorted by literal path and divided into include files with at most
|
|
28
|
+
1,000 routes each. At 100,000 routes this produces 100 include files. The entry
|
|
29
|
+
`urlcode.yaml` holds the include list and empty `routes`. `provenance.json`
|
|
30
|
+
records the source label, input fingerprint, counts and each shard's first/last
|
|
31
|
+
path. It does not embed the original input or claim exact source line mappings
|
|
32
|
+
for successful output rows; retain the original file in version control if that
|
|
33
|
+
traceability is needed. Invalid row diagnostics preserve their source positions.
|
|
34
|
+
|
|
35
|
+
Dry-run performs conversion and output planning without writing the requested
|
|
36
|
+
destination. Import requires a new output directory under an existing parent;
|
|
37
|
+
there is no implicit merge or overwrite mode. As with recipes, dependencies are
|
|
38
|
+
written first and a rename publishes the completed entry YAML last. Failed
|
|
39
|
+
writes clean up the directory created by that invocation. Source data is never
|
|
40
|
+
interpreted as shell code. The caller must control the output parent while
|
|
41
|
+
publishing.
|
|
42
|
+
|
|
43
|
+
## Local scale evidence
|
|
44
|
+
|
|
45
|
+
Run each dataset in a fresh process, sequentially, without concurrent builds:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
node benchmarks/bulk.ts 1000
|
|
49
|
+
node benchmarks/bulk.ts 10000
|
|
50
|
+
node benchmarks/bulk.ts 100000
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Observed on 2026-09-17 against the final integrated next-phase working tree,
|
|
54
|
+
with the merged TypeScript 6.0.3 dependency lock, Node v26.8.2, macOS arm64,
|
|
55
|
+
Apple M4 Pro and 48 GiB system memory. These measurements include the final
|
|
56
|
+
capability, revision-pin and egress activation paths. Synthetic routes redirect `/rN` to `https://example.com/items/N`.
|
|
57
|
+
Conversion includes input validation, semantic compilation and sharded output
|
|
58
|
+
publication; activation uses the normal `createRuntime` loader and compiler.
|
|
59
|
+
Lookup measures 5,000 `Runtime.handle` requests at concurrency one after 100
|
|
60
|
+
warm-up requests, validating each status and Location. It excludes socket/TLS
|
|
61
|
+
transport. Memory is sampled process RSS/heap after phases, not peak memory or
|
|
62
|
+
an isolated worker measurement.
|
|
63
|
+
|
|
64
|
+
| Routes | Includes | Conversion ms | Activation ms | RSS after activation MiB | Heap MiB | Lookup p95 ms |
|
|
65
|
+
| ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
66
|
+
| 1,000 | 1 | 56 | 139 | 180 | 27 | 0.002292 |
|
|
67
|
+
| 10,000 | 10 | 272 | 373 | 288 | 46 | 0.002916 |
|
|
68
|
+
| 100,000 | 100 | 5,530 | 8,838 | 537 | 214 | 0.003791 |
|
|
69
|
+
|
|
70
|
+
All three sequential fresh-process datasets activated and returned the expected
|
|
71
|
+
redirects. These are single observations, not a repeated-run statistical study;
|
|
72
|
+
background host load can change startup and memory measurements. Unlike the
|
|
73
|
+
previous single-document 100,000-route routing benchmark, the sharded project
|
|
74
|
+
fits the existing configuration worker's 256 MiB heap and 10-second deadline.
|
|
75
|
+
No loader, route-count, compiler deadline or runtime safety limit was increased.
|
|
76
|
+
This local result does not establish cross-platform capacity, provider
|
|
77
|
+
performance, peak-memory bounds, concurrency/soak behavior or production SLOs.
|
|
78
|
+
The benchmark reports failure phase and error if a limit is reached on another
|
|
79
|
+
machine; it does not retry with relaxed limits.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Capabilities and normalized route representation
|
|
2
|
+
|
|
3
|
+
URLCode is a portable runtime for programmable URL behavior: **URL behavior as
|
|
4
|
+
code**. YAML describes behavior; operators supply infrastructure and authority.
|
|
5
|
+
|
|
6
|
+
## Inspect target support
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
urlcode capabilities
|
|
10
|
+
urlcode capabilities --target self-hosted
|
|
11
|
+
urlcode capabilities --target cloudflare --json
|
|
12
|
+
urlcode capabilities --target aws
|
|
13
|
+
urlcode capabilities --target vercel
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This command needs no project or credentials. `node` is an alias for
|
|
17
|
+
`self-hosted`, matching the existing embedding API. Unknown target names fail.
|
|
18
|
+
JSON has `format: 1`, target deployment evidence and capability rows.
|
|
19
|
+
`doctor` also reports `capabilityTargets`; its `providers` list remains empty
|
|
20
|
+
because no provider deployment has been verified. Canonical
|
|
21
|
+
names follow the schema (`respond`, `link`, `policies.security`), not marketing
|
|
22
|
+
synonyms. `proxy` and `signals` are self-hosted capabilities requiring external
|
|
23
|
+
revision-pinned origin grants. `conditions` (`match`) and `conditional` (disjoint
|
|
24
|
+
cases) are supported by self-hosted/AWS/Vercel and refused by Cloudflare until
|
|
25
|
+
artifact lowering exists. See [egress](EGRESS.md) and [conditions](CONDITIONS.md).
|
|
26
|
+
|
|
27
|
+
| Support | Meaning |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| native | Implemented by the local runtime or Node adapter |
|
|
30
|
+
| compiled | Implemented by the Cloudflare compiler and artifact runtime |
|
|
31
|
+
| conditional | Depends on configuration; inspect the actual project |
|
|
32
|
+
| delegated | Existing policy contract relies on provider behavior |
|
|
33
|
+
| refused | No implementation that this target can activate |
|
|
34
|
+
| unknown | No support evidence; fail closed during project analysis |
|
|
35
|
+
|
|
36
|
+
`native` and `compiled` describe local implementation tests. AWS, Vercel and
|
|
37
|
+
Cloudflare deployment evidence remains **unverified**. This is not a blanket
|
|
38
|
+
exact-portability promise. Cloudflare coalesces duplicate headers and receives a
|
|
39
|
+
normalized Request target; AWS accepts payload v2 only. See [Cloudflare](CLOUDFLARE.md),
|
|
40
|
+
[AWS](AWS.md) and [Vercel](VERCEL.md) for transport limits. Compression is
|
|
41
|
+
explicitly delegated, not verified equivalent to operator-selected settings.
|
|
42
|
+
Route throttle counters and caches remain per instance. No supported entry
|
|
43
|
+
bypasses semantic validation, required operator grants or deployment prerequisites.
|
|
44
|
+
|
|
45
|
+
## One capability or one schema fragment
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
urlcode capabilities redirect
|
|
49
|
+
urlcode capabilities policies.throttle --json
|
|
50
|
+
urlcode schema route
|
|
51
|
+
urlcode schema policies.cache --json
|
|
52
|
+
urlcode schema site.sitemap --yaml
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`urlcode capabilities <name>` prints one catalog entry: its kind (handler,
|
|
56
|
+
policy, routing, request, binding, egress, middleware or project), a summary,
|
|
57
|
+
the resolved schema fragment(s), constraints, the operator grants the capability
|
|
58
|
+
needs at activation, support per target, the targets that refuse it, and the
|
|
59
|
+
bundled recipes and cookbook routes that use it. Names are the catalog names
|
|
60
|
+
(`redirect`, `bindings`, `policies.cache`); an unknown name fails with exit 1
|
|
61
|
+
and lists the valid names. `--target` applies to the full catalog only.
|
|
62
|
+
|
|
63
|
+
`urlcode schema <path>` prints only that fragment of
|
|
64
|
+
`schemas/urlcode.schema.json` with local `$ref`s resolved inline. Paths are
|
|
65
|
+
top-level document keys (`routes`, `policies`, `site`, `extensions`), `route`,
|
|
66
|
+
or a route property (`redirect`, `middleware`, `match`, `env`), optionally
|
|
67
|
+
followed by nested property names (`policies.cache`, `request.body`,
|
|
68
|
+
`site.sitemap`). Resolution is bounded and cycle-safe; where a nested object is
|
|
69
|
+
its own path (`route` inside `routes`, `policies` inside `route`) it is
|
|
70
|
+
summarized with a `$comment` naming that path so every fragment stays under
|
|
71
|
+
16 KiB. Fragments describe shape only: they carry no defaults, validation
|
|
72
|
+
result or operator authority, and the full schema remains the contract.
|
|
73
|
+
|
|
74
|
+
Both commands read bundled package data and need no project, credentials or
|
|
75
|
+
network. The SDK exposes them as `getCapability(name)` and
|
|
76
|
+
`getSchemaFragment(path)`; the MCP server as `get_capability` and `get_schema`
|
|
77
|
+
(see [tooling](TOOLING.md)). Grant descriptions name the operator flag or policy
|
|
78
|
+
involved, never binding values.
|
|
79
|
+
|
|
80
|
+
## Programmatic analysis
|
|
81
|
+
|
|
82
|
+
The main package exports `getCapabilities`, `routeCapabilities`,
|
|
83
|
+
`analyzeProjectCapabilities`, `analyzeCompiledCapabilities`,
|
|
84
|
+
`assertTargetCompatibility`, `normalizeCapabilityTarget` and their report types.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
import { loadDocument, analyzeProjectCapabilities,
|
|
88
|
+
assertTargetCompatibility } from '@jimhoyd/urlcode';
|
|
89
|
+
|
|
90
|
+
const loaded = await loadDocument('./project');
|
|
91
|
+
const report = analyzeProjectCapabilities(loaded, 'cloudflare');
|
|
92
|
+
console.log(report.issues); // path, capability, support, reason; never binding values
|
|
93
|
+
assertTargetCompatibility(report);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This low-level example examines declared routes. Runtime activation and builds
|
|
97
|
+
first expand `site` conventions using the operator origin, then analyze all
|
|
98
|
+
routes including generated ones. A report is a compatibility preflight, **not**
|
|
99
|
+
a substitute for compilation/validation. Disabled and expired routes are still
|
|
100
|
+
checked; project `dynamicLinks: true` is a requirement even with no link route.
|
|
101
|
+
`compatible` means there are no refused, unknown or unresolved conditional
|
|
102
|
+
requirements. Explicit delegation and transport limitations still apply.
|
|
103
|
+
|
|
104
|
+
Requirements include effective inherited/profile policies after route overrides
|
|
105
|
+
and `false` removals. Policy modules' existing `targets(config)` functions remain
|
|
106
|
+
the source of policy decisions. The catalog says serverless throttle is
|
|
107
|
+
conditional; a project report resolves `partition: route` to native and the
|
|
108
|
+
client partitions to refused. Reports contain paths and capability facts, not
|
|
109
|
+
sources, destinations, binding names/values, code, validator closures or assets.
|
|
110
|
+
|
|
111
|
+
Build/activation refusals aggregate all incompatible requirements and name each
|
|
112
|
+
route, capability, target and reason before any artifact files are written.
|
|
113
|
+
Unsupported bindings on Cloudflare fail before credentials are resolved.
|
|
114
|
+
|
|
115
|
+
## Existing IR, formalized
|
|
116
|
+
|
|
117
|
+
The implementation already has a useful internal representation:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
strict YAML + schema validation + includes
|
|
121
|
+
→ site expansion
|
|
122
|
+
→ shared capability preflight (declarations, no credentials)
|
|
123
|
+
→ semantic route compilation
|
|
124
|
+
→ CompiledRouteTable / CompiledRoute
|
|
125
|
+
→ capability analysis / target lowering
|
|
126
|
+
→ host assets + policy chains + isolated function pool, or Worker artifact
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`CompiledRoute` in `src/types.ts` extends shared `MatchableRoute` with validated
|
|
130
|
+
parameters, normalized HTTP replies/headers, resolved bindings and source
|
|
131
|
+
references. `CompiledRouteTable` indexes literal paths, parameter buckets and
|
|
132
|
+
static mounts. `router.ts` owns precedence, collision checks, default methods,
|
|
133
|
+
input/reference validation and normalization. `match.ts` supplies portable
|
|
134
|
+
request-time matching to both runtime and Worker. No second parser or competing
|
|
135
|
+
route IR is introduced.
|
|
136
|
+
|
|
137
|
+
`routeCapabilities` is a value-free projection used for both declaration
|
|
138
|
+
preflight and compiled analysis. Preflight deliberately precedes full semantic
|
|
139
|
+
validation to report unsupported features before reading missing assets or
|
|
140
|
+
resolving secrets; it cannot authorize or validate a project. Normal compilation
|
|
141
|
+
and policy validation still run. AWS/Vercel consume the shared runtime IR;
|
|
142
|
+
Cloudflare additionally analyzes the compiled table before serializing its
|
|
143
|
+
allowlisted artifact fields and standalone validators.
|
|
144
|
+
|
|
145
|
+
The compiled table is internal, mutable during activation and **not serializable
|
|
146
|
+
as an interchange contract**: bindings contain resolved secrets, validators are
|
|
147
|
+
functions, assets contain bytes and policy chains own host state. The existing
|
|
148
|
+
Cloudflare artifact is a separate versioned lowering, not a replacement IR.
|
|
149
|
+
[Interchange](INTERCHANGE.md) projects a validated literal-redirect subset and
|
|
150
|
+
explicitly reports unsupported semantics; it never dumps compiled routes.
|
|
151
|
+
The next-phase schema extends this same IR with normalized condition cases,
|
|
152
|
+
proxy headers and signal definitions. Resolved egress headers are private runtime
|
|
153
|
+
state and must never be serialized. Capability analysis itself adds no authority
|
|
154
|
+
and does not run in the request path.
|
|
155
|
+
|
|
156
|
+
See the [repository review and incremental plan](NEXT-PHASE-PLAN.md) for the
|
|
157
|
+
implementation status. Provider deployment tests, independent security review and
|
|
158
|
+
operational soak/recovery proof are separate work.
|
|
159
|
+
|
|
160
|
+
## Local performance check
|
|
161
|
+
|
|
162
|
+
2026-09-17, Node 26.8.2, macOS arm64, Apple M4 Pro, 48 GiB RAM.
|
|
163
|
+
`npm run benchmark -- <count>` runs 5,000 loopback requests at concurrency 16.
|
|
164
|
+
Single runs against baseline `1a00294` and this change, not a statistical study:
|
|
165
|
+
|
|
166
|
+
| Routes | Startup ms before / after | RSS MiB before / after | Heap MiB before / after | Requests/s before / after | p95 ms before / after |
|
|
167
|
+
| --- | --- | --- | --- | --- | --- |
|
|
168
|
+
| 1,000 | 135 / 138 | 183 / 182 | 29 / 34 | 24,645 / 24,701 | 1.18 / 1.23 |
|
|
169
|
+
| 10,000 | 312 / 303 | 211 / 207 | 55 / 63 | 6,762 / 6,829 | 4.08 / 4.09 |
|
|
170
|
+
|
|
171
|
+
Both baseline and updated 100,000-route runs failed with `Configuration worker
|
|
172
|
+
resource limit or failure` before route compilation. The worker's existing
|
|
173
|
+
resource bounds are unchanged; the configured 100k route ceiling is not evidence
|
|
174
|
+
that every 100k YAML document fits those bounds. Bulk-scale remediation and
|
|
175
|
+
repeatable memory profiling were subsequent work at Phase A. The new
|
|
176
|
+
[bulk sharding benchmark](BULK.md) successfully activates 100,000 routes within
|
|
177
|
+
the unchanged worker limits. Capability analysis adds
|
|
178
|
+
linear activation work and temporary report allocations; no request-time checks
|
|
179
|
+
were added. These measurements are not provider, soak or capacity certification.
|
package/docs/CAPACITY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Capacity, concurrency and system limits
|
|
2
2
|
|
|
3
|
-
These are 0.
|
|
3
|
+
These are 0.3.0 implementation limits and planning models, not a throughput
|
|
4
4
|
SLA. Route count, connections, in-flight requests and sandbox concurrency are
|
|
5
5
|
four different quantities. Always measure the actual application on deployment
|
|
6
6
|
hardware with the intended proxy, TLS, logging and limits enabled.
|
package/docs/CI.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Checking a project on GitHub
|
|
2
|
+
|
|
3
|
+
`jimhoyd-com/urlcode/action` is a composite GitHub Action for a URLCode
|
|
4
|
+
*project*: a repository with a `urlcode.yaml`. It runs the same local checks
|
|
5
|
+
you run by hand and, on pull requests, keeps one comment up to date with the
|
|
6
|
+
route-inventory diff against the base branch. It needs no cloud credentials;
|
|
7
|
+
the only token it touches is the workflow's own `GITHUB_TOKEN`.
|
|
8
|
+
|
|
9
|
+
The starter ships it as `.github/workflows/urlcode.yml` (`urlcode init` copies
|
|
10
|
+
it; the [template repository](https://github.com/jimhoyd-com/urlcode-template)
|
|
11
|
+
carries the same file):
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
name: urlcode
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
pull_request:
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
pull-requests: write # the sticky route-diff comment; drop it to only log the diff
|
|
21
|
+
jobs:
|
|
22
|
+
check:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
- uses: jimhoyd-com/urlcode/action@main # pin a release tag or commit
|
|
29
|
+
with:
|
|
30
|
+
expect-routes: 2
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The action lives at `action/action.yml` in the runtime repository, so the
|
|
34
|
+
reference is `jimhoyd-com/urlcode/action@<ref>`. Pin `<ref>` the way you pin
|
|
35
|
+
the runtime: a release tag or a commit SHA, not `main`, once the project is
|
|
36
|
+
past its first commit.
|
|
37
|
+
|
|
38
|
+
## What it runs
|
|
39
|
+
|
|
40
|
+
| Step | Command | Fails the job when |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Install | `npm ci` in the project (see below) | Dependencies do not install |
|
|
43
|
+
| Validate | `urlcode validate --project <project>` | The YAML, includes, functions or bindings do not load |
|
|
44
|
+
| Test | `urlcode test --project <project>` | A `tests/requests.json` fixture fails |
|
|
45
|
+
| Audit | `urlcode audit --project <project> --expect-routes N --compliance <profile>` | Count mismatch, failed generated check, uncovered active route/method, or a `high` compliance finding without `compliance-warn` |
|
|
46
|
+
| Route diff | `urlcode routes --compare base.json --format markdown` | Never; it reports |
|
|
47
|
+
|
|
48
|
+
Every command is the CLI documented in [readiness](READINESS.md) and
|
|
49
|
+
[compliance](COMPLIANCE.md); the action adds no check of its own. `--origin`
|
|
50
|
+
is passed to validate, test and audit when set. Steps run with `bash`, so the
|
|
51
|
+
action works on the Linux, macOS and Windows runners.
|
|
52
|
+
|
|
53
|
+
The project's runtime comes from the project. With a `package.json` the action
|
|
54
|
+
runs `npm ci` (or `npm install` without a lockfile) and uses the
|
|
55
|
+
`@jimhoyd/urlcode` that resolves from there, hoisted or not. A project without
|
|
56
|
+
`package.json`, such as a fresh `urlcode init`, gets the `runtime` input
|
|
57
|
+
installed into a private prefix under the runner's temp directory:
|
|
58
|
+
`@jimhoyd/urlcode` (the latest release) by default, or a version
|
|
59
|
+
(`@jimhoyd/urlcode@0.3.0`) or an absolute tarball path.
|
|
60
|
+
|
|
61
|
+
## Inputs
|
|
62
|
+
|
|
63
|
+
| Input | Default | Meaning |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `project` | `.` | Directory containing `urlcode.yaml`, relative to the workspace |
|
|
66
|
+
| `node-version` | `26` | Passed to `actions/setup-node` |
|
|
67
|
+
| `runtime` | `@jimhoyd/urlcode` | npm spec installed when the project has no `package.json` |
|
|
68
|
+
| `expect-routes` | empty | `audit --expect-routes N`; empty skips the count check |
|
|
69
|
+
| `compliance` | `baseline` | `baseline`, `strict`, `privacy` or `none` |
|
|
70
|
+
| `compliance-rules` | empty | Absolute path to an operator rules module outside the project |
|
|
71
|
+
| `compliance-warn` | `false` | Report findings without failing (`--compliance-warn`) |
|
|
72
|
+
| `origin` | empty | Public origin of the deployment under review |
|
|
73
|
+
| `route-diff` | `true` | Post the route diff on pull requests |
|
|
74
|
+
|
|
75
|
+
Output `route-diff` is the path of the Markdown diff on a pull request, empty
|
|
76
|
+
otherwise, for a later step that wants to upload or reuse it.
|
|
77
|
+
|
|
78
|
+
## The sticky comment
|
|
79
|
+
|
|
80
|
+
On `pull_request` events the action fetches the base commit, checks it out into
|
|
81
|
+
a temporary git worktree, runs `urlcode routes` there and on the head with the
|
|
82
|
+
head's runtime, and renders `routes --compare` as Markdown: one table each for
|
|
83
|
+
added, removed and changed routes, or "No route changes". A changed route lists
|
|
84
|
+
each differing field (handler, methods, state, middleware count, policies,
|
|
85
|
+
generated marker or policy description) with its before and after value.
|
|
86
|
+
|
|
87
|
+
`action/comment.mjs` then finds the pull request's comments for the marker
|
|
88
|
+
`<!-- urlcode-route-diff project="<project>" -->` and updates that comment,
|
|
89
|
+
or creates it on the first run. The key is the project directory, so a
|
|
90
|
+
repository with several projects gets one comment per project and never a
|
|
91
|
+
pile of stale ones. Updating comments needs `pull-requests: write`; on a
|
|
92
|
+
fork's pull request or without that permission the API answers 403 or 404 and
|
|
93
|
+
the step logs a notice and exits 0, leaving the diff in the job log. The same
|
|
94
|
+
happens when the base commit is not reachable or its YAML does not load with
|
|
95
|
+
the head runtime. The diff is generic: only the action knows about GitHub.
|
|
96
|
+
|
|
97
|
+
## Custom compliance rules
|
|
98
|
+
|
|
99
|
+
Write a rules module as [compliance](COMPLIANCE.md#writing-custom-rules)
|
|
100
|
+
describes and keep it outside the audited project, because it runs as trusted
|
|
101
|
+
host code. In a repository with the project at the root, put it in a sibling
|
|
102
|
+
directory and pass the absolute path:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
- uses: jimhoyd-com/urlcode/action@main
|
|
106
|
+
with:
|
|
107
|
+
expect-routes: 25
|
|
108
|
+
compliance: strict
|
|
109
|
+
compliance-rules: ${{ github.workspace }}/ci/rules.mjs
|
|
110
|
+
compliance-warn: true
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`ci/rules.mjs` is inside the checkout but not inside the project only when
|
|
114
|
+
`project` is a subdirectory; with `project: .` keep the rules in a second
|
|
115
|
+
checkout or under `${{ runner.temp }}`. `compliance-rules` alone implies
|
|
116
|
+
`compliance: baseline`; `compliance: none` without rules skips the compliance
|
|
117
|
+
section entirely.
|
|
118
|
+
|
|
119
|
+
## Exit codes
|
|
120
|
+
|
|
121
|
+
The job fails when any of validate, test or audit exits nonzero; the
|
|
122
|
+
[audit exit codes](COMPLIANCE.md#exit-codes) apply unchanged. The route diff
|
|
123
|
+
and the comment never fail the job. A failing install (missing `@jimhoyd/urlcode`
|
|
124
|
+
dependency, unavailable `runtime` spec) fails the job before any check runs.
|
|
125
|
+
|
|
126
|
+
## The same checks locally
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
urlcode validate --project .
|
|
130
|
+
urlcode test --project .
|
|
131
|
+
urlcode audit --project . --expect-routes 2 --compliance baseline
|
|
132
|
+
git stash && urlcode routes --project . > /tmp/base.json && git stash pop
|
|
133
|
+
urlcode routes --project . --compare /tmp/base.json --format markdown
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or `make validate`, `make test` and `make audit ARGS='--expect-routes 2'`
|
|
137
|
+
from the starter Makefile. The runtime repository exercises the action on
|
|
138
|
+
every pull request against `examples/cookbook` (`.github/workflows/ci.yml`,
|
|
139
|
+
job `action`) with the packed tarball as `runtime`, and `test/action.test.ts`
|
|
140
|
+
checks that `action.yml` is a composite action with the inputs above and that
|
|
141
|
+
every third-party action it or the starter workflow uses is pinned to a
|
|
142
|
+
commit.
|
|
@@ -0,0 +1,74 @@
|
|
|
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.
|
|
@@ -0,0 +1,108 @@
|
|
|
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` and `--link-store` bindings 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).
|