@pithy-sh/core 0.13.0 → 0.15.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 (354) hide show
  1. package/README.md +54 -0
  2. package/dist/audit/auditEvent.js +1 -1
  3. package/dist/audit/chain.d.ts +289 -0
  4. package/dist/audit/chain.d.ts.map +1 -0
  5. package/dist/audit/chain.js +400 -0
  6. package/dist/audit/citations.d.ts +52 -0
  7. package/dist/audit/citations.d.ts.map +1 -0
  8. package/dist/audit/citations.js +13 -0
  9. package/dist/capability/bindings.d.ts +22 -0
  10. package/dist/capability/bindings.d.ts.map +1 -1
  11. package/dist/capability/bindings.js +32 -2
  12. package/dist/capability/capability.d.ts +123 -16
  13. package/dist/capability/capability.d.ts.map +1 -1
  14. package/dist/capability/capability.js +17 -2
  15. package/dist/capability/erasure.d.ts +75 -0
  16. package/dist/capability/erasure.d.ts.map +1 -0
  17. package/dist/capability/erasure.js +2 -0
  18. package/dist/capability/manifest.d.ts +5 -0
  19. package/dist/capability/manifest.d.ts.map +1 -1
  20. package/dist/capability/personColumns.d.ts +28 -0
  21. package/dist/capability/personColumns.d.ts.map +1 -0
  22. package/dist/capability/personColumns.js +66 -0
  23. package/dist/capability/personRows.d.ts +62 -0
  24. package/dist/capability/personRows.d.ts.map +1 -0
  25. package/dist/capability/personRows.js +59 -0
  26. package/dist/capability/purge.d.ts +86 -19
  27. package/dist/capability/purge.d.ts.map +1 -1
  28. package/dist/capability/purge.js +83 -5
  29. package/dist/capability/settings.d.ts +10 -0
  30. package/dist/capability/settings.d.ts.map +1 -1
  31. package/dist/capability/vectorIndex.d.ts +36 -0
  32. package/dist/capability/vectorIndex.d.ts.map +1 -0
  33. package/dist/capability/vectorIndex.js +25 -0
  34. package/dist/cli/commandCatalog.generated.d.ts +438 -3
  35. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  36. package/dist/cli/commandCatalog.generated.js +454 -2
  37. package/dist/cli/commandFacts.d.ts +8 -5
  38. package/dist/cli/commandFacts.d.ts.map +1 -1
  39. package/dist/cli/commandFacts.js +12 -3
  40. package/dist/consent/purpose.d.ts +70 -0
  41. package/dist/consent/purpose.d.ts.map +1 -0
  42. package/dist/consent/purpose.js +97 -0
  43. package/dist/consent/reader.d.ts +99 -0
  44. package/dist/consent/reader.d.ts.map +1 -0
  45. package/dist/consent/reader.js +33 -0
  46. package/dist/controlPlane/audit/actions.d.ts +15 -4
  47. package/dist/controlPlane/audit/actions.d.ts.map +1 -1
  48. package/dist/controlPlane/audit/actions.js +16 -5
  49. package/dist/controlPlane/capability.d.ts +10 -0
  50. package/dist/controlPlane/capability.d.ts.map +1 -1
  51. package/dist/controlPlane/capability.js +56 -8
  52. package/dist/controlPlane/context.d.ts +4 -0
  53. package/dist/controlPlane/context.d.ts.map +1 -1
  54. package/dist/controlPlane/context.js +3 -2
  55. package/dist/controlPlane/data/connection.d.ts +21 -2
  56. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  57. package/dist/controlPlane/data/connection.js +13 -4
  58. package/dist/controlPlane/data/connections.d.ts +94 -0
  59. package/dist/controlPlane/data/connections.d.ts.map +1 -0
  60. package/dist/controlPlane/data/connections.js +151 -0
  61. package/dist/controlPlane/data/hold.d.ts +46 -0
  62. package/dist/controlPlane/data/hold.d.ts.map +1 -0
  63. package/dist/controlPlane/data/hold.js +36 -0
  64. package/dist/controlPlane/data/oidcTrust.d.ts +122 -0
  65. package/dist/controlPlane/data/oidcTrust.d.ts.map +1 -0
  66. package/dist/controlPlane/data/oidcTrust.js +173 -0
  67. package/dist/controlPlane/data/tables.d.ts +4 -0
  68. package/dist/controlPlane/data/tables.d.ts.map +1 -1
  69. package/dist/controlPlane/data/tables.js +9 -4
  70. package/dist/controlPlane/data/trustFirstCall.d.ts +16 -0
  71. package/dist/controlPlane/data/trustFirstCall.d.ts.map +1 -0
  72. package/dist/controlPlane/data/trustFirstCall.js +25 -0
  73. package/dist/controlPlane/discovery/adminRoute.d.ts +165 -2
  74. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
  75. package/dist/controlPlane/discovery/adminRoute.js +32 -8
  76. package/dist/controlPlane/discovery/routeInput.d.ts +23 -0
  77. package/dist/controlPlane/discovery/routeInput.d.ts.map +1 -0
  78. package/dist/controlPlane/discovery/routeInput.js +80 -0
  79. package/dist/controlPlane/discovery/toolName.d.ts +21 -0
  80. package/dist/controlPlane/discovery/toolName.d.ts.map +1 -0
  81. package/dist/controlPlane/discovery/toolName.js +25 -0
  82. package/dist/controlPlane/erasure/attest.d.ts +78 -0
  83. package/dist/controlPlane/erasure/attest.d.ts.map +1 -0
  84. package/dist/controlPlane/erasure/attest.js +164 -0
  85. package/dist/controlPlane/erasure/read.d.ts +39 -0
  86. package/dist/controlPlane/erasure/read.d.ts.map +1 -0
  87. package/dist/controlPlane/erasure/read.js +140 -0
  88. package/dist/controlPlane/error/errors.d.ts +61 -2
  89. package/dist/controlPlane/error/errors.d.ts.map +1 -1
  90. package/dist/controlPlane/error/errors.js +13 -0
  91. package/dist/controlPlane/evidence/derive.d.ts +124 -0
  92. package/dist/controlPlane/evidence/derive.d.ts.map +1 -0
  93. package/dist/controlPlane/evidence/derive.js +343 -0
  94. package/dist/controlPlane/evidence/mapping.d.ts +85 -0
  95. package/dist/controlPlane/evidence/mapping.d.ts.map +1 -0
  96. package/dist/controlPlane/evidence/mapping.js +114 -0
  97. package/dist/controlPlane/evidence/pack.d.ts +46 -0
  98. package/dist/controlPlane/evidence/pack.d.ts.map +1 -0
  99. package/dist/controlPlane/evidence/pack.js +124 -0
  100. package/dist/controlPlane/http/connections.d.ts +96 -0
  101. package/dist/controlPlane/http/connections.d.ts.map +1 -0
  102. package/dist/controlPlane/http/connections.js +109 -0
  103. package/dist/controlPlane/http/guard.d.ts +23 -2
  104. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  105. package/dist/controlPlane/http/guard.js +69 -12
  106. package/dist/controlPlane/http/handlers.d.ts +212 -10
  107. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  108. package/dist/controlPlane/http/handlers.js +117 -26
  109. package/dist/controlPlane/http/holds.d.ts +57 -0
  110. package/dist/controlPlane/http/holds.d.ts.map +1 -0
  111. package/dist/controlPlane/http/holds.js +98 -0
  112. package/dist/controlPlane/http/presence.d.ts +27 -0
  113. package/dist/controlPlane/http/presence.d.ts.map +1 -0
  114. package/dist/controlPlane/http/presence.js +67 -0
  115. package/dist/controlPlane/http/processing.d.ts +73 -0
  116. package/dist/controlPlane/http/processing.d.ts.map +1 -0
  117. package/dist/controlPlane/http/processing.js +17 -0
  118. package/dist/controlPlane/http/responses.d.ts +927 -3
  119. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  120. package/dist/controlPlane/http/responses.js +283 -7
  121. package/dist/controlPlane/http/routes.d.ts +12 -2
  122. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  123. package/dist/controlPlane/http/routes.js +182 -5
  124. package/dist/controlPlane/http/schemas.d.ts +63 -0
  125. package/dist/controlPlane/http/schemas.d.ts.map +1 -1
  126. package/dist/controlPlane/http/schemas.js +39 -1
  127. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  128. package/dist/controlPlane/http/verify.js +13 -1
  129. package/dist/controlPlane/http/verifyTrust.d.ts +76 -0
  130. package/dist/controlPlane/http/verifyTrust.d.ts.map +1 -0
  131. package/dist/controlPlane/http/verifyTrust.js +75 -0
  132. package/dist/controlPlane/migrations/0004_connection_trusts.d.ts +15 -0
  133. package/dist/controlPlane/migrations/0004_connection_trusts.d.ts.map +1 -0
  134. package/dist/controlPlane/migrations/0004_connection_trusts.js +23 -0
  135. package/dist/controlPlane/migrations/0005_holds.d.ts +12 -0
  136. package/dist/controlPlane/migrations/0005_holds.d.ts.map +1 -0
  137. package/dist/controlPlane/migrations/0005_holds.js +26 -0
  138. package/dist/controlPlane/processing/derive.d.ts +77 -0
  139. package/dist/controlPlane/processing/derive.d.ts.map +1 -0
  140. package/dist/controlPlane/processing/derive.js +206 -0
  141. package/dist/controlPlane/processing/read.d.ts +10 -0
  142. package/dist/controlPlane/processing/read.d.ts.map +1 -0
  143. package/dist/controlPlane/processing/read.js +89 -0
  144. package/dist/controlPlane/scope/kind.d.ts +46 -0
  145. package/dist/controlPlane/scope/kind.d.ts.map +1 -0
  146. package/dist/controlPlane/scope/kind.js +52 -0
  147. package/dist/controlPlane/scope/scope.d.ts +58 -3
  148. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  149. package/dist/controlPlane/scope/scope.js +59 -4
  150. package/dist/controlPlane/token/jws.js +1 -1
  151. package/dist/controlPlane/token/mint.js +1 -1
  152. package/dist/controlPlane/wire.d.ts +23 -0
  153. package/dist/controlPlane/wire.d.ts.map +1 -1
  154. package/dist/controlPlane/wire.js +24 -1
  155. package/dist/createBackend.d.ts +13 -0
  156. package/dist/createBackend.d.ts.map +1 -1
  157. package/dist/createBackend.js +11 -3
  158. package/dist/createEntrypoint.d.ts +7 -0
  159. package/dist/createEntrypoint.d.ts.map +1 -1
  160. package/dist/createEntrypoint.js +4 -0
  161. package/dist/error/http.d.ts +168 -0
  162. package/dist/error/http.d.ts.map +1 -1
  163. package/dist/error/payload.d.ts +336 -0
  164. package/dist/error/payload.d.ts.map +1 -1
  165. package/dist/error/payload.js +112 -0
  166. package/dist/error/pithyError.d.ts +22 -0
  167. package/dist/error/pithyError.d.ts.map +1 -1
  168. package/dist/error/pithyError.js +31 -1
  169. package/dist/http/routeSpec.d.ts +46 -1
  170. package/dist/http/routeSpec.d.ts.map +1 -1
  171. package/dist/http/routeSpec.js +39 -5
  172. package/dist/http/verification.d.ts.map +1 -1
  173. package/dist/http/verification.js +1 -1
  174. package/dist/i18n/translator.d.ts +2 -1
  175. package/dist/i18n/translator.d.ts.map +1 -1
  176. package/dist/index.d.ts +1 -1
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +3 -3
  179. package/dist/logger/record.d.ts +98 -0
  180. package/dist/logger/record.d.ts.map +1 -1
  181. package/dist/mcp/annotations.d.ts +20 -0
  182. package/dist/mcp/annotations.d.ts.map +1 -0
  183. package/dist/mcp/annotations.js +23 -0
  184. package/dist/mcp/tools.d.ts +75 -0
  185. package/dist/mcp/tools.d.ts.map +1 -0
  186. package/dist/mcp/tools.js +118 -0
  187. package/dist/openapi/document.d.ts.map +1 -1
  188. package/dist/openapi/document.js +8 -26
  189. package/dist/openapi/operations.d.ts +35 -0
  190. package/dist/openapi/operations.d.ts.map +1 -0
  191. package/dist/openapi/operations.js +37 -0
  192. package/dist/presence/channel.d.ts +11 -0
  193. package/dist/presence/channel.d.ts.map +1 -0
  194. package/dist/presence/channel.js +65 -0
  195. package/dist/presence/declaration.d.ts +154 -0
  196. package/dist/presence/declaration.d.ts.map +1 -0
  197. package/dist/presence/declaration.js +164 -0
  198. package/dist/presence/durableObject.d.ts +61 -0
  199. package/dist/presence/durableObject.d.ts.map +1 -0
  200. package/dist/presence/durableObject.js +257 -0
  201. package/dist/presence/frames.d.ts +166 -0
  202. package/dist/presence/frames.d.ts.map +1 -0
  203. package/dist/presence/frames.js +90 -0
  204. package/dist/presence/http/guard.d.ts +11 -0
  205. package/dist/presence/http/guard.d.ts.map +1 -0
  206. package/dist/presence/http/guard.js +20 -0
  207. package/dist/presence/http/responses.d.ts +31 -0
  208. package/dist/presence/http/responses.d.ts.map +1 -0
  209. package/dist/presence/http/responses.js +34 -0
  210. package/dist/presence/http/routes.d.ts +24 -0
  211. package/dist/presence/http/routes.d.ts.map +1 -0
  212. package/dist/presence/http/routes.js +127 -0
  213. package/dist/presence/http/schemas.d.ts +29 -0
  214. package/dist/presence/http/schemas.d.ts.map +1 -0
  215. package/dist/presence/http/schemas.js +28 -0
  216. package/dist/presence/names.d.ts +23 -0
  217. package/dist/presence/names.d.ts.map +1 -0
  218. package/dist/presence/names.js +22 -0
  219. package/dist/presence/room.d.ts +95 -0
  220. package/dist/presence/room.d.ts.map +1 -0
  221. package/dist/presence/room.js +105 -0
  222. package/dist/presence/ticket.d.ts +55 -0
  223. package/dist/presence/ticket.d.ts.map +1 -0
  224. package/dist/presence/ticket.js +109 -0
  225. package/dist/privacy/classify.js +1 -1
  226. package/dist/privacy/encrypted.js +1 -1
  227. package/dist/processing/activity.d.ts +134 -0
  228. package/dist/processing/activity.d.ts.map +1 -0
  229. package/dist/processing/activity.js +161 -0
  230. package/dist/processing/composed.d.ts +18 -0
  231. package/dist/processing/composed.d.ts.map +1 -0
  232. package/dist/processing/composed.js +28 -0
  233. package/dist/processing/vocabulary.d.ts +26 -0
  234. package/dist/processing/vocabulary.d.ts.map +1 -0
  235. package/dist/processing/vocabulary.js +35 -0
  236. package/dist/queue/consumer.d.ts +59 -0
  237. package/dist/queue/consumer.d.ts.map +1 -0
  238. package/dist/queue/consumer.js +46 -0
  239. package/dist/retention/composed.d.ts +37 -0
  240. package/dist/retention/composed.d.ts.map +1 -0
  241. package/dist/retention/composed.js +66 -0
  242. package/dist/retention/holds.d.ts +61 -0
  243. package/dist/retention/holds.d.ts.map +1 -0
  244. package/dist/retention/holds.js +107 -0
  245. package/dist/retention/holdsPurge.d.ts +22 -0
  246. package/dist/retention/holdsPurge.d.ts.map +1 -0
  247. package/dist/retention/holdsPurge.js +50 -0
  248. package/dist/retention/policy.d.ts +132 -0
  249. package/dist/retention/policy.d.ts.map +1 -0
  250. package/dist/retention/policy.js +197 -0
  251. package/dist/session/rotated.d.ts +34 -0
  252. package/dist/session/rotated.d.ts.map +1 -0
  253. package/dist/session/rotated.js +25 -0
  254. package/dist/test-utils/connectionFixtures.d.ts +13 -0
  255. package/dist/test-utils/connectionFixtures.d.ts.map +1 -0
  256. package/dist/test-utils/connectionFixtures.js +58 -0
  257. package/dist/test-utils/interleave.d.ts +12 -0
  258. package/dist/test-utils/interleave.d.ts.map +1 -0
  259. package/dist/test-utils/interleave.js +26 -0
  260. package/dist/testWorker.d.ts +8 -0
  261. package/dist/testWorker.d.ts.map +1 -0
  262. package/dist/testWorker.js +9 -0
  263. package/dist/version.generated.d.ts +1 -1
  264. package/dist/version.generated.js +1 -1
  265. package/dist/workflow/dispatchRoute.js +1 -1
  266. package/package.json +1 -1
  267. package/src/audit/auditEvent.ts +1 -1
  268. package/src/audit/chain.ts +551 -0
  269. package/src/audit/citations.ts +63 -0
  270. package/src/capability/bindings.ts +61 -0
  271. package/src/capability/capability.ts +156 -19
  272. package/src/capability/erasure.ts +82 -0
  273. package/src/capability/personColumns.ts +83 -0
  274. package/src/capability/personRows.ts +125 -0
  275. package/src/capability/purge.ts +158 -21
  276. package/src/capability/settings.ts +7 -0
  277. package/src/capability/vectorIndex.ts +60 -0
  278. package/src/cli/commandCatalog.generated.ts +487 -2
  279. package/src/cli/commandFacts.ts +12 -3
  280. package/src/cloudflare-test.d.ts +3 -1
  281. package/src/consent/purpose.ts +160 -0
  282. package/src/consent/reader.ts +134 -0
  283. package/src/controlPlane/audit/actions.ts +15 -4
  284. package/src/controlPlane/capability.ts +99 -20
  285. package/src/controlPlane/context.ts +10 -2
  286. package/src/controlPlane/data/connection.ts +16 -2
  287. package/src/controlPlane/data/connections.ts +231 -0
  288. package/src/controlPlane/data/hold.ts +52 -0
  289. package/src/controlPlane/data/oidcTrust.ts +249 -0
  290. package/src/controlPlane/data/tables.ts +7 -0
  291. package/src/controlPlane/data/trustFirstCall.ts +37 -0
  292. package/src/controlPlane/discovery/adminRoute.ts +71 -9
  293. package/src/controlPlane/discovery/routeInput.ts +86 -0
  294. package/src/controlPlane/discovery/toolName.ts +23 -0
  295. package/src/controlPlane/erasure/attest.ts +205 -0
  296. package/src/controlPlane/erasure/read.ts +169 -0
  297. package/src/controlPlane/error/errors.ts +69 -2
  298. package/src/controlPlane/evidence/derive.ts +426 -0
  299. package/src/controlPlane/evidence/mapping.ts +118 -0
  300. package/src/controlPlane/evidence/pack.ts +190 -0
  301. package/src/controlPlane/http/connections.ts +129 -0
  302. package/src/controlPlane/http/guard.ts +118 -9
  303. package/src/controlPlane/http/handlers.ts +172 -40
  304. package/src/controlPlane/http/holds.ts +108 -0
  305. package/src/controlPlane/http/presence.ts +71 -0
  306. package/src/controlPlane/http/processing.ts +19 -0
  307. package/src/controlPlane/http/responses.ts +590 -7
  308. package/src/controlPlane/http/routes.ts +302 -3
  309. package/src/controlPlane/http/schemas.ts +126 -0
  310. package/src/controlPlane/http/verify.ts +19 -2
  311. package/src/controlPlane/http/verifyTrust.ts +176 -0
  312. package/src/controlPlane/migrations/0004_connection_trusts.ts +30 -0
  313. package/src/controlPlane/migrations/0005_holds.ts +40 -0
  314. package/src/controlPlane/processing/derive.ts +359 -0
  315. package/src/controlPlane/processing/read.ts +102 -0
  316. package/src/controlPlane/scope/kind.ts +63 -0
  317. package/src/controlPlane/scope/scope.ts +65 -3
  318. package/src/controlPlane/wire.ts +26 -0
  319. package/src/createBackend.ts +31 -6
  320. package/src/createEntrypoint.ts +17 -0
  321. package/src/error/payload.ts +234 -0
  322. package/src/error/pithyError.ts +42 -0
  323. package/src/http/routeSpec.ts +75 -5
  324. package/src/http/verification.ts +3 -1
  325. package/src/i18n/translator.ts +2 -1
  326. package/src/index.ts +2 -0
  327. package/src/mcp/annotations.ts +38 -0
  328. package/src/mcp/tools.ts +220 -0
  329. package/src/openapi/document.ts +8 -28
  330. package/src/openapi/operations.ts +62 -0
  331. package/src/presence/channel.ts +46 -0
  332. package/src/presence/declaration.ts +283 -0
  333. package/src/presence/durableObject.ts +287 -0
  334. package/src/presence/frames.ts +153 -0
  335. package/src/presence/http/guard.ts +23 -0
  336. package/src/presence/http/responses.ts +63 -0
  337. package/src/presence/http/routes.ts +165 -0
  338. package/src/presence/http/schemas.ts +39 -0
  339. package/src/presence/names.ts +45 -0
  340. package/src/presence/room.ts +145 -0
  341. package/src/presence/ticket.ts +145 -0
  342. package/src/processing/activity.ts +289 -0
  343. package/src/processing/composed.ts +33 -0
  344. package/src/processing/vocabulary.ts +43 -0
  345. package/src/queue/consumer.ts +104 -0
  346. package/src/retention/composed.ts +75 -0
  347. package/src/retention/holds.ts +175 -0
  348. package/src/retention/holdsPurge.ts +50 -0
  349. package/src/retention/policy.ts +305 -0
  350. package/src/session/rotated.ts +53 -0
  351. package/src/test-utils/connectionFixtures.ts +58 -0
  352. package/src/test-utils/interleave.ts +31 -0
  353. package/src/testWorker.ts +13 -0
  354. package/src/version.generated.ts +1 -1
@@ -38,7 +38,7 @@ export declare const RegisteredKey: z.ZodObject<{
38
38
  }, z.core.$strip>;
39
39
  export type RegisteredKey = z.output<typeof RegisteredKey>;
40
40
  /**
41
- * What kind of client a connection is for (#721): `agent`, `dashboard` or `script`.
41
+ * What kind of client a connection is for: `agent`, `dashboard` or `script`.
42
42
  *
43
43
  * The audit trail's `actorType: "control-plane"` names all three identically, and the questions asked
44
44
  * after an incident are exactly the ones that tell them apart — was it a person at the dashboard, a
@@ -69,7 +69,7 @@ export type ConnectionPrincipal = z.output<typeof ConnectionPrincipal>;
69
69
  /** The longest purpose a connection may carry. One line on a review screen, with room to say what it does. */
70
70
  export declare const MAX_CONNECTION_PURPOSE = 200;
71
71
  /**
72
- * What a connection is for, in the words of whoever registered it (#721).
72
+ * What a connection is for, in the words of whoever registered it.
73
73
  *
74
74
  * **Required at registration, and refused when absent**, because a review listing credentials by key id
75
75
  * cannot support the decision it exists for. **Untrusted input**: a customer writes it and something else
@@ -77,6 +77,14 @@ export declare const MAX_CONNECTION_PURPOSE = 200;
77
77
  */
78
78
  export declare const ConnectionPurpose: z.ZodString;
79
79
  export type ConnectionPurpose = z.output<typeof ConnectionPurpose>;
80
+ /** Which credential kinds a connection holds, live or not. */
81
+ export declare const ConnectionCredentialKind: z.ZodEnum<{
82
+ both: "both";
83
+ key: "key";
84
+ none: "none";
85
+ trust: "trust";
86
+ }>;
87
+ export type ConnectionCredentialKind = z.infer<typeof ConnectionCredentialKind>;
80
88
  /**
81
89
  * One management-client connection — the row in `pithy_controlplane_connections`.
82
90
  *
@@ -111,6 +119,17 @@ export declare const ControlPlaneConnection: z.ZodObject<{
111
119
  validUntil: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
112
120
  revokedAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
113
121
  }, z.core.$strip>>>;
122
+ trusts: z.ZodOptional<import("../../data/codecs").SqliteJson<z.ZodArray<z.ZodObject<{
123
+ trustId: z.ZodString;
124
+ issuer: z.ZodURL;
125
+ jwksUrl: z.ZodURL;
126
+ audience: z.ZodString;
127
+ claims: z.ZodRecord<z.ZodString, z.ZodString>;
128
+ authorizedBy: z.ZodEmail;
129
+ validFrom: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>;
130
+ revokedAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
131
+ firstCallAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
132
+ }, z.core.$strip>>>>;
114
133
  createdAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
115
134
  updatedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
116
135
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/connection.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;GASG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB;;;;iBAgBoF,CAAC;AAClH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;iBAoBoF,CAAC;AAC/G,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB;;;;EAI/B,CAAC;AACJ,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE3E;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,YAK7B,CAAC;AACJ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE,8GAA8G;AAC9G,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAiB1C;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,aAW3B,CAAC;AACJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEnE;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkEhC,CAAC;AACJ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC7E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/connection.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;GASG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB;;;;iBAgBoF,CAAC;AAClH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;iBAoBoF,CAAC;AAC/G,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB;;;;EAI/B,CAAC;AACJ,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE3E;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,YAK7B,CAAC;AACJ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE,8GAA8G;AAC9G,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAiB1C;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,aAW3B,CAAC;AACJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEnE,8DAA8D;AAC9D,eAAO,MAAM,wBAAwB;;;;;EAIlC,CAAC;AACJ,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuEhC,CAAC;AACJ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC7E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
@@ -1,7 +1,8 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
- import { JsonDate, SQLiteDate, sqliteJson } from "../../data/codecs.js";
4
3
  import { ControlPlaneScope } from "../scope/scope.js";
4
+ import { JsonDate, SQLiteDate, sqliteJson } from "../../data/codecs.js";
5
+ import { OidcTrust } from "./oidcTrust.js";
5
6
  import { z } from "zod";
6
7
  //#region src/controlPlane/data/connection.ts
7
8
  /**
@@ -35,7 +36,7 @@ const RegisteredKey = z.object({
35
36
  revokedAt: JsonDate.nullable().describe("When this key was revoked outright, or null. Revocation ignores `validUntil` and takes effect immediately — it is the adopter's unilateral control, and it must not wait for a window to close.")
36
37
  }).describe("One registered public key with its validity window. Two live at once during a rotation overlap.");
37
38
  /**
38
- * What kind of client a connection is for (#721): `agent`, `dashboard` or `script`.
39
+ * What kind of client a connection is for: `agent`, `dashboard` or `script`.
39
40
  *
40
41
  * The audit trail's `actorType: "control-plane"` names all three identically, and the questions asked
41
42
  * after an incident are exactly the ones that tell them apart — was it a person at the dashboard, a
@@ -78,13 +79,20 @@ const MAX_CONNECTION_PURPOSE = 200;
78
79
  */
79
80
  const UNRENDERABLE = /[\p{Cc}\p{Bidi_Control}\u2028\u2029\u200B\u2060\uFEFF]/u;
80
81
  /**
81
- * What a connection is for, in the words of whoever registered it (#721).
82
+ * What a connection is for, in the words of whoever registered it.
82
83
  *
83
84
  * **Required at registration, and refused when absent**, because a review listing credentials by key id
84
85
  * cannot support the decision it exists for. **Untrusted input**: a customer writes it and something else
85
86
  * renders it, so it is validated on the way in, and again on every read of the row.
86
87
  */
87
88
  const ConnectionPurpose = z.string().trim().min(1, "Say what this client is for.").max(200, `A purpose is 200 characters at most.`).refine((purpose) => !UNRENDERABLE.test(purpose), "A purpose is one line of plain text: no control, bidi or invisible characters.").describe(`What this connection's client is for, in the registering operator's words — one line of plain text, 200 characters at most, with no control, bidi or invisible characters. Written by a customer and rendered elsewhere, so validated at the boundary.`);
89
+ /** Which credential kinds a connection holds, live or not. */
90
+ const ConnectionCredentialKind = z.enum([
91
+ "key",
92
+ "trust",
93
+ "both",
94
+ "none"
95
+ ]).describe("Which credential kinds the connection holds, live or not: `key` (registered Ed25519 keys), `trust` (OIDC trusts), `both`, or `none`.");
88
96
  /**
89
97
  * One management-client connection — the row in `pithy_controlplane_connections`.
90
98
  *
@@ -105,8 +113,9 @@ const ControlPlaneConnection = z.object({
105
113
  purpose: ConnectionPurpose.nullable().default(null).describe("What this connection's client is for, in the registering operator's words. Required at registration and editable with `pithy dashboard connect --update --purpose`; null only for a connection registered before it was recorded."),
106
114
  scopes: sqliteJson(z.array(ControlPlaneScope)).describe("The operations this connection was granted, stored and enforced on the adopter's side. Enforced only by the caller, a scope would not be a limit at all."),
107
115
  keys: sqliteJson(z.array(RegisteredKey)).describe("Every key this connection may sign with, current and superseded. One JSON column rather than a child table, so a rotation is one atomic UPDATE and verification is one read."),
116
+ trusts: sqliteJson(z.array(OidcTrust)).optional().describe("Every OIDC trust this connection honors, live and revoked (#722) — the second credential kind beside `keys`, so a connection may hold a trust, a key, or both. Optional on the type, and absent reads as none: a connection value built before trusts existed still describes a connection, and the column itself defaults to an empty list."),
108
117
  createdAt: SQLiteDate.describe("When the connection was registered. Ms-epoch in SQLite, a Date in TypeScript."),
109
118
  updatedAt: SQLiteDate.describe("When the row last changed — a key registered or expired, or the Worker URL re-pointed.")
110
119
  }).describe("One management-client connection: its identity, the environment and URL it is bound to, the operations it was granted, and its versioned public keys.");
111
120
  //#endregion
112
- export { ConnectionPrincipal, ConnectionPurpose, ControlPlaneActorKind, ControlPlaneConnection, Ed25519PublicJwk, MAX_CONNECTION_PURPOSE, RegisteredKey };
121
+ export { ConnectionCredentialKind, ConnectionPrincipal, ConnectionPurpose, ControlPlaneActorKind, ControlPlaneConnection, Ed25519PublicJwk, MAX_CONNECTION_PURPOSE, RegisteredKey };
@@ -0,0 +1,94 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { type ConnectionCredentialKind, ControlPlaneConnection, type RegisteredKey } from "./connection";
4
+ import { type OidcTrust } from "./oidcTrust";
5
+ import { type ControlPlaneDatabase } from "./tables";
6
+ /**
7
+ * Every connection in one database, read for a review: which person authorized each, and which still answer.
8
+ *
9
+ * `GET {basePath}/connections` and `pithy dashboard connections` both read through {@link listConnections}, so the
10
+ * Worker's answer and the CLI's cannot list different things.
11
+ */
12
+ /** Which credential kinds a connection holds, live or not. */
13
+ export declare function connectionCredentialKind(connection: ControlPlaneConnection): ConnectionCredentialKind;
14
+ /** Whether any key or trust on the connection can verify a call at `now`. */
15
+ export declare function connectionLive(connection: ControlPlaneConnection, now: Date): boolean;
16
+ /**
17
+ * When the connection was revoked: the latest revocation among its credentials, once none is live and every one of
18
+ * them was revoked. Null while anything is live, and for a connection whose keys merely expired.
19
+ */
20
+ export declare function connectionRevokedAt(connection: ControlPlaneConnection, now: Date): Date | null;
21
+ /** What {@link revokeCredentials} changed. */
22
+ export interface RevokedCredentials {
23
+ /** The connection with every key and trust not yet revoked revoked at `now`. */
24
+ readonly connection: ControlPlaneConnection;
25
+ readonly revokedKeyIds: readonly string[];
26
+ readonly revokedTrustIds: readonly string[];
27
+ }
28
+ /**
29
+ * Revoke every key and trust not yet revoked, whatever its window. One that has not started will verify once its
30
+ * window opens, and the revoker's clock may run behind the Worker that stamped it, so it is revoked too. A credential
31
+ * already revoked keeps its instant.
32
+ */
33
+ export declare function revokeCredentials(connection: ControlPlaneConnection, now: Date): RevokedCredentials;
34
+ /** Whether a connection was authorized by `principal`. An email, compared without regard to case. */
35
+ export declare function authorizedByPrincipal(connection: ControlPlaneConnection, principal: string): boolean;
36
+ /** Which connections {@link listConnections} reads. */
37
+ export interface ConnectionFilter {
38
+ /** Only connections this email authorized. Unattributed connections are listed apart either way. */
39
+ readonly principal?: string | undefined;
40
+ /** Only connections bound to this environment. */
41
+ readonly environment?: string | undefined;
42
+ }
43
+ /** The connections a filter selects, with the ones no principal was recorded for kept apart. */
44
+ export interface ConnectionListing {
45
+ /** Connections with a recorded principal, matching the filter, oldest first. */
46
+ readonly connections: readonly ControlPlaneConnection[];
47
+ /** Connections registered before principals were recorded, oldest first. Never matched by a principal. */
48
+ readonly unattributed: readonly ControlPlaneConnection[];
49
+ }
50
+ /** Every connection in the database, attributed ones matching the filter, unattributed ones apart. */
51
+ export declare function listConnections(db: ControlPlaneDatabase, filter?: ConnectionFilter): Promise<ConnectionListing>;
52
+ /** Read one connection by id, or null. */
53
+ export declare function readConnection(db: ControlPlaneDatabase, id: string): Promise<ControlPlaneConnection | null>;
54
+ /**
55
+ * A connection read for a write to its credential columns, with those columns exactly as stored. A write made from it
56
+ * lands only while they are unchanged, so a revocation landing in between is never overwritten.
57
+ */
58
+ export interface ConnectionForWrite {
59
+ readonly connection: ControlPlaneConnection;
60
+ /** The keys column as stored, compared as stored. */
61
+ readonly storedKeys: unknown;
62
+ /** The trusts column as stored, compared as stored. */
63
+ readonly storedTrusts: unknown;
64
+ }
65
+ /** Read one connection by id for a write, or null. With `environment`, null for a connection bound to another. */
66
+ export declare function readConnectionForWrite(db: ControlPlaneDatabase, id: string, options?: {
67
+ readonly environment?: string | undefined;
68
+ }): Promise<ConnectionForWrite | null>;
69
+ /**
70
+ * Write a connection's keys and stamp `updatedAt`, only while its keys column still holds what `read` saw. False
71
+ * when it no longer does: another write, a revocation among them, landed in between and nothing was written.
72
+ */
73
+ export declare function writeKeys(db: ControlPlaneDatabase, read: ConnectionForWrite, keys: readonly RegisteredKey[], now: Date): Promise<boolean>;
74
+ /** The credential columns a {@link writeCredentials} call replaces. A column left out is not written. */
75
+ export interface CredentialColumns {
76
+ readonly keys?: readonly RegisteredKey[] | undefined;
77
+ readonly trusts?: readonly OidcTrust[] | undefined;
78
+ }
79
+ /**
80
+ * Write the given credential columns and stamp `updatedAt`, only while both the keys and the trusts columns still hold
81
+ * what `read` saw. False when either changed in between, and nothing was written.
82
+ */
83
+ export declare function writeCredentials(db: ControlPlaneDatabase, read: ConnectionForWrite, columns: CredentialColumns, now: Date): Promise<boolean>;
84
+ /**
85
+ * Revoke every key and trust of one connection not yet revoked, or answer null when no connection has that id. With
86
+ * `environment`, a connection bound to another environment answers null too.
87
+ *
88
+ * Writes the two credential columns and the stamp and nothing else on the row, and only while both columns still
89
+ * hold what it read; when another write landed in between, it reads again.
90
+ */
91
+ export declare function revokeConnection(db: ControlPlaneDatabase, id: string, now: Date, options?: {
92
+ readonly environment?: string | undefined;
93
+ }): Promise<RevokedCredentials | null>;
94
+ //# sourceMappingURL=connections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connections.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/connections.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,wBAAwB,EAAE,sBAAsB,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzG,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAmC,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAEtF;;;;;GAKG;AAEH,8DAA8D;AAC9D,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,sBAAsB,GAAG,wBAAwB,CAOrG;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAErF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAU9F;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,EAAE,IAAI,GAAG,kBAAkB,CAenG;AAED,qGAAqG;AACrG,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEpG;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,oGAAoG;IACpG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,kDAAkD;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,gGAAgG;AAChG,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACxD,0GAA0G;IAC1G,QAAQ,CAAC,YAAY,EAAE,SAAS,sBAAsB,EAAE,CAAC;CAC1D;AAED,sGAAsG;AACtG,wBAAsB,eAAe,CACnC,EAAE,EAAE,oBAAoB,EACxB,MAAM,GAAE,gBAAqB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAW5B;AAED,0CAA0C;AAC1C,wBAAsB,cAAc,CAAC,EAAE,EAAE,oBAAoB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAGjH;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;IAC5C,qDAAqD;IACrD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED,kHAAkH;AAClH,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,oBAAoB,EACxB,EAAE,EAAE,MAAM,EACV,OAAO,GAAE;IAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GAC1D,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAMpC;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,oBAAoB,EACxB,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,SAAS,aAAa,EAAE,EAC9B,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,OAAO,CAAC,CAWlB;AAED,yGAAyG;AACzG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,CAAC;CACpD;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,oBAAoB,EACxB,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,iBAAiB,EAC1B,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,OAAO,CAAC,CAelB;AAKD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,oBAAoB,EACxB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,IAAI,EACT,OAAO,GAAE;IAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GAC1D,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAcpC"}
@@ -0,0 +1,151 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { ConflictError } from "../../error/pithyError.js";
4
+ import { activeTrusts } from "./oidcTrust.js";
5
+ import { ControlPlaneConnection } from "./connection.js";
6
+ import { CONTROL_PLANE_CONNECTIONS_TABLE } from "./tables.js";
7
+ import { activeKeys } from "./keyLifecycle.js";
8
+ import { sql } from "kysely";
9
+ //#region src/controlPlane/data/connections.ts
10
+ /**
11
+ * Every connection in one database, read for a review: which person authorized each, and which still answer.
12
+ *
13
+ * `GET {basePath}/connections` and `pithy dashboard connections` both read through {@link listConnections}, so the
14
+ * Worker's answer and the CLI's cannot list different things.
15
+ */
16
+ /** Which credential kinds a connection holds, live or not. */
17
+ function connectionCredentialKind(connection) {
18
+ const keys = connection.keys.length > 0;
19
+ const trusts = (connection.trusts ?? []).length > 0;
20
+ if (keys && trusts) return "both";
21
+ if (keys) return "key";
22
+ if (trusts) return "trust";
23
+ return "none";
24
+ }
25
+ /** Whether any key or trust on the connection can verify a call at `now`. */
26
+ function connectionLive(connection, now) {
27
+ return activeKeys(connection.keys, now).length > 0 || activeTrusts(connection.trusts ?? [], now).length > 0;
28
+ }
29
+ /**
30
+ * When the connection was revoked: the latest revocation among its credentials, once none is live and every one of
31
+ * them was revoked. Null while anything is live, and for a connection whose keys merely expired.
32
+ */
33
+ function connectionRevokedAt(connection, now) {
34
+ if (connectionLive(connection, now)) return null;
35
+ const credentials = [...connection.keys, ...connection.trusts ?? []];
36
+ if (credentials.length === 0) return null;
37
+ let latest = null;
38
+ for (const credential of credentials) {
39
+ if (credential.revokedAt === null) return null;
40
+ if (latest === null || credential.revokedAt.getTime() > latest.getTime()) latest = credential.revokedAt;
41
+ }
42
+ return latest;
43
+ }
44
+ /**
45
+ * Revoke every key and trust not yet revoked, whatever its window. One that has not started will verify once its
46
+ * window opens, and the revoker's clock may run behind the Worker that stamped it, so it is revoked too. A credential
47
+ * already revoked keeps its instant.
48
+ */
49
+ function revokeCredentials(connection, now) {
50
+ const keyIds = connection.keys.filter((key) => key.revokedAt === null).map((key) => key.keyId);
51
+ const trustIds = (connection.trusts ?? []).filter((trust) => trust.revokedAt === null).map((trust) => trust.trustId);
52
+ return {
53
+ connection: {
54
+ ...connection,
55
+ keys: connection.keys.map((key) => key.revokedAt === null ? {
56
+ ...key,
57
+ revokedAt: now
58
+ } : key),
59
+ trusts: (connection.trusts ?? []).map((trust) => trust.revokedAt === null ? {
60
+ ...trust,
61
+ revokedAt: now
62
+ } : trust),
63
+ updatedAt: keyIds.length + trustIds.length > 0 ? now : connection.updatedAt
64
+ },
65
+ revokedKeyIds: keyIds,
66
+ revokedTrustIds: trustIds
67
+ };
68
+ }
69
+ /** Whether a connection was authorized by `principal`. An email, compared without regard to case. */
70
+ function authorizedByPrincipal(connection, principal) {
71
+ return connection.authorizedBy !== null && connection.authorizedBy.toLowerCase() === principal.toLowerCase();
72
+ }
73
+ /** Every connection in the database, attributed ones matching the filter, unattributed ones apart. */
74
+ async function listConnections(db, filter = {}) {
75
+ let query = db.selectFrom(CONTROL_PLANE_CONNECTIONS_TABLE).selectAll().orderBy("createdAt").orderBy("id");
76
+ if (filter.environment !== void 0) query = query.where("environment", "=", filter.environment);
77
+ const rows = (await query.execute()).map((row) => ControlPlaneConnection.parse(row));
78
+ const { principal } = filter;
79
+ return {
80
+ connections: rows.filter((row) => row.authorizedBy !== null && (principal === void 0 || authorizedByPrincipal(row, principal))),
81
+ unattributed: rows.filter((row) => row.authorizedBy === null)
82
+ };
83
+ }
84
+ /** Read one connection by id, or null. */
85
+ async function readConnection(db, id) {
86
+ const row = await db.selectFrom(CONTROL_PLANE_CONNECTIONS_TABLE).selectAll().where("id", "=", id).executeTakeFirst();
87
+ return row ? ControlPlaneConnection.parse(row) : null;
88
+ }
89
+ /** Read one connection by id for a write, or null. With `environment`, null for a connection bound to another. */
90
+ async function readConnectionForWrite(db, id, options = {}) {
91
+ let query = db.selectFrom(CONTROL_PLANE_CONNECTIONS_TABLE).selectAll().where("id", "=", id);
92
+ if (options.environment !== void 0) query = query.where("environment", "=", options.environment);
93
+ const row = await query.executeTakeFirst();
94
+ if (!row) return null;
95
+ return {
96
+ connection: ControlPlaneConnection.parse(row),
97
+ storedKeys: row.keys,
98
+ storedTrusts: row.trusts
99
+ };
100
+ }
101
+ /**
102
+ * Write a connection's keys and stamp `updatedAt`, only while its keys column still holds what `read` saw. False
103
+ * when it no longer does: another write, a revocation among them, landed in between and nothing was written.
104
+ */
105
+ async function writeKeys(db, read, keys, now) {
106
+ const result = await db.updateTable(CONTROL_PLANE_CONNECTIONS_TABLE).set({
107
+ keys: ControlPlaneConnection.shape.keys.encode([...keys]),
108
+ updatedAt: ControlPlaneConnection.shape.updatedAt.encode(now)
109
+ }).where("id", "=", read.connection.id).where(sql`${sql.ref("keys")} = ${read.storedKeys}`).executeTakeFirst();
110
+ return Number(result.numUpdatedRows) > 0;
111
+ }
112
+ /**
113
+ * Write the given credential columns and stamp `updatedAt`, only while both the keys and the trusts columns still hold
114
+ * what `read` saw. False when either changed in between, and nothing was written.
115
+ */
116
+ async function writeCredentials(db, read, columns, now) {
117
+ const result = await db.updateTable(CONTROL_PLANE_CONNECTIONS_TABLE).set({
118
+ ...columns.keys === void 0 ? {} : { keys: ControlPlaneConnection.shape.keys.encode([...columns.keys]) },
119
+ ...columns.trusts === void 0 ? {} : { trusts: ControlPlaneConnection.shape.trusts.encode([...columns.trusts]) },
120
+ updatedAt: ControlPlaneConnection.shape.updatedAt.encode(now)
121
+ }).where("id", "=", read.connection.id).where(sql`${sql.ref("keys")} = ${read.storedKeys}`).where(sql`${sql.ref("trusts")} = ${read.storedTrusts}`).executeTakeFirst();
122
+ return Number(result.numUpdatedRows) > 0;
123
+ }
124
+ /** How many times {@link revokeConnection} reads again after a write landed between its read and its own. */
125
+ const REVOKE_ATTEMPTS = 5;
126
+ /**
127
+ * Revoke every key and trust of one connection not yet revoked, or answer null when no connection has that id. With
128
+ * `environment`, a connection bound to another environment answers null too.
129
+ *
130
+ * Writes the two credential columns and the stamp and nothing else on the row, and only while both columns still
131
+ * hold what it read; when another write landed in between, it reads again.
132
+ */
133
+ async function revokeConnection(db, id, now, options = {}) {
134
+ for (let attempt = 0; attempt < REVOKE_ATTEMPTS; attempt++) {
135
+ const read = await readConnectionForWrite(db, id, options);
136
+ if (read === null) return null;
137
+ const revoked = revokeCredentials(read.connection, now);
138
+ if (revoked.revokedKeyIds.length + revoked.revokedTrustIds.length === 0) return revoked;
139
+ if (await writeCredentials(db, read, {
140
+ keys: revoked.connection.keys,
141
+ trusts: revoked.connection.trusts ?? []
142
+ }, now)) return revoked;
143
+ }
144
+ throw new ConflictError({
145
+ message: "The connection kept changing while it was being revoked.",
146
+ action: "Revoke it again.",
147
+ detail: `connection ${id} changed between read and write ${REVOKE_ATTEMPTS} times`
148
+ });
149
+ }
150
+ //#endregion
151
+ export { authorizedByPrincipal, connectionCredentialKind, connectionLive, connectionRevokedAt, listConnections, readConnection, readConnectionForWrite, revokeConnection, revokeCredentials, writeCredentials, writeKeys };
@@ -0,0 +1,46 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ /**
5
+ * **A legal hold: one subject preserved, with the reason it was placed and, once released, why.**
6
+ *
7
+ * Placed and released over the control plane, behind `retention:hold`, because a hold is operational and urgent:
8
+ * counsel says preserve everything about this customer, and the next scheduled purge is tonight. While a hold stands
9
+ * no purge, prune or inactivity deletion touches its subject. Released holds are kept — the row is the record that
10
+ * one stood, from when to when, and why it ended — so nothing here is ever deleted.
11
+ */
12
+ /** What a hold preserves: one user, one organization, or one tenant's audit trail. */
13
+ export declare const HoldSubjectType: z.ZodEnum<{
14
+ audit: "audit";
15
+ organization: "organization";
16
+ user: "user";
17
+ }>;
18
+ export type HoldSubjectType = z.infer<typeof HoldSubjectType>;
19
+ /** Whether a hold is in force. */
20
+ export declare const HoldState: z.ZodEnum<{
21
+ active: "active";
22
+ released: "released";
23
+ }>;
24
+ export type HoldState = z.infer<typeof HoldState>;
25
+ /** One hold, as `pithy_controlplane_holds` stores it. */
26
+ export declare const LegalHold: z.ZodObject<{
27
+ id: z.ZodString;
28
+ subjectType: z.ZodEnum<{
29
+ audit: "audit";
30
+ organization: "organization";
31
+ user: "user";
32
+ }>;
33
+ subjectId: z.ZodNullable<z.ZodString>;
34
+ reason: z.ZodString;
35
+ placedBy: z.ZodString;
36
+ placedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
37
+ state: z.ZodEnum<{
38
+ active: "active";
39
+ released: "released";
40
+ }>;
41
+ releasedAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>>;
42
+ releasedBy: z.ZodNullable<z.ZodString>;
43
+ releaseReason: z.ZodNullable<z.ZodString>;
44
+ }, z.core.$strip>;
45
+ export type LegalHold = z.output<typeof LegalHold>;
46
+ //# sourceMappingURL=hold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hold.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/hold.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;GAOG;AAEH,sFAAsF;AACtF,eAAO,MAAM,eAAe;;;;EAIzB,CAAC;AACJ,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,kCAAkC;AAClC,eAAO,MAAM,SAAS;;;EAE6E,CAAC;AACpG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAElD,yDAAyD;AACzD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;iBAoBgE,CAAC;AACvF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC"}
@@ -0,0 +1,36 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { SQLiteDate } from "../../data/codecs.js";
4
+ import { z } from "zod";
5
+ //#region src/controlPlane/data/hold.ts
6
+ /**
7
+ * **A legal hold: one subject preserved, with the reason it was placed and, once released, why.**
8
+ *
9
+ * Placed and released over the control plane, behind `retention:hold`, because a hold is operational and urgent:
10
+ * counsel says preserve everything about this customer, and the next scheduled purge is tonight. While a hold stands
11
+ * no purge, prune or inactivity deletion touches its subject. Released holds are kept — the row is the record that
12
+ * one stood, from when to when, and why it ended — so nothing here is ever deleted.
13
+ */
14
+ /** What a hold preserves: one user, one organization, or one tenant's audit trail. */
15
+ const HoldSubjectType = z.enum([
16
+ "user",
17
+ "organization",
18
+ "audit"
19
+ ]).describe("What the hold preserves: `user`, one account and everything its purge would erase; `organization`, one tenant and everything its purge would erase; `audit`, one tenant's audit trail, which the prune then leaves whole.");
20
+ /** Whether a hold is in force. */
21
+ const HoldState = z.enum(["active", "released"]).describe("`active` while the hold stands; `released` once it has been lifted, with the reason.");
22
+ /** One hold, as `pithy_controlplane_holds` stores it. */
23
+ const LegalHold = z.object({
24
+ id: z.string().min(1).describe("The hold's id. Text, never a sequence: it is named on a release route."),
25
+ subjectType: HoldSubjectType,
26
+ subjectId: z.string().min(1).max(255).nullable().describe("The held user's or organization's id, or the tenant whose audit trail is held. Null only for an `audit` hold on the untenanted trail."),
27
+ reason: z.string().min(1).max(500).describe("Why the hold was placed, in whoever placed it's words."),
28
+ placedBy: z.string().describe("The management client's own user id that placed it — the token's `sub`."),
29
+ placedAt: SQLiteDate.describe("When the hold was placed. Ms-epoch in SQLite."),
30
+ state: HoldState,
31
+ releasedAt: SQLiteDate.nullable().describe("When the hold was released, or null while it stands."),
32
+ releasedBy: z.string().nullable().describe("Who released it, or null while it stands."),
33
+ releaseReason: z.string().max(500).nullable().describe("Why it was released, or null while it stands.")
34
+ }).describe("A legal hold on one user, one organization or one tenant's audit trail.");
35
+ //#endregion
36
+ export { HoldState, HoldSubjectType, LegalHold };
@@ -0,0 +1,122 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ /**
5
+ * An OIDC trust: the second credential kind a connection may hold, beside a registered key
6
+ * (`docs/AGENT-ACCESS.md` §6.2, #722).
7
+ *
8
+ * A key is a bearer: one client holds the private half. A trust is a **policy**: a trusted issuer, the
9
+ * audience its tokens must name, and a matcher over the claims that say which workload a token was minted
10
+ * for. Nobody holds a secret. The workload asks its own identity provider for a short-lived token, and the
11
+ * Worker checks it against the issuer's published keys with the verifier the webhooks already use,
12
+ * `core/src/http/oidcWebhook.ts`.
13
+ *
14
+ * ## The claims become data here, and that is a deliberate departure
15
+ *
16
+ * In a webhook the claims check is a predicate the **route** holds, because whoever wrote the route knows
17
+ * the legitimate sender. A connection's trust is per adopter, decided at registration by the same person who
18
+ * grants the scopes, so the matcher is stored with the trust and validated on the way in. That is the whole
19
+ * reason {@link OidcClaimMatch} exists rather than a function.
20
+ *
21
+ * ## The claims check is the boundary, not the signature
22
+ *
23
+ * An identity provider signs for everybody with the same keys. GitHub signs a token for every repository on
24
+ * the planet. A trust whose matcher constrains nothing accepts all of them, so {@link OidcClaimMatch} refuses
25
+ * one at registration — structurally, not as a warning — and refuses the claims that name the token rather
26
+ * than the workload ({@link RESERVED_OIDC_CLAIMS}), since a matcher made of those constrains nothing either.
27
+ *
28
+ * ## Named claims, not the `sub` string
29
+ *
30
+ * A `sub` is a concatenation whose composition belongs to the provider and to the adopter's own template.
31
+ * The claims beneath it — GitHub's `repository_id`, `environment`, `ref`; GitLab's `project_id` — are
32
+ * published individually and are stable. So the matcher is a map of claim names to values, and `sub` is one
33
+ * claim among the others rather than a special case.
34
+ *
35
+ * ## No rotation, and revocation changes shape
36
+ *
37
+ * There is no key to append, prove and expire. A token cannot be revoked; the trust is, and every token
38
+ * already minted stays live until it expires — {@link TRUST_REVOCATION_LATENCY} is the sentence that says
39
+ * so at the moment it matters.
40
+ *
41
+ * Nothing in this module reads a clock, a binding or D1. `now` is injected wherever time matters.
42
+ */
43
+ /**
44
+ * Claims that name the token rather than the workload: who minted it, for whom, and when. A matcher built
45
+ * from these alone would accept every workload the issuer serves, so none of them may be registered.
46
+ * `iss` and `aud` are checked anyway, by the trust's own issuer and audience.
47
+ */
48
+ export declare const RESERVED_OIDC_CLAIMS: readonly ["iss", "aud", "exp", "nbf", "iat", "jti"];
49
+ /** The most claims one trust may match on. Far above any real constraint; a bound on a stored row. */
50
+ export declare const MAX_TRUST_CLAIMS = 16;
51
+ /** The most claims a refusal records, so a token carrying a hundred cannot fill the audit trail. */
52
+ export declare const MAX_OBSERVED_CLAIMS = 32;
53
+ /** The longest claim value a matcher stores or a refusal records. */
54
+ export declare const MAX_CLAIM_VALUE = 256;
55
+ /**
56
+ * One claim name in a matcher. A name, not a path: nested claims are not matched, because a matcher that
57
+ * walks a structure is a matcher with more ways to be wrong on the check the whole trust rests on.
58
+ */
59
+ export declare const OidcClaimName: z.ZodString;
60
+ export type OidcClaimName = z.infer<typeof OidcClaimName>;
61
+ /** One claim value in a matcher: compared as a string against the token's claim of the same name. */
62
+ export declare const OidcClaimValue: z.ZodString;
63
+ export type OidcClaimValue = z.infer<typeof OidcClaimValue>;
64
+ /**
65
+ * Which workloads a trust accepts: every named claim must equal its value.
66
+ *
67
+ * **At least one, and that is the refusal the whole credential kind rests on.** An issuer with no claim
68
+ * constraint accepts every workload it serves.
69
+ */
70
+ export declare const OidcClaimMatch: z.ZodRecord<z.ZodString, z.ZodString>;
71
+ export type OidcClaimMatch = z.infer<typeof OidcClaimMatch>;
72
+ /**
73
+ * One registered OIDC trust, an element of the connection's `trusts` column.
74
+ *
75
+ * Stored beside `keys` rather than inside it: the two kinds share nothing but the connection they grant
76
+ * through, and a union array would make every key reader learn about trusts.
77
+ */
78
+ export declare const OidcTrust: z.ZodObject<{
79
+ trustId: z.ZodString;
80
+ issuer: z.ZodURL;
81
+ jwksUrl: z.ZodURL;
82
+ audience: z.ZodString;
83
+ claims: z.ZodRecord<z.ZodString, z.ZodString>;
84
+ authorizedBy: z.ZodEmail;
85
+ validFrom: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>;
86
+ revokedAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
87
+ firstCallAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
88
+ }, z.core.$strip>;
89
+ export type OidcTrust = z.output<typeof OidcTrust>;
90
+ /**
91
+ * What a caller should expect after a trust is revoked, said at the moment it is revoked rather than
92
+ * discovered afterwards.
93
+ */
94
+ export declare const TRUST_REVOCATION_LATENCY = "A token cannot be revoked, only the trust. Every call naming this trust is refused from now on, but a token already minted stays in its holder's hands until it expires \u2014 the issuer's own lifetime, typically minutes.";
95
+ /** Every trust that may verify a call at `now`: not revoked, and started. */
96
+ export declare function activeTrusts(trusts: readonly OidcTrust[], now: Date): OidcTrust[];
97
+ /** The trust a call names, if it is live at `now`, or null. The revocation check lives here so no caller forgets it. */
98
+ export declare function findVerifyingTrust(trusts: readonly OidcTrust[], trustId: string, now: Date): OidcTrust | null;
99
+ /** Register a trust beside the others. A duplicate id is refused: the id is what a call addresses. */
100
+ export declare function appendTrust(trusts: readonly OidcTrust[], trust: OidcTrust): OidcTrust[];
101
+ /**
102
+ * Revoke one trust at `now`, or answer null when no trust answers to that id. A trust already revoked keeps
103
+ * the instant it was first revoked: the window a call was refused under must not move.
104
+ */
105
+ export declare function revokeTrust(trusts: readonly OidcTrust[], trustId: string, now: Date): OidcTrust[] | null;
106
+ /**
107
+ * Whether a token's claims satisfy a trust's matcher: every registered claim present and equal.
108
+ *
109
+ * An empty matcher matches nothing. Registration refuses one, and this refuses it again rather than letting
110
+ * a hand-edited row vacuously admit every workload.
111
+ */
112
+ export declare function claimsMatch(match: Readonly<Record<string, string>>, claims: Readonly<Record<string, unknown>>): boolean;
113
+ /**
114
+ * The claims a refused token carried, as a refusal records them: the workload's claims, as strings, capped.
115
+ *
116
+ * This is what makes a custom issuer workable and a profiled one self-correcting — register, let the
117
+ * workload run once, read what it actually sent. Only ever called once the signature, issuer and audience
118
+ * have verified, so the caller demonstrably holds a genuine token for this audience and naming its claims
119
+ * leaks nothing it does not already hold.
120
+ */
121
+ export declare function observedClaims(claims: Readonly<Record<string, unknown>>): Record<string, string>;
122
+ //# sourceMappingURL=oidcTrust.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidcTrust.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/oidcTrust.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,YAAI,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAExF,sGAAsG;AACtG,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,oGAAoG;AACpG,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,qEAAqE;AACrE,eAAO,MAAM,eAAe,MAAM,CAAC;AAUnC;;;GAGG;AACH,eAAO,MAAM,aAAa,aAQvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,qGAAqG;AACrG,eAAO,MAAM,cAAc,aAOxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,cAAc,uCAUxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAK5D;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;iBAuCnB,CAAC;AACJ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,iOACsL,CAAC;AAE5N,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,SAAS,EAAE,CAEjF;AAED,wHAAwH;AACxH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,CAE7G;AAED,sGAAsG;AACtG,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE,CASvF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,SAAS,EAAE,GAAG,IAAI,CAKxG;AAUD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACvC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,OAAO,CAIT;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUhG"}