@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,425 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Publish a credential-library persona as a ConsentFlow contract with auto-boosts.
4
+ *
5
+ * Usage (from services/learn-card-network/brain-service):
6
+ * bun run seed:demo-persona student
7
+ *
8
+ * The script is idempotent: profile, signing authority, Boost, contract, and
9
+ * AUTO_RECEIVE identifiers are stable. Re-running updates fixture content and
10
+ * replaces the contract's auto-boost set without changing its URI.
11
+ *
12
+ * Every target requires explicit database, domain, signing-authority endpoint,
13
+ * and 64-character signing seed configuration.
14
+ */
15
+
16
+ import { fileURLToPath } from 'url';
17
+ import { createHmac } from 'node:crypto';
18
+
19
+ import * as bs58 from 'bs58';
20
+ import * as dotenv from 'dotenv';
21
+ import { MongoClient } from 'mongodb';
22
+ import { Neogma } from 'neogma';
23
+ import Redis from 'ioredis';
24
+ import * as nacl from 'tweetnacl';
25
+ import { v4 as uuid, v5 as uuidv5 } from 'uuid';
26
+
27
+ import {
28
+ getBundle,
29
+ getFixture,
30
+ prepareFixture,
31
+ type CredentialBundleEntry,
32
+ type CredentialBundleIssuer,
33
+ } from '@learncard/credential-library';
34
+ import type { UnsignedVC } from '@learncard/types';
35
+ import { flattenObject } from '../src/helpers/objects.helpers';
36
+
37
+ dotenv.config({ path: fileURLToPath(new URL('../.env', import.meta.url)) });
38
+
39
+ const PERSONA_NAMESPACE = '5c4bb193-6e65-43d9-940d-d85b758a94f2';
40
+ const SIGNING_AUTHORITY_NAME = 'sample-personas';
41
+
42
+ const NEO4J_URI = process.env.NEO4J_URI ?? 'bolt://localhost:7687';
43
+ const NEO4J_USERNAME = process.env.NEO4J_USERNAME ?? 'neo4j';
44
+ const NEO4J_PASSWORD = process.env.NEO4J_PASSWORD ?? 'this-is-the-password';
45
+ const MONGO_URI = process.env.MONGO_URI ?? 'mongodb://localhost:27017/?replicaSet=rs0';
46
+ const MONGO_DB_NAME = process.env.MONGO_DB_NAME ?? 'lca-api';
47
+ const REDIS_HOST = process.env.REDIS_HOST ?? 'localhost';
48
+ const REDIS_PORT = Number.parseInt(process.env.REDIS_PORT ?? '6379', 10);
49
+ const BRAIN_DOMAIN = process.env.DOMAIN_NAME || 'localhost%3A4000';
50
+ const SIGNING_AUTHORITY_ENDPOINT = process.env.DEMO_PERSONA_SIGNING_AUTHORITY_ENDPOINT;
51
+ const SIGNING_AUTHORITY_SEED = process.env.DEMO_PERSONA_SA_SEED;
52
+
53
+ const personaId = process.argv[2];
54
+
55
+ if (!personaId) {
56
+ throw new Error('Usage: bun scripts/seed-demo-persona.ts <personaId>');
57
+ }
58
+
59
+ if (!SIGNING_AUTHORITY_SEED || !SIGNING_AUTHORITY_ENDPOINT) {
60
+ throw new Error(
61
+ 'DEMO_PERSONA_SA_SEED and DEMO_PERSONA_SIGNING_AUTHORITY_ENDPOINT are required.'
62
+ );
63
+ }
64
+
65
+ if (!/^[0-9a-f]{64}$/i.test(SIGNING_AUTHORITY_SEED)) {
66
+ throw new Error('DEMO_PERSONA_SA_SEED must contain exactly 64 hexadecimal characters.');
67
+ }
68
+
69
+ const transformProfileId = (raw: string): string => raw.toLowerCase().replace(/:/g, '%3A');
70
+
71
+ const getDidWeb = (domain: string, profileId: string): string =>
72
+ `did:web:${domain}:users:${profileId}`;
73
+
74
+ const constructContractUri = (contractId: string): string => {
75
+ const encodedDomain = BRAIN_DOMAIN.includes('localhost')
76
+ ? BRAIN_DOMAIN.replace(/:/g, '%3A')
77
+ : BRAIN_DOMAIN.replace(/:/g, '/');
78
+
79
+ return `lc:network:${encodedDomain.replace(/\/trpc$/, '')}/trpc:contract:${contractId}`;
80
+ };
81
+
82
+ const deriveDidKeyFromSeed = (hexSeed: string): string => {
83
+ const seedBytes = Buffer.from(hexSeed, 'hex');
84
+ const keyPair = nacl.sign.keyPair.fromSeed(seedBytes);
85
+ const multicodec = Buffer.concat([Buffer.from('ed01', 'hex'), Buffer.from(keyPair.publicKey)]);
86
+
87
+ return `did:key:z${bs58.encode(multicodec)}`;
88
+ };
89
+ const deriveSigningAuthoritySeed = (profileId: string): string =>
90
+ createHmac('sha256', Buffer.from(SIGNING_AUTHORITY_SEED, 'hex'))
91
+ .update(`learncard:sample-persona:${profileId}`)
92
+ .digest('hex');
93
+
94
+ const clearProfileDidDocumentCache = async (profileId: string): Promise<void> => {
95
+ const redis = new Redis({
96
+ host: REDIS_HOST,
97
+ port: REDIS_PORT,
98
+ lazyConnect: true,
99
+ connectTimeout: 2_000,
100
+ retryStrategy: () => null,
101
+ });
102
+
103
+ try {
104
+ await redis.connect();
105
+ await redis.del(`did-doc:${profileId}`);
106
+ } catch {
107
+ console.warn(
108
+ `Could not clear did-doc:${profileId} at ${REDIS_HOST}:${REDIS_PORT}; delete that Redis key manually, or restart the brain service if it uses an in-memory cache.`
109
+ );
110
+ } finally {
111
+ redis.disconnect();
112
+ }
113
+ };
114
+
115
+ const shiftDate = (offsetDays: number): string => {
116
+ const date = new Date();
117
+ date.setUTCDate(date.getUTCDate() + offsetDays);
118
+ return date.toISOString();
119
+ };
120
+
121
+ const prepareBundleCredential = (entry: CredentialBundleEntry, issuerDid: string): UnsignedVC => {
122
+ const fixture = getFixture(entry.fixtureId);
123
+ const prepared = prepareFixture(fixture, {
124
+ issuerDid,
125
+ subjectDid: 'did:example:123',
126
+ validFrom: shiftDate(entry.validFromOffsetDays),
127
+ freshIds: false,
128
+ });
129
+
130
+ if (entry.name) prepared.name = entry.name;
131
+
132
+ return prepared;
133
+ };
134
+
135
+ const toBoostTemplate = (credential: UnsignedVC, issuerDid: string): string => {
136
+ const template = structuredClone(credential) as Record<string, unknown>;
137
+ delete template.id;
138
+ delete template.proof;
139
+ template.issuer = issuerDid;
140
+
141
+ const subjects = Array.isArray(template.credentialSubject)
142
+ ? template.credentialSubject
143
+ : [template.credentialSubject];
144
+ for (const subject of subjects) {
145
+ if (subject && typeof subject === 'object') {
146
+ Object.assign(subject, { id: 'did:example:123' });
147
+ }
148
+ }
149
+
150
+ return JSON.stringify(template);
151
+ };
152
+
153
+ const ensureSigningAuthority = async (
154
+ ownerDid: string,
155
+ did: string,
156
+ seed: string
157
+ ): Promise<void> => {
158
+ const client = new MongoClient(MONGO_URI);
159
+
160
+ try {
161
+ await client.connect();
162
+ await client
163
+ .db(MONGO_DB_NAME)
164
+ .collection('signingauthorities')
165
+ .updateOne(
166
+ { ownerDid, name: SIGNING_AUTHORITY_NAME },
167
+ {
168
+ $set: { seed, did },
169
+ $setOnInsert: { _id: uuid(), ownerDid, name: SIGNING_AUTHORITY_NAME },
170
+ },
171
+ { upsert: true }
172
+ );
173
+ } finally {
174
+ await client.close();
175
+ }
176
+ };
177
+
178
+ const main = async (): Promise<void> => {
179
+ const bundle = getBundle(personaId);
180
+ const [firstEntry] = bundle.entries;
181
+ if (!firstEntry) {
182
+ throw new Error(`Credential bundle ${bundle.id} has no entries.`);
183
+ }
184
+
185
+ const issuerProfiles = new Map<
186
+ string,
187
+ CredentialBundleIssuer & {
188
+ profileId: `sample-${string}`;
189
+ did: string;
190
+ signingAuthorityDid: string;
191
+ signingAuthoritySeed: string;
192
+ }
193
+ >();
194
+ for (const entry of bundle.entries) {
195
+ const profileId = transformProfileId(entry.issuer.profileId);
196
+ if (!profileId.startsWith('sample-')) {
197
+ throw new Error(`Refusing to seed non-sample issuer profile: ${profileId}`);
198
+ }
199
+ const existingIssuer = issuerProfiles.get(profileId);
200
+
201
+ if (
202
+ existingIssuer &&
203
+ (existingIssuer.displayName !== entry.issuer.displayName ||
204
+ existingIssuer.image !== entry.issuer.image)
205
+ ) {
206
+ throw new Error(`Conflicting metadata configured for sample issuer ${profileId}.`);
207
+ }
208
+
209
+ const signingAuthoritySeed = deriveSigningAuthoritySeed(profileId);
210
+ const signingAuthorityDid = deriveDidKeyFromSeed(signingAuthoritySeed);
211
+ issuerProfiles.set(profileId, {
212
+ ...entry.issuer,
213
+ profileId: profileId as `sample-${string}`,
214
+ did: getDidWeb(BRAIN_DOMAIN, profileId),
215
+ signingAuthorityDid,
216
+ signingAuthoritySeed,
217
+ });
218
+ }
219
+
220
+ const contractOwnerProfileId = transformProfileId(firstEntry.issuer.profileId);
221
+ const contractId = uuidv5(`persona:${bundle.id}:contract`, PERSONA_NAMESPACE);
222
+ const neogma = new Neogma({
223
+ url: NEO4J_URI,
224
+ username: NEO4J_USERNAME,
225
+ password: NEO4J_PASSWORD,
226
+ });
227
+ const run = neogma.queryRunner.run.bind(neogma.queryRunner);
228
+
229
+ try {
230
+ for (const issuer of issuerProfiles.values()) {
231
+ const profileResult = await run(
232
+ `MERGE (p:Profile {profileId: $profileId})
233
+ ON CREATE SET p.isSamplePersona = true
234
+ WITH p
235
+ WHERE p.isSamplePersona = true
236
+ AND (p.did IS NULL OR p.did = $did)
237
+ SET p.displayName = $displayName,
238
+ p.shortBio = $shortBio,
239
+ p.image = $image,
240
+ p.did = $did
241
+ RETURN p.did AS did`,
242
+ {
243
+ profileId: issuer.profileId,
244
+ displayName: issuer.displayName,
245
+ shortBio: 'Issuer for LearnCard sample credentials',
246
+ image: issuer.image ?? '',
247
+ did: issuer.signingAuthorityDid,
248
+ }
249
+ );
250
+ const storedSigningAuthorityDid = profileResult.records[0]?.get('did');
251
+ if (!storedSigningAuthorityDid) {
252
+ throw new Error(
253
+ `Refusing to overwrite existing non-sample profile ${issuer.profileId}.`
254
+ );
255
+ }
256
+ if (storedSigningAuthorityDid !== issuer.signingAuthorityDid) {
257
+ throw new Error(
258
+ `Profile ${issuer.profileId} did not retain its signing authority DID.`
259
+ );
260
+ }
261
+
262
+ await run(
263
+ `MERGE (sa:SigningAuthority {endpoint: $endpoint})
264
+ WITH sa
265
+ MATCH (p:Profile {profileId: $profileId})
266
+ MERGE (p)-[r:USES_SIGNING_AUTHORITY {name: $name}]->(sa)
267
+ SET r.did = $did, r.isPrimary = true
268
+ RETURN r`,
269
+ {
270
+ endpoint: SIGNING_AUTHORITY_ENDPOINT,
271
+ profileId: issuer.profileId,
272
+ name: SIGNING_AUTHORITY_NAME,
273
+ did: issuer.signingAuthorityDid,
274
+ }
275
+ );
276
+ await clearProfileDidDocumentCache(issuer.profileId);
277
+ await ensureSigningAuthority(
278
+ issuer.did,
279
+ issuer.signingAuthorityDid,
280
+ issuer.signingAuthoritySeed
281
+ );
282
+ }
283
+
284
+ const autoBoosts: Array<{ boostId: string; issuerProfileId: string }> = [];
285
+ for (const entry of bundle.entries) {
286
+ const fixture = getFixture(entry.fixtureId);
287
+ const profileId = transformProfileId(entry.issuer.profileId);
288
+ const issuer = issuerProfiles.get(profileId);
289
+ if (!issuer) {
290
+ throw new Error(`Missing seeded issuer profile ${profileId}.`);
291
+ }
292
+
293
+ const boostId = uuidv5(
294
+ `persona:${bundle.id}:fixture:${entry.fixtureId}`,
295
+ PERSONA_NAMESPACE
296
+ );
297
+ const credential = prepareBundleCredential(entry, issuer.did);
298
+ const boost = toBoostTemplate(credential, issuer.did);
299
+ autoBoosts.push({ boostId, issuerProfileId: profileId });
300
+ const boostProperties = flattenObject({
301
+ id: boostId,
302
+ boost,
303
+ name: entry.name ?? fixture.name,
304
+ type: fixture.profile,
305
+ category: 'Achievement',
306
+ status: 'LIVE',
307
+ meta: { personaId: bundle.id, fixtureId: fixture.id },
308
+ });
309
+
310
+ const boostResult = await run(
311
+ `MERGE (b:Boost {id: $boostId})
312
+ SET b = $properties
313
+ WITH b
314
+ OPTIONAL MATCH (b)-[oldCreated:CREATED_BY]->(:Profile)
315
+ DELETE oldCreated
316
+ WITH DISTINCT b
317
+ OPTIONAL MATCH (:Profile)-[oldRole:HAS_ROLE {roleId: '__creator__'}]->(b)
318
+ DELETE oldRole
319
+ WITH DISTINCT b
320
+ MATCH (p:Profile {profileId: $profileId})
321
+ MERGE (b)-[created:CREATED_BY]->(p)
322
+ SET created.date = $date
323
+ MERGE (p)-[:HAS_ROLE {roleId: '__creator__'}]->(b)
324
+ WITH b
325
+ MATCH (b)-[:CREATED_BY]->(creator:Profile)
326
+ MATCH (roleOwner:Profile)-[:HAS_ROLE {roleId: '__creator__'}]->(b)
327
+ RETURN collect(creator.profileId) AS creatorProfileIds,
328
+ collect(roleOwner.profileId) AS roleOwnerProfileIds`,
329
+ {
330
+ boostId,
331
+ properties: boostProperties,
332
+ profileId,
333
+ date: new Date().toISOString(),
334
+ }
335
+ );
336
+ const creatorProfileIds = boostResult.records[0]?.get('creatorProfileIds');
337
+ const roleOwnerProfileIds = boostResult.records[0]?.get('roleOwnerProfileIds');
338
+ if (
339
+ creatorProfileIds?.length !== 1 ||
340
+ creatorProfileIds[0] !== profileId ||
341
+ roleOwnerProfileIds?.length !== 1 ||
342
+ roleOwnerProfileIds[0] !== profileId
343
+ ) {
344
+ throw new Error(`Boost ${boostId} has ambiguous profile ownership.`);
345
+ }
346
+ }
347
+
348
+ const contract = {
349
+ read: {
350
+ personal: { name: { required: false } },
351
+ credentials: { categories: { Achievement: { required: false } } },
352
+ },
353
+ write: {
354
+ personal: {},
355
+ credentials: {
356
+ categories: { Achievement: { required: false, defaultEnabled: true } },
357
+ },
358
+ },
359
+ };
360
+ const now = new Date().toISOString();
361
+ const contractProperties = flattenObject({
362
+ id: contractId,
363
+ name: `${bundle.displayName} Sample Credentials`,
364
+ subtitle: bundle.blurb,
365
+ description: bundle.blurb,
366
+ reasonForAccessing: '',
367
+ needsGuardianConsent: false,
368
+ redirectUrl: '',
369
+ frontDoorBoostUri: '',
370
+ image: '',
371
+ contract,
372
+ updatedAt: now,
373
+ });
374
+
375
+ await run(
376
+ `MERGE (c:ConsentFlowContract {id: $contractId})
377
+ ON CREATE SET c.createdAt = $createdAt
378
+ WITH c, c.createdAt AS createdAt
379
+ SET c = $properties,
380
+ c.createdAt = createdAt
381
+ WITH c
382
+ OPTIONAL MATCH (c)-[oldOwner:CREATED_BY]->(:Profile)
383
+ DELETE oldOwner
384
+ WITH DISTINCT c
385
+ MATCH (p:Profile {profileId: $profileId})
386
+ MERGE (c)-[:CREATED_BY]->(p)
387
+ RETURN c`,
388
+ {
389
+ contractId,
390
+ createdAt: now,
391
+ profileId: contractOwnerProfileId,
392
+ properties: contractProperties,
393
+ }
394
+ );
395
+
396
+ await run(
397
+ `MATCH (c:ConsentFlowContract {id: $contractId})
398
+ OPTIONAL MATCH (c)-[old:AUTO_RECEIVE]->(:Boost)
399
+ DELETE old
400
+ WITH DISTINCT c
401
+ UNWIND $autoBoosts AS autoBoost
402
+ MATCH (b:Boost {id: autoBoost.boostId})
403
+ CREATE (c)-[:AUTO_RECEIVE {
404
+ signingAuthorityEndpoint: $endpoint,
405
+ signingAuthorityName: $name,
406
+ issuer: autoBoost.issuerProfileId
407
+ }]->(b)`,
408
+ {
409
+ contractId,
410
+ autoBoosts,
411
+ endpoint: SIGNING_AUTHORITY_ENDPOINT,
412
+ name: SIGNING_AUTHORITY_NAME,
413
+ }
414
+ );
415
+
416
+ console.log(constructContractUri(contractId));
417
+ } finally {
418
+ await neogma.driver.close();
419
+ }
420
+ };
421
+
422
+ main().catch(error => {
423
+ console.error(error);
424
+ process.exitCode = 1;
425
+ });
@@ -0,0 +1,60 @@
1
+ import {
2
+ SQSClient,
3
+ CreateQueueCommand,
4
+ GetQueueUrlCommand,
5
+ GetQueueAttributesCommand,
6
+ SetQueueAttributesCommand,
7
+ } from '@aws-sdk/client-sqs';
8
+
9
+ const endpoint = process.env.INBOX_QUEUE_ENDPOINT ?? 'http://localhost:9324';
10
+ const client = new SQSClient({
11
+ region: process.env.AWS_REGION ?? 'us-east-1',
12
+ endpoint,
13
+ credentials: { accessKeyId: 'local', secretAccessKey: 'local' },
14
+ });
15
+
16
+ const ensureQueue = async (name: string): Promise<string> => {
17
+ let url: string | undefined;
18
+ try {
19
+ url = (await client.send(new GetQueueUrlCommand({ QueueName: name }))).QueueUrl;
20
+ } catch (error) {
21
+ if (
22
+ !(error instanceof Error) ||
23
+ !['QueueDoesNotExist', 'AWS.SimpleQueueService.NonExistentQueue'].includes(error.name)
24
+ )
25
+ throw error;
26
+ url = (await client.send(new CreateQueueCommand({ QueueName: name }))).QueueUrl;
27
+ }
28
+ if (!url) throw new Error(`Missing URL for ${name}`);
29
+ // ElasticMQ may advertise its container hostname; use the caller's reachable endpoint.
30
+ return `${endpoint.replace(/\/$/, '')}${new URL(url).pathname}`;
31
+ };
32
+
33
+ try {
34
+ const dlqUrl = await ensureQueue('inbox-dlq');
35
+ const attributes = await client.send(
36
+ new GetQueueAttributesCommand({
37
+ QueueUrl: dlqUrl,
38
+ AttributeNames: ['QueueArn'],
39
+ })
40
+ );
41
+ if (!attributes.Attributes?.QueueArn)
42
+ throw new Error('Inbox dead-letter queue ARN is missing.');
43
+ const queueUrl = await ensureQueue('inbox');
44
+ await client.send(
45
+ new SetQueueAttributesCommand({
46
+ QueueUrl: queueUrl,
47
+ Attributes: {
48
+ VisibilityTimeout: '1800',
49
+ RedrivePolicy: JSON.stringify({
50
+ deadLetterTargetArn: attributes.Attributes.QueueArn,
51
+ maxReceiveCount: '5',
52
+ }),
53
+ },
54
+ })
55
+ );
56
+ console.log(`INBOX_QUEUE_URL=${queueUrl}`);
57
+ console.log(`INBOX_DEAD_LETTER_QUEUE_URL=${dlqUrl}`);
58
+ } finally {
59
+ client.destroy();
60
+ }
@@ -47,6 +47,8 @@ provider:
47
47
  stage: ${opt:stage, "dev"}
48
48
  region: ${opt:region, "us-east-1"}
49
49
  environment:
50
+ INBOX_QUEUE_URL: ${env:INBOX_QUEUE_URL, ''}
51
+ INBOX_QUEUE_ENDPOINT: ${env:INBOX_QUEUE_ENDPOINT, ''}
50
52
  PORT: ${env:PORT, 3000}
51
53
  LAMBDA_STAGE: ${opt:stage, "dev"}
52
54
  SEED: ${env:SEED}
package/serverless.yml CHANGED
@@ -15,6 +15,20 @@ plugins:
15
15
  - serverless-prune-plugin
16
16
 
17
17
  constructs:
18
+ inbox-queue:
19
+ type: queue
20
+ batchSize: 1
21
+ maxConcurrency: 10
22
+ maxRetries: 5
23
+ worker:
24
+ handler: lambda.inboxQueueWorker
25
+ timeout: 300
26
+ reservedConcurrency: 10
27
+ vpc:
28
+ securityGroupIds:
29
+ - 'Fn::GetAtt': [ServerlessSecurityGroup, GroupId]
30
+ subnetIds:
31
+ - Ref: PrivateSubnetA
18
32
  notifications-queue:
19
33
  type: queue
20
34
  worker:
@@ -128,6 +142,7 @@ provider:
128
142
  PORT: ${env:PORT, 3000}
129
143
  INBOX_DELETE_EXPIRED_RECORDS: ${env:INBOX_DELETE_EXPIRED_RECORDS, "false"}
130
144
  LAMBDA_STAGE: ${opt:stage, "dev"}
145
+ INBOX_BATCH_ITEMS_PER_HOUR: ${env:INBOX_BATCH_ITEMS_PER_HOUR, '10000'}
131
146
  SEED: ${env:SEED}
132
147
  DOMAIN_NAME: ${env:DOMAIN_NAME}
133
148
  CLIENT_APP_DOMAIN_NAME: ${env:CLIENT_APP_DOMAIN_NAME}
@@ -153,6 +168,7 @@ provider:
153
168
  REDIS_PORT:
154
169
  'Fn::GetAtt': [ElasticCacheCluster, RedisEndpoint.Port]
155
170
  NOTIFICATIONS_QUEUE_URL: ${construct:notifications-queue.queueUrl}
171
+ INBOX_QUEUE_URL: ${construct:inbox-queue.queueUrl}
156
172
  SKILL_EMBEDDING_GOOGLE_API_KEY: ${env:SKILL_EMBEDDING_GOOGLE_API_KEY, ""}
157
173
  SKILL_EMBEDDING_GOOGLE_MODEL: ${env:SKILL_EMBEDDING_GOOGLE_MODEL, "gemini-embedding-001"}
158
174
  SKILL_EMBEDDING_BATCH_SIZE: ${env:SKILL_EMBEDDING_BATCH_SIZE, "25"}
@@ -177,6 +193,25 @@ provider:
177
193
  CREDENTIAL_REFRESH_ENABLED: ${env:CREDENTIAL_REFRESH_ENABLED, "false"}
178
194
  CREDENTIAL_REFRESH_DIGEST_SECRET: ${env:CREDENTIAL_REFRESH_DIGEST_SECRET, ""}
179
195
  CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS: ${env:CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS, "24"}
196
+ # Stable HMAC key for passcode-bearing share-link request fingerprints.
197
+ SHARE_LINK_REQUEST_HASH_SECRET: ${env:SHARE_LINK_REQUEST_HASH_SECRET, ""}
198
+ # LC-2187 share-link service wiring and automatic maintenance. Every value is
199
+ # bounded and re-validated fail-closed at runtime. Never read from the
200
+ # scheduled event payload.
201
+ SHARE_LINK_MAINTENANCE_NAMESPACE: ${env:SHARE_LINK_MAINTENANCE_NAMESPACE, ""}
202
+ SHARE_LINK_MAINTENANCE_ORIGIN: ${env:SHARE_LINK_MAINTENANCE_ORIGIN, ""}
203
+ SHARE_LINK_MAINTENANCE_AUDIENCE: ${env:SHARE_LINK_MAINTENANCE_AUDIENCE, ""}
204
+ SHARE_LINK_MAINTENANCE_ALLOW_INSECURE_LOOPBACK: ${env:SHARE_LINK_MAINTENANCE_ALLOW_INSECURE_LOOPBACK, "false"}
205
+ SHARE_LINK_MAINTENANCE_CLAIMANT: ${env:SHARE_LINK_MAINTENANCE_CLAIMANT, "brain-share-link-maintenance"}
206
+ SHARE_LINK_MAINTENANCE_RECOVERY_LIMIT: ${env:SHARE_LINK_MAINTENANCE_RECOVERY_LIMIT, "25"}
207
+ SHARE_LINK_MAINTENANCE_CLEANUP_LIMIT: ${env:SHARE_LINK_MAINTENANCE_CLEANUP_LIMIT, "25"}
208
+ SHARE_LINK_MAINTENANCE_LEASE_MS: ${env:SHARE_LINK_MAINTENANCE_LEASE_MS, "120000"}
209
+ SHARE_LINK_MAINTENANCE_CLAIM_MS: ${env:SHARE_LINK_MAINTENANCE_CLAIM_MS, "300000"}
210
+ SHARE_LINK_MAINTENANCE_BUDGET_MS: ${env:SHARE_LINK_MAINTENANCE_BUDGET_MS, "60000"}
211
+ SHARE_LINK_MAINTENANCE_INTERVAL_MS: ${env:SHARE_LINK_MAINTENANCE_INTERVAL_MS, "300000"}
212
+ SHARE_LINK_MAINTENANCE_REQUEST_TIMEOUT_MS: ${env:SHARE_LINK_MAINTENANCE_REQUEST_TIMEOUT_MS, "10000"}
213
+ SHARE_LINK_MAINTENANCE_FINALIZE_RESERVE_MS: ${env:SHARE_LINK_MAINTENANCE_FINALIZE_RESERVE_MS, "5000"}
214
+ SHARE_LINK_MAINTENANCE_GRAPH_TIMEOUT_MS: ${env:SHARE_LINK_MAINTENANCE_GRAPH_TIMEOUT_MS, "15000"}
180
215
  tracing:
181
216
  apiGateway: true
182
217
  lambda: true
@@ -190,6 +225,31 @@ provider:
190
225
  - ETag
191
226
 
192
227
  functions:
228
+ inboxDeadLetterWorker:
229
+ handler: lambda.inboxDeadLetterWorker
230
+ timeout: 30
231
+ reservedConcurrency: 2
232
+ vpc:
233
+ securityGroupIds:
234
+ - 'Fn::GetAtt': [ServerlessSecurityGroup, GroupId]
235
+ subnetIds:
236
+ - Ref: PrivateSubnetA
237
+ events:
238
+ - sqs:
239
+ arn: ${construct:inbox-queue.dlqArn}
240
+ batchSize: 1
241
+ functionResponseType: ReportBatchItemFailures
242
+ inboxQueueDispatcher:
243
+ handler: lambda.inboxQueueDispatcher
244
+ timeout: 60
245
+ reservedConcurrency: 1
246
+ vpc:
247
+ securityGroupIds:
248
+ - 'Fn::GetAtt': [ServerlessSecurityGroup, GroupId]
249
+ subnetIds:
250
+ - Ref: PrivateSubnetA
251
+ events:
252
+ - schedule: rate(1 minute)
193
253
  inboxMaintenance:
194
254
  handler: lambda.inboxMaintenanceHandler
195
255
  timeout: 300
@@ -203,6 +263,23 @@ functions:
203
263
  rate: rate(1 day)
204
264
  enabled: true
205
265
 
266
+ # LC-2187 share-link recovery + cleanup. Dedicated entrypoint (no lambda.ts
267
+ # import-time work). Runs automatically with valid service configuration;
268
+ # absent wiring remains inert and partial wiring fails closed. The cadence
269
+ # matches the default Docker maintenance interval (5 minutes).
270
+ shareLinkMaintenance:
271
+ handler: shareLinkMaintenanceLambda.shareLinkMaintenanceHandler
272
+ timeout: 300
273
+ vpc:
274
+ securityGroupIds:
275
+ - 'Fn::GetAtt': [ServerlessSecurityGroup, GroupId]
276
+ subnetIds:
277
+ - Ref: PrivateSubnetA
278
+ events:
279
+ - schedule:
280
+ rate: rate(5 minutes)
281
+ enabled: true
282
+
206
283
  trpc:
207
284
  handler: lambda.trpcHandler
208
285
  vpc:
@@ -288,6 +365,40 @@ functions:
288
365
 
289
366
  resources:
290
367
  Resources:
368
+ InboxQueueAgeAlarm:
369
+ Type: AWS::CloudWatch::Alarm
370
+ Properties:
371
+ AlarmDescription: Inbox issuance has waited over five minutes
372
+ Namespace: AWS/SQS
373
+ MetricName: ApproximateAgeOfOldestMessage
374
+ Dimensions:
375
+ - Name: QueueName
376
+ Value:
377
+ Fn::Select:
378
+ - 4
379
+ - Fn::Split: ['/', '${construct:inbox-queue.queueUrl}']
380
+ Statistic: Maximum
381
+ Period: 60
382
+ EvaluationPeriods: 2
383
+ Threshold: 300
384
+ ComparisonOperator: GreaterThanThreshold
385
+ TreatMissingData: notBreaching
386
+ InboxDispatcherErrorsAlarm:
387
+ Type: AWS::CloudWatch::Alarm
388
+ Properties:
389
+ AlarmDescription: Durable inbox dispatch or recovery failed in three of five minutes
390
+ Namespace: AWS/Lambda
391
+ MetricName: Errors
392
+ Dimensions:
393
+ - Name: FunctionName
394
+ Value: { Ref: InboxQueueDispatcherLambdaFunction }
395
+ Statistic: Sum
396
+ Period: 60
397
+ EvaluationPeriods: 5
398
+ DatapointsToAlarm: 3
399
+ Threshold: 0
400
+ ComparisonOperator: GreaterThanThreshold
401
+ TreatMissingData: notBreaching
291
402
  VPC:
292
403
  Type: AWS::EC2::VPC
293
404
  Properties: