@pithy-sh/core 0.12.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 (362) 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/audit/composed.d.ts +18 -0
  10. package/dist/audit/composed.d.ts.map +1 -0
  11. package/dist/audit/composed.js +28 -0
  12. package/dist/capability/bindings.d.ts +22 -0
  13. package/dist/capability/bindings.d.ts.map +1 -1
  14. package/dist/capability/bindings.js +32 -2
  15. package/dist/capability/capability.d.ts +133 -15
  16. package/dist/capability/capability.d.ts.map +1 -1
  17. package/dist/capability/capability.js +17 -2
  18. package/dist/capability/erasure.d.ts +75 -0
  19. package/dist/capability/erasure.d.ts.map +1 -0
  20. package/dist/capability/erasure.js +2 -0
  21. package/dist/capability/manifest.d.ts +5 -0
  22. package/dist/capability/manifest.d.ts.map +1 -1
  23. package/dist/capability/personColumns.d.ts +28 -0
  24. package/dist/capability/personColumns.d.ts.map +1 -0
  25. package/dist/capability/personColumns.js +66 -0
  26. package/dist/capability/personRows.d.ts +62 -0
  27. package/dist/capability/personRows.d.ts.map +1 -0
  28. package/dist/capability/personRows.js +59 -0
  29. package/dist/capability/purge.d.ts +113 -4
  30. package/dist/capability/purge.d.ts.map +1 -1
  31. package/dist/capability/purge.js +138 -5
  32. package/dist/capability/settings.d.ts +10 -0
  33. package/dist/capability/settings.d.ts.map +1 -1
  34. package/dist/capability/vectorIndex.d.ts +36 -0
  35. package/dist/capability/vectorIndex.d.ts.map +1 -0
  36. package/dist/capability/vectorIndex.js +25 -0
  37. package/dist/cli/commandCatalog.generated.d.ts +434 -4
  38. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  39. package/dist/cli/commandCatalog.generated.js +455 -2
  40. package/dist/cli/commandFacts.d.ts +8 -5
  41. package/dist/cli/commandFacts.d.ts.map +1 -1
  42. package/dist/cli/commandFacts.js +12 -3
  43. package/dist/consent/purpose.d.ts +70 -0
  44. package/dist/consent/purpose.d.ts.map +1 -0
  45. package/dist/consent/purpose.js +97 -0
  46. package/dist/consent/reader.d.ts +99 -0
  47. package/dist/consent/reader.d.ts.map +1 -0
  48. package/dist/consent/reader.js +33 -0
  49. package/dist/controlPlane/audit/actions.d.ts +15 -4
  50. package/dist/controlPlane/audit/actions.d.ts.map +1 -1
  51. package/dist/controlPlane/audit/actions.js +16 -5
  52. package/dist/controlPlane/capability.d.ts +10 -0
  53. package/dist/controlPlane/capability.d.ts.map +1 -1
  54. package/dist/controlPlane/capability.js +56 -8
  55. package/dist/controlPlane/context.d.ts +4 -0
  56. package/dist/controlPlane/context.d.ts.map +1 -1
  57. package/dist/controlPlane/context.js +3 -2
  58. package/dist/controlPlane/data/connection.d.ts +21 -2
  59. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  60. package/dist/controlPlane/data/connection.js +13 -4
  61. package/dist/controlPlane/data/connections.d.ts +94 -0
  62. package/dist/controlPlane/data/connections.d.ts.map +1 -0
  63. package/dist/controlPlane/data/connections.js +151 -0
  64. package/dist/controlPlane/data/hold.d.ts +46 -0
  65. package/dist/controlPlane/data/hold.d.ts.map +1 -0
  66. package/dist/controlPlane/data/hold.js +36 -0
  67. package/dist/controlPlane/data/oidcTrust.d.ts +122 -0
  68. package/dist/controlPlane/data/oidcTrust.d.ts.map +1 -0
  69. package/dist/controlPlane/data/oidcTrust.js +173 -0
  70. package/dist/controlPlane/data/tables.d.ts +4 -0
  71. package/dist/controlPlane/data/tables.d.ts.map +1 -1
  72. package/dist/controlPlane/data/tables.js +9 -4
  73. package/dist/controlPlane/data/trustFirstCall.d.ts +16 -0
  74. package/dist/controlPlane/data/trustFirstCall.d.ts.map +1 -0
  75. package/dist/controlPlane/data/trustFirstCall.js +25 -0
  76. package/dist/controlPlane/discovery/adminRoute.d.ts +165 -2
  77. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
  78. package/dist/controlPlane/discovery/adminRoute.js +32 -8
  79. package/dist/controlPlane/discovery/routeInput.d.ts +23 -0
  80. package/dist/controlPlane/discovery/routeInput.d.ts.map +1 -0
  81. package/dist/controlPlane/discovery/routeInput.js +80 -0
  82. package/dist/controlPlane/discovery/toolName.d.ts +21 -0
  83. package/dist/controlPlane/discovery/toolName.d.ts.map +1 -0
  84. package/dist/controlPlane/discovery/toolName.js +25 -0
  85. package/dist/controlPlane/erasure/attest.d.ts +78 -0
  86. package/dist/controlPlane/erasure/attest.d.ts.map +1 -0
  87. package/dist/controlPlane/erasure/attest.js +164 -0
  88. package/dist/controlPlane/erasure/read.d.ts +39 -0
  89. package/dist/controlPlane/erasure/read.d.ts.map +1 -0
  90. package/dist/controlPlane/erasure/read.js +140 -0
  91. package/dist/controlPlane/error/errors.d.ts +61 -2
  92. package/dist/controlPlane/error/errors.d.ts.map +1 -1
  93. package/dist/controlPlane/error/errors.js +13 -0
  94. package/dist/controlPlane/evidence/derive.d.ts +124 -0
  95. package/dist/controlPlane/evidence/derive.d.ts.map +1 -0
  96. package/dist/controlPlane/evidence/derive.js +343 -0
  97. package/dist/controlPlane/evidence/mapping.d.ts +85 -0
  98. package/dist/controlPlane/evidence/mapping.d.ts.map +1 -0
  99. package/dist/controlPlane/evidence/mapping.js +114 -0
  100. package/dist/controlPlane/evidence/pack.d.ts +46 -0
  101. package/dist/controlPlane/evidence/pack.d.ts.map +1 -0
  102. package/dist/controlPlane/evidence/pack.js +124 -0
  103. package/dist/controlPlane/http/connections.d.ts +96 -0
  104. package/dist/controlPlane/http/connections.d.ts.map +1 -0
  105. package/dist/controlPlane/http/connections.js +109 -0
  106. package/dist/controlPlane/http/guard.d.ts +23 -2
  107. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  108. package/dist/controlPlane/http/guard.js +103 -7
  109. package/dist/controlPlane/http/handlers.d.ts +212 -10
  110. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  111. package/dist/controlPlane/http/handlers.js +117 -26
  112. package/dist/controlPlane/http/holds.d.ts +57 -0
  113. package/dist/controlPlane/http/holds.d.ts.map +1 -0
  114. package/dist/controlPlane/http/holds.js +98 -0
  115. package/dist/controlPlane/http/presence.d.ts +27 -0
  116. package/dist/controlPlane/http/presence.d.ts.map +1 -0
  117. package/dist/controlPlane/http/presence.js +67 -0
  118. package/dist/controlPlane/http/processing.d.ts +73 -0
  119. package/dist/controlPlane/http/processing.d.ts.map +1 -0
  120. package/dist/controlPlane/http/processing.js +17 -0
  121. package/dist/controlPlane/http/responses.d.ts +927 -3
  122. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  123. package/dist/controlPlane/http/responses.js +283 -7
  124. package/dist/controlPlane/http/routes.d.ts +12 -2
  125. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  126. package/dist/controlPlane/http/routes.js +182 -5
  127. package/dist/controlPlane/http/schemas.d.ts +63 -0
  128. package/dist/controlPlane/http/schemas.d.ts.map +1 -1
  129. package/dist/controlPlane/http/schemas.js +39 -1
  130. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  131. package/dist/controlPlane/http/verify.js +13 -1
  132. package/dist/controlPlane/http/verifyTrust.d.ts +76 -0
  133. package/dist/controlPlane/http/verifyTrust.d.ts.map +1 -0
  134. package/dist/controlPlane/http/verifyTrust.js +75 -0
  135. package/dist/controlPlane/migrations/0004_connection_trusts.d.ts +15 -0
  136. package/dist/controlPlane/migrations/0004_connection_trusts.d.ts.map +1 -0
  137. package/dist/controlPlane/migrations/0004_connection_trusts.js +23 -0
  138. package/dist/controlPlane/migrations/0005_holds.d.ts +12 -0
  139. package/dist/controlPlane/migrations/0005_holds.d.ts.map +1 -0
  140. package/dist/controlPlane/migrations/0005_holds.js +26 -0
  141. package/dist/controlPlane/processing/derive.d.ts +77 -0
  142. package/dist/controlPlane/processing/derive.d.ts.map +1 -0
  143. package/dist/controlPlane/processing/derive.js +206 -0
  144. package/dist/controlPlane/processing/read.d.ts +10 -0
  145. package/dist/controlPlane/processing/read.d.ts.map +1 -0
  146. package/dist/controlPlane/processing/read.js +89 -0
  147. package/dist/controlPlane/scope/kind.d.ts +46 -0
  148. package/dist/controlPlane/scope/kind.d.ts.map +1 -0
  149. package/dist/controlPlane/scope/kind.js +52 -0
  150. package/dist/controlPlane/scope/scope.d.ts +58 -3
  151. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  152. package/dist/controlPlane/scope/scope.js +59 -4
  153. package/dist/controlPlane/token/jws.js +1 -1
  154. package/dist/controlPlane/token/mint.js +1 -1
  155. package/dist/controlPlane/wire.d.ts +23 -0
  156. package/dist/controlPlane/wire.d.ts.map +1 -1
  157. package/dist/controlPlane/wire.js +24 -1
  158. package/dist/createBackend.d.ts +13 -0
  159. package/dist/createBackend.d.ts.map +1 -1
  160. package/dist/createBackend.js +11 -3
  161. package/dist/createEntrypoint.d.ts +14 -1
  162. package/dist/createEntrypoint.d.ts.map +1 -1
  163. package/dist/createEntrypoint.js +19 -5
  164. package/dist/error/http.d.ts +216 -0
  165. package/dist/error/http.d.ts.map +1 -1
  166. package/dist/error/payload.d.ts +432 -0
  167. package/dist/error/payload.d.ts.map +1 -1
  168. package/dist/error/payload.js +144 -0
  169. package/dist/error/pithyError.d.ts +22 -0
  170. package/dist/error/pithyError.d.ts.map +1 -1
  171. package/dist/error/pithyError.js +31 -1
  172. package/dist/http/routeSpec.d.ts +46 -1
  173. package/dist/http/routeSpec.d.ts.map +1 -1
  174. package/dist/http/routeSpec.js +39 -5
  175. package/dist/http/validation.d.ts +11 -2
  176. package/dist/http/validation.d.ts.map +1 -1
  177. package/dist/http/validation.js +20 -3
  178. package/dist/http/verification.d.ts.map +1 -1
  179. package/dist/http/verification.js +1 -1
  180. package/dist/i18n/translator.d.ts +2 -1
  181. package/dist/i18n/translator.d.ts.map +1 -1
  182. package/dist/index.d.ts +1 -1
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.js +3 -3
  185. package/dist/logger/record.d.ts +126 -0
  186. package/dist/logger/record.d.ts.map +1 -1
  187. package/dist/mcp/annotations.d.ts +20 -0
  188. package/dist/mcp/annotations.d.ts.map +1 -0
  189. package/dist/mcp/annotations.js +23 -0
  190. package/dist/mcp/tools.d.ts +75 -0
  191. package/dist/mcp/tools.d.ts.map +1 -0
  192. package/dist/mcp/tools.js +118 -0
  193. package/dist/openapi/document.d.ts.map +1 -1
  194. package/dist/openapi/document.js +8 -26
  195. package/dist/openapi/operations.d.ts +35 -0
  196. package/dist/openapi/operations.d.ts.map +1 -0
  197. package/dist/openapi/operations.js +37 -0
  198. package/dist/presence/channel.d.ts +11 -0
  199. package/dist/presence/channel.d.ts.map +1 -0
  200. package/dist/presence/channel.js +65 -0
  201. package/dist/presence/declaration.d.ts +154 -0
  202. package/dist/presence/declaration.d.ts.map +1 -0
  203. package/dist/presence/declaration.js +164 -0
  204. package/dist/presence/durableObject.d.ts +61 -0
  205. package/dist/presence/durableObject.d.ts.map +1 -0
  206. package/dist/presence/durableObject.js +257 -0
  207. package/dist/presence/frames.d.ts +166 -0
  208. package/dist/presence/frames.d.ts.map +1 -0
  209. package/dist/presence/frames.js +90 -0
  210. package/dist/presence/http/guard.d.ts +11 -0
  211. package/dist/presence/http/guard.d.ts.map +1 -0
  212. package/dist/presence/http/guard.js +20 -0
  213. package/dist/presence/http/responses.d.ts +31 -0
  214. package/dist/presence/http/responses.d.ts.map +1 -0
  215. package/dist/presence/http/responses.js +34 -0
  216. package/dist/presence/http/routes.d.ts +24 -0
  217. package/dist/presence/http/routes.d.ts.map +1 -0
  218. package/dist/presence/http/routes.js +127 -0
  219. package/dist/presence/http/schemas.d.ts +29 -0
  220. package/dist/presence/http/schemas.d.ts.map +1 -0
  221. package/dist/presence/http/schemas.js +28 -0
  222. package/dist/presence/names.d.ts +23 -0
  223. package/dist/presence/names.d.ts.map +1 -0
  224. package/dist/presence/names.js +22 -0
  225. package/dist/presence/room.d.ts +95 -0
  226. package/dist/presence/room.d.ts.map +1 -0
  227. package/dist/presence/room.js +105 -0
  228. package/dist/presence/ticket.d.ts +55 -0
  229. package/dist/presence/ticket.d.ts.map +1 -0
  230. package/dist/presence/ticket.js +109 -0
  231. package/dist/privacy/classify.js +1 -1
  232. package/dist/privacy/encrypted.js +1 -1
  233. package/dist/processing/activity.d.ts +134 -0
  234. package/dist/processing/activity.d.ts.map +1 -0
  235. package/dist/processing/activity.js +161 -0
  236. package/dist/processing/composed.d.ts +18 -0
  237. package/dist/processing/composed.d.ts.map +1 -0
  238. package/dist/processing/composed.js +28 -0
  239. package/dist/processing/vocabulary.d.ts +26 -0
  240. package/dist/processing/vocabulary.d.ts.map +1 -0
  241. package/dist/processing/vocabulary.js +35 -0
  242. package/dist/queue/consumer.d.ts +59 -0
  243. package/dist/queue/consumer.d.ts.map +1 -0
  244. package/dist/queue/consumer.js +46 -0
  245. package/dist/retention/composed.d.ts +37 -0
  246. package/dist/retention/composed.d.ts.map +1 -0
  247. package/dist/retention/composed.js +66 -0
  248. package/dist/retention/holds.d.ts +61 -0
  249. package/dist/retention/holds.d.ts.map +1 -0
  250. package/dist/retention/holds.js +107 -0
  251. package/dist/retention/holdsPurge.d.ts +22 -0
  252. package/dist/retention/holdsPurge.d.ts.map +1 -0
  253. package/dist/retention/holdsPurge.js +50 -0
  254. package/dist/retention/policy.d.ts +132 -0
  255. package/dist/retention/policy.d.ts.map +1 -0
  256. package/dist/retention/policy.js +197 -0
  257. package/dist/session/rotated.d.ts +34 -0
  258. package/dist/session/rotated.d.ts.map +1 -0
  259. package/dist/session/rotated.js +25 -0
  260. package/dist/test-utils/connectionFixtures.d.ts +13 -0
  261. package/dist/test-utils/connectionFixtures.d.ts.map +1 -0
  262. package/dist/test-utils/connectionFixtures.js +58 -0
  263. package/dist/test-utils/interleave.d.ts +12 -0
  264. package/dist/test-utils/interleave.d.ts.map +1 -0
  265. package/dist/test-utils/interleave.js +26 -0
  266. package/dist/testWorker.d.ts +8 -0
  267. package/dist/testWorker.d.ts.map +1 -0
  268. package/dist/testWorker.js +9 -0
  269. package/dist/version.generated.d.ts +1 -1
  270. package/dist/version.generated.js +1 -1
  271. package/dist/workflow/dispatchRoute.js +1 -1
  272. package/package.json +1 -1
  273. package/src/audit/auditEvent.ts +1 -1
  274. package/src/audit/chain.ts +551 -0
  275. package/src/audit/citations.ts +63 -0
  276. package/src/audit/composed.ts +30 -0
  277. package/src/capability/bindings.ts +61 -0
  278. package/src/capability/capability.ts +166 -18
  279. package/src/capability/erasure.ts +82 -0
  280. package/src/capability/personColumns.ts +83 -0
  281. package/src/capability/personRows.ts +125 -0
  282. package/src/capability/purge.ts +208 -8
  283. package/src/capability/settings.ts +7 -0
  284. package/src/capability/vectorIndex.ts +60 -0
  285. package/src/cli/commandCatalog.generated.ts +488 -2
  286. package/src/cli/commandFacts.ts +12 -3
  287. package/src/cloudflare-test.d.ts +3 -1
  288. package/src/consent/purpose.ts +160 -0
  289. package/src/consent/reader.ts +134 -0
  290. package/src/controlPlane/audit/actions.ts +15 -4
  291. package/src/controlPlane/capability.ts +99 -20
  292. package/src/controlPlane/context.ts +10 -2
  293. package/src/controlPlane/data/connection.ts +16 -2
  294. package/src/controlPlane/data/connections.ts +231 -0
  295. package/src/controlPlane/data/hold.ts +52 -0
  296. package/src/controlPlane/data/oidcTrust.ts +249 -0
  297. package/src/controlPlane/data/tables.ts +7 -0
  298. package/src/controlPlane/data/trustFirstCall.ts +37 -0
  299. package/src/controlPlane/discovery/adminRoute.ts +71 -9
  300. package/src/controlPlane/discovery/routeInput.ts +86 -0
  301. package/src/controlPlane/discovery/toolName.ts +23 -0
  302. package/src/controlPlane/erasure/attest.ts +205 -0
  303. package/src/controlPlane/erasure/read.ts +169 -0
  304. package/src/controlPlane/error/errors.ts +69 -2
  305. package/src/controlPlane/evidence/derive.ts +426 -0
  306. package/src/controlPlane/evidence/mapping.ts +118 -0
  307. package/src/controlPlane/evidence/pack.ts +190 -0
  308. package/src/controlPlane/http/connections.ts +129 -0
  309. package/src/controlPlane/http/guard.ts +178 -10
  310. package/src/controlPlane/http/handlers.ts +172 -40
  311. package/src/controlPlane/http/holds.ts +108 -0
  312. package/src/controlPlane/http/presence.ts +71 -0
  313. package/src/controlPlane/http/processing.ts +19 -0
  314. package/src/controlPlane/http/responses.ts +590 -7
  315. package/src/controlPlane/http/routes.ts +302 -3
  316. package/src/controlPlane/http/schemas.ts +126 -0
  317. package/src/controlPlane/http/verify.ts +19 -2
  318. package/src/controlPlane/http/verifyTrust.ts +176 -0
  319. package/src/controlPlane/migrations/0004_connection_trusts.ts +30 -0
  320. package/src/controlPlane/migrations/0005_holds.ts +40 -0
  321. package/src/controlPlane/processing/derive.ts +359 -0
  322. package/src/controlPlane/processing/read.ts +102 -0
  323. package/src/controlPlane/scope/kind.ts +63 -0
  324. package/src/controlPlane/scope/scope.ts +65 -3
  325. package/src/controlPlane/wire.ts +26 -0
  326. package/src/createBackend.ts +31 -6
  327. package/src/createEntrypoint.ts +34 -2
  328. package/src/error/payload.ts +302 -0
  329. package/src/error/pithyError.ts +42 -0
  330. package/src/http/routeSpec.ts +75 -5
  331. package/src/http/validation.ts +35 -3
  332. package/src/http/verification.ts +3 -1
  333. package/src/i18n/translator.ts +2 -1
  334. package/src/index.ts +2 -0
  335. package/src/mcp/annotations.ts +38 -0
  336. package/src/mcp/tools.ts +220 -0
  337. package/src/openapi/document.ts +8 -28
  338. package/src/openapi/operations.ts +62 -0
  339. package/src/presence/channel.ts +46 -0
  340. package/src/presence/declaration.ts +283 -0
  341. package/src/presence/durableObject.ts +287 -0
  342. package/src/presence/frames.ts +153 -0
  343. package/src/presence/http/guard.ts +23 -0
  344. package/src/presence/http/responses.ts +63 -0
  345. package/src/presence/http/routes.ts +165 -0
  346. package/src/presence/http/schemas.ts +39 -0
  347. package/src/presence/names.ts +45 -0
  348. package/src/presence/room.ts +145 -0
  349. package/src/presence/ticket.ts +145 -0
  350. package/src/processing/activity.ts +289 -0
  351. package/src/processing/composed.ts +33 -0
  352. package/src/processing/vocabulary.ts +43 -0
  353. package/src/queue/consumer.ts +104 -0
  354. package/src/retention/composed.ts +75 -0
  355. package/src/retention/holds.ts +175 -0
  356. package/src/retention/holdsPurge.ts +50 -0
  357. package/src/retention/policy.ts +305 -0
  358. package/src/session/rotated.ts +53 -0
  359. package/src/test-utils/connectionFixtures.ts +58 -0
  360. package/src/test-utils/interleave.ts +31 -0
  361. package/src/testWorker.ts +13 -0
  362. package/src/version.generated.ts +1 -1
@@ -0,0 +1,12 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { Migration } from "kysely/migration";
4
+ /**
5
+ * Legal holds. Appended: the seam has shipped and adopters' databases hold its rows.
6
+ *
7
+ * One row per hold, kept after it is released, so the table is the record of every hold that ever stood. Indexed on
8
+ * `(state, subject_type, subject_id)`, because the question every purge and prune asks is "is an active hold on this
9
+ * subject", and it is asked once per step.
10
+ */
11
+ export declare const controlplane_0005_holds: Migration;
12
+ //# sourceMappingURL=0005_holds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0005_holds.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/migrations/0005_holds.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,EAAE,SA0BrC,CAAC"}
@@ -0,0 +1,26 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ //#region src/controlPlane/migrations/0005_holds.ts
4
+ /**
5
+ * Legal holds. Appended: the seam has shipped and adopters' databases hold its rows.
6
+ *
7
+ * One row per hold, kept after it is released, so the table is the record of every hold that ever stood. Indexed on
8
+ * `(state, subject_type, subject_id)`, because the question every purge and prune asks is "is an active hold on this
9
+ * subject", and it is asked once per step.
10
+ */
11
+ const controlplane_0005_holds = {
12
+ up: async (db) => {
13
+ await db.schema.createTable("pithyControlplaneHolds").addColumn("id", "text", (c) => c.primaryKey()).addColumn("subjectType", "text", (c) => c.notNull()).addColumn("subjectId", "text").addColumn("reason", "text", (c) => c.notNull()).addColumn("placedBy", "text", (c) => c.notNull()).addColumn("placedAt", "integer", (c) => c.notNull()).addColumn("state", "text", (c) => c.notNull()).addColumn("releasedAt", "integer").addColumn("releasedBy", "text").addColumn("releaseReason", "text").execute();
14
+ await db.schema.createIndex("pithyControlplaneHoldsSubjectIdx").on("pithyControlplaneHolds").columns([
15
+ "state",
16
+ "subjectType",
17
+ "subjectId"
18
+ ]).execute();
19
+ },
20
+ down: async (db) => {
21
+ await db.schema.dropIndex("pithyControlplaneHoldsSubjectIdx").execute();
22
+ await db.schema.dropTable("pithyControlplaneHolds").execute();
23
+ }
24
+ };
25
+ //#endregion
26
+ export { controlplane_0005_holds };
@@ -0,0 +1,77 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { ConsentPurpose } from "../../consent/purpose";
4
+ import type { ConsentPurposeStanding } from "../../consent/reader";
5
+ import type { PrivacyInventory } from "../../privacy/report";
6
+ import type { ResolvedActivity } from "../../processing/activity";
7
+ import type { EffectiveRetention } from "../../retention/policy";
8
+ import { type EvidenceControlId, type EvidenceState } from "../evidence/mapping";
9
+ import type { ControlPlaneProcessingResponse } from "../http/responses";
10
+ /**
11
+ * The record of processing activities, derived. Pure: `./read.ts` gathers what the Worker declares and records, and
12
+ * this turns it into the GDPR Article 30 record.
13
+ *
14
+ * **It describes the processing and changes none of it.** Every gap is named and none is filled in: an activity with
15
+ * no legal basis reads as having none, and the record never guesses one.
16
+ */
17
+ /** The evidence pack's controls that protect stored personal data, which every activity cites. */
18
+ export declare const PROCESSING_MEASURES: readonly ["data-protection", "audit-logging", "management-access", "key-rotation", "route-verification", "csrf"];
19
+ /** The platform every Worker runs on, which receives everything it serves and stores. */
20
+ export declare const CLOUDFLARE = "Cloudflare";
21
+ /** What the record knows of consent. */
22
+ export type ConsentInput =
23
+ /** The composed reader answered: every purpose in its catalog, with counts. */
24
+ {
25
+ readonly state: "read";
26
+ readonly standings: readonly ConsentPurposeStanding[];
27
+ }
28
+ /** No consent capability is composed: purposes may be declared, and no grant can be recorded. */
29
+ | {
30
+ readonly state: "not-composed";
31
+ readonly declared: readonly ConsentPurpose[];
32
+ }
33
+ /**
34
+ * A reader is composed and its record could not be read: its whole catalog, read from memory, or null where the
35
+ * reader cannot list one.
36
+ */
37
+ | {
38
+ readonly state: "unreadable";
39
+ readonly catalog: readonly ConsentPurpose[] | null;
40
+ };
41
+ /** A recipient a binding implies, on the capability that declares the binding. */
42
+ export interface BindingRecipient {
43
+ readonly name: string;
44
+ readonly receives: string;
45
+ readonly classes: readonly string[];
46
+ readonly capability: string;
47
+ }
48
+ /** Everything the derivation reads. */
49
+ export interface ProcessingInputs {
50
+ readonly activities: readonly ResolvedActivity[];
51
+ /** The privacy inventory, or null where it could not be walked. */
52
+ readonly inventory: PrivacyInventory | null;
53
+ /** The resolved retention, or null where the Worker recorded none. */
54
+ readonly retention: EffectiveRetention | null;
55
+ readonly consent: ConsentInput;
56
+ readonly bindingRecipients: readonly BindingRecipient[];
57
+ /** Each control's state from the evidence pack, or null where the connection may not read it. */
58
+ readonly measures: Readonly<Partial<Record<EvidenceControlId, EvidenceState>>> | null;
59
+ /** Whether a gap may name a stored table and field, which only a connection holding `privacy:read` may read. */
60
+ readonly fieldNames: boolean;
61
+ }
62
+ /** The record, without the envelope the route adds. */
63
+ export type DerivedProcessingRecord = Omit<ControlPlaneProcessingResponse, "environment" | "generatedAt" | "version">;
64
+ /** A capability shaped enough to say which bindings it needs. */
65
+ interface BindingDeclarer {
66
+ readonly name: string;
67
+ readonly requiredBindings: readonly {
68
+ readonly type: string;
69
+ readonly name: string;
70
+ }[];
71
+ }
72
+ /** Workers AI, once for each capability whose `ai` binding this Worker has. */
73
+ export declare function workersAiRecipients(capabilities: readonly BindingDeclarer[], env: Record<string, unknown>): BindingRecipient[];
74
+ /** Derive the record from what the Worker declares and records. */
75
+ export declare function deriveProcessingRecord(inputs: ProcessingInputs): DerivedProcessingRecord;
76
+ export {};
77
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/processing/derive.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAqB,KAAK,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,KAAK,EACV,8BAA8B,EAM/B,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AAEH,kGAAkG;AAClG,eAAO,MAAM,mBAAmB,kHAOiB,CAAC;AAElD,yFAAyF;AACzF,eAAO,MAAM,UAAU,eAAe,CAAC;AAKvC,wCAAwC;AACxC,MAAM,MAAM,YAAY;AACtB,+EAA+E;AAC7E;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,sBAAsB,EAAE,CAAA;CAAE;AACnF,iGAAiG;GAC/F;IAAE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;CAAE;AAClF;;;GAGG;GACD;IAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAAA;CAAE,CAAC;AAEzF,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACjD,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC5C,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACxD,iGAAiG;IACjG,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACtF,gHAAgH;IAChH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,uDAAuD;AACvD,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,8BAA8B,EAAE,aAAa,GAAG,aAAa,GAAG,SAAS,CAAC,CAAC;AAEtH,iEAAiE;AACjE,UAAU,eAAe;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,gBAAgB,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxF;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,SAAS,eAAe,EAAE,EACxC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,gBAAgB,EAAE,CAWpB;AAoLD,mEAAmE;AACnE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,uBAAuB,CA2ExF"}
@@ -0,0 +1,206 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { EVIDENCE_CONTROLS } from "../evidence/mapping.js";
4
+ //#region src/controlPlane/processing/derive.ts
5
+ /**
6
+ * The record of processing activities, derived. Pure: `./read.ts` gathers what the Worker declares and records, and
7
+ * this turns it into the GDPR Article 30 record.
8
+ *
9
+ * **It describes the processing and changes none of it.** Every gap is named and none is filled in: an activity with
10
+ * no legal basis reads as having none, and the record never guesses one.
11
+ */
12
+ /** The evidence pack's controls that protect stored personal data, which every activity cites. */
13
+ const PROCESSING_MEASURES = [
14
+ "data-protection",
15
+ "audit-logging",
16
+ "management-access",
17
+ "key-rotation",
18
+ "route-verification",
19
+ "csrf"
20
+ ];
21
+ /** The platform every Worker runs on, which receives everything it serves and stores. */
22
+ const CLOUDFLARE = "Cloudflare";
23
+ const CLOUDFLARE_RECEIVES = "Every request this Worker serves, and everything it stores in D1, KV, R2 and Durable Objects.";
24
+ /** Workers AI, once for each capability whose `ai` binding this Worker has. */
25
+ function workersAiRecipients(capabilities, env) {
26
+ return capabilities.flatMap((capability) => capability.requiredBindings.filter((binding) => binding.type === "ai" && env[binding.name] != null).map((binding) => ({
27
+ name: "Cloudflare Workers AI",
28
+ receives: `What ${capability.name} sends for inference through the ${binding.name} binding.`,
29
+ classes: [],
30
+ capability: capability.name
31
+ })));
32
+ }
33
+ /** The catalog's purposes, however the record learned of them, or null where it could learn none. */
34
+ function catalogOf(consent) {
35
+ const purposes = consent.state === "read" ? consent.standings.map((entry) => entry.purpose) : consent.state === "unreadable" ? consent.catalog : consent.declared;
36
+ return purposes === null ? null : new Map(purposes.map((purpose) => [purpose.id, purpose]));
37
+ }
38
+ /** A consent-based activity's purpose and its counts, where they are known. */
39
+ function consentView(id, consent) {
40
+ const purpose = catalogOf(consent)?.get(id);
41
+ const counted = consent.state === "read" ? consent.standings.find((entry) => entry.purpose.id === id) : void 0;
42
+ const zero = consent.state === "not-composed" && purpose !== void 0 ? 0 : null;
43
+ return {
44
+ purpose: id,
45
+ noticeVersion: purpose?.noticeVersion ?? null,
46
+ explicit: purpose?.explicit ?? null,
47
+ grants: counted?.grants ?? zero,
48
+ withdrawals: counted?.withdrawals ?? zero,
49
+ standing: counted?.standing ?? zero
50
+ };
51
+ }
52
+ /** The retention behind an activity's classes. */
53
+ function retentionView(classes, retention) {
54
+ if (retention === null) return null;
55
+ return {
56
+ classAges: retention.classAges.filter((age) => classes.includes(age.class)).map((age) => ({ ...age })),
57
+ purgeWindows: retention.purgeWindows.map((window) => ({ ...window }))
58
+ };
59
+ }
60
+ /** A recipient as the record shows it. */
61
+ function recipient(name, receives, classes, source) {
62
+ return {
63
+ name,
64
+ receives,
65
+ classes: [...classes],
66
+ source
67
+ };
68
+ }
69
+ /** The gaps one activity has. */
70
+ function activityGaps(activity, stored, catalog) {
71
+ const gaps = [];
72
+ if (activity.legalBasis === null) gaps.push({
73
+ kind: "no-legal-basis",
74
+ subject: activity.id,
75
+ detail: "No legal basis is declared. State one on the activity, or in the processing block's overrides."
76
+ });
77
+ if (activity.legalBasis === "consent") {
78
+ if (activity.consentPurpose === null) gaps.push({
79
+ kind: "no-consent-purpose",
80
+ subject: activity.id,
81
+ detail: "The basis is consent and no consent purpose is named."
82
+ });
83
+ else if (catalog !== null && !catalog.has(activity.consentPurpose)) gaps.push({
84
+ kind: "no-consent-purpose",
85
+ subject: activity.id,
86
+ detail: `The basis is consent, and no composed catalog declares the purpose ${activity.consentPurpose}.`
87
+ });
88
+ }
89
+ if (stored !== null) {
90
+ const unstored = activity.classes.filter((each) => !stored.has(each));
91
+ if (unstored.length > 0) gaps.push({
92
+ kind: "class-not-stored",
93
+ subject: activity.id,
94
+ detail: `It claims ${unstored.join(", ")}, which no composed schema stores.`
95
+ });
96
+ }
97
+ return gaps;
98
+ }
99
+ /** Stored classes no activity of their capability, and none of the adopter's, claims. Tables only with field names. */
100
+ function unclaimedGaps(activities, inventory, fieldNames) {
101
+ const adopter = new Set(activities.filter((each) => each.declaredBy === "config").flatMap((each) => each.classes));
102
+ const byCapability = /* @__PURE__ */ new Map();
103
+ for (const schema of inventory.schemas) {
104
+ const claimed = new Set(activities.filter((each) => each.declaredBy === schema.capability).flatMap((each) => each.classes));
105
+ for (const field of schema.fields) {
106
+ if (claimed.has(field.class) || adopter.has(field.class)) continue;
107
+ const classes = byCapability.get(schema.capability) ?? /* @__PURE__ */ new Map();
108
+ const tables = classes.get(field.class) ?? /* @__PURE__ */ new Set();
109
+ tables.add(schema.name);
110
+ classes.set(field.class, tables);
111
+ byCapability.set(schema.capability, classes);
112
+ }
113
+ }
114
+ return [...byCapability].flatMap(([capability, classes]) => [...classes].map(([cls, tables]) => ({
115
+ kind: "class-unclaimed",
116
+ subject: capability,
117
+ detail: fieldNames ? `It stores ${cls} in ${[...tables].join(", ")}, and no processing activity claims it.` : `It stores ${cls}, and no processing activity claims it.`
118
+ })));
119
+ }
120
+ /**
121
+ * Every stored `phi` field no consent purpose in the catalog covers: field by field with field names, and otherwise
122
+ * once for each capability that stores one. Silent where the catalog is unknown.
123
+ */
124
+ function uncoveredPhi(inventory, catalog, fieldNames) {
125
+ if (catalog === null) return [];
126
+ if ([...catalog.values()].some((purpose) => purpose.classes.includes("phi"))) return [];
127
+ if (!fieldNames) return [...new Set(inventory.schemas.filter((schema) => schema.fields.some((field) => field.class === "phi")).map((schema) => schema.capability))].map((capability) => ({
128
+ kind: "phi-not-covered",
129
+ subject: capability,
130
+ detail: "It stores phi, and no consent purpose covers phi."
131
+ }));
132
+ return inventory.schemas.flatMap((schema) => schema.fields.filter((field) => field.class === "phi").map((field) => ({
133
+ kind: "phi-not-covered",
134
+ subject: `${schema.capability}/${schema.name}.${field.path}`,
135
+ detail: "A phi field is stored, and no consent purpose covers phi."
136
+ })));
137
+ }
138
+ /** Every purpose covering a class that holds no standing grant. Silent where the record could not be read. */
139
+ function grantlessPurposes(consent) {
140
+ if (consent.state === "unreadable") return [];
141
+ if (consent.state === "not-composed") return consent.declared.filter((purpose) => purpose.classes.length > 0).map((purpose) => ({
142
+ kind: "no-standing-grant",
143
+ subject: purpose.id,
144
+ detail: `It covers ${purpose.classes.join(", ")}, and no consent capability is composed to record a grant.`
145
+ }));
146
+ return consent.standings.filter((entry) => entry.purpose.classes.length > 0 && entry.standing === 0).map((entry) => ({
147
+ kind: "no-standing-grant",
148
+ subject: entry.purpose.id,
149
+ detail: `It covers ${entry.purpose.classes.join(", ")}, and no subject holds a current, unexpired grant.`
150
+ }));
151
+ }
152
+ /** Derive the record from what the Worker declares and records. */
153
+ function deriveProcessingRecord(inputs) {
154
+ const { activities, inventory, retention, consent } = inputs;
155
+ const stored = inventory === null ? null : new Set(inventory.observed);
156
+ const catalog = catalogOf(consent);
157
+ const entries = activities.map((activity) => ({
158
+ id: activity.id,
159
+ purpose: activity.purpose,
160
+ declaredBy: activity.declaredBy,
161
+ classes: [...activity.classes],
162
+ legalBasis: activity.legalBasis,
163
+ basisSource: activity.basisSource,
164
+ consent: activity.legalBasis === "consent" && activity.consentPurpose !== null ? consentView(activity.consentPurpose, consent) : null,
165
+ recipients: [
166
+ recipient(CLOUDFLARE, CLOUDFLARE_RECEIVES, activity.classes, "platform"),
167
+ ...inputs.bindingRecipients.filter((each) => each.capability === activity.declaredBy).map((each) => recipient(each.name, each.receives, each.classes, "binding")),
168
+ ...activity.recipients.map((each) => recipient(each.name, each.receives, each.classes, "declared"))
169
+ ],
170
+ retention: retentionView(activity.classes, retention),
171
+ measures: [...PROCESSING_MEASURES],
172
+ gaps: activityGaps(activity, stored, catalog)
173
+ }));
174
+ const recipients = [recipient(CLOUDFLARE, CLOUDFLARE_RECEIVES, inventory?.observed ?? [], "platform")];
175
+ const seen = /* @__PURE__ */ new Set();
176
+ const others = [...inputs.bindingRecipients.map((each) => recipient(each.name, each.receives, each.classes, "binding")), ...activities.flatMap((activity) => activity.recipients.map((each) => recipient(each.name, each.receives, each.classes, "declared")))];
177
+ for (const each of others) {
178
+ const key = `${each.name}\u0000${each.receives}`;
179
+ if (seen.has(key)) continue;
180
+ seen.add(key);
181
+ recipients.push(each);
182
+ }
183
+ const notDerivable = [];
184
+ if (inventory === null) notDerivable.push("The privacy inventory could not be walked, so data classes are not cross-checked.");
185
+ if (consent.state === "unreadable") notDerivable.push("The consent record could not be read, so grant counts are unknown.");
186
+ if (catalog === null) notDerivable.push("The consent catalog could not be listed, so consent purposes and phi coverage are not cross-checked.");
187
+ return {
188
+ activities: entries,
189
+ recipients,
190
+ measures: PROCESSING_MEASURES.map((control) => ({
191
+ control,
192
+ title: EVIDENCE_CONTROLS[control].title,
193
+ soc2: [...EVIDENCE_CONTROLS[control].soc2],
194
+ iso27001: [...EVIDENCE_CONTROLS[control].iso27001],
195
+ state: inputs.measures?.[control] ?? null
196
+ })),
197
+ gaps: [
198
+ ...inventory === null ? [] : unclaimedGaps(activities, inventory, inputs.fieldNames),
199
+ ...inventory === null ? [] : uncoveredPhi(inventory, catalog, inputs.fieldNames),
200
+ ...grantlessPurposes(consent)
201
+ ],
202
+ notDerivable
203
+ };
204
+ }
205
+ //#endregion
206
+ export { CLOUDFLARE, PROCESSING_MEASURES, deriveProcessingRecord, workersAiRecipients };
@@ -0,0 +1,10 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { Context } from "hono";
4
+ import type { PithyHonoEnv } from "../../capability/capability";
5
+ import type { ControlPlaneContext } from "../context";
6
+ import type { ControlPlaneHandlerDeps } from "../http/handlers";
7
+ import type { ControlPlaneProcessingResponse } from "../http/responses";
8
+ /** Read everything the record derives from, and derive it. */
9
+ export declare function readProcessingRecord(c: Context<PithyHonoEnv>, deps: ControlPlaneHandlerDeps, context: ControlPlaneContext): Promise<ControlPlaneProcessingResponse>;
10
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/processing/read.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAOhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;AA8DxE,8DAA8D;AAC9D,wBAAsB,oBAAoB,CACxC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,EACxB,IAAI,EAAE,uBAAuB,EAC7B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,8BAA8B,CAAC,CAmBzC"}
@@ -0,0 +1,89 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { PRIVACY_READ_SCOPE } from "../scope/scope.js";
4
+ import { workerVersionMetadata } from "../../worker/identity.js";
5
+ import { causeMessage } from "../../error/cause.js";
6
+ import { readEvidencePack } from "../evidence/pack.js";
7
+ import { composedConsent, composedConsentPurposes } from "../../consent/reader.js";
8
+ import { composedProcessing, effectiveProcessing } from "../../processing/composed.js";
9
+ import { deriveProcessingRecord, workersAiRecipients } from "./derive.js";
10
+ //#region src/controlPlane/processing/read.ts
11
+ /**
12
+ * The reads behind the processing record: what the Worker composes, declares and records, gathered once per call.
13
+ * Each read that fails costs the record what it would have said, named in `notDerivable`, and the record still
14
+ * answers. A cause goes to the Worker's log and never into the record.
15
+ *
16
+ * **It never widens a scope.** The measures carry the evidence pack's states only when the connection also holds
17
+ * `evidence:read`; without it they are cited by control and their state is null. A gap names a stored table or field
18
+ * only when the connection also holds `privacy:read`; without it, the capability and the class.
19
+ */
20
+ /** The privacy inventory, or null where the walk threw. */
21
+ function inventoryOf(c, deps) {
22
+ try {
23
+ return deps.privacyInventory();
24
+ } catch (cause) {
25
+ c.var.log.warn("processing: privacy inventory unreadable", { reason: causeMessage(cause) ?? String(cause) });
26
+ return null;
27
+ }
28
+ }
29
+ /** What the composed consent capability says, or that none is composed. */
30
+ async function consentOf(c, deps) {
31
+ const composition = deps.composition();
32
+ const declared = composedConsentPurposes(composition);
33
+ const reader = composedConsent(composition, c.env);
34
+ if (reader === null) return {
35
+ state: "not-composed",
36
+ declared
37
+ };
38
+ if (reader.standings !== void 0) try {
39
+ return {
40
+ state: "read",
41
+ standings: await reader.standings()
42
+ };
43
+ } catch (cause) {
44
+ c.var.log.warn("processing: consent record unreadable", { reason: causeMessage(cause) ?? String(cause) });
45
+ }
46
+ return {
47
+ state: "unreadable",
48
+ catalog: catalogOf(c, reader)
49
+ };
50
+ }
51
+ /** The reader's whole catalog from memory, or null where it cannot list one. */
52
+ function catalogOf(c, reader) {
53
+ if (reader.purposes === void 0) return null;
54
+ try {
55
+ return reader.purposes();
56
+ } catch (cause) {
57
+ c.var.log.warn("processing: consent catalog unreadable", { reason: causeMessage(cause) ?? String(cause) });
58
+ return null;
59
+ }
60
+ }
61
+ /** Each control's state, read through the evidence pack, or null without `evidence:read`. */
62
+ async function measuresOf(c, deps, context) {
63
+ if (!context.grantedScopes.includes("evidence:read")) return null;
64
+ const pack = await readEvidencePack(c, deps, context);
65
+ return Object.fromEntries(pack.controls.map((entry) => [entry.control, entry.state]));
66
+ }
67
+ /** Read everything the record derives from, and derive it. */
68
+ async function readProcessingRecord(c, deps, context) {
69
+ const env = c.env;
70
+ const composition = deps.composition();
71
+ const [consent, measures] = await Promise.all([consentOf(c, deps), measuresOf(c, deps, context)]);
72
+ const record = deriveProcessingRecord({
73
+ activities: composedProcessing() ?? effectiveProcessing(void 0, composition),
74
+ inventory: inventoryOf(c, deps),
75
+ retention: deps.retention?.() ?? null,
76
+ consent,
77
+ bindingRecipients: workersAiRecipients(composition, env),
78
+ measures,
79
+ fieldNames: context.grantedScopes.includes(PRIVACY_READ_SCOPE)
80
+ });
81
+ return {
82
+ environment: context.environment,
83
+ generatedAt: deps.now().toISOString(),
84
+ version: workerVersionMetadata(c.env).id,
85
+ ...record
86
+ };
87
+ }
88
+ //#endregion
89
+ export { readProcessingRecord };
@@ -0,0 +1,46 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ /**
5
+ * What kind of operation a control-plane scope grants, declared by the capability that owns the scope.
6
+ *
7
+ * Scopes are federated and open, so a management client cannot tell from the string whether `auth:users:suspend`
8
+ * reads, writes or does something worse. The kind is the closed vocabulary it maps its own roles onto instead: the
9
+ * app tools' effects, with `reads` beneath them and `credentials` (mints, rotates or revokes a credential) above.
10
+ * Ordered from least to most restrictive. Written out rather than spread from `TOOL_EFFECTS`, so a scope declaration
11
+ * stays importable from a browser program without the route machinery; `kind.test.ts` holds the two together.
12
+ */
13
+ export declare const SCOPE_KINDS: readonly ["reads", "writes", "sends", "charges", "deletes", "credentials"];
14
+ export declare const ScopeKind: z.ZodEnum<{
15
+ charges: "charges";
16
+ credentials: "credentials";
17
+ deletes: "deletes";
18
+ reads: "reads";
19
+ sends: "sends";
20
+ writes: "writes";
21
+ }>;
22
+ export type ScopeKind = z.infer<typeof ScopeKind>;
23
+ /** The kind a client assumes for a scoped route that states none: a Worker too old to say. */
24
+ export declare const MOST_RESTRICTIVE_SCOPE_KIND: ScopeKind;
25
+ /** The part of an admin route a kind is read from. */
26
+ export interface ScopedRoute {
27
+ readonly scope: string | null;
28
+ readonly kind?: ScopeKind | undefined;
29
+ }
30
+ /**
31
+ * The kind of a route as a client reads it: its declared kind, {@link MOST_RESTRICTIVE_SCOPE_KIND} for a scoped
32
+ * route that declares none, and null for a route with no scope, which grants nothing to classify.
33
+ */
34
+ export declare function scopeKindOf(route: ScopedRoute): ScopeKind | null;
35
+ /** One scope whose routes declare more than one kind. */
36
+ export interface ScopeKindConflict {
37
+ readonly scope: string;
38
+ /** Every kind declared for it, in the order first seen. */
39
+ readonly kinds: readonly ScopeKind[];
40
+ }
41
+ /**
42
+ * Every scope whose routes disagree about its kind. Holding a scope confers every route that requires it, so a
43
+ * scope has exactly one kind, and two routes declaring different ones is a declaration to fix.
44
+ */
45
+ export declare function scopeKindConflicts(routes: Iterable<ScopedRoute>): ScopeKindConflict[];
46
+ //# sourceMappingURL=kind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kind.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/scope/kind.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,YAAI,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,CAAU,CAAC;AAEtG,eAAO,MAAM,SAAS;;;;;;;EAInB,CAAC;AACJ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAElD,8FAA8F;AAC9F,eAAO,MAAM,2BAA2B,EAAE,SAAyB,CAAC;AAEpE,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAGhE;AAED,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,iBAAiB,EAAE,CAUrF"}
@@ -0,0 +1,52 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ //#region src/controlPlane/scope/kind.ts
5
+ /**
6
+ * What kind of operation a control-plane scope grants, declared by the capability that owns the scope.
7
+ *
8
+ * Scopes are federated and open, so a management client cannot tell from the string whether `auth:users:suspend`
9
+ * reads, writes or does something worse. The kind is the closed vocabulary it maps its own roles onto instead: the
10
+ * app tools' effects, with `reads` beneath them and `credentials` (mints, rotates or revokes a credential) above.
11
+ * Ordered from least to most restrictive. Written out rather than spread from `TOOL_EFFECTS`, so a scope declaration
12
+ * stays importable from a browser program without the route machinery; `kind.test.ts` holds the two together.
13
+ */
14
+ const SCOPE_KINDS = [
15
+ "reads",
16
+ "writes",
17
+ "sends",
18
+ "charges",
19
+ "deletes",
20
+ "credentials"
21
+ ];
22
+ const ScopeKind = z.enum(SCOPE_KINDS).describe("What kind of operation a scope grants: `reads`, `writes`, `sends` (mail or a message leaves the system), `charges` (money or an entitlement moves), `deletes`, or `credentials` (a credential is minted, rotated or revoked).");
23
+ /** The kind a client assumes for a scoped route that states none: a Worker too old to say. */
24
+ const MOST_RESTRICTIVE_SCOPE_KIND = "credentials";
25
+ /**
26
+ * The kind of a route as a client reads it: its declared kind, {@link MOST_RESTRICTIVE_SCOPE_KIND} for a scoped
27
+ * route that declares none, and null for a route with no scope, which grants nothing to classify.
28
+ */
29
+ function scopeKindOf(route) {
30
+ if (route.scope === null) return null;
31
+ return route.kind ?? "credentials";
32
+ }
33
+ /**
34
+ * Every scope whose routes disagree about its kind. Holding a scope confers every route that requires it, so a
35
+ * scope has exactly one kind, and two routes declaring different ones is a declaration to fix.
36
+ */
37
+ function scopeKindConflicts(routes) {
38
+ const kinds = /* @__PURE__ */ new Map();
39
+ for (const route of routes) {
40
+ const kind = scopeKindOf(route);
41
+ if (route.scope === null || kind === null) continue;
42
+ const seen = kinds.get(route.scope) ?? [];
43
+ if (!seen.includes(kind)) seen.push(kind);
44
+ kinds.set(route.scope, seen);
45
+ }
46
+ return [...kinds].filter(([, seen]) => seen.length > 1).map(([scope, seen]) => ({
47
+ scope,
48
+ kinds: seen
49
+ }));
50
+ }
51
+ //#endregion
52
+ export { MOST_RESTRICTIVE_SCOPE_KIND, SCOPE_KINDS, ScopeKind, scopeKindConflicts, scopeKindOf };
@@ -51,25 +51,80 @@ export declare const MANIFEST_READ_SCOPE: ControlPlaneScope;
51
51
  export declare const KEYS_ROTATE_SCOPE: ControlPlaneScope;
52
52
  /**
53
53
  * Read where each database this Worker binds stands: what is applied, what is pending, what is undeclared,
54
- * and which run put it there (#729). Read-only by construction — every route behind it is a `GET`, and
54
+ * and which run put it there. Read-only by construction — every route behind it is a `GET`, and
55
55
  * nothing over the seam runs, reverses or resets a migration. The two migration health keys on the seam's
56
56
  * manifest entry sit behind it too, so a connection without it gets no number rather than a zero.
57
57
  */
58
58
  export declare const MIGRATIONS_READ_SCOPE: ControlPlaneScope;
59
59
  /**
60
60
  * Read this Worker's full privacy inventory: every storage schema holding a marked field, each marked field's
61
- * path, its class, and where it is stored (#132). Read-only — the one route behind it is a `GET`.
61
+ * path, its class, and where it is stored. Read-only — the one route behind it is a `GET`.
62
62
  *
63
63
  * **Its own scope rather than `manifest:read`**, because field names are the kind of thing an adopter may
64
64
  * reasonably withhold from a client that has no business seeing them. The manifest's privacy block carries
65
65
  * classes and counts only, so a connection without this one still sees the posture headline.
66
66
  */
67
67
  export declare const PRIVACY_READ_SCOPE: ControlPlaneScope;
68
+ /**
69
+ * Read this Worker's evidence pack: the SOC 2 and ISO 27001 evidence it derives from its own declarations.
70
+ * Read-only — the one route behind it is a `GET`, and it performs nothing it reports on.
71
+ *
72
+ * **Its own scope**, because the pack reads across the seam: every connection's principal, kind, purpose and
73
+ * grant, and every route's verification. It never widens another scope. A fact a capability keeps behind its own
74
+ * read — migration counts behind {@link MIGRATIONS_READ_SCOPE}, a capability's health behind that capability's
75
+ * scope — stays behind it, and a connection without that scope gets `not-derivable` naming it.
76
+ */
77
+ export declare const EVIDENCE_READ_SCOPE: ControlPlaneScope;
78
+ /**
79
+ * Read the erasure attestation: one subject's purge, or every purge in a period, with the audit events that
80
+ * record each step. Read-only — both routes behind it are `GET`s — and kind `reads`.
81
+ *
82
+ * **Its own scope**, because it answers across subjects: which accounts and organizations were deleted, by which
83
+ * kind of actor and why. It carries nothing about a person beyond the id the lifecycle trail already keeps, and it
84
+ * never widens `audit:events:read`: it cites an event by id and chain position, never its contents.
85
+ */
86
+ export declare const ERASURE_READ_SCOPE: ControlPlaneScope;
87
+ /**
88
+ * Read the record of processing activities, GDPR Article 30: each activity's purpose, data classes, legal basis,
89
+ * recipients, retention and measures, and every gap. Read-only — the one route behind it is a `GET` — and kind
90
+ * `reads`.
91
+ *
92
+ * **Its own scope**, because it reads across the Worker: every activity, the consent counts behind each consent
93
+ * basis, and where each stored class sits. It never widens another: the measures carry the evidence pack's states
94
+ * only for a connection that also holds {@link EVIDENCE_READ_SCOPE}, and a gap names a stored table or field only for
95
+ * one that also holds {@link PRIVACY_READ_SCOPE}. Without it, a gap names the capability and the class.
96
+ */
97
+ export declare const PROCESSING_READ_SCOPE: ControlPlaneScope;
98
+ /**
99
+ * Place and release a legal hold on a user, an organization or a tenant's audit trail. A write — both routes
100
+ * behind it are `POST`s — and kind `writes`, so `pithy dashboard connect` offers it and never grants it by default.
101
+ *
102
+ * **It can only preserve.** A hold suspends purges, prunes and inactivity deletions; releasing one returns the subject
103
+ * to the policy the Worker deploys with. No route behind it, or anywhere, shortens a retention.
104
+ */
105
+ export declare const RETENTION_HOLD_SCOPE: ControlPlaneScope;
106
+ /**
107
+ * Read the legal holds: every hold, standing or released, with who placed it and why. Read-only — the one
108
+ * route behind it is a `GET` — and kind `reads`. The manifest's count of standing holds sits behind it too:
109
+ * `activeHolds`, in the health of the `controlplane` entry.
110
+ */
111
+ export declare const RETENTION_READ_SCOPE: ControlPlaneScope;
112
+ /**
113
+ * List every connection bound to this Worker's environment: who authorized it, what kind of client it is for, its purpose, its
114
+ * credential kinds, its grant and when it was registered or revoked. Kind `reads`. Never key material.
115
+ */
116
+ export declare const CONNECTIONS_READ_SCOPE: ControlPlaneScope;
117
+ /**
118
+ * Revoke every key and trust another connection in this Worker's environment holds, with a reason. Kind
119
+ * `credentials`. Refused on the caller's own connection, which ends through `disconnect`.
120
+ */
121
+ export declare const CONNECTIONS_REVOKE_SCOPE: ControlPlaneScope;
68
122
  /**
69
123
  * The seam's base grant — what `pithy dashboard connect` grants whatever the Worker composes. Ping is absent
70
124
  * because it is not grantable; see {@link ANY_VERIFIED_CALLER}.
71
125
  *
72
- * **{@link MIGRATIONS_READ_SCOPE} and {@link PRIVACY_READ_SCOPE} are absent on purpose.** Each is a read —
126
+ * **The seam's reads are absent on purpose** — {@link MIGRATIONS_READ_SCOPE}, {@link PRIVACY_READ_SCOPE},
127
+ * {@link EVIDENCE_READ_SCOPE}, {@link ERASURE_READ_SCOPE}, {@link PROCESSING_READ_SCOPE}. Each is a read —
73
128
  * every route requiring it is a `GET` — so `connect` derives it from the seam's `adminRoutes` the way it
74
129
  * derives every capability's reads. The base exists for `keys:rotate`, which is not a read and would
75
130
  * otherwise drop out.
@@ -1 +1 @@
1
- {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/scope/scope.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAexB;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,aAQ3B,CAAC;AACJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,EAAE,OAAO,MAA2D,CAAC;AAErG;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,GAAG,OAAO,mBAAmB,CAAC;AAErF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,iBAAmC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,iBAAiC,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,iBAAqC,CAAC;AAE1E;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBAAkC,CAAC;AAEpE;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,iBAAiB,EAA6C,CAAC;AAElG;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,uBAAuB,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAIrH"}
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/scope/scope.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAexB;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,aAQ3B,CAAC;AACJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,EAAE,OAAO,MAA2D,CAAC;AAErG;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,GAAG,OAAO,mBAAmB,CAAC;AAErF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,iBAAmC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,iBAAiC,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,iBAAqC,CAAC;AAE1E;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBAAkC,CAAC;AAEpE;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,iBAAmC,CAAC;AAEtE;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBAAkC,CAAC;AAEpE;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,EAAE,iBAAqC,CAAC;AAE1E;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAoC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAoC,CAAC;AAExE;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAAsC,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAAwC,CAAC;AAEhF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,iBAAiB,EAA6C,CAAC;AAElG;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,uBAAuB,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAIrH"}