@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.2

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 (380) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +122 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +108 -0
  3. package/.claude-plugin/marketplace.json +18 -0
  4. package/CONTRIBUTING.md +30 -2
  5. package/README.md +195 -255
  6. package/ROADMAP.md +143 -15
  7. package/SECURITY.md +31 -9
  8. package/dist/BUILD-MANIFEST.json +72 -47
  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/build-static.js +134 -0
  17. package/dist/bulk.js +37 -0
  18. package/dist/capabilities.js +262 -0
  19. package/dist/capability-query.js +71 -0
  20. package/dist/catalog.js +105 -0
  21. package/dist/cli.js +165 -34
  22. package/dist/client-address.js +1 -1
  23. package/dist/compliance-rules/baseline.js +9 -17
  24. package/dist/compliance-rules/privacy.js +7 -18
  25. package/dist/compliance-rules/shared.js +0 -2
  26. package/dist/compliance-rules/strict.js +5 -5
  27. package/dist/compliance.js +6 -8
  28. package/dist/conditions.js +88 -0
  29. package/dist/config.js +69 -6
  30. package/dist/context.js +155 -0
  31. package/dist/ecosystem-cli.js +88 -0
  32. package/dist/egress.js +98 -0
  33. package/dist/examples.js +92 -0
  34. package/dist/explain-cli.js +64 -0
  35. package/dist/explain.js +131 -0
  36. package/dist/extensions.js +231 -0
  37. package/dist/function-sources.js +49 -5
  38. package/dist/function-worker.js +3 -1
  39. package/dist/functions.js +84 -13
  40. package/dist/guest-api.js +29 -3
  41. package/dist/index.js +40 -6
  42. package/dist/init-with.js +165 -0
  43. package/dist/interchange-cli.js +42 -0
  44. package/dist/interchange.js +189 -0
  45. package/dist/manifest.js +109 -0
  46. package/dist/match.js +2 -2
  47. package/dist/mcp-authoring.js +147 -0
  48. package/dist/mcp.js +97 -0
  49. package/dist/observability.js +7 -21
  50. package/dist/operator-host.js +29 -0
  51. package/dist/plugins.js +12 -0
  52. package/dist/policies/agents.js +2 -2
  53. package/dist/policies/cache.js +8 -3
  54. package/dist/policies/compression.js +2 -1
  55. package/dist/policies/security.js +0 -0
  56. package/dist/policies.js +1 -1
  57. package/dist/policy.js +56 -15
  58. package/dist/prerender.js +100 -41
  59. package/dist/project-tests.js +3 -3
  60. package/dist/provider-verification.js +92 -0
  61. package/dist/proxy.js +44 -0
  62. package/dist/readiness.js +34 -11
  63. package/dist/recipes.js +41 -0
  64. package/dist/route-diff.js +106 -0
  65. package/dist/router.js +45 -7
  66. package/dist/runtime.js +164 -64
  67. package/dist/sandbox.js +48 -0
  68. package/dist/scaffold.js +0 -0
  69. package/dist/schema-query.js +62 -0
  70. package/dist/scripts/operational-drills.js +12 -54
  71. package/dist/server.js +3 -29
  72. package/dist/signals.js +24 -0
  73. package/dist/site.js +0 -0
  74. package/dist/tooling.js +96 -0
  75. package/dist/trusted-functions.js +210 -0
  76. package/dist/types/adapters.d.ts +7 -4
  77. package/dist/types/agent-lists.d.ts +0 -1
  78. package/dist/types/agents-guide.d.ts +17 -0
  79. package/dist/types/authoring-files.d.ts +10 -0
  80. package/dist/types/aws.d.ts +3 -1
  81. package/dist/types/build-cloudflare.d.ts +1 -0
  82. package/dist/types/build-static.d.ts +43 -0
  83. package/dist/types/bulk.d.ts +27 -0
  84. package/dist/types/capabilities.d.ts +64 -0
  85. package/dist/types/capability-query.d.ts +24 -0
  86. package/dist/types/catalog.d.ts +65 -0
  87. package/dist/types/client-address.d.ts +0 -1
  88. package/dist/types/compliance-rules/baseline.d.ts +1 -9
  89. package/dist/types/compliance-rules/privacy.d.ts +1 -4
  90. package/dist/types/compliance-rules/shared.d.ts +0 -2
  91. package/dist/types/compliance-rules/strict.d.ts +0 -5
  92. package/dist/types/compliance.d.ts +0 -3
  93. package/dist/types/conditions.d.ts +19 -0
  94. package/dist/types/config.d.ts +21 -2
  95. package/dist/types/context.d.ts +66 -0
  96. package/dist/types/ecosystem-cli.d.ts +17 -0
  97. package/dist/types/egress.d.ts +46 -0
  98. package/dist/types/examples.d.ts +50 -0
  99. package/dist/types/explain-cli.d.ts +11 -0
  100. package/dist/types/explain.d.ts +95 -0
  101. package/dist/types/extensions.d.ts +177 -0
  102. package/dist/types/function-sources.d.ts +9 -0
  103. package/dist/types/functions.d.ts +48 -5
  104. package/dist/types/guest-api.d.ts +1 -0
  105. package/dist/types/index.d.ts +36 -6
  106. package/dist/types/init-with.d.ts +30 -0
  107. package/dist/types/interchange-cli.d.ts +16 -0
  108. package/dist/types/interchange.d.ts +42 -0
  109. package/dist/types/manifest.d.ts +79 -0
  110. package/dist/types/match.d.ts +1 -0
  111. package/dist/types/mcp-authoring.d.ts +92 -0
  112. package/dist/types/mcp.d.ts +12 -0
  113. package/dist/types/observability.d.ts +3 -14
  114. package/dist/types/operator-host.d.ts +8 -0
  115. package/dist/types/plugins.d.ts +2 -0
  116. package/dist/types/policies/agents.d.ts +0 -2
  117. package/dist/types/policies/compression.d.ts +2 -0
  118. package/dist/types/policies/security.d.ts +0 -1
  119. package/dist/types/policy.d.ts +15 -4
  120. package/dist/types/project-tests.d.ts +3 -2
  121. package/dist/types/provider-verification.d.ts +53 -0
  122. package/dist/types/proxy.d.ts +21 -0
  123. package/dist/types/readiness.d.ts +10 -3
  124. package/dist/types/recipes.d.ts +30 -0
  125. package/dist/types/route-diff.d.ts +27 -0
  126. package/dist/types/router.d.ts +2 -1
  127. package/dist/types/runtime.d.ts +11 -27
  128. package/dist/types/sandbox.d.ts +12 -0
  129. package/dist/types/scaffold.d.ts +0 -2
  130. package/dist/types/schema-query.d.ts +12 -0
  131. package/dist/types/server.d.ts +1 -4
  132. package/dist/types/signals.d.ts +25 -0
  133. package/dist/types/site.d.ts +0 -1
  134. package/dist/types/tooling.d.ts +115 -0
  135. package/dist/types/trusted-functions.d.ts +29 -0
  136. package/dist/types/types.d.ts +71 -7
  137. package/dist/types/typescript-authoring.d.ts +12 -0
  138. package/dist/types/vercel.d.ts +3 -1
  139. package/dist/types/verify-deployment.d.ts +47 -0
  140. package/dist/types.js +37 -5
  141. package/dist/typescript-authoring.js +142 -0
  142. package/dist/vercel.js +4 -3
  143. package/dist/verify-deployment.js +270 -0
  144. package/docs/AI-AUTHORING.md +232 -15
  145. package/docs/AWS.md +4 -4
  146. package/docs/BEST-PRACTICES.md +3 -2
  147. package/docs/BULK.md +79 -0
  148. package/docs/CAPABILITIES.md +192 -0
  149. package/docs/CAPACITY.md +129 -32
  150. package/docs/CI.md +142 -0
  151. package/docs/CLOUDFLARE.md +1 -2
  152. package/docs/COMPLIANCE.md +6 -9
  153. package/docs/CONDITIONS.md +74 -0
  154. package/docs/DEPLOYMENT-CHECKS.md +108 -0
  155. package/docs/EGRESS.md +125 -0
  156. package/docs/EXTENSIONS.md +398 -0
  157. package/docs/FRAMEWORK.md +198 -0
  158. package/docs/FUNCTION-SECURITY.md +129 -32
  159. package/docs/INSTALL.md +45 -12
  160. package/docs/INTERCHANGE.md +134 -0
  161. package/docs/LOAD-TESTING.md +4 -4
  162. package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
  163. package/docs/MIDDLEWARE.md +29 -16
  164. package/docs/MONITORING.md +2 -19
  165. package/docs/NEXT-PHASE-PLAN.md +98 -0
  166. package/docs/NEXT-STEPS.md +634 -0
  167. package/docs/OBSERVABILITY.md +11 -18
  168. package/docs/OPEN-DECISIONS.md +212 -0
  169. package/docs/OPERATIONAL-PROOF.md +30 -31
  170. package/docs/OPERATIONS.md +29 -35
  171. package/docs/PLUGINS.md +37 -0
  172. package/docs/POLICIES.md +23 -309
  173. package/docs/PRERENDER.md +41 -1
  174. package/docs/PROJECT-DIRECTION.md +75 -8
  175. package/docs/PROVIDER-VERIFICATION.md +84 -0
  176. package/docs/READINESS.md +21 -1
  177. package/docs/README.md +87 -34
  178. package/docs/RECIPES.md +99 -0
  179. package/docs/RELEASE-READINESS.md +57 -35
  180. package/docs/RELEASE-SECURITY.md +116 -7
  181. package/docs/RESILIENCE.md +16 -15
  182. package/docs/ROUTING.md +8 -10
  183. package/docs/SANDBOX-REVIEW.md +19 -6
  184. package/docs/SCAFFOLDING.md +0 -2
  185. package/docs/SECURITY-AUDIT.md +41 -1
  186. package/docs/SPECIFICATION.md +150 -29
  187. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
  188. package/docs/SPIKE-BUSINESS-SUITE.md +1021 -0
  189. package/docs/SPIKE-CORE-LAYERING.md +337 -0
  190. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
  191. package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
  192. package/docs/SPIKE-EXTENSIONS.md +6 -0
  193. package/docs/SPIKE-LAMBDA-COMPILE.md +201 -0
  194. package/docs/SPIKE-MONOREPO.md +322 -0
  195. package/docs/STANDARDS.md +150 -142
  196. package/docs/STARTERS.md +21 -1
  197. package/docs/STATIC.md +94 -0
  198. package/docs/TOOLING.md +295 -0
  199. package/docs/TUNNELS.md +0 -3
  200. package/docs/TYPESCRIPT-AUTHORING.md +82 -0
  201. package/docs/TYPESCRIPT.md +25 -4
  202. package/docs/USABILITY-REVIEW.md +129 -0
  203. package/docs/VERCEL.md +4 -5
  204. package/docs/VERSION-ALIGNMENT.md +205 -0
  205. package/docs/YAML-GUIDE.md +15 -479
  206. package/docs/YAML-REFERENCE.md +143 -22
  207. package/docs/policies/agents.md +1 -1
  208. package/docs/policies/cache.md +13 -0
  209. package/docs/policies/contract.md +52 -0
  210. package/docs/policies/hardened.md +56 -0
  211. package/docs/policies/interoperability.md +169 -0
  212. package/docs/policies/operations.md +45 -0
  213. package/docs/yaml/assets.md +36 -0
  214. package/docs/yaml/conditions.md +20 -0
  215. package/docs/yaml/functions.md +160 -0
  216. package/docs/yaml/middleware.md +29 -0
  217. package/docs/yaml/organization.md +74 -0
  218. package/docs/yaml/policies.md +37 -0
  219. package/docs/yaml/redirects.md +64 -0
  220. package/docs/yaml/responses.md +57 -0
  221. package/docs/yaml/site.md +24 -0
  222. package/examples/assets/example.yaml +17 -0
  223. package/examples/aws/example.yaml +20 -0
  224. package/examples/cloudflare/example.yaml +19 -0
  225. package/examples/compliance/example.yaml +11 -0
  226. package/examples/conditions/README.md +12 -0
  227. package/examples/conditions/example.yaml +19 -0
  228. package/examples/conditions/tests/requests.json +13 -0
  229. package/examples/conditions/urlcode.yaml +24 -0
  230. package/examples/cookbook/README.md +8 -4
  231. package/examples/cookbook/example.yaml +17 -0
  232. package/examples/cookbook/functions/catalog.mjs +3 -0
  233. package/examples/cookbook/functions/fail.mjs +4 -0
  234. package/examples/cookbook/functions/items.mjs +3 -0
  235. package/examples/cookbook/functions/profile.mjs +3 -0
  236. package/examples/cookbook/functions/resource.mjs +3 -0
  237. package/examples/cookbook/functions/status.mjs +3 -0
  238. package/examples/cookbook/middleware/auth.mjs +48 -0
  239. package/examples/cookbook/middleware/body.mjs +15 -0
  240. package/examples/cookbook/middleware/bucket.mjs +29 -0
  241. package/examples/cookbook/middleware/cors.mjs +21 -0
  242. package/examples/cookbook/middleware/debug.mjs +13 -0
  243. package/examples/cookbook/middleware/envelope.mjs +11 -0
  244. package/examples/cookbook/middleware/errors.mjs +11 -0
  245. package/examples/cookbook/middleware/etag.mjs +18 -0
  246. package/examples/cookbook/middleware/locale.mjs +20 -0
  247. package/examples/cookbook/middleware/maintenance.mjs +10 -0
  248. package/examples/cookbook/middleware/methods.mjs +15 -0
  249. package/examples/cookbook/middleware/negotiate.mjs +20 -0
  250. package/examples/cookbook/middleware/referer.mjs +12 -0
  251. package/examples/cookbook/middleware/request-id.mjs +16 -0
  252. package/examples/cookbook/route-index.json +676 -0
  253. package/examples/cookbook/routes/middleware.yaml +126 -0
  254. package/examples/cookbook/tests/requests.json +526 -0
  255. package/examples/cookbook/urlcode.yaml +1 -0
  256. package/examples/egress/README.md +22 -0
  257. package/examples/egress/example.yaml +19 -0
  258. package/examples/egress/urlcode.yaml +19 -0
  259. package/examples/extensions/README.md +7 -0
  260. package/examples/extensions/example.yaml +21 -0
  261. package/examples/extensions/urlcode.yaml +25 -0
  262. package/examples/monitoring/example.yaml +8 -0
  263. package/examples/prerender/README.md +2 -2
  264. package/examples/prerender/example.yaml +16 -0
  265. package/examples/provider-conformance/README.md +12 -0
  266. package/examples/provider-conformance/example.yaml +14 -0
  267. package/examples/provider-conformance/urlcode.yaml +34 -0
  268. package/examples/tunnel/example.yaml +8 -0
  269. package/examples/vercel/example.yaml +19 -0
  270. package/llms-full.txt +3084 -0
  271. package/llms.txt +61 -21
  272. package/package.json +36 -7
  273. package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
  274. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +122 -0
  275. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +108 -0
  276. package/recipes/authenticated-json-api/README.md +51 -0
  277. package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
  278. package/recipes/authenticated-json-api/recipe.yaml +34 -0
  279. package/recipes/authenticated-json-api/tests/requests.json +39 -0
  280. package/recipes/authenticated-json-api/urlcode.yaml +12 -0
  281. package/recipes/contact-form/README.md +25 -0
  282. package/recipes/contact-form/functions/contact.mjs +17 -0
  283. package/recipes/contact-form/recipe.yaml +33 -0
  284. package/recipes/contact-form/tests/requests.json +47 -0
  285. package/recipes/contact-form/urlcode.yaml +18 -0
  286. package/recipes/cors-api/README.md +16 -0
  287. package/recipes/cors-api/functions/items.mjs +3 -0
  288. package/recipes/cors-api/middleware/cors.mjs +21 -0
  289. package/recipes/cors-api/recipe.yaml +26 -0
  290. package/recipes/cors-api/tests/requests.json +65 -0
  291. package/recipes/cors-api/urlcode.yaml +12 -0
  292. package/recipes/health-page/README.md +13 -0
  293. package/recipes/health-page/recipe.yaml +23 -0
  294. package/recipes/health-page/tests/requests.json +36 -0
  295. package/recipes/health-page/urlcode.yaml +19 -0
  296. package/recipes/json-api/README.md +6 -0
  297. package/recipes/json-api/functions/echo.mjs +3 -0
  298. package/recipes/json-api/recipe.yaml +25 -0
  299. package/recipes/json-api/tests/requests.json +34 -0
  300. package/recipes/json-api/urlcode.yaml +12 -0
  301. package/recipes/middleware/README.md +34 -0
  302. package/recipes/middleware/functions/catalog.mjs +3 -0
  303. package/recipes/middleware/functions/fail.mjs +4 -0
  304. package/recipes/middleware/functions/items.mjs +3 -0
  305. package/recipes/middleware/functions/profile.mjs +3 -0
  306. package/recipes/middleware/functions/resource.mjs +3 -0
  307. package/recipes/middleware/functions/status.mjs +3 -0
  308. package/recipes/middleware/middleware/auth.mjs +48 -0
  309. package/recipes/middleware/middleware/body.mjs +15 -0
  310. package/recipes/middleware/middleware/bucket.mjs +29 -0
  311. package/recipes/middleware/middleware/cors.mjs +21 -0
  312. package/recipes/middleware/middleware/debug.mjs +13 -0
  313. package/recipes/middleware/middleware/envelope.mjs +11 -0
  314. package/recipes/middleware/middleware/errors.mjs +11 -0
  315. package/recipes/middleware/middleware/etag.mjs +18 -0
  316. package/recipes/middleware/middleware/locale.mjs +20 -0
  317. package/recipes/middleware/middleware/maintenance.mjs +10 -0
  318. package/recipes/middleware/middleware/methods.mjs +15 -0
  319. package/recipes/middleware/middleware/negotiate.mjs +20 -0
  320. package/recipes/middleware/middleware/referer.mjs +12 -0
  321. package/recipes/middleware/middleware/request-id.mjs +16 -0
  322. package/recipes/middleware/public/guide.txt +1 -0
  323. package/recipes/middleware/recipe.yaml +50 -0
  324. package/recipes/middleware/tests/requests.json +528 -0
  325. package/recipes/middleware/urlcode.yaml +127 -0
  326. package/recipes/protected-download/README.md +22 -0
  327. package/recipes/protected-download/files/report.txt +1 -0
  328. package/recipes/protected-download/recipe.yaml +31 -0
  329. package/recipes/protected-download/tests/requests.json +32 -0
  330. package/recipes/protected-download/urlcode.yaml +15 -0
  331. package/recipes/redirect/README.md +7 -0
  332. package/recipes/redirect/recipe.yaml +25 -0
  333. package/recipes/redirect/tests/requests.json +19 -0
  334. package/recipes/redirect/urlcode.yaml +9 -0
  335. package/recipes/static-plus-api/README.md +15 -0
  336. package/recipes/static-plus-api/functions/info.mjs +3 -0
  337. package/recipes/static-plus-api/public/assets/index.html +3 -0
  338. package/recipes/static-plus-api/public/assets/site.css +1 -0
  339. package/recipes/static-plus-api/public/index.html +8 -0
  340. package/recipes/static-plus-api/recipe.yaml +29 -0
  341. package/recipes/static-plus-api/tests/requests.json +56 -0
  342. package/recipes/static-plus-api/urlcode.yaml +23 -0
  343. package/recipes/typescript/README.md +8 -0
  344. package/recipes/typescript/functions/hello.ts +5 -0
  345. package/recipes/typescript/recipe.yaml +23 -0
  346. package/recipes/typescript/tests/requests.json +18 -0
  347. package/recipes/typescript/urlcode.yaml +5 -0
  348. package/recipes/webhook-receiver/README.md +20 -0
  349. package/recipes/webhook-receiver/functions/receive.mjs +16 -0
  350. package/recipes/webhook-receiver/recipe.yaml +27 -0
  351. package/recipes/webhook-receiver/tests/requests.json +59 -0
  352. package/recipes/webhook-receiver/urlcode.yaml +23 -0
  353. package/schemas/recipe.schema.json +139 -0
  354. package/schemas/urlcode.schema.json +659 -110
  355. package/skills/urlcode/SKILL.md +119 -0
  356. package/starters/default/.github/workflows/urlcode.yml +23 -0
  357. package/starters/default/.mcp.json +12 -0
  358. package/starters/default/AGENTS.md +79 -0
  359. package/starters/default/urlcode.yaml +0 -1
  360. package/dist/link-api.js +0 -136
  361. package/dist/link-cli.js +0 -141
  362. package/dist/link-events.js +0 -76
  363. package/dist/link-records.js +0 -31
  364. package/dist/link-store-worker.js +0 -150
  365. package/dist/link-store.js +0 -250
  366. package/dist/management-policy.js +0 -41
  367. package/dist/sqlite-version.js +0 -6
  368. package/dist/types/link-api.d.ts +0 -30
  369. package/dist/types/link-cli.d.ts +0 -36
  370. package/dist/types/link-events.d.ts +0 -27
  371. package/dist/types/link-records.d.ts +0 -11
  372. package/dist/types/link-store-worker.d.ts +0 -1
  373. package/dist/types/link-store.d.ts +0 -130
  374. package/dist/types/management-policy.d.ts +0 -9
  375. package/dist/types/sqlite-version.d.ts +0 -1
  376. package/docs/DYNAMIC-LINKS.md +0 -561
  377. package/docs/MANAGEMENT-SECURITY.md +0 -82
  378. package/examples/live-links/README.md +0 -11
  379. package/examples/live-links/tests/requests.json +0 -6
  380. package/examples/live-links/urlcode.yaml +0 -16
@@ -33,15 +33,12 @@ named.
33
33
  | `reload` | `status` `ok`/`rejected`; `version` string and `routes` integer on `ok` | `app.reload()` or the development watcher swapped, or refused to swap, the snapshot. |
34
34
  | `watch` | `status` `failed` | The development watcher could not fingerprint the project. |
35
35
  | `function_worker` | `status` `started`/`restarting`, `slot` integer; `attempt` and `delayMs` integers on `restarting` | A function worker became ready or is scheduled for replacement. |
36
- | `link_store_worker` | `status` `started`/`restarting`, `readOnly` boolean; `attempt` and `delayMs` on `restarting` | The same for a link-store connection. |
37
- | `link_observer` | `status` `failed`/`dropped`/`closed`; `reason` `timeout`/`error` on `failed`; `dropped` integer on `dropped`; `queued`, `delivered`, `dropped`, `failed`, `timedOut`, `closed` on `closed` | Only with `linkEvents`: the link collector failed, the queue shed events, or the channel drained at shutdown. |
38
- | `link_request` | `requestId`, `collection`, `route`, `code` (only with `includeCode`), `method`, `status`, `outcome`, `durationMs` | Delivered to the `linkEvents.observe` function only, after the response is over. It never reaches the log or observers; its `outcome` is counted in the metrics. |
36
+ | `signal` | `outcome` (`accepted`, `delivered`, `failed`, `dropped`), positive `count` | Best-effort webhook totals; no destination, request data or secrets. |
39
37
  | `logs_dropped` | `count` integer | The JSON logger shed records because stdout was not writable. Written by the logger itself, so observers do not see it. |
40
38
  | `observer` | `status` `failed`, `name` string | An observer hook threw or rejected. Written to the default log only, never to observers. |
41
39
  | `throttle` | `route`, `outcome` `allowed`/`exceeded`, `remaining` integer | A throttle decision. `allowed` is logged only in `mode: report`; enforce mode logs refusals. |
42
40
  | `agents` | `route`, `list` string, `outcome` `denied`/`reported` | A User-Agent matched a list. The list name is logged, never the header. |
43
41
  | `cache` | `route`, `outcome` `hit`/`stale`/`miss`/`store` | A cache lookup or store. |
44
- | `management_request` | `timestamp`, `requestId`, `collection`, `action`, `authenticated`, `principal`, `status`, `outcome`, `durationMs` | The link-management API (`startLinkApi`), which has its own logger; `principal` is the operator-configured principal id. |
45
42
  | `listening` | `address`, `port`, `mode`, `origin` | Printed once by the CLI at startup, not emitted by the server. |
46
43
 
47
44
  Every event carries `event` (its name). Numbers are JSON numbers, never
@@ -50,11 +47,10 @@ strings.
50
47
  ### Privacy guarantees
51
48
 
52
49
  No event, snapshot or exposition carries a request URL, path, query string,
53
- header, body, client address, User-Agent string, binding, secret, stored link
54
- destination or user exception text. `route` is always a configured pattern
50
+ header, body, client address, User-Agent string, binding, secret or user
51
+ exception text. `route` is always a configured pattern
55
52
  from reviewed YAML. `requestId` is server-generated unless
56
- `--trust-request-id` accepts one from a trusted proxy. `code` in
57
- `link_request` is redacted unless the operator sets `includeCode`. An observer
53
+ `--trust-request-id` accepts one from a trusted proxy. An observer
58
54
  that logs should keep the same rule; nothing in an event lets it break it.
59
55
 
60
56
  ## Observers
@@ -97,12 +93,12 @@ call. A hook that throws or returns a rejecting promise is isolated: the
97
93
  request is unaffected, the next observer still runs, `observers.errors` in
98
94
  the snapshot increments and one `observer` record goes to the default log.
99
95
  Nothing is retried; an observer that needs delivery guarantees owns its own
100
- queue, like the [link event channel](MONITORING.md#the-link-event-channel).
96
+ queue.
101
97
 
102
98
  `onMetrics` receives a fresh snapshot every `metricsIntervalMs`
103
99
  (`startServer` option, `0` off by default, 1 s to 1 h) and once at `close()`.
104
- `onClose` runs in reverse order after the runtime and the link channel have
105
- drained. `app.observers` lists the `{ name, version }` pairs.
100
+ `onClose` runs in reverse order after the runtime has closed.
101
+ `app.observers` lists the `{ name, version }` pairs.
106
102
 
107
103
  `createRuntime(project, { observers })` takes the same array for embedding
108
104
  without the server: the runtime's own sink and counters are then yours, and
@@ -123,7 +119,7 @@ and capped at 10 000 keys.
123
119
 
124
120
  | Field | Type | Meaning |
125
121
  |---|---|---|
126
- | `version` | gauge | Snapshot shape version, currently `1`. |
122
+ | `version` | gauge | Snapshot shape version, currently `2`. |
127
123
  | `uptimeSeconds`, `rssBytes` | gauge | Process facts. |
128
124
  | `requests.total`, `requests.byStatusClass.{2xx,3xx,4xx,5xx}` | counter | Application responses, including shed 503s. |
129
125
  | `requests.inFlight` | gauge | Requests holding application admission now. |
@@ -134,12 +130,10 @@ and capped at 10 000 keys.
134
130
  | `watch.failed` | counter | Development watcher failures. |
135
131
  | `functionWorkers.started`, `functionWorkers.restarts` | counter | Worker starts and scheduled replacements. |
136
132
  | `functionWorkers.healthySlots`, `functionWorkers.slots` | gauge | Ready slots and configured slots of the serving runtime. |
137
- | `linkStoreWorkers.started`, `linkStoreWorkers.restarts` | counter | The same for link-store connections. |
138
133
  | `policies.throttle.{allowed,exceeded}` | counter | Throttle decisions (see the catalogue for what enforce mode logs). |
139
134
  | `policies.agents.{denied,reported}` | counter | Agents decisions. |
140
135
  | `policies.cache.{hit,stale,miss,store}` | counter | Cache outcomes. |
141
- | `linkRequests.{completed,aborted,missing,disabled,expired,invalid_code,invalid_record,unavailable}` | counter | Dynamic link outcomes, counted whether or not `linkEvents` is set. |
142
- | `linkObserver.failed`, `linkObserver.dropped` | counter | Link collector failures and dropped events. |
136
+ | `signals.{accepted,delivered,failed,dropped}` | counter | Best-effort webhook outcomes; exposed as `signals_total` with outcome labels. |
143
137
  | `logsDropped` | counter | Records the JSON logger shed. |
144
138
  | `observers.errors` | counter | Observer hooks that threw or rejected. |
145
139
 
@@ -151,8 +145,8 @@ aggregation across replicas is the scraper's job.
151
145
 
152
146
  ## Prometheus exposition
153
147
 
154
- `startServer({ metrics: true })` serves `GET /_urlcode/metrics` as
155
- `text/plain; version=0.0.4`, rendered from the same snapshot by
148
+ `startServer({ metrics: true })`, or `urlcode serve --metrics` on the command
149
+ line, serves `GET /_urlcode/metrics` as `text/plain; version=0.0.4`, rendered from the same snapshot by
156
150
  `renderPrometheus(snapshot)`, a pure function you can also call yourself.
157
151
  Every metric is prefixed `urlcode_`; counters end in `_total`; the only labels
158
152
  are `status_class`, `route` and `outcome`.
@@ -169,7 +163,6 @@ urlcode_function_worker_restarts_total 0
169
163
  urlcode_function_worker_healthy_slots 2
170
164
  urlcode_throttle_total{outcome="exceeded"} 14
171
165
  urlcode_cache_total{outcome="hit"} 511
172
- urlcode_link_requests_total{outcome="completed"} 0
173
166
  urlcode_logs_dropped_total 0
174
167
  urlcode_observer_errors_total 0
175
168
  urlcode_uptime_seconds 86400
@@ -0,0 +1,212 @@
1
+ # Open decisions
2
+
3
+ Status: written 2026-09-19 from a review of the nine repositories. Every item
4
+ here is a maintainer decision that documentation cannot make. Each states what
5
+ is actually true today, what the options are, what it costs to leave open, and
6
+ a recommendation. Nothing here is committed scope; the
7
+ [roadmap](../ROADMAP.md) owns sequence and the
8
+ [readiness register](RELEASE-READINESS.md) owns what is proven.
9
+
10
+ This page exists because several documented positions have drifted apart from
11
+ each other and from the source. The drift is not the decisions themselves — it
12
+ is that they were never recorded in one place where the next contributor, human
13
+ or agent, would look.
14
+
15
+ ## 1. Publish `0.4.0-alpha.2` — decided
16
+
17
+ **Decided 2026-09-19: publish `alpha.2`.** The recommendation below was taken
18
+ rather than walking back a correct declaration. The alignment is prepared
19
+ across all nine repositories and the order is recorded in
20
+ [the version register](VERSIONS.md); what remains is merging and tagging.
21
+
22
+ The second hazard was taken with it: `@jimhoyd/urlcode-auth` moves `latest`
23
+ up to `0.1.0-alpha.2` so that a plain `npm install` no longer resolves a build
24
+ older than the floor `@jimhoyd/urlcode-admin` declares. That is a registry
25
+ operation (`npm dist-tag add`), not a source change, and it is listed in the
26
+ release steps.
27
+
28
+ The original entry, for the record:
29
+
30
+ > **Today:** the repository source is `0.4.0-alpha.2`. The npm dist-tags are
31
+ > `latest = 0.3.0` and `alpha = 0.4.0-alpha.1`. `alpha.2` carries the
32
+ > trusted-by-default execution change ([the decision](SPIKE-DEFAULT-TRUST-MODEL.md)).
33
+ >
34
+ > **Why it blocks other things:** `@jimhoyd/urlcode-middleware@0.1.0-alpha.1` is
35
+ > published with `peerDependencies: { "@jimhoyd/urlcode": ">=0.4.0-alpha.2" }`.
36
+ > No published core version satisfies that range, so a registry install of that
37
+ > package cannot resolve its peer. The range is correct; the publication order
38
+ > was not.
39
+ >
40
+ > **Recommendation:** publish `alpha.2`. It is the only option that does not
41
+ > require walking back a correct declaration.
42
+
43
+ ## 2. Consolidate the repositories, or commit to the split
44
+
45
+ **Today:** nine repositories. [The monorepo spike](SPIKE-MONOREPO.md) is
46
+ complete, its layout is marked decided (option A), its migration mechanics and
47
+ sequencing are written, and it has three open questions for the maintainer. It
48
+ has been neither accepted nor rejected. It was drafted for four repositories;
49
+ `urlcode-dynamic-link`, `urlcode-middleware` and `urlcode-short` have appeared
50
+ since.
51
+
52
+ **Cost of leaving it open:** the migration gets larger with every repository
53
+ added, and the coordination work the spike describes — pinning reviewed peer
54
+ revisions by hand, chasing prose across repositories when a contract changes —
55
+ is paid again on every contract change in the meantime.
56
+
57
+ **Recommendation:** decide before the next structural change, in either
58
+ direction. A recorded "no, and here is what we do instead about cross-repo
59
+ drift" closes this as well as a yes does.
60
+
61
+ ## 3. One way to attach middleware, or two
62
+
63
+ **Today:** core has the native `routes.<path>.middleware[]` array.
64
+ `urlcode-middleware` provides the same capability through the extension seam
65
+ and states that core's native array "keeps working unchanged".
66
+ [The layering spike](SPIKE-CORE-LAYERING.md) proposed *extracting* middleware
67
+ to narrow core to YAML plus `function`; what exists is an addition alongside it.
68
+
69
+ **Why this is a principle question:** the
70
+ [extension model review](SPIKE-EXTENSION-MODEL.md) rejected an earlier design
71
+ for exactly this reason — two ways to protect a route would drift apart — and
72
+ resolved it by removing one. The same test applies here.
73
+
74
+ **Options:** deprecate the native array on a stated timeline and complete the
75
+ extraction; or keep both and document precisely when each is correct, treating
76
+ the extension as a bounded variant rather than a successor.
77
+
78
+ **Recommendation:** decide explicitly and write the answer into both
79
+ repositories. Either is defensible; leaving both undescribed is not.
80
+
81
+ ## 4. Where the backlog lives
82
+
83
+ **Today:** [next steps](NEXT-STEPS.md) holds roughly thirty tracked items
84
+ across nine phases. The repository has one open issue (#58). `AGENTS.md`
85
+ instructs contributors and agents to file what they find as issues on the
86
+ owning repository.
87
+
88
+ **The contradiction:** the instruction points at the issue tracker; the actual
89
+ plan is a Markdown file that no tracker reflects. A plan document also has no
90
+ state, so items stay written as future work after they ship — which is how
91
+ route-level `auth` came to be described as an invented field in merged agent
92
+ guidance (see item 7).
93
+
94
+ **Options:** move the phase items into issues and keep the document as the
95
+ narrative that links them; or keep the document as the system of record and
96
+ amend `AGENTS.md` to say so.
97
+
98
+ **Recommendation:** issues for items, document for sequence. It is the only
99
+ arrangement where "done" is recorded automatically.
100
+
101
+ ## 5. Gate the business suite on evidence
102
+
103
+ **Today:** [the business suite spike](SPIKE-BUSINESS-SUITE.md) proposes seven
104
+ applications. [Project direction](PROJECT-DIRECTION.md) states the evidence
105
+ test: the framework grows from measured repetition, not from a list of things
106
+ applications might need. Phase 6 of [next steps](NEXT-STEPS.md) says candidate
107
+ areas are built only when the repetition log shows them repeating. The
108
+ repetition log does not exist yet, and the Phase 0 agent benchmark has not been
109
+ run.
110
+
111
+ **Recommendation:** record on the spike itself that it is gated behind the
112
+ benchmark and the repetition log, or amend the evidence test. Holding both
113
+ positions unannotated makes the stated principle decorative.
114
+
115
+ ## 6. Finish the documentation move, or describe the split
116
+
117
+ **Today:** `AGENTS.md` states that urlcode-docs is the documentation home and
118
+ the only place readers are sent, that new reader-facing pages go there, and
119
+ that pages still under `docs/` here are being migrated. Roughly forty
120
+ reader-facing pages remain in this repository, several duplicated in
121
+ urlcode-docs.
122
+
123
+ **Observed consequence:** the same fact drifts between copies. The public
124
+ documentation site carried the correct trusted-by-default contract while
125
+ `docs/OPERATIONS.md` in this repository still described functions as untrusted
126
+ and isolated by default. <!-- trust-model-prose: historical -->
127
+
128
+ **Options:** finish the migration against a dated list; or state which pages
129
+ stay here permanently and why, and add a check that fails when a new
130
+ reader-facing page appears beside them.
131
+
132
+ ## 7. A review window, and a check that does not need one
133
+
134
+ **Today:** [governance](../GOVERNANCE.md) records one maintainer, a required
135
+ approval count of zero, and that "an independent human review is not yet
136
+ guaranteed". Pull requests and CI are mandatory; a second pair of eyes is not.
137
+
138
+ **What that permitted, concretely:** on 2026-09-19, three coordinated pull
139
+ requests (urlcode#158, urlcode-template#6, urlcode-docs#17) opened and merged
140
+ within nine minutes. They recorded a genuine improvement — the declarative-first
141
+ principle, propagated to every copy in one change — and alongside it the
142
+ statement that agents must "never invent an `auth` field". Route-level `auth` is
143
+ implemented: it is defined in `schemas/urlcode.schema.json` as a short form
144
+ expanding to `policies.extensions.auth`, expanded in `src/config.ts`, typed in
145
+ `src/types.ts`, and asserted in `test/recipes.test.ts`. The guidance now
146
+ instructs agents away from a supported declarative short form and toward the
147
+ lower-level policy form — the opposite of the principle the same change
148
+ introduced — in the generated project guide, the bundled starter, both
149
+ authoring skills, the packaged plugin skill and the two generated `llms` files.
150
+
151
+ Nothing failed. Lint, typecheck, generated-resource checks, the package smoke
152
+ test and 501 tests all passed, because no check compares what the guidance
153
+ claims against what the schema implements.
154
+
155
+ **The check, now implemented.** `scripts/check-guidance-claims.ts` runs inside
156
+ `npm run check` and exits non-zero on a contradiction between agent-facing
157
+ guidance and the schema, so this class of error fails CI instead of depending on
158
+ a reviewer's attention:
159
+
160
+ - Inputs: the agent-facing surfaces — `src/agents-guide.ts`,
161
+ `starters/default/AGENTS.md`, `skills/urlcode/SKILL.md`,
162
+ `.claude/skills/*/SKILL.md`, `packaging/claude-plugin/skills/*/SKILL.md`,
163
+ `llms.txt`, `llms-full.txt`, `docs/AI-AUTHORING.md`.
164
+ - Assertion one: every YAML field named as valid in those files resolves in
165
+ `schemas/urlcode.schema.json`. A guidance file may not teach a field the
166
+ schema does not accept.
167
+ - Assertion two, the one that would have caught this: no field that the schema
168
+ *does* define is described as unsupported, invented or nonexistent. Match the
169
+ negative phrasings deliberately ("never invent", "does not exist",
170
+ "unsupported field") within a short window of a schema-resolvable field name.
171
+ - Assertion three: handlers listed as available match the capability catalog,
172
+ so a removed handler (`link`, extracted to `urlcode-dynamic-link` in
173
+ `f7dbe54`) cannot linger in generated guidance.
174
+ - Exits non-zero on violation, with a documented `<!-- guidance-claims: ignore -->`
175
+ marker for text that is deliberately about another version. Verified against
176
+ both regressions: reintroducing the "never invent an `auth` field" sentence
177
+ fails the check, and adding the removed `link` handler to the inventory line
178
+ fails it.
179
+
180
+ What remains a decision: whether `npm run check` membership is enough, or the
181
+ check should also be named in the repository's required status checks so it
182
+ cannot be bypassed.
183
+
184
+ **Recommendation:** the check is in; keep it required. It is the part that does
185
+ not depend on a person being available. Whether to
186
+ also raise the required approval count is a separate call, and
187
+ [governance](../GOVERNANCE.md) already states the condition — when the trusted
188
+ maintainer team grows.
189
+
190
+ ## 8. One publishing convention
191
+
192
+ **Today:** `urlcode-dynamic-link` and `urlcode-middleware` keep
193
+ `"private": true` on their main branch and drop it in the release commit;
194
+ `urlcode-auth`, `urlcode-admin` and `urlcode-ui` do not. All are published.
195
+ Both practices are defensible; having both means the flag no longer indicates
196
+ publication state.
197
+
198
+ **Recommendation:** pick one and state it where release process is documented.
199
+
200
+ ## 9. Adjudicate the vendored skill drift
201
+
202
+ **Today:** `npm run check:downstream-skills` reports that `urlcode-template`'s
203
+ vendored authoring and operations skills differ from core's current `main` by
204
+ 79 and 91 lines. The report is advisory by design and never fails, because a
205
+ downstream repository can correctly pin an older published core version
206
+ ([issue 155](https://github.com/jimhoyd-com/urlcode/issues/155)).
207
+
208
+ **What is missing:** the judgment the report defers. No one has read the two
209
+ diffs and recorded which side is right for the template's `0.4.0-alpha.1` pin.
210
+
211
+ **Recommendation:** review both diffs once, record the verdict, and re-run the
212
+ report when the template repins.
@@ -1,42 +1,41 @@
1
1
  # Operational evidence and deployment acceptance
2
2
 
3
- `node scripts/operational-drills.ts` creates and deletes its own temporary project
4
- and store. It runs mixed real HTTP requests against native redirects, isolated
5
- functions and live links while updating records; asserts responses; rejects a bad
6
- reload; activates and rolls back a configuration; then closes all users of the
7
- store, explicitly verifies a successful WAL checkpoint, and restores a copied database. It checks integrity, record/version and
8
- latest durable audit revision. `URLCODE_SOAK_SECONDS=60` selects a longer run
9
- (1–3600 seconds, default 5). Output is JSON with request count, batch p99 duration,
10
- RSS and restore time. Batch latency is not per-request p99 or a capacity promise.
11
- CI runs the short drill on all six supported Node/OS combinations.
3
+ `node scripts/operational-drills.ts` creates and deletes its own temporary
4
+ project. It runs mixed real HTTP requests against native redirects and
5
+ `function` routes; asserts responses; rejects a bad reload; and activates and
6
+ rolls back a configuration. `URLCODE_SOAK_SECONDS=60` selects a longer run
7
+ (1–3600 seconds, default 5). Output is JSON with request count, batch p99
8
+ duration and RSS. Batch latency is not per-request p99 or a capacity promise.
9
+ CI's `verify` job runs the short drill across a 3-Node (22/24/26) × 3-OS
10
+ (Linux/macOS/Windows) matrix, nine combinations, but only on a push to `main`.
11
+ A pull request runs the same drill across all three Node versions on Linux
12
+ only (3 of the 9 combinations); the macOS/Windows legs only run once a PR
13
+ merges, per `.github/workflows/ci.yml`'s matrix.
12
14
 
13
- The container job additionally uses `--disk-full-dir /state` on a disposable
14
- 16 MiB tmpfs. It reserves recovery space, writes until a real volume exhaustion
15
- failure, checks available disk space, releases the reserve, proves a subsequent
16
- mutation succeeds, and checks integrity and equal committed link/audit counts.
17
- Never point this option at production storage: it intentionally consumes up to
18
- 84 MiB of writes in a newly created temporary child directory. The CI mount is
19
- nonpersistent. An I/O device failure or power cut is a different failure mode.
15
+ Core has no durable store of its own, so this drill has no backup/restore or
16
+ disk-exhaustion exercise: a future extension package owning durable state
17
+ (such as the planned `urlcode-dynamic-link`) is responsible for its own
18
+ persistence proof once it exists.
20
19
 
21
20
  ## Required proof on the intended deployment
22
21
 
23
- Local/CI passes do not close these gates. The deployment owner must record:
22
+ Local/CI passes do not close these gates. `urlcode verify-deployment --target`
23
+ ([deployment checks](DEPLOYMENT-CHECKS.md)) records that the deployed responses
24
+ match the project and is the first step of the rollback drill below, not a
25
+ substitute for it. The deployment owner must record:
24
26
 
25
27
  | Exercise | Acceptance evidence |
26
28
  |---|---|
27
- | Soak | At least 24 hours at expected peak and burst load through actual TLS/ingress; native/function/live-link mix, slow clients, response correctness, p50/p95/p99, throughput, error/rejection rate, RSS plateau, CPU, FD and disk/WAL growth. Define numerical SLOs before starting |
28
- | Restore | Restore a consistent backup onto a separate host/volume. Reconcile collection counts, latest committed revisions and audit journal; measure RPO/RTO against agreed targets. Test encrypted backup access and credential recovery |
29
- | Logical export/restore | Run `links export` against a store under write load, restore it with `links import` onto a separate store, and reconcile record counts and every field. Prove the export's digest verifies, that a truncated stream is rejected, and that stale management ETags are discarded because the restore reassigns versions. An export carries no audit journal, so pair it with a file backup |
30
- | Kill/restart | Kill server and writer with outstanding reads/writes. Reconcile uncertain mutations by version/request ID, run integrity checks, restore readiness, and prove no duplicate successful conditional writes |
31
- | Disk/log exhaustion | Fill the actual disposable staging storage type and log destination. Verify bounded 503s, audit/mutation atomicity, alert delivery through an independent sink, reserve-space recovery and readiness after recovery |
32
- | Rollback | Deploy candidate by exact digest beside last-good, run route assertions, switch ingress, drain, then switch back. Verify configuration/policy compatibility and audit continuity; never downgrade to an unaudited writer |
33
- | Monitoring | Deliver test alerts for missing logs, sustained errors, pool rejection/failure, low disk, restart storms, backup age and failed readiness to a named on-call owner |
29
+ | Soak | At least 24 hours at expected peak and burst load through actual TLS/ingress; native/function mix, slow clients, response correctness, p50/p95/p99, throughput, error/rejection rate, RSS plateau, CPU and FD growth. Define numerical SLOs before starting |
30
+ | Kill/restart | Kill the server with outstanding requests. Reconcile uncertain mutations by request ID, restore readiness, and prove no duplicate side effects from application code |
31
+ | Rollback | Deploy candidate by exact digest beside last-good, run route assertions, switch ingress, drain, then switch back. Verify configuration/policy compatibility |
32
+ | Monitoring | Deliver test alerts for missing logs, sustained errors, pool rejection/failure, low disk, restart storms and failed readiness to a named on-call owner |
33
+
34
+ If your deployment adds a durable-state extension, add that extension's own
35
+ backup/restore, logical export/import and disk/log exhaustion exercises to
36
+ this table; core's proof above does not cover them.
34
37
 
35
38
  Record date, operator, source/app/policy/image digests, topology, hardware/limits,
36
- commands, duration, synthetic dataset size, raw metrics/log locations, result,
37
- RPO/RTO and unresolved findings. A reviewer signs the acceptance record; a blank
38
- record is not a pass. Do not copy only the main file of a live WAL database.
39
- The executable local drill uses a fully quiesced, explicitly checkpointed database intentionally. A last
40
- read-only connection can leave WAL frames even after every connection closes;
41
- closing alone is insufficient proof that the main database file is a full backup. For a
42
- live-backup system, prove its SQLite-consistent snapshot method separately.
39
+ commands, duration, synthetic dataset size, raw metrics/log locations, result
40
+ and unresolved findings. A reviewer signs the acceptance record; a blank
41
+ record is not a pass.
@@ -35,18 +35,20 @@ The supplied image packages the runtime; it does not copy your application or
35
35
  local secret files. Build from the reviewed runtime checkout:
36
36
 
37
37
  ```sh
38
- docker build -t urlcode:0.2.0 .
38
+ docker build -t urlcode:0.3.0 .
39
39
  docker run --rm --name my-links \
40
40
  --read-only --cap-drop ALL --security-opt no-new-privileges \
41
41
  --memory 512m --cpus 1 --pids-limit 128 \
42
42
  -p 127.0.0.1:3000:3000 \
43
43
  -v "$PWD/starters/default:/project:ro" \
44
- urlcode:0.2.0
44
+ urlcode:0.3.0
45
45
  ```
46
46
 
47
47
  Replace the example mount with your app. The image uses the unprivileged `node`
48
- user; ensure mounted config/functions are readable by it. Only operator-owned components such as the optional link store can use writable
49
- mounts. Sandboxed application functions cannot access mounted files or installed
48
+ user; ensure mounted config/functions are readable by it. Core has no writable
49
+ mount of its own; a future mount-based extension (like `auth`/`admin`, see
50
+ [extensions](EXTENSIONS.md)) is the place for operator-owned writable state.
51
+ Sandboxed application functions cannot access mounted files or installed
50
52
  Node packages. The resource values above illustrate
51
53
  container limits, not a sizing recommendation; large configuration compilation
52
54
  can need more memory. Measure your workload. Tag/redeploy immutable image digests
@@ -63,7 +65,10 @@ at the proxy; they are unauthenticated and reveal route count/config digest.
63
65
 
64
66
  If functions perform sensitive actions, implement authentication and authorization
65
67
  in the application. A short URL is not automatically an access-control mechanism.
66
- Functions are untrusted and isolated in WASM by default. Keep separate deployment
68
+ Functions and middleware run trusted and unsandboxed by default, in the host
69
+ process with full Node, filesystem and network access; a route that declares
70
+ `sandbox: true` runs isolated in QuickJS/WebAssembly instead (see
71
+ [function security](FUNCTION-SECURITY.md)). Keep separate deployment
67
72
  processes/containers and narrowly scoped credentials as additional boundaries.
68
73
  Do not expose a public code-upload/multi-tenant service on the basis of the self-hosted release alone
69
74
  without separate security review and stronger service-level containment.
@@ -89,14 +94,14 @@ production does not watch or refresh secret values automatically.
89
94
 
90
95
  - `GET /_urlcode/health`: process liveness.
91
96
  - `GET /_urlcode/ready`: 200 when the active snapshot and all function workers
92
- are available and configured link-store readers are healthy; 503 while a worker/store
97
+ are available; 503 while a worker
93
98
  is unavailable. Busy workers alone do not
94
- mark readiness down. A failed worker or store connection is replaced with
99
+ mark readiness down. A failed worker is replaced with
95
100
  exponential backoff (250 ms doubling to a 30-second ceiling) and readiness
96
101
  reports 503 until every slot is serving again. Replacement does not stop, so a
97
102
  request-triggered deadline cannot disable functions until an operator restarts;
98
103
  a cause that keeps recurring keeps the instance shedding load and needs an
99
- operator. Alert on sustained `function_worker`/`link_store_worker` restart events.
104
+ operator. Alert on sustained `function_worker` restart events.
100
105
  - Probes are answered from their own admission budget (16 by default,
101
106
  `--max-in-flight-health`), so they stay available while the application is
102
107
  saturated without being an unmetered endpoint. They are unauthenticated and
@@ -125,11 +130,15 @@ production does not watch or refresh secret values automatically.
125
130
  admitted through response completion; excess requests receive 503. Health probes
126
131
  remain available under admission saturation. A 15-second socket inactivity
127
132
  timeout closes stalled readers/writers. Proxy timeouts/rate limits still matter.
128
- - Functions: 2 concurrent workers (`--workers`), no queue, 5-second deadline
129
- (`--function-timeout-ms`), 1 MiB buffered response (`--max-response-bytes`) and
130
- 16 KiB response headers. Saturation 503; timeout 504; error 502.
133
+ - Functions: a `sandbox: true` route gets 2 concurrent workers (`--workers`),
134
+ no queue and a 5-second deadline (`--function-timeout-ms`); a trusted route
135
+ (`sandbox` false or absent, the default) shares the in-flight admission cap
136
+ instead of a worker pool and races the same deadline. Either mode buffers
137
+ 1 MiB of response (`--max-response-bytes`) and 16 KiB response headers.
138
+ Saturation 503; timeout 504; error 502.
131
139
  QuickJS guests have a 32 MiB heap and 512 KiB stack budget and no network or
132
- host capabilities. Outer workers have additional V8 limits. Total process/WASM
140
+ host capabilities; a trusted route has neither budget and full Node access.
141
+ Outer workers have additional V8 limits. Total process/WASM
133
142
  memory still needs deployment-level limits; do not equate guest budget with RSS.
134
143
 
135
144
  `urlcode serve`/`dev` and the JavaScript server API both configure workers,
@@ -140,30 +149,23 @@ deployment controls, not portable route behavior. Horizontal replicas
140
149
  must use identical application/config versions and secret bindings. In-memory
141
150
  function state is reset after every invocation, not durable/shared application state.
142
151
  General application storage needs a future explicit capability broker; no
143
- storage/network access is exposed to the guest. The optional native
144
- [link store](DYNAMIC-LINKS.md) supports live short-link records on one host.
152
+ storage/network access is exposed to the guest. Stored short links are moving
153
+ to a future `urlcode-dynamic-link` extension package, not yet published; core
154
+ no longer has a native link store.
145
155
 
146
156
  The health version combines route-definition and asset-representation digests;
147
157
  it does not identify the complete function/runtime release. Record runtime commit,
148
158
  application commit, dependency locks and image digest in your deployment system.
149
159
 
150
- ## Optional dynamic-link deployment
151
-
152
- Keep SQLite and management tokens outside the application, in a private durable
153
- local directory. Initialize through `links init/create`, bind public serving with
154
- `--link-store`, and expose management on a separate private listener. Restrict
155
- its token to your trusted backend; apply ingress limits and backups. Public
156
- serving opens read-only pools; management has a separate writer and read pool.
157
- Budget connections across processes and monitor writer health separately. See
158
- [dynamic-link operations](DYNAMIC-LINKS.md). Multiple host replicas must not share
159
- this file over a network filesystem; no distributed adapter is included yet.
160
-
161
160
  ## Deployment and rollback procedure
162
161
 
163
162
  1. Build a candidate from pinned runtime/application revisions and lockfiles.
164
163
  2. Validate its config/bindings and run local HTTP tests without external redirects.
165
164
  3. Start it on an alternate private port/container. Check readiness and representative
166
- redirect/function behavior through the intended proxy configuration.
165
+ redirect/function behavior through the intended proxy configuration:
166
+ `urlcode verify-deployment --project . --target https://candidate.host` compares
167
+ version, fixtures, policy headers and site files with the project
168
+ ([deployment checks](DEPLOYMENT-CHECKS.md)).
167
169
  4. Switch proxy traffic after checks pass. Drain the previous instance before stopping.
168
170
  5. If checks or observed behavior fail, route traffic back to the retained previous
169
171
  instance/image and its compatible secret bindings.
@@ -171,7 +173,7 @@ this file over a network filesystem; no distributed adapter is included yet.
171
173
  This is an operator procedure, not an implemented deployment control plane.
172
174
  Rollback cannot undo a function's external side effects or migrate an app's
173
175
  state automatically. Plan those independently. Keep Git definitions backed up;
174
- back up any app-owned persistent state separately. YAML routes require no database; dynamic link records require separate backups.
176
+ back up any app-owned persistent state separately. YAML routes require no database.
175
177
 
176
178
  ## Capacity and incident planning
177
179
 
@@ -197,11 +199,3 @@ adapter is made by the current release.
197
199
  The [2026-09-16 internal audit](SECURITY-AUDIT.md) records fixes, regression evidence
198
200
  and remaining security/operational gates. This is not an independent assessment.
199
201
 
200
- ## Management hardening baseline
201
-
202
- Management is now restricted to literal loopback addresses. Prefer `--auth-file`
203
- for individual expiring, revocable credentials with collection/action scopes.
204
- Every successful built-in store mutation has an atomic, durable SQLite audit row;
205
- HTTP request logs remain best effort. See [management security](MANAGEMENT-SECURITY.md)
206
- for policy examples, compatibility, archival and rollback requirements, and
207
- [operational proof](OPERATIONAL-PROOF.md) for executable recovery drills.
package/docs/PLUGINS.md CHANGED
@@ -42,6 +42,7 @@ const auditPlugin = {
42
42
  name: 'audit', // ^[a-z][a-z0-9-]{0,63}$, unique per runtime
43
43
  version: '1.0.0', // any string up to 64 characters
44
44
  targets: ['node', 'vercel'], // subset of node, vercel, aws, cloudflare
45
+ credentialHeaders: ['Cookie', 'Authorization'], // optional; withheld from guests
45
46
  async onActivate(runtime) {}, // may throw to refuse activation
46
47
  async onRequest(request) {}, // return a result to short-circuit
47
48
  async onResponse(request, result) { return result; }, // return the result to send
@@ -112,6 +113,42 @@ it reaches the guest: there is no sandbox handle, no deadline, no `env` or
112
113
  deadline, read or write guest state, or obtain a binding the operator policy
113
114
  did not grant to the route.
114
115
 
116
+ ## Withholding credential headers from application code
117
+
118
+ An operator plugin may declare `credentialHeaders: ['Cookie', 'Authorization']`.
119
+ Each list contains at most 64 unique HTTP header names, compared
120
+ case-insensitively, each at most 128 characters. The runtime validates the lists
121
+ and captures their union before activation hooks run. Later mutation of a plugin
122
+ object does not weaken the running boundary; a reload captures a new declaration.
123
+ No YAML key can enable, disable or override this operator setting.
124
+
125
+ The union applies to every matched route in that runtime, including public
126
+ functions and guest middleware on native routes. Before processing application
127
+ inputs, the runtime makes a separate header copy and removes those names. Guest
128
+ `Request.headers`, `ctx.inputs.header` and header-derived `ctx.args` receive no
129
+ corresponding values. Header schema defaults are also omitted from the guest
130
+ context for those names. A required protected header input without a default fails with
131
+ `Missing required parameter`, even when the original request supplied it;
132
+ projects should not declare credential inputs that the operator withholds.
133
+ Native redirect query mappings also use this sanitized application context, so
134
+ they cannot reflect a withheld header into a response visible to middleware.
135
+ Selecting `Cookie` removes the entire header, not just one named cookie.
136
+
137
+ Host request/response/error hooks, first-party policies and request-body checks
138
+ retain the original headers. The runtime does not mutate the caller's Headers
139
+ object. Existing applications behave exactly as before when no plugin declares
140
+ credential headers. Plugins still need at least one lifecycle or request hook;
141
+ a header list alone is not a complete plugin.
142
+
143
+ This is a data boundary, not authentication, authorization or cache protection.
144
+ It does not create a principal, validate a token, protect a route, or prevent
145
+ shared caching. In particular, withholding Cookie or Authorization from guests
146
+ does not make a personalized response safe to cache. Operators must separately
147
+ configure the appropriate authentication and cache behavior. It also does not
148
+ redact URL/query/body content, explicit binding grants, logs or values deliberately
149
+ reflected by trusted host plugins. Host plugins retain full access to the request
150
+ and remain trusted operator code.
151
+
115
152
  ## Results
116
153
 
117
154
  A result is what the runtime writes: