@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,164 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ //#region src/controlPlane/erasure/attest.ts
4
+ /**
5
+ * **The erasure attestation, assembled (#823)** — pure functions over a subject's lifecycle rows and the audit events
6
+ * that name it, so every rule here is tested without a database.
7
+ *
8
+ * ## Which deletion
9
+ *
10
+ * A subject can be deleted, restored, and deleted again. The attestation is about the **deletion in force**: the
11
+ * first `deleted` row after the last `restored`. A subject whose last act was a restore has no erasure to attest.
12
+ * A hard deletion that brought a soft one forward writes a second `deleted` row in the same cycle, so the deletion
13
+ * reads as `hard` and comes due when the later row says.
14
+ *
15
+ * ## Where the purge stands
16
+ *
17
+ * `complete` once a `purged` row is written. `failed` when the last thing the purge wrote was its failure — a later
18
+ * pass that runs a step again puts it back `in_progress`. `scheduled` while nothing has run and the window is still
19
+ * open. `in_progress` otherwise. **Overdue** is not a state: a purge not complete two days after it came due has
20
+ * missed a daily pass, whatever it last wrote, and is named with the step it stopped at.
21
+ *
22
+ * ## A step run twice
23
+ *
24
+ * The daily pass is the purge's outer retry, so a failed purge's steps run again the next day and record again.
25
+ * Each capability is shown once, in the order it first ran, as it last finished.
26
+ */
27
+ /** How long after coming due a purge that has not completed is overdue: two daily passes. */
28
+ const ERASURE_OVERDUE_AFTER_MS = 1728e5;
29
+ /** The step a purge that has run nothing is named as having stopped at. */
30
+ const ERASURE_NOT_STARTED = "due";
31
+ /** The rows of the deletion in force, or `null` when there is none — never deleted, or restored since. */
32
+ function deletionInForce(rows) {
33
+ let start = -1;
34
+ for (let index = 0; index < rows.length; index += 1) {
35
+ const row = rows[index];
36
+ if (row?.action === "restored") start = -1;
37
+ else if (row?.action === "deleted" && start === -1) start = index;
38
+ }
39
+ return start === -1 ? null : rows.slice(start);
40
+ }
41
+ /** A citation as the wire carries it. */
42
+ function cited(citation) {
43
+ if (!citation) return null;
44
+ return {
45
+ eventId: citation.eventId,
46
+ action: citation.action,
47
+ occurredAt: citation.occurredAt.toISOString(),
48
+ tenant: citation.tenant,
49
+ seq: citation.seq,
50
+ hash: citation.hash
51
+ };
52
+ }
53
+ /** The newest citation matching, or undefined. */
54
+ function latest(citations, match) {
55
+ let found;
56
+ for (const citation of citations) if (match(citation) && (!found || citation.occurredAt.getTime() >= found.occurredAt.getTime())) found = citation;
57
+ return found;
58
+ }
59
+ /** One subject's attestation, or `null` when it has no deletion in force. */
60
+ function attest(input) {
61
+ const cycle = deletionInForce(input.rows);
62
+ const deletion = cycle?.[0];
63
+ if (!cycle || !deletion) return null;
64
+ const deletions = cycle.filter((row) => row.action === "deleted");
65
+ const mode = deletions.some((row) => row.mode === "hard") ? "hard" : deletion.mode ?? null;
66
+ const purgeAfter = [...deletions].reverse().find((row) => row.purgeAfter)?.purgeAfter ?? null;
67
+ const citations = (input.citations ?? []).filter((each) => each.occurredAt.getTime() >= deletion.at.getTime());
68
+ const steps = /* @__PURE__ */ new Map();
69
+ let activity;
70
+ let purged;
71
+ for (const row of cycle) if (row.action === "purge_step" || row.action === "purge_retained") {
72
+ steps.set(row.step ?? "", row);
73
+ activity = row;
74
+ } else if (row.action === "purge_failed") activity = row;
75
+ else if (row.action === "purged") purged = row;
76
+ const state = stateOf({
77
+ purged,
78
+ activity,
79
+ purgeAfter,
80
+ now: input.now
81
+ });
82
+ const overdue = (state === "in_progress" || state === "failed") && purgeAfter !== null && input.now.getTime() - purgeAfter.getTime() > 1728e5;
83
+ const stoppedAt = state === "failed" || overdue ? activity?.step ?? "due" : null;
84
+ const views = [...steps.values()].map((row) => {
85
+ const capability = row.step ?? "";
86
+ const retained = row.action === "purge_retained";
87
+ return {
88
+ capability,
89
+ disposition: retained ? "retained" : "erased",
90
+ reason: retained ? row.reason : null,
91
+ summary: retained ? null : row.reason,
92
+ completedAt: row.at.toISOString(),
93
+ audit: cited(latest(citations, (each) => each.action === input.actions.step && each.step === capability))
94
+ };
95
+ });
96
+ return {
97
+ subject: {
98
+ type: input.subject,
99
+ id: input.subjectId
100
+ },
101
+ deletion: {
102
+ at: deletion.at.toISOString(),
103
+ actorType: deletion.actorType,
104
+ reason: deletion.reason,
105
+ mode,
106
+ purgeAfter: purgeAfter ? purgeAfter.toISOString() : null,
107
+ audit: cited(citations.find((each) => input.actions.deleted.includes(each.action)))
108
+ },
109
+ state,
110
+ overdue,
111
+ stoppedAt,
112
+ steps: views,
113
+ completedAt: purged ? purged.at.toISOString() : null,
114
+ completion: purged ? cited(latest(citations, (each) => each.action === input.actions.purged)) : null,
115
+ failure: state === "failed" ? cited(latest(citations, (each) => each.action === input.actions.failed)) : null,
116
+ trail: input.citations === null ? "absent" : "recorded"
117
+ };
118
+ }
119
+ /** Where a purge stands, from its last activity. See the file header. */
120
+ function stateOf(input) {
121
+ if (input.purged) return "complete";
122
+ if (input.activity?.action === "purge_failed") return "failed";
123
+ if (!input.activity && (input.purgeAfter === null || input.purgeAfter.getTime() > input.now.getTime())) return "scheduled";
124
+ return "in_progress";
125
+ }
126
+ /** One attestation, summarized for the period read. */
127
+ function periodEntry(attestation) {
128
+ return {
129
+ subject: attestation.subject,
130
+ state: attestation.state,
131
+ overdue: attestation.overdue,
132
+ purgeAfter: attestation.deletion.purgeAfter,
133
+ completedAt: attestation.completedAt,
134
+ stoppedAt: attestation.stoppedAt
135
+ };
136
+ }
137
+ /** The period's counts and the purges that need someone, from its entries. */
138
+ function summarize(entries) {
139
+ const counts = {
140
+ scheduled: 0,
141
+ inProgress: 0,
142
+ complete: 0,
143
+ failed: 0,
144
+ overdue: 0
145
+ };
146
+ for (const entry of entries) {
147
+ if (entry.state === "scheduled") counts.scheduled += 1;
148
+ else if (entry.state === "in_progress") counts.inProgress += 1;
149
+ else if (entry.state === "complete") counts.complete += 1;
150
+ else counts.failed += 1;
151
+ if (entry.overdue) counts.overdue += 1;
152
+ }
153
+ return {
154
+ counts,
155
+ attention: entries.filter((entry) => entry.state === "failed" || entry.overdue)
156
+ };
157
+ }
158
+ /** Every entry, oldest due first; a deletion recorded before #823 has no due instant and sorts last. */
159
+ function byDue(entries) {
160
+ const key = (entry) => entry.purgeAfter ? Date.parse(entry.purgeAfter) : Number.MAX_SAFE_INTEGER;
161
+ return [...entries].sort((a, b) => key(a) - key(b) || a.subject.id.localeCompare(b.subject.id));
162
+ }
163
+ //#endregion
164
+ export { ERASURE_NOT_STARTED, ERASURE_OVERDUE_AFTER_MS, attest, byDue, deletionInForce, periodEntry, summarize };
@@ -0,0 +1,39 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { Capability } from "../../capability/capability";
4
+ import type { ErasureRecords } from "../../capability/erasure";
5
+ import type { PurgeSubject } from "../../capability/purge";
6
+ import type { ControlPlaneErasurePeriodResponse, ControlPlaneErasureSubjectResponse } from "../http/responses";
7
+ import type { ErasurePeriodQuery } from "../http/schemas";
8
+ /**
9
+ * **The two erasure reads (#823), over whatever this Worker composes.**
10
+ *
11
+ * The rows live in the capability that owns each subject — auth for a user, organization for an organization —
12
+ * and are read through the {@link ErasureRecords} each declares. The events live in the audit trail and are read
13
+ * through the citation reader `@pithy-sh/audit` declares. Neither is imported: both are found among the composed
14
+ * capabilities, so a Worker with no organization attests users only and says so, and one with no audit attests from
15
+ * the lifecycle rows and says the trail is absent.
16
+ */
17
+ /** The default window: a quarter. */
18
+ export declare const ERASURE_DEFAULT_WINDOW_MS: number;
19
+ /** The longest window one read covers: a year and a day, so a year's audit is one call. */
20
+ export declare const ERASURE_MAX_WINDOW_MS: number;
21
+ /** What the reads need from the seam. */
22
+ export interface ErasureReadDeps {
23
+ /** Every composed capability. */
24
+ readonly composition: () => readonly Capability[];
25
+ /** The clock. */
26
+ readonly now: () => Date;
27
+ }
28
+ /** The capability recording each subject's purges, first declared wins. */
29
+ export declare function erasureProviders(capabilities: readonly Capability[]): Map<PurgeSubject, ErasureRecords>;
30
+ /** The window a period read covers, defaulted and bounded. */
31
+ export declare function erasureWindow(query: ErasurePeriodQuery, now: Date): {
32
+ since: Date;
33
+ until: Date;
34
+ };
35
+ /** `GET /control-plane/erasure/:subjectType/:subjectId`. */
36
+ export declare function readErasureSubject(env: Record<string, unknown>, deps: ErasureReadDeps, subject: PurgeSubject, subjectId: string): Promise<ControlPlaneErasureSubjectResponse>;
37
+ /** `GET /control-plane/erasure?since&until`. */
38
+ export declare function readErasurePeriod(env: Record<string, unknown>, deps: ErasureReadDeps, query: ErasurePeriodQuery): Promise<ControlPlaneErasurePeriodResponse>;
39
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/erasure/read.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,KAAK,EACV,iCAAiC,EACjC,kCAAkC,EAEnC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D;;;;;;;;GAQG;AAEH,qCAAqC;AACrC,eAAO,MAAM,yBAAyB,QAAkB,CAAC;AAEzD,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,QAAmB,CAAC;AAKtD,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,UAAU,EAAE,CAAC;IAClD,iBAAiB;IACjB,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,SAAS,UAAU,EAAE,GAAG,GAAG,CAAC,YAAY,EAAE,cAAc,CAAC,CAOvG;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,IAAI,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,CAgBhG;AA2BD,4DAA4D;AAC5D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kCAAkC,CAAC,CAuB7C;AAED,gDAAgD;AAChD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,iCAAiC,CAAC,CAmC5C"}
@@ -0,0 +1,140 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { NotFoundError, ValidationError } from "../../error/pithyError.js";
4
+ import { composedAuditCitations } from "../../audit/citations.js";
5
+ import { attest, byDue, periodEntry, summarize } from "./attest.js";
6
+ //#region src/controlPlane/erasure/read.ts
7
+ /**
8
+ * **The two erasure reads (#823), over whatever this Worker composes.**
9
+ *
10
+ * The rows live in the capability that owns each subject — auth for a user, organization for an organization —
11
+ * and are read through the {@link ErasureRecords} each declares. The events live in the audit trail and are read
12
+ * through the citation reader `@pithy-sh/audit` declares. Neither is imported: both are found among the composed
13
+ * capabilities, so a Worker with no organization attests users only and says so, and one with no audit attests from
14
+ * the lifecycle rows and says the trail is absent.
15
+ */
16
+ /** The default window: a quarter. */
17
+ const ERASURE_DEFAULT_WINDOW_MS = 7776e6;
18
+ /** The longest window one read covers: a year and a day, so a year's audit is one call. */
19
+ const ERASURE_MAX_WINDOW_MS = 316224e5;
20
+ /** How many subjects one history or citation read names at once — inside D1's bound-parameter limit. */
21
+ const CHUNK = 50;
22
+ /** The capability recording each subject's purges, first declared wins. */
23
+ function erasureProviders(capabilities) {
24
+ const providers = /* @__PURE__ */ new Map();
25
+ for (const capability of capabilities) {
26
+ const records = capability.erasureRecords;
27
+ if (records && !providers.has(records.subject)) providers.set(records.subject, records);
28
+ }
29
+ return providers;
30
+ }
31
+ /** The window a period read covers, defaulted and bounded. */
32
+ function erasureWindow(query, now) {
33
+ const until = query.until ? new Date(query.until) : now;
34
+ const since = query.since ? new Date(query.since) : /* @__PURE__ */ new Date(until.getTime() - ERASURE_DEFAULT_WINDOW_MS);
35
+ if (since.getTime() > until.getTime()) throw new ValidationError({
36
+ message: "`since` is after `until`.",
37
+ detail: `erasure window ${since.toISOString()} to ${until.toISOString()}`
38
+ });
39
+ if (until.getTime() - since.getTime() > 316224e5) throw new ValidationError({
40
+ message: "An erasure window is at most 366 days. Ask for a shorter one.",
41
+ detail: `erasure window ${since.toISOString()} to ${until.toISOString()}`
42
+ });
43
+ return {
44
+ since,
45
+ until
46
+ };
47
+ }
48
+ /** Every value in chunks of {@link CHUNK}. */
49
+ function chunks(values) {
50
+ const out = [];
51
+ for (let index = 0; index < values.length; index += CHUNK) out.push(values.slice(index, index + CHUNK));
52
+ return out;
53
+ }
54
+ /** The audit events a subject's purge records, for these ids, or `null` with no audit composed. */
55
+ async function citationsFor(capabilities, env, records, ids) {
56
+ const reader = composedAuditCitations(capabilities, env);
57
+ if (!reader) return null;
58
+ const { deleted, step, failed, purged } = records.auditActions;
59
+ const actions = [
60
+ ...deleted,
61
+ step,
62
+ failed,
63
+ purged
64
+ ];
65
+ const found = [];
66
+ for (const resourceIds of chunks(ids)) found.push(...await reader({
67
+ resourceType: records.resourceType,
68
+ resourceIds,
69
+ actions
70
+ }));
71
+ return found;
72
+ }
73
+ /** `GET /control-plane/erasure/:subjectType/:subjectId`. */
74
+ async function readErasureSubject(env, deps, subject, subjectId) {
75
+ const capabilities = deps.composition();
76
+ const records = erasureProviders(capabilities).get(subject);
77
+ if (!records) throw new NotFoundError({
78
+ message: `This Worker records no erasures for ${subject === "user" ? "a user" : "an organization"}.`,
79
+ action: `Compose the capability that owns ${subject} purges in this Worker, then ask again.`,
80
+ detail: `no composed capability declares erasureRecords for ${subject}`
81
+ });
82
+ const now = deps.now();
83
+ const rows = await records.read(env).history([subjectId]);
84
+ const citations = rows.length === 0 ? null : await citationsFor(capabilities, env, records, [subjectId]);
85
+ const attestation = rows.length === 0 ? null : attest({
86
+ subject,
87
+ subjectId,
88
+ rows,
89
+ citations,
90
+ actions: records.auditActions,
91
+ now
92
+ });
93
+ if (!attestation) throw new NotFoundError({
94
+ message: "No erasure on record for this subject.",
95
+ action: "It was never deleted, or it was restored. Nothing has been erased.",
96
+ detail: `no deletion in force for ${subject} ${subjectId}`
97
+ });
98
+ return {
99
+ generatedAt: now.toISOString(),
100
+ attestation
101
+ };
102
+ }
103
+ /** `GET /control-plane/erasure?since&until`. */
104
+ async function readErasurePeriod(env, deps, query) {
105
+ const now = deps.now();
106
+ const { since, until } = erasureWindow(query, now);
107
+ const providers = erasureProviders(deps.composition());
108
+ const entries = [];
109
+ for (const [subject, records] of providers) {
110
+ const reader = records.read(env);
111
+ const ids = await reader.subjectsIn(since, until);
112
+ for (const batch of chunks(ids)) {
113
+ const rows = await reader.history(batch);
114
+ for (const subjectId of batch) {
115
+ const attestation = attest({
116
+ subject,
117
+ subjectId,
118
+ rows: rows.filter((row) => row.subjectId === subjectId),
119
+ citations: null,
120
+ actions: records.auditActions,
121
+ now
122
+ });
123
+ if (attestation) entries.push(periodEntry(attestation));
124
+ }
125
+ }
126
+ }
127
+ const purges = byDue(entries);
128
+ const { counts, attention } = summarize(purges);
129
+ return {
130
+ generatedAt: now.toISOString(),
131
+ since: since.toISOString(),
132
+ until: until.toISOString(),
133
+ subjects: [...providers.keys()],
134
+ counts,
135
+ attention,
136
+ purges
137
+ };
138
+ }
139
+ //#endregion
140
+ export { ERASURE_DEFAULT_WINDOW_MS, ERASURE_MAX_WINDOW_MS, erasureProviders, erasureWindow, readErasurePeriod, readErasureSubject };
@@ -3,6 +3,7 @@
3
3
  import { PithyError } from "../../error/pithyError";
4
4
  import type { MessageParams } from "../../i18n/catalog";
5
5
  import type { UnmarkedString } from "../../privacy/types";
6
+ import type { ControlPlaneActorKind } from "../data/connection";
6
7
  /**
7
8
  * The `control-plane` strategy's throw sugar. The `controlplane/*` codes live in core's closed
8
9
  * `KitErrorPayload` union (CLAUDE.md §Errors); these subclasses are the vehicles that set one.
@@ -35,18 +36,76 @@ export declare class ControlPlaneNotConnectedError extends PithyError {
35
36
  cause?: unknown;
36
37
  });
37
38
  }
39
+ /**
40
+ * What a call under an OIDC trust carried when its claims did not match (#722), for the audit row and nobody
41
+ * else.
42
+ *
43
+ * Set only once the token's signature, issuer and audience have verified: by then the caller demonstrably
44
+ * holds a genuine token for this audience, so naming the claims it sent leaks nothing it does not already
45
+ * hold — and those claims are what an operator picks a matcher from. Before that the single-error discipline
46
+ * holds, and no refusal carries this.
47
+ */
48
+ export interface ControlPlaneRefusedTrust {
49
+ /** The connection whose trust was named. */
50
+ readonly connectionId: string;
51
+ /** The trust the call named. */
52
+ readonly trustId: string;
53
+ /** The workload's claims, as strings, capped — `observedClaims` in `../data/oidcTrust`. */
54
+ readonly observedClaims: Readonly<Record<string, string>>;
55
+ }
38
56
  /**
39
57
  * The credential failed verification. One error for every step — pass the step in `detail` so the log
40
58
  * can say which, and the caller cannot.
41
59
  */
42
60
  export declare class ControlPlaneInvalidCredentialError extends PithyError {
43
- constructor(args?: ControlPlaneErrorArgs, options?: {
61
+ /**
62
+ * A claim mismatch on an otherwise verified OIDC token, for the denial row. A property of the vehicle, never of
63
+ * `payload`, so the refusal a caller receives is the same one every other step answers. Null everywhere else.
64
+ */
65
+ readonly refusedTrust: ControlPlaneRefusedTrust | null;
66
+ constructor(args?: ControlPlaneErrorArgs & {
67
+ refusedTrust?: ControlPlaneRefusedTrust;
68
+ }, options?: {
44
69
  cause?: unknown;
45
70
  });
46
71
  }
72
+ /**
73
+ * Who a scope refusal refused, as verification proved it (#812).
74
+ *
75
+ * By the scope step the signature, the key, the lifetime and the body digest have all verified, so these are facts
76
+ * rather than claims. The gate writes them onto the denial row, which is what lets a known credential probing
77
+ * for scopes it lacks be traced back to it.
78
+ */
79
+ export interface ControlPlaneRefusedCaller {
80
+ /** The connection the verified token addressed. */
81
+ readonly connectionId: string;
82
+ /** The token's `sub`, proven by the signature. */
83
+ readonly subject: string;
84
+ /** The registered key that verified the signature — or, for a call under an OIDC trust, the trust. */
85
+ readonly keyId: string;
86
+ /**
87
+ * Said only for a call under an OIDC trust (#722), in which case `keyId` names the trust. Absent reads as a key, as
88
+ * it does on the allowed row, so a trust's refusals are not recorded as a key's with an id no key has.
89
+ */
90
+ readonly credentialKind?: "trust";
91
+ /**
92
+ * The kind of client the connection was registered as, from the adopter's row and never the token — the same value
93
+ * the allowed row is stamped with, so a trail filtered on kind holds a client's refusals beside its calls. Null for a
94
+ * connection registered before kinds were recorded.
95
+ */
96
+ readonly actorKind: ControlPlaneActorKind | null;
97
+ }
47
98
  /** The credential verified, but nothing granted it this operation. */
48
99
  export declare class ControlPlaneInsufficientScopeError extends PithyError {
49
- constructor(args?: ControlPlaneErrorArgs, options?: {
100
+ /**
101
+ * The verified caller, for the audit row and nobody else. A property of the vehicle, never of `payload`: the HTTP
102
+ * codec encodes the payload alone, so the refusal a caller receives is unchanged. Null when a throw site has no
103
+ * verified caller to name.
104
+ */
105
+ readonly caller: ControlPlaneRefusedCaller | null;
106
+ constructor(args?: ControlPlaneErrorArgs & {
107
+ caller?: ControlPlaneRefusedCaller;
108
+ }, options?: {
50
109
  cause?: unknown;
51
110
  });
52
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/error/errors.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;;;;;;GAYG;AAEH,UAAU,qBAAqB;IAC7B,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,UAAU;IAC3D,YAAY,IAAI,GAAE,qBAA0B,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAc1E;CACF;AAED;;;GAGG;AACH,qBAAa,kCAAmC,SAAQ,UAAU;IAChE,YAAY,IAAI,GAAE,qBAA0B,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAc1E;CACF;AAED,sEAAsE;AACtE,qBAAa,kCAAmC,SAAQ,UAAU;IAChE,YAAY,IAAI,GAAE,qBAA0B,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAc1E;CACF;AAED,0DAA0D;AAC1D,qBAAa,4BAA6B,SAAQ,UAAU;IAC1D,YAAY,IAAI,GAAE,qBAA0B,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAY1E;CACF;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,UAAU;IAC1D,YAAY,IAAI,GAAE,qBAA0B,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAY1E;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/error/errors.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE;;;;;;;;;;;;GAYG;AAEH,UAAU,qBAAqB;IAC7B,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,UAAU;IAC3D,YAAY,IAAI,GAAE,qBAA0B,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAc1E;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,wBAAwB;IACvC,4CAA4C;IAC5C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gCAAgC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2FAA2F;IAC3F,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC3D;AAED;;;GAGG;AACH,qBAAa,kCAAmC,SAAQ,UAAU;IAChE;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAEvD,YACE,IAAI,GAAE,qBAAqB,GAAG;QAAE,YAAY,CAAC,EAAE,wBAAwB,CAAA;KAAO,EAC9E,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAgB9B;CACF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,mDAAmD;IACnD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,qBAAqB,GAAG,IAAI,CAAC;CAClD;AAED,sEAAsE;AACtE,qBAAa,kCAAmC,SAAQ,UAAU;IAChE;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAElD,YACE,IAAI,GAAE,qBAAqB,GAAG;QAAE,MAAM,CAAC,EAAE,yBAAyB,CAAA;KAAO,EACzE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAgB9B;CACF;AAED,0DAA0D;AAC1D,qBAAa,4BAA6B,SAAQ,UAAU;IAC1D,YAAY,IAAI,GAAE,qBAA0B,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAY1E;CACF;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,UAAU;IAC1D,YAAY,IAAI,GAAE,qBAA0B,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAY1E;CACF"}
@@ -24,6 +24,11 @@ var ControlPlaneNotConnectedError = class extends PithyError {
24
24
  * can say which, and the caller cannot.
25
25
  */
26
26
  var ControlPlaneInvalidCredentialError = class extends PithyError {
27
+ /**
28
+ * A claim mismatch on an otherwise verified OIDC token, for the denial row. A property of the vehicle, never of
29
+ * `payload`, so the refusal a caller receives is the same one every other step answers. Null everywhere else.
30
+ */
31
+ refusedTrust;
27
32
  constructor(args = {}, options) {
28
33
  super({
29
34
  code: "controlplane/invalid_credential",
@@ -33,10 +38,17 @@ var ControlPlaneInvalidCredentialError = class extends PithyError {
33
38
  detail: args.detail,
34
39
  params: args.params
35
40
  }, options);
41
+ this.refusedTrust = args.refusedTrust ?? null;
36
42
  }
37
43
  };
38
44
  /** The credential verified, but nothing granted it this operation. */
39
45
  var ControlPlaneInsufficientScopeError = class extends PithyError {
46
+ /**
47
+ * The verified caller, for the audit row and nobody else. A property of the vehicle, never of `payload`: the HTTP
48
+ * codec encodes the payload alone, so the refusal a caller receives is unchanged. Null when a throw site has no
49
+ * verified caller to name.
50
+ */
51
+ caller;
40
52
  constructor(args = {}, options) {
41
53
  super({
42
54
  code: "controlplane/insufficient_scope",
@@ -46,6 +58,7 @@ var ControlPlaneInsufficientScopeError = class extends PithyError {
46
58
  detail: args.detail,
47
59
  params: args.params
48
60
  }, options);
61
+ this.caller = args.caller ?? null;
49
62
  }
50
63
  };
51
64
  /** The named key is not registered on this connection. */
@@ -0,0 +1,124 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { DescribedOperation } from "../../openapi/operations";
4
+ import type { PrivacyAttestation } from "../../privacy/posture";
5
+ import type { PrivacyInventory } from "../../privacy/report";
6
+ import type { RetirementVerdict } from "../../privacy/retirement";
7
+ import type { ControlPlaneConnection, RegisteredKey } from "../data/connection";
8
+ import { type CapabilityHealthReport, type HealthSummaryKey } from "../discovery/healthSummary";
9
+ import type { EvidenceEntry, EvidenceFact, MigrationDatabaseView } from "../http/responses";
10
+ import { type EvidenceControlId, type EvidenceState } from "./mapping";
11
+ /**
12
+ * The derivation behind each control in the evidence pack (#821): plain inputs in, facts and a state out.
13
+ *
14
+ * **Every function here is pure.** The reads — a D1 query, a health producer, the route table — happen in
15
+ * `./pack.ts`, which hands each one what it found or the fact that it could not look. So every rule for a state
16
+ * is tested here directly, without a Worker, and the rules cannot drift by being restated at a read site.
17
+ *
18
+ * **What decides a state.** A gap is something positively missing. Not-derivable is the Worker being unable to
19
+ * say. Evidenced is neither. A control with both reports `gap`, and lists the gaps first and the unknowns after,
20
+ * because something known to be missing is the more actionable fact and the unknowns are still there to read.
21
+ * Nothing here says more than the facts do: an unknown is never folded into `evidenced`.
22
+ */
23
+ /** What a control's derivation produced, before the mapping table's references are attached. */
24
+ export interface Derived {
25
+ state: EvidenceState;
26
+ reasons: string[];
27
+ facts: EvidenceFact[];
28
+ }
29
+ /** Settle a control's state from what was missing and what could not be read. See the module doc. */
30
+ export declare function settle(gaps: readonly string[], unknowns: readonly string[], facts: EvidenceFact[]): Derived;
31
+ /** One entry of the pack: the mapping table's row for the control, beside what was derived. */
32
+ export declare function evidenceEntry(control: EvidenceControlId, derived: Derived): EvidenceEntry;
33
+ /** What the audit-logging control reads. */
34
+ export interface AuditLoggingInput {
35
+ /** The composed capability declaring an audit recorder, with each binding it writes through, or null for none. */
36
+ recorder: {
37
+ capability: string;
38
+ bindings: readonly {
39
+ binding: string;
40
+ present: boolean;
41
+ }[];
42
+ } | null;
43
+ /** Whether this very request's emit seam is a recorder rather than the no-op: the guard wrote through it. */
44
+ emitting: boolean;
45
+ }
46
+ /**
47
+ * Audit logging in force: a composed recorder, the binding it writes through, and this request's emit seam
48
+ * actually recording. The seam's guard emits on every control-plane call, allowed or denied, so a recorder on
49
+ * the seam is a record of every management call — including the one reading this pack.
50
+ */
51
+ export declare function deriveAuditLogging(input: AuditLoggingInput): Derived;
52
+ /**
53
+ * Every route declares how a caller is verified: `describeRoute`'s `verification`, read off the composed route
54
+ * table. A route with no `describeRoute` declares nothing, and is named.
55
+ */
56
+ export declare function deriveRouteVerification(operations: readonly DescribedOperation[] | undefined): Derived;
57
+ /** What the management-access and key-rotation controls read off one connection row. Never key material. */
58
+ export type ConnectionRecord = Pick<ControlPlaneConnection, "id" | "authorizedBy" | "actorKind" | "purpose" | "scopes"> & {
59
+ keys: readonly Pick<RegisteredKey, "validFrom" | "validUntil" | "revokedAt">[];
60
+ };
61
+ /**
62
+ * Management access attributed and scoped: every connection in this environment, with the principal who
63
+ * authorized it, its actor kind, its purpose (#721) and its granted scopes. The principal is the email the
64
+ * connection records and nothing else; no audit row's actor is copied in.
65
+ */
66
+ export declare function deriveManagementAccess(connections: readonly ConnectionRecord[] | null): Derived;
67
+ /** What secrets' rotation status was on this call: its health report and the keys that declare it, or absent. */
68
+ export type SecretsRotationInput = {
69
+ state: "not-composed";
70
+ } | {
71
+ state: "composed";
72
+ keys: readonly HealthSummaryKey[];
73
+ report: CapabilityHealthReport;
74
+ };
75
+ /** What the key-rotation control reads. */
76
+ export interface KeyRotationInput {
77
+ connections: readonly ConnectionRecord[] | null;
78
+ now: Date;
79
+ secrets: SecretsRotationInput;
80
+ retirement: RetirementVerdict;
81
+ }
82
+ /**
83
+ * Keys and secrets rotate: each connection's live keys and the age of the oldest, secrets' declared cadence and
84
+ * what is past it, and the field key's retirement guard (#390). Ages are facts and never graded here: the seam
85
+ * declares no cadence for control-plane keys, and inventing one would be the pack's opinion.
86
+ */
87
+ export declare function deriveKeyRotation(input: KeyRotationInput): Derived;
88
+ /** What the change-history control reads. */
89
+ export interface ChangeHistoryInput {
90
+ /** Whether this connection holds `migrations:read`, the scope the seam keeps migration state behind. */
91
+ granted: boolean;
92
+ /** Each bound database's ledger, read only when granted. */
93
+ databases: readonly MigrationDatabaseView[];
94
+ }
95
+ /**
96
+ * Change history per environment: each bound database's applied migrations, the last group and when it applied
97
+ * (#729). Behind `migrations:read`, as the same numbers are on the manifest, so the pack never widens it.
98
+ */
99
+ export declare function deriveChangeHistory(input: ChangeHistoryInput): Derived;
100
+ /** What the data-protection control reads. */
101
+ export interface DataProtectionInput {
102
+ /** The privacy inventory of every stored schema, or null where the walk could not run. */
103
+ inventory: PrivacyInventory | null;
104
+ /** What the last `pithy deploy` attested the project declares, or null where none reached this Worker. */
105
+ attestation: PrivacyAttestation | null;
106
+ }
107
+ /**
108
+ * Data classified and encrypted at rest: the classes the stored schemas hold (#132) beside the classes the
109
+ * project declared, and the fields marked `encrypted()` (#390). Classes and counts only, never a field's name:
110
+ * those are `GET /privacy`'s, behind their own scope.
111
+ */
112
+ export declare function deriveDataProtection(input: DataProtectionInput): Derived;
113
+ /** What the CSRF control reads. */
114
+ export interface CsrfInput {
115
+ operations: readonly DescribedOperation[] | undefined;
116
+ /** Whether a composed capability published a same-origin policy on this request. */
117
+ sameOriginPublished: boolean;
118
+ }
119
+ /**
120
+ * CSRF wherever cookie mode is: the routes that accept a session cookie, and whether a composed capability
121
+ * publishes the same-origin policy `requireSameOrigin()` enforces. No route taking a cookie needs none.
122
+ */
123
+ export declare function deriveCsrf(input: CsrfInput): Derived;
124
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/evidence/derive.ts"],"names":[],"mappings":"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"}