@pithy-sh/core 0.13.0 → 0.14.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 +433 -3
  35. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  36. package/dist/cli/commandCatalog.generated.js +449 -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 +482 -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
@@ -1,13 +1,19 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
- import { ANY_VERIFIED_CALLER, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, MIGRATIONS_READ_SCOPE, PRIVACY_READ_SCOPE } from "../scope/scope.js";
4
- import { requireControlPlane } from "./guard.js";
3
+ import { ANY_VERIFIED_CALLER, CONNECTIONS_READ_SCOPE, CONNECTIONS_REVOKE_SCOPE, ERASURE_READ_SCOPE, EVIDENCE_READ_SCOPE, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, MIGRATIONS_READ_SCOPE, PRIVACY_READ_SCOPE, PROCESSING_READ_SCOPE, RETENTION_HOLD_SCOPE, RETENTION_READ_SCOPE } from "../scope/scope.js";
4
+ import { ControlPlanePresenceTicketRequest } from "../../presence/http/schemas.js";
5
5
  import { describeRoute } from "../../http/routeSpec.js";
6
6
  import { validate } from "../../http/validation.js";
7
+ import { PresenceTicket } from "../../presence/http/responses.js";
8
+ import { requireControlPlane } from "./guard.js";
7
9
  import { ControlPlaneManifest } from "../discovery/adminRoute.js";
8
- import { expireKeyHandler, listKeysHandler, manifestHandler, migrationsHandler, pingHandler, privacyHandler, registerKeyHandler } from "./handlers.js";
9
- import { ControlPlaneKeysResponse, ControlPlaneMigrationsResponse, ControlPlanePingResponse, ControlPlanePrivacyResponse, ExpireKeyResponse, RegisterKeyResponse } from "./responses.js";
10
- import { ExpireKeyParams, ExpireKeyRequest, RegisterKeyRequest } from "./schemas.js";
10
+ import { erasurePeriodHandler, erasureSubjectHandler, evidenceHandler, expireKeyHandler, listKeysHandler, manifestHandler, migrationsHandler, pingHandler, privacyHandler, registerKeyHandler } from "./handlers.js";
11
+ import { listConnectionsHandler, revokeConnectionHandler } from "./connections.js";
12
+ import { listHoldsHandler, placeHoldHandler, releaseHoldHandler } from "./holds.js";
13
+ import { presenceTicketGate, presenceTicketHandler } from "./presence.js";
14
+ import { processingHandler } from "./processing.js";
15
+ import { ControlPlaneConnectionsResponse, ControlPlaneErasurePeriodResponse, ControlPlaneErasureSubjectResponse, ControlPlaneEvidenceResponse, ControlPlaneHoldResponse, ControlPlaneHoldsResponse, ControlPlaneKeysResponse, ControlPlaneMigrationsResponse, ControlPlanePingResponse, ControlPlanePrivacyResponse, ControlPlaneProcessingResponse, ExpireKeyResponse, RegisterKeyResponse, RevokeConnectionResponse } from "./responses.js";
16
+ import { ConnectionListQuery, ConnectionParams, ErasurePeriodQuery, ErasureSubjectParams, ExpireKeyParams, ExpireKeyRequest, HoldListQuery, HoldParams, PlaceHoldRequest, RegisterKeyRequest, ReleaseHoldRequest, RevokeConnectionRequest } from "./schemas.js";
11
17
  //#region src/controlPlane/http/routes.ts
12
18
  /**
13
19
  * The seam's own routes, distinct from the admin routes capabilities contribute.
@@ -17,8 +23,18 @@ import { ExpireKeyParams, ExpireKeyRequest, RegisterKeyRequest } from "./schemas
17
23
  * GET /control-plane/keys → the registration state (keys:rotate)
18
24
  * POST /control-plane/keys → register a key (keys:rotate)
19
25
  * POST /control-plane/keys/:keyId/expire → retire a superseded key (keys:rotate)
26
+ * GET /control-plane/connections → every connection, by principal (connections:read)
27
+ * POST /control-plane/connections/:id/revoke → revoke another connection (connections:revoke)
20
28
  * GET /control-plane/migrations → where each database stands (migrations:read)
21
29
  * GET /control-plane/privacy → what each schema holds (privacy:read)
30
+ * GET /control-plane/evidence → SOC 2 and ISO 27001 evidence (evidence:read)
31
+ * GET /control-plane/processing → the Article 30 record (processing:read)
32
+ * GET /control-plane/erasure → every purge in a period (erasure:read)
33
+ * GET /control-plane/erasure/:type/:id → one subject's purge, attested (erasure:read)
34
+ * GET /control-plane/holds → every legal hold (retention:read)
35
+ * POST /control-plane/holds → place a legal hold (retention:hold)
36
+ * POST /control-plane/holds/:id/release → release one, with a reason (retention:hold)
37
+ * POST /control-plane/presence/tickets → a ticket into a presence room (the room kind's scope)
22
38
  *
23
39
  * Every one is `control-plane` and default-denied — with no connection registered they all answer 403,
24
40
  * and that is the shipped state of a Worker nobody has connected.
@@ -48,45 +64,137 @@ function controlPlaneRouteDescriptors(basePath) {
48
64
  return [
49
65
  {
50
66
  method: "GET",
67
+ operation: "ping",
51
68
  path: `${basePath}/ping`,
52
69
  scope: null,
53
70
  summary: "Prove connectivity and which key answered. Always available to a verified caller."
54
71
  },
55
72
  {
56
73
  method: "GET",
74
+ operation: "manifest.read",
57
75
  path: `${basePath}/manifest`,
58
76
  scope: MANIFEST_READ_SCOPE,
77
+ kind: "reads",
59
78
  summary: "What this Worker composes, and how to call each capability's admin surface."
60
79
  },
61
80
  {
62
81
  method: "GET",
82
+ operation: "keys.list",
63
83
  path: `${basePath}/keys`,
64
84
  scope: KEYS_ROTATE_SCOPE,
85
+ kind: "credentials",
65
86
  summary: "The registered keys, their validity windows, and their ages."
66
87
  },
67
88
  {
68
89
  method: "POST",
90
+ operation: "keys.register",
69
91
  path: `${basePath}/keys`,
70
92
  scope: KEYS_ROTATE_SCOPE,
93
+ kind: "credentials",
71
94
  summary: "Register a successor public key. Signed with the key it replaces; appends only."
72
95
  },
73
96
  {
74
97
  method: "POST",
98
+ operation: "keys.expire",
75
99
  path: `${basePath}/keys/:keyId/expire`,
76
100
  scope: KEYS_ROTATE_SCOPE,
101
+ kind: "credentials",
77
102
  summary: "Retire a superseded key. Must be signed with the successor it names as proven."
78
103
  },
79
104
  {
80
105
  method: "GET",
106
+ operation: "connections.list",
107
+ path: `${basePath}/connections`,
108
+ scope: CONNECTIONS_READ_SCOPE,
109
+ kind: "reads",
110
+ summary: "Every connection bound to this Worker's environment: who authorized it, what kind of client it is for and why, its credentials, grant and trust constraints, and when it was registered or revoked. Filterable by principal. No key material."
111
+ },
112
+ {
113
+ method: "POST",
114
+ operation: "connections.revoke",
115
+ path: `${basePath}/connections/:connectionId/revoke`,
116
+ scope: CONNECTIONS_REVOKE_SCOPE,
117
+ kind: "credentials",
118
+ summary: "Revoke every key and trust another connection in this environment holds, with a reason. Never the calling connection, which ends through disconnect."
119
+ },
120
+ {
121
+ method: "GET",
122
+ operation: "migrations.read",
81
123
  path: `${basePath}/migrations`,
82
124
  scope: MIGRATIONS_READ_SCOPE,
125
+ kind: "reads",
83
126
  summary: "What each bound database has applied, what is pending, what is undeclared, and which run put it there. Undeclared is relative to this Worker."
84
127
  },
85
128
  {
86
129
  method: "GET",
130
+ operation: "privacy.read",
87
131
  path: `${basePath}/privacy`,
88
132
  scope: PRIVACY_READ_SCOPE,
133
+ kind: "reads",
89
134
  summary: "Every stored schema holding a privacy-marked field, each field's path and class, and where it lives. Derived from the running code."
135
+ },
136
+ {
137
+ method: "GET",
138
+ operation: "evidence.read",
139
+ path: `${basePath}/evidence`,
140
+ scope: EVIDENCE_READ_SCOPE,
141
+ kind: "reads",
142
+ summary: "SOC 2 and ISO 27001 evidence derived from what this Worker composes and records: each control's facts, their sources, and what could be said. Evidence for an auditor, never a judgment."
143
+ },
144
+ {
145
+ method: "GET",
146
+ operation: "processing.read",
147
+ path: `${basePath}/processing`,
148
+ scope: PROCESSING_READ_SCOPE,
149
+ kind: "reads",
150
+ summary: "The record of processing activities, GDPR Article 30: each purpose, its data classes, legal basis, recipients, retention and measures, and every gap named."
151
+ },
152
+ {
153
+ method: "GET",
154
+ operation: "erasure.list",
155
+ path: `${basePath}/erasure`,
156
+ scope: ERASURE_READ_SCOPE,
157
+ kind: "reads",
158
+ summary: "Every purge in a period: how many completed, are in progress or failed, and each failed or overdue one with the step it stopped at."
159
+ },
160
+ {
161
+ method: "GET",
162
+ operation: "erasure.read",
163
+ path: `${basePath}/erasure/:subjectType/:subjectId`,
164
+ scope: ERASURE_READ_SCOPE,
165
+ kind: "reads",
166
+ summary: "One user's or organization's erasure: the deletion, every step erased or kept with its reason, the state, and the audit events that record each."
167
+ },
168
+ {
169
+ method: "GET",
170
+ operation: "holds.list",
171
+ path: `${basePath}/holds`,
172
+ scope: RETENTION_READ_SCOPE,
173
+ kind: "reads",
174
+ summary: "Every legal hold, standing first: its subject, who placed it and why, and when and why it was released."
175
+ },
176
+ {
177
+ method: "POST",
178
+ operation: "holds.place",
179
+ path: `${basePath}/holds`,
180
+ scope: RETENTION_HOLD_SCOPE,
181
+ kind: "writes",
182
+ summary: "Place a legal hold on a user, an organization or a tenant's audit trail, with a reason. Every purge, prune and inactivity deletion touching it stops until it is released."
183
+ },
184
+ {
185
+ method: "POST",
186
+ operation: "holds.release",
187
+ path: `${basePath}/holds/:holdId/release`,
188
+ scope: RETENTION_HOLD_SCOPE,
189
+ kind: "writes",
190
+ summary: "Release a legal hold, with a reason. The subject returns to the retention the Worker deploys with."
191
+ },
192
+ {
193
+ method: "POST",
194
+ operation: "presence.tickets.mint",
195
+ path: `${basePath}/presence/tickets`,
196
+ scope: null,
197
+ summary: "Mint a single-use ticket into one presence room. Needs the room kind's own scope, which the manifest lists beside each kind."
90
198
  }
91
199
  ];
92
200
  }
@@ -121,6 +229,20 @@ function registerControlPlaneRoutes(app, basePath, deps) {
121
229
  scopes: [KEYS_ROTATE_SCOPE],
122
230
  responses: { 200: ExpireKeyResponse }
123
231
  }), requireControlPlane(KEYS_ROTATE_SCOPE), validate("param", ExpireKeyParams), validate("json", ExpireKeyRequest), (c) => expireKeyHandler(deps)(c, c.req.valid("param"), c.req.valid("json")));
232
+ app.get(`${basePath}/connections`, describeRoute({
233
+ summary: "List every connection in this Worker's environment.",
234
+ description: "Every connection bound to this Worker's environment, the caller's own among them; a database shared with another environment never lists that environment's rows. Each connection's id, environment, actor kind, purpose, authorizing principal, credential kind, granted scopes, every trust's subject constraint, whether it is live, and when it was registered and revoked. `principal` narrows to the connections one email authorized; connections registered before principals were recorded are listed apart in `unattributed` and never match. No key material and no secret. Read-only.",
235
+ verification: "control-plane",
236
+ scopes: [CONNECTIONS_READ_SCOPE],
237
+ responses: { 200: ControlPlaneConnectionsResponse }
238
+ }), requireControlPlane(CONNECTIONS_READ_SCOPE), validate("query", ConnectionListQuery), (c) => listConnectionsHandler(deps)(c, c.req.valid("query")));
239
+ app.post(`${basePath}/connections/:connectionId/revoke`, describeRoute({
240
+ summary: "Revoke another connection.",
241
+ description: "Revokes every key and trust on the connection not yet revoked, at once, with a reason: a live one, and one whose window has not opened yet. A token already minted under a trust stays usable until it expires. Refused on the calling connection, which ends through `pithy dashboard disconnect`, naming its environment with `--env`. A connection bound to another environment answers 404. `pithy dashboard connect` keeps one connection per environment, replacing the one stored, so this route has a target only where the database holds more than one connection for this environment. Audited as `controlplane/connection_revoked`.",
242
+ verification: "control-plane",
243
+ scopes: [CONNECTIONS_REVOKE_SCOPE],
244
+ responses: { 200: RevokeConnectionResponse }
245
+ }), requireControlPlane(CONNECTIONS_REVOKE_SCOPE), validate("param", ConnectionParams), validate("json", RevokeConnectionRequest), (c) => revokeConnectionHandler(deps)(c, c.req.valid("param"), c.req.valid("json")));
124
246
  app.get(`${basePath}/migrations`, describeRoute({
125
247
  summary: "Report where each database this Worker binds stands.",
126
248
  description: "Applied, pending and undeclared migrations per database, each naming its namespace, and the last group with whether it is still reversible. Undeclared is relative to this Worker: a database shared with another Worker lists that Worker's migrations there. Read-only.",
@@ -135,6 +257,61 @@ function registerControlPlaneRoutes(app, basePath, deps) {
135
257
  scopes: [PRIVACY_READ_SCOPE],
136
258
  responses: { 200: ControlPlanePrivacyResponse }
137
259
  }), requireControlPlane(PRIVACY_READ_SCOPE), privacyHandler(deps));
260
+ app.get(`${basePath}/evidence`, describeRoute({
261
+ summary: "Report the evidence this Worker derives for SOC 2 and ISO 27001.",
262
+ description: "Seven controls, each with its SOC 2 and ISO/IEC 27001:2022 references, the facts read, the declaration each came from, and `evidenced`, `gap` or `not-derivable` with the reason. Derived from what this Worker composes and records. Evidence for an auditor to weigh, never a judgment. A fact another read keeps behind its own scope stays behind it. Read-only.",
263
+ verification: "control-plane",
264
+ scopes: [EVIDENCE_READ_SCOPE],
265
+ responses: { 200: ControlPlaneEvidenceResponse }
266
+ }), requireControlPlane(EVIDENCE_READ_SCOPE), evidenceHandler(deps));
267
+ app.get(`${basePath}/processing`, describeRoute({
268
+ summary: "Report the record of processing activities.",
269
+ description: "Every processing activity the composed capabilities and the `processing` block declare: its purpose, the data classes it uses cross-checked against the privacy inventory, its legal basis or a gap, a consent basis's purpose and grant counts, its recipients with what each receives, its effective retention, and the evidence pack's controls. Every gap is named and none is filled in. It describes the processing and changes none of it. Read-only.",
270
+ verification: "control-plane",
271
+ scopes: [PROCESSING_READ_SCOPE],
272
+ responses: { 200: ControlPlaneProcessingResponse }
273
+ }), requireControlPlane(PROCESSING_READ_SCOPE), processingHandler(deps));
274
+ app.get(`${basePath}/erasure`, describeRoute({
275
+ summary: "Report every purge in a period.",
276
+ description: "Every user and organization purge that came due, ran a step, failed or completed in the window: counts of scheduled, in progress, complete, failed and overdue, and each failed or overdue one named with the step it stopped at. The window defaults to the 90 days before `until`, and is at most 366 days. Read-only.",
277
+ verification: "control-plane",
278
+ scopes: [ERASURE_READ_SCOPE],
279
+ responses: { 200: ControlPlaneErasurePeriodResponse }
280
+ }), requireControlPlane(ERASURE_READ_SCOPE), validate("query", ErasurePeriodQuery), (c) => erasurePeriodHandler(deps)(c, c.req.valid("query")));
281
+ app.get(`${basePath}/erasure/:subjectType/:subjectId`, describeRoute({
282
+ summary: "Attest one subject's erasure.",
283
+ description: "For one user or organization: when it was deleted, by which kind of actor and why, soft or hard, every purge step with its capability, whether it erased or kept the rows and why, and when it finished, the purge's state, and the audit events that record each step by event id and chain position. Nothing about the subject beyond its id. Read-only.",
284
+ verification: "control-plane",
285
+ scopes: [ERASURE_READ_SCOPE],
286
+ responses: { 200: ControlPlaneErasureSubjectResponse }
287
+ }), requireControlPlane(ERASURE_READ_SCOPE), validate("param", ErasureSubjectParams), (c) => erasureSubjectHandler(deps)(c, c.req.valid("param")));
288
+ app.get(`${basePath}/holds`, describeRoute({
289
+ summary: "List the legal holds.",
290
+ description: "Every hold, standing first and newest first within each: the subject, who placed it and why, and when, by whom and why it was released. `active` counts the holds standing. Read-only.",
291
+ verification: "control-plane",
292
+ scopes: [RETENTION_READ_SCOPE],
293
+ responses: { 200: ControlPlaneHoldsResponse }
294
+ }), requireControlPlane(RETENTION_READ_SCOPE), validate("query", HoldListQuery), (c) => listHoldsHandler(deps)(c, c.req.valid("query")));
295
+ app.post(`${basePath}/holds`, describeRoute({
296
+ summary: "Place a legal hold.",
297
+ description: "Holds one user, one organization, or one tenant's audit trail, with a reason. While it stands no purge, prune or inactivity deletion touches the subject, and a purge already running stops at its next step. Audited. A hold can only preserve: nothing here shortens a retention.",
298
+ verification: "control-plane",
299
+ scopes: [RETENTION_HOLD_SCOPE],
300
+ responses: { 201: ControlPlaneHoldResponse }
301
+ }), requireControlPlane(RETENTION_HOLD_SCOPE), validate("json", PlaceHoldRequest), (c) => placeHoldHandler(deps)(c, c.req.valid("json")));
302
+ app.post(`${basePath}/holds/:holdId/release`, describeRoute({
303
+ summary: "Release a legal hold.",
304
+ description: "Ends one standing hold, with a reason, and keeps it as a record. The subject returns to the Worker's retention; a purge it stopped resumes on the next daily pass. Audited.",
305
+ verification: "control-plane",
306
+ scopes: [RETENTION_HOLD_SCOPE],
307
+ responses: { 200: ControlPlaneHoldResponse }
308
+ }), requireControlPlane(RETENTION_HOLD_SCOPE), validate("param", HoldParams), validate("json", ReleaseHoldRequest), (c) => releaseHoldHandler(deps)(c, c.req.valid("param"), c.req.valid("json")));
309
+ app.post(`${basePath}/presence/tickets`, describeRoute({
310
+ summary: "Mint a presence ticket for a management client.",
311
+ description: "Admits the participant the body names to one room of any kind this Worker composes. Sign the call with the room kind's own scope, as the manifest lists it; presence adds no scope. Single use, for 60 seconds. Open the socket at `socketPath`, offering `protocol` and `pithy.ticket.<ticket>` in `Sec-WebSocket-Protocol`.",
312
+ verification: "control-plane",
313
+ responses: { 201: PresenceTicket }
314
+ }), presenceTicketGate(deps), validate("json", ControlPlanePresenceTicketRequest), (c) => presenceTicketHandler(deps)(c, c.req.valid("json")));
138
315
  }
139
316
  //#endregion
140
317
  export { controlPlaneRouteDescriptors, registerControlPlaneRoutes };
@@ -38,4 +38,67 @@ export declare const ExpireKeyRequest: z.ZodObject<{
38
38
  provenKeyId: z.ZodString;
39
39
  }, z.core.$strip>;
40
40
  export type ExpireKeyRequest = z.infer<typeof ExpireKeyRequest>;
41
+ /** `GET /control-plane/erasure/:subjectType/:subjectId` — which subject. */
42
+ export declare const ErasureSubjectParams: z.ZodObject<{
43
+ subjectType: z.ZodEnum<{
44
+ organization: "organization";
45
+ user: "user";
46
+ }>;
47
+ subjectId: z.ZodString;
48
+ }, z.core.$strip>;
49
+ export type ErasureSubjectParams = z.infer<typeof ErasureSubjectParams>;
50
+ /** `GET /control-plane/erasure?since&until` — the period. */
51
+ export declare const ErasurePeriodQuery: z.ZodObject<{
52
+ since: z.ZodOptional<z.ZodISODateTime>;
53
+ until: z.ZodOptional<z.ZodISODateTime>;
54
+ }, z.core.$strip>;
55
+ export type ErasurePeriodQuery = z.infer<typeof ErasurePeriodQuery>;
56
+ /**
57
+ * Place a legal hold. The subject and a reason, and nothing else: no field shortens a retention, because a hold
58
+ * can only preserve.
59
+ */
60
+ export declare const PlaceHoldRequest: z.ZodObject<{
61
+ subjectType: z.ZodEnum<{
62
+ audit: "audit";
63
+ organization: "organization";
64
+ user: "user";
65
+ }>;
66
+ subjectId: z.ZodNullable<z.ZodString>;
67
+ reason: z.ZodString;
68
+ }, z.core.$strict>;
69
+ export type PlaceHoldRequest = z.infer<typeof PlaceHoldRequest>;
70
+ /** The path parameter of the release route. */
71
+ export declare const HoldParams: z.ZodObject<{
72
+ holdId: z.ZodString;
73
+ }, z.core.$strip>;
74
+ export type HoldParams = z.infer<typeof HoldParams>;
75
+ /** Release a legal hold, with a reason. */
76
+ export declare const ReleaseHoldRequest: z.ZodObject<{
77
+ reason: z.ZodString;
78
+ }, z.core.$strict>;
79
+ export type ReleaseHoldRequest = z.infer<typeof ReleaseHoldRequest>;
80
+ /** Which holds to list. */
81
+ export declare const HoldListQuery: z.ZodObject<{
82
+ state: z.ZodOptional<z.ZodEnum<{
83
+ active: "active";
84
+ released: "released";
85
+ }>>;
86
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
87
+ }, z.core.$strip>;
88
+ export type HoldListQuery = z.infer<typeof HoldListQuery>;
89
+ /** Which connections to list. */
90
+ export declare const ConnectionListQuery: z.ZodObject<{
91
+ principal: z.ZodOptional<z.ZodEmail>;
92
+ }, z.core.$strip>;
93
+ export type ConnectionListQuery = z.infer<typeof ConnectionListQuery>;
94
+ /** The path parameter of the revoke route. */
95
+ export declare const ConnectionParams: z.ZodObject<{
96
+ connectionId: z.ZodUUID;
97
+ }, z.core.$strip>;
98
+ export type ConnectionParams = z.infer<typeof ConnectionParams>;
99
+ /** Revoke a connection, with a reason. */
100
+ export declare const RevokeConnectionRequest: z.ZodObject<{
101
+ reason: z.ZodString;
102
+ }, z.core.$strict>;
103
+ export type RevokeConnectionRequest = z.infer<typeof RevokeConnectionRequest>;
41
104
  //# sourceMappingURL=schemas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/schemas.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;GAgBG;AAEH,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB;;;;;;;iBAa5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4FAA4F;AAC5F,eAAO,MAAM,eAAe;;iBAU+B,CAAC;AAC5D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,sGAAsG;AACtG,eAAO,MAAM,gBAAgB;;iBAY1B,CAAC;AACJ,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/schemas.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;GAgBG;AAEH,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB;;;;;;;iBAa5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4FAA4F;AAC5F,eAAO,MAAM,eAAe;;iBAU+B,CAAC;AAC5D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,sGAAsG;AACtG,eAAO,MAAM,gBAAgB;;iBAY1B,CAAC;AACJ,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB;;;;;;iBAKkB,CAAC;AACpD,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB;;;iBAWuB,CAAC;AACvD,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;kBAyB8B,CAAC;AAC5D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,+CAA+C;AAC/C,eAAO,MAAM,UAAU;;iBAEY,CAAC;AACpC,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,2CAA2C;AAC3C,eAAO,MAAM,kBAAkB;;kBAUoB,CAAC;AACpD,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,2BAA2B;AAC3B,eAAO,MAAM,aAAa;;;;;;iBAWe,CAAC;AAC1C,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,iCAAiC;AACjC,eAAO,MAAM,mBAAmB;;iBAUS,CAAC;AAC1C,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,8CAA8C;AAC9C,eAAO,MAAM,gBAAgB;;iBAEW,CAAC;AACzC,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,0CAA0C;AAC1C,eAAO,MAAM,uBAAuB;;kBAUkB,CAAC;AACvD,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -1,5 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
+ import { HoldState, HoldSubjectType } from "../data/hold.js";
3
4
  import { Ed25519PublicJwk } from "../data/connection.js";
4
5
  import { z } from "zod";
5
6
  //#region src/controlPlane/http/schemas.ts
@@ -29,5 +30,42 @@ const RegisterKeyRequest = z.object({
29
30
  const ExpireKeyParams = z.object({ keyId: z.string().min(1).max(64).describe("The key to give an end date. Bounded but unconstrained in shape — an unknown id answers 404, not 400.") }).describe("The path parameters of the key-expiry route.");
30
31
  /** `POST /control-plane/keys/:keyId/expire` — the body, carrying the proof that a successor works. */
31
32
  const ExpireKeyRequest = z.object({ provenKeyId: z.string().min(1).max(64).describe("The replacement key that has already been proven against a live `ping`. Required, and checked to be currently active: expiring a key without naming a working successor is the one failure mode with no recovery path, so the route refuses rather than trusting the caller to have checked.") }).describe("A request to expire one superseded key. The successor must be named and must already be live — append, verify, then expire, never replace.");
33
+ /** `GET /control-plane/erasure/:subjectType/:subjectId` — which subject. */
34
+ const ErasureSubjectParams = z.object({
35
+ subjectType: z.enum(["user", "organization"]).describe("The kind of subject: `user` or `organization`."),
36
+ subjectId: z.string().min(1).max(255).describe("The subject's id.")
37
+ }).describe("The subject whose erasure to attest.");
38
+ /** `GET /control-plane/erasure?since&until` — the period. */
39
+ const ErasurePeriodQuery = z.object({
40
+ since: z.iso.datetime({ offset: true }).optional().describe("The window's inclusive start, ISO-8601. Defaults to 90 days before `until`."),
41
+ until: z.iso.datetime({ offset: true }).optional().describe("The window's inclusive end, ISO-8601. Defaults to now.")
42
+ }).describe("The period to attest: at most 366 days.");
43
+ /**
44
+ * Place a legal hold. The subject and a reason, and nothing else: no field shortens a retention, because a hold
45
+ * can only preserve.
46
+ */
47
+ const PlaceHoldRequest = z.object({
48
+ subjectType: HoldSubjectType,
49
+ subjectId: z.string().min(1).max(255).nullable().describe("The user's or organization's id, or the tenant whose audit trail to hold. Null only for `audit`, which then holds the untenanted trail."),
50
+ reason: z.string().trim().min(1).max(500).describe("Why the hold is placed, in one or two sentences. Required, and recorded with the hold and its audit event.")
51
+ }).strict().refine((body) => body.subjectId !== null || body.subjectType === "audit", {
52
+ message: "Only an audit hold may name no subject: a user or organization hold names its id.",
53
+ path: ["subjectId"]
54
+ }).describe("A legal hold to place: the subject, and why.");
55
+ /** The path parameter of the release route. */
56
+ const HoldParams = z.object({ holdId: z.string().min(1).max(64).describe("The hold's id, as placing it returned.") }).describe("The hold to release.");
57
+ /** Release a legal hold, with a reason. */
58
+ const ReleaseHoldRequest = z.object({ reason: z.string().trim().min(1).max(500).describe("Why the hold is released. Required, and recorded with the release and its audit event.") }).strict().describe("A legal hold's release: why it ends.");
59
+ /** Which holds to list. */
60
+ const HoldListQuery = z.object({
61
+ state: HoldState.optional().describe("Only holds in this state. Absent lists both, standing first."),
62
+ limit: z.coerce.number().int().min(1).max(500).optional().describe("How many holds to list, at most 500. Defaults to 100.")
63
+ }).describe("Which legal holds to list.");
64
+ /** Which connections to list. */
65
+ const ConnectionListQuery = z.object({ principal: z.email().max(254).optional().describe("Only connections this email authorized, compared without regard to case. Connections with no recorded principal are listed apart either way and never match.") }).describe("Which connections to list.");
66
+ /** The path parameter of the revoke route. */
67
+ const ConnectionParams = z.object({ connectionId: z.uuid().describe("The connection's id, as the listing reports it.") }).describe("The connection to revoke.");
68
+ /** Revoke a connection, with a reason. */
69
+ const RevokeConnectionRequest = z.object({ reason: z.string().trim().min(1).max(500).describe("Why the connection is revoked. Required, and recorded on the revocation's audit event.") }).strict().describe("A connection's revocation: why it ends.");
32
70
  //#endregion
33
- export { ExpireKeyParams, ExpireKeyRequest, RegisterKeyRequest };
71
+ export { ConnectionListQuery, ConnectionParams, ErasurePeriodQuery, ErasureSubjectParams, ExpireKeyParams, ExpireKeyRequest, HoldListQuery, HoldParams, PlaceHoldRequest, RegisterKeyRequest, ReleaseHoldRequest, RevokeConnectionRequest };
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/verify.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAOjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,KAAK,uBAAuB,EAAe,MAAM,gBAAgB,CAAC;AAI3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH;;;;;;;;;GASG;AACH,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAE7E,0GAA0G;AAC1G,MAAM,WAAW,sBAAsB;IACrC,8FAA8F;IAC9F,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;IACjF;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,sCAAsC;IACtC,MAAM,EAAE,WAAW,CAAC;IACpB,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,MAAM,EAAE,kBAAkB,CAAC;IAC3B,8DAA8D;IAC9D,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,iGAAiG;IACjG,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,EAAE,UAAU,CAAC;IACjB,4EAA4E;IAC5E,WAAW,EAAE,uBAAuB,CAAC;CACtC;AAOD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CA6E9B"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/verify.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAOjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,KAAK,uBAAuB,EAAe,MAAM,gBAAgB,CAAC;AAI3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH;;;;;;;;;GASG;AACH,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAE7E,0GAA0G;AAC1G,MAAM,WAAW,sBAAsB;IACrC,8FAA8F;IAC9F,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;IACjF;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,sCAAsC;IACtC,MAAM,EAAE,WAAW,CAAC;IACpB,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,MAAM,EAAE,kBAAkB,CAAC;IAC3B,8DAA8D;IAC9D,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,iGAAiG;IACjG,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,EAAE,UAAU,CAAC;IACjB,4EAA4E;IAC5E,WAAW,EAAE,uBAAuB,CAAC;CACtC;AAOD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CA8F9B"}
@@ -37,7 +37,19 @@ async function verifyControlPlaneCall(call, deps) {
37
37
  if (claims.exp <= nowSeconds - skew) deny(`token expired at ${claims.exp}, now ${nowSeconds}`);
38
38
  if (claims.iat > nowSeconds + skew) deny(`token issued at ${claims.iat} is in the future, now ${nowSeconds}`);
39
39
  await verifyBodyDigest(call.body, claims.bodySha256);
40
- if (!scopeCovers(call.requirement, claims.scope, connection.scopes)) throw new ControlPlaneInsufficientScopeError({ detail: `required ${String(call.requirement)}; token carried ${claims.scope}; connection grants [${connection.scopes.join(", ")}]` });
40
+ if (!scopeCovers(call.requirement, claims.scope, connection.scopes)) {
41
+ const firstPresentation = await deps.replay.claim(claims.jti, connection.id);
42
+ const spent = firstPresentation ? "" : `; jti ${claims.jti} was already spent`;
43
+ throw new ControlPlaneInsufficientScopeError({
44
+ detail: `required ${String(call.requirement)}; token carried ${claims.scope}; connection grants [${connection.scopes.join(", ")}]${spent}`,
45
+ ...firstPresentation ? { caller: {
46
+ connectionId: connection.id,
47
+ subject: claims.sub,
48
+ keyId: key.keyId,
49
+ actorKind: connection.actorKind
50
+ } } : {}
51
+ });
52
+ }
41
53
  if (!await deps.replay.claim(claims.jti, connection.id)) deny(`jti ${claims.jti} was already spent`);
42
54
  return ControlPlaneContext.parse({
43
55
  connectionId: connection.id,
@@ -0,0 +1,76 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { type JwksCache, type OidcJwksFetch } from "../../http/oidcWebhook";
4
+ import { ControlPlaneContext } from "../context";
5
+ import type { ControlPlaneConnection } from "../data/connection";
6
+ import { type ControlPlaneRequirement } from "../scope/scope";
7
+ /**
8
+ * Verify one call made under an OIDC trust (#722, `docs/AGENT-ACCESS.md` §6.2) — the second credential kind's
9
+ * half of the decision `verify.ts` makes for a key.
10
+ *
11
+ * ## Per call, and the call still names its scope
12
+ *
13
+ * An identity token proves **who**, never **what**. So every call presents the workload's token on
14
+ * `pithy-control-plane-oidc`, names the trust on `pithy-control-plane-trust`, and names the one scope it
15
+ * exercises on `pithy-control-plane-scope`; the token is verified against the trust on every call, and the
16
+ * scope is checked against the connection's grant exactly as a key's token is.
17
+ *
18
+ * **No exchange, deliberately.** Trading the token once for a short-lived credential would keep every
19
+ * property of the key path, and it would put a key on the workload's host — the custody this credential kind
20
+ * exists to remove. What remains is stated rather than hidden: a captured token replays against this one
21
+ * audience until it expires, which is the provider's lifetime, minutes, and the same bound as revocation's
22
+ * latency. There is no `jti` to spend, because one token serves every call in its job.
23
+ *
24
+ * ## The existing verifier, not a second one
25
+ *
26
+ * The token goes through `verifyOidcToken` from `core/src/http/oidcWebhook.ts`, with its ordering intact:
27
+ * the algorithm pinned before a key is touched, the key resolved by `kid` from the issuer's published set,
28
+ * the signature checked over the received segments, and only then a claim read. Its predicate is handed
29
+ * `() => true` on purpose — the issuer, audience and freshness are still its to check, and the claims match
30
+ * runs here, after, because a refusal here has to know **what** the token carried, which a boolean cannot say.
31
+ *
32
+ * ## One error, with one narrow exception
33
+ *
34
+ * Every failure raises {@link ControlPlaneInvalidCredentialError}, as on the key path, with the step in
35
+ * `detail`. The exception is what it carries, never what it says: a claim mismatch on a token whose signature,
36
+ * issuer and audience all verified records the claims it carried for the audit row. That is what makes a
37
+ * custom issuer workable and a profiled one self-correcting. Before those three verify, nothing is recorded.
38
+ */
39
+ /** What the trust path needs from the outside world. Every one is injectable, so every step is testable. */
40
+ export interface ControlPlaneTrustVerifyDeps {
41
+ /**
42
+ * This environment's connection, or null when none is registered. One per environment (`docs/CONTROL-PLANE.md`
43
+ * §15), so a call names only the trust, and the connection is the one the Worker's own environment owns.
44
+ */
45
+ loadEnvironmentConnection: () => Promise<ControlPlaneConnection | null>;
46
+ /** This Worker's environment name, checked against the connection's. */
47
+ environment: string;
48
+ /** The clock. */
49
+ now: () => Date;
50
+ /**
51
+ * Where the issuers' key sets are held between calls. Built once per seam, never per request: an anonymous
52
+ * caller naming a trust and an invented `kid` must cost a map lookup, not a round trip to the issuer.
53
+ */
54
+ jwksCache: JwksCache;
55
+ /** The HTTP seam the issuers' key endpoints are reached through. Defaults to the runtime's `fetch`. */
56
+ transport?: OidcJwksFetch;
57
+ /**
58
+ * Record the first allowed call under a trust, so a listing can stop saying *awaiting a first call*. Evidence
59
+ * rather than part of the decision: it runs after the call is allowed, once, and a failure is swallowed.
60
+ */
61
+ recordFirstCall: (connectionId: string, trustId: string, at: Date) => Promise<void>;
62
+ }
63
+ /** One inbound call under a trust, reduced to what verification needs. */
64
+ export interface ControlPlaneTrustCall {
65
+ /** The workload's OIDC token, from `pithy-control-plane-oidc`. */
66
+ token: string | undefined;
67
+ /** The trust the call names, from `pithy-control-plane-trust`. */
68
+ trustId: string | undefined;
69
+ /** The scope the call names, from `pithy-control-plane-scope`. */
70
+ scope: string | undefined;
71
+ /** What the route demands. */
72
+ requirement: ControlPlaneRequirement;
73
+ }
74
+ /** Verify one call under an OIDC trust, or throw. Returns the context the handler runs under. */
75
+ export declare function verifyControlPlaneTrustCall(call: ControlPlaneTrustCall, deps: ControlPlaneTrustVerifyDeps): Promise<ControlPlaneContext>;
76
+ //# sourceMappingURL=verifyTrust.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifyTrust.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/http/verifyTrust.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAmB,MAAM,wBAAwB,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAOjE,OAAO,EAAE,KAAK,uBAAuB,EAAkC,MAAM,gBAAgB,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,4GAA4G;AAC5G,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,yBAAyB,EAAE,MAAM,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;IACxE,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB;;;OAGG;IACH,SAAS,EAAE,SAAS,CAAC;IACrB,uGAAuG;IACvG,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;OAGG;IACH,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,kEAAkE;IAClE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,8BAA8B;IAC9B,WAAW,EAAE,uBAAuB,CAAC;CACtC;AAOD,iGAAiG;AACjG,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,qBAAqB,EAC3B,IAAI,EAAE,2BAA2B,GAChC,OAAO,CAAC,mBAAmB,CAAC,CAiF9B"}
@@ -0,0 +1,75 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { WebhookUnverifiedError } from "../../error/pithyError.js";
4
+ import { ControlPlaneScope, scopeCovers } from "../scope/scope.js";
5
+ import { verifyOidcToken } from "../../http/oidcWebhook.js";
6
+ import { ControlPlaneInsufficientScopeError, ControlPlaneInvalidCredentialError, ControlPlaneNotConnectedError } from "../error/errors.js";
7
+ import { claimsMatch, findVerifyingTrust, observedClaims } from "../data/oidcTrust.js";
8
+ import { ControlPlaneContext } from "../context.js";
9
+ //#region src/controlPlane/http/verifyTrust.ts
10
+ /** Raise the one credential error, naming the failing step for the log and nobody else. */
11
+ function deny(step) {
12
+ throw new ControlPlaneInvalidCredentialError({ detail: step });
13
+ }
14
+ /** Verify one call under an OIDC trust, or throw. Returns the context the handler runs under. */
15
+ async function verifyControlPlaneTrustCall(call, deps) {
16
+ if (!call.token) deny("no OIDC token presented");
17
+ if (!call.trustId) deny("an OIDC call named no trust");
18
+ const scope = ControlPlaneScope.safeParse(call.scope);
19
+ if (!scope.success) deny("an OIDC call named no well-formed scope");
20
+ const now = deps.now();
21
+ const connection = await deps.loadEnvironmentConnection();
22
+ if (!connection) throw new ControlPlaneNotConnectedError({ detail: `no connection registered in environment ${deps.environment}` });
23
+ if (connection.environment !== deps.environment) deny(`connection ${connection.id} is bound to ${connection.environment}, not ${deps.environment}`);
24
+ const trust = findVerifyingTrust(connection.trusts ?? [], call.trustId, now);
25
+ if (!trust) deny(`trust ${call.trustId} is not a live trust on connection ${connection.id}`);
26
+ let claims;
27
+ try {
28
+ claims = await verifyOidcToken(call.token, {
29
+ issuers: [trust.issuer],
30
+ jwksUrl: trust.jwksUrl,
31
+ audience: trust.audience,
32
+ claims: () => true,
33
+ jwksCache: deps.jwksCache,
34
+ ...deps.transport === void 0 ? {} : { transport: deps.transport },
35
+ now
36
+ });
37
+ } catch (error) {
38
+ if (error instanceof WebhookUnverifiedError) deny(`trust ${trust.trustId}: ${error.payload.detail ?? "unverified"}`);
39
+ throw error;
40
+ }
41
+ if (!claimsMatch(trust.claims, claims)) throw new ControlPlaneInvalidCredentialError({
42
+ detail: `trust ${trust.trustId}: the token's claims do not match the trust's`,
43
+ refusedTrust: {
44
+ connectionId: connection.id,
45
+ trustId: trust.trustId,
46
+ observedClaims: observedClaims(claims)
47
+ }
48
+ });
49
+ const subject = typeof claims.sub === "string" && claims.sub !== "" ? claims.sub : `trust:${trust.trustId}`;
50
+ if (!scopeCovers(call.requirement, scope.data, connection.scopes)) throw new ControlPlaneInsufficientScopeError({
51
+ detail: `required ${String(call.requirement)}; call named ${scope.data}; connection grants [${connection.scopes.join(", ")}]; trust ${trust.trustId}`,
52
+ caller: {
53
+ connectionId: connection.id,
54
+ subject,
55
+ keyId: trust.trustId,
56
+ credentialKind: "trust",
57
+ actorKind: connection.actorKind
58
+ }
59
+ });
60
+ if (trust.firstCallAt === null) await deps.recordFirstCall(connection.id, trust.trustId, now).catch(() => void 0);
61
+ return ControlPlaneContext.parse({
62
+ connectionId: connection.id,
63
+ environment: connection.environment,
64
+ issuer: trust.issuer,
65
+ subject,
66
+ scope: scope.data,
67
+ grantedScopes: connection.scopes,
68
+ keyId: trust.trustId,
69
+ tokenId: typeof claims.jti === "string" ? claims.jti : "",
70
+ credentialKind: "trust",
71
+ actorKind: connection.actorKind
72
+ });
73
+ }
74
+ //#endregion
75
+ export { verifyControlPlaneTrustCall };
@@ -0,0 +1,15 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { Migration } from "kysely/migration";
4
+ /**
5
+ * A connection's OIDC trusts — `#722`, the second credential kind beside a registered key.
6
+ *
7
+ * **Appended, not folded into `0001`.** The seam has shipped and adopters' databases hold rows written by it.
8
+ *
9
+ * **One JSON column beside `keys`, defaulting to an empty list.** The same reasons `keys` is one column rather
10
+ * than a child table: verifying a call stays a single read, and revoking a trust is one `UPDATE`. Not null,
11
+ * with `'[]'` as its default, so every existing row reads as a connection holding no trust — which is exactly
12
+ * what it is — and nothing is backfilled.
13
+ */
14
+ export declare const controlplane_0004_connection_trusts: Migration;
15
+ //# sourceMappingURL=0004_connection_trusts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0004_connection_trusts.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/migrations/0004_connection_trusts.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC,EAAE,SAajD,CAAC"}
@@ -0,0 +1,23 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ //#region src/controlPlane/migrations/0004_connection_trusts.ts
4
+ /**
5
+ * A connection's OIDC trusts — `#722`, the second credential kind beside a registered key.
6
+ *
7
+ * **Appended, not folded into `0001`.** The seam has shipped and adopters' databases hold rows written by it.
8
+ *
9
+ * **One JSON column beside `keys`, defaulting to an empty list.** The same reasons `keys` is one column rather
10
+ * than a child table: verifying a call stays a single read, and revoking a trust is one `UPDATE`. Not null,
11
+ * with `'[]'` as its default, so every existing row reads as a connection holding no trust — which is exactly
12
+ * what it is — and nothing is backfilled.
13
+ */
14
+ const controlplane_0004_connection_trusts = {
15
+ up: async (db) => {
16
+ await db.schema.alterTable("pithyControlplaneConnections").addColumn("trusts", "text", (column) => column.notNull().defaultTo("[]")).execute();
17
+ },
18
+ down: async (db) => {
19
+ await db.schema.alterTable("pithyControlplaneConnections").dropColumn("trusts").execute();
20
+ }
21
+ };
22
+ //#endregion
23
+ export { controlplane_0004_connection_trusts };