@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.
Files changed (326) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +106 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +114 -0
  3. package/.claude-plugin/marketplace.json +18 -0
  4. package/CONTRIBUTING.md +30 -2
  5. package/README.md +157 -230
  6. package/ROADMAP.md +85 -2
  7. package/SECURITY.md +8 -1
  8. package/dist/BUILD-MANIFEST.json +62 -32
  9. package/dist/adapters.js +4 -23
  10. package/dist/agent-lists.js +1 -1
  11. package/dist/agents-guide.js +113 -0
  12. package/dist/authoring-files.js +60 -0
  13. package/dist/authoring.js +11 -1
  14. package/dist/aws.js +4 -3
  15. package/dist/build-cloudflare.js +11 -24
  16. package/dist/bulk.js +37 -0
  17. package/dist/capabilities.js +208 -0
  18. package/dist/capability-query.js +72 -0
  19. package/dist/catalog.js +105 -0
  20. package/dist/cli.js +154 -15
  21. package/dist/client-address.js +1 -1
  22. package/dist/compliance-rules/baseline.js +8 -8
  23. package/dist/compliance-rules/privacy.js +3 -3
  24. package/dist/compliance-rules/strict.js +5 -5
  25. package/dist/conditions.js +88 -0
  26. package/dist/config.js +60 -4
  27. package/dist/context.js +156 -0
  28. package/dist/ecosystem-cli.js +88 -0
  29. package/dist/egress.js +98 -0
  30. package/dist/examples.js +92 -0
  31. package/dist/explain-cli.js +64 -0
  32. package/dist/explain.js +132 -0
  33. package/dist/extensions.js +172 -0
  34. package/dist/function-sources.js +15 -3
  35. package/dist/index.js +37 -0
  36. package/dist/init-with.js +165 -0
  37. package/dist/interchange-cli.js +42 -0
  38. package/dist/interchange.js +189 -0
  39. package/dist/link-cli.js +1 -1
  40. package/dist/management-policy.js +0 -1
  41. package/dist/manifest.js +111 -0
  42. package/dist/match.js +2 -2
  43. package/dist/mcp-authoring.js +147 -0
  44. package/dist/mcp.js +97 -0
  45. package/dist/observability.js +6 -0
  46. package/dist/operator-host.js +29 -0
  47. package/dist/plugins.js +12 -0
  48. package/dist/policies/agents.js +2 -2
  49. package/dist/policies/compression.js +2 -1
  50. package/dist/policies/security.js +0 -0
  51. package/dist/policies.js +1 -1
  52. package/dist/policy.js +29 -7
  53. package/dist/prerender.js +100 -41
  54. package/dist/project-tests.js +3 -3
  55. package/dist/provider-verification.js +92 -0
  56. package/dist/proxy.js +44 -0
  57. package/dist/readiness.js +5 -5
  58. package/dist/recipes.js +41 -0
  59. package/dist/route-diff.js +106 -0
  60. package/dist/router.js +42 -2
  61. package/dist/runtime.js +95 -14
  62. package/dist/schema-query.js +62 -0
  63. package/dist/signals.js +24 -0
  64. package/dist/site.js +0 -0
  65. package/dist/tooling.js +96 -0
  66. package/dist/types/adapters.d.ts +7 -4
  67. package/dist/types/agent-lists.d.ts +0 -1
  68. package/dist/types/agents-guide.d.ts +17 -0
  69. package/dist/types/authoring-files.d.ts +10 -0
  70. package/dist/types/aws.d.ts +3 -1
  71. package/dist/types/build-cloudflare.d.ts +1 -0
  72. package/dist/types/bulk.d.ts +27 -0
  73. package/dist/types/capabilities.d.ts +55 -0
  74. package/dist/types/capability-query.d.ts +24 -0
  75. package/dist/types/catalog.d.ts +65 -0
  76. package/dist/types/client-address.d.ts +0 -1
  77. package/dist/types/compliance-rules/baseline.d.ts +1 -9
  78. package/dist/types/compliance-rules/privacy.d.ts +1 -4
  79. package/dist/types/compliance-rules/strict.d.ts +0 -5
  80. package/dist/types/conditions.d.ts +19 -0
  81. package/dist/types/config.d.ts +20 -2
  82. package/dist/types/context.d.ts +65 -0
  83. package/dist/types/ecosystem-cli.d.ts +17 -0
  84. package/dist/types/egress.d.ts +46 -0
  85. package/dist/types/examples.d.ts +50 -0
  86. package/dist/types/explain-cli.d.ts +11 -0
  87. package/dist/types/explain.d.ts +95 -0
  88. package/dist/types/extensions.d.ts +122 -0
  89. package/dist/types/function-sources.d.ts +5 -0
  90. package/dist/types/index.d.ts +33 -0
  91. package/dist/types/init-with.d.ts +30 -0
  92. package/dist/types/interchange-cli.d.ts +16 -0
  93. package/dist/types/interchange.d.ts +42 -0
  94. package/dist/types/link-cli.d.ts +2 -1
  95. package/dist/types/management-policy.d.ts +0 -1
  96. package/dist/types/manifest.d.ts +81 -0
  97. package/dist/types/match.d.ts +1 -0
  98. package/dist/types/mcp-authoring.d.ts +92 -0
  99. package/dist/types/mcp.d.ts +12 -0
  100. package/dist/types/observability.d.ts +2 -0
  101. package/dist/types/operator-host.d.ts +8 -0
  102. package/dist/types/plugins.d.ts +2 -0
  103. package/dist/types/policies/agents.d.ts +0 -2
  104. package/dist/types/policies/compression.d.ts +2 -0
  105. package/dist/types/policies/security.d.ts +0 -1
  106. package/dist/types/policy.d.ts +15 -4
  107. package/dist/types/project-tests.d.ts +3 -1
  108. package/dist/types/provider-verification.d.ts +53 -0
  109. package/dist/types/proxy.d.ts +21 -0
  110. package/dist/types/readiness.d.ts +1 -1
  111. package/dist/types/recipes.d.ts +30 -0
  112. package/dist/types/route-diff.d.ts +27 -0
  113. package/dist/types/runtime.d.ts +11 -0
  114. package/dist/types/schema-query.d.ts +12 -0
  115. package/dist/types/signals.d.ts +25 -0
  116. package/dist/types/site.d.ts +0 -1
  117. package/dist/types/tooling.d.ts +115 -0
  118. package/dist/types/types.d.ts +57 -0
  119. package/dist/types/typescript-authoring.d.ts +12 -0
  120. package/dist/types/vercel.d.ts +3 -1
  121. package/dist/types/verify-deployment.d.ts +47 -0
  122. package/dist/types.js +21 -2
  123. package/dist/typescript-authoring.js +104 -0
  124. package/dist/vercel.js +4 -3
  125. package/dist/verify-deployment.js +270 -0
  126. package/docs/AI-AUTHORING.md +130 -8
  127. package/docs/BULK.md +79 -0
  128. package/docs/CAPABILITIES.md +179 -0
  129. package/docs/CAPACITY.md +1 -1
  130. package/docs/CI.md +142 -0
  131. package/docs/CONDITIONS.md +74 -0
  132. package/docs/DEPLOYMENT-CHECKS.md +108 -0
  133. package/docs/DYNAMIC-LINKS.md +18 -518
  134. package/docs/EGRESS.md +125 -0
  135. package/docs/EXTENSIONS.md +226 -0
  136. package/docs/FRAMEWORK.md +182 -0
  137. package/docs/INSTALL.md +45 -7
  138. package/docs/INTERCHANGE.md +134 -0
  139. package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
  140. package/docs/MIDDLEWARE.md +2 -0
  141. package/docs/NEXT-PHASE-PLAN.md +90 -0
  142. package/docs/NEXT-STEPS.md +415 -0
  143. package/docs/OBSERVABILITY.md +4 -2
  144. package/docs/OPERATIONAL-PROOF.md +4 -1
  145. package/docs/OPERATIONS.md +6 -3
  146. package/docs/PLUGINS.md +37 -0
  147. package/docs/POLICIES.md +12 -309
  148. package/docs/PRERENDER.md +40 -0
  149. package/docs/PROJECT-DIRECTION.md +42 -0
  150. package/docs/PROVIDER-VERIFICATION.md +84 -0
  151. package/docs/READINESS.md +21 -1
  152. package/docs/README.md +82 -31
  153. package/docs/RECIPES.md +99 -0
  154. package/docs/RELEASE-READINESS.md +11 -9
  155. package/docs/RELEASE-SECURITY.md +27 -4
  156. package/docs/SECURITY-AUDIT.md +1 -1
  157. package/docs/SPECIFICATION.md +95 -8
  158. package/docs/SPIKE-BUSINESS-SUITE.md +1013 -0
  159. package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
  160. package/docs/SPIKE-EXTENSIONS.md +1 -0
  161. package/docs/SPIKE-LAMBDA-COMPILE.md +199 -0
  162. package/docs/STANDARDS.md +150 -142
  163. package/docs/STARTERS.md +21 -1
  164. package/docs/TOOLING.md +291 -0
  165. package/docs/TYPESCRIPT-AUTHORING.md +67 -0
  166. package/docs/TYPESCRIPT.md +1 -1
  167. package/docs/USABILITY-REVIEW.md +123 -0
  168. package/docs/YAML-GUIDE.md +18 -479
  169. package/docs/YAML-REFERENCE.md +127 -16
  170. package/docs/links/cli.md +110 -0
  171. package/docs/links/limits.md +175 -0
  172. package/docs/links/management-api.md +80 -0
  173. package/docs/links/pools.md +75 -0
  174. package/docs/links/setup.md +135 -0
  175. package/docs/policies/agents.md +1 -1
  176. package/docs/policies/contract.md +52 -0
  177. package/docs/policies/hardened.md +56 -0
  178. package/docs/policies/interoperability.md +169 -0
  179. package/docs/policies/operations.md +45 -0
  180. package/docs/yaml/assets.md +36 -0
  181. package/docs/yaml/conditions.md +20 -0
  182. package/docs/yaml/functions.md +160 -0
  183. package/docs/yaml/links.md +30 -0
  184. package/docs/yaml/middleware.md +29 -0
  185. package/docs/yaml/organization.md +74 -0
  186. package/docs/yaml/policies.md +37 -0
  187. package/docs/yaml/redirects.md +64 -0
  188. package/docs/yaml/responses.md +57 -0
  189. package/docs/yaml/site.md +24 -0
  190. package/examples/assets/example.yaml +17 -0
  191. package/examples/aws/example.yaml +20 -0
  192. package/examples/cloudflare/example.yaml +19 -0
  193. package/examples/compliance/example.yaml +11 -0
  194. package/examples/conditions/README.md +12 -0
  195. package/examples/conditions/example.yaml +19 -0
  196. package/examples/conditions/tests/requests.json +13 -0
  197. package/examples/conditions/urlcode.yaml +24 -0
  198. package/examples/cookbook/README.md +8 -4
  199. package/examples/cookbook/example.yaml +17 -0
  200. package/examples/cookbook/functions/catalog.mjs +3 -0
  201. package/examples/cookbook/functions/fail.mjs +4 -0
  202. package/examples/cookbook/functions/items.mjs +3 -0
  203. package/examples/cookbook/functions/profile.mjs +3 -0
  204. package/examples/cookbook/functions/resource.mjs +3 -0
  205. package/examples/cookbook/functions/status.mjs +3 -0
  206. package/examples/cookbook/middleware/auth.mjs +48 -0
  207. package/examples/cookbook/middleware/body.mjs +15 -0
  208. package/examples/cookbook/middleware/bucket.mjs +19 -0
  209. package/examples/cookbook/middleware/cors.mjs +21 -0
  210. package/examples/cookbook/middleware/debug.mjs +13 -0
  211. package/examples/cookbook/middleware/envelope.mjs +11 -0
  212. package/examples/cookbook/middleware/errors.mjs +11 -0
  213. package/examples/cookbook/middleware/etag.mjs +18 -0
  214. package/examples/cookbook/middleware/locale.mjs +16 -0
  215. package/examples/cookbook/middleware/maintenance.mjs +10 -0
  216. package/examples/cookbook/middleware/methods.mjs +15 -0
  217. package/examples/cookbook/middleware/negotiate.mjs +20 -0
  218. package/examples/cookbook/middleware/referer.mjs +12 -0
  219. package/examples/cookbook/middleware/request-id.mjs +16 -0
  220. package/examples/cookbook/route-index.json +676 -0
  221. package/examples/cookbook/routes/middleware.yaml +126 -0
  222. package/examples/cookbook/tests/requests.json +526 -0
  223. package/examples/cookbook/urlcode.yaml +1 -0
  224. package/examples/egress/README.md +22 -0
  225. package/examples/egress/example.yaml +19 -0
  226. package/examples/egress/urlcode.yaml +19 -0
  227. package/examples/extensions/README.md +7 -0
  228. package/examples/extensions/example.yaml +21 -0
  229. package/examples/extensions/urlcode.yaml +25 -0
  230. package/examples/live-links/example.yaml +21 -0
  231. package/examples/monitoring/example.yaml +8 -0
  232. package/examples/prerender/example.yaml +16 -0
  233. package/examples/provider-conformance/README.md +12 -0
  234. package/examples/provider-conformance/example.yaml +14 -0
  235. package/examples/provider-conformance/urlcode.yaml +34 -0
  236. package/examples/tunnel/example.yaml +8 -0
  237. package/examples/vercel/example.yaml +19 -0
  238. package/llms-full.txt +2709 -0
  239. package/llms.txt +48 -19
  240. package/package.json +29 -7
  241. package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
  242. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +106 -0
  243. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +114 -0
  244. package/recipes/authenticated-json-api/README.md +51 -0
  245. package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
  246. package/recipes/authenticated-json-api/recipe.yaml +34 -0
  247. package/recipes/authenticated-json-api/tests/requests.json +39 -0
  248. package/recipes/authenticated-json-api/urlcode.yaml +12 -0
  249. package/recipes/contact-form/README.md +25 -0
  250. package/recipes/contact-form/functions/contact.mjs +17 -0
  251. package/recipes/contact-form/recipe.yaml +33 -0
  252. package/recipes/contact-form/tests/requests.json +47 -0
  253. package/recipes/contact-form/urlcode.yaml +18 -0
  254. package/recipes/cors-api/README.md +16 -0
  255. package/recipes/cors-api/functions/items.mjs +3 -0
  256. package/recipes/cors-api/middleware/cors.mjs +21 -0
  257. package/recipes/cors-api/recipe.yaml +26 -0
  258. package/recipes/cors-api/tests/requests.json +65 -0
  259. package/recipes/cors-api/urlcode.yaml +12 -0
  260. package/recipes/health-page/README.md +13 -0
  261. package/recipes/health-page/recipe.yaml +23 -0
  262. package/recipes/health-page/tests/requests.json +36 -0
  263. package/recipes/health-page/urlcode.yaml +19 -0
  264. package/recipes/json-api/README.md +6 -0
  265. package/recipes/json-api/functions/echo.mjs +3 -0
  266. package/recipes/json-api/recipe.yaml +25 -0
  267. package/recipes/json-api/tests/requests.json +34 -0
  268. package/recipes/json-api/urlcode.yaml +12 -0
  269. package/recipes/middleware/README.md +34 -0
  270. package/recipes/middleware/functions/catalog.mjs +3 -0
  271. package/recipes/middleware/functions/fail.mjs +4 -0
  272. package/recipes/middleware/functions/items.mjs +3 -0
  273. package/recipes/middleware/functions/profile.mjs +3 -0
  274. package/recipes/middleware/functions/resource.mjs +3 -0
  275. package/recipes/middleware/functions/status.mjs +3 -0
  276. package/recipes/middleware/middleware/auth.mjs +48 -0
  277. package/recipes/middleware/middleware/body.mjs +15 -0
  278. package/recipes/middleware/middleware/bucket.mjs +19 -0
  279. package/recipes/middleware/middleware/cors.mjs +21 -0
  280. package/recipes/middleware/middleware/debug.mjs +13 -0
  281. package/recipes/middleware/middleware/envelope.mjs +11 -0
  282. package/recipes/middleware/middleware/errors.mjs +11 -0
  283. package/recipes/middleware/middleware/etag.mjs +18 -0
  284. package/recipes/middleware/middleware/locale.mjs +16 -0
  285. package/recipes/middleware/middleware/maintenance.mjs +10 -0
  286. package/recipes/middleware/middleware/methods.mjs +15 -0
  287. package/recipes/middleware/middleware/negotiate.mjs +20 -0
  288. package/recipes/middleware/middleware/referer.mjs +12 -0
  289. package/recipes/middleware/middleware/request-id.mjs +16 -0
  290. package/recipes/middleware/public/guide.txt +1 -0
  291. package/recipes/middleware/recipe.yaml +50 -0
  292. package/recipes/middleware/tests/requests.json +528 -0
  293. package/recipes/middleware/urlcode.yaml +127 -0
  294. package/recipes/protected-download/README.md +22 -0
  295. package/recipes/protected-download/files/report.txt +1 -0
  296. package/recipes/protected-download/recipe.yaml +31 -0
  297. package/recipes/protected-download/tests/requests.json +32 -0
  298. package/recipes/protected-download/urlcode.yaml +15 -0
  299. package/recipes/redirect/README.md +7 -0
  300. package/recipes/redirect/recipe.yaml +25 -0
  301. package/recipes/redirect/tests/requests.json +19 -0
  302. package/recipes/redirect/urlcode.yaml +9 -0
  303. package/recipes/static-plus-api/README.md +15 -0
  304. package/recipes/static-plus-api/functions/info.mjs +3 -0
  305. package/recipes/static-plus-api/public/assets/index.html +3 -0
  306. package/recipes/static-plus-api/public/assets/site.css +1 -0
  307. package/recipes/static-plus-api/public/index.html +8 -0
  308. package/recipes/static-plus-api/recipe.yaml +29 -0
  309. package/recipes/static-plus-api/tests/requests.json +56 -0
  310. package/recipes/static-plus-api/urlcode.yaml +23 -0
  311. package/recipes/typescript/README.md +7 -0
  312. package/recipes/typescript/functions/hello.ts +5 -0
  313. package/recipes/typescript/recipe.yaml +23 -0
  314. package/recipes/typescript/tests/requests.json +18 -0
  315. package/recipes/typescript/urlcode.yaml +5 -0
  316. package/recipes/webhook-receiver/README.md +16 -0
  317. package/recipes/webhook-receiver/functions/receive.mjs +16 -0
  318. package/recipes/webhook-receiver/recipe.yaml +26 -0
  319. package/recipes/webhook-receiver/tests/requests.json +59 -0
  320. package/recipes/webhook-receiver/urlcode.yaml +16 -0
  321. package/schemas/recipe.schema.json +138 -0
  322. package/schemas/urlcode.schema.json +656 -80
  323. package/skills/urlcode/SKILL.md +98 -0
  324. package/starters/default/.github/workflows/urlcode.yml +23 -0
  325. package/starters/default/.mcp.json +12 -0
  326. 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.2.0 implementation limits and planning models, not a throughput
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).