@pithy-sh/core 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +54 -0
  2. package/dist/audit/auditEvent.js +1 -1
  3. package/dist/audit/chain.d.ts +289 -0
  4. package/dist/audit/chain.d.ts.map +1 -0
  5. package/dist/audit/chain.js +400 -0
  6. package/dist/audit/citations.d.ts +52 -0
  7. package/dist/audit/citations.d.ts.map +1 -0
  8. package/dist/audit/citations.js +13 -0
  9. package/dist/capability/bindings.d.ts +22 -0
  10. package/dist/capability/bindings.d.ts.map +1 -1
  11. package/dist/capability/bindings.js +32 -2
  12. package/dist/capability/capability.d.ts +123 -16
  13. package/dist/capability/capability.d.ts.map +1 -1
  14. package/dist/capability/capability.js +17 -2
  15. package/dist/capability/erasure.d.ts +75 -0
  16. package/dist/capability/erasure.d.ts.map +1 -0
  17. package/dist/capability/erasure.js +2 -0
  18. package/dist/capability/manifest.d.ts +5 -0
  19. package/dist/capability/manifest.d.ts.map +1 -1
  20. package/dist/capability/personColumns.d.ts +28 -0
  21. package/dist/capability/personColumns.d.ts.map +1 -0
  22. package/dist/capability/personColumns.js +66 -0
  23. package/dist/capability/personRows.d.ts +62 -0
  24. package/dist/capability/personRows.d.ts.map +1 -0
  25. package/dist/capability/personRows.js +59 -0
  26. package/dist/capability/purge.d.ts +86 -19
  27. package/dist/capability/purge.d.ts.map +1 -1
  28. package/dist/capability/purge.js +83 -5
  29. package/dist/capability/settings.d.ts +10 -0
  30. package/dist/capability/settings.d.ts.map +1 -1
  31. package/dist/capability/vectorIndex.d.ts +36 -0
  32. package/dist/capability/vectorIndex.d.ts.map +1 -0
  33. package/dist/capability/vectorIndex.js +25 -0
  34. package/dist/cli/commandCatalog.generated.d.ts +438 -3
  35. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  36. package/dist/cli/commandCatalog.generated.js +454 -2
  37. package/dist/cli/commandFacts.d.ts +8 -5
  38. package/dist/cli/commandFacts.d.ts.map +1 -1
  39. package/dist/cli/commandFacts.js +12 -3
  40. package/dist/consent/purpose.d.ts +70 -0
  41. package/dist/consent/purpose.d.ts.map +1 -0
  42. package/dist/consent/purpose.js +97 -0
  43. package/dist/consent/reader.d.ts +99 -0
  44. package/dist/consent/reader.d.ts.map +1 -0
  45. package/dist/consent/reader.js +33 -0
  46. package/dist/controlPlane/audit/actions.d.ts +15 -4
  47. package/dist/controlPlane/audit/actions.d.ts.map +1 -1
  48. package/dist/controlPlane/audit/actions.js +16 -5
  49. package/dist/controlPlane/capability.d.ts +10 -0
  50. package/dist/controlPlane/capability.d.ts.map +1 -1
  51. package/dist/controlPlane/capability.js +56 -8
  52. package/dist/controlPlane/context.d.ts +4 -0
  53. package/dist/controlPlane/context.d.ts.map +1 -1
  54. package/dist/controlPlane/context.js +3 -2
  55. package/dist/controlPlane/data/connection.d.ts +21 -2
  56. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  57. package/dist/controlPlane/data/connection.js +13 -4
  58. package/dist/controlPlane/data/connections.d.ts +94 -0
  59. package/dist/controlPlane/data/connections.d.ts.map +1 -0
  60. package/dist/controlPlane/data/connections.js +151 -0
  61. package/dist/controlPlane/data/hold.d.ts +46 -0
  62. package/dist/controlPlane/data/hold.d.ts.map +1 -0
  63. package/dist/controlPlane/data/hold.js +36 -0
  64. package/dist/controlPlane/data/oidcTrust.d.ts +122 -0
  65. package/dist/controlPlane/data/oidcTrust.d.ts.map +1 -0
  66. package/dist/controlPlane/data/oidcTrust.js +173 -0
  67. package/dist/controlPlane/data/tables.d.ts +4 -0
  68. package/dist/controlPlane/data/tables.d.ts.map +1 -1
  69. package/dist/controlPlane/data/tables.js +9 -4
  70. package/dist/controlPlane/data/trustFirstCall.d.ts +16 -0
  71. package/dist/controlPlane/data/trustFirstCall.d.ts.map +1 -0
  72. package/dist/controlPlane/data/trustFirstCall.js +25 -0
  73. package/dist/controlPlane/discovery/adminRoute.d.ts +165 -2
  74. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
  75. package/dist/controlPlane/discovery/adminRoute.js +32 -8
  76. package/dist/controlPlane/discovery/routeInput.d.ts +23 -0
  77. package/dist/controlPlane/discovery/routeInput.d.ts.map +1 -0
  78. package/dist/controlPlane/discovery/routeInput.js +80 -0
  79. package/dist/controlPlane/discovery/toolName.d.ts +21 -0
  80. package/dist/controlPlane/discovery/toolName.d.ts.map +1 -0
  81. package/dist/controlPlane/discovery/toolName.js +25 -0
  82. package/dist/controlPlane/erasure/attest.d.ts +78 -0
  83. package/dist/controlPlane/erasure/attest.d.ts.map +1 -0
  84. package/dist/controlPlane/erasure/attest.js +164 -0
  85. package/dist/controlPlane/erasure/read.d.ts +39 -0
  86. package/dist/controlPlane/erasure/read.d.ts.map +1 -0
  87. package/dist/controlPlane/erasure/read.js +140 -0
  88. package/dist/controlPlane/error/errors.d.ts +61 -2
  89. package/dist/controlPlane/error/errors.d.ts.map +1 -1
  90. package/dist/controlPlane/error/errors.js +13 -0
  91. package/dist/controlPlane/evidence/derive.d.ts +124 -0
  92. package/dist/controlPlane/evidence/derive.d.ts.map +1 -0
  93. package/dist/controlPlane/evidence/derive.js +343 -0
  94. package/dist/controlPlane/evidence/mapping.d.ts +85 -0
  95. package/dist/controlPlane/evidence/mapping.d.ts.map +1 -0
  96. package/dist/controlPlane/evidence/mapping.js +114 -0
  97. package/dist/controlPlane/evidence/pack.d.ts +46 -0
  98. package/dist/controlPlane/evidence/pack.d.ts.map +1 -0
  99. package/dist/controlPlane/evidence/pack.js +124 -0
  100. package/dist/controlPlane/http/connections.d.ts +96 -0
  101. package/dist/controlPlane/http/connections.d.ts.map +1 -0
  102. package/dist/controlPlane/http/connections.js +109 -0
  103. package/dist/controlPlane/http/guard.d.ts +23 -2
  104. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  105. package/dist/controlPlane/http/guard.js +69 -12
  106. package/dist/controlPlane/http/handlers.d.ts +212 -10
  107. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  108. package/dist/controlPlane/http/handlers.js +117 -26
  109. package/dist/controlPlane/http/holds.d.ts +57 -0
  110. package/dist/controlPlane/http/holds.d.ts.map +1 -0
  111. package/dist/controlPlane/http/holds.js +98 -0
  112. package/dist/controlPlane/http/presence.d.ts +27 -0
  113. package/dist/controlPlane/http/presence.d.ts.map +1 -0
  114. package/dist/controlPlane/http/presence.js +67 -0
  115. package/dist/controlPlane/http/processing.d.ts +73 -0
  116. package/dist/controlPlane/http/processing.d.ts.map +1 -0
  117. package/dist/controlPlane/http/processing.js +17 -0
  118. package/dist/controlPlane/http/responses.d.ts +927 -3
  119. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  120. package/dist/controlPlane/http/responses.js +283 -7
  121. package/dist/controlPlane/http/routes.d.ts +12 -2
  122. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  123. package/dist/controlPlane/http/routes.js +182 -5
  124. package/dist/controlPlane/http/schemas.d.ts +63 -0
  125. package/dist/controlPlane/http/schemas.d.ts.map +1 -1
  126. package/dist/controlPlane/http/schemas.js +39 -1
  127. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  128. package/dist/controlPlane/http/verify.js +13 -1
  129. package/dist/controlPlane/http/verifyTrust.d.ts +76 -0
  130. package/dist/controlPlane/http/verifyTrust.d.ts.map +1 -0
  131. package/dist/controlPlane/http/verifyTrust.js +75 -0
  132. package/dist/controlPlane/migrations/0004_connection_trusts.d.ts +15 -0
  133. package/dist/controlPlane/migrations/0004_connection_trusts.d.ts.map +1 -0
  134. package/dist/controlPlane/migrations/0004_connection_trusts.js +23 -0
  135. package/dist/controlPlane/migrations/0005_holds.d.ts +12 -0
  136. package/dist/controlPlane/migrations/0005_holds.d.ts.map +1 -0
  137. package/dist/controlPlane/migrations/0005_holds.js +26 -0
  138. package/dist/controlPlane/processing/derive.d.ts +77 -0
  139. package/dist/controlPlane/processing/derive.d.ts.map +1 -0
  140. package/dist/controlPlane/processing/derive.js +206 -0
  141. package/dist/controlPlane/processing/read.d.ts +10 -0
  142. package/dist/controlPlane/processing/read.d.ts.map +1 -0
  143. package/dist/controlPlane/processing/read.js +89 -0
  144. package/dist/controlPlane/scope/kind.d.ts +46 -0
  145. package/dist/controlPlane/scope/kind.d.ts.map +1 -0
  146. package/dist/controlPlane/scope/kind.js +52 -0
  147. package/dist/controlPlane/scope/scope.d.ts +58 -3
  148. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  149. package/dist/controlPlane/scope/scope.js +59 -4
  150. package/dist/controlPlane/token/jws.js +1 -1
  151. package/dist/controlPlane/token/mint.js +1 -1
  152. package/dist/controlPlane/wire.d.ts +23 -0
  153. package/dist/controlPlane/wire.d.ts.map +1 -1
  154. package/dist/controlPlane/wire.js +24 -1
  155. package/dist/createBackend.d.ts +13 -0
  156. package/dist/createBackend.d.ts.map +1 -1
  157. package/dist/createBackend.js +11 -3
  158. package/dist/createEntrypoint.d.ts +7 -0
  159. package/dist/createEntrypoint.d.ts.map +1 -1
  160. package/dist/createEntrypoint.js +4 -0
  161. package/dist/error/http.d.ts +168 -0
  162. package/dist/error/http.d.ts.map +1 -1
  163. package/dist/error/payload.d.ts +336 -0
  164. package/dist/error/payload.d.ts.map +1 -1
  165. package/dist/error/payload.js +112 -0
  166. package/dist/error/pithyError.d.ts +22 -0
  167. package/dist/error/pithyError.d.ts.map +1 -1
  168. package/dist/error/pithyError.js +31 -1
  169. package/dist/http/routeSpec.d.ts +46 -1
  170. package/dist/http/routeSpec.d.ts.map +1 -1
  171. package/dist/http/routeSpec.js +39 -5
  172. package/dist/http/verification.d.ts.map +1 -1
  173. package/dist/http/verification.js +1 -1
  174. package/dist/i18n/translator.d.ts +2 -1
  175. package/dist/i18n/translator.d.ts.map +1 -1
  176. package/dist/index.d.ts +1 -1
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +3 -3
  179. package/dist/logger/record.d.ts +98 -0
  180. package/dist/logger/record.d.ts.map +1 -1
  181. package/dist/mcp/annotations.d.ts +20 -0
  182. package/dist/mcp/annotations.d.ts.map +1 -0
  183. package/dist/mcp/annotations.js +23 -0
  184. package/dist/mcp/tools.d.ts +75 -0
  185. package/dist/mcp/tools.d.ts.map +1 -0
  186. package/dist/mcp/tools.js +118 -0
  187. package/dist/openapi/document.d.ts.map +1 -1
  188. package/dist/openapi/document.js +8 -26
  189. package/dist/openapi/operations.d.ts +35 -0
  190. package/dist/openapi/operations.d.ts.map +1 -0
  191. package/dist/openapi/operations.js +37 -0
  192. package/dist/presence/channel.d.ts +11 -0
  193. package/dist/presence/channel.d.ts.map +1 -0
  194. package/dist/presence/channel.js +65 -0
  195. package/dist/presence/declaration.d.ts +154 -0
  196. package/dist/presence/declaration.d.ts.map +1 -0
  197. package/dist/presence/declaration.js +164 -0
  198. package/dist/presence/durableObject.d.ts +61 -0
  199. package/dist/presence/durableObject.d.ts.map +1 -0
  200. package/dist/presence/durableObject.js +257 -0
  201. package/dist/presence/frames.d.ts +166 -0
  202. package/dist/presence/frames.d.ts.map +1 -0
  203. package/dist/presence/frames.js +90 -0
  204. package/dist/presence/http/guard.d.ts +11 -0
  205. package/dist/presence/http/guard.d.ts.map +1 -0
  206. package/dist/presence/http/guard.js +20 -0
  207. package/dist/presence/http/responses.d.ts +31 -0
  208. package/dist/presence/http/responses.d.ts.map +1 -0
  209. package/dist/presence/http/responses.js +34 -0
  210. package/dist/presence/http/routes.d.ts +24 -0
  211. package/dist/presence/http/routes.d.ts.map +1 -0
  212. package/dist/presence/http/routes.js +127 -0
  213. package/dist/presence/http/schemas.d.ts +29 -0
  214. package/dist/presence/http/schemas.d.ts.map +1 -0
  215. package/dist/presence/http/schemas.js +28 -0
  216. package/dist/presence/names.d.ts +23 -0
  217. package/dist/presence/names.d.ts.map +1 -0
  218. package/dist/presence/names.js +22 -0
  219. package/dist/presence/room.d.ts +95 -0
  220. package/dist/presence/room.d.ts.map +1 -0
  221. package/dist/presence/room.js +105 -0
  222. package/dist/presence/ticket.d.ts +55 -0
  223. package/dist/presence/ticket.d.ts.map +1 -0
  224. package/dist/presence/ticket.js +109 -0
  225. package/dist/privacy/classify.js +1 -1
  226. package/dist/privacy/encrypted.js +1 -1
  227. package/dist/processing/activity.d.ts +134 -0
  228. package/dist/processing/activity.d.ts.map +1 -0
  229. package/dist/processing/activity.js +161 -0
  230. package/dist/processing/composed.d.ts +18 -0
  231. package/dist/processing/composed.d.ts.map +1 -0
  232. package/dist/processing/composed.js +28 -0
  233. package/dist/processing/vocabulary.d.ts +26 -0
  234. package/dist/processing/vocabulary.d.ts.map +1 -0
  235. package/dist/processing/vocabulary.js +35 -0
  236. package/dist/queue/consumer.d.ts +59 -0
  237. package/dist/queue/consumer.d.ts.map +1 -0
  238. package/dist/queue/consumer.js +46 -0
  239. package/dist/retention/composed.d.ts +37 -0
  240. package/dist/retention/composed.d.ts.map +1 -0
  241. package/dist/retention/composed.js +66 -0
  242. package/dist/retention/holds.d.ts +61 -0
  243. package/dist/retention/holds.d.ts.map +1 -0
  244. package/dist/retention/holds.js +107 -0
  245. package/dist/retention/holdsPurge.d.ts +22 -0
  246. package/dist/retention/holdsPurge.d.ts.map +1 -0
  247. package/dist/retention/holdsPurge.js +50 -0
  248. package/dist/retention/policy.d.ts +132 -0
  249. package/dist/retention/policy.d.ts.map +1 -0
  250. package/dist/retention/policy.js +197 -0
  251. package/dist/session/rotated.d.ts +34 -0
  252. package/dist/session/rotated.d.ts.map +1 -0
  253. package/dist/session/rotated.js +25 -0
  254. package/dist/test-utils/connectionFixtures.d.ts +13 -0
  255. package/dist/test-utils/connectionFixtures.d.ts.map +1 -0
  256. package/dist/test-utils/connectionFixtures.js +58 -0
  257. package/dist/test-utils/interleave.d.ts +12 -0
  258. package/dist/test-utils/interleave.d.ts.map +1 -0
  259. package/dist/test-utils/interleave.js +26 -0
  260. package/dist/testWorker.d.ts +8 -0
  261. package/dist/testWorker.d.ts.map +1 -0
  262. package/dist/testWorker.js +9 -0
  263. package/dist/version.generated.d.ts +1 -1
  264. package/dist/version.generated.js +1 -1
  265. package/dist/workflow/dispatchRoute.js +1 -1
  266. package/package.json +1 -1
  267. package/src/audit/auditEvent.ts +1 -1
  268. package/src/audit/chain.ts +551 -0
  269. package/src/audit/citations.ts +63 -0
  270. package/src/capability/bindings.ts +61 -0
  271. package/src/capability/capability.ts +156 -19
  272. package/src/capability/erasure.ts +82 -0
  273. package/src/capability/personColumns.ts +83 -0
  274. package/src/capability/personRows.ts +125 -0
  275. package/src/capability/purge.ts +158 -21
  276. package/src/capability/settings.ts +7 -0
  277. package/src/capability/vectorIndex.ts +60 -0
  278. package/src/cli/commandCatalog.generated.ts +487 -2
  279. package/src/cli/commandFacts.ts +12 -3
  280. package/src/cloudflare-test.d.ts +3 -1
  281. package/src/consent/purpose.ts +160 -0
  282. package/src/consent/reader.ts +134 -0
  283. package/src/controlPlane/audit/actions.ts +15 -4
  284. package/src/controlPlane/capability.ts +99 -20
  285. package/src/controlPlane/context.ts +10 -2
  286. package/src/controlPlane/data/connection.ts +16 -2
  287. package/src/controlPlane/data/connections.ts +231 -0
  288. package/src/controlPlane/data/hold.ts +52 -0
  289. package/src/controlPlane/data/oidcTrust.ts +249 -0
  290. package/src/controlPlane/data/tables.ts +7 -0
  291. package/src/controlPlane/data/trustFirstCall.ts +37 -0
  292. package/src/controlPlane/discovery/adminRoute.ts +71 -9
  293. package/src/controlPlane/discovery/routeInput.ts +86 -0
  294. package/src/controlPlane/discovery/toolName.ts +23 -0
  295. package/src/controlPlane/erasure/attest.ts +205 -0
  296. package/src/controlPlane/erasure/read.ts +169 -0
  297. package/src/controlPlane/error/errors.ts +69 -2
  298. package/src/controlPlane/evidence/derive.ts +426 -0
  299. package/src/controlPlane/evidence/mapping.ts +118 -0
  300. package/src/controlPlane/evidence/pack.ts +190 -0
  301. package/src/controlPlane/http/connections.ts +129 -0
  302. package/src/controlPlane/http/guard.ts +118 -9
  303. package/src/controlPlane/http/handlers.ts +172 -40
  304. package/src/controlPlane/http/holds.ts +108 -0
  305. package/src/controlPlane/http/presence.ts +71 -0
  306. package/src/controlPlane/http/processing.ts +19 -0
  307. package/src/controlPlane/http/responses.ts +590 -7
  308. package/src/controlPlane/http/routes.ts +302 -3
  309. package/src/controlPlane/http/schemas.ts +126 -0
  310. package/src/controlPlane/http/verify.ts +19 -2
  311. package/src/controlPlane/http/verifyTrust.ts +176 -0
  312. package/src/controlPlane/migrations/0004_connection_trusts.ts +30 -0
  313. package/src/controlPlane/migrations/0005_holds.ts +40 -0
  314. package/src/controlPlane/processing/derive.ts +359 -0
  315. package/src/controlPlane/processing/read.ts +102 -0
  316. package/src/controlPlane/scope/kind.ts +63 -0
  317. package/src/controlPlane/scope/scope.ts +65 -3
  318. package/src/controlPlane/wire.ts +26 -0
  319. package/src/createBackend.ts +31 -6
  320. package/src/createEntrypoint.ts +17 -0
  321. package/src/error/payload.ts +234 -0
  322. package/src/error/pithyError.ts +42 -0
  323. package/src/http/routeSpec.ts +75 -5
  324. package/src/http/verification.ts +3 -1
  325. package/src/i18n/translator.ts +2 -1
  326. package/src/index.ts +2 -0
  327. package/src/mcp/annotations.ts +38 -0
  328. package/src/mcp/tools.ts +220 -0
  329. package/src/openapi/document.ts +8 -28
  330. package/src/openapi/operations.ts +62 -0
  331. package/src/presence/channel.ts +46 -0
  332. package/src/presence/declaration.ts +283 -0
  333. package/src/presence/durableObject.ts +287 -0
  334. package/src/presence/frames.ts +153 -0
  335. package/src/presence/http/guard.ts +23 -0
  336. package/src/presence/http/responses.ts +63 -0
  337. package/src/presence/http/routes.ts +165 -0
  338. package/src/presence/http/schemas.ts +39 -0
  339. package/src/presence/names.ts +45 -0
  340. package/src/presence/room.ts +145 -0
  341. package/src/presence/ticket.ts +145 -0
  342. package/src/processing/activity.ts +289 -0
  343. package/src/processing/composed.ts +33 -0
  344. package/src/processing/vocabulary.ts +43 -0
  345. package/src/queue/consumer.ts +104 -0
  346. package/src/retention/composed.ts +75 -0
  347. package/src/retention/holds.ts +175 -0
  348. package/src/retention/holdsPurge.ts +50 -0
  349. package/src/retention/policy.ts +305 -0
  350. package/src/session/rotated.ts +53 -0
  351. package/src/test-utils/connectionFixtures.ts +58 -0
  352. package/src/test-utils/interleave.ts +31 -0
  353. package/src/testWorker.ts +13 -0
  354. package/src/version.generated.ts +1 -1
@@ -0,0 +1,62 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { z } from "zod";
4
+ import { type ErasingPurgeContribution, type PurgeContext } from "./purge";
5
+ /**
6
+ * The purge contribution most capabilities need, written once (#814): rows in D1 that name a person by a column,
7
+ * exported on an access request and deleted by a purge.
8
+ *
9
+ * `@pithy-sh/rating`'s ratings, `@pithy-sh/leaderboard`'s entries, `@pithy-sh/matchmaking`'s friendships — each is
10
+ * a table whose rows carry the person's id in a column, and nothing else to do with them than read them out and
11
+ * delete them. A capability with more to do — bytes in R2, an address rather than an id, a row that must be
12
+ * anonymized — writes its own contribution with the same types.
13
+ *
14
+ * **One transaction.** The deletes are one `d1.batch()`, in the order the tables are listed, so a failure leaves
15
+ * every row where it was and the retried step erases the lot. **Idempotent**: a second run deletes nothing.
16
+ */
17
+ /** One table a person's rows are in, and how they are found and exported. */
18
+ export interface PersonTable {
19
+ /** The table's camelCase name, as the capability's tables map keys it. */
20
+ readonly table: string;
21
+ /** Its row schema. Rows are decoded through it, so the export carries the app shape and never SQLite's. */
22
+ readonly schema: z.ZodObject;
23
+ /** The columns that name the person. A row is theirs when any of them is the subject's id. */
24
+ readonly columns: readonly string[];
25
+ /**
26
+ * Columns a row must also hold exactly — `{ subjectType: "user" }`, where one id column names people and
27
+ * organizations both and only a person's rows are theirs.
28
+ */
29
+ readonly match?: Readonly<Record<string, string>>;
30
+ /** The key the rows are exported under — `ratings`, `entries`. */
31
+ readonly as: string;
32
+ /** A column to read them in, so an export reads the same twice. */
33
+ readonly orderBy?: string;
34
+ /** Columns the export leaves out — a credential, or another person's data the row carries beside theirs. */
35
+ readonly omit?: readonly string[];
36
+ /**
37
+ * A per-row redaction, for a credential that sits in a column only some rows hold it in — a Google Play purchase
38
+ * token keys a purchase row on that rail alone. It is handed the decoded row, before `omit` drops any column, and
39
+ * returns the row to export.
40
+ */
41
+ readonly redact?: (row: Record<string, unknown>) => Record<string, unknown>;
42
+ }
43
+ /** What {@link personRowsPurge} is built from. */
44
+ export interface PersonRowsPurgeInput {
45
+ /** The capability, for the fault a missing binding raises. */
46
+ readonly capability: string;
47
+ /** The one sentence the purge records with this step. */
48
+ readonly summary: string;
49
+ /** The D1 binding the tables live in. */
50
+ readonly database?: string;
51
+ /** The tables, in the order a purge deletes from them — children before the rows they reference. */
52
+ readonly tables: readonly PersonTable[];
53
+ }
54
+ /**
55
+ * The export half alone: every row naming the person in `tables`, decoded, under each table's key. For a
56
+ * contribution that **retains** its rows (#814) — `@pithy-sh/payments` keeps a purchase for tax law, and still
57
+ * answers for it.
58
+ */
59
+ export declare function personRowsExport(input: Omit<PersonRowsPurgeInput, "summary">): (context: PurgeContext) => Promise<Record<string, unknown>>;
60
+ /** Build a user contribution that exports, then on a purge deletes, every row naming the person in `tables`. */
61
+ export declare function personRowsPurge(input: PersonRowsPurgeInput): ErasingPurgeContribution;
62
+ //# sourceMappingURL=personRows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"personRows.d.ts","sourceRoot":"","sources":["../../src/capability/personRows.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAAE,KAAK,wBAAwB,EAAc,KAAK,YAAY,EAAgB,MAAM,SAAS,CAAC;AAErG;;;;;;;;;;;GAWG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2GAA2G;IAC3G,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IAC7B,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,kEAAkE;IAClE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,4GAA4G;IAC5G,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7E;AAED,kDAAkD;AAClD,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;CACzC;AAiBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,GAC3C,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAmB7D;AAED,gHAAgH;AAChH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,wBAAwB,CAmBrF"}
@@ -0,0 +1,59 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { exportRows, purgeBinding } from "./purge.js";
4
+ import { createDatabase } from "../data/db.js";
5
+ import { sql } from "kysely";
6
+ //#region src/capability/personRows.ts
7
+ /** `(column = id OR column = id …) AND match…` over a table's person columns. */
8
+ function theirs({ columns, match }, id) {
9
+ return (eb) => eb.and([eb.or(columns.map((column) => eb(sql.ref(column), "=", id))), ...Object.entries(match ?? {}).map(([column, value]) => eb(sql.ref(column), "=", value))]);
10
+ }
11
+ /**
12
+ * The export half alone: every row naming the person in `tables`, decoded, under each table's key. For a
13
+ * contribution that **retains** its rows (#814) — `@pithy-sh/payments` keeps a purchase for tax law, and still
14
+ * answers for it.
15
+ */
16
+ function personRowsExport(input) {
17
+ const database = input.database ?? "DB";
18
+ return async ({ subject, env }) => {
19
+ const db = createDatabase(purgeBinding(env, database, input.capability), {});
20
+ const read = await Promise.all(input.tables.map(async (table) => {
21
+ let query = db.selectFrom(table.table).selectAll().where(theirs(table, subject.id));
22
+ if (table.orderBy) query = query.orderBy(sql.ref(table.orderBy));
23
+ const rows = (await query.execute()).map((row) => {
24
+ const parsed = table.schema.parse(row);
25
+ const decoded = table.redact ? table.redact(parsed) : parsed;
26
+ for (const column of table.omit ?? []) delete decoded[column];
27
+ return decoded;
28
+ });
29
+ return [table.as, exportRows(rows)];
30
+ }));
31
+ return Object.fromEntries(read);
32
+ };
33
+ }
34
+ /** Build a user contribution that exports, then on a purge deletes, every row naming the person in `tables`. */
35
+ function personRowsPurge(input) {
36
+ const database = input.database ?? "DB";
37
+ const open = (env) => {
38
+ const d1 = purgeBinding(env, database, input.capability);
39
+ return {
40
+ d1,
41
+ db: createDatabase(d1, {})
42
+ };
43
+ };
44
+ return {
45
+ subject: "user",
46
+ summary: input.summary,
47
+ erase: async ({ subject, env }) => {
48
+ const { d1, db } = open(env);
49
+ const statements = input.tables.map((table) => {
50
+ const compiled = db.deleteFrom(table.table).where(theirs(table, subject.id)).compile();
51
+ return d1.prepare(compiled.sql).bind(...compiled.parameters);
52
+ });
53
+ if (statements.length > 0) await d1.batch(statements);
54
+ },
55
+ export: personRowsExport(input)
56
+ };
57
+ }
58
+ //#endregion
59
+ export { personRowsExport, personRowsPurge };
@@ -28,6 +28,13 @@ import { z } from "zod";
28
28
  * contribution with no `export` is listed as holding rows it did not say how to export, which is the honest
29
29
  * answer and the one an operator can act on.
30
30
  *
31
+ * ## Or it keeps them, and says why (#814)
32
+ *
33
+ * Not every row about a person should go. A payment record is kept for tax law, a suppression has to outlive the
34
+ * person or the unsubscribe stops holding, and the audit trail is the record of who acted. So a contribution has a
35
+ * third disposition beside erase and anonymize: `retain`, with a one-sentence basis written for the person. The
36
+ * purge records that step as retained and erases nothing of it; the export carries the rows and the basis.
37
+ *
31
38
  * ## An adopter declares one too
32
39
  *
33
40
  * On their own `app` capability, for their own tables, with the same types. The contract is exported for
@@ -52,30 +59,68 @@ export interface PurgeContext {
52
59
  };
53
60
  /** The Worker's bindings for this run, from which the contribution opens its own database. */
54
61
  readonly env: Record<string, unknown>;
62
+ /**
63
+ * The subject's email address, read on demand from the capability that owns the subject — `null` when it has
64
+ * none, or no longer has one (#814). For a capability that holds rows by address rather than by id: a tester's
65
+ * membership, a suppression, an invitation.
66
+ *
67
+ * **A function, never a value**, so the address is read inside the step that needs it and is never carried in
68
+ * a Workflow's journal, where a purge would otherwise leave it behind. Absent where nobody can say, and a
69
+ * contribution that needs it then finds nothing by address.
70
+ */
71
+ readonly address?: () => Promise<string | null>;
55
72
  }
56
- /** One capability's answer to "how are this subject's rows erased". */
57
- export interface PurgeContribution {
73
+ /** What every contribution says, whichever its disposition. */
74
+ interface PurgeContributionBase {
58
75
  /** The kind of subject this erases. */
59
76
  readonly subject: PurgeSubject;
60
77
  /**
61
- * One sentence on what is erased or anonymized — `Deletes the user's uploads and their stored bytes.`
78
+ * One sentence on what is erased, anonymized or kept — `Deletes the user's uploads and their stored bytes.`
62
79
  * Recorded with the step that ran it, so the trail says what a purge did and not only that it ran.
63
80
  */
64
81
  readonly summary: string;
82
+ /**
83
+ * What this capability holds about the subject, as one JSON object — the read side of `erase` (#622). It
84
+ * answers an access request, so it carries the subject's own data and never a credential: no session token,
85
+ * no provider token, no push token. Optional on an erasing contribution: one without it is listed in the
86
+ * export as rows it erases and did not say how to export. A throw costs this capability's entry, never the
87
+ * document.
88
+ */
89
+ readonly export?: (context: PurgeContext) => Promise<Record<string, unknown>>;
90
+ }
91
+ /** A contribution that erases or anonymizes the subject's rows. */
92
+ export interface ErasingPurgeContribution extends PurgeContributionBase {
65
93
  /**
66
94
  * Erase or anonymize this subject's rows. **Idempotent**: run again after it succeeded, or after a failure
67
95
  * that came once the rows were gone, it must succeed having found nothing to do. Throwing fails this step
68
96
  * and only this step.
69
97
  */
70
98
  readonly erase: (context: PurgeContext) => Promise<void>;
71
- /**
72
- * What this capability holds about the subject, as one JSON object — the read side of `erase` (#622). It
73
- * answers an access request, so it carries the subject's own data and never a credential: no session token,
74
- * no provider token, no push token. Optional: a contribution without one is listed in the export as rows it
75
- * erases and did not say how to export. A throw costs this capability's entry, never the document.
76
- */
77
- readonly export?: (context: PurgeContext) => Promise<Record<string, unknown>>;
99
+ /** Never set on an erasing contribution. */
100
+ readonly retain?: undefined;
101
+ }
102
+ /**
103
+ * A contribution that **keeps** the subject's rows through a purge, and says why (#814). A payment record kept
104
+ * for tax law, a suppression that must outlive the person or the unsubscribe stops holding, the audit trail of
105
+ * who acted. The purge records the step as retained and never erases a row of it; the export carries the rows
106
+ * and the basis, because rows that are kept are rows that are answered for.
107
+ */
108
+ export interface RetainingPurgeContribution extends PurgeContributionBase {
109
+ /** Why the rows are kept, in one sentence written for the person — `Kept for tax records.` */
110
+ readonly retain: {
111
+ readonly basis: string;
112
+ };
113
+ /** Required: what is kept is exported. */
114
+ readonly export: (context: PurgeContext) => Promise<Record<string, unknown>>;
115
+ /** Never set on a retaining contribution. */
116
+ readonly erase?: undefined;
78
117
  }
118
+ /** One capability's answer to "what happens to this subject's rows": erased, anonymized, or kept and why. */
119
+ export type PurgeContribution = ErasingPurgeContribution | RetainingPurgeContribution;
120
+ /** What a purge does with a contribution's rows: `erase` (or anonymize), or `retain`. */
121
+ export type PurgeDisposition = "erase" | "retain";
122
+ /** Whether a contribution keeps the subject's rows rather than erasing them. */
123
+ export declare function isRetained(contribution: PurgeContribution): contribution is RetainingPurgeContribution;
79
124
  /** One step of a purge: which capability it runs, and the contribution that capability declared. */
80
125
  export interface PurgeStep {
81
126
  /** The step's name — the capability's, so a retried step names who failed. */
@@ -94,21 +139,24 @@ export declare function purgeSteps(capabilities: readonly {
94
139
  readonly name: string;
95
140
  readonly purge?: readonly PurgeContribution[];
96
141
  }[], subject: PurgeSubject): PurgeStep[];
97
- /** What one step's export answered: its data, that it declared none, or that it could not be read. */
98
- export type PurgeExportEntry = {
142
+ /** What every export entry says: the capability, its sentence, and what a purge does with its rows. */
143
+ interface PurgeExportEntryBase {
99
144
  readonly capability: string;
100
145
  readonly summary: string;
146
+ /** `erase` — a purge erases or anonymizes these rows — or `retain`, a purge keeps them (#814). */
147
+ readonly disposition: PurgeDisposition;
148
+ /** For `retain`, why the rows are kept, in the sentence the contribution wrote for the person. */
149
+ readonly basis?: string;
150
+ }
151
+ /** What one step's export answered: its data, that it declared none, or that it could not be read. */
152
+ export type PurgeExportEntry = (PurgeExportEntryBase & {
101
153
  readonly state: "exported";
102
154
  readonly data: Record<string, unknown>;
103
- } | {
104
- readonly capability: string;
105
- readonly summary: string;
155
+ }) | (PurgeExportEntryBase & {
106
156
  readonly state: "not_exported";
107
- } | {
108
- readonly capability: string;
109
- readonly summary: string;
157
+ }) | (PurgeExportEntryBase & {
110
158
  readonly state: "unavailable";
111
- };
159
+ });
112
160
  /**
113
161
  * Read every step's export for one subject — the export document's body, in step order (#622).
114
162
  *
@@ -119,4 +167,23 @@ export type PurgeExportEntry = {
119
167
  * document promises an object per capability and a client validates it.
120
168
  */
121
169
  export declare function purgeExports(steps: readonly PurgeStep[], context: PurgeContext): Promise<PurgeExportEntry[]>;
170
+ /**
171
+ * One binding a contribution opens off a run's env — or a wiring fault naming it and the capability (#814). The
172
+ * purge and the export both run in the adopter's Worker, so a missing binding is that Worker's wiring, and the
173
+ * fault says which binding to add rather than failing deep inside a query.
174
+ */
175
+ export declare function purgeBinding<T>(env: Record<string, unknown>, name: string, capability: string, kind?: string): T;
176
+ /**
177
+ * Rows as an export carries them (#814): plain JSON, a `Date` as its ISO-8601 string — the shape the export
178
+ * document crosses the wire in, so what a contribution answers is what a reader parses, and a test compares
179
+ * exactly that.
180
+ */
181
+ export declare function exportRows<T>(rows: readonly T[]): Record<string, unknown>[];
182
+ /**
183
+ * Every path in an export's data whose field name holds a credential — empty for an export that carries none.
184
+ * Each capability's export test asserts it empty, so a column added later with a token in it fails the build
185
+ * rather than reaching an access request (#814).
186
+ */
187
+ export declare function credentialFields(value: unknown, path?: string): string[];
188
+ export {};
122
189
  //# sourceMappingURL=purge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../../src/capability/purge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;EAItB,CAAC;AACJ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,8FAA8F;IAC9F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC/E;AAED,oGAAoG;AACpG,MAAM,WAAW,SAAS;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,YAAY,EAAE,iBAAiB,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI,CAoC9G;AAED,6GAA6G;AAC7G,wBAAgB,UAAU,CACxB,YAAY,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAAE,EAAE,EACjG,OAAO,EAAE,YAAY,GACpB,SAAS,EAAE,CAOb;AAED,sGAAsG;AACtG,MAAM,MAAM,gBAAgB,GACxB;IACE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC,GACD;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;CAAE,GACzF;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAO7F;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAclH"}
1
+ {"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../../src/capability/purge.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
- import { ValidationError } from "../error/pithyError.js";
3
+ import { InternalError, ValidationError } from "../error/pithyError.js";
4
4
  import { z } from "zod";
5
5
  //#region src/capability/purge.ts
6
6
  /**
@@ -30,6 +30,13 @@ import { z } from "zod";
30
30
  * contribution with no `export` is listed as holding rows it did not say how to export, which is the honest
31
31
  * answer and the one an operator can act on.
32
32
  *
33
+ * ## Or it keeps them, and says why (#814)
34
+ *
35
+ * Not every row about a person should go. A payment record is kept for tax law, a suppression has to outlive the
36
+ * person or the unsubscribe stops holding, and the audit trail is the record of who acted. So a contribution has a
37
+ * third disposition beside erase and anonymize: `retain`, with a one-sentence basis written for the person. The
38
+ * purge records that step as retained and erases nothing of it; the export carries the rows and the basis.
39
+ *
33
40
  * ## An adopter declares one too
34
41
  *
35
42
  * On their own `app` capability, for their own tables, with the same types. The contract is exported for
@@ -41,6 +48,10 @@ import { z } from "zod";
41
48
  * holding rows scoped to a tenant declares one for it, and the organization purge runs it.
42
49
  */
43
50
  const PurgeSubject = z.enum(["user", "organization"]).describe("The kind of subject a purge erases. `user` is one person's account in `@pithy-sh/auth`, identified by its user id. `organization` is one tenant in `@pithy-sh/organization`, identified by its organization id.");
51
+ /** Whether a contribution keeps the subject's rows rather than erasing them. */
52
+ function isRetained(contribution) {
53
+ return contribution.retain !== void 0;
54
+ }
44
55
  /**
45
56
  * Refuse a capability's purge contributions that no purge could run as declared: an unknown subject, two for
46
57
  * one subject, or one with nothing to record. Called by `defineCapability`, so the refusal names the capability
@@ -67,12 +78,32 @@ function assertPurgeContributions(capability, contributions) {
67
78
  message: `The ${capability} capability's ${subject.data} purge has an export that is not a function.`,
68
79
  action: "Give it an `export` that answers what this capability holds about the subject, or leave it out."
69
80
  });
81
+ if (contribution.retain !== void 0) {
82
+ assertRetention(capability, subject.data, contribution);
83
+ continue;
84
+ }
70
85
  if (typeof contribution.erase !== "function") throw new ValidationError({
71
86
  message: `The ${capability} capability's ${subject.data} purge has no erase function.`,
72
- action: "Give it an `erase` that removes or anonymizes the subject's rows."
87
+ action: "Give it an `erase` that removes or anonymizes the subject's rows, or a `retain` that says why it keeps them."
73
88
  });
74
89
  }
75
90
  }
91
+ /** Refuse a retaining contribution the person could not be answered with: no basis, no export, or an erase too. */
92
+ function assertRetention(capability, subject, contribution) {
93
+ const basis = contribution.retain?.basis;
94
+ if (typeof basis !== "string" || basis.trim() === "") throw new ValidationError({
95
+ message: `The ${capability} capability's ${subject} purge retains the rows with no basis.`,
96
+ action: "Say in one sentence, written for the person, why they are kept — `Kept for tax records.`"
97
+ });
98
+ if (typeof contribution.export !== "function") throw new ValidationError({
99
+ message: `The ${capability} capability's ${subject} purge retains the rows and has no export.`,
100
+ action: "Give it an `export`: rows that are kept are rows an access request is answered with."
101
+ });
102
+ if (contribution.erase !== void 0) throw new ValidationError({
103
+ message: `The ${capability} capability's ${subject} purge both retains and erases the rows.`,
104
+ action: "Declare one disposition: `retain` with its basis, or `erase`."
105
+ });
106
+ }
76
107
  /** The steps a purge of `subject` runs, in composition order — libraries first, the adopter's `app` last. */
77
108
  function purgeSteps(capabilities, subject) {
78
109
  const steps = [];
@@ -100,9 +131,15 @@ function isRecord(value) {
100
131
  */
101
132
  async function purgeExports(steps, context) {
102
133
  return Promise.all(steps.map(async ({ step, contribution }) => {
103
- const base = {
134
+ const base = isRetained(contribution) ? {
104
135
  capability: step,
105
- summary: contribution.summary
136
+ summary: contribution.summary,
137
+ disposition: "retain",
138
+ basis: contribution.retain.basis
139
+ } : {
140
+ capability: step,
141
+ summary: contribution.summary,
142
+ disposition: "erase"
106
143
  };
107
144
  const read = contribution.export;
108
145
  if (read === void 0) return {
@@ -127,5 +164,46 @@ async function purgeExports(steps, context) {
127
164
  }
128
165
  }));
129
166
  }
167
+ /**
168
+ * One binding a contribution opens off a run's env — or a wiring fault naming it and the capability (#814). The
169
+ * purge and the export both run in the adopter's Worker, so a missing binding is that Worker's wiring, and the
170
+ * fault says which binding to add rather than failing deep inside a query.
171
+ */
172
+ function purgeBinding(env, name, capability, kind = "D1 database") {
173
+ const binding = env[name];
174
+ if (typeof binding !== "object" || binding === null) throw new InternalError({
175
+ message: `The ${capability} capability's ${kind} is not bound, so its purge step could not run.`,
176
+ action: `Bind the ${kind} named ${name} to the Worker that runs the purge.`,
177
+ detail: `${capability} purge contribution found no ${name} binding on the env`
178
+ });
179
+ return binding;
180
+ }
181
+ /**
182
+ * Rows as an export carries them (#814): plain JSON, a `Date` as its ISO-8601 string — the shape the export
183
+ * document crosses the wire in, so what a contribution answers is what a reader parses, and a test compares
184
+ * exactly that.
185
+ */
186
+ function exportRows(rows) {
187
+ return JSON.parse(JSON.stringify(rows));
188
+ }
189
+ /**
190
+ * A field name that holds a credential — a token, a secret, a password, a digest of one, a sealed value. What an
191
+ * export never carries (#814): an access request is answered with what is held, never with the means to act as
192
+ * the person or to reach what they shared.
193
+ */
194
+ const CREDENTIAL_FIELD = /secret|token|password|credential|digest|private_?key|encrypted/i;
195
+ /**
196
+ * Every path in an export's data whose field name holds a credential — empty for an export that carries none.
197
+ * Each capability's export test asserts it empty, so a column added later with a token in it fails the build
198
+ * rather than reaching an access request (#814).
199
+ */
200
+ function credentialFields(value, path = "") {
201
+ if (Array.isArray(value)) return value.flatMap((item, index) => credentialFields(item, `${path}[${index}]`));
202
+ if (typeof value !== "object" || value === null) return [];
203
+ return Object.entries(value).flatMap(([key, inner]) => {
204
+ const here = path ? `${path}.${key}` : key;
205
+ return [...CREDENTIAL_FIELD.test(key) ? [here] : [], ...credentialFields(inner, here)];
206
+ });
207
+ }
130
208
  //#endregion
131
- export { PurgeSubject, assertPurgeContributions, purgeExports, purgeSteps };
209
+ export { PurgeSubject, assertPurgeContributions, credentialFields, exportRows, isRetained, purgeBinding, purgeExports, purgeSteps };
@@ -142,6 +142,16 @@ export interface SettingsAccountReader {
142
142
  * Asked by secrets of the manager token the runtime token superseded, which nothing revokes for you.
143
143
  */
144
144
  accountToken(name: string): Promise<boolean>;
145
+ /**
146
+ * **Whether an R2 bucket's objects under `prefix` are under an indefinite lock (#822)** — `locked`, `unlocked`, or
147
+ * `missing` when no bucket of that name exists on the account. Asked by audit, whose checkpoints are evidence only
148
+ * while nothing can replace them. Optional, so a reader built before the question existed still compiles; a
149
+ * check asking a reader without it reports the environment unanswered rather than guessing.
150
+ */
151
+ r2BucketLock?(request: {
152
+ bucket: string;
153
+ prefix: string;
154
+ }): Promise<"locked" | "unlocked" | "missing">;
145
155
  }
146
156
  /** What {@link SettingsAccountReader.appRows} is asked. */
147
157
  export interface AppRowsRequest {
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/capability/settings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/capability/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * A Vectorize index a capability owns and re-embeds itself — the contribution `pithy vector reprocess` reads.
5
+ *
6
+ * ## Why a contribution
7
+ *
8
+ * `@pithy-sh/vector` re-embeds the indexes an adopter declares in `vector({ indexes })`. A capability that
9
+ * builds an index of its own — `@pithy-sh/support`'s semantic message index — uses vector's public modules to
10
+ * embed and to re-embed, and runs vector's reprocess orchestration in a Workflow it hosts, over its own corpus.
11
+ * What it cannot do is teach vector's CLI that the index exists: capabilities depend on core seams, never on
12
+ * each other. So it says so here, on its own `Capability`, the way it states its migrations and its purge, and
13
+ * `pithy vector reprocess` reads every composed declaration — in a project that never composes `vector()` too.
14
+ *
15
+ * ## What the CLI does with one
16
+ *
17
+ * It starts `reprocessJob`, one of this capability's own `workflows`, under the deployed name every Workflow
18
+ * gets — `<project>-<env>-<capability>-<job>` — with the parameters every reprocess takes: `{ index, all }`.
19
+ * A model change, a repair and a full rebuild (`--all`) are then the same three cases they are for any index.
20
+ */
21
+ export interface VectorIndexContribution {
22
+ /**
23
+ * The index's name as `pithy vector reprocess --index` takes it — `support-messages`. Unique across the
24
+ * project, and never one of the adopter's own `vector()` index keys.
25
+ */
26
+ readonly name: string;
27
+ /** The job, in this capability's `workflows`, that re-embeds the index. */
28
+ readonly reprocessJob: string;
29
+ }
30
+ /**
31
+ * Refuse a declaration no reprocess could run: a duplicate name, or a job the capability does not register.
32
+ * Called by `defineCapability`, so the mistake is named where it is written rather than on the day somebody
33
+ * changes a model.
34
+ */
35
+ export declare function assertVectorIndexContributions(capability: string, indexes: readonly VectorIndexContribution[], jobs: readonly string[]): void;
36
+ //# sourceMappingURL=vectorIndex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectorIndex.d.ts","sourceRoot":"","sources":["../../src/capability/vectorIndex.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,uBAAuB,EAAE,EAC3C,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,IAAI,CAiBN"}
@@ -0,0 +1,25 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { InternalError } from "../error/pithyError.js";
4
+ //#region src/capability/vectorIndex.ts
5
+ /**
6
+ * Refuse a declaration no reprocess could run: a duplicate name, or a job the capability does not register.
7
+ * Called by `defineCapability`, so the mistake is named where it is written rather than on the day somebody
8
+ * changes a model.
9
+ */
10
+ function assertVectorIndexContributions(capability, indexes, jobs) {
11
+ const seen = /* @__PURE__ */ new Set();
12
+ for (const index of indexes) {
13
+ if (seen.has(index.name)) throw new InternalError({
14
+ message: `The ${capability} capability declares the vector index \`${index.name}\` twice.`,
15
+ detail: `vectorIndexes on ${capability} repeats ${index.name}`
16
+ });
17
+ seen.add(index.name);
18
+ if (!jobs.includes(index.reprocessJob)) throw new InternalError({
19
+ message: `The ${capability} capability's vector index \`${index.name}\` names a reprocess job it does not register.`,
20
+ detail: `vectorIndexes on ${capability}: no workflow job ${index.reprocessJob}; registered: ${jobs.join(", ") || "none"}`
21
+ });
22
+ }
23
+ }
24
+ //#endregion
25
+ export { assertVectorIndexContributions };