@pithy-sh/core 0.9.0 → 0.11.0

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 (298) hide show
  1. package/dist/access/access.d.ts +172 -0
  2. package/dist/access/access.d.ts.map +1 -0
  3. package/dist/access/access.js +147 -0
  4. package/dist/access/acting.d.ts +49 -0
  5. package/dist/access/acting.d.ts.map +1 -0
  6. package/dist/access/acting.js +21 -0
  7. package/dist/access/policyCheck.d.ts +33 -0
  8. package/dist/access/policyCheck.d.ts.map +1 -0
  9. package/dist/access/policyCheck.js +48 -0
  10. package/dist/access/usage.d.ts +83 -0
  11. package/dist/access/usage.d.ts.map +1 -0
  12. package/dist/access/usage.js +104 -0
  13. package/dist/access/visibility.d.ts +66 -0
  14. package/dist/access/visibility.d.ts.map +1 -0
  15. package/dist/access/visibility.js +86 -0
  16. package/dist/asyncapi/document.d.ts +46 -0
  17. package/dist/asyncapi/document.d.ts.map +1 -0
  18. package/dist/asyncapi/document.js +115 -0
  19. package/dist/audit/auditEvent.d.ts +5 -0
  20. package/dist/audit/auditEvent.d.ts.map +1 -1
  21. package/dist/audit/auditEvent.js +2 -0
  22. package/dist/capability/bindings.d.ts +47 -2
  23. package/dist/capability/bindings.d.ts.map +1 -1
  24. package/dist/capability/bindings.js +117 -7
  25. package/dist/capability/capability.d.ts +105 -28
  26. package/dist/capability/capability.d.ts.map +1 -1
  27. package/dist/capability/composition.d.ts.map +1 -1
  28. package/dist/capability/composition.js +5 -1
  29. package/dist/capability/manifest.d.ts +2 -1
  30. package/dist/capability/manifest.d.ts.map +1 -1
  31. package/dist/capability/manifest.js +1 -1
  32. package/dist/capability/settings.d.ts +96 -4
  33. package/dist/capability/settings.d.ts.map +1 -1
  34. package/dist/capability/settings.js +85 -4
  35. package/dist/cli/command.d.ts +364 -0
  36. package/dist/cli/command.d.ts.map +1 -0
  37. package/dist/cli/command.js +418 -0
  38. package/dist/cli/commandCatalog.generated.d.ts +2437 -0
  39. package/dist/cli/commandCatalog.generated.d.ts.map +1 -0
  40. package/dist/cli/commandCatalog.generated.js +2564 -0
  41. package/dist/cli/commandFacts.d.ts +164 -0
  42. package/dist/cli/commandFacts.d.ts.map +1 -0
  43. package/dist/cli/commandFacts.js +216 -0
  44. package/dist/controlPlane/capability.d.ts.map +1 -1
  45. package/dist/controlPlane/capability.js +12 -2
  46. package/dist/controlPlane/context.d.ts +5 -0
  47. package/dist/controlPlane/context.d.ts.map +1 -1
  48. package/dist/controlPlane/context.js +3 -1
  49. package/dist/controlPlane/data/connection.d.ts +47 -0
  50. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  51. package/dist/controlPlane/data/connection.js +55 -1
  52. package/dist/controlPlane/error/errors.d.ts.map +1 -1
  53. package/dist/controlPlane/error/errors.js +4 -3
  54. package/dist/controlPlane/http/cors.d.ts +4 -4
  55. package/dist/controlPlane/http/cors.js +4 -4
  56. package/dist/controlPlane/http/guard.d.ts +11 -0
  57. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  58. package/dist/controlPlane/http/guard.js +28 -2
  59. package/dist/controlPlane/http/handlers.d.ts +60 -0
  60. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  61. package/dist/controlPlane/http/handlers.js +31 -2
  62. package/dist/controlPlane/http/readiness.d.ts +33 -0
  63. package/dist/controlPlane/http/readiness.d.ts.map +1 -0
  64. package/dist/controlPlane/http/readiness.js +39 -0
  65. package/dist/controlPlane/http/responses.d.ts +147 -0
  66. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  67. package/dist/controlPlane/http/responses.js +61 -4
  68. package/dist/controlPlane/http/routes.d.ts +2 -1
  69. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  70. package/dist/controlPlane/http/routes.js +50 -9
  71. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  72. package/dist/controlPlane/http/verify.js +2 -1
  73. package/dist/controlPlane/migrationState/migrationState.d.ts +102 -0
  74. package/dist/controlPlane/migrationState/migrationState.d.ts.map +1 -0
  75. package/dist/controlPlane/migrationState/migrationState.js +142 -0
  76. package/dist/controlPlane/migrations/0003_connection_principal.d.ts +21 -0
  77. package/dist/controlPlane/migrations/0003_connection_principal.d.ts.map +1 -0
  78. package/dist/controlPlane/migrations/0003_connection_principal.js +33 -0
  79. package/dist/controlPlane/scope/scope.d.ts +13 -2
  80. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  81. package/dist/controlPlane/scope/scope.js +14 -3
  82. package/dist/createBackend.d.ts +7 -0
  83. package/dist/createBackend.d.ts.map +1 -1
  84. package/dist/createBackend.js +41 -6
  85. package/dist/createEntrypoint.d.ts.map +1 -1
  86. package/dist/createEntrypoint.js +5 -3
  87. package/dist/entitlement/allowance.d.ts +25 -0
  88. package/dist/entitlement/allowance.d.ts.map +1 -0
  89. package/dist/entitlement/allowance.js +80 -0
  90. package/dist/entitlement/entitlement.d.ts +41 -0
  91. package/dist/entitlement/entitlement.d.ts.map +1 -1
  92. package/dist/entitlement/meter.d.ts +57 -0
  93. package/dist/entitlement/meter.d.ts.map +1 -0
  94. package/dist/entitlement/meter.js +13 -0
  95. package/dist/entitlement/require.js +1 -1
  96. package/dist/error/cause.d.ts +16 -116
  97. package/dist/error/cause.d.ts.map +1 -1
  98. package/dist/error/cause.js +19 -153
  99. package/dist/error/http.d.ts +184 -0
  100. package/dist/error/http.d.ts.map +1 -1
  101. package/dist/error/payload.d.ts +368 -0
  102. package/dist/error/payload.d.ts.map +1 -1
  103. package/dist/error/payload.js +126 -4
  104. package/dist/error/pithyError.d.ts +44 -0
  105. package/dist/error/pithyError.d.ts.map +1 -1
  106. package/dist/error/pithyError.js +56 -1
  107. package/dist/error/unresolved.d.ts +265 -0
  108. package/dist/error/unresolved.d.ts.map +1 -0
  109. package/dist/error/unresolved.js +599 -0
  110. package/dist/error/wire.d.ts +20 -0
  111. package/dist/error/wire.d.ts.map +1 -0
  112. package/dist/error/wire.js +19 -0
  113. package/dist/http/authContext.d.ts +7 -0
  114. package/dist/http/authContext.d.ts.map +1 -1
  115. package/dist/http/authContext.js +8 -1
  116. package/dist/http/oidcWebhook.d.ts +3 -3
  117. package/dist/http/oidcWebhook.js +3 -3
  118. package/dist/http/routeContract.js +1 -1
  119. package/dist/http/routeSpec.d.ts +66 -0
  120. package/dist/http/routeSpec.d.ts.map +1 -0
  121. package/dist/http/routeSpec.js +40 -0
  122. package/dist/http/sameOrigin.d.ts +1 -1
  123. package/dist/http/sameOrigin.js +1 -1
  124. package/dist/http/scopeGates.d.ts +20 -0
  125. package/dist/http/scopeGates.d.ts.map +1 -0
  126. package/dist/http/scopeGates.js +36 -0
  127. package/dist/http/signedWebhook.d.ts +3 -3
  128. package/dist/http/signedWebhook.js +3 -3
  129. package/dist/http/socket.d.ts +52 -0
  130. package/dist/http/socket.d.ts.map +1 -0
  131. package/dist/http/socket.js +2 -0
  132. package/dist/http/subprotocol.d.ts +29 -0
  133. package/dist/http/subprotocol.d.ts.map +1 -0
  134. package/dist/http/subprotocol.js +37 -0
  135. package/dist/http/validation.d.ts +40 -8
  136. package/dist/http/validation.d.ts.map +1 -1
  137. package/dist/http/validation.js +25 -1
  138. package/dist/index.d.ts +1 -1
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js +4 -4
  141. package/dist/logger/record.d.ts +107 -0
  142. package/dist/logger/record.d.ts.map +1 -1
  143. package/dist/migrations/bookkeeping.d.ts +21 -0
  144. package/dist/migrations/bookkeeping.d.ts.map +1 -1
  145. package/dist/migrations/bookkeeping.js +32 -3
  146. package/dist/migrations/capabilitySets.d.ts +23 -0
  147. package/dist/migrations/capabilitySets.d.ts.map +1 -0
  148. package/dist/migrations/capabilitySets.js +74 -0
  149. package/dist/migrations/groups.d.ts +140 -0
  150. package/dist/migrations/groups.d.ts.map +1 -0
  151. package/dist/migrations/groups.js +191 -0
  152. package/dist/migrations/migrateStep.d.ts +13 -0
  153. package/dist/migrations/migrateStep.d.ts.map +1 -0
  154. package/dist/migrations/migrateStep.js +20 -0
  155. package/dist/migrations/owner.d.ts +7 -0
  156. package/dist/migrations/owner.d.ts.map +1 -1
  157. package/dist/migrations/owner.js +11 -7
  158. package/dist/migrations/registry.d.ts +26 -0
  159. package/dist/migrations/registry.d.ts.map +1 -1
  160. package/dist/migrations/registry.js +38 -7
  161. package/dist/migrations/retained.js +2 -2
  162. package/dist/migrations/runner.d.ts +76 -5
  163. package/dist/migrations/runner.d.ts.map +1 -1
  164. package/dist/migrations/runner.js +130 -19
  165. package/dist/migrations/state.d.ts +60 -0
  166. package/dist/migrations/state.d.ts.map +1 -0
  167. package/dist/migrations/state.js +46 -0
  168. package/dist/naming/domains.d.ts +17 -0
  169. package/dist/naming/domains.d.ts.map +1 -1
  170. package/dist/naming/domains.js +62 -4
  171. package/dist/naming/environment.d.ts.map +1 -1
  172. package/dist/naming/environment.js +2 -1
  173. package/dist/naming/feature.d.ts +27 -5
  174. package/dist/naming/feature.d.ts.map +1 -1
  175. package/dist/naming/feature.js +36 -6
  176. package/dist/naming/limits.d.ts +47 -3
  177. package/dist/naming/limits.d.ts.map +1 -1
  178. package/dist/naming/limits.js +47 -3
  179. package/dist/naming/packageName.d.ts +17 -0
  180. package/dist/naming/packageName.d.ts.map +1 -0
  181. package/dist/naming/packageName.js +22 -0
  182. package/dist/naming/provisionScope.d.ts +6 -0
  183. package/dist/naming/provisionScope.d.ts.map +1 -1
  184. package/dist/naming/provisionScope.js +10 -4
  185. package/dist/naming/resourceNames.d.ts +21 -2
  186. package/dist/naming/resourceNames.d.ts.map +1 -1
  187. package/dist/naming/resourceNames.js +21 -3
  188. package/dist/openapi/document.d.ts +106 -0
  189. package/dist/openapi/document.d.ts.map +1 -0
  190. package/dist/openapi/document.js +314 -0
  191. package/dist/openapi/routeGroups.d.ts +8 -0
  192. package/dist/openapi/routeGroups.d.ts.map +1 -0
  193. package/dist/openapi/routeGroups.js +23 -0
  194. package/dist/seed/writeD1.d.ts +7 -1
  195. package/dist/seed/writeD1.d.ts.map +1 -1
  196. package/dist/seed/writeD1.js +9 -3
  197. package/dist/seed/writeKv.d.ts +2 -0
  198. package/dist/seed/writeKv.d.ts.map +1 -1
  199. package/dist/seed/writeKv.js +6 -2
  200. package/dist/text/comments.js +66 -4
  201. package/dist/text/literals.d.ts +43 -0
  202. package/dist/text/literals.d.ts.map +1 -0
  203. package/dist/text/literals.js +293 -0
  204. package/dist/version.generated.d.ts +1 -1
  205. package/dist/version.generated.d.ts.map +1 -1
  206. package/dist/version.generated.js +1 -1
  207. package/dist/worker/serviceLoopback.d.ts +36 -0
  208. package/dist/worker/serviceLoopback.d.ts.map +1 -0
  209. package/dist/worker/serviceLoopback.js +74 -0
  210. package/dist/workflow/dispatchRoute.d.ts.map +1 -1
  211. package/dist/workflow/dispatchRoute.js +12 -6
  212. package/dist/workflow/faults.d.ts +12 -1
  213. package/dist/workflow/faults.d.ts.map +1 -1
  214. package/dist/workflow/faults.js +24 -18
  215. package/dist/workflow/hostEnv.d.ts +1 -0
  216. package/dist/workflow/hostEnv.d.ts.map +1 -1
  217. package/dist/workflow/hostEnv.js +4 -2
  218. package/dist/workflow/loopback.d.ts.map +1 -1
  219. package/dist/workflow/loopback.js +2 -1
  220. package/package.json +4 -4
  221. package/src/access/access.ts +298 -0
  222. package/src/access/acting.ts +59 -0
  223. package/src/access/policyCheck.ts +77 -0
  224. package/src/access/usage.ts +141 -0
  225. package/src/access/visibility.ts +109 -0
  226. package/src/asyncapi/document.ts +164 -0
  227. package/src/audit/auditEvent.ts +4 -0
  228. package/src/capability/bindings.ts +159 -13
  229. package/src/capability/capability.ts +106 -29
  230. package/src/capability/composition.ts +2 -1
  231. package/src/capability/settings.ts +165 -4
  232. package/src/cli/command.ts +865 -0
  233. package/src/cli/commandCatalog.generated.ts +2723 -0
  234. package/src/cli/commandFacts.ts +220 -0
  235. package/src/controlPlane/capability.ts +25 -1
  236. package/src/controlPlane/context.ts +4 -0
  237. package/src/controlPlane/data/connection.ts +90 -0
  238. package/src/controlPlane/error/errors.ts +10 -3
  239. package/src/controlPlane/http/cors.ts +4 -4
  240. package/src/controlPlane/http/guard.ts +35 -2
  241. package/src/controlPlane/http/handlers.ts +41 -0
  242. package/src/controlPlane/http/readiness.ts +50 -0
  243. package/src/controlPlane/http/responses.ts +140 -2
  244. package/src/controlPlane/http/routes.ts +82 -10
  245. package/src/controlPlane/http/verify.ts +2 -0
  246. package/src/controlPlane/migrationState/migrationState.ts +200 -0
  247. package/src/controlPlane/migrations/0003_connection_principal.ts +37 -0
  248. package/src/controlPlane/scope/scope.ts +14 -2
  249. package/src/createBackend.ts +59 -8
  250. package/src/createEntrypoint.ts +9 -3
  251. package/src/entitlement/allowance.ts +85 -0
  252. package/src/entitlement/entitlement.ts +38 -0
  253. package/src/entitlement/meter.ts +69 -0
  254. package/src/entitlement/require.ts +1 -1
  255. package/src/error/cause.ts +18 -181
  256. package/src/error/payload.ts +270 -4
  257. package/src/error/pithyError.ts +76 -0
  258. package/src/error/unresolved.ts +853 -0
  259. package/src/error/wire.ts +40 -0
  260. package/src/http/authContext.ts +13 -1
  261. package/src/http/oidcWebhook.ts +3 -3
  262. package/src/http/routeContract.ts +1 -1
  263. package/src/http/routeSpec.ts +81 -0
  264. package/src/http/sameOrigin.ts +1 -1
  265. package/src/http/scopeGates.ts +34 -0
  266. package/src/http/signedWebhook.ts +3 -3
  267. package/src/http/socket.ts +55 -0
  268. package/src/http/subprotocol.ts +47 -0
  269. package/src/http/validation.ts +50 -8
  270. package/src/index.ts +1 -0
  271. package/src/migrations/bookkeeping.ts +39 -2
  272. package/src/migrations/capabilitySets.ts +87 -0
  273. package/src/migrations/groups.ts +295 -0
  274. package/src/migrations/migrateStep.ts +19 -0
  275. package/src/migrations/owner.ts +10 -6
  276. package/src/migrations/registry.ts +46 -2
  277. package/src/migrations/retained.ts +2 -2
  278. package/src/migrations/runner.ts +214 -22
  279. package/src/migrations/state.ts +94 -0
  280. package/src/naming/domains.ts +75 -16
  281. package/src/naming/environment.ts +2 -1
  282. package/src/naming/feature.ts +46 -7
  283. package/src/naming/limits.ts +35 -3
  284. package/src/naming/packageName.ts +20 -0
  285. package/src/naming/provisionScope.ts +21 -2
  286. package/src/naming/resourceNames.ts +52 -5
  287. package/src/openapi/document.ts +412 -0
  288. package/src/openapi/routeGroups.ts +25 -0
  289. package/src/seed/writeD1.ts +13 -4
  290. package/src/seed/writeKv.ts +6 -2
  291. package/src/text/comments.ts +56 -4
  292. package/src/text/literals.ts +294 -0
  293. package/src/version.generated.ts +1 -1
  294. package/src/worker/serviceLoopback.ts +105 -0
  295. package/src/workflow/dispatchRoute.ts +13 -6
  296. package/src/workflow/faults.ts +56 -27
  297. package/src/workflow/hostEnv.ts +9 -1
  298. package/src/workflow/loopback.ts +2 -1
@@ -14,11 +14,18 @@ import { z } from "zod";
14
14
  * stored choice outrank their device's `Accept-Language`. That link read `c.var.locale` at first,
15
15
  * which nothing ever wrote, so it silently contributed nothing and the chain degraded to
16
16
  * `param → cookie → header → default` while the docs said otherwise.
17
+ *
18
+ * **`scopes` answers one question: did this request come from the adopter's own trusted backend, or from a
19
+ * device?** The name reads like a grant to the user, and it is not one. Tenancy roles went to `c.var.acting`
20
+ * with `@pithy-sh/organization`; management access is the control plane's, with its own credential and its
21
+ * own scope type. What is left is an attestation about the caller, which `@pithy-sh/auth`'s `resolveScopes`
22
+ * makes on every request from something the request proves — a service binding, a verified client
23
+ * certificate, a short-lived token bound to this origin — and never from anything it merely says (#560).
17
24
  */
18
25
  const AuthContext = z.object({
19
26
  userId: z.string().describe("ID of the authenticated user (populated by @pithy-sh/auth)."),
20
27
  sessionId: z.string().describe("ID of the active session this request belongs to."),
21
- scopes: z.array(z.string()).default([]).describe("Permission scopes granted to this session."),
28
+ scopes: z.array(z.string()).default([]).describe("Attestations about the caller, not permissions granted to the user: what this request's caller was established to be — the adopter's own trusted backend, say — by the `resolveScopes` seam on `@pithy-sh/auth`, resolved per request and never cached. Empty when no resolver is configured. Tenancy roles are not here; they are `c.var.acting`."),
22
29
  locale: Locale.nullish().describe("The reader's own stored language, from `pithy_auth_users.locale`; null or absent when they have never chosen."),
23
30
  authenticatedAt: z.date().nullish().describe("When the holder of this session last actually authenticated — a sign-in, never a rotation, which carries the value forward unchanged. It rides here for the same reason `locale` does: the session lookup has already loaded the row, so a gate that needs it should not pay for a second read. Distinct from the session's own `createdAt`, which `/token/rotate` stamps fresh and is therefore resettable by anyone holding a stolen refresh token. Null or absent for a session written before the column existed, and for a Worker whose auth capability predates it.")
24
31
  }).describe("Per-request authenticated identity; the seam other capabilities depend on.");
@@ -295,8 +295,8 @@ export interface OidcWebhookGuardOptions extends OidcWebhookScheme {
295
295
  */
296
296
  export declare function verifyOidcToken(token: string, options: VerifyOidcTokenOptions): Promise<OidcClaims>;
297
297
  /**
298
- * The OIDC gate, as a route wears it: `app.post(path, requireOidcWebhook({…}), zValidator("json", Body,
299
- * validationHook), handler)`.
298
+ * The OIDC gate, as a route wears it: `app.post(path, requireOidcWebhook({…}), validate("json", Body),
299
+ * handler)`.
300
300
  *
301
301
  * ## Why the header is read before anything else
302
302
  *
@@ -332,7 +332,7 @@ export declare function verifyOidcToken(token: string, options: VerifyOidcTokenO
332
332
  * null-check.
333
333
  *
334
334
  * The body is never read here — unlike the HMAC gate, whose proof covers the bytes. An OIDC token proves who
335
- * sent the request and says nothing about what they sent, so the route's `zValidator` is the only thing that
335
+ * sent the request and says nothing about what they sent, so the route's `validate` is the only thing that
336
336
  * touches the body, and it sees an untouched stream.
337
337
  */
338
338
  export declare function requireOidcWebhook(options: OidcWebhookGuardOptions): MiddlewareHandler<PithyHonoEnv>;
@@ -234,8 +234,8 @@ async function verifyOidcToken(token, options) {
234
234
  return claims;
235
235
  }
236
236
  /**
237
- * The OIDC gate, as a route wears it: `app.post(path, requireOidcWebhook({…}), zValidator("json", Body,
238
- * validationHook), handler)`.
237
+ * The OIDC gate, as a route wears it: `app.post(path, requireOidcWebhook({…}), validate("json", Body),
238
+ * handler)`.
239
239
  *
240
240
  * ## Why the header is read before anything else
241
241
  *
@@ -271,7 +271,7 @@ async function verifyOidcToken(token, options) {
271
271
  * null-check.
272
272
  *
273
273
  * The body is never read here — unlike the HMAC gate, whose proof covers the bytes. An OIDC token proves who
274
- * sent the request and says nothing about what they sent, so the route's `zValidator` is the only thing that
274
+ * sent the request and says nothing about what they sent, so the route's `validate` is the only thing that
275
275
  * touches the body, and it sees an untouched stream.
276
276
  */
277
277
  function requireOidcWebhook(options) {
@@ -35,7 +35,7 @@ function probeContext(record) {
35
35
  set: () => {}
36
36
  };
37
37
  }
38
- /** Does this middleware read `c.req.param()` — i.e. is it a `zValidator("param", …)`? */
38
+ /** Does this middleware read `c.req.param()` — i.e. is it a `validate("param", …)`? */
39
39
  async function readsPathParams(handler) {
40
40
  let read = false;
41
41
  try {
@@ -0,0 +1,66 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { MiddlewareHandler } from "hono";
4
+ import type { z } from "zod";
5
+ import type { VerificationStrategy } from "./verification";
6
+ /**
7
+ * The third half of a route declaration. `validate` says what a caller may send and the guards say who
8
+ * may send it; this says what the route is for, how its caller is verified, and what comes back. It is
9
+ * the part the OpenAPI contract (`../openapi/document.ts`) cannot read from anywhere else: a guard is an
10
+ * anonymous closure, and a handler's `c.json(… satisfies Response)` is a compile-time check that leaves
11
+ * nothing behind at runtime.
12
+ *
13
+ * It sits first on the route line, ahead of the guards, and does nothing when it runs but call `next`.
14
+ * Its whole value is the spec it carries under {@link ROUTE_SPEC}, which a reader of the composed app
15
+ * finds by walking `app.routes`.
16
+ *
17
+ * ```ts
18
+ * app.get(
19
+ * `${base}/:board/top`,
20
+ * describeRoute({ summary: "Read the top of a board.", verification: ["bearer", "session"], responses: { 200: TopPage } }),
21
+ * requireAuth(),
22
+ * validate("param", BoardParam),
23
+ * handler,
24
+ * );
25
+ * ```
26
+ */
27
+ /** The key a {@link describeRoute} middleware carries its spec under. Global, so two copies of core agree. */
28
+ export declare const ROUTE_SPEC: unique symbol;
29
+ /** One response a route can give that is not a JSON body: bytes, a redirect, HTML, or nothing at all. */
30
+ export interface RouteResponse {
31
+ /** What this response means, in one sentence. */
32
+ readonly description: string;
33
+ /** The JSON body, when there is one. Its input side is the wire: what a client parses with it. */
34
+ readonly schema?: z.ZodType;
35
+ /** The media type when the body is not JSON, e.g. `text/html` or `application/octet-stream`. */
36
+ readonly contentType?: string;
37
+ }
38
+ /** The HTTP methods an `app.all` route is documented under. OpenAPI has no "any method". */
39
+ export type DocumentedMethod = "get" | "post" | "put" | "patch" | "delete" | "head" | "options";
40
+ /** What a route is for, how its caller is verified, and what it returns. */
41
+ export interface RouteSpec {
42
+ /** One sentence, in the brand voice. The operation's title in the contract. */
43
+ readonly summary: string;
44
+ /** Anything a client author must know that the schemas do not say. */
45
+ readonly description?: string;
46
+ /**
47
+ * How the caller is verified. A list when the route takes more than one — `requireAuth()` accepts a
48
+ * bearer token or a session, so most authenticated routes declare both.
49
+ */
50
+ readonly verification: VerificationStrategy | readonly VerificationStrategy[];
51
+ /** Scopes the caller's credential must carry, beyond being verified. */
52
+ readonly scopes?: readonly string[];
53
+ /**
54
+ * Every success response, by status. A Zod schema is a JSON body described by the schema itself; a
55
+ * {@link RouteResponse} covers everything else. Errors are not listed: every route can answer with the
56
+ * one `PithyError` body, and the contract says so once.
57
+ */
58
+ readonly responses: Readonly<Record<number, z.ZodType | RouteResponse>>;
59
+ /** For an `app.all` route only: the methods it is documented under. */
60
+ readonly methods?: readonly DocumentedMethod[];
61
+ }
62
+ /** Declare what a route is for, how its caller is verified, and what it returns. See {@link RouteSpec}. */
63
+ export declare function describeRoute(spec: RouteSpec): MiddlewareHandler;
64
+ /** The spec a {@link describeRoute} middleware carries, or `undefined` for any other handler. */
65
+ export declare function routeSpecOf(handler: unknown): RouteSpec | undefined;
66
+ //# sourceMappingURL=routeSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routeSpec.d.ts","sourceRoot":"","sources":["../../src/http/routeSpec.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,8GAA8G;AAC9G,eAAO,MAAM,UAAU,eAAgC,CAAC;AAExD,yGAAyG;AACzG,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAC5B,gGAAgG;IAChG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,4FAA4F;AAC5F,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhG,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,oBAAoB,GAAG,SAAS,oBAAoB,EAAE,CAAC;IAC9E,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC;IACxE,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAChD;AAED,2GAA2G;AAC3G,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,iBAAiB,CAKhE;AAED,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAGnE"}
@@ -0,0 +1,40 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ //#region src/http/routeSpec.ts
4
+ /**
5
+ * The third half of a route declaration. `validate` says what a caller may send and the guards say who
6
+ * may send it; this says what the route is for, how its caller is verified, and what comes back. It is
7
+ * the part the OpenAPI contract (`../openapi/document.ts`) cannot read from anywhere else: a guard is an
8
+ * anonymous closure, and a handler's `c.json(… satisfies Response)` is a compile-time check that leaves
9
+ * nothing behind at runtime.
10
+ *
11
+ * It sits first on the route line, ahead of the guards, and does nothing when it runs but call `next`.
12
+ * Its whole value is the spec it carries under {@link ROUTE_SPEC}, which a reader of the composed app
13
+ * finds by walking `app.routes`.
14
+ *
15
+ * ```ts
16
+ * app.get(
17
+ * `${base}/:board/top`,
18
+ * describeRoute({ summary: "Read the top of a board.", verification: ["bearer", "session"], responses: { 200: TopPage } }),
19
+ * requireAuth(),
20
+ * validate("param", BoardParam),
21
+ * handler,
22
+ * );
23
+ * ```
24
+ */
25
+ /** The key a {@link describeRoute} middleware carries its spec under. Global, so two copies of core agree. */
26
+ const ROUTE_SPEC = Symbol.for("pithy.routeSpec");
27
+ /** Declare what a route is for, how its caller is verified, and what it returns. See {@link RouteSpec}. */
28
+ function describeRoute(spec) {
29
+ const middleware = async (_c, next) => {
30
+ await next();
31
+ };
32
+ return Object.assign(middleware, { [ROUTE_SPEC]: spec });
33
+ }
34
+ /** The spec a {@link describeRoute} middleware carries, or `undefined` for any other handler. */
35
+ function routeSpecOf(handler) {
36
+ if (typeof handler !== "function") return void 0;
37
+ return handler[ROUTE_SPEC];
38
+ }
39
+ //#endregion
40
+ export { ROUTE_SPEC, describeRoute, routeSpecOf };
@@ -40,7 +40,7 @@ export type SameOriginGate = MiddlewareHandler<PithyHonoEnv>;
40
40
  * Require this request to come from an origin the Worker trusts — the CSRF gate a cookie-authenticated
41
41
  * mutating route wears, beside the strategy that says who the caller is.
42
42
  *
43
- * app.post("/organizations", requireAuth(), requireSameOrigin(), zValidator(…), handler)
43
+ * app.post("/organizations", requireAuth(), requireSameOrigin(), validate(…), handler)
44
44
  *
45
45
  * A bearer request carries no ambient credential and is CSRF-exempt; that exemption belongs to the
46
46
  * published gate, since the capability that owns the credential model is the one that knows.
@@ -6,7 +6,7 @@ import { ForbiddenError } from "../error/pithyError.js";
6
6
  * Require this request to come from an origin the Worker trusts — the CSRF gate a cookie-authenticated
7
7
  * mutating route wears, beside the strategy that says who the caller is.
8
8
  *
9
- * app.post("/organizations", requireAuth(), requireSameOrigin(), zValidator(…), handler)
9
+ * app.post("/organizations", requireAuth(), requireSameOrigin(), validate(…), handler)
10
10
  *
11
11
  * A bearer request carries no ambient credential and is CSRF-exempt; that exemption belongs to the
12
12
  * published gate, since the capability that owns the credential model is the one that knows.
@@ -0,0 +1,20 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { Hono } from "hono";
4
+ import type { PithyHonoEnv, ScopeGate } from "../capability/capability";
5
+ /**
6
+ * **A capability's scope gates, read off its own routes rather than listed beside them (#560).**
7
+ *
8
+ * `Capability.scopeGates` is what `pithy doctor` names when nothing composed fills `AuthContext.scopes`.
9
+ * A list written out by hand next to the routes is a second copy of something the routes already say: every
10
+ * gated route carries `describeRoute({ scopes })`, because that is how the OpenAPI contract learns it. So the
11
+ * registrar is run against a throwaway app and the specs are read back — the same walk `openApiDocument`
12
+ * makes over a composed app, and a route whose scope changes changes what this returns with it.
13
+ *
14
+ * **Only `bearer` and `session` routes.** Those are the strategies `requireAuth()` takes, so a scope on one
15
+ * is an `AuthContext` scope. A `control-plane` route also names scopes, and they are a different credential
16
+ * entirely — a signed management token with its own type, never `c.var.auth` — so naming one here would tell
17
+ * an adopter to configure a resolver that could never open it.
18
+ */
19
+ export declare function scopeGatesOf(register: (app: Hono<PithyHonoEnv>) => void): ScopeGate[];
20
+ //# sourceMappingURL=scopeGates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopeGates.d.ts","sourceRoot":"","sources":["../../src/http/scopeGates.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAGxE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,GAAG,SAAS,EAAE,CAYrF"}
@@ -0,0 +1,36 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { routeSpecOf } from "./routeSpec.js";
4
+ import { Hono } from "hono";
5
+ //#region src/http/scopeGates.ts
6
+ /**
7
+ * **A capability's scope gates, read off its own routes rather than listed beside them (#560).**
8
+ *
9
+ * `Capability.scopeGates` is what `pithy doctor` names when nothing composed fills `AuthContext.scopes`.
10
+ * A list written out by hand next to the routes is a second copy of something the routes already say: every
11
+ * gated route carries `describeRoute({ scopes })`, because that is how the OpenAPI contract learns it. So the
12
+ * registrar is run against a throwaway app and the specs are read back — the same walk `openApiDocument`
13
+ * makes over a composed app, and a route whose scope changes changes what this returns with it.
14
+ *
15
+ * **Only `bearer` and `session` routes.** Those are the strategies `requireAuth()` takes, so a scope on one
16
+ * is an `AuthContext` scope. A `control-plane` route also names scopes, and they are a different credential
17
+ * entirely — a signed management token with its own type, never `c.var.auth` — so naming one here would tell
18
+ * an adopter to configure a resolver that could never open it.
19
+ */
20
+ function scopeGatesOf(register) {
21
+ const app = new Hono();
22
+ register(app);
23
+ const gates = [];
24
+ for (const route of app.routes) {
25
+ const spec = routeSpecOf(route.handler);
26
+ if (!spec?.scopes || spec.scopes.length === 0) continue;
27
+ if (![spec.verification].flat().some((strategy) => strategy === "bearer" || strategy === "session")) continue;
28
+ for (const scope of spec.scopes) gates.push({
29
+ scope,
30
+ route: `${route.method} ${route.path}`
31
+ });
32
+ }
33
+ return gates;
34
+ }
35
+ //#endregion
36
+ export { scopeGatesOf };
@@ -139,8 +139,8 @@ export declare function checkSignedWebhook(body: string, header: string | null,
139
139
  */
140
140
  export declare function verifySignedWebhook(body: string, header: string | null, options: VerifySignedWebhookOptions): Promise<void>;
141
141
  /**
142
- * The `signed-webhook` gate, as a route wears it: `app.post(path, requireSignedWebhook({…}), zValidator("json",
143
- * Body, validationHook), handler)`.
142
+ * The `signed-webhook` gate, as a route wears it: `app.post(path, requireSignedWebhook({…}), validate("json",
143
+ * Body), handler)`.
144
144
  *
145
145
  * ## Why the header is read before anything else
146
146
  *
@@ -164,7 +164,7 @@ export declare function verifySignedWebhook(body: string, header: string | null,
164
164
  * `c.req.text()` is not.
165
165
  *
166
166
  * `c.req.text()` rather than `c.req.raw.text()`, and the distinction is load-bearing. Hono caches its own
167
- * body reads and serves `c.req.json()` out of the cached text, so a `zValidator("json", …)` after this gate
167
+ * body reads and serves `c.req.json()` out of the cached text, so a `validate("json", …)` after this gate
168
168
  * parses identical bytes. Reading `c.req.raw` would consume the stream and leave the validator with nothing.
169
169
  */
170
170
  export declare function requireSignedWebhook(options: SignedWebhookGuardOptions): MiddlewareHandler<PithyHonoEnv>;
@@ -206,8 +206,8 @@ async function verifySignedWebhook(body, header, options) {
206
206
  if (refusal !== void 0) throw webhookRefusal(refusal, options);
207
207
  }
208
208
  /**
209
- * The `signed-webhook` gate, as a route wears it: `app.post(path, requireSignedWebhook({…}), zValidator("json",
210
- * Body, validationHook), handler)`.
209
+ * The `signed-webhook` gate, as a route wears it: `app.post(path, requireSignedWebhook({…}), validate("json",
210
+ * Body), handler)`.
211
211
  *
212
212
  * ## Why the header is read before anything else
213
213
  *
@@ -231,7 +231,7 @@ async function verifySignedWebhook(body, header, options) {
231
231
  * `c.req.text()` is not.
232
232
  *
233
233
  * `c.req.text()` rather than `c.req.raw.text()`, and the distinction is load-bearing. Hono caches its own
234
- * body reads and serves `c.req.json()` out of the cached text, so a `zValidator("json", …)` after this gate
234
+ * body reads and serves `c.req.json()` out of the cached text, so a `validate("json", …)` after this gate
235
235
  * parses identical bytes. Reading `c.req.raw` would consume the stream and leave the validator with nothing.
236
236
  */
237
237
  function requireSignedWebhook(options) {
@@ -0,0 +1,52 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { z } from "zod";
4
+ import type { SocketVersions } from "./subprotocol";
5
+ import type { VerificationStrategy } from "./verification";
6
+ /**
7
+ * **A WebSocket, described for the AsyncAPI contract.** The socket's counterpart of `describeRoute`.
8
+ *
9
+ * OpenAPI cannot describe a socket: after the upgrade there are no requests, only frames each way. So a
10
+ * capability that serves one declares it on its `sockets` contribution, built from its resolved config —
11
+ * the path it mounted, how a caller is verified, the versions it serves, and a Zod schema for every frame
12
+ * each side may send. `../asyncapi/document.ts` reads the declarations into an AsyncAPI document, the way
13
+ * `../openapi/document.ts` reads the route table into OpenAPI.
14
+ *
15
+ * The schemas are the ones the socket parses with, never a copy: a frame the Worker receives is parsed with
16
+ * the schema declared here, so the document and the socket cannot disagree about what is accepted.
17
+ */
18
+ /** One frame one side of a socket may send. */
19
+ export interface SocketMessage {
20
+ /** The frame's name, unique within its socket: `state`, `error`, `action`. */
21
+ readonly name: string;
22
+ /** What the frame is, in one sentence. */
23
+ readonly summary: string;
24
+ /** The frame's JSON body. Absent for a plain-text frame. */
25
+ readonly schema?: z.ZodType;
26
+ /** A plain-text frame's exact text — `ping`, `pong`. The one exception to JSON frames, stated per frame. */
27
+ readonly text?: string;
28
+ }
29
+ /** One WebSocket a capability serves. */
30
+ export interface SocketChannel {
31
+ /** A stable name for the socket, unique in the Worker: `multiplayerSession`, `matchmakingPresence`. */
32
+ readonly name: string;
33
+ /** The upgrade path, as Hono registered it: `/multiplayer/sessions/:id/socket`. */
34
+ readonly path: string;
35
+ /** What the socket is for, in one sentence. */
36
+ readonly summary: string;
37
+ /** Anything a client author must know that the schemas do not say. */
38
+ readonly description?: string;
39
+ /** How the upgrade request is verified. */
40
+ readonly verification: VerificationStrategy | readonly VerificationStrategy[];
41
+ /** The path parameters' schema — the same one the upgrade route validates them with. */
42
+ readonly params?: z.ZodType;
43
+ /** The subprotocol versions this socket serves. */
44
+ readonly versions: SocketVersions;
45
+ /** Every frame the Worker sends. */
46
+ readonly send: readonly SocketMessage[];
47
+ /** Every frame the Worker accepts. Anything else is answered with a `validation/invalid_input` error frame. */
48
+ readonly receive: readonly SocketMessage[];
49
+ /** Further named schemas a client needs, such as each configured game's `action` and `view`. */
50
+ readonly schemas?: Readonly<Record<string, z.ZodType>>;
51
+ }
52
+ //# sourceMappingURL=socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/http/socket.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D;;;;;;;;;;;GAWG;AAEH,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAC5B,4GAA4G;IAC5G,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,uGAAuG;IACvG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2CAA2C;IAC3C,QAAQ,CAAC,YAAY,EAAE,oBAAoB,GAAG,SAAS,oBAAoB,EAAE,CAAC;IAC9E,wFAAwF;IACxF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAC;IACxC,+GAA+G;IAC/G,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,gGAAgG;IAChG,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;CACxD"}
@@ -0,0 +1,2 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
@@ -0,0 +1,29 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * **A socket's version is negotiated at the upgrade, and the Worker serves two.**
5
+ *
6
+ * An app store client lags its Worker by months — the same version boundary an admin response crosses
7
+ * (CLAUDE.md §HTTP). So a socket's frames change the way a response does: a field added to a frame is
8
+ * optional, and a client ignores a `type` and a key it does not know. A change no client could ignore is a
9
+ * new subprotocol, offered in `Sec-WebSocket-Protocol` (`pithy.multiplayer.v1`), and the Worker keeps
10
+ * serving the one before it, so a client released before the change keeps playing.
11
+ *
12
+ * The first versioned protocol's previous version is the unversioned one: a client that offers no
13
+ * subprotocol at all, which is every client written before versions existed. `previous: null` names it.
14
+ */
15
+ /** The header a client offers subprotocols in, and the server answers with the one it chose. */
16
+ export declare const SUBPROTOCOL_HEADER = "Sec-WebSocket-Protocol";
17
+ /** The versions one socket serves: the current one, and the one before it — `null` when that is unversioned. */
18
+ export interface SocketVersions {
19
+ readonly current: string;
20
+ readonly previous: string | null;
21
+ }
22
+ /**
23
+ * The version this upgrade speaks: the current one when the client offers it, else the previous one when
24
+ * the client offers that, else `null` for an unversioned client where the previous version is unversioned.
25
+ * A client that offers only versions this Worker does not serve is refused with a 400 naming the ones it
26
+ * does — a socket that opened anyway would speak frames the client cannot read.
27
+ */
28
+ export declare function negotiateSubprotocol(offered: string | null | undefined, versions: SocketVersions): string | null;
29
+ //# sourceMappingURL=subprotocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subprotocol.d.ts","sourceRoot":"","sources":["../../src/http/subprotocol.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AAEH,gGAAgG;AAChG,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAE3D,gHAAgH;AAChH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAahH"}
@@ -0,0 +1,37 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { ValidationError } from "../error/pithyError.js";
4
+ //#region src/http/subprotocol.ts
5
+ /**
6
+ * **A socket's version is negotiated at the upgrade, and the Worker serves two.**
7
+ *
8
+ * An app store client lags its Worker by months — the same version boundary an admin response crosses
9
+ * (CLAUDE.md §HTTP). So a socket's frames change the way a response does: a field added to a frame is
10
+ * optional, and a client ignores a `type` and a key it does not know. A change no client could ignore is a
11
+ * new subprotocol, offered in `Sec-WebSocket-Protocol` (`pithy.multiplayer.v1`), and the Worker keeps
12
+ * serving the one before it, so a client released before the change keeps playing.
13
+ *
14
+ * The first versioned protocol's previous version is the unversioned one: a client that offers no
15
+ * subprotocol at all, which is every client written before versions existed. `previous: null` names it.
16
+ */
17
+ /** The header a client offers subprotocols in, and the server answers with the one it chose. */
18
+ const SUBPROTOCOL_HEADER = "Sec-WebSocket-Protocol";
19
+ /**
20
+ * The version this upgrade speaks: the current one when the client offers it, else the previous one when
21
+ * the client offers that, else `null` for an unversioned client where the previous version is unversioned.
22
+ * A client that offers only versions this Worker does not serve is refused with a 400 naming the ones it
23
+ * does — a socket that opened anyway would speak frames the client cannot read.
24
+ */
25
+ function negotiateSubprotocol(offered, versions) {
26
+ const asked = (offered ?? "").split(",").map((protocol) => protocol.trim()).filter((protocol) => protocol.length > 0);
27
+ if (asked.length === 0 && versions.previous === null) return null;
28
+ if (asked.includes(versions.current)) return versions.current;
29
+ if (versions.previous !== null && asked.includes(versions.previous)) return versions.previous;
30
+ const served = [versions.current, ...versions.previous === null ? [] : [versions.previous]].join(", ");
31
+ throw new ValidationError({
32
+ message: `This socket speaks ${served}${versions.previous === null ? ", or no subprotocol" : ""}. The client offered ${asked.length === 0 ? "none" : asked.join(", ")}.`,
33
+ action: `Offer ${versions.current} in ${SUBPROTOCOL_HEADER}.`
34
+ });
35
+ }
36
+ //#endregion
37
+ export { SUBPROTOCOL_HEADER, negotiateSubprotocol };
@@ -1,5 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
+ import type { ValidationTargets } from "hono";
3
4
  import type { z } from "zod";
4
5
  /**
5
6
  * How a route declares what it accepts. Its sibling {@link ./verification.VerificationStrategy}
@@ -7,15 +8,21 @@ import type { z } from "zod";
7
8
  * mandatory, and both belong on the route line — reading a route tells you what it takes without
8
9
  * opening the handler.
9
10
  *
10
- * Routes use `zValidator(target, Schema, validationHook)` from `@hono/zod-validator`. The library
11
- * owns the middleware and the `c.req.valid(target)` typing; this file owns exactly one thing —
12
- * what happens when the parse fails. Without the hook, zod-validator answers with its own JSON
13
- * body, which is neither a `PithyError` payload nor routed through `pithyErrorHandler`. With it,
14
- * a malformed request is indistinguishable from every other failure the app can produce.
11
+ * Routes use `validate(target, Schema)`. `@hono/zod-validator` owns the middleware and the
12
+ * `c.req.valid(target)` typing; this file owns two things — what happens when the parse fails, and
13
+ * leaving the schema readable on the middleware afterwards.
15
14
  *
16
- * The hook is deliberately not a wrapper around `zValidator`. A wrapper would be a second name for
17
- * a library we do not re-export (CLAUDE.md §Toolchain), and it would hide the target from the route
18
- * line — the one thing the migration exists to make visible.
15
+ * The failure: without {@link validationHook}, zod-validator answers with its own JSON body, which is
16
+ * neither a `PithyError` payload nor routed through `pithyErrorHandler`. With it, a malformed request
17
+ * is indistinguishable from every other failure the app can produce.
18
+ *
19
+ * The schema: `zValidator` returns the same anonymous closure whatever it validates, so once a route is
20
+ * registered nothing can tell which schema it holds. That was tolerable while the only reader was the
21
+ * route line. The OpenAPI contract (`../openapi/document.ts`) is a second reader, and it reads the
22
+ * composed app, so the schema has to survive registration. `validate` is `zValidator` with the hook
23
+ * applied and the `{ target, schema }` pair attached under {@link REQUEST_CONTRACT}. It was once kept
24
+ * out of here on purpose, as a second name for a library; it earns the name now by carrying the one
25
+ * thing the library drops. The target stays the first argument, so the route line reads as before.
19
26
  */
20
27
  /**
21
28
  * The outcome `@hono/zod-validator` hands a hook. Declared structurally rather than by importing
@@ -36,4 +43,29 @@ export type ValidationOutcome = {
36
43
  * validation is declared, not what a caller sees when it fails.
37
44
  */
38
45
  export declare function validationHook(outcome: ValidationOutcome): void;
46
+ /** The key a {@link validate} middleware carries its contract under. Global, so two copies of core agree. */
47
+ export declare const REQUEST_CONTRACT: unique symbol;
48
+ /** What one validator declares: the part of the request it reads, and the schema that part must satisfy. */
49
+ export interface RequestContract {
50
+ /** The request part, as Hono names it: `json`, `query`, `param`, `header`, `cookie` or `form`. */
51
+ readonly target: keyof ValidationTargets;
52
+ /** The schema the part is parsed with. Its input side is what a caller sends. */
53
+ readonly schema: z.ZodType;
54
+ }
55
+ /**
56
+ * Declare what a route accepts in one part of the request, and fail a malformed one as a
57
+ * `validation/invalid_input` 400. The handler reads the parsed value with `c.req.valid(target)`.
58
+ *
59
+ * ```ts
60
+ * app.post(path, requireAuth(), validate("param", BoardParam), validate("json", SubmitScoreBody), handler);
61
+ * ```
62
+ */
63
+ export declare function validate<Target extends keyof ValidationTargets, Schema extends z.ZodType>(target: Target, schema: Schema): import("hono").MiddlewareHandler<import("hono").Env, string, {
64
+ in: (undefined extends (Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never) ? true : false) extends true ? { [K in Target]?: ([Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never] extends [ValidationTargets[K]] ? ValidationTargets[K] & (Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never) : import("hono/validator").InferInput<Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never, K, import("hono/types").ParsedFormValue>) | undefined; } : { [K in Target]: [Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never] extends [ValidationTargets[K]] ? ValidationTargets[K] & (Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never) : import("hono/validator").InferInput<Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").input<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.core.input<Schema> : never, K, import("hono/types").ParsedFormValue>; };
65
+ out: { [K in Target]: Schema extends import("zod/v3").ZodType<any, import("zod/v3").ZodTypeDef, any> ? import("zod/v3").output<Schema> : Schema extends z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>> ? z.infer<Schema> : never; };
66
+ }, never> & {
67
+ [REQUEST_CONTRACT]: RequestContract;
68
+ };
69
+ /** The contract a {@link validate} middleware carries, or `undefined` for any other handler. */
70
+ export declare function requestContractOf(handler: unknown): RequestContract | undefined;
39
71
  //# sourceMappingURL=validation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/http/validation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAA;CAAE,CAAC;AAEhG;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAG/D"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/http/validation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAA;CAAE,CAAC;AAEhG;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAG/D;AAED,6GAA6G;AAC7G,eAAO,MAAM,gBAAgB,eAAsC,CAAC;AAEpE,4GAA4G;AAC5G,MAAM,WAAW,eAAe;IAC9B,kGAAkG;IAClG,QAAQ,CAAC,MAAM,EAAE,MAAM,iBAAiB,CAAC;IACzC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,MAAM,iBAAiB,EAAE,MAAM,SAAS,CAAC,CAAC,OAAO,EACvF,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM;;;;;EAKf;AAED,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAG/E"}
@@ -1,6 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { fromZodError } from "../error/pithyError.js";
4
+ import { zValidator } from "@hono/zod-validator";
4
5
  //#region src/http/validation.ts
5
6
  /**
6
7
  * The one validation-failure hook every provided route passes to `zValidator`. Maps the `ZodError`
@@ -12,5 +13,28 @@ function validationHook(outcome) {
12
13
  if (outcome.success) return;
13
14
  throw fromZodError(outcome.error);
14
15
  }
16
+ /** The key a {@link validate} middleware carries its contract under. Global, so two copies of core agree. */
17
+ const REQUEST_CONTRACT = Symbol.for("pithy.requestContract");
18
+ /**
19
+ * Declare what a route accepts in one part of the request, and fail a malformed one as a
20
+ * `validation/invalid_input` 400. The handler reads the parsed value with `c.req.valid(target)`.
21
+ *
22
+ * ```ts
23
+ * app.post(path, requireAuth(), validate("param", BoardParam), validate("json", SubmitScoreBody), handler);
24
+ * ```
25
+ */
26
+ function validate(target, schema) {
27
+ const middleware = zValidator(target, schema, validationHook);
28
+ const contract = {
29
+ target,
30
+ schema
31
+ };
32
+ return Object.assign(middleware, { [REQUEST_CONTRACT]: contract });
33
+ }
34
+ /** The contract a {@link validate} middleware carries, or `undefined` for any other handler. */
35
+ function requestContractOf(handler) {
36
+ if (typeof handler !== "function") return void 0;
37
+ return handler[REQUEST_CONTRACT];
38
+ }
15
39
  //#endregion
16
- export { validationHook };
40
+ export { REQUEST_CONTRACT, requestContractOf, validate, validationHook };
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export { type AdminRouteDrift, missingAdminRoutes } from "./controlPlane/discove
28
28
  export { type CapabilityHealth, type CapabilityHealthInput, type CapabilityHealthSource, capabilityHealthSources, defineCapabilityHealth, readCapabilityHealth, } from "./controlPlane/discovery/health";
29
29
  export { CapabilityHealthReport, type CapabilityHealthWire, HealthCountNominal, HealthNominal, type HealthStanding, HealthSummary, HealthSummaryKey, HealthSummaryValue, HealthValueCost, HealthValueKind, healthAttention, healthReport, healthWire, type NamedHealthValue, namedHealthValues, standingOf, } from "./controlPlane/discovery/healthSummary";
30
30
  export { requireControlPlane } from "./controlPlane/http/guard";
31
- export { ANY_VERIFIED_CALLER, type ControlPlaneRequirement, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, SEAM_SCOPES, } from "./controlPlane/scope/scope";
31
+ export { ANY_VERIFIED_CALLER, type ControlPlaneRequirement, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, MIGRATIONS_READ_SCOPE, SEAM_SCOPES, } from "./controlPlane/scope/scope";
32
32
  export { exportPublicJwk, type MintControlPlaneToken, mintControlPlaneToken } from "./controlPlane/token/mint";
33
33
  export { CONTROL_PLANE_HEADER, CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER, type WorkerBuild, workerBuildChanged, } from "./controlPlane/wire";
34
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACtG,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,YAAY,EACZ,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACzG,OAAO,EACL,+BAA+B,EAC/B,KAAK,oBAAoB,EACzB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,EACL,UAAU,EACV,KAAK,gBAAgB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,GAClB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAQ1F,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,eAAe,EACf,YAAY,EACZ,UAAU,EACV,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,UAAU,GACX,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,WAAW,GACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAI/G,OAAO,EACL,oBAAoB,EACpB,oCAAoC,EACpC,4BAA4B,EAC5B,KAAK,WAAW,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACtG,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,YAAY,EACZ,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACzG,OAAO,EACL,+BAA+B,EAC/B,KAAK,oBAAoB,EACzB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,EACL,UAAU,EACV,KAAK,gBAAgB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,GAClB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAQ1F,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,eAAe,EACf,YAAY,EACZ,UAAU,EACV,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,UAAU,GACX,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,WAAW,GACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAI/G,OAAO,EACL,oBAAoB,EACpB,oCAAoC,EACpC,4BAA4B,EAC5B,KAAK,WAAW,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
- import { ANY_VERIFIED_CALLER, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, SEAM_SCOPES } from "./controlPlane/scope/scope.js";
3
+ import { ANY_VERIFIED_CALLER, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, MIGRATIONS_READ_SCOPE, SEAM_SCOPES } from "./controlPlane/scope/scope.js";
4
4
  import { ControlPlaneConfig } from "./controlPlane/config/config.js";
5
5
  import { ControlPlaneConnection, Ed25519PublicJwk, RegisteredKey } from "./controlPlane/data/connection.js";
6
6
  import { CONTROL_PLANE_CONNECTIONS_TABLE, controlPlaneDatabase } from "./controlPlane/data/tables.js";
@@ -10,10 +10,10 @@ import { ControlPlaneAuditActions } from "./controlPlane/audit/actions.js";
10
10
  import { CONTROL_PLANE_HEADER, CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER, workerBuildChanged } from "./controlPlane/wire.js";
11
11
  import { ControlPlaneContext } from "./controlPlane/context.js";
12
12
  import { requireControlPlane } from "./controlPlane/http/guard.js";
13
- import { CONTROL_PLANE_KV_BINDING } from "./controlPlane/replay/kvGuard.js";
14
- import { CONTROLPLANE_MIGRATION_ORDER, controlplane, isControlPlaneCapability } from "./controlPlane/capability.js";
15
13
  import { ManifestConfigKey, ManifestConfigValue, ManifestConfigValues, defineManifestConfig, namedConfigValues } from "./controlPlane/discovery/configuration.js";
16
14
  import { AdminRoute, CapabilityDeclaration, CapabilityDescriptor, ControlPlaneManifest } from "./controlPlane/discovery/adminRoute.js";
15
+ import { CONTROL_PLANE_KV_BINDING } from "./controlPlane/replay/kvGuard.js";
16
+ import { CONTROLPLANE_MIGRATION_ORDER, controlplane, isControlPlaneCapability } from "./controlPlane/capability.js";
17
17
  import { missingAdminRoutes } from "./controlPlane/discovery/drift.js";
18
18
  import { exportPublicJwk, mintControlPlaneToken } from "./controlPlane/token/mint.js";
19
- export { ANY_VERIFIED_CALLER, AdminRoute, CONTROLPLANE_MIGRATION_ORDER, CONTROL_PLANE_CONNECTIONS_TABLE, CONTROL_PLANE_HEADER, CONTROL_PLANE_KV_BINDING, CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER, CapabilityDeclaration, CapabilityDescriptor, CapabilityHealthReport, ControlPlaneAuditActions, ControlPlaneConfig, ControlPlaneConnection, ControlPlaneContext, ControlPlaneManifest, ControlPlaneScope, Ed25519PublicJwk, HealthCountNominal, HealthNominal, HealthSummary, HealthSummaryKey, HealthSummaryValue, HealthValueCost, HealthValueKind, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, ManifestConfigKey, ManifestConfigValue, ManifestConfigValues, RegisteredKey, SEAM_SCOPES, capabilityHealthSources, controlPlaneDatabase, controlplane, defineCapabilityHealth, defineManifestConfig, exportPublicJwk, healthAttention, healthReport, healthWire, isControlPlaneCapability, mintControlPlaneToken, missingAdminRoutes, namedConfigValues, namedHealthValues, readCapabilityHealth, requireControlPlane, standingOf, workerBuildChanged };
19
+ export { ANY_VERIFIED_CALLER, AdminRoute, CONTROLPLANE_MIGRATION_ORDER, CONTROL_PLANE_CONNECTIONS_TABLE, CONTROL_PLANE_HEADER, CONTROL_PLANE_KV_BINDING, CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER, CapabilityDeclaration, CapabilityDescriptor, CapabilityHealthReport, ControlPlaneAuditActions, ControlPlaneConfig, ControlPlaneConnection, ControlPlaneContext, ControlPlaneManifest, ControlPlaneScope, Ed25519PublicJwk, HealthCountNominal, HealthNominal, HealthSummary, HealthSummaryKey, HealthSummaryValue, HealthValueCost, HealthValueKind, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, MIGRATIONS_READ_SCOPE, ManifestConfigKey, ManifestConfigValue, ManifestConfigValues, RegisteredKey, SEAM_SCOPES, capabilityHealthSources, controlPlaneDatabase, controlplane, defineCapabilityHealth, defineManifestConfig, exportPublicJwk, healthAttention, healthReport, healthWire, isControlPlaneCapability, mintControlPlaneToken, missingAdminRoutes, namedConfigValues, namedHealthValues, readCapabilityHealth, requireControlPlane, standingOf, workerBuildChanged };