@learncard/network-brain-service 4.1.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/.env.example +14 -0
  2. package/CHANGELOG.md +164 -0
  3. package/README.md +27 -1
  4. package/bootstrapLambda.test.ts +70 -32
  5. package/config/config.dev.json +1 -0
  6. package/config/config.example.json +2 -1
  7. package/config/config.production.json +1 -0
  8. package/config/config.scouts.dev.json +1 -0
  9. package/config/config.scouts.production.json +1 -0
  10. package/consentFlowDeadlineLambda.test.ts +108 -0
  11. package/consentFlowDeadlineLambda.ts +18 -0
  12. package/consentFlowDeadlineLambdaApp.ts +14 -0
  13. package/dist/accesslayer/boost/create.d.ts +8 -2
  14. package/dist/accesslayer/boost/create.d.ts.map +1 -1
  15. package/dist/accesslayer/campaign/audience.d.ts +4 -4
  16. package/dist/accesslayer/campaign/audience.d.ts.map +1 -1
  17. package/dist/accesslayer/campaign/create.d.ts +1 -1
  18. package/dist/accesslayer/campaign/create.d.ts.map +1 -1
  19. package/dist/accesslayer/campaign/delivery.d.ts +5 -0
  20. package/dist/accesslayer/campaign/delivery.d.ts.map +1 -0
  21. package/dist/accesslayer/campaign/dispatch.d.ts +5 -0
  22. package/dist/accesslayer/campaign/dispatch.d.ts.map +1 -0
  23. package/dist/accesslayer/campaign/dispatch.test.d.ts +2 -0
  24. package/dist/accesslayer/campaign/dispatch.test.d.ts.map +1 -0
  25. package/dist/accesslayer/campaign/email.d.ts +10 -0
  26. package/dist/accesslayer/campaign/email.d.ts.map +1 -0
  27. package/dist/accesslayer/campaign/email.test.d.ts +2 -0
  28. package/dist/accesslayer/campaign/email.test.d.ts.map +1 -0
  29. package/dist/accesslayer/campaign/preferences.d.ts +5 -0
  30. package/dist/accesslayer/campaign/preferences.d.ts.map +1 -0
  31. package/dist/accesslayer/campaign/read.d.ts.map +1 -1
  32. package/dist/accesslayer/campaign/test.d.ts +5 -0
  33. package/dist/accesslayer/campaign/test.d.ts.map +1 -0
  34. package/dist/accesslayer/campaign/test.test.d.ts +2 -0
  35. package/dist/accesslayer/campaign/test.test.d.ts.map +1 -0
  36. package/dist/accesslayer/campaign/update.d.ts.map +1 -1
  37. package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
  38. package/dist/accesslayer/consentflowcontract/delete.d.ts +1 -1
  39. package/dist/accesslayer/consentflowcontract/delete.d.ts.map +1 -1
  40. package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
  41. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +2 -1
  42. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  43. package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +19 -4
  44. package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
  45. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +6 -1
  46. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  47. package/dist/accesslayer/consentflowcontract/versions.d.ts +68 -0
  48. package/dist/accesslayer/consentflowcontract/versions.d.ts.map +1 -0
  49. package/dist/accesslayer/credential/create.d.ts +3 -1
  50. package/dist/accesslayer/credential/create.d.ts.map +1 -1
  51. package/dist/accesslayer/credential/delete.d.ts +1 -0
  52. package/dist/accesslayer/credential/delete.d.ts.map +1 -1
  53. package/dist/accesslayer/credential/relationships/create.d.ts +15 -3
  54. package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
  55. package/dist/accesslayer/credential/relationships/read.d.ts +2 -0
  56. package/dist/accesslayer/credential/relationships/read.d.ts.map +1 -1
  57. package/dist/accesslayer/credential/relationships/update.d.ts.map +1 -1
  58. package/dist/accesslayer/credential-refresh/read.d.ts +1 -0
  59. package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
  60. package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
  61. package/dist/accesslayer/inbox-credential/create.d.ts +1 -0
  62. package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
  63. package/dist/accesslayer/inbox-credential/refresh.d.ts +7 -0
  64. package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -1
  65. package/dist/app.d.ts +11 -1
  66. package/dist/app.d.ts.map +1 -1
  67. package/dist/cache/app-store.caches.d.ts +61 -1
  68. package/dist/cache/app-store.caches.d.ts.map +1 -1
  69. package/dist/campaign-unsubscribe.d.ts +7 -0
  70. package/dist/campaign-unsubscribe.d.ts.map +1 -0
  71. package/dist/campaign-unsubscribe.test.d.ts +2 -0
  72. package/dist/campaign-unsubscribe.test.d.ts.map +1 -0
  73. package/dist/config/environment.d.ts +42 -0
  74. package/dist/config/environment.d.ts.map +1 -1
  75. package/dist/config/stageConfig.d.ts +4 -0
  76. package/dist/config/stageConfig.d.ts.map +1 -1
  77. package/dist/credential-refresh.d.ts.map +1 -1
  78. package/dist/helpers/boost.helpers.d.ts +4 -1
  79. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  80. package/dist/helpers/campaign-app-origin.helpers.d.ts +2 -0
  81. package/dist/helpers/campaign-app-origin.helpers.d.ts.map +1 -0
  82. package/dist/helpers/campaign-email.helpers.d.ts +13 -0
  83. package/dist/helpers/campaign-email.helpers.d.ts.map +1 -0
  84. package/dist/helpers/campaign-notifications.helpers.d.ts +7 -0
  85. package/dist/helpers/campaign-notifications.helpers.d.ts.map +1 -0
  86. package/dist/helpers/campaign-queue.helpers.d.ts +7 -0
  87. package/dist/helpers/campaign-queue.helpers.d.ts.map +1 -0
  88. package/dist/helpers/campaign-queue.helpers.test.d.ts +2 -0
  89. package/dist/helpers/campaign-queue.helpers.test.d.ts.map +1 -0
  90. package/dist/helpers/campaign-unsubscribe-page.d.ts +2 -0
  91. package/dist/helpers/campaign-unsubscribe-page.d.ts.map +1 -0
  92. package/dist/helpers/campaign-unsubscribe.helpers.d.ts +7 -0
  93. package/dist/helpers/campaign-unsubscribe.helpers.d.ts.map +1 -0
  94. package/dist/helpers/campaign-webhook.helpers.test.d.ts +2 -0
  95. package/dist/helpers/campaign-webhook.helpers.test.d.ts.map +1 -0
  96. package/dist/helpers/consentflow-deadline.helpers.d.ts +5 -0
  97. package/dist/helpers/consentflow-deadline.helpers.d.ts.map +1 -0
  98. package/dist/helpers/consentflow-legacy-migration.helpers.d.ts +2 -0
  99. package/dist/helpers/consentflow-legacy-migration.helpers.d.ts.map +1 -0
  100. package/dist/helpers/contract-events.helpers.d.ts.map +1 -1
  101. package/dist/helpers/contract.helpers.d.ts +1 -1
  102. package/dist/helpers/contract.helpers.d.ts.map +1 -1
  103. package/dist/helpers/credential-display.helpers.d.ts +5 -0
  104. package/dist/helpers/credential-display.helpers.d.ts.map +1 -0
  105. package/dist/helpers/credential-display.helpers.test.d.ts +2 -0
  106. package/dist/helpers/credential-display.helpers.test.d.ts.map +1 -0
  107. package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -1
  108. package/dist/helpers/credential-refresh-materiality.helpers.d.ts +4 -0
  109. package/dist/helpers/credential-refresh-materiality.helpers.d.ts.map +1 -1
  110. package/dist/helpers/credential-refresh.helpers.d.ts +30 -1
  111. package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
  112. package/dist/helpers/credential-status-mutation.helpers.d.ts +5 -0
  113. package/dist/helpers/credential-status-mutation.helpers.d.ts.map +1 -0
  114. package/dist/helpers/credential-status-mutation.helpers.test.d.ts +2 -0
  115. package/dist/helpers/credential-status-mutation.helpers.test.d.ts.map +1 -0
  116. package/dist/helpers/credential.helpers.d.ts +1 -1
  117. package/dist/helpers/credential.helpers.d.ts.map +1 -1
  118. package/dist/helpers/inbox-refresh.helpers.d.ts +4 -2
  119. package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -1
  120. package/dist/helpers/inbox.helpers.d.ts +1 -0
  121. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  122. package/dist/helpers/learnCard.helpers.d.ts +1 -0
  123. package/dist/helpers/learnCard.helpers.d.ts.map +1 -1
  124. package/dist/helpers/managed-status.helpers.d.ts +7 -0
  125. package/dist/helpers/managed-status.helpers.d.ts.map +1 -0
  126. package/dist/helpers/managed-status.helpers.test.d.ts +2 -0
  127. package/dist/helpers/managed-status.helpers.test.d.ts.map +1 -0
  128. package/dist/helpers/notification-delivery-boundary.helpers.test.d.ts +2 -0
  129. package/dist/helpers/notification-delivery-boundary.helpers.test.d.ts.map +1 -0
  130. package/dist/helpers/notification-webhook-fetch.helpers.d.ts +13 -0
  131. package/dist/helpers/notification-webhook-fetch.helpers.d.ts.map +1 -0
  132. package/dist/helpers/notification-webhook-fetch.helpers.test.d.ts +2 -0
  133. package/dist/helpers/notification-webhook-fetch.helpers.test.d.ts.map +1 -0
  134. package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
  135. package/dist/helpers/notifications.helpers.d.ts.map +1 -1
  136. package/dist/helpers/refresh-send-intent.helpers.d.ts +13 -5
  137. package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -1
  138. package/dist/helpers/refresh-send-intent.helpers.test.d.ts +2 -0
  139. package/dist/helpers/refresh-send-intent.helpers.test.d.ts.map +1 -0
  140. package/dist/helpers/send-contract.helpers.d.ts +11 -0
  141. package/dist/helpers/send-contract.helpers.d.ts.map +1 -0
  142. package/dist/helpers/status-list.helpers.d.ts +12 -1
  143. package/dist/helpers/status-list.helpers.d.ts.map +1 -1
  144. package/dist/index.js +2 -2
  145. package/dist/models/ConsentFlowContract.d.ts.map +1 -1
  146. package/dist/models/ConsentFlowContractVersion.d.ts +12 -0
  147. package/dist/models/ConsentFlowContractVersion.d.ts.map +1 -0
  148. package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
  149. package/dist/models/Credential.d.ts +2 -0
  150. package/dist/models/Credential.d.ts.map +1 -1
  151. package/dist/models/CredentialRefresh.d.ts +5 -0
  152. package/dist/models/CredentialRefresh.d.ts.map +1 -1
  153. package/dist/models/Integration.d.ts +1 -1
  154. package/dist/models/Profile.d.ts.map +1 -1
  155. package/dist/models/campaign-constraints.d.ts.map +1 -1
  156. package/dist/models/index.d.ts +1 -0
  157. package/dist/models/index.d.ts.map +1 -1
  158. package/dist/openapi.d.ts.map +1 -1
  159. package/dist/publish-metafile.json +1 -1
  160. package/dist/routes/activity.d.ts +15 -5
  161. package/dist/routes/activity.d.ts.map +1 -1
  162. package/dist/routes/app-store.d.ts +15 -5
  163. package/dist/routes/app-store.d.ts.map +1 -1
  164. package/dist/routes/auth-grants.d.ts +12 -2
  165. package/dist/routes/auth-grants.d.ts.map +1 -1
  166. package/dist/routes/bench.d.ts +11 -1
  167. package/dist/routes/bench.d.ts.map +1 -1
  168. package/dist/routes/boosts.d.ts +70 -15
  169. package/dist/routes/boosts.d.ts.map +1 -1
  170. package/dist/routes/campaigns.d.ts +183 -51
  171. package/dist/routes/campaigns.d.ts.map +1 -1
  172. package/dist/routes/claim-hooks.d.ts +11 -1
  173. package/dist/routes/claim-hooks.d.ts.map +1 -1
  174. package/dist/routes/contact-methods.d.ts +11 -1
  175. package/dist/routes/contact-methods.d.ts.map +1 -1
  176. package/dist/routes/contracts.d.ts +374 -8
  177. package/dist/routes/contracts.d.ts.map +1 -1
  178. package/dist/routes/credential-input-privacy.test.d.ts +2 -0
  179. package/dist/routes/credential-input-privacy.test.d.ts.map +1 -0
  180. package/dist/routes/credential-refreshes.d.ts +210 -1
  181. package/dist/routes/credential-refreshes.d.ts.map +1 -1
  182. package/dist/routes/credential-share-previews.d.ts +11 -1
  183. package/dist/routes/credential-share-previews.d.ts.map +1 -1
  184. package/dist/routes/credentials.d.ts +132 -3
  185. package/dist/routes/credentials.d.ts.map +1 -1
  186. package/dist/routes/did-metadata.d.ts +11 -1
  187. package/dist/routes/did-metadata.d.ts.map +1 -1
  188. package/dist/routes/federation.d.ts +11 -1
  189. package/dist/routes/federation.d.ts.map +1 -1
  190. package/dist/routes/inbox.d.ts +54 -19
  191. package/dist/routes/inbox.d.ts.map +1 -1
  192. package/dist/routes/index.d.ts +100 -1
  193. package/dist/routes/index.d.ts.map +1 -1
  194. package/dist/routes/integrations.d.ts +14 -4
  195. package/dist/routes/integrations.d.ts.map +1 -1
  196. package/dist/routes/presentations.d.ts +11 -1
  197. package/dist/routes/presentations.d.ts.map +1 -1
  198. package/dist/routes/profile-manager.d.ts +11 -1
  199. package/dist/routes/profile-manager.d.ts.map +1 -1
  200. package/dist/routes/profiles.d.ts +11 -1
  201. package/dist/routes/profiles.d.ts.map +1 -1
  202. package/dist/routes/public-share-links.d.ts +22 -2
  203. package/dist/routes/public-share-links.d.ts.map +1 -1
  204. package/dist/routes/share-links.d.ts +36 -16
  205. package/dist/routes/share-links.d.ts.map +1 -1
  206. package/dist/routes/skill-frameworks.d.ts +11 -1
  207. package/dist/routes/skill-frameworks.d.ts.map +1 -1
  208. package/dist/routes/skills.d.ts +11 -1
  209. package/dist/routes/skills.d.ts.map +1 -1
  210. package/dist/routes/storage.d.ts +37 -23
  211. package/dist/routes/storage.d.ts.map +1 -1
  212. package/dist/routes/test.d.ts +79 -3
  213. package/dist/routes/test.d.ts.map +1 -1
  214. package/dist/routes/utilities.d.ts +11 -1
  215. package/dist/routes/utilities.d.ts.map +1 -1
  216. package/dist/routes/workflows.d.ts +11 -1
  217. package/dist/routes/workflows.d.ts.map +1 -1
  218. package/dist/services/delivery/adapters/postmark.adapter.d.ts +3 -0
  219. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  220. package/dist/services/delivery/delivery.service.d.ts +10 -0
  221. package/dist/services/delivery/delivery.service.d.ts.map +1 -1
  222. package/dist/types/activity.d.ts +3 -3
  223. package/dist/types/app-manifest.d.ts +10 -10
  224. package/dist/types/campaign.d.ts +13 -1
  225. package/dist/types/campaign.d.ts.map +1 -1
  226. package/dist/types/consentflowcontract.d.ts +175 -7
  227. package/dist/types/consentflowcontract.d.ts.map +1 -1
  228. package/dist/types/credential-refresh.d.ts +27 -0
  229. package/dist/types/credential-refresh.d.ts.map +1 -1
  230. package/dist/types/credential.d.ts +5 -0
  231. package/dist/types/credential.d.ts.map +1 -1
  232. package/dist/types/integration.d.ts +3 -3
  233. package/lambda.ts +44 -14
  234. package/lambdaApp.ts +20 -0
  235. package/package.json +20 -19
  236. package/project.json +5 -0
  237. package/scripts/migrate-consent-flow-versions.ts +9 -0
  238. package/scripts/report-send-template-exposure.ts +47 -0
  239. package/scripts/verify-python-contracts.ts +16 -1
  240. package/serverless-local.yml +15 -2
  241. package/serverless.function-env.cjs +4 -0
  242. package/serverless.local.yml +12 -0
  243. package/serverless.yml +48 -3
  244. package/src/accesslayer/boost/create.ts +62 -7
  245. package/src/accesslayer/campaign/README.md +275 -6
  246. package/src/accesslayer/campaign/audience.ts +39 -7
  247. package/src/accesslayer/campaign/create.ts +4 -2
  248. package/src/accesslayer/campaign/delivery.ts +66 -0
  249. package/src/accesslayer/campaign/dispatch.test.ts +59 -0
  250. package/src/accesslayer/campaign/dispatch.ts +172 -0
  251. package/src/accesslayer/campaign/email.test.ts +73 -0
  252. package/src/accesslayer/campaign/email.ts +91 -0
  253. package/src/accesslayer/campaign/preferences.ts +53 -0
  254. package/src/accesslayer/campaign/read.ts +3 -1
  255. package/src/accesslayer/campaign/state.ts +1 -1
  256. package/src/accesslayer/campaign/test.test.ts +31 -0
  257. package/src/accesslayer/campaign/test.ts +75 -0
  258. package/src/accesslayer/campaign/update.ts +15 -4
  259. package/src/accesslayer/consentflowcontract/create.ts +20 -1
  260. package/src/accesslayer/consentflowcontract/delete.ts +121 -3
  261. package/src/accesslayer/consentflowcontract/read.ts +15 -9
  262. package/src/accesslayer/consentflowcontract/relationships/create.ts +70 -36
  263. package/src/accesslayer/consentflowcontract/relationships/read.ts +305 -73
  264. package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +1 -1
  265. package/src/accesslayer/consentflowcontract/relationships/update.ts +269 -98
  266. package/src/accesslayer/consentflowcontract/versions.ts +790 -0
  267. package/src/accesslayer/credential/create.ts +33 -9
  268. package/src/accesslayer/credential/delete.ts +45 -0
  269. package/src/accesslayer/credential/relationships/create.ts +102 -47
  270. package/src/accesslayer/credential/relationships/update.ts +8 -116
  271. package/src/accesslayer/credential-refresh/read.ts +26 -2
  272. package/src/accesslayer/credential-refresh/update.ts +6 -2
  273. package/src/accesslayer/inbox-credential/create.ts +7 -1
  274. package/src/accesslayer/inbox-credential/refresh.ts +14 -4
  275. package/src/campaign-unsubscribe.test.ts +158 -0
  276. package/src/campaign-unsubscribe.ts +86 -0
  277. package/src/config/environment.test.ts +52 -0
  278. package/src/config/environment.ts +58 -0
  279. package/src/config/serverlessFunctionEnv.test.ts +59 -0
  280. package/src/credential-refresh.ts +1 -0
  281. package/src/docker-entry.ts +26 -1
  282. package/src/helpers/boost-storage.helpers.test.ts +107 -41
  283. package/src/helpers/boost.helpers.ts +88 -40
  284. package/src/helpers/campaign-app-origin.helpers.ts +25 -0
  285. package/src/helpers/campaign-email.helpers.ts +113 -0
  286. package/src/helpers/campaign-notifications.helpers.ts +127 -0
  287. package/src/helpers/campaign-queue.helpers.test.ts +442 -0
  288. package/src/helpers/campaign-queue.helpers.ts +122 -0
  289. package/src/helpers/campaign-unsubscribe-page.ts +107 -0
  290. package/src/helpers/campaign-unsubscribe.helpers.ts +56 -0
  291. package/src/helpers/campaign-webhook.helpers.test.ts +137 -0
  292. package/src/helpers/consentflow-deadline.helpers.ts +22 -0
  293. package/src/helpers/consentflow-legacy-migration.helpers.ts +179 -0
  294. package/src/helpers/contract-events.helpers.ts +35 -3
  295. package/src/helpers/contract-requests.helpers.ts +1 -1
  296. package/src/helpers/contract.helpers.ts +5 -1
  297. package/src/helpers/credential-display.helpers.test.ts +68 -0
  298. package/src/helpers/credential-display.helpers.ts +15 -0
  299. package/src/helpers/credential-refresh-email.helpers.ts +5 -12
  300. package/src/helpers/credential-refresh-materiality.helpers.test.ts +138 -1
  301. package/src/helpers/credential-refresh-materiality.helpers.ts +62 -1
  302. package/src/helpers/credential-refresh-proof.helpers.test.ts +1 -1
  303. package/src/helpers/credential-refresh-proof.helpers.ts +2 -2
  304. package/src/helpers/credential-refresh.helpers.ts +1166 -376
  305. package/src/helpers/credential-status-mutation.helpers.test.ts +155 -0
  306. package/src/helpers/credential-status-mutation.helpers.ts +166 -0
  307. package/src/helpers/credential.helpers.ts +19 -11
  308. package/src/helpers/finalize-inbox.helpers.ts +1 -1
  309. package/src/helpers/inbox-refresh.helpers.ts +34 -8
  310. package/src/helpers/inbox.helpers.ts +2 -0
  311. package/src/helpers/learnCard.helpers.ts +23 -0
  312. package/src/helpers/managed-status.helpers.test.ts +101 -0
  313. package/src/helpers/managed-status.helpers.ts +147 -0
  314. package/src/helpers/notification-delivery-boundary.helpers.test.ts +114 -0
  315. package/src/helpers/notification-webhook-fetch.helpers.test.ts +228 -0
  316. package/src/helpers/notification-webhook-fetch.helpers.ts +166 -0
  317. package/src/helpers/notificationQueue.helpers.ts +5 -0
  318. package/src/helpers/notifications.helpers.ts +19 -12
  319. package/src/helpers/refresh-send-intent.helpers.test.ts +60 -0
  320. package/src/helpers/refresh-send-intent.helpers.ts +81 -18
  321. package/src/helpers/send-contract.helpers.ts +36 -0
  322. package/src/helpers/status-list.helpers.test.ts +29 -12
  323. package/src/helpers/status-list.helpers.ts +329 -20
  324. package/src/models/ConsentFlowContract.ts +3 -0
  325. package/src/models/ConsentFlowContractVersion.ts +50 -0
  326. package/src/models/ConsentFlowTerms.ts +4 -0
  327. package/src/models/Credential.ts +2 -0
  328. package/src/models/CredentialRefresh.ts +2 -0
  329. package/src/models/Profile.ts +2 -0
  330. package/src/models/campaign-constraints.ts +13 -8
  331. package/src/models/index.ts +2 -0
  332. package/src/openapi.ts +21 -0
  333. package/src/routes/app-store.ts +2 -1
  334. package/src/routes/boosts.ts +219 -137
  335. package/src/routes/campaigns.ts +98 -19
  336. package/src/routes/contracts.ts +433 -115
  337. package/src/routes/credential-input-privacy.test.ts +92 -0
  338. package/src/routes/credential-refreshes.ts +273 -2
  339. package/src/routes/federation.ts +7 -2
  340. package/src/routes/inbox.ts +6 -1
  341. package/src/routes/index.ts +31 -2
  342. package/src/routes/workflows.ts +61 -46
  343. package/src/services/delivery/adapters/postmark.adapter.test.ts +70 -1
  344. package/src/services/delivery/adapters/postmark.adapter.ts +84 -11
  345. package/src/services/delivery/delivery.service.ts +4 -0
  346. package/src/types/campaign.ts +14 -1
  347. package/src/types/consentflowcontract.ts +43 -2
  348. package/src/types/credential-refresh.ts +11 -1
  349. package/src/types/credential.ts +2 -0
  350. package/test/boost-status-cleanup.spec.ts +123 -0
  351. package/test/campaigns-email.unit.spec.ts +268 -0
  352. package/test/campaigns.spec.ts +804 -30
  353. package/test/consent-autoboost-idempotency.spec.ts +432 -0
  354. package/test/consent-data-boundaries.spec.ts +322 -26
  355. package/test/consent-legacy-migration.spec.ts +315 -0
  356. package/test/consent-recipients.spec.ts +7 -7
  357. package/test/consent-version-updates.spec.ts +761 -0
  358. package/test/consentflow.spec.ts +698 -226
  359. package/test/contract-requests.spec.ts +55 -9
  360. package/test/credential-refresh-allocation.spec.ts +212 -14
  361. package/test/credential-refresh-email.spec.ts +6 -10
  362. package/test/credential-refresh-endpoint.spec.ts +3 -0
  363. package/test/credential-refresh-publication.spec.ts +52 -15
  364. package/test/credential-storage.neo4j.spec.ts +33 -6
  365. package/test/helpers/legacy-autoboost.ts +48 -0
  366. package/test/holder-mode-send.spec.ts +710 -0
  367. package/test/inbox-refresh.spec.ts +105 -0
  368. package/test/inbox-security.e2e.ts +18 -22
  369. package/test/managed-status-revocation.spec.ts +970 -0
  370. package/test/notifications-worker.spec.ts +31 -25
  371. package/test/refresh-send.spec.ts +334 -5
  372. package/test/smart-resume-consent.spec.ts +1 -1
  373. package/test/verification/campaigns.ts +40 -0
  374. package/vitest.campaigns.config.ts +2 -0
  375. package/vitest.config.ts +14 -0
@@ -1,6 +1,124 @@
1
- import { ConsentFlowContract } from '@models';
1
+ import { neogma } from '@instance';
2
2
  import { DbContractType } from 'types/consentflowcontract';
3
+ import { TRPCError } from '@trpc/server';
4
+ import { ConsentFlowContractValidator } from '@learncard/types';
5
+ import { diffContractChange, buildContractUpdatePreview } from '@learncard/helpers';
6
+ import { getConsentFlowContractVersionRecord } from './versions';
7
+ import { tryDispatchContractEvent } from '@helpers/contract-events.helpers';
8
+ import { constructUri } from '@helpers/uri.helpers';
9
+ import { environment } from '@environment';
10
+ import { v4 as uuid } from 'uuid';
3
11
 
4
- export const deleteConsentFlowContract = async (contract: DbContractType): Promise<number> => {
5
- return ConsentFlowContract.delete({ where: { id: contract.id }, detach: true });
12
+ /** Retire in place so terms and transaction history remain exportable. */
13
+ export const deleteConsentFlowContract = async (
14
+ contract: DbContractType,
15
+ domain = environment.DOMAIN_NAME ?? 'network.learncard.com',
16
+ appBaseUrl = domain
17
+ ): Promise<number> => {
18
+ const contractId = contract.id;
19
+ const currentVersion = contract.currentVersion ?? contract.version ?? 1;
20
+ const snapshot = await getConsentFlowContractVersionRecord(contractId, currentVersion);
21
+ if (!snapshot)
22
+ throw new TRPCError({
23
+ code: 'PRECONDITION_FAILED',
24
+ message: 'Contract version snapshot is missing',
25
+ });
26
+
27
+ const diff = diffContractChange(snapshot, {
28
+ ...snapshot,
29
+ contract: ConsentFlowContractValidator.parse({}),
30
+ });
31
+ const kind = 'relaxed' as const;
32
+ const preview = buildContractUpdatePreview({ name: snapshot.name, kind, diff });
33
+ const createdAt = new Date().toISOString();
34
+ const nextVersion = currentVersion + 1;
35
+ const reviewUrl = `${appBaseUrl.replace(/\/+$/, '')}/consent-flow/review?uri=${encodeURIComponent(constructUri('contract', contractId, domain))}`;
36
+ const eventId = `${contractId}:version:${nextVersion}`;
37
+ const notificationData = {
38
+ contractUri: constructUri('contract', contractId, domain),
39
+ contractName: snapshot.name,
40
+ fromVersion: currentVersion,
41
+ toVersion: nextVersion,
42
+ kind,
43
+ diff,
44
+ reviewUrl,
45
+ preview,
46
+ };
47
+ const result = await neogma.queryRunner.run(
48
+ `MATCH (c:ConsentFlowContract {id:$id})
49
+ WHERE coalesce(c.status,'active') <> 'retired'
50
+ SET c._contractVersionLock=coalesce(c._contractVersionLock,0)+1
51
+ WITH c
52
+ WHERE coalesce(c.currentVersion,1)=$currentVersion
53
+ OPTIONAL MATCH (terms:ConsentFlowTerms)-[:CONSENTS_TO]->(c)
54
+ WHERE terms.status IN ['live','needs_reconsent','suspended','consumed']
55
+ WITH c, collect(DISTINCT terms) AS terms
56
+ MATCH (c)-[:CREATED_BY]->(owner:Profile)
57
+ WITH c, terms, collect(DISTINCT owner.profileId)[0] AS ownerProfileId
58
+ UNWIND CASE WHEN size(terms)=0 THEN [null] ELSE terms END AS term
59
+ OPTIONAL MATCH (term)<-[:CREATED_BY]-(consenter:Profile)
60
+ WITH c, terms, ownerProfileId, collect(DISTINCT consenter.profileId) AS recipientProfileIds
61
+ CREATE (version:ConsentFlowContractVersion {
62
+ id:$versionId, version:$nextVersion, contract:$contractSnapshot,
63
+ autoboosts:$autoboostSnapshot, writers:$writerSnapshot,
64
+ name:$name, subtitle:$subtitle, description:$description,
65
+ reasonForAccessing:$reasonForAccessing, image:$image,
66
+ needsGuardianConsent:$needsGuardianConsent, redirectUrl:$redirectUrl,
67
+ frontDoorBoostUri:$frontDoorBoostUri, expiresAt:$expiresAt,
68
+ kind:$kind, createdAt:$createdAt
69
+ })
70
+ CREATE (c)-[:HAS_VERSION]->(version)
71
+ SET c.currentVersion=$nextVersion, c.status='retired', c.updatedAt=$createdAt,
72
+ c.latestUpdate=$latestUpdate
73
+ REMOVE c._contractVersionLock
74
+ FOREACH (term IN terms |
75
+ SET term.status='withdrawn', term.mutationVersion=coalesce(term.mutationVersion,0)+1, term.updatedAt=$createdAt
76
+ CREATE (transaction:ConsentFlowTransaction {id:randomUUID(), action:'withdraw', date:$createdAt})-[:IS_FOR]->(term))
77
+ CREATE (event:ConsentFlowEvent {
78
+ id:$eventId, contractId:c.id, fromId:ownerProfileId, ownerId:ownerProfileId,
79
+ domain:$eventDomain, kind:'contract_updated', payload:$eventPayload,
80
+ cleanupPending:true, createdAt:$createdAt
81
+ })
82
+ FOREACH (recipientId IN [id IN recipientProfileIds WHERE id IS NOT NULL AND id <> ownerProfileId] |
83
+ CREATE (event)-[:HAS_DELIVERY]->(:ConsentFlowEventDelivery {
84
+ id:event.id + ':' + recipientId, toId:recipientId, state:'pending',
85
+ nextAttemptAt:$createdAt, eventCreatedAt:$createdAt, attempts:0, role:'target'
86
+ }))
87
+ RETURN count(DISTINCT c) AS count`,
88
+ {
89
+ id: contractId,
90
+ currentVersion,
91
+ eventId,
92
+ eventDomain: domain,
93
+ eventPayload: JSON.stringify(notificationData),
94
+ nextVersion,
95
+ versionId: uuid(),
96
+ contractSnapshot: JSON.stringify(snapshot.contract),
97
+ autoboostSnapshot: JSON.stringify(snapshot.autoboosts),
98
+ writerSnapshot: JSON.stringify(snapshot.writers),
99
+ name: snapshot.name,
100
+ subtitle: snapshot.subtitle ?? null,
101
+ description: snapshot.description ?? null,
102
+ reasonForAccessing: snapshot.reasonForAccessing ?? null,
103
+ image: snapshot.image ?? null,
104
+ needsGuardianConsent: snapshot.needsGuardianConsent ?? false,
105
+ redirectUrl: snapshot.redirectUrl ?? null,
106
+ frontDoorBoostUri: snapshot.frontDoorBoostUri ?? null,
107
+ expiresAt: snapshot.expiresAt ?? null,
108
+ kind,
109
+ createdAt,
110
+ latestUpdate: JSON.stringify({
111
+ fromVersion: currentVersion,
112
+ toVersion: nextVersion,
113
+ kind,
114
+ diff,
115
+ createdAt,
116
+ preview,
117
+ }),
118
+ }
119
+ );
120
+ const record = result.records[0];
121
+ if (!record || Number(record.get('count') ?? 0) === 0) return 0;
122
+ await tryDispatchContractEvent(eventId);
123
+ return Number(record.get('count'));
6
124
  };
@@ -3,21 +3,27 @@ import { QueryBuilder, BindParam } from 'neogma';
3
3
 
4
4
  import { getIdFromUri } from '@helpers/uri.helpers';
5
5
  import { ConsentFlowContract, ConsentFlowInstance, Profile } from '@models';
6
- import { DbContractType } from 'types/consentflowcontract';
6
+ import { DbContractType, DbContractValidator } from 'types/consentflowcontract';
7
7
  import { inflateObject } from '@helpers/objects.helpers';
8
8
  import type { ContractRequestFields } from '@learncard/types';
9
9
  import { ProfileType } from 'types/profile';
10
10
 
11
11
  export const getContractById = async (id: string): Promise<DbContractType | null> => {
12
- return inflateObject<DbContractType>(
13
- (
14
- await new QueryBuilder()
15
- .match({ model: ConsentFlowContract, where: { id }, identifier: 'contract' })
16
- .return('contract')
17
- .limit(1)
18
- .run()
19
- ).records[0]?.toObject().contract.properties
12
+ const result = await neogma.queryRunner.run(
13
+ `MATCH (contract:ConsentFlowContract {id:$id})
14
+ OPTIONAL MATCH (contract)-[:HAS_VERSION]->(version:ConsentFlowContractVersion)
15
+ WITH contract, version ORDER BY version.version DESC
16
+ WITH contract, collect(version)[0] AS latest
17
+ RETURN properties(contract) AS contract, properties(latest) AS latest`,
18
+ { id }
20
19
  );
20
+ const record = result.records[0];
21
+ if (!record) return null;
22
+ const contract = DbContractValidator.parse(inflateObject(record.get('contract')));
23
+ const latest = record.get('latest') as Record<string, unknown> | null;
24
+ contract.currentVersion = Number(contract.currentVersion ?? latest?.version ?? 1);
25
+ contract.version = Number(latest?.version ?? contract.currentVersion);
26
+ return contract;
21
27
  };
22
28
 
23
29
  export const getContractByUri = async (uri: string): Promise<DbContractType | null> => {
@@ -1,4 +1,3 @@
1
- import { termsReferralSnapshotCypher } from '@helpers/consent-referral.helpers';
2
1
  import { appendConsentEvent, tryDispatchContractEvent } from '@helpers/contract-events.helpers';
3
2
  import {
4
3
  ConsentFlowTerms as ConsentFlowTermsType,
@@ -20,7 +19,14 @@ import { DbContractType, FlatDbTermsType } from 'types/consentflowcontract';
20
19
  import { BoostType } from 'types/boost';
21
20
  import { flattenObject, inflateObject } from '@helpers/objects.helpers';
22
21
  import { convertQueryResultToPropertiesObjectArray } from '@helpers/neo4j.helpers';
23
- import { reconsentTerms } from './update';
22
+ import {
23
+ reconsentTerms,
24
+ claimAutoBoostTransaction,
25
+ completeAutoBoostTransaction,
26
+ releaseAutoBoostClaim,
27
+ isAutoBoostConsentCurrent,
28
+ } from './update';
29
+ import { getConsentFlowContractVersionRecord } from '@accesslayer/consentflowcontract/versions';
24
30
  import { getBoostUri, sendBoost, stampIssuanceDate } from '@helpers/boost.helpers';
25
31
  import { setCredentialSubjectIds } from '@helpers/credentialSubject.helpers';
26
32
  import { getDidWeb } from '@helpers/did.helpers';
@@ -85,6 +91,7 @@ export const consentToContract = async (
85
91
  audienceVersion,
86
92
  expectedRequestId,
87
93
  smartResumeFingerprint,
94
+ acceptVersion,
88
95
  }: {
89
96
  terms: ConsentFlowTermsType;
90
97
  expiresAt?: string;
@@ -94,9 +101,11 @@ export const consentToContract = async (
94
101
  audienceVersion?: number;
95
102
  expectedRequestId?: string;
96
103
  smartResumeFingerprint?: string;
104
+ acceptVersion?: number;
97
105
  },
98
106
  domain: string
99
107
  ) => {
108
+ const expectedContractVersion = Number(contract.currentVersion ?? 1);
100
109
  const terms = cloneDeep(_terms);
101
110
  const deniedWriters: string[] = terms.deniedWriters ?? [];
102
111
 
@@ -143,6 +152,7 @@ export const consentToContract = async (
143
152
  audienceVersion,
144
153
  smartResumeFingerprint,
145
154
  expectedRequestId,
155
+ acceptVersion,
146
156
  },
147
157
  domain
148
158
  );
@@ -174,6 +184,7 @@ export const consentToContract = async (
174
184
  : {}),
175
185
  ...(guardianApproval ? { guardianApproval } : {}),
176
186
  }),
187
+ expectedContractVersion,
177
188
  audienceVersion: audienceVersion ?? null,
178
189
  expectedRequestId: expectedRequestId ?? null,
179
190
  consenterProfileId: consenter.profileId,
@@ -181,7 +192,9 @@ export const consentToContract = async (
181
192
  ),
182
193
  contract.id
183
194
  )
184
- .where(`${audienceVersionWhere} AND ${consentMutationWhere}`)
195
+ .where(
196
+ `${audienceVersionWhere} AND ${consentMutationWhere} AND coalesce(contract.currentVersion, 1) = $expectedContractVersion AND coalesce(contract.status, 'active') <> 'retired'`
197
+ )
185
198
  .set('contract.hasConsented = true')
186
199
  .with('contract')
187
200
  .match({
@@ -209,7 +222,9 @@ export const consentToContract = async (
209
222
  model: ConsentFlowTerms,
210
223
  properties: {
211
224
  id: termsId,
212
- status: oneTime ? 'stale' : 'live',
225
+ status: oneTime ? 'consumed' : 'live',
226
+ consentedVersion: contract.currentVersion ?? 1,
227
+ lastReviewedVersion: contract.currentVersion ?? 1,
213
228
  createdAt: transaction.date,
214
229
  updatedAt: transaction.date,
215
230
  ...(expiresAt ? { expiresAt } : {}),
@@ -248,23 +263,31 @@ export const consentToContract = async (
248
263
  );
249
264
  assertAudienceMutation(result.records.length);
250
265
 
251
- const autoBoosts = await ConsentFlowContract.findRelationships({
252
- alias: 'autoReceive',
253
- where: { source: { id: contract.id } },
254
- });
266
+ const versionSnapshot = await getConsentFlowContractVersionRecord(
267
+ contract.id,
268
+ Number(contract.currentVersion ?? 1)
269
+ );
270
+ const configuredAutoBoosts = new Map(
271
+ (versionSnapshot?.autoboosts ?? []).map(config => [config.id, config])
272
+ );
273
+ const autoBoosts = (
274
+ await ConsentFlowContract.findRelationships({
275
+ alias: 'autoReceive',
276
+ where: { source: { id: contract.id } },
277
+ })
278
+ ).filter(boost => configuredAutoBoosts.has(boost.target.id));
255
279
 
256
280
  if (autoBoosts.length > 0) {
257
281
  await Promise.all(
258
282
  autoBoosts.map(async boost => {
283
+ let claimId: string | undefined;
284
+ let sendStarted = false;
259
285
  try {
260
- const signingAuthorityEndpoint =
261
- boost.relationship?.signingAuthorityEndpoint || 'default';
262
- const signingAuthorityName =
263
- boost.relationship?.signingAuthorityName || 'default';
264
-
286
+ const config = configuredAutoBoosts.get(boost.target.id)!;
287
+ const signingAuthorityEndpoint = config.signingAuthorityEndpoint || 'default';
288
+ const signingAuthorityName = config.signingAuthorityName || 'default';
265
289
  const issuer =
266
- (boost.relationship?.issuer &&
267
- (await getProfileByProfileId(boost.relationship.issuer))) ||
290
+ (config.issuer && (await getProfileByProfileId(config.issuer))) ||
268
291
  contractOwner;
269
292
 
270
293
  if (terms.deniedWriters?.includes(issuer.profileId)) return;
@@ -278,6 +301,14 @@ export const consentToContract = async (
278
301
 
279
302
  if (categoryWritePermission !== true) return;
280
303
  }
304
+ claimId =
305
+ (await claimAutoBoostTransaction(
306
+ contract.id,
307
+ termsId,
308
+ String(boost.target.id),
309
+ Boolean(oneTime)
310
+ )) ?? undefined;
311
+ if (!claimId) return;
281
312
 
282
313
  const contractOwnerSigningAuthority = await getSigningAuthorityForUserByName(
283
314
  issuer,
@@ -286,6 +317,8 @@ export const consentToContract = async (
286
317
  );
287
318
 
288
319
  if (!contractOwnerSigningAuthority) {
320
+ await releaseAutoBoostClaim(claimId);
321
+ claimId = undefined;
289
322
  console.error(
290
323
  `Signing authority "${signingAuthorityName}" at endpoint "${signingAuthorityEndpoint}" not found for contract owner`
291
324
  );
@@ -325,36 +358,24 @@ export const consentToContract = async (
325
358
  [getDidWeb(domain, contractOwner.profileId)]
326
359
  );
327
360
 
328
- const boostTransaction = {
329
- id: uuid(),
330
- action: 'write',
331
- date: new Date().toISOString(),
332
- } as const satisfies ConsentFlowTransaction;
333
-
334
361
  const termsResult = await new QueryBuilder()
335
362
  .match({
336
363
  identifier: 'terms',
337
364
  model: ConsentFlowTerms,
338
365
  where: { id: termsId },
339
366
  })
340
- .create({
341
- related: [
342
- {
343
- identifier: 'boostTransaction',
344
- model: ConsentFlowTransactionModel,
345
- properties: boostTransaction,
346
- },
347
- ConsentFlowTransactionModel.getRelationshipByAlias('isFor'),
348
- { identifier: 'terms' },
349
- ],
350
- })
351
- .raw(termsReferralSnapshotCypher('boostTransaction'))
352
367
  .return('terms')
353
368
  .run();
354
-
355
369
  const updatedTerms = termsResult.records[0]?.get('terms')
356
370
  .properties as FlatDbTermsType;
357
-
371
+ if (
372
+ !(await isAutoBoostConsentCurrent(contract.id, termsId, Boolean(oneTime)))
373
+ ) {
374
+ await releaseAutoBoostClaim(claimId);
375
+ claimId = undefined;
376
+ return;
377
+ }
378
+ sendStarted = true;
358
379
  await sendBoost({
359
380
  from: { type: 'profile', profile: contractOwner },
360
381
  to: consenter,
@@ -364,9 +385,22 @@ export const consentToContract = async (
364
385
  skipNotification: true,
365
386
  autoAcceptCredential: false,
366
387
  contractTerms: inflateObject(updatedTerms),
388
+ contractWriterProfileId: issuer.profileId,
389
+ allowConsumedOneTime: Boolean(oneTime),
367
390
  });
391
+ await completeAutoBoostTransaction(claimId, termsId, String(boost.target.id));
392
+ claimId = undefined;
368
393
  } catch (error) {
369
- console.error('Error processing auto-boost:', error);
394
+ if (claimId && sendStarted) {
395
+ console.error(
396
+ 'Auto-boost claim send failed; delivery may be ambiguous',
397
+ { claimId, termsId, boostId: String(boost.target.id) },
398
+ error
399
+ );
400
+ } else {
401
+ console.error('Error processing auto-boost:', error);
402
+ }
403
+ if (claimId && !sendStarted) await releaseAutoBoostClaim(claimId);
370
404
  }
371
405
  })
372
406
  );