@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,28 @@
1
+ import {
2
+ consumeInboxQueueOnce,
3
+ consumeInboxDeadLettersOnce,
4
+ dispatchInboxJobs,
5
+ } from './helpers/inbox-queue.helpers';
6
+
7
+ let running = true;
8
+ process.on('SIGINT', () => {
9
+ running = false;
10
+ });
11
+ process.on('SIGTERM', () => {
12
+ running = false;
13
+ });
14
+
15
+ /** Run separately from the HTTP process; restarting either process leaves durable jobs intact. */
16
+ const run = async (): Promise<void> => {
17
+ while (running) {
18
+ try {
19
+ await dispatchInboxJobs();
20
+ await consumeInboxQueueOnce(10);
21
+ await consumeInboxDeadLettersOnce();
22
+ } catch {
23
+ console.error('Inbox queue iteration failed; durable jobs will be retried.');
24
+ await new Promise(resolve => setTimeout(resolve, 1000));
25
+ }
26
+ }
27
+ };
28
+ void run();
@@ -21,6 +21,7 @@ export const AuthGrant = ModelFactory<FlatAuthGrantType, AuthGrantRelationships>
21
21
  challenge: { type: 'string', required: true, uniqueItems: true },
22
22
  status: { type: 'string', required: true },
23
23
  scope: { type: 'string', required: true },
24
+ actAs: { type: 'string', required: false },
24
25
  createdAt: { type: 'string', required: true },
25
26
  expiresAt: { type: 'string', required: false },
26
27
  },
@@ -7,11 +7,7 @@ import { Boost, BoostInstance } from './Boost';
7
7
  import { AppStoreListing, AppStoreListingInstance } from './AppStoreListing';
8
8
 
9
9
  export type CredentialActivityEventType =
10
- | 'CREATED'
11
- | 'DELIVERED'
12
- | 'CLAIMED'
13
- | 'EXPIRED'
14
- | 'FAILED';
10
+ 'CREATED' | 'DELIVERED' | 'CLAIMED' | 'EXPIRED' | 'FAILED';
15
11
 
16
12
  export type CredentialActivityRecipientType = 'profile' | 'email' | 'phone';
17
13
 
@@ -32,6 +28,7 @@ export type CredentialActivityType = {
32
28
  eventType: CredentialActivityEventType;
33
29
  timestamp: string;
34
30
  actorProfileId?: string;
31
+ onBehalfOf?: string;
35
32
  recipientType: CredentialActivityRecipientType;
36
33
  recipientIdentifier: string;
37
34
  boostUri?: string;
@@ -70,6 +67,7 @@ export const CredentialActivity = ModelFactory<
70
67
  },
71
68
  timestamp: { type: 'string', required: true },
72
69
  actorProfileId: { type: 'string', required: false },
70
+ onBehalfOf: { type: 'string', required: false },
73
71
  recipientType: {
74
72
  type: 'string',
75
73
  required: true,
@@ -36,16 +36,18 @@ export const CredentialRefresh = ModelFactory<
36
36
  {
37
37
  label: 'CredentialRefresh',
38
38
  schema: {
39
+ inboxCredentialId: { type: 'string', required: false },
40
+ inboxCredentialStatus: { type: 'string', required: false },
39
41
  refreshId: { type: 'string', required: true },
40
42
  issuerProfileId: { type: 'string', required: true },
41
43
  issuerDid: { type: 'string', required: true },
42
44
  holderProfileId: { type: 'string', required: false },
43
- holderDid: { type: 'string', required: true },
45
+ holderDid: { type: 'string', required: false },
44
46
  credentialId: { type: 'string', required: true },
45
47
  state: {
46
48
  type: 'string',
47
49
  required: true,
48
- enum: ['awaiting_claim', 'active', 'revoked'],
50
+ enum: ['pending_holder', 'awaiting_claim', 'active', 'revoked'],
49
51
  },
50
52
  currentVersion: { type: 'number', required: true },
51
53
  etag: { type: 'string', required: false },
@@ -6,6 +6,7 @@ import { Profile, ProfileInstance } from './Profile';
6
6
  import ContactMethod, { ContactMethodInstance } from './ContactMethod';
7
7
 
8
8
  export type InboxCredentialType = {
9
+ refreshId?: string;
9
10
  id: string;
10
11
  credential?: string; // Versioned JWE; removed immediately after successful finalization
11
12
  isSigned: boolean;
@@ -86,6 +87,7 @@ export const InboxCredential = ModelFactory<InboxCredentialType, InboxCredential
86
87
  {
87
88
  label: 'InboxCredential',
88
89
  schema: {
90
+ refreshId: { type: 'string', required: false },
89
91
  id: { type: 'string', required: true, uniqueItems: true },
90
92
  credential: { type: 'string', required: false },
91
93
  isSigned: { type: 'boolean', required: true },
@@ -9,8 +9,8 @@ export type PresentationRelationships = {
9
9
  presentationReceived: ModelRelatedNodesI<
10
10
  { createOne: (typeof Profile)['createOne'] },
11
11
  ProfileInstance,
12
- { from: string; date: string },
13
- { from: string; date: string }
12
+ { from: string; date: string; metadata?: Record<string, unknown> },
13
+ { from: string; date: string; metadata?: Record<string, unknown> }
14
14
  >;
15
15
  };
16
16
 
@@ -75,8 +75,8 @@ export type ProfileRelationships = {
75
75
  presentationSent: ModelRelatedNodesI<
76
76
  typeof Presentation,
77
77
  PresentationInstance,
78
- { to: string; date: string },
79
- { to: string; date: string }
78
+ { to: string; date: string; metadata?: Record<string, unknown> },
79
+ { to: string; date: string; metadata?: Record<string, unknown> }
80
80
  >;
81
81
  usesSigningAuthority: ModelRelatedNodesI<
82
82
  typeof SigningAuthority,
@@ -238,6 +238,7 @@ export const Profile: any = ModelFactory<FlatProfileType, ProfileRelationships>(
238
238
  properties: {
239
239
  to: { property: 'to', schema: { type: 'string', required: true } },
240
240
  date: { property: 'date', schema: { type: 'string', required: true } },
241
+ metadata: { property: 'metadata', schema: { type: 'object', required: false } },
241
242
  },
242
243
  },
243
244
  usesSigningAuthority: {
@@ -0,0 +1,130 @@
1
+ import { ModelFactory, NeogmaInstance } from 'neogma';
2
+
3
+ import { neogma } from '@instance';
4
+
5
+ /** Lifecycle status of a share. Only `active` is ever served to a recipient. */
6
+ export type ShareLinkStatus = 'pending' | 'active' | 'stopped';
7
+
8
+ /**
9
+ * Whether the visible content pointer for a share is committed.
10
+ *
11
+ * `staging` means a reservation exists but the object is not visible yet;
12
+ * `content_missing` means Brain believes content should be visible but LearnCloud
13
+ * does not have it, so the share fails closed and is never served.
14
+ */
15
+ export type ShareContentState = 'staging' | 'finalized' | 'content_missing';
16
+
17
+ /**
18
+ * Persisted ShareLink metadata (LC-2187).
19
+ *
20
+ * This node is the only Brain-side source of truth for the *visible* content
21
+ * pointer. It stores an opaque immutable `activeObjectRef` plus content/recovery
22
+ * hashes and byte counts — never ciphertext, a raw private source URI or a key.
23
+ * The concrete encrypted envelope and owner recovery JWE live together in the
24
+ * dedicated LearnCloud collection keyed by the immutable object reference.
25
+ *
26
+ * `version` is the committed record revision used for optimistic concurrency; a
27
+ * reservation (`staging`) does not change it. `contentVersion` is the visible
28
+ * content revision and changes only when a replacement is finalized.
29
+ */
30
+ export type ShareLinkRecord = {
31
+ id: string;
32
+ namespace: string;
33
+ ownerProfileId: string;
34
+ version: number;
35
+ contentVersion: number;
36
+ /**
37
+ * Monotonic fence. Bumped on every reservation and on revoke; a worker may
38
+ * only finalize the reservation whose generation still equals this value.
39
+ */
40
+ generation: number;
41
+ status: ShareLinkStatus;
42
+ contentState: ShareContentState;
43
+ activeObjectRef: string | null;
44
+ activeObjectOperationId: string | null;
45
+ activeContentHash: string | null;
46
+ activeContentBytes: number | null;
47
+ activeRecoveryHash: string | null;
48
+ /** Operation binding: the operation id that produced the visible revision. */
49
+ lastOperationId: string | null;
50
+ /** Operation binding: the create request that first bound this share id. */
51
+ createdByClientRequestId: string | null;
52
+ title: string;
53
+ note: string | null;
54
+ selectedCount: number;
55
+ expiresAt: string | null;
56
+ stoppedAt: string | null;
57
+ viewCount: number;
58
+ lastViewedAt: string | null;
59
+ /** Argon2id PHC string. Plaintext passcodes are never persisted. */
60
+ passcodeHash?: string | null;
61
+ notifyOnView?: boolean;
62
+ minorPolicyIsMinor: boolean | null;
63
+ minorPolicyResolved: boolean;
64
+ minorPolicyDefaultExpiryDays: number;
65
+ minorPolicyViewCountingEnabled: boolean;
66
+ createdAt: string;
67
+ updatedAt: string;
68
+ };
69
+
70
+ /** Neo4j omits null properties; the repository maps missing values back to null. */
71
+ type ShareLinkProperties = { lockTick?: number } & {
72
+ [Key in keyof ShareLinkRecord]:
73
+ | Exclude<ShareLinkRecord[Key], null>
74
+ | (null extends ShareLinkRecord[Key] ? undefined : never);
75
+ };
76
+ export type ShareLinkInstance = NeogmaInstance<ShareLinkProperties, Record<string, never>>;
77
+
78
+ /**
79
+ * `:ShareLink` model registration. The repository writes this label with raw
80
+ * parameterized Cypher so it can hold the share write lock across read + write
81
+ * within one transaction; the model exists for schema/typed instance support and
82
+ * constraint registration.
83
+ */
84
+ export const ShareLink = ModelFactory<ShareLinkProperties, Record<string, never>>(
85
+ {
86
+ label: 'ShareLink',
87
+ schema: {
88
+ id: { type: 'string', required: true },
89
+ namespace: { type: 'string', required: true },
90
+ ownerProfileId: { type: 'string', required: true },
91
+ version: { type: 'number', required: true },
92
+ contentVersion: { type: 'number', required: true },
93
+ generation: { type: 'number', required: true },
94
+ status: { type: 'string', required: true, enum: ['pending', 'active', 'stopped'] },
95
+ contentState: {
96
+ type: 'string',
97
+ required: true,
98
+ enum: ['staging', 'finalized', 'content_missing'],
99
+ },
100
+ activeObjectRef: { type: 'string', required: false },
101
+ activeObjectOperationId: { type: 'string', required: false },
102
+ activeContentHash: { type: 'string', required: false },
103
+ activeContentBytes: { type: 'number', required: false },
104
+ activeRecoveryHash: { type: 'string', required: false },
105
+ lastOperationId: { type: 'string', required: false },
106
+ createdByClientRequestId: { type: 'string', required: false },
107
+ title: { type: 'string', required: true },
108
+ note: { type: 'string', required: false },
109
+ selectedCount: { type: 'number', required: true },
110
+ expiresAt: { type: 'string', required: false },
111
+ stoppedAt: { type: 'string', required: false },
112
+ viewCount: { type: 'number', required: true },
113
+ lastViewedAt: { type: 'string', required: false },
114
+ passcodeHash: { type: 'string', required: false },
115
+ // LC-2187 records created before opt-in alerts may omit this field.
116
+ notifyOnView: { type: 'boolean', required: false },
117
+ minorPolicyIsMinor: { type: 'boolean', required: false },
118
+ minorPolicyResolved: { type: 'boolean', required: true },
119
+ minorPolicyDefaultExpiryDays: { type: 'number', required: true },
120
+ minorPolicyViewCountingEnabled: { type: 'boolean', required: true },
121
+ createdAt: { type: 'string', required: true },
122
+ updatedAt: { type: 'string', required: true },
123
+ lockTick: { type: 'number', required: false },
124
+ },
125
+ primaryKeyField: 'id',
126
+ },
127
+ neogma
128
+ );
129
+
130
+ export default ShareLink;
@@ -0,0 +1,69 @@
1
+ import { ModelFactory, NeogmaInstance } from 'neogma';
2
+
3
+ import { neogma } from '@instance';
4
+
5
+ /**
6
+ * Persisted, short-lived view receipt (LC-2187 ADR-10).
7
+ *
8
+ * Only the hash of the opaque CSPRNG token is stored — never the token, a viewer
9
+ * identity, an IP, a fingerprint, a credential, a key or any request body. The
10
+ * node is bound to one immutable committed tuple
11
+ * `(namespace, ownerProfileId, shareId, contentVersion, objectRef, operationId)`
12
+ * so a receipt for a superseded revision can never count toward the current one.
13
+ *
14
+ * `consumedAt` is the single-use marker; `lockTick` exists only so a transaction
15
+ * can take a write lock on the receipt AFTER it holds the share write lock.
16
+ * `expiresAt` bounds retention and is indexed for one-shot pruning.
17
+ */
18
+ export type ShareViewReceiptRecord = {
19
+ receiptHash: string;
20
+ namespace: string;
21
+ ownerProfileId: string;
22
+ shareId: string;
23
+ contentVersion: number;
24
+ objectRef: string;
25
+ operationId: string;
26
+ createdAt: string;
27
+ expiresAt: string;
28
+ consumedAt: string | null;
29
+ };
30
+
31
+ type ShareViewReceiptProperties = { lockTick?: number } & {
32
+ [Key in keyof ShareViewReceiptRecord]:
33
+ | Exclude<ShareViewReceiptRecord[Key], null>
34
+ | (null extends ShareViewReceiptRecord[Key] ? undefined : never);
35
+ };
36
+
37
+ export type ShareViewReceiptInstance = NeogmaInstance<
38
+ ShareViewReceiptProperties,
39
+ Record<string, never>
40
+ >;
41
+
42
+ /**
43
+ * `:ShareViewReceipt` model registration. The repository writes this label with
44
+ * raw parameterized Cypher so consume + increment happen in one transaction with
45
+ * explicit share-then-receipt lock ordering; the model exists for schema and
46
+ * constraint registration.
47
+ */
48
+ export const ShareViewReceipt = ModelFactory<ShareViewReceiptProperties, Record<string, never>>(
49
+ {
50
+ label: 'ShareViewReceipt',
51
+ schema: {
52
+ receiptHash: { type: 'string', required: true },
53
+ namespace: { type: 'string', required: true },
54
+ ownerProfileId: { type: 'string', required: true },
55
+ shareId: { type: 'string', required: true },
56
+ contentVersion: { type: 'number', required: true },
57
+ objectRef: { type: 'string', required: true },
58
+ operationId: { type: 'string', required: true },
59
+ createdAt: { type: 'string', required: true },
60
+ expiresAt: { type: 'string', required: true },
61
+ consumedAt: { type: 'string', required: false },
62
+ lockTick: { type: 'number', required: false },
63
+ },
64
+ primaryKeyField: 'receiptHash',
65
+ },
66
+ neogma
67
+ );
68
+
69
+ export default ShareViewReceipt;
@@ -38,11 +38,16 @@ describe('credential refresh constraint readiness', () => {
38
38
  releaseFirstQuery();
39
39
  await Promise.all([first, second]);
40
40
 
41
- expect(mocks.run).toHaveBeenCalledTimes(3);
41
+ expect(mocks.run).toHaveBeenCalledTimes(8);
42
42
  expect(mocks.run.mock.calls.map(([query]) => query)).toEqual([
43
+ 'CREATE CONSTRAINT inbox_refresh_issue_key_unique IF NOT EXISTS FOR (i:InboxCredential) REQUIRE (i.refreshIssueKey) IS UNIQUE',
44
+ 'CREATE CONSTRAINT inbox_refresh_publication_key_unique IF NOT EXISTS FOR (p:InboxRefreshPublication) REQUIRE (p.refreshIdempotencyKey) IS UNIQUE',
45
+ 'CREATE CONSTRAINT inbox_refresh_version_key_unique IF NOT EXISTS FOR (p:InboxRefreshPublication) REQUIRE (p.refreshVersionKey) IS UNIQUE',
43
46
  'CREATE CONSTRAINT credential_refresh_id_unique IF NOT EXISTS FOR (r:CredentialRefresh) REQUIRE (r.refreshId) IS UNIQUE',
44
47
  'CREATE CONSTRAINT credential_refresh_version_key_unique IF NOT EXISTS FOR (c:Credential) REQUIRE (c.refreshVersionKey) IS UNIQUE',
45
48
  'CREATE CONSTRAINT credential_refresh_idempotency_key_unique IF NOT EXISTS FOR (c:Credential) REQUIRE (c.refreshIdempotencyKey) IS UNIQUE',
49
+ 'CREATE CONSTRAINT credential_refresh_email_delivery_key_unique IF NOT EXISTS FOR (d:CredentialRefreshEmailDelivery) REQUIRE (d.deliveryKey) IS UNIQUE',
50
+ 'CREATE CONSTRAINT refresh_send_intent_key_unique IF NOT EXISTS FOR (i:RefreshSendIntent) REQUIRE (i.intentKey) IS UNIQUE',
46
51
  ]);
47
52
  });
48
53
 
@@ -57,7 +62,7 @@ describe('credential refresh constraint readiness', () => {
57
62
  await import('./credential-refresh-constraints');
58
63
 
59
64
  await expect(ensureCredentialRefreshConstraints()).resolves.toBeUndefined();
60
- expect(mocks.run).toHaveBeenCalledTimes(3);
65
+ expect(mocks.run).toHaveBeenCalledTimes(8);
61
66
  });
62
67
 
63
68
  it('clears failed readiness so a later request can retry', async () => {
@@ -70,6 +75,6 @@ describe('credential refresh constraint readiness', () => {
70
75
  await expect(ensureCredentialRefreshConstraints()).rejects.toBe(setupFailure);
71
76
  await expect(ensureCredentialRefreshConstraints()).resolves.toBeUndefined();
72
77
 
73
- expect(mocks.run).toHaveBeenCalledTimes(4);
78
+ expect(mocks.run).toHaveBeenCalledTimes(9);
74
79
  });
75
80
  });
@@ -1,9 +1,14 @@
1
1
  import { neogma } from '@instance';
2
2
 
3
3
  const CREDENTIAL_REFRESH_CONSTRAINT_QUERIES = [
4
+ 'CREATE CONSTRAINT inbox_refresh_issue_key_unique IF NOT EXISTS FOR (i:InboxCredential) REQUIRE (i.refreshIssueKey) IS UNIQUE',
5
+ 'CREATE CONSTRAINT inbox_refresh_publication_key_unique IF NOT EXISTS FOR (p:InboxRefreshPublication) REQUIRE (p.refreshIdempotencyKey) IS UNIQUE',
6
+ 'CREATE CONSTRAINT inbox_refresh_version_key_unique IF NOT EXISTS FOR (p:InboxRefreshPublication) REQUIRE (p.refreshVersionKey) IS UNIQUE',
4
7
  'CREATE CONSTRAINT credential_refresh_id_unique IF NOT EXISTS FOR (r:CredentialRefresh) REQUIRE (r.refreshId) IS UNIQUE',
5
8
  'CREATE CONSTRAINT credential_refresh_version_key_unique IF NOT EXISTS FOR (c:Credential) REQUIRE (c.refreshVersionKey) IS UNIQUE',
6
9
  'CREATE CONSTRAINT credential_refresh_idempotency_key_unique IF NOT EXISTS FOR (c:Credential) REQUIRE (c.refreshIdempotencyKey) IS UNIQUE',
10
+ 'CREATE CONSTRAINT credential_refresh_email_delivery_key_unique IF NOT EXISTS FOR (d:CredentialRefreshEmailDelivery) REQUIRE (d.deliveryKey) IS UNIQUE',
11
+ 'CREATE CONSTRAINT refresh_send_intent_key_unique IF NOT EXISTS FOR (i:RefreshSendIntent) REQUIRE (i.intentKey) IS UNIQUE',
7
12
  ];
8
13
 
9
14
  type Neo4jSchemaError = {
@@ -69,6 +69,7 @@ Presentation.addRelationships({
69
69
  properties: {
70
70
  from: { property: 'from', schema: { type: 'string', required: true } },
71
71
  date: { property: 'date', schema: { type: 'string', required: true } },
72
+ metadata: { property: 'metadata', schema: { type: 'object', required: false } },
72
73
  },
73
74
  },
74
75
  });
@@ -256,4 +257,5 @@ export * from './Integration';
256
257
  export * from './AppStoreListing';
257
258
  export * from './CredentialActivity';
258
259
  export * from './CredentialRefresh';
260
+ export * from './ShareLink';
259
261
  export * from './StatusList';
@@ -0,0 +1,93 @@
1
+ import { neogma } from '@instance';
2
+
3
+ /**
4
+ * Uniqueness rules required for the LC-2187 share lifecycle.
5
+ *
6
+ * A uniqueness constraint alone does not serialize two transactions consuming one
7
+ * reservation; the repository additionally holds a `:ShareLink` write lock before
8
+ * reading status/version/reservation state. These rules are the hard backstop that
9
+ * makes the resulting race fail closed instead of forking state.
10
+ */
11
+ const SHARE_LINK_CONSTRAINT_QUERIES = [
12
+ // A share id is globally unique across namespaces.
13
+ 'CREATE CONSTRAINT share_link_id_unique IF NOT EXISTS FOR (s:ShareLink) REQUIRE (s.id) IS UNIQUE',
14
+ // One idempotency record per (namespace, owner, operation kind, client request).
15
+ 'CREATE CONSTRAINT share_link_operation_key_unique IF NOT EXISTS FOR (o:ShareLinkOperation) REQUIRE (o.namespace, o.ownerProfileId, o.opKind, o.clientRequestId) IS UNIQUE',
16
+ // At most one in-flight reservation per share.
17
+ 'CREATE CONSTRAINT share_link_reservation_share_unique IF NOT EXISTS FOR (r:ShareLinkReservation) REQUIRE (r.shareId) IS UNIQUE',
18
+ // Cleanup is bound to exactly one immutable object reference.
19
+ 'CREATE CONSTRAINT share_content_cleanup_object_unique IF NOT EXISTS FOR (c:ShareContentCleanupJob) REQUIRE (c.objectRef) IS UNIQUE',
20
+ // One persisted receipt node per opaque token hash (the token itself is never stored).
21
+ 'CREATE CONSTRAINT share_view_receipt_hash_unique IF NOT EXISTS FOR (v:ShareViewReceipt) REQUIRE (v.receiptHash) IS UNIQUE',
22
+ ];
23
+
24
+ /**
25
+ * Non-unique indexes that keep the lifecycle queries bounded. Index creation is
26
+ * idempotent and shares the same readiness gate as the constraints.
27
+ */
28
+ const SHARE_LINK_INDEX_QUERIES = [
29
+ 'CREATE INDEX share_link_ns_owner_idx IF NOT EXISTS FOR (s:ShareLink) ON (s.namespace, s.ownerProfileId)',
30
+ // Bounded owner-list keyset pagination: filters on the leading
31
+ // (namespace, ownerProfileId) prefix and orders by the immutable
32
+ // (createdAt, id) key without a full scan/sort.
33
+ 'CREATE INDEX share_link_ns_owner_created_idx IF NOT EXISTS FOR (s:ShareLink) ON (s.namespace, s.ownerProfileId, s.createdAt, s.id)',
34
+ 'CREATE INDEX share_link_status_idx IF NOT EXISTS FOR (s:ShareLink) ON (s.status)',
35
+ 'CREATE INDEX share_link_operation_operation_id_idx IF NOT EXISTS FOR (o:ShareLinkOperation) ON (o.operationId)',
36
+ 'CREATE INDEX share_link_operation_share_id_idx IF NOT EXISTS FOR (o:ShareLinkOperation) ON (o.shareId)',
37
+ 'CREATE INDEX share_link_reservation_lease_idx IF NOT EXISTS FOR (r:ShareLinkReservation) ON (r.leaseExpiresAt)',
38
+ // Bounded recovery discovery enumerates expired reservations for one namespace.
39
+ 'CREATE INDEX share_link_reservation_ns_lease_idx IF NOT EXISTS FOR (r:ShareLinkReservation) ON (r.namespace, r.leaseExpiresAt)',
40
+ 'CREATE INDEX share_content_cleanup_due_idx IF NOT EXISTS FOR (c:ShareContentCleanupJob) ON (c.status, c.nextAttemptAt)',
41
+ // One-shot receipt pruning is bounded by expiry/consumed state.
42
+ 'CREATE INDEX share_view_receipt_expiry_idx IF NOT EXISTS FOR (v:ShareViewReceipt) ON (v.expiresAt)',
43
+ 'CREATE INDEX share_view_receipt_share_idx IF NOT EXISTS FOR (v:ShareViewReceipt) ON (v.namespace, v.shareId)',
44
+ ];
45
+
46
+ type Neo4jSchemaError = {
47
+ code?: string;
48
+ };
49
+
50
+ const isEquivalentSchemaRuleRace = (error: unknown): boolean =>
51
+ (error as Neo4jSchemaError | undefined)?.code ===
52
+ 'Neo.ClientError.Schema.EquivalentSchemaRuleAlreadyExists';
53
+
54
+ const createShareLinkSchema = async (): Promise<void> => {
55
+ for (const query of [...SHARE_LINK_CONSTRAINT_QUERIES, ...SHARE_LINK_INDEX_QUERIES]) {
56
+ try {
57
+ await neogma.queryRunner.run(query);
58
+ } catch (error) {
59
+ // Concurrent service starts can both observe a missing rule before one
60
+ // wins creation. The losing CREATE is equivalent to the desired ready state.
61
+ if (!isEquivalentSchemaRuleRace(error)) throw error;
62
+ }
63
+ }
64
+ };
65
+
66
+ let constraintReadiness: Promise<void> | undefined;
67
+
68
+ /**
69
+ * Ensures the uniqueness rules and indexes required for the share lifecycle.
70
+ *
71
+ * Every public repository method awaits this before its transaction, so a caller
72
+ * can never observe the "constraints not initialized" window. Concurrent callers
73
+ * share one attempt; a failed attempt is cleared so a later request can retry
74
+ * instead of leaving the repository permanently unavailable.
75
+ */
76
+ export const ensureShareLinkConstraints = (): Promise<void> => {
77
+ if (!constraintReadiness) {
78
+ const pendingReadiness = createShareLinkSchema();
79
+ constraintReadiness = pendingReadiness;
80
+
81
+ void pendingReadiness.catch(() => {
82
+ if (constraintReadiness === pendingReadiness) constraintReadiness = undefined;
83
+ });
84
+ }
85
+
86
+ return constraintReadiness;
87
+ };
88
+
89
+ /** Exposed for focused tests that assert the exact schema statements. */
90
+ export const SHARE_LINK_SCHEMA_QUERIES = [
91
+ ...SHARE_LINK_CONSTRAINT_QUERIES,
92
+ ...SHARE_LINK_INDEX_QUERIES,
93
+ ] as const;
package/src/openapi.ts CHANGED
@@ -25,6 +25,14 @@ export const openApiDocument = generateOpenApiDocument(appRouter, {
25
25
  ],
26
26
  });
27
27
 
28
+ // Keep this path/status aligned with inboxBatchResponseMeta in helpers/inbox-batch-http.helpers.ts.
29
+ // The adapter's responseMeta returns 202 for durable batch acceptance.
30
+ const batchResponses = openApiDocument.paths?.['/inbox/issue-batch']?.post?.responses;
31
+ if (batchResponses?.['200']) {
32
+ batchResponses['202'] = batchResponses['200'];
33
+ delete batchResponses['200'];
34
+ }
35
+
28
36
  // trpc-to-openapi requires a top-level ZodObject and cannot render the validator's
29
37
  // cross-field refinement. Preserve the runtime-safe object parser while documenting
30
38
  // the two mutually exclusive publication shapes for generated OpenAPI clients.
@@ -0,0 +1,49 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+
3
+ /**
4
+ * LC-2187 public share-link HTTP adapter seam.
5
+ *
6
+ * The Fastify OpenAPI adapter has no `responseMeta` hook, so the no-store
7
+ * contract for the anonymous public share-link routes is applied at the adapter
8
+ * boundary itself. This module is deliberately free of the server bootstrap
9
+ * (which listens on import) so a real Fastify instance can be exercised in
10
+ * tests without starting the application.
11
+ */
12
+
13
+ /**
14
+ * Prefixes that identify a mounted public share-link OpenAPI request.
15
+ *
16
+ * The trpc-to-openapi Fastify adapter rewrites `request.raw.url` by stripping
17
+ * its `/api` base path before the handler runs, so the `onSend` hook sees
18
+ * `/public/share-links/...` even though the client requested
19
+ * `/api/public/share-links/...`. Both forms are matched (with a path-boundary
20
+ * check) so a sibling route such as `/public/share-links-other` is not treated
21
+ * as public.
22
+ */
23
+ const PUBLIC_SHARE_LINK_PREFIXES = ['/api/public/share-links', '/public/share-links'] as const;
24
+
25
+ /** True for the mounted public share-link OpenAPI route prefix only. */
26
+ export const isPublicShareLinkOpenApiRequestUrl = (url: string | undefined): boolean => {
27
+ if (!url) return false;
28
+
29
+ const path = url.split('?')[0] ?? '';
30
+
31
+ return PUBLIC_SHARE_LINK_PREFIXES.some(
32
+ prefix => path === prefix || path.startsWith(`${prefix}/`)
33
+ );
34
+ };
35
+
36
+ /**
37
+ * Installs the adapter-boundary `Cache-Control: private, no-store` on every
38
+ * public share-link OpenAPI response, independent of any upstream cache layer.
39
+ * It is safe to register on any Fastify instance (tRPC-only test apps included).
40
+ */
41
+ export const registerPublicShareLinkNoStore = (app: FastifyInstance): void => {
42
+ app.addHook('onSend', async (request, reply, payload) => {
43
+ if (isPublicShareLinkOpenApiRequestUrl(request.raw.url)) {
44
+ reply.header('Cache-Control', 'private, no-store');
45
+ }
46
+
47
+ return payload;
48
+ });
49
+ };