@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
@@ -1,6 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { PithyError } from "../../error/pithyError.js";
4
+ import { commandName } from "../../cli/command.js";
4
5
  //#region src/controlPlane/error/errors.ts
5
6
  /**
6
7
  * No connection is registered for this environment. The shipped default of a Worker that composes the
@@ -12,7 +13,7 @@ var ControlPlaneNotConnectedError = class extends PithyError {
12
13
  code: "controlplane/not_connected",
13
14
  status: 403,
14
15
  message: args.message ?? "No management client is connected to this environment.",
15
- action: args.action ?? "Run pithy dashboard connect --env <environment> to register one.",
16
+ action: args.action ?? `Register one for this environment with ${commandName("dashboard connect")}, naming it with --env.`,
16
17
  detail: args.detail,
17
18
  params: args.params
18
19
  }, options);
@@ -28,7 +29,7 @@ var ControlPlaneInvalidCredentialError = class extends PithyError {
28
29
  code: "controlplane/invalid_credential",
29
30
  status: 401,
30
31
  message: args.message ?? "That credential is not valid here.",
31
- action: args.action ?? "Rotate the connection's key, or re-run pithy dashboard connect --update.",
32
+ action: args.action ?? `Rotate the connection's key, or update this environment's connection with ${commandName("dashboard connect")} and its --update flag.`,
32
33
  detail: args.detail,
33
34
  params: args.params
34
35
  }, options);
@@ -41,7 +42,7 @@ var ControlPlaneInsufficientScopeError = class extends PithyError {
41
42
  code: "controlplane/insufficient_scope",
42
43
  status: 403,
43
44
  message: args.message ?? "This connection is not scoped for that operation.",
44
- action: args.action ?? "Re-run pithy dashboard connect --update and grant the scope this call needs.",
45
+ action: args.action ?? `Update this environment's connection with ${commandName("dashboard connect")} and its --update flag, granting the scope this call needs.`,
45
46
  detail: args.detail,
46
47
  params: args.params
47
48
  }, options);
@@ -93,10 +93,10 @@ export declare function corsMiddleware(entry: CorsSurfaceEntry, origins: Readonl
93
93
  /**
94
94
  * Mount one `app.use(path, …)` per entry.
95
95
  *
96
- * **Called from the capability's `middleware` hook, never its `routes` hook, and that is load-bearing.**
97
- * `createBackend` runs every capability's `middleware` hook before **any** capability's `routes` hook
98
- * (`createBackend.ts:262-267`), which is what makes this independent of the order an adopter happens to
99
- * list their capabilities in.
96
+ * **Called from `createBackend` ahead of every capability's `middleware`, never from a `routes` hook,
97
+ * and that is load-bearing.** Registered there, it is independent of the order an adopter happens to
98
+ * list their capabilities in, and it can put a header on whatever a capability's middleware or a
99
+ * readiness guard short-circuits.
100
100
  *
101
101
  * From `routes` it looks like it works and does not. The preflight still answers — an `OPTIONS` has no
102
102
  * route handler to lose to, so this `use` is the only thing that matches it — but a capability composed
@@ -170,10 +170,10 @@ function corsMiddleware(entry, origins, maxAgeSeconds) {
170
170
  /**
171
171
  * Mount one `app.use(path, …)` per entry.
172
172
  *
173
- * **Called from the capability's `middleware` hook, never its `routes` hook, and that is load-bearing.**
174
- * `createBackend` runs every capability's `middleware` hook before **any** capability's `routes` hook
175
- * (`createBackend.ts:262-267`), which is what makes this independent of the order an adopter happens to
176
- * list their capabilities in.
173
+ * **Called from `createBackend` ahead of every capability's `middleware`, never from a `routes` hook,
174
+ * and that is load-bearing.** Registered there, it is independent of the order an adopter happens to
175
+ * list their capabilities in, and it can put a header on whatever a capability's middleware or a
176
+ * readiness guard short-circuits.
177
177
  *
178
178
  * From `routes` it looks like it works and does not. The preflight still answers — an `OPTIONS` has no
179
179
  * route handler to lose to, so this `use` is the only thing that matches it — but a capability composed
@@ -1,8 +1,10 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
3
  import type { MiddlewareHandler } from "hono";
4
+ import type { AuditEmit } from "../../audit/recorder";
4
5
  import type { PithyHonoEnv } from "../../capability/capability";
5
6
  import type { ControlPlaneContext } from "../context";
7
+ import type { ControlPlaneActorKind } from "../data/connection";
6
8
  import type { ControlPlaneRequirement } from "../scope/scope";
7
9
  import { type ControlPlaneVerifyDeps } from "./verify";
8
10
  /**
@@ -76,4 +78,13 @@ export declare const CONTROL_PLANE_GUARD: unique symbol;
76
78
  /** Whether a mounted handler is a control-plane gate. */
77
79
  export declare function isControlPlaneGuard(handler: unknown): boolean;
78
80
  export declare function requireControlPlane(requirement: ControlPlaneRequirement): MiddlewareHandler<PithyHonoEnv>;
81
+ /**
82
+ * The recorder a verified call's handlers write through: the request's own, with the connection's actor
83
+ * kind added to every `control-plane` event that does not already carry one.
84
+ *
85
+ * Only `control-plane` events, because the kind describes a management client and nothing else — a
86
+ * handler recording what happened to a user is recording a user. An event that states a kind keeps it.
87
+ * A connection with no recorded kind adds nothing, so its rows read exactly as they did before.
88
+ */
89
+ export declare function stampActorKind(emit: AuditEmit, actorKind: ControlPlaneActorKind | null): AuditEmit;
79
90
  //# sourceMappingURL=guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAIhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,EAAE,KAAK,sBAAsB,EAA0B,MAAM,UAAU,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,uBAAuB,KACjC,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElC;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,sBAAsB,GAAG,oBAAoB,CAgB7F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMzE;AAED;;;;;;GAMG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,eAAwC,CAAC;AAEzE,yDAAyD;AACzD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,uBAAuB,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAkEzG"}
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAMhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,EAAE,KAAK,sBAAsB,EAA0B,MAAM,UAAU,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,uBAAuB,KACjC,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElC;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,sBAAsB,GAAG,oBAAoB,CAgB7F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMzE;AAED;;;;;;GAMG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,eAAwC,CAAC;AAEzE,yDAAyD;AACzD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,uBAAuB,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAuEzG;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAIlG"}
@@ -1,7 +1,9 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { PithyError } from "../../error/pithyError.js";
4
+ import { commandName, remedy } from "../../cli/command.js";
4
5
  import { workerVersionMetadata } from "../../worker/identity.js";
6
+ import { compositionEnvironment } from "../../env/ambient.js";
5
7
  import { ControlPlaneAuditActions, safeEmit } from "../audit/actions.js";
6
8
  import { ControlPlaneNotConnectedError } from "../error/errors.js";
7
9
  import { CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER } from "../wire.js";
@@ -69,7 +71,7 @@ function requireControlPlane(requirement) {
69
71
  if (!verify) {
70
72
  const denial = new ControlPlaneNotConnectedError({
71
73
  message: "This deployment does not accept management-client calls.",
72
- action: "Add `controlplane()` to this Worker's pithy.config.ts, then run pithy dashboard connect.",
74
+ action: `Add \`controlplane()\` to this Worker's pithy.config.ts, then ${connectStep(compositionEnvironment(c.env))}.`,
73
75
  detail: "no control-plane verifier on the request — the controlplane capability is not composed"
74
76
  });
75
77
  await auditDenial(c, requirement, denial);
@@ -83,6 +85,7 @@ function requireControlPlane(requirement) {
83
85
  throw cause;
84
86
  }
85
87
  c.set("controlPlane", context);
88
+ c.set("emit", stampActorKind(c.var.emit, context.actorKind));
86
89
  await safeEmit(c.var.emit, {
87
90
  action: ControlPlaneAuditActions.callAllowed,
88
91
  outcome: "success",
@@ -108,6 +111,21 @@ function requireControlPlane(requirement) {
108
111
  return guard;
109
112
  }
110
113
  /**
114
+ * The recorder a verified call's handlers write through: the request's own, with the connection's actor
115
+ * kind added to every `control-plane` event that does not already carry one.
116
+ *
117
+ * Only `control-plane` events, because the kind describes a management client and nothing else — a
118
+ * handler recording what happened to a user is recording a user. An event that states a kind keeps it.
119
+ * A connection with no recorded kind adds nothing, so its rows read exactly as they did before.
120
+ */
121
+ function stampActorKind(emit, actorKind) {
122
+ if (actorKind === null) return emit;
123
+ return (event) => emit(event.actorType === "control-plane" && event.actorKind == null ? {
124
+ ...event,
125
+ actorKind
126
+ } : event);
127
+ }
128
+ /**
111
129
  * Record a refused call.
112
130
  *
113
131
  * **Denials are audited, not only successes.** `denied` is a first-class outcome precisely for this, and
@@ -140,5 +158,13 @@ async function auditDenial(c, requirement, cause) {
140
158
  }
141
159
  }, c.var.log);
142
160
  }
161
+ /**
162
+ * The step that connects a management client, as this deployed Worker can name it: with its own environment when
163
+ * it knows it, and never with a Worker — it cannot know the `apps/` directory that provisioned it (#711).
164
+ */
165
+ function connectStep(env) {
166
+ if (env === void 0) return `connect one with ${commandName("dashboard connect")}, naming this environment with --env`;
167
+ return remedy("dashboard connect", { flags: { "--env": env } }, { runtime: true }).say((command) => `run ${command}`);
168
+ }
143
169
  //#endregion
144
- export { CONTROL_PLANE_GUARD, createControlPlaneVerifier, isControlPlaneGuard, requireControlPlane, workerVersionHeaders };
170
+ export { CONTROL_PLANE_GUARD, createControlPlaneVerifier, isControlPlaneGuard, requireControlPlane, stampActorKind, workerVersionHeaders };
@@ -6,6 +6,7 @@ import type { ControlPlaneConfig } from "../config/config";
6
6
  import { type ControlPlaneDatabase } from "../data/tables";
7
7
  import type { CapabilityDeclaration } from "../discovery/adminRoute";
8
8
  import { type CapabilityHealthSource } from "../discovery/health";
9
+ import { type ComposedMigrationDatabase } from "../migrationState/migrationState";
9
10
  import type { ExpireKeyParams, ExpireKeyRequest, RegisterKeyRequest } from "./schemas";
10
11
  /** The pieces of the composed capability a handler needs. Passed in, so nothing reaches for a global. */
11
12
  export interface ControlPlaneHandlerDeps {
@@ -31,6 +32,11 @@ export interface ControlPlaneHandlerDeps {
31
32
  * value is not: the numbers are resolved per request, behind the scope each one is declared under.
32
33
  */
33
34
  composedHealth: () => ReadonlyMap<string, CapabilityHealthSource>;
35
+ /**
36
+ * Every database this Worker binds, with the provider this build composes for it — captured by the same
37
+ * `compose` hook through `collectMigrationSets`, the function `pithy migrate` composes with (#729).
38
+ */
39
+ composedMigrations: () => readonly ComposedMigrationDatabase[];
34
40
  /** The clock, injected so a test can stand at any instant. */
35
41
  now: () => Date;
36
42
  }
@@ -108,6 +114,57 @@ export declare function manifestHandler(deps: ControlPlaneHandlerDeps): (c: Cont
108
114
  }[];
109
115
  grantedScopes: string[];
110
116
  }, import("hono/utils/http-status").ContentfulStatusCode, "json">>;
117
+ /**
118
+ * `GET /control-plane/migrations` — where each database this Worker binds stands (#729).
119
+ *
120
+ * Read-only: it reports, and nothing over the seam runs, reverses or resets a migration.
121
+ *
122
+ * **One unreadable ledger costs one database its answer.** Each read catches its own failure and lands on
123
+ * `unreadable`, so `Promise.all` still resolves and the route answers 200 with what it has. The cause is
124
+ * logged here, for the operator, and never projected: a D1 error can name a table or a query.
125
+ */
126
+ export declare function migrationsHandler(deps: ControlPlaneHandlerDeps): (c: Context<PithyHonoEnv>) => Promise<Response & import("hono").TypedResponse<{
127
+ environment: string;
128
+ databases: ({
129
+ state: "readable";
130
+ database: string;
131
+ binding: string;
132
+ applied: {
133
+ name: string;
134
+ namespace: string | null;
135
+ }[];
136
+ pending: {
137
+ name: string;
138
+ namespace: string | null;
139
+ }[];
140
+ undeclared: {
141
+ name: string;
142
+ namespace: string | null;
143
+ }[];
144
+ lastGroup: {
145
+ group: string;
146
+ appliedAt: string;
147
+ migrations: {
148
+ name: string;
149
+ namespace: string | null;
150
+ }[];
151
+ position: {
152
+ state: "top";
153
+ } | {
154
+ state: "buried";
155
+ above: string[];
156
+ ungrouped: {
157
+ name: string;
158
+ namespace: string | null;
159
+ }[];
160
+ };
161
+ } | null;
162
+ } | {
163
+ state: "unreadable";
164
+ database: string;
165
+ binding: string;
166
+ })[];
167
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">>;
111
168
  /** `GET /control-plane/keys` — the registration state, so a client can surface a stale key. */
112
169
  export declare function listKeysHandler(deps: ControlPlaneHandlerDeps): (c: Context<PithyHonoEnv>) => Promise<Response & import("hono").TypedResponse<{
113
170
  connectionId: string;
@@ -123,6 +180,9 @@ export declare function listKeysHandler(deps: ControlPlaneHandlerDeps): (c: Cont
123
180
  validUntil: string | null;
124
181
  revokedAt: string | null;
125
182
  }[];
183
+ authorizedBy: string | null;
184
+ actorKind: "agent" | "dashboard" | "script" | null;
185
+ purpose: string | null;
126
186
  }, import("hono/utils/http-status").ContentfulStatusCode, "json">>;
127
187
  /**
128
188
  * `POST /control-plane/keys` — register a new public key, authenticated by the key it succeeds.
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/handlers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAI3D,OAAO,EAAmC,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC5F,OAAO,KAAK,EAAE,qBAAqB,EAA4B,MAAM,yBAAyB,CAAC;AAC/F,OAAO,EAAE,KAAK,sBAAsB,EAAwB,MAAM,qBAAqB,CAAC;AAUxF,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAuBvF,yGAAyG;AACzG,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,MAAM,EAAE,kBAAkB,CAAC;IAC3B,oEAAoE;IACpE,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,oBAAoB,CAAC;IAC7D;;;;;;;;OAQG;IACH,oBAAoB,EAAE,MAAM,SAAS,qBAAqB,EAAE,CAAC;IAC7D;;;;;;OAMG;IACH,cAAc,EAAE,MAAM,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAClE,8DAA8D;IAC9D,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAgED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,uBAAuB,OAC5C,OAAO,CAAC,YAAY,CAAC;;;;;;kEAUjC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,uBAAuB,OAC1C,OAAO,CAAC,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEAyBvC;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,IAAI,EAAE,uBAAuB,OAC1C,OAAO,CAAC,YAAY,CAAC;;;;;;;;;;;;;;mEASvC;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,uBAAuB,OAC7C,OAAO,CAAC,YAAY,CAAC,QAAQ,kBAAkB;;;;;;;;;;;;;;iBAoDjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,OAC3C,OAAO,CAAC,YAAY,CAAC,UAAU,eAAe,QAAQ,gBAAgB;;;;;;;;;;;;;;mEA8CxF"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/handlers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAIhE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAI3D,OAAO,EAAmC,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC5F,OAAO,KAAK,EAAE,qBAAqB,EAA4B,MAAM,yBAAyB,CAAC;AAC/F,OAAO,EAAE,KAAK,sBAAsB,EAAwB,MAAM,qBAAqB,CAAC;AAGxF,OAAO,EAAE,KAAK,yBAAyB,EAA0B,MAAM,kCAAkC,CAAC;AAS1G,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAuBvF,yGAAyG;AACzG,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,MAAM,EAAE,kBAAkB,CAAC;IAC3B,oEAAoE;IACpE,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,oBAAoB,CAAC;IAC7D;;;;;;;;OAQG;IACH,oBAAoB,EAAE,MAAM,SAAS,qBAAqB,EAAE,CAAC;IAC7D;;;;;;OAMG;IACH,cAAc,EAAE,MAAM,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAClE;;;OAGG;IACH,kBAAkB,EAAE,MAAM,SAAS,yBAAyB,EAAE,CAAC;IAC/D,8DAA8D;IAC9D,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAgED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,uBAAuB,OAC5C,OAAO,CAAC,YAAY,CAAC;;;;;;kEAUjC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,uBAAuB,OAC1C,OAAO,CAAC,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEAyBvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,uBAAuB,OAC5C,OAAO,CAAC,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEAgBvC;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,IAAI,EAAE,uBAAuB,OAC1C,OAAO,CAAC,YAAY,CAAC;;;;;;;;;;;;;;;;;mEAcvC;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,uBAAuB,OAC7C,OAAO,CAAC,YAAY,CAAC,QAAQ,kBAAkB;;;;;;;;;;;;;;iBAoDjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,OAC3C,OAAO,CAAC,YAAY,CAAC,UAAU,eAAe,QAAQ,gBAAgB;;;;;;;;;;;;;;mEA8CxF"}
@@ -8,6 +8,8 @@ import { readCapabilityHealth } from "../discovery/health.js";
8
8
  import { ControlPlaneAuditActions, safeEmit } from "../audit/actions.js";
9
9
  import { ControlPlaneInvalidCredentialError, ControlPlaneKeyConflictError } from "../error/errors.js";
10
10
  import { appendKey, expireKey, pruneKeys } from "../data/keyLifecycle.js";
11
+ import { causeMessage } from "../../error/cause.js";
12
+ import { readDatabaseMigrations } from "../migrationState/migrationState.js";
11
13
  //#region src/controlPlane/http/handlers.ts
12
14
  /**
13
15
  * The seam's own route handlers.
@@ -121,6 +123,30 @@ function manifestHandler(deps) {
121
123
  });
122
124
  };
123
125
  }
126
+ /**
127
+ * `GET /control-plane/migrations` — where each database this Worker binds stands (#729).
128
+ *
129
+ * Read-only: it reports, and nothing over the seam runs, reverses or resets a migration.
130
+ *
131
+ * **One unreadable ledger costs one database its answer.** Each read catches its own failure and lands on
132
+ * `unreadable`, so `Promise.all` still resolves and the route answers 200 with what it has. The cause is
133
+ * logged here, for the operator, and never projected: a D1 error can name a table or a query.
134
+ */
135
+ function migrationsHandler(deps) {
136
+ return async (c) => {
137
+ const context = caller(c);
138
+ const env = c.env;
139
+ const databases = await Promise.all(deps.composedMigrations().map((entry) => readDatabaseMigrations(env, entry, (failed, cause) => c.var.log.warn("migration ledger unreadable", {
140
+ database: failed.database,
141
+ binding: failed.binding,
142
+ reason: causeMessage(cause) ?? String(cause)
143
+ }))));
144
+ return c.json({
145
+ environment: context.environment,
146
+ databases
147
+ });
148
+ };
149
+ }
124
150
  /** `GET /control-plane/keys` — the registration state, so a client can surface a stale key. */
125
151
  function listKeysHandler(deps) {
126
152
  return async (c) => {
@@ -129,7 +155,10 @@ function listKeysHandler(deps) {
129
155
  return c.json({
130
156
  connectionId: connection.id,
131
157
  environment: connection.environment,
132
- keys: connection.keys.map(publicKeyView)
158
+ keys: connection.keys.map(publicKeyView),
159
+ authorizedBy: connection.authorizedBy,
160
+ actorKind: connection.actorKind,
161
+ purpose: connection.purpose
133
162
  });
134
163
  };
135
164
  }
@@ -244,4 +273,4 @@ function expireKeyHandler(deps) {
244
273
  };
245
274
  }
246
275
  //#endregion
247
- export { expireKeyHandler, listKeysHandler, manifestHandler, pingHandler, registerKeyHandler };
276
+ export { expireKeyHandler, listKeysHandler, manifestHandler, migrationsHandler, pingHandler, registerKeyHandler };
@@ -0,0 +1,33 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { Hono } from "hono";
4
+ import type { PithyHonoEnv, PithyReadinessGuard } from "../../capability/capability";
5
+ /**
6
+ * Mount every capability's readiness guard, exempting the admin surface.
7
+ *
8
+ * **The control plane answers whatever else is wrong with the Worker.** It is the diagnostic surface: a
9
+ * management client reads the manifest, migration state and drift through it. A guard that refuses every
10
+ * request because an index drifted would take that surface down on exactly the fault it exists to report,
11
+ * and the client could not tell drift from a Worker that is simply down.
12
+ *
13
+ * **The exemption is the guard, not the middleware.** The seam itself runs on capability middleware —
14
+ * `controlplane()` publishes the verifier and `audit()` publishes `emit` — so skipping middleware on a
15
+ * seam request would trade a 500 for a seam that denies everything, or one that stops auditing without a
16
+ * word. Readiness guards are the only middleware that refuses because the Worker is broken, so they are
17
+ * the only thing skipped.
18
+ *
19
+ * **Hono decides what is on the surface, not a matcher written here.** One `app.use` per admin path marks
20
+ * the request, so a `:segment` matches exactly as it does for the route and the CORS layer, and a literal
21
+ * path never matches what is beneath it. The surface is the one `corsSurface(all)` computes, which
22
+ * `routeContract.test.ts` already holds to the registered routes, so there is no second list to drift.
23
+ *
24
+ * The mark is a `WeakSet` over the raw `Request`, closed over here. A context variable would put an
25
+ * internal flag on `PithyVars`, where every capability could read it and one could set it.
26
+ *
27
+ * Call after every capability's `middleware` and before any capability's `routes`. Before the routes,
28
+ * because a terminal handler never calls `next()` and a guard mounted after it never runs.
29
+ */
30
+ export declare function registerReadinessGuards(app: Hono<PithyHonoEnv>, surface: readonly {
31
+ path: string;
32
+ }[], guards: readonly PithyReadinessGuard[]): void;
33
+ //# sourceMappingURL=readiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/readiness.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,EACvB,OAAO,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,EACpC,MAAM,EAAE,SAAS,mBAAmB,EAAE,GACrC,IAAI,CAcN"}
@@ -0,0 +1,39 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ //#region src/controlPlane/http/readiness.ts
4
+ /**
5
+ * Mount every capability's readiness guard, exempting the admin surface.
6
+ *
7
+ * **The control plane answers whatever else is wrong with the Worker.** It is the diagnostic surface: a
8
+ * management client reads the manifest, migration state and drift through it. A guard that refuses every
9
+ * request because an index drifted would take that surface down on exactly the fault it exists to report,
10
+ * and the client could not tell drift from a Worker that is simply down.
11
+ *
12
+ * **The exemption is the guard, not the middleware.** The seam itself runs on capability middleware —
13
+ * `controlplane()` publishes the verifier and `audit()` publishes `emit` — so skipping middleware on a
14
+ * seam request would trade a 500 for a seam that denies everything, or one that stops auditing without a
15
+ * word. Readiness guards are the only middleware that refuses because the Worker is broken, so they are
16
+ * the only thing skipped.
17
+ *
18
+ * **Hono decides what is on the surface, not a matcher written here.** One `app.use` per admin path marks
19
+ * the request, so a `:segment` matches exactly as it does for the route and the CORS layer, and a literal
20
+ * path never matches what is beneath it. The surface is the one `corsSurface(all)` computes, which
21
+ * `routeContract.test.ts` already holds to the registered routes, so there is no second list to drift.
22
+ *
23
+ * The mark is a `WeakSet` over the raw `Request`, closed over here. A context variable would put an
24
+ * internal flag on `PithyVars`, where every capability could read it and one could set it.
25
+ *
26
+ * Call after every capability's `middleware` and before any capability's `routes`. Before the routes,
27
+ * because a terminal handler never calls `next()` and a guard mounted after it never runs.
28
+ */
29
+ function registerReadinessGuards(app, surface, guards) {
30
+ if (guards.length === 0) return;
31
+ const onSurface = /* @__PURE__ */ new WeakSet();
32
+ for (const path of new Set(surface.map((entry) => entry.path))) app.use(path, async (c, next) => {
33
+ onSurface.add(c.req.raw);
34
+ await next();
35
+ });
36
+ for (const guard of guards) app.use("*", (c, next) => onSurface.has(c.req.raw) ? next() : guard(c, next));
37
+ }
38
+ //#endregion
39
+ export { registerReadinessGuards };
@@ -62,6 +62,13 @@ export declare const ControlPlaneKeysResponse: z.ZodObject<{
62
62
  validUntil: z.ZodNullable<z.ZodISODateTime>;
63
63
  revokedAt: z.ZodNullable<z.ZodISODateTime>;
64
64
  }, z.core.$strip>>;
65
+ authorizedBy: z.ZodOptional<z.ZodNullable<z.ZodEmail>>;
66
+ actorKind: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
67
+ agent: "agent";
68
+ dashboard: "dashboard";
69
+ script: "script";
70
+ }>>>;
71
+ purpose: z.ZodOptional<z.ZodNullable<z.ZodString>>;
65
72
  }, z.core.$strip>;
66
73
  export type ControlPlaneKeysResponse = z.output<typeof ControlPlaneKeysResponse>;
67
74
  /** `POST /control-plane/keys` — the appended key, and the set as it now stands. */
@@ -98,4 +105,144 @@ export declare const ExpireKeyResponse: z.ZodObject<{
98
105
  }, z.core.$strip>>;
99
106
  }, z.core.$strip>;
100
107
  export type ExpireKeyResponse = z.output<typeof ExpireKeyResponse>;
108
+ /**
109
+ * One migration as `GET {basePath}/migrations` reports it: its composed name, and whose it is.
110
+ *
111
+ * The namespace is a capability's name, which the manifest already reports in `capabilities[]`. So a
112
+ * client tells an adopter's own migrations from a composed capability's by comparing the two — no
113
+ * convention, and no list kept anywhere.
114
+ */
115
+ export declare const MigrationView: z.ZodObject<{
116
+ name: z.ZodString;
117
+ namespace: z.ZodNullable<z.ZodString>;
118
+ }, z.core.$strip>;
119
+ export type MigrationView = z.output<typeof MigrationView>;
120
+ /** Whether the last group is still reversible by name, or what is in its way. */
121
+ export declare const MigrationGroupPositionView: z.ZodDiscriminatedUnion<[z.ZodObject<{
122
+ state: z.ZodLiteral<"top">;
123
+ }, z.core.$strip>, z.ZodObject<{
124
+ state: z.ZodLiteral<"buried">;
125
+ above: z.ZodArray<z.ZodString>;
126
+ ungrouped: z.ZodArray<z.ZodObject<{
127
+ name: z.ZodString;
128
+ namespace: z.ZodNullable<z.ZodString>;
129
+ }, z.core.$strip>>;
130
+ }, z.core.$strip>], "state">;
131
+ export type MigrationGroupPositionView = z.output<typeof MigrationGroupPositionView>;
132
+ /** The run that last moved a database, and whether it can still be taken back. */
133
+ export declare const MigrationGroupView: z.ZodObject<{
134
+ group: z.ZodString;
135
+ appliedAt: z.ZodISODateTime;
136
+ migrations: z.ZodArray<z.ZodObject<{
137
+ name: z.ZodString;
138
+ namespace: z.ZodNullable<z.ZodString>;
139
+ }, z.core.$strip>>;
140
+ position: z.ZodDiscriminatedUnion<[z.ZodObject<{
141
+ state: z.ZodLiteral<"top">;
142
+ }, z.core.$strip>, z.ZodObject<{
143
+ state: z.ZodLiteral<"buried">;
144
+ above: z.ZodArray<z.ZodString>;
145
+ ungrouped: z.ZodArray<z.ZodObject<{
146
+ name: z.ZodString;
147
+ namespace: z.ZodNullable<z.ZodString>;
148
+ }, z.core.$strip>>;
149
+ }, z.core.$strip>], "state">;
150
+ }, z.core.$strip>;
151
+ export type MigrationGroupView = z.output<typeof MigrationGroupView>;
152
+ /**
153
+ * One database this Worker binds, read or not.
154
+ *
155
+ * **An unreadable database carries no list at all**, never empty ones. `pending: []` reads as up to date,
156
+ * and a database nobody read is not that. The state is on the value, so a client reaches a list only by
157
+ * narrowing.
158
+ */
159
+ export declare const MigrationDatabaseView: z.ZodDiscriminatedUnion<[z.ZodObject<{
160
+ state: z.ZodLiteral<"readable">;
161
+ database: z.ZodString;
162
+ binding: z.ZodString;
163
+ applied: z.ZodArray<z.ZodObject<{
164
+ name: z.ZodString;
165
+ namespace: z.ZodNullable<z.ZodString>;
166
+ }, z.core.$strip>>;
167
+ pending: z.ZodArray<z.ZodObject<{
168
+ name: z.ZodString;
169
+ namespace: z.ZodNullable<z.ZodString>;
170
+ }, z.core.$strip>>;
171
+ undeclared: z.ZodArray<z.ZodObject<{
172
+ name: z.ZodString;
173
+ namespace: z.ZodNullable<z.ZodString>;
174
+ }, z.core.$strip>>;
175
+ lastGroup: z.ZodNullable<z.ZodObject<{
176
+ group: z.ZodString;
177
+ appliedAt: z.ZodISODateTime;
178
+ migrations: z.ZodArray<z.ZodObject<{
179
+ name: z.ZodString;
180
+ namespace: z.ZodNullable<z.ZodString>;
181
+ }, z.core.$strip>>;
182
+ position: z.ZodDiscriminatedUnion<[z.ZodObject<{
183
+ state: z.ZodLiteral<"top">;
184
+ }, z.core.$strip>, z.ZodObject<{
185
+ state: z.ZodLiteral<"buried">;
186
+ above: z.ZodArray<z.ZodString>;
187
+ ungrouped: z.ZodArray<z.ZodObject<{
188
+ name: z.ZodString;
189
+ namespace: z.ZodNullable<z.ZodString>;
190
+ }, z.core.$strip>>;
191
+ }, z.core.$strip>], "state">;
192
+ }, z.core.$strip>>;
193
+ }, z.core.$strip>, z.ZodObject<{
194
+ state: z.ZodLiteral<"unreadable">;
195
+ database: z.ZodString;
196
+ binding: z.ZodString;
197
+ }, z.core.$strip>], "state">;
198
+ export type MigrationDatabaseView = z.output<typeof MigrationDatabaseView>;
199
+ /**
200
+ * `GET /control-plane/migrations` — where every database this Worker binds stands.
201
+ *
202
+ * Asked of the Worker because the Worker *is* the deployed build. `pithy doctor` reads the project on disk,
203
+ * which is what the operator has and not necessarily what is deployed.
204
+ */
205
+ export declare const ControlPlaneMigrationsResponse: z.ZodObject<{
206
+ environment: z.ZodString;
207
+ databases: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
208
+ state: z.ZodLiteral<"readable">;
209
+ database: z.ZodString;
210
+ binding: z.ZodString;
211
+ applied: z.ZodArray<z.ZodObject<{
212
+ name: z.ZodString;
213
+ namespace: z.ZodNullable<z.ZodString>;
214
+ }, z.core.$strip>>;
215
+ pending: z.ZodArray<z.ZodObject<{
216
+ name: z.ZodString;
217
+ namespace: z.ZodNullable<z.ZodString>;
218
+ }, z.core.$strip>>;
219
+ undeclared: z.ZodArray<z.ZodObject<{
220
+ name: z.ZodString;
221
+ namespace: z.ZodNullable<z.ZodString>;
222
+ }, z.core.$strip>>;
223
+ lastGroup: z.ZodNullable<z.ZodObject<{
224
+ group: z.ZodString;
225
+ appliedAt: z.ZodISODateTime;
226
+ migrations: z.ZodArray<z.ZodObject<{
227
+ name: z.ZodString;
228
+ namespace: z.ZodNullable<z.ZodString>;
229
+ }, z.core.$strip>>;
230
+ position: z.ZodDiscriminatedUnion<[z.ZodObject<{
231
+ state: z.ZodLiteral<"top">;
232
+ }, z.core.$strip>, z.ZodObject<{
233
+ state: z.ZodLiteral<"buried">;
234
+ above: z.ZodArray<z.ZodString>;
235
+ ungrouped: z.ZodArray<z.ZodObject<{
236
+ name: z.ZodString;
237
+ namespace: z.ZodNullable<z.ZodString>;
238
+ }, z.core.$strip>>;
239
+ }, z.core.$strip>], "state">;
240
+ }, z.core.$strip>>;
241
+ }, z.core.$strip>, z.ZodObject<{
242
+ state: z.ZodLiteral<"unreadable">;
243
+ database: z.ZodString;
244
+ binding: z.ZodString;
245
+ }, z.core.$strip>], "state">>;
246
+ }, z.core.$strip>;
247
+ export type ControlPlaneMigrationsResponse = z.output<typeof ControlPlaneMigrationsResponse>;
101
248
  //# sourceMappingURL=responses.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/responses.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;GAkBG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,aAAa;;;;;;;;;;iBAcmF,CAAC;AAC9G,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;;;;iBAQwB,CAAC;AAC9D,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEjF,+FAA+F;AAC/F,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;iBAMwC,CAAC;AAC9E,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEjF,mFAAmF;AACnF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;iBAQyB,CAAC;AAC1D,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE,gGAAgG;AAChG,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBAM6B,CAAC;AAC5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
1
+ {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/responses.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;GAkBG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,aAAa;;;;;;;;;;iBAcmF,CAAC;AAC9G,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;;;;iBAQwB,CAAC;AAC9D,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEjF,+FAA+F;AAC/F,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;iBAuBwE,CAAC;AAC9G,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEjF,mFAAmF;AACnF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;iBAQyB,CAAC;AAC1D,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE,gGAAgG;AAChG,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBAM6B,CAAC;AAC5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;iBAU+C,CAAC;AAC1E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D,iFAAiF;AACjF,eAAO,MAAM,0BAA0B;;;;;;;;;4BAyBkE,CAAC;AAC1G,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAErF,kFAAkF;AAClF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;iBAW8C,CAAC;AAC9E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BA+BwE,CAAC;AAC3G,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE3E;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAO+D,CAAC;AAC3G,MAAM,MAAM,8BAA8B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,8BAA8B,CAAC,CAAC"}