@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,545 @@
1
+ import { int } from 'neo4j-driver';
2
+
3
+ import {
4
+ clampShareLinkRecoveryBatch,
5
+ classifyOperationBinding,
6
+ classifyReservationBinding,
7
+ computeLeaseExpiry,
8
+ isLeaseActive,
9
+ } from '@helpers/share-link-lifecycle';
10
+ import { neogma } from '@instance';
11
+
12
+ import { ensureShareLinkConstraints } from '../../models/share-link-constraints';
13
+ import { failShareLink } from './errors';
14
+ import {
15
+ deleteReservation,
16
+ enqueueCleanupJob,
17
+ lockShare,
18
+ markOperationAbandoned,
19
+ readNodeProperties,
20
+ readReservation,
21
+ toShareLinkOperationRecord,
22
+ toShareLinkRecord,
23
+ toShareLinkReservationRecord,
24
+ } from './helpers';
25
+ import { getShareLink } from './read';
26
+ import { withShareLinkRead, withShareLinkTransaction, type Neo4jQueryResult } from './transaction';
27
+ import type {
28
+ AbandonRecoveredReservationInput,
29
+ AbandonRecoveredReservationResult,
30
+ ClaimRecoverableReservationInput,
31
+ ClaimRecoverableReservationResult,
32
+ DiscoverRecoverableReservationsInput,
33
+ DiscoverRecoverableReservationsResult,
34
+ ShareLinkRecoveryKey,
35
+ ShareLinkRecoveryTarget,
36
+ } from './types';
37
+
38
+ type GraphRunner = {
39
+ run: (query: string, params?: Record<string, unknown>) => Promise<Neo4jQueryResult>;
40
+ };
41
+
42
+ const isOpaqueIdentifier = (value: unknown, maxLength = 128): value is string =>
43
+ typeof value === 'string' &&
44
+ value.length >= 1 &&
45
+ value.length <= maxLength &&
46
+ /^[A-Za-z0-9._~-]+$/.test(value);
47
+
48
+ const assertRecoveryKey = (input: ShareLinkRecoveryKey): void => {
49
+ if (
50
+ !isOpaqueIdentifier(input.namespace) ||
51
+ !isOpaqueIdentifier(input.ownerProfileId) ||
52
+ !isOpaqueIdentifier(input.shareId) ||
53
+ !isOpaqueIdentifier(input.operationId)
54
+ ) {
55
+ failShareLink('INVALID_INPUT', 'recovery key must be well-formed opaque identifiers');
56
+ }
57
+ };
58
+
59
+ function assertLeaseOwner(leaseOwner: unknown): asserts leaseOwner is string {
60
+ if (!isOpaqueIdentifier(leaseOwner)) {
61
+ failShareLink('INVALID_INPUT', 'leaseOwner must be a well-formed opaque identifier');
62
+ }
63
+ }
64
+
65
+ /** Do not let permissive legacy record converters normalize corrupt fences. */
66
+ const hasValidShareFence = (props: Record<string, unknown>): boolean =>
67
+ ['version', 'contentVersion', 'generation'].every(
68
+ field =>
69
+ Number.isSafeInteger(props[field]) &&
70
+ (props[field] as number) >= 1 &&
71
+ (props[field] as number) < Number.MAX_SAFE_INTEGER
72
+ ) &&
73
+ typeof props.status === 'string' &&
74
+ ['pending', 'active', 'stopped'].includes(props.status) &&
75
+ typeof props.contentState === 'string' &&
76
+ ['staging', 'finalized', 'content_missing'].includes(props.contentState);
77
+
78
+ /** Metadata will be activated without the original request, so validate it before conversion. */
79
+ const hasValidReservationIntent = (props: Record<string, unknown>): boolean =>
80
+ typeof props.title === 'string' &&
81
+ (props.note == null || typeof props.note === 'string') &&
82
+ (props.expiresAt == null ||
83
+ (typeof props.expiresAt === 'string' && Number.isFinite(Date.parse(props.expiresAt)))) &&
84
+ Number.isSafeInteger(props.selectedCount) &&
85
+ (props.selectedCount as number) >= 1 &&
86
+ (props.selectedCount as number) <= 50 &&
87
+ (props.opKind !== 'create' ||
88
+ (typeof props.objectRef === 'string' && props.contentVersion === 1)) &&
89
+ (props.opKind !== 'update' ||
90
+ props.contentVersion == null ||
91
+ props.contentVersion === (props.baseContentVersion as number) + 1);
92
+
93
+ /** Binds the idempotency operation record to the exact scoped key. */
94
+ const readOperationForRecovery = async (
95
+ runner: GraphRunner,
96
+ input: ShareLinkRecoveryKey
97
+ ): Promise<Record<string, unknown> | null> => {
98
+ const result = await runner.run(
99
+ `MATCH (o:ShareLinkOperation {operationId: $operationId})
100
+ WHERE o.namespace = $namespace
101
+ AND o.ownerProfileId = $ownerProfileId
102
+ AND o.shareId = $shareId
103
+ RETURN o LIMIT 1`,
104
+ input
105
+ );
106
+
107
+ return readNodeProperties(result, 'o');
108
+ };
109
+
110
+ /**
111
+ * Read-only scoped recovery lookup derived entirely from persisted Brain state.
112
+ *
113
+ * A reservation that matches the exact namespace/owner/share/operation key is
114
+ * returned with its committed share snapshot. When the reservation is already
115
+ * consumed but the operation committed — a finalize whose response was lost —
116
+ * the current committed share is returned instead, so a retry can observe success
117
+ * without re-uploading or reconstructing the deleted reservation. A wrong
118
+ * namespace/owner/operation returns `absent`; this function performs no remote
119
+ * I/O and never trusts a caller-supplied object ref, hash or generation.
120
+ */
121
+ export const readShareLinkRecoveryTarget = async (
122
+ input: ShareLinkRecoveryKey
123
+ ): Promise<ShareLinkRecoveryTarget> => {
124
+ assertRecoveryKey(input);
125
+
126
+ const share = await getShareLink({
127
+ shareId: input.shareId,
128
+ namespace: input.namespace,
129
+ ownerProfileId: input.ownerProfileId,
130
+ });
131
+
132
+ if (!share) return { state: 'absent' };
133
+
134
+ const reservationResult = await neogma.queryRunner.run(
135
+ 'MATCH (r:ShareLinkReservation {shareId: $shareId}) RETURN r LIMIT 1',
136
+ { shareId: input.shareId }
137
+ );
138
+ const reservationProps = readNodeProperties(reservationResult, 'r');
139
+
140
+ if (reservationProps && classifyReservationBinding(reservationProps, input) === 'ok') {
141
+ return {
142
+ state: 'reservation',
143
+ share,
144
+ reservation: toShareLinkReservationRecord(reservationProps),
145
+ };
146
+ }
147
+
148
+ const operationProps = await readOperationForRecovery(neogma.queryRunner, input);
149
+
150
+ if (operationProps && classifyOperationBinding(operationProps, input) === 'ok') {
151
+ const operation = toShareLinkOperationRecord(operationProps);
152
+
153
+ if (operation.status === 'committed') {
154
+ return { state: 'committed', share };
155
+ }
156
+ }
157
+
158
+ return { state: 'absent' };
159
+ };
160
+
161
+ /**
162
+ * Enumerates only *expired in-flight* reservation keys for one explicit
163
+ * namespace. The bounded query joins the share and idempotency operation so it
164
+ * returns neither active leases nor stale/foreign bindings; malformed records
165
+ * fail to match and are therefore never offered for reclamation.
166
+ */
167
+ export const discoverRecoverableReservations = async (
168
+ input: DiscoverRecoverableReservationsInput
169
+ ): Promise<DiscoverRecoverableReservationsResult> => {
170
+ await ensureShareLinkConstraints();
171
+
172
+ if (!isOpaqueIdentifier(input.namespace)) {
173
+ failShareLink('INVALID_INPUT', 'discovery requires an explicit opaque namespace');
174
+ }
175
+
176
+ const now = input.now ?? new Date();
177
+ const limit = clampShareLinkRecoveryBatch(input.limit);
178
+
179
+ const result = await withShareLinkRead(
180
+ runner =>
181
+ runner.run(
182
+ `MATCH (r:ShareLinkReservation {namespace: $namespace})
183
+ MATCH (s:ShareLink {id: r.shareId})
184
+ MATCH (o:ShareLinkOperation {operationId: r.operationId})
185
+ WHERE r.leaseExpiresAt <= $now
186
+ AND s.namespace = $namespace
187
+ AND s.ownerProfileId = r.ownerProfileId
188
+ AND s.status <> 'stopped'
189
+ AND s.generation = r.generation
190
+ AND s.version = r.baseVersion
191
+ AND s.contentVersion = r.baseContentVersion
192
+ AND o.status = 'in_progress'
193
+ AND o.shareId = r.shareId
194
+ AND o.namespace = r.namespace
195
+ AND o.ownerProfileId = r.ownerProfileId
196
+ RETURN DISTINCT r.namespace AS namespace,
197
+ r.ownerProfileId AS ownerProfileId,
198
+ r.shareId AS shareId,
199
+ r.operationId AS operationId,
200
+ r.leaseExpiresAt AS leaseExpiresAt
201
+ ORDER BY leaseExpiresAt ASC, shareId ASC
202
+ LIMIT $limit`,
203
+ { namespace: input.namespace, now: now.toISOString(), limit: int(limit) }
204
+ ),
205
+ { timeoutMs: input.transactionTimeoutMs, noInlineRetry: input.noInlineRetry }
206
+ );
207
+
208
+ const keys: ShareLinkRecoveryKey[] = [];
209
+
210
+ for (const record of result.records) {
211
+ const namespace = record.get('namespace');
212
+ const ownerProfileId = record.get('ownerProfileId');
213
+ const shareId = record.get('shareId');
214
+ const operationId = record.get('operationId');
215
+
216
+ if (
217
+ typeof namespace === 'string' &&
218
+ typeof ownerProfileId === 'string' &&
219
+ typeof shareId === 'string' &&
220
+ typeof operationId === 'string'
221
+ ) {
222
+ keys.push({ namespace, ownerProfileId, shareId, operationId });
223
+ }
224
+ }
225
+
226
+ return { keys };
227
+ };
228
+
229
+ /**
230
+ * Atomically claims one exact expired reservation for a recovery worker.
231
+ *
232
+ * Under the share write lock the persisted reservation, operation and share are
233
+ * re-read and validated against the current version/content version, generation,
234
+ * owner/namespace/operation binding and non-stopped state before anything is
235
+ * written. A claim then bumps the share and reservation generation together and
236
+ * renews only the lease owner/expiry, so two claimers cannot both win and the
237
+ * immutable object tuple, request hashes, metadata and original operation id are
238
+ * preserved. Any malformed persisted binding fails closed.
239
+ */
240
+ export const claimRecoverableReservation = async (
241
+ input: ClaimRecoverableReservationInput
242
+ ): Promise<ClaimRecoverableReservationResult> => {
243
+ await ensureShareLinkConstraints();
244
+ assertRecoveryKey(input);
245
+ assertLeaseOwner(input.leaseOwner);
246
+
247
+ return withShareLinkTransaction(
248
+ async tx => {
249
+ const lockedProps = await lockShare(tx, input.shareId);
250
+ // Production uses the actual clock after the lock is held; an injected
251
+ // test clock is allowed for deterministic lease windows.
252
+ const now = input.now ?? new Date();
253
+ const nowIso = now.toISOString();
254
+
255
+ if (!lockedProps) return { outcome: 'not_claimable', reason: 'absent' };
256
+
257
+ const share = toShareLinkRecord(lockedProps);
258
+
259
+ if (
260
+ share.id !== input.shareId ||
261
+ share.namespace !== input.namespace ||
262
+ share.ownerProfileId !== input.ownerProfileId
263
+ ) {
264
+ return { outcome: 'not_claimable', reason: 'absent' };
265
+ }
266
+ if (!hasValidShareFence(lockedProps)) {
267
+ return { outcome: 'not_claimable', reason: 'malformed_binding' };
268
+ }
269
+
270
+ const reservationProps = await readReservation(tx, input.shareId);
271
+
272
+ if (!reservationProps) {
273
+ return { outcome: 'not_claimable', reason: 'absent' };
274
+ }
275
+
276
+ const binding = classifyReservationBinding(reservationProps, input);
277
+
278
+ if (binding === 'malformed_binding' || !hasValidReservationIntent(reservationProps)) {
279
+ return { outcome: 'not_claimable', reason: 'malformed_binding' };
280
+ }
281
+ if (binding === 'binding_mismatch') {
282
+ return { outcome: 'not_claimable', reason: 'binding_mismatch' };
283
+ }
284
+
285
+ const reservation = toShareLinkReservationRecord(reservationProps);
286
+
287
+ if (share.status === 'stopped') {
288
+ return { outcome: 'not_claimable', reason: 'share_not_recoverable' };
289
+ }
290
+ if (share.generation !== reservation.generation) {
291
+ return { outcome: 'not_claimable', reason: 'stale_generation' };
292
+ }
293
+ if (
294
+ share.version !== reservation.baseVersion ||
295
+ share.contentVersion !== reservation.baseContentVersion
296
+ ) {
297
+ return { outcome: 'not_claimable', reason: 'version_changed' };
298
+ }
299
+ if (isLeaseActive(reservation.leaseExpiresAt, now)) {
300
+ return { outcome: 'not_claimable', reason: 'lease_active' };
301
+ }
302
+
303
+ const operationProps = await readOperationForRecovery(tx, input);
304
+
305
+ if (!operationProps) {
306
+ return { outcome: 'not_claimable', reason: 'operation_not_in_progress' };
307
+ }
308
+
309
+ const operationBinding = classifyOperationBinding(operationProps, {
310
+ ...input,
311
+ opKind: reservation.opKind,
312
+ clientRequestId: reservation.clientRequestId,
313
+ });
314
+
315
+ if (operationBinding === 'malformed_binding') {
316
+ return { outcome: 'not_claimable', reason: 'malformed_binding' };
317
+ }
318
+ if (
319
+ !/^[a-f0-9]{64}$/.test(reservation.requestHash) ||
320
+ typeof operationProps.requestHash !== 'string' ||
321
+ !/^[a-f0-9]{64}$/.test(operationProps.requestHash)
322
+ ) {
323
+ return { outcome: 'not_claimable', reason: 'malformed_binding' };
324
+ }
325
+ if (reservation.requestHash !== operationProps.requestHash) {
326
+ return { outcome: 'not_claimable', reason: 'binding_mismatch' };
327
+ }
328
+ if (operationBinding === 'binding_mismatch') {
329
+ return { outcome: 'not_claimable', reason: 'binding_mismatch' };
330
+ }
331
+ if (toShareLinkOperationRecord(operationProps).status !== 'in_progress') {
332
+ return { outcome: 'not_claimable', reason: 'operation_not_in_progress' };
333
+ }
334
+
335
+ const nextGeneration = reservation.generation + 1;
336
+ const leaseExpiresAt = computeLeaseExpiry(now, input.leaseMs);
337
+
338
+ await tx.run(
339
+ `MATCH (s:ShareLink {id: $shareId})
340
+ SET s.generation = $generation,
341
+ s.lockTick = coalesce(s.lockTick, 0) + 1,
342
+ s.updatedAt = $now`,
343
+ { shareId: input.shareId, generation: nextGeneration, now: nowIso }
344
+ );
345
+ await tx.run(
346
+ `MATCH (r:ShareLinkReservation {shareId: $shareId})
347
+ SET r.generation = $generation,
348
+ r.leaseOwner = $leaseOwner,
349
+ r.leaseExpiresAt = $leaseExpiresAt,
350
+ r.updatedAt = $now`,
351
+ {
352
+ shareId: input.shareId,
353
+ generation: nextGeneration,
354
+ leaseOwner: input.leaseOwner,
355
+ leaseExpiresAt,
356
+ now: nowIso,
357
+ }
358
+ );
359
+
360
+ const claimedShareProps = await tx.run(
361
+ 'MATCH (s:ShareLink {id: $shareId}) RETURN s LIMIT 1',
362
+ { shareId: input.shareId }
363
+ );
364
+ const claimedReservationProps = await readReservation(tx, input.shareId);
365
+
366
+ const claimedShare = readNodeProperties(claimedShareProps, 's');
367
+ const claimedReservation = claimedReservationProps;
368
+
369
+ if (!claimedShare || !claimedReservation) {
370
+ failShareLink('CONFLICT', 'failed to persist the recovery claim');
371
+ }
372
+
373
+ return {
374
+ outcome: 'claimed',
375
+ share: toShareLinkRecord(claimedShare),
376
+ reservation: toShareLinkReservationRecord(claimedReservation),
377
+ };
378
+ },
379
+ { timeoutMs: input.transactionTimeoutMs, noInlineRetry: input.noInlineRetry }
380
+ );
381
+ };
382
+
383
+ /**
384
+ * Fenced recovery abandon.
385
+ *
386
+ * This is deliberately separate from the internal `abandonReservation`: it
387
+ * requires both the claimed generation and lease owner AND an active lease, so a
388
+ * worker whose lease already lapsed — or a stale generation, even when the
389
+ * claimant string is reused — cannot destroy work another worker now owns.
390
+ * Intentional stop/revoke semantics still use the internal path and are
391
+ * untouched.
392
+ */
393
+ export const abandonRecoveredReservation = async (
394
+ input: AbandonRecoveredReservationInput
395
+ ): Promise<AbandonRecoveredReservationResult> => {
396
+ await ensureShareLinkConstraints();
397
+ assertRecoveryKey(input);
398
+ assertLeaseOwner(input.leaseOwner);
399
+
400
+ if (!Number.isSafeInteger(input.generation) || input.generation < 1) {
401
+ failShareLink('INVALID_INPUT', 'generation must be a positive safe integer');
402
+ }
403
+
404
+ return withShareLinkTransaction(
405
+ async tx => {
406
+ const lockedProps = await lockShare(tx, input.shareId);
407
+ const now = input.now ?? new Date();
408
+ const nowIso = now.toISOString();
409
+
410
+ if (!lockedProps) return { outcome: 'not_claimable', reason: 'absent' };
411
+
412
+ const share = toShareLinkRecord(lockedProps);
413
+
414
+ if (
415
+ share.id !== input.shareId ||
416
+ share.namespace !== input.namespace ||
417
+ share.ownerProfileId !== input.ownerProfileId
418
+ ) {
419
+ return { outcome: 'not_claimable', reason: 'absent' };
420
+ }
421
+ if (!hasValidShareFence(lockedProps)) {
422
+ return { outcome: 'not_claimable', reason: 'malformed_binding' };
423
+ }
424
+
425
+ const reservationProps = await readReservation(tx, input.shareId);
426
+
427
+ if (!reservationProps) {
428
+ const operationProps = await readOperationForRecovery(tx, input);
429
+
430
+ if (
431
+ operationProps &&
432
+ classifyOperationBinding(operationProps, input) === 'ok' &&
433
+ toShareLinkOperationRecord(operationProps).status === 'committed'
434
+ ) {
435
+ return { outcome: 'already_finalized', share };
436
+ }
437
+
438
+ return { outcome: 'already_absent' };
439
+ }
440
+
441
+ const binding = classifyReservationBinding(reservationProps, input);
442
+
443
+ if (binding === 'malformed_binding' || !hasValidReservationIntent(reservationProps)) {
444
+ return { outcome: 'not_claimable', reason: 'malformed_binding' };
445
+ }
446
+ if (binding === 'binding_mismatch') {
447
+ return { outcome: 'already_absent' };
448
+ }
449
+
450
+ const reservation = toShareLinkReservationRecord(reservationProps);
451
+
452
+ if (
453
+ share.status === 'stopped' ||
454
+ (reservation.objectRef && share.activeObjectRef === reservation.objectRef)
455
+ ) {
456
+ return { outcome: 'not_claimable', reason: 'share_not_recoverable' };
457
+ }
458
+ if (share.generation !== reservation.generation) {
459
+ return { outcome: 'not_claimable', reason: 'stale_generation' };
460
+ }
461
+ if (
462
+ share.version !== reservation.baseVersion ||
463
+ share.contentVersion !== reservation.baseContentVersion
464
+ ) {
465
+ return { outcome: 'not_claimable', reason: 'version_changed' };
466
+ }
467
+
468
+ if (
469
+ reservation.generation !== input.generation ||
470
+ reservation.leaseOwner !== input.leaseOwner
471
+ ) {
472
+ return { outcome: 'not_claimable', reason: 'stale_generation' };
473
+ }
474
+ if (!isLeaseActive(reservation.leaseExpiresAt, now)) {
475
+ return { outcome: 'not_claimable', reason: 'lease_lapsed' };
476
+ }
477
+
478
+ const operationProps = await readOperationForRecovery(tx, input);
479
+
480
+ if (!operationProps) {
481
+ return { outcome: 'not_claimable', reason: 'operation_not_in_progress' };
482
+ }
483
+
484
+ const operationBinding = classifyOperationBinding(operationProps, {
485
+ ...input,
486
+ opKind: reservation.opKind,
487
+ clientRequestId: reservation.clientRequestId,
488
+ });
489
+
490
+ if (operationBinding === 'malformed_binding') {
491
+ return { outcome: 'not_claimable', reason: 'malformed_binding' };
492
+ }
493
+ if (
494
+ !/^[a-f0-9]{64}$/.test(reservation.requestHash) ||
495
+ typeof operationProps.requestHash !== 'string' ||
496
+ !/^[a-f0-9]{64}$/.test(operationProps.requestHash)
497
+ ) {
498
+ return { outcome: 'not_claimable', reason: 'malformed_binding' };
499
+ }
500
+ if (reservation.requestHash !== operationProps.requestHash) {
501
+ return { outcome: 'not_claimable', reason: 'binding_mismatch' };
502
+ }
503
+ if (operationBinding === 'binding_mismatch') {
504
+ return { outcome: 'not_claimable', reason: 'binding_mismatch' };
505
+ }
506
+
507
+ const operation = toShareLinkOperationRecord(operationProps);
508
+
509
+ if (operation.status === 'committed') {
510
+ return { outcome: 'already_finalized', share };
511
+ }
512
+ if (operation.status !== 'in_progress') {
513
+ return { outcome: 'not_claimable', reason: 'operation_not_in_progress' };
514
+ }
515
+
516
+ let cleanupQueuedFor: string | null = null;
517
+
518
+ if (reservation.objectRef) {
519
+ await enqueueCleanupJob(tx, {
520
+ objectRef: reservation.objectRef,
521
+ operationId: reservation.operationId,
522
+ namespace: reservation.namespace,
523
+ ownerProfileId: reservation.ownerProfileId,
524
+ shareId: reservation.shareId,
525
+ contentVersion: reservation.contentVersion ?? reservation.baseContentVersion,
526
+ reason: 'abandoned',
527
+ now: nowIso,
528
+ });
529
+ cleanupQueuedFor = reservation.objectRef;
530
+ }
531
+
532
+ await markOperationAbandoned(tx, {
533
+ namespace: reservation.namespace,
534
+ ownerProfileId: reservation.ownerProfileId,
535
+ opKind: reservation.opKind,
536
+ clientRequestId: reservation.clientRequestId,
537
+ now: nowIso,
538
+ });
539
+ await deleteReservation(tx, input.shareId);
540
+
541
+ return { outcome: 'abandoned', cleanupQueuedFor };
542
+ },
543
+ { timeoutMs: input.transactionTimeoutMs, noInlineRetry: input.noInlineRetry }
544
+ );
545
+ };