@learncard/network-brain-service 3.18.0 → 3.19.1

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 (478) hide show
  1. package/.env.example +38 -2
  2. package/CHANGELOG.md +118 -0
  3. package/README.md +42 -0
  4. package/compose.inbox.yml +7 -0
  5. package/dist/accesslayer/auth-grant/read.d.ts +1 -0
  6. package/dist/accesslayer/auth-grant/read.d.ts.map +1 -1
  7. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  8. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  9. package/dist/accesslayer/credential-activity/create.d.ts.map +1 -1
  10. package/dist/accesslayer/credential-refresh/email-delivery.d.ts +18 -0
  11. package/dist/accesslayer/credential-refresh/email-delivery.d.ts.map +1 -0
  12. package/dist/accesslayer/credential-refresh/index.d.ts +1 -0
  13. package/dist/accesslayer/credential-refresh/index.d.ts.map +1 -1
  14. package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
  15. package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
  16. package/dist/accesslayer/inbox-batch/store.d.ts +33 -0
  17. package/dist/accesslayer/inbox-batch/store.d.ts.map +1 -0
  18. package/dist/accesslayer/inbox-credential/create.d.ts +5 -0
  19. package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
  20. package/dist/accesslayer/inbox-credential/refresh.d.ts +71 -0
  21. package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -0
  22. package/dist/accesslayer/inbox-credential/update.d.ts +1 -1
  23. package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
  24. package/dist/accesslayer/presentation/read.d.ts.map +1 -1
  25. package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
  26. package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
  27. package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
  28. package/dist/accesslayer/share-link/abandon.d.ts +3 -0
  29. package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
  30. package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
  31. package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
  32. package/dist/accesslayer/share-link/errors.d.ts +8 -0
  33. package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
  34. package/dist/accesslayer/share-link/finalize.d.ts +3 -0
  35. package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
  36. package/dist/accesslayer/share-link/helpers.d.ts +56 -0
  37. package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
  38. package/dist/accesslayer/share-link/index.d.ts +12 -0
  39. package/dist/accesslayer/share-link/index.d.ts.map +1 -0
  40. package/dist/accesslayer/share-link/list.d.ts +5 -0
  41. package/dist/accesslayer/share-link/list.d.ts.map +1 -0
  42. package/dist/accesslayer/share-link/read.d.ts +5 -0
  43. package/dist/accesslayer/share-link/read.d.ts.map +1 -0
  44. package/dist/accesslayer/share-link/receipt.d.ts +11 -0
  45. package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
  46. package/dist/accesslayer/share-link/recovery.d.ts +6 -0
  47. package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
  48. package/dist/accesslayer/share-link/reserve.d.ts +4 -0
  49. package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
  50. package/dist/accesslayer/share-link/revoke.d.ts +3 -0
  51. package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
  52. package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
  53. package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
  54. package/dist/accesslayer/share-link/transaction.d.ts +15 -0
  55. package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
  56. package/dist/accesslayer/share-link/types.d.ts +370 -0
  57. package/dist/accesslayer/share-link/types.d.ts.map +1 -0
  58. package/dist/app.d.ts +4 -0
  59. package/dist/app.d.ts.map +1 -1
  60. package/dist/cache/atomic-counter.test-helpers.d.ts +3 -0
  61. package/dist/cache/atomic-counter.test-helpers.d.ts.map +1 -0
  62. package/dist/cache/atomic-counter.test.d.ts +2 -0
  63. package/dist/cache/atomic-counter.test.d.ts.map +1 -0
  64. package/dist/cache/index.d.ts +1 -1
  65. package/dist/cache/index.d.ts.map +1 -1
  66. package/dist/config/environment.d.ts +55 -0
  67. package/dist/config/environment.d.ts.map +1 -1
  68. package/dist/constants/auth-grant.d.ts +2 -0
  69. package/dist/constants/auth-grant.d.ts.map +1 -1
  70. package/dist/credential-refresh.d.ts.map +1 -1
  71. package/dist/helpers/activity.helpers.d.ts +4 -0
  72. package/dist/helpers/activity.helpers.d.ts.map +1 -1
  73. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  74. package/dist/helpers/contact-method.helpers.d.ts.map +1 -1
  75. package/dist/helpers/contact-method.helpers.test.d.ts +2 -0
  76. package/dist/helpers/contact-method.helpers.test.d.ts.map +1 -0
  77. package/dist/helpers/credential-refresh-email-content.helpers.d.ts +44 -0
  78. package/dist/helpers/credential-refresh-email-content.helpers.d.ts.map +1 -0
  79. package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts +2 -0
  80. package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts.map +1 -0
  81. package/dist/helpers/credential-refresh-email.helpers.d.ts +17 -0
  82. package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -0
  83. package/dist/helpers/credential-refresh-proof.helpers.d.ts +4 -0
  84. package/dist/helpers/credential-refresh-proof.helpers.d.ts.map +1 -0
  85. package/dist/helpers/credential-refresh-proof.helpers.test.d.ts +2 -0
  86. package/dist/helpers/credential-refresh-proof.helpers.test.d.ts.map +1 -0
  87. package/dist/helpers/credential-refresh.helpers.d.ts +45 -2
  88. package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
  89. package/dist/helpers/credential.helpers.d.ts +2 -1
  90. package/dist/helpers/credential.helpers.d.ts.map +1 -1
  91. package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
  92. package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
  93. package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
  94. package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
  95. package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
  96. package/dist/helpers/inbox-batch-http.helpers.d.ts +15 -0
  97. package/dist/helpers/inbox-batch-http.helpers.d.ts.map +1 -0
  98. package/dist/helpers/inbox-batch-http.helpers.test.d.ts +2 -0
  99. package/dist/helpers/inbox-batch-http.helpers.test.d.ts.map +1 -0
  100. package/dist/helpers/inbox-batch-status.helpers.d.ts +3 -0
  101. package/dist/helpers/inbox-batch-status.helpers.d.ts.map +1 -0
  102. package/dist/helpers/inbox-batch.helpers.d.ts +10 -0
  103. package/dist/helpers/inbox-batch.helpers.d.ts.map +1 -0
  104. package/dist/helpers/inbox-batch.helpers.test.d.ts +2 -0
  105. package/dist/helpers/inbox-batch.helpers.test.d.ts.map +1 -0
  106. package/dist/helpers/inbox-issuance-error.helpers.d.ts +13 -0
  107. package/dist/helpers/inbox-issuance-error.helpers.d.ts.map +1 -0
  108. package/dist/helpers/inbox-queue.helpers.d.ts +11 -0
  109. package/dist/helpers/inbox-queue.helpers.d.ts.map +1 -0
  110. package/dist/helpers/inbox-queue.helpers.test.d.ts +2 -0
  111. package/dist/helpers/inbox-queue.helpers.test.d.ts.map +1 -0
  112. package/dist/helpers/inbox-refresh.helpers.d.ts +186 -0
  113. package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -0
  114. package/dist/helpers/inbox.helpers.d.ts +9 -1
  115. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  116. package/dist/helpers/notificationMessages.d.ts +1 -1
  117. package/dist/helpers/notificationMessages.d.ts.map +1 -1
  118. package/dist/helpers/notifications.helpers.d.ts.map +1 -1
  119. package/dist/helpers/presentation-metadata.d.ts +4 -0
  120. package/dist/helpers/presentation-metadata.d.ts.map +1 -0
  121. package/dist/helpers/presentation.helpers.d.ts +1 -1
  122. package/dist/helpers/presentation.helpers.d.ts.map +1 -1
  123. package/dist/helpers/profile.helpers.d.ts +1 -0
  124. package/dist/helpers/profile.helpers.d.ts.map +1 -1
  125. package/dist/helpers/rateLimit.helpers.d.ts.map +1 -1
  126. package/dist/helpers/refresh-send-intent.helpers.d.ts +51 -0
  127. package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -0
  128. package/dist/helpers/share-content-client/adapters.d.ts +10 -0
  129. package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
  130. package/dist/helpers/share-content-client/canonical.d.ts +7 -0
  131. package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
  132. package/dist/helpers/share-content-client/client.d.ts +5 -0
  133. package/dist/helpers/share-content-client/client.d.ts.map +1 -0
  134. package/dist/helpers/share-content-client/config.d.ts +11 -0
  135. package/dist/helpers/share-content-client/config.d.ts.map +1 -0
  136. package/dist/helpers/share-content-client/index.d.ts +6 -0
  137. package/dist/helpers/share-content-client/index.d.ts.map +1 -0
  138. package/dist/helpers/share-content-client/responses.d.ts +16 -0
  139. package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
  140. package/dist/helpers/share-content-client/types.d.ts +145 -0
  141. package/dist/helpers/share-content-client/types.d.ts.map +1 -0
  142. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
  143. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
  144. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
  145. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
  146. package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
  147. package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
  148. package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
  149. package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
  150. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
  151. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
  152. package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
  153. package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
  154. package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
  155. package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
  156. package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
  157. package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
  158. package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
  159. package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
  160. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
  161. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
  162. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
  163. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
  164. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
  165. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
  166. package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
  167. package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
  168. package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
  169. package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
  170. package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
  171. package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
  172. package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
  173. package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
  174. package/dist/helpers/share-link-maintenance/runner.d.ts +19 -0
  175. package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
  176. package/dist/helpers/share-link-maintenance/runtime.d.ts +36 -0
  177. package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
  178. package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
  179. package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
  180. package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
  181. package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
  182. package/dist/helpers/share-link-maintenance/types.d.ts +53 -0
  183. package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
  184. package/dist/helpers/share-link-owner/config.d.ts +16 -0
  185. package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
  186. package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
  187. package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
  188. package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
  189. package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
  190. package/dist/helpers/share-link-owner-projection.d.ts +4 -0
  191. package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
  192. package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
  193. package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
  194. package/dist/helpers/share-link-passcode.d.ts +6 -0
  195. package/dist/helpers/share-link-passcode.d.ts.map +1 -0
  196. package/dist/helpers/share-link-policy/index.d.ts +4 -0
  197. package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
  198. package/dist/helpers/share-link-policy/production.d.ts +13 -0
  199. package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
  200. package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
  201. package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
  202. package/dist/helpers/share-link-policy/types.d.ts +16 -0
  203. package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
  204. package/dist/helpers/share-link-public/config.d.ts +13 -0
  205. package/dist/helpers/share-link-public/config.d.ts.map +1 -0
  206. package/dist/helpers/share-link-view-notification.d.ts +2 -0
  207. package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
  208. package/dist/helpers/shim.d.ts +1 -1
  209. package/dist/helpers/shim.d.ts.map +1 -1
  210. package/dist/inbox-queue-local.d.ts +2 -0
  211. package/dist/inbox-queue-local.d.ts.map +1 -0
  212. package/dist/index.js +2 -2
  213. package/dist/models/AuthGrant.d.ts.map +1 -1
  214. package/dist/models/CredentialActivity.d.ts +1 -0
  215. package/dist/models/CredentialActivity.d.ts.map +1 -1
  216. package/dist/models/CredentialRefresh.d.ts +5 -2
  217. package/dist/models/CredentialRefresh.d.ts.map +1 -1
  218. package/dist/models/InboxCredential.d.ts +1 -0
  219. package/dist/models/InboxCredential.d.ts.map +1 -1
  220. package/dist/models/Presentation.d.ts +2 -0
  221. package/dist/models/Presentation.d.ts.map +1 -1
  222. package/dist/models/Profile.d.ts +2 -0
  223. package/dist/models/Profile.d.ts.map +1 -1
  224. package/dist/models/ShareLink.d.ts +44 -0
  225. package/dist/models/ShareLink.d.ts.map +1 -0
  226. package/dist/models/ShareViewReceipt.d.ts +22 -0
  227. package/dist/models/ShareViewReceipt.d.ts.map +1 -0
  228. package/dist/models/credential-refresh-constraints.d.ts.map +1 -1
  229. package/dist/models/index.d.ts +1 -0
  230. package/dist/models/index.d.ts.map +1 -1
  231. package/dist/models/share-link-constraints.d.ts +3 -0
  232. package/dist/models/share-link-constraints.d.ts.map +1 -0
  233. package/dist/openapi.d.ts.map +1 -1
  234. package/dist/public-share-link-http.d.ts +4 -0
  235. package/dist/public-share-link-http.d.ts.map +1 -0
  236. package/dist/routes/act-as.test.d.ts +2 -0
  237. package/dist/routes/act-as.test.d.ts.map +1 -0
  238. package/dist/routes/activity.d.ts +3 -0
  239. package/dist/routes/activity.d.ts.map +1 -1
  240. package/dist/routes/app-store.d.ts.map +1 -1
  241. package/dist/routes/auth-grants.d.ts +10 -0
  242. package/dist/routes/auth-grants.d.ts.map +1 -1
  243. package/dist/routes/boosts.d.ts +494 -0
  244. package/dist/routes/boosts.d.ts.map +1 -1
  245. package/dist/routes/contracts.d.ts +2 -2
  246. package/dist/routes/contracts.d.ts.map +1 -1
  247. package/dist/routes/credential-refreshes.d.ts.map +1 -1
  248. package/dist/routes/credentials.d.ts +1 -1
  249. package/dist/routes/credentials.d.ts.map +1 -1
  250. package/dist/routes/inbox.d.ts +953 -11
  251. package/dist/routes/inbox.d.ts.map +1 -1
  252. package/dist/routes/index.d.ts +154 -85
  253. package/dist/routes/index.d.ts.map +1 -1
  254. package/dist/routes/presentations.d.ts +1 -0
  255. package/dist/routes/presentations.d.ts.map +1 -1
  256. package/dist/routes/profile-manager.d.ts.map +1 -1
  257. package/dist/routes/profiles.d.ts.map +1 -1
  258. package/dist/routes/public-share-links.d.ts +230 -0
  259. package/dist/routes/public-share-links.d.ts.map +1 -0
  260. package/dist/routes/share-links.d.ts +844 -0
  261. package/dist/routes/share-links.d.ts.map +1 -0
  262. package/dist/routes/workflows.d.ts.map +1 -1
  263. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  264. package/dist/services/delivery/adapters/postmark.adapter.test.d.ts +2 -0
  265. package/dist/services/delivery/adapters/postmark.adapter.test.d.ts.map +1 -0
  266. package/dist/services/registry/registry.factory.d.ts.map +1 -1
  267. package/dist/types/activity.d.ts +4 -0
  268. package/dist/types/activity.d.ts.map +1 -1
  269. package/dist/types/consentflowcontract.d.ts +1 -1
  270. package/dist/types/credential-refresh.d.ts +10 -1
  271. package/dist/types/credential-refresh.d.ts.map +1 -1
  272. package/dist/types/inbox-batch.d.ts +38 -0
  273. package/dist/types/inbox-batch.d.ts.map +1 -0
  274. package/inbox-e2e-setup.ts +53 -1
  275. package/inbox-elasticmq.conf +10 -0
  276. package/lambda.ts +64 -16
  277. package/package.json +24 -16
  278. package/scripts/seed-demo-persona.ts +425 -0
  279. package/scripts/setup-inbox-queues.ts +60 -0
  280. package/serverless-local.yml +2 -0
  281. package/serverless.yml +111 -0
  282. package/shareLinkMaintenanceLambda.ts +49 -0
  283. package/src/accesslayer/auth-grant/read.ts +2 -1
  284. package/src/accesslayer/consentflowcontract/relationships/create.ts +9 -16
  285. package/src/accesslayer/consentflowcontract/relationships/update.ts +15 -28
  286. package/src/accesslayer/credential-activity/create.ts +1 -0
  287. package/src/accesslayer/credential-refresh/email-delivery.ts +116 -0
  288. package/src/accesslayer/credential-refresh/index.ts +1 -0
  289. package/src/accesslayer/credential-refresh/read.ts +8 -2
  290. package/src/accesslayer/credential-refresh/update.ts +4 -0
  291. package/src/accesslayer/inbox-batch/store.ts +469 -0
  292. package/src/accesslayer/inbox-credential/create.ts +51 -19
  293. package/src/accesslayer/inbox-credential/refresh.ts +221 -0
  294. package/src/accesslayer/inbox-credential/update.ts +1 -1
  295. package/src/accesslayer/presentation/read.ts +57 -25
  296. package/src/accesslayer/presentation/relationships/create.ts +54 -11
  297. package/src/accesslayer/presentation/relationships/read.ts +21 -6
  298. package/src/accesslayer/share-link/abandon.ts +100 -0
  299. package/src/accesslayer/share-link/cleanup.ts +271 -0
  300. package/src/accesslayer/share-link/errors.ts +32 -0
  301. package/src/accesslayer/share-link/finalize.ts +265 -0
  302. package/src/accesslayer/share-link/helpers.ts +419 -0
  303. package/src/accesslayer/share-link/index.ts +11 -0
  304. package/src/accesslayer/share-link/list.ts +162 -0
  305. package/src/accesslayer/share-link/read.ts +107 -0
  306. package/src/accesslayer/share-link/receipt.ts +548 -0
  307. package/src/accesslayer/share-link/recovery.ts +545 -0
  308. package/src/accesslayer/share-link/reserve.ts +684 -0
  309. package/src/accesslayer/share-link/revoke.ts +197 -0
  310. package/src/accesslayer/share-link/transaction-execution.ts +72 -0
  311. package/src/accesslayer/share-link/transaction.ts +126 -0
  312. package/src/accesslayer/share-link/types.ts +601 -0
  313. package/src/app.ts +6 -0
  314. package/src/cache/atomic-counter.test-helpers.ts +27 -0
  315. package/src/cache/atomic-counter.test.ts +6 -0
  316. package/src/cache/index.ts +25 -13
  317. package/src/config/environment.ts +40 -0
  318. package/src/constants/auth-grant.ts +5 -0
  319. package/src/credential-refresh.ts +5 -1
  320. package/src/docker-entry.ts +65 -0
  321. package/src/helpers/activity.helpers.ts +4 -0
  322. package/src/helpers/app-counter.helpers.ts +1 -1
  323. package/src/helpers/boost.helpers.ts +4 -31
  324. package/src/helpers/contact-method.helpers.test.ts +43 -0
  325. package/src/helpers/contact-method.helpers.ts +3 -4
  326. package/src/helpers/credential-refresh-email-content.helpers.test.ts +222 -0
  327. package/src/helpers/credential-refresh-email-content.helpers.ts +155 -0
  328. package/src/helpers/credential-refresh-email.helpers.ts +201 -0
  329. package/src/helpers/credential-refresh-proof.helpers.test.ts +103 -0
  330. package/src/helpers/credential-refresh-proof.helpers.ts +43 -0
  331. package/src/helpers/credential-refresh.helpers.ts +375 -99
  332. package/src/helpers/credential.helpers.ts +4 -1
  333. package/src/helpers/credentialSubject.helpers.test.ts +114 -0
  334. package/src/helpers/credentialSubject.helpers.ts +40 -0
  335. package/src/helpers/finalize-inbox.helpers.ts +29 -14
  336. package/src/helpers/inbox-batch-http.helpers.test.ts +188 -0
  337. package/src/helpers/inbox-batch-http.helpers.ts +150 -0
  338. package/src/helpers/inbox-batch-status.helpers.ts +11 -0
  339. package/src/helpers/inbox-batch.helpers.test.ts +668 -0
  340. package/src/helpers/inbox-batch.helpers.ts +374 -0
  341. package/src/helpers/inbox-issuance-error.helpers.ts +25 -0
  342. package/src/helpers/inbox-queue.helpers.test.ts +253 -0
  343. package/src/helpers/inbox-queue.helpers.ts +372 -0
  344. package/src/helpers/inbox-refresh.helpers.ts +432 -0
  345. package/src/helpers/inbox.helpers.ts +275 -36
  346. package/src/helpers/notificationMessages.ts +18 -1
  347. package/src/helpers/notifications.helpers.ts +4 -2
  348. package/src/helpers/presentation-metadata.ts +41 -0
  349. package/src/helpers/presentation.helpers.ts +27 -20
  350. package/src/helpers/profile.helpers.ts +7 -0
  351. package/src/helpers/rateLimit.helpers.ts +2 -5
  352. package/src/helpers/refresh-send-intent.helpers.ts +273 -0
  353. package/src/helpers/share-content-client/adapters.ts +55 -0
  354. package/src/helpers/share-content-client/canonical.ts +25 -0
  355. package/src/helpers/share-content-client/client.ts +440 -0
  356. package/src/helpers/share-content-client/config.ts +189 -0
  357. package/src/helpers/share-content-client/index.ts +27 -0
  358. package/src/helpers/share-content-client/responses.ts +273 -0
  359. package/src/helpers/share-content-client/types.ts +253 -0
  360. package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
  361. package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
  362. package/src/helpers/share-link-coordinator/coordinator.ts +714 -0
  363. package/src/helpers/share-link-coordinator/index.ts +8 -0
  364. package/src/helpers/share-link-coordinator/recovery-runner.ts +428 -0
  365. package/src/helpers/share-link-coordinator/runtime.ts +88 -0
  366. package/src/helpers/share-link-coordinator/stat.ts +30 -0
  367. package/src/helpers/share-link-coordinator/types.ts +353 -0
  368. package/src/helpers/share-link-lifecycle/index.ts +4 -0
  369. package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
  370. package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
  371. package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
  372. package/src/helpers/share-link-lifecycle/types.ts +58 -0
  373. package/src/helpers/share-link-maintenance/budget.ts +63 -0
  374. package/src/helpers/share-link-maintenance/config.ts +312 -0
  375. package/src/helpers/share-link-maintenance/index.ts +24 -0
  376. package/src/helpers/share-link-maintenance/runner.ts +223 -0
  377. package/src/helpers/share-link-maintenance/runtime.ts +334 -0
  378. package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
  379. package/src/helpers/share-link-maintenance/telemetry.ts +70 -0
  380. package/src/helpers/share-link-maintenance/types.ts +89 -0
  381. package/src/helpers/share-link-owner/config.ts +48 -0
  382. package/src/helpers/share-link-owner/diagnostics.ts +89 -0
  383. package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
  384. package/src/helpers/share-link-owner-projection.ts +52 -0
  385. package/src/helpers/share-link-passcode-abuse.ts +36 -0
  386. package/src/helpers/share-link-passcode.ts +47 -0
  387. package/src/helpers/share-link-policy/index.ts +7 -0
  388. package/src/helpers/share-link-policy/production.ts +88 -0
  389. package/src/helpers/share-link-policy/resolver.ts +108 -0
  390. package/src/helpers/share-link-policy/types.ts +48 -0
  391. package/src/helpers/share-link-public/config.ts +41 -0
  392. package/src/helpers/share-link-view-notification.ts +18 -0
  393. package/src/helpers/shim.ts +32 -31
  394. package/src/inbox-queue-local.ts +28 -0
  395. package/src/models/AuthGrant.ts +1 -0
  396. package/src/models/CredentialActivity.ts +3 -5
  397. package/src/models/CredentialRefresh.ts +4 -2
  398. package/src/models/InboxCredential.ts +2 -0
  399. package/src/models/Presentation.ts +2 -2
  400. package/src/models/Profile.ts +3 -2
  401. package/src/models/ShareLink.ts +130 -0
  402. package/src/models/ShareViewReceipt.ts +69 -0
  403. package/src/models/credential-refresh-constraints.test.ts +8 -3
  404. package/src/models/credential-refresh-constraints.ts +5 -0
  405. package/src/models/index.ts +2 -0
  406. package/src/models/share-link-constraints.ts +93 -0
  407. package/src/openapi.ts +8 -0
  408. package/src/public-share-link-http.ts +49 -0
  409. package/src/routes/act-as.test.ts +239 -0
  410. package/src/routes/app-store.ts +4 -13
  411. package/src/routes/auth-grants.ts +13 -11
  412. package/src/routes/boosts.ts +916 -103
  413. package/src/routes/contracts.ts +2 -11
  414. package/src/routes/credential-refreshes.test.ts +8 -2
  415. package/src/routes/credential-refreshes.ts +4 -1
  416. package/src/routes/credentials.ts +2 -1
  417. package/src/routes/inbox.ts +95 -49
  418. package/src/routes/index.ts +216 -46
  419. package/src/routes/presentations.ts +19 -3
  420. package/src/routes/profile-manager.ts +10 -2
  421. package/src/routes/profiles.ts +17 -6
  422. package/src/routes/public-share-links.ts +814 -0
  423. package/src/routes/share-links.ts +640 -0
  424. package/src/routes/skills.ts +1 -1
  425. package/src/routes/workflows.ts +43 -24
  426. package/src/services/delivery/adapters/postmark.adapter.test.ts +54 -0
  427. package/src/services/delivery/adapters/postmark.adapter.ts +16 -1
  428. package/src/services/registry/registry.factory.ts +2 -1
  429. package/src/types/activity.ts +2 -0
  430. package/src/types/credential-refresh.ts +21 -2
  431. package/src/types/inbox-batch.ts +46 -0
  432. package/test/atomic-counter.e2e.ts +12 -0
  433. package/test/auth-grant.spec.ts +21 -21
  434. package/test/credential-refresh-email.spec.ts +663 -0
  435. package/test/credential-refresh-publication.spec.ts +2 -0
  436. package/test/dids.spec.ts +10 -1
  437. package/test/guardian-gated-routes.spec.ts +13 -0
  438. package/test/helpers/inbox-e2e-environment.ts +6 -0
  439. package/test/helpers/share-link-fixtures.ts +94 -0
  440. package/test/inbox-batch.spec.ts +1338 -0
  441. package/test/inbox-refresh.spec.ts +534 -0
  442. package/test/managed-recipient-inbox.spec.ts +130 -0
  443. package/test/notificationMessages.spec.ts +13 -0
  444. package/test/openapi.spec.ts +61 -1
  445. package/test/presentations.spec.ts +54 -6
  446. package/test/profiles.spec.ts +67 -1
  447. package/test/refresh-send.spec.ts +1799 -0
  448. package/test/registry.spec.ts +56 -11
  449. package/test/share-content-client.contract.spec.ts +365 -0
  450. package/test/share-content-client.unit.spec.ts +462 -0
  451. package/test/share-link-abuse.unit.spec.ts +65 -0
  452. package/test/share-link-coordinator.neo4j.spec.ts +684 -0
  453. package/test/share-link-coordinator.unit.spec.ts +990 -0
  454. package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
  455. package/test/share-link-lifecycle.neo4j.spec.ts +1268 -0
  456. package/test/share-link-lifecycle.unit.spec.ts +205 -0
  457. package/test/share-link-list-cursor.unit.spec.ts +86 -0
  458. package/test/share-link-list.neo4j.spec.ts +324 -0
  459. package/test/share-link-maintenance.neo4j.spec.ts +355 -0
  460. package/test/share-link-maintenance.unit.spec.ts +1113 -0
  461. package/test/share-link-owner-policy.unit.spec.ts +210 -0
  462. package/test/share-link-owner-route.unit.spec.ts +752 -0
  463. package/test/share-link-owner-runtime.unit.spec.ts +102 -0
  464. package/test/share-link-passcode.unit.spec.ts +29 -0
  465. package/test/share-link-policy.unit.spec.ts +135 -0
  466. package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
  467. package/test/share-link-production-policy.unit.spec.ts +77 -0
  468. package/test/share-link-public-config.unit.spec.ts +64 -0
  469. package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
  470. package/test/share-link-public-route.unit.spec.ts +800 -0
  471. package/test/share-link-receipt.neo4j.spec.ts +943 -0
  472. package/test/share-link-recovery.neo4j.spec.ts +935 -0
  473. package/test/share-link-recovery.unit.spec.ts +726 -0
  474. package/vitest.config.ts +10 -0
  475. package/vitest.inbox-e2e.config.ts +5 -1
  476. package/vitest.integration.config.ts +2 -0
  477. package/vitest.share-links-neo4j.config.ts +19 -0
  478. package/vitest.share-links-unit.config.ts +28 -0
@@ -0,0 +1,548 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import { ensureShareLinkConstraints } from '../../models/share-link-constraints';
4
+ import { failShareLink } from './errors';
5
+ import { lockShare, readNodeProperties } from './helpers';
6
+ import { withShareLinkRead, withShareLinkTransaction } from './transaction';
7
+ import type { ShareLinkTransaction } from './transaction';
8
+ import type {
9
+ ConsumeShareViewReceiptInput,
10
+ ConsumeShareViewReceiptOutcome,
11
+ PersistShareViewReceiptInput,
12
+ PruneShareViewReceiptsInput,
13
+ } from './types';
14
+
15
+ /**
16
+ * LC-2187 view-receipt repository.
17
+ *
18
+ * Security invariants:
19
+ * - The raw opaque token is NEVER stored; only `sha256(token)` keyed by a
20
+ * uniqueness constraint. Padding values returned to ineligible viewers are
21
+ * never persisted, so they can never be consumed.
22
+ * - Consume + `viewCount` increment + `lastViewedAt` happen in ONE Neo4j
23
+ * transaction that (1) locks the share, (2) locks the receipt, then (3)
24
+ * re-reads status/version/expiry/policy/consumed state. The share lock is the
25
+ * SAME lock revoke/update/finalize take, so an acknowledgement that races a
26
+ * revoke/replacement either commits before it or observes the stop and does
27
+ * not count.
28
+ * - Eligibility is a conjunction of the committed snapshot policy re-read under
29
+ * the lock and a narrowly typed, transaction-compatible trusted policy source
30
+ * consulted inside the same transaction. A pre-lock boolean is never authority;
31
+ * absence or failure of the source is ineligible. A uniqueness constraint alone
32
+ * is insufficient.
33
+ * - No remote/network I/O ever runs inside the transaction.
34
+ */
35
+
36
+ const DEFAULT_PRUNE_LIMIT = 200;
37
+ const MAX_PRUNE_LIMIT = 1000;
38
+ const MAX_RECEIPT_TTL_SECONDS = 86_400;
39
+
40
+ const RECEIPT_HASH_RE = /^[0-9a-f]{64}$/;
41
+
42
+ /**
43
+ * A durable receipt is only trustworthy when every required binding field is
44
+ * present and well-formed. Malformed state must never be interpreted as a
45
+ * permissive default, so these helpers return `null` and callers fail closed.
46
+ */
47
+ const requiredString = (value: unknown): string | null =>
48
+ typeof value === 'string' && value.length > 0 ? value : null;
49
+
50
+ const requiredFiniteNumber = (value: unknown): number | null =>
51
+ typeof value === 'number' && Number.isFinite(value) ? value : null;
52
+
53
+ /**
54
+ * Strict version validator. A durable version is a safe positive integer:
55
+ * negative, fractional, zero, NaN and Infinity values are malformed and fail
56
+ * closed rather than being coerced or defaulted.
57
+ */
58
+ const requiredPositiveInteger = (value: unknown): number | null =>
59
+ typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : null;
60
+
61
+ /** A required ISO timestamp that actually parses; `null`/garbage fail closed. */
62
+ const requiredInstantMs = (value: unknown): number | null => {
63
+ if (typeof value !== 'string' || value.length === 0) return null;
64
+
65
+ const parsed = Date.parse(value);
66
+ return Number.isNaN(parsed) ? null : parsed;
67
+ };
68
+
69
+ /**
70
+ * A valid optional instant. Absent/`null` is a legitimate "unset" and returns
71
+ * `null`; any other value MUST parse, otherwise `undefined` is returned so the
72
+ * caller can fail closed instead of treating malformed state as unset.
73
+ */
74
+ const optionalInstantMs = (value: unknown): number | null | undefined => {
75
+ if (value === null || value === undefined) return null;
76
+
77
+ return requiredInstantMs(value) ?? undefined;
78
+ };
79
+
80
+ export const hashShareViewReceipt = (receipt: string): string =>
81
+ createHash('sha256').update(receipt, 'utf8').digest('hex');
82
+
83
+ const readReceiptByHash = async (
84
+ tx: ShareLinkTransaction,
85
+ receiptHash: string
86
+ ): Promise<Record<string, unknown> | null> => {
87
+ const result = await tx.run(
88
+ 'MATCH (v:ShareViewReceipt {receiptHash: $receiptHash}) RETURN v LIMIT 1',
89
+ { receiptHash }
90
+ );
91
+
92
+ return readNodeProperties(result, 'v');
93
+ };
94
+
95
+ /**
96
+ * Takes the receipt write lock. Must only run AFTER the share lock is held by
97
+ * the same transaction, so every receipt/consume path takes locks in the same
98
+ * share-then-receipt order as revoke/update.
99
+ */
100
+ const lockReceipt = async (
101
+ tx: ShareLinkTransaction,
102
+ receiptHash: string
103
+ ): Promise<Record<string, unknown> | null> => {
104
+ const result = await tx.run(
105
+ `MATCH (v:ShareViewReceipt {receiptHash: $receiptHash})
106
+ SET v.lockTick = coalesce(v.lockTick, 0) + 1
107
+ RETURN v`,
108
+ { receiptHash }
109
+ );
110
+
111
+ return readNodeProperties(result, 'v');
112
+ };
113
+
114
+ /**
115
+ * Persists one eligible receipt. Returns `false` (never throws for the caller's
116
+ * counting decision) when a hash collision, malformed input, stale share,
117
+ * missing/unverifiable eligibility source or ineligible owner makes the node
118
+ * unwritable; the public route still returns an identically shaped padding
119
+ * token, so no eligibility-dependent response shape is exposed.
120
+ *
121
+ * The share lock is taken FIRST (the same lock revoke/update/finalize take),
122
+ * then the complete current tuple/status/version/expiry is re-read under it,
123
+ * then the trusted policy source is consulted inside the same transaction, and
124
+ * only then is the receipt created. A pre-lock boolean is never authority.
125
+ */
126
+ export const persistShareViewReceipt = async (
127
+ input: PersistShareViewReceiptInput
128
+ ): Promise<boolean> => {
129
+ const receiptHash = hashShareViewReceipt(input.receipt);
130
+ if (!RECEIPT_HASH_RE.test(receiptHash)) return false;
131
+
132
+ // Required full binding. A receipt missing any immutable identity field must
133
+ // never be written, because consume could not distinguish it from a receipt
134
+ // for a different revision.
135
+ const namespace = requiredString(input.namespace);
136
+ const ownerProfileId = requiredString(input.ownerProfileId);
137
+ const shareId = requiredString(input.shareId);
138
+ const objectRef = requiredString(input.objectRef);
139
+ const operationId = requiredString(input.operationId);
140
+ const shareVersion = requiredPositiveInteger(input.shareVersion);
141
+ const contentVersion = requiredPositiveInteger(input.contentVersion);
142
+ const ttlSeconds = requiredFiniteNumber(input.ttlSeconds);
143
+
144
+ if (
145
+ namespace === null ||
146
+ ownerProfileId === null ||
147
+ shareId === null ||
148
+ objectRef === null ||
149
+ operationId === null ||
150
+ shareVersion === null ||
151
+ contentVersion === null ||
152
+ ttlSeconds === null ||
153
+ ttlSeconds < 0 ||
154
+ ttlSeconds > MAX_RECEIPT_TTL_SECONDS
155
+ ) {
156
+ return false;
157
+ }
158
+
159
+ // No trusted, transaction-compatible eligibility source means no write. The
160
+ // production age source is unknown today, so production passes no source and
161
+ // never issues a receipt or a count.
162
+ const eligibilitySource = input.eligibilitySource;
163
+ if (!eligibilitySource) return false;
164
+
165
+ await ensureShareLinkConstraints();
166
+
167
+ const now = input.now ?? (() => new Date());
168
+
169
+ try {
170
+ return await withShareLinkTransaction(async tx => {
171
+ // Share lock FIRST (same lock revoke/update/finalize take).
172
+ const shareProps = await lockShare(tx, shareId);
173
+ if (!shareProps) return false;
174
+
175
+ const currentNamespace = requiredString(shareProps.namespace);
176
+ const currentOwner = requiredString(shareProps.ownerProfileId);
177
+ const status = requiredString(shareProps.status);
178
+ const contentState = requiredString(shareProps.contentState);
179
+ const currentVersion = requiredPositiveInteger(shareProps.version);
180
+ const currentContentVersion = requiredPositiveInteger(shareProps.contentVersion);
181
+ const currentObjectRef = requiredString(shareProps.activeObjectRef);
182
+ const currentOperationId = requiredString(shareProps.activeObjectOperationId);
183
+
184
+ if (
185
+ currentNamespace === null ||
186
+ currentNamespace !== namespace ||
187
+ currentOwner === null ||
188
+ currentOwner !== ownerProfileId ||
189
+ status !== 'active' ||
190
+ contentState !== 'finalized' ||
191
+ currentVersion === null ||
192
+ currentVersion !== shareVersion ||
193
+ currentContentVersion === null ||
194
+ currentContentVersion !== contentVersion ||
195
+ currentObjectRef === null ||
196
+ currentObjectRef !== objectRef ||
197
+ currentOperationId === null ||
198
+ currentOperationId !== operationId
199
+ ) {
200
+ return false;
201
+ }
202
+
203
+ if (
204
+ shareProps.minorPolicyViewCountingEnabled !== true ||
205
+ shareProps.minorPolicyResolved !== true ||
206
+ shareProps.minorPolicyIsMinor !== false
207
+ )
208
+ return false;
209
+
210
+ // Optional share expiry: absent/null is unlimited, but a malformed
211
+ // value must never be interpreted as unlimited.
212
+ const shareExpiresMs = optionalInstantMs(shareProps.expiresAt);
213
+ if (shareExpiresMs === undefined) return false;
214
+
215
+ // Clock sampled only AFTER the blocking share lock, so a delayed lock
216
+ // can never produce a receipt whose TTL was computed against stale time.
217
+ const lockedNow = now();
218
+ if (shareExpiresMs !== null && shareExpiresMs <= lockedNow.getTime()) return false;
219
+
220
+ // Trusted policy source consulted under the same lock. A throwing or
221
+ // non-true result is ineligible and writes nothing.
222
+ let eligible: boolean;
223
+ try {
224
+ eligible = await eligibilitySource.isEligible(tx, {
225
+ namespace,
226
+ ownerProfileId,
227
+ shareId,
228
+ shareVersion,
229
+ contentVersion,
230
+ objectRef,
231
+ operationId,
232
+ now: lockedNow,
233
+ });
234
+ } catch {
235
+ return false;
236
+ }
237
+ if (eligible !== true) return false;
238
+
239
+ // Eligibility may itself wait on graph locks; sample again before writing.
240
+ const writeNow = now();
241
+ if (shareExpiresMs !== null && shareExpiresMs <= writeNow.getTime()) return false;
242
+ const nowIso = writeNow.toISOString();
243
+ const expiresAt = new Date(writeNow.getTime() + ttlSeconds * 1000).toISOString();
244
+
245
+ // CREATE, not MERGE: the `receiptHash` uniqueness constraint turns a
246
+ // collision into a typed failure instead of silently rebinding an
247
+ // existing receipt (and its future count) to a different immutable
248
+ // object. On collision we return `false` and count nothing.
249
+ await tx.run(
250
+ `CREATE (v:ShareViewReceipt {
251
+ receiptHash: $receiptHash,
252
+ namespace: $namespace,
253
+ ownerProfileId: $ownerProfileId,
254
+ shareId: $shareId,
255
+ shareVersion: $shareVersion,
256
+ contentVersion: $contentVersion,
257
+ objectRef: $objectRef,
258
+ operationId: $operationId,
259
+ createdAt: $now,
260
+ expiresAt: $expiresAt,
261
+ consumedAt: null
262
+ })`,
263
+ {
264
+ receiptHash,
265
+ namespace,
266
+ ownerProfileId,
267
+ shareId,
268
+ shareVersion,
269
+ contentVersion,
270
+ objectRef,
271
+ operationId,
272
+ now: nowIso,
273
+ expiresAt,
274
+ }
275
+ );
276
+
277
+ return true;
278
+ });
279
+ } catch {
280
+ // A collision/constraint race is not an eligibility signal. Fail closed
281
+ // on counting (the receipt simply never exists) without changing shape.
282
+ return false;
283
+ }
284
+ };
285
+
286
+ /**
287
+ * Returns the owner bound to a persisted receipt, or `null` for any unknown or
288
+ * padding token. The caller uses this only to re-resolve authoritative policy
289
+ * BEFORE the consume transaction; the transaction re-reads the binding under the
290
+ * locks, so this pre-read is never authority by itself.
291
+ */
292
+ export const readShareViewReceiptContext = async (
293
+ receipt: string,
294
+ namespace: string
295
+ ): Promise<{ ownerProfileId: string; shareId: string } | null> => {
296
+ const receiptHash = hashShareViewReceipt(receipt);
297
+ if (!RECEIPT_HASH_RE.test(receiptHash)) return null;
298
+
299
+ const trustedNamespace = requiredString(namespace);
300
+ if (trustedNamespace === null) return null;
301
+
302
+ await ensureShareLinkConstraints();
303
+
304
+ const props = await withShareLinkRead(runner => readReceiptByHash(runner, receiptHash));
305
+
306
+ if (!props) return null;
307
+
308
+ // A valid token minted in a different namespace is not this deployment's
309
+ // receipt. The persisted namespace must match the trusted configured one.
310
+ const receiptNamespace = requiredString(props.namespace);
311
+ if (receiptNamespace === null || receiptNamespace !== trustedNamespace) return null;
312
+
313
+ const ownerProfileId = requiredString(props.ownerProfileId);
314
+ const shareId = requiredString(props.shareId);
315
+ return ownerProfileId && shareId ? { ownerProfileId, shareId } : null;
316
+ };
317
+
318
+ export const readShareViewReceiptOwner = async (
319
+ receipt: string,
320
+ namespace: string
321
+ ): Promise<string | null> =>
322
+ (await readShareViewReceiptContext(receipt, namespace))?.ownerProfileId ?? null;
323
+
324
+ /**
325
+ * Atomically consumes one eligible receipt and increments the share view
326
+ * counter exactly once, even under concurrent duplicate acknowledgements.
327
+ *
328
+ * The whole guard chain runs under the share lock and then the receipt lock:
329
+ * - share must exist, be `active`/`finalized`, unexpired, and still bound to
330
+ * the same `contentVersion`/owner/namespace as the receipt;
331
+ * - the receipt must exist, be unconsumed and unexpired;
332
+ * - counting requires BOTH the freshly resolved authoritative policy and the
333
+ * committed snapshot policy to permit it.
334
+ *
335
+ * Any guard failure performs no write. Time is sampled after the locks are held.
336
+ */
337
+ export const consumeShareViewReceipt = async (
338
+ input: ConsumeShareViewReceiptInput
339
+ ): Promise<ConsumeShareViewReceiptOutcome> => {
340
+ const receiptHash = hashShareViewReceipt(input.receipt);
341
+ if (!RECEIPT_HASH_RE.test(receiptHash)) return 'not_found';
342
+
343
+ const trustedNamespace = requiredString(input.namespace);
344
+ if (trustedNamespace === null) return 'not_found';
345
+
346
+ await ensureShareLinkConstraints();
347
+
348
+ const now = input.now ?? (() => new Date());
349
+
350
+ try {
351
+ return await withShareLinkTransaction(async tx => {
352
+ // Discover the share from durable receipt state only; no caller field
353
+ // other than the opaque token is trusted.
354
+ const discovered = await readReceiptByHash(tx, receiptHash);
355
+ if (!discovered) return 'not_found';
356
+
357
+ const shareId = requiredString(discovered.shareId);
358
+ if (!shareId) return 'not_found';
359
+
360
+ // Share lock FIRST (same lock revoke/update/finalize take).
361
+ const shareProps = await lockShare(tx, shareId);
362
+ if (!shareProps) return 'not_found';
363
+
364
+ // Then the receipt lock, then re-read both under the locks.
365
+ const receiptProps = await lockReceipt(tx, receiptHash);
366
+ if (!receiptProps) return 'not_found';
367
+
368
+ // Production time sampled AFTER both locks are held.
369
+ const lockedNow = now();
370
+
371
+ // Complete, well-formed immutable binding. Every identity/version
372
+ // field is required; malformed durable state fails closed and can
373
+ // never be interpreted as a default.
374
+ const namespace = requiredString(shareProps.namespace);
375
+ const ownerProfileId = requiredString(shareProps.ownerProfileId);
376
+ const status = requiredString(shareProps.status);
377
+ const contentState = requiredString(shareProps.contentState);
378
+ const contentVersion = requiredPositiveInteger(shareProps.contentVersion);
379
+ const shareVersion = requiredPositiveInteger(shareProps.version);
380
+ const shareObjectRef = requiredString(shareProps.activeObjectRef);
381
+ const shareOperationId = requiredString(shareProps.activeObjectOperationId);
382
+
383
+ const receiptNamespace = requiredString(receiptProps.namespace);
384
+ const receiptOwner = requiredString(receiptProps.ownerProfileId);
385
+ const receiptShareId = requiredString(receiptProps.shareId);
386
+ const receiptShareVersion = requiredPositiveInteger(receiptProps.shareVersion);
387
+ const receiptContentVersion = requiredPositiveInteger(receiptProps.contentVersion);
388
+ const receiptObjectRef = requiredString(receiptProps.objectRef);
389
+ const receiptOperationId = requiredString(receiptProps.operationId);
390
+ const receiptExpiresMs = requiredInstantMs(receiptProps.expiresAt);
391
+
392
+ // Optional instants: absent/null is valid "unset"; a malformed
393
+ // value must never be read as unset/unlimited.
394
+ const consumedAtMs = optionalInstantMs(receiptProps.consumedAt);
395
+ const shareExpiresMs = optionalInstantMs(shareProps.expiresAt);
396
+ if (consumedAtMs === undefined || shareExpiresMs === undefined) return 'not_found';
397
+
398
+ // The trusted configured namespace must match BOTH the receipt and the
399
+ // share. A token from another namespace can never be consumed here.
400
+ if (
401
+ namespace === null ||
402
+ namespace !== trustedNamespace ||
403
+ receiptNamespace === null ||
404
+ receiptNamespace !== trustedNamespace
405
+ ) {
406
+ return 'binding_mismatch';
407
+ }
408
+
409
+ if (
410
+ ownerProfileId === null ||
411
+ shareId === '' ||
412
+ shareObjectRef === null ||
413
+ shareOperationId === null ||
414
+ shareVersion === null ||
415
+ contentVersion === null
416
+ ) {
417
+ return 'not_found';
418
+ }
419
+
420
+ // A malformed receipt version is unreadable durable state, not a
421
+ // binding mismatch to compare; fail closed before any write.
422
+ if (receiptShareVersion === null || receiptContentVersion === null) {
423
+ return 'not_found';
424
+ }
425
+
426
+ if (
427
+ receiptOwner !== ownerProfileId ||
428
+ receiptShareId !== shareId ||
429
+ // Full immutable identity, not just the content version.
430
+ receiptShareVersion !== shareVersion ||
431
+ receiptObjectRef !== shareObjectRef ||
432
+ receiptOperationId !== shareOperationId
433
+ ) {
434
+ return 'binding_mismatch';
435
+ }
436
+ // A present `consumedAt` is authoritative: a malformed non-null value
437
+ // must never be treated as "unconsumed". Absence/null is unconsumed.
438
+ if (consumedAtMs !== null) return 'already_consumed';
439
+ // Required, parseable receipt expiry. Missing/garbage fails closed.
440
+ if (receiptExpiresMs === null || receiptExpiresMs <= lockedNow.getTime()) {
441
+ return 'expired';
442
+ }
443
+ if (status !== 'active' || contentState !== 'finalized') return 'not_active';
444
+ if (shareExpiresMs !== null && shareExpiresMs <= lockedNow.getTime()) {
445
+ return 'not_active';
446
+ }
447
+ if (receiptContentVersion !== contentVersion) return 'version_mismatch';
448
+
449
+ const committedEligible =
450
+ shareProps.minorPolicyViewCountingEnabled === true &&
451
+ shareProps.minorPolicyResolved === true &&
452
+ shareProps.minorPolicyIsMinor === false;
453
+
454
+ // Counting requires BOTH the committed snapshot policy and the
455
+ // transaction-compatible trusted policy source to permit it. There
456
+ // is no pre-lock boolean authority; an absent source is ineligible.
457
+ if (!committedEligible) return 'ineligible';
458
+
459
+ const eligibilitySource = input.eligibilitySource;
460
+ if (!eligibilitySource) return 'ineligible';
461
+
462
+ let eligible: boolean;
463
+ try {
464
+ eligible = await eligibilitySource.isEligible(tx, {
465
+ namespace,
466
+ ownerProfileId,
467
+ shareId,
468
+ shareVersion,
469
+ contentVersion,
470
+ objectRef: shareObjectRef,
471
+ operationId: shareOperationId,
472
+ now: lockedNow,
473
+ });
474
+ } catch {
475
+ return 'ineligible';
476
+ }
477
+ if (eligible !== true) return 'ineligible';
478
+
479
+ const writeNow = now();
480
+ if (receiptExpiresMs <= writeNow.getTime()) return 'expired';
481
+ if (shareExpiresMs !== null && shareExpiresMs <= writeNow.getTime())
482
+ return 'not_active';
483
+ const nowIso = writeNow.toISOString();
484
+
485
+ const write = await tx.run(
486
+ `MATCH (v:ShareViewReceipt {receiptHash: $receiptHash})
487
+ MATCH (s:ShareLink {id: $shareId})
488
+ SET v.consumedAt = $now,
489
+ s.viewCount = coalesce(s.viewCount, 0) + 1,
490
+ s.lastViewedAt = $now`,
491
+ { receiptHash, shareId, now: nowIso }
492
+ );
493
+
494
+ if (!write) return 'not_found';
495
+ return 'consumed';
496
+ });
497
+ } catch {
498
+ return 'not_found';
499
+ }
500
+ };
501
+
502
+ /**
503
+ * Explicit one-shot pruning of expired/consumed receipt nodes.
504
+ *
505
+ * `MATCH ... WITH v LIMIT n DETACH DELETE v RETURN count(*)` reclaims at most
506
+ * `limit` nodes per call. It never touches `ShareLink`,
507
+ * `ShareLinkOperation`, reservations, cleanup jobs or share content — those
508
+ * have their own retention rules. The disabled maintenance pass invokes this
509
+ * with a bounded limit and an explicit no-inline-retry graph transaction.
510
+ */
511
+ export const pruneShareViewReceipts = async (
512
+ input: PruneShareViewReceiptsInput
513
+ ): Promise<number> => {
514
+ const namespace = requiredString(input.namespace);
515
+ if (namespace === null) {
516
+ failShareLink('INVALID_INPUT', 'a trusted namespace is required for receipt pruning');
517
+ }
518
+
519
+ if (input.limit !== undefined && (!Number.isFinite(input.limit) || input.limit < 1)) {
520
+ // Never let NaN/Infinity reach Cypher as an unbounded or malformed limit.
521
+ failShareLink('INVALID_INPUT', 'receipt prune limit must be a positive finite number');
522
+ }
523
+
524
+ const requested = input.limit ?? DEFAULT_PRUNE_LIMIT;
525
+ const limit = Math.max(1, Math.min(MAX_PRUNE_LIMIT, Math.floor(requested)));
526
+ const nowIso = (input.now ?? new Date()).toISOString();
527
+
528
+ await ensureShareLinkConstraints();
529
+
530
+ return withShareLinkRead(async runner => {
531
+ const result = await runner.run(
532
+ `MATCH (v:ShareViewReceipt {namespace: $namespace})
533
+ WHERE (v.expiresAt IS NOT NULL AND v.expiresAt <= $now)
534
+ OR v.consumedAt IS NOT NULL
535
+ WITH v LIMIT toInteger($limit)
536
+ DETACH DELETE v
537
+ RETURN count(*) AS deleted`,
538
+ { namespace, now: nowIso, limit }
539
+ );
540
+
541
+ const raw = result.records[0]?.get('deleted') as
542
+ number | { toNumber?: () => number } | undefined;
543
+ if (typeof raw === 'number') return raw;
544
+ if (raw && typeof raw.toNumber === 'function') return raw.toNumber();
545
+
546
+ return 0;
547
+ }, input.transaction);
548
+ };