@learncard/network-brain-service 4.1.3 → 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 +143 -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,7 @@
1
1
  import {
2
2
  assertInboxRefreshEnabled,
3
3
  inboxRefreshRequestDigest,
4
+ getInboxRefreshRequestDigestCandidates,
4
5
  getInboxRefreshReplay,
5
6
  getInboxRefreshReceipt,
6
7
  resumeInboxRefreshDelivery,
@@ -41,6 +42,7 @@ import {
41
42
  PrepareRefreshableSendResult,
42
43
  AllocateCredentialStatusInputValidator,
43
44
  AllocatedBitstringStatusListEntryValidator,
45
+ ManagedCredentialRefreshBindingValidator,
44
46
  } from '@learncard/types';
45
47
  import {
46
48
  isVC2Format,
@@ -58,7 +60,7 @@ import {
58
60
  logCredentialFailed,
59
61
  } from '@helpers/activity.helpers';
60
62
 
61
- import { t, profileRoute } from '@routes';
63
+ import { t, profileRoute, credentialProfileRoute } from '@routes';
62
64
 
63
65
  import {
64
66
  getBoostByUri,
@@ -133,7 +135,11 @@ import {
133
135
  buildObv3AlignmentsForBoost,
134
136
  normalizeCredentialAlignments,
135
137
  } from '@services/skills-provider/inject';
136
- import { createBoost } from '@accesslayer/boost/create';
138
+ import {
139
+ createBoost,
140
+ createDisplayOnlyBoost,
141
+ normalizePublicDisplay,
142
+ } from '@accesslayer/boost/create';
137
143
  import { getBoostOwner } from '@accesslayer/boost/relationships/read';
138
144
  import { BoostInstance } from '@models';
139
145
  import { getProfileByProfileId } from '@accesslayer/profile/read';
@@ -145,12 +151,7 @@ import {
145
151
  getSigningAuthorityForUserByName,
146
152
  getPrimarySigningAuthorityForUser,
147
153
  } from '@accesslayer/signing-authority/relationships/read';
148
- import {
149
- getContractDetailsByUri,
150
- getContractTermsForProfile,
151
- getWritersForContract,
152
- } from '@accesslayer/consentflowcontract/relationships/read';
153
- import { setRelatedBoostForContract } from '@accesslayer/consentflowcontract/relationships/create';
154
+ import { resolveContractForSend } from '@helpers/send-contract.helpers';
154
155
 
155
156
  import {
156
157
  isClaimLinkAlreadySetForBoost,
@@ -198,12 +199,14 @@ import {
198
199
  extractManagedRefreshHandoff,
199
200
  getBoundRefreshBoostId,
200
201
  getCredentialRefreshServiceUrl,
202
+ getHolderEncryptionRecipientKeyIds,
201
203
  peekCredentialRefreshInitialBinding,
202
204
  sendRefreshableCredential,
203
205
  } from '@helpers/credential-refresh.helpers';
204
206
  import {
205
207
  claimRefreshSendIntent,
206
208
  computeRefreshSendRequestDigest,
209
+ getRefreshSendRequestDigestCandidates,
207
210
  getRefreshSendIntent,
208
211
  markRefreshSendIntentDelivered,
209
212
  reconcileBoundRefreshSendIntent,
@@ -212,6 +215,10 @@ import {
212
215
  type RefreshSendIntent,
213
216
  } from '@helpers/refresh-send-intent.helpers';
214
217
  import { getCredentialRefreshRuntimeEnvironment } from '@environment';
218
+ import { getCredentialRefresh } from '@accesslayer/credential-refresh';
219
+ import { prepareRefreshStatusEntries } from '@helpers/managed-status.helpers';
220
+ import { getCredentialStatusRootId, getStatusManagement } from '@helpers/status-list.helpers';
221
+ import { getBitstringStatusListEntries } from '@learncard/helpers';
215
222
  import { ensureCredentialRefreshConstraints } from '../models/credential-refresh-constraints';
216
223
  import { userHasRequiredScopes } from '@helpers/auth-grant.helpers';
217
224
  import { AUTH_GRANT_NO_ACCESS_SCOPE } from 'src/constants/auth-grant';
@@ -252,12 +259,16 @@ const resolveBoostCredentialInstance = async ({
252
259
  }) => {
253
260
  const {
254
261
  getCredentialByUri,
262
+ getCredentialById,
255
263
  getCredentialInstanceForBoostAndProfile,
256
264
  isCredentialInstanceOfBoost,
257
265
  } = await import('@accesslayer/credential/read');
258
266
 
259
267
  if (credentialUri) {
260
- const resolvedCredential = await getCredentialByUri(decodeURIComponent(credentialUri));
268
+ const version = await getCredentialByUri(decodeURIComponent(credentialUri));
269
+ const resolvedCredential = version
270
+ ? await getCredentialById(await getCredentialStatusRootId(version.id))
271
+ : null;
261
272
 
262
273
  if (!resolvedCredential) {
263
274
  throw new TRPCError({
@@ -281,63 +292,6 @@ const resolveBoostCredentialInstance = async ({
281
292
  return getCredentialInstanceForBoostAndProfile(boostId, recipientProfileId);
282
293
  };
283
294
 
284
- /**
285
- * Resolves the approved consent-flow contract terms for a unified send, and links a
286
- * newly created boost to the contract. Shared by the normal and managed-refresh send
287
- * paths so contract linkage semantics stay identical.
288
- */
289
- const resolveContractForSend = async (params: {
290
- profile: ProfileType;
291
- targetProfile: ProfileType;
292
- boost: BoostInstance;
293
- boostCreated: boolean;
294
- contractUri?: string;
295
- }) => {
296
- const { profile, targetProfile, boost, boostCreated, contractUri } = params;
297
-
298
- let contractTerms = null as Awaited<ReturnType<typeof getContractTermsForProfile>> | null;
299
- let contractDetails: Awaited<ReturnType<typeof getContractDetailsByUri>> | null = null;
300
-
301
- if (contractUri) {
302
- const decodedContractUri = decodeURIComponent(contractUri);
303
- contractDetails = await traceDb('getContractDetailsByUri', () =>
304
- getContractDetailsByUri(decodedContractUri)
305
- );
306
-
307
- if (!contractDetails) {
308
- throw new TRPCError({
309
- code: 'NOT_FOUND',
310
- message: 'Could not find contract',
311
- });
312
- }
313
-
314
- const terms = await traceDb('getContractTermsForProfile', () =>
315
- getContractTermsForProfile(targetProfile, contractDetails!.contract)
316
- );
317
-
318
- const writers = await traceDb('getWritersForContract', () =>
319
- getWritersForContract(contractDetails!.contract)
320
- );
321
- const isWriter = writers.some(writer => writer.profileId === profile.profileId);
322
- const isDenied = terms?.terms.deniedWriters?.includes(profile.profileId) ?? false;
323
- const categoryAllowed = boost.category
324
- ? terms?.terms.write?.credentials?.categories?.[boost.category] === true
325
- : true;
326
-
327
- if (terms && isWriter && !isDenied && categoryAllowed) {
328
- contractTerms = terms;
329
- }
330
- }
331
-
332
- if (boostCreated && contractDetails) {
333
- await traceDb('setRelatedBoostForContract', () =>
334
- setRelatedBoostForContract(contractDetails!.contract, boost)
335
- );
336
- }
337
-
338
- return contractTerms;
339
- };
340
-
341
295
  /** Validate managed send support and recipient before creating any send state. */
342
296
  const validateRefreshSendRecipient = async ({
343
297
  profile,
@@ -447,50 +401,85 @@ const managedRefreshServiceFor = (refreshId: string, domain: string) => ({
447
401
  * retried call reuses the same boost/allocation, or returns the completed result.
448
402
  */
449
403
  const prepareManagedRefreshSend = async (params: {
404
+ statusBinding?: 'server';
405
+ /** Internal signing already owns allocation; preserve its existing request digest. */
406
+ serverSigning?: boolean;
407
+ statusPurposes?: ('revocation' | 'suspension')[];
450
408
  profile: ProfileType;
451
409
  /** Returned by validateRefreshSendRecipient in this request, before any mutation. */
452
410
  targetProfile: ProfileType;
453
411
  domain: string;
454
412
  templateUri?: string;
455
413
  template?: z.infer<typeof SendBoostTemplateValidator>;
414
+ display?: import('@learncard/types').CredentialDisplay;
456
415
  contractUri?: string;
457
416
  credentialId?: string;
458
417
  templateData?: Record<string, unknown>;
459
418
  integrationId?: string;
460
419
  idempotencyKey?: string;
420
+ /** `'holder'` requests student-only encryption (LC-2345); default `'network'`. */
421
+ encryption?: 'network' | 'holder';
461
422
  }): Promise<PrepareRefreshableSendResult & { intent?: RefreshSendIntent }> => {
462
423
  const { profile, targetProfile, domain, templateUri, template, contractUri } = params;
463
424
  // Local recipients authenticate refresh requests as their network profile DID,
464
425
  // including when the caller addressed them by their controller did:key.
465
426
  const holderDid = getDidWeb(domain, targetProfile.profileId);
427
+ const holderMode = params.encryption === 'holder';
428
+ // Holder and network sends share the same explicit status-allocation contract.
429
+ let preallocateStatus =
430
+ (params.serverSigning && !template?.credential.credentialStatus) ||
431
+ params.statusBinding === 'server';
432
+ // Computed once and reused for both the request digest and anchor creation below,
433
+ // so a retried/keyed request can never digest a different display than the one
434
+ // actually stored on the anchor.
435
+ const normalizedDisplay = normalizePublicDisplay(params.display, template);
466
436
 
467
437
  let intent: RefreshSendIntent | undefined;
468
438
 
469
- if (params.idempotencyKey) {
439
+ if (params.idempotencyKey || holderMode) {
440
+ const request = {
441
+ recipientProfileId: targetProfile.profileId,
442
+ holderDid,
443
+ templateUri,
444
+ display: normalizedDisplay,
445
+ contractUri,
446
+ integrationId: params.integrationId,
447
+ credentialId: params.credentialId,
448
+ statusPurposes: params.statusPurposes,
449
+ encryption: params.encryption,
450
+ statusBinding: params.statusBinding,
451
+ };
470
452
  const claim = await claimRefreshSendIntent({
471
453
  issuerProfileId: profile.profileId,
472
- idempotencyKey: params.idempotencyKey,
473
- requestDigest: computeRefreshSendRequestDigest({
474
- recipientProfileId: targetProfile.profileId,
475
- holderDid,
476
- templateUri,
477
- template,
478
- contractUri,
479
- templateData: params.templateData,
480
- integrationId: params.integrationId,
481
- credentialId: params.credentialId,
482
- }),
454
+ idempotencyKey: params.idempotencyKey ?? uuid(),
455
+ requestDigest: computeRefreshSendRequestDigest(request),
456
+ requestDigestCandidates: getRefreshSendRequestDigestCandidates(request),
483
457
  });
484
458
 
485
459
  intent = claim.intent;
486
460
 
487
461
  if (claim.kind !== 'owned') {
462
+ const refresh = await getCredentialRefresh(intent.refreshId!);
488
463
  const prepared = {
464
+ ...(preallocateStatus && refresh?.statusEntries
465
+ ? {
466
+ statusEntries: JSON.parse(refresh.statusEntries),
467
+ statusManagement: getStatusManagement(
468
+ JSON.parse(refresh.statusEntries),
469
+ domain
470
+ ),
471
+ }
472
+ : {}),
489
473
  boostUri: intent.boostUri!,
490
474
  credentialId: intent.credentialId!,
491
475
  refreshId: intent.refreshId!,
492
476
  refreshService: managedRefreshServiceFor(intent.refreshId!, domain),
493
477
  holderDid: intent.holderDid!,
478
+ // Recomputed fresh (never cached on the intent): a manager change
479
+ // between prepare calls must be reflected immediately.
480
+ ...(refresh?.encryptionMode === 'holder'
481
+ ? { recipientKeyIds: await getHolderEncryptionRecipientKeyIds(targetProfile) }
482
+ : {}),
494
483
  intent,
495
484
  };
496
485
  const completed =
@@ -517,7 +506,7 @@ const prepareManagedRefreshSend = async (params: {
517
506
 
518
507
  if (!boost) throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find boost' });
519
508
  } else {
520
- boost = await createInlineBoostForSend(template!, profile, domain);
509
+ boost = await createDisplayOnlyBoost(normalizedDisplay, profile, domain);
521
510
  boostUri = getBoostUri(boost.id, domain);
522
511
 
523
512
  if (intent) intent = await recordRefreshSendIntent(intent, { boostUri });
@@ -560,28 +549,57 @@ const prepareManagedRefreshSend = async (params: {
560
549
  holderDid,
561
550
  credentialId,
562
551
  domain,
552
+ ...(holderMode ? { encryptionMode: 'holder' as const } : {}),
563
553
  })
564
554
  );
565
555
 
566
556
  refreshId = allocation.refreshId;
567
557
  }
568
558
 
559
+ // Older clients allocate their own coordinates after preparation. Bind those
560
+ // only at the signed handoff, where ownership and index reuse are validated.
561
+ const templateCredential = JSON.parse(boost.dataValues.boost) as UnsignedVC;
562
+ if (params.serverSigning && templateCredential.credentialStatus) preallocateStatus = false;
563
+ const statusEntries = preallocateStatus
564
+ ? await prepareRefreshStatusEntries(
565
+ refreshId,
566
+ profile.profileId,
567
+ domain,
568
+ params.statusPurposes
569
+ )
570
+ : undefined;
569
571
  if (intent) {
570
572
  intent = await recordRefreshSendIntent(intent, {
571
573
  boostUri,
572
574
  credentialId,
573
575
  refreshId,
574
576
  holderDid,
577
+ contractUri,
578
+ integrationId: params.integrationId,
575
579
  state: 'prepared',
576
580
  });
577
581
  }
578
582
 
579
583
  return {
580
584
  boostUri,
585
+ ...(statusEntries
586
+ ? { statusEntries, statusManagement: getStatusManagement(statusEntries, domain) }
587
+ : templateCredential.credentialStatus
588
+ ? {
589
+ statusManagement: getStatusManagement(
590
+ getBitstringStatusListEntries(templateCredential),
591
+ domain
592
+ ),
593
+ }
594
+ : {}),
581
595
  credentialId,
582
596
  refreshId,
583
597
  refreshService: managedRefreshServiceFor(refreshId, domain),
584
598
  holderDid,
599
+ // Recomputed every call (never persisted): see getHolderEncryptionRecipientKeyIds.
600
+ ...(holderMode
601
+ ? { recipientKeyIds: await getHolderEncryptionRecipientKeyIds(targetProfile) }
602
+ : {}),
585
603
  ...(intent ? { intent } : {}),
586
604
  };
587
605
  };
@@ -590,7 +608,7 @@ export const boostsRouter = t.router({
590
608
  // SDK preparation only: guards, boost anchor and refresh allocation. Never signs or
591
609
  // delivers. With an idempotencyKey a retried call reuses the same boost/allocation,
592
610
  // or returns `completed` when that key already delivered.
593
- prepareRefreshableSend: profileRoute
611
+ prepareRefreshableSend: credentialProfileRoute
594
612
  .meta({ requiredScope: 'boosts:write' })
595
613
  .input(PrepareRefreshableSendInputValidator)
596
614
  .output(PrepareRefreshableSendResultValidator)
@@ -1081,7 +1099,7 @@ export const boostsRouter = t.router({
1081
1099
  return credentialUri;
1082
1100
  }),
1083
1101
 
1084
- send: profileRoute
1102
+ send: credentialProfileRoute
1085
1103
  .meta({
1086
1104
  openapi: {
1087
1105
  protect: true,
@@ -1131,6 +1149,8 @@ export const boostsRouter = t.router({
1131
1149
  issuerProfileId: profile.profileId,
1132
1150
  idempotencyKey: input.idempotencyKey,
1133
1151
  requestDigest: inboxDigest!,
1152
+ requestDigestCandidates:
1153
+ getInboxRefreshRequestDigestCandidates(input),
1134
1154
  });
1135
1155
  if (claim.kind === 'delivered' && claim.intent.result)
1136
1156
  return claim.intent.result;
@@ -1138,7 +1158,8 @@ export const boostsRouter = t.router({
1138
1158
  const replay = await getInboxRefreshReplay(
1139
1159
  profile.profileId,
1140
1160
  inboxKey,
1141
- inboxDigest!
1161
+ inboxDigest!,
1162
+ getInboxRefreshRequestDigestCandidates(input)
1142
1163
  );
1143
1164
  if (
1144
1165
  replay &&
@@ -1229,23 +1250,45 @@ export const boostsRouter = t.router({
1229
1250
  // Keyed managed send without a pre-signed credential: preparation (boost +
1230
1251
  // allocation) goes through the idempotency intent instead of the generic
1231
1252
  // boost creation below.
1253
+ if (input.template?.status === 'DRAFT') {
1254
+ throw new TRPCError({
1255
+ code: 'FORBIDDEN',
1256
+ message:
1257
+ 'Draft Boosts can not be sent. Only Published Boosts can be sent.',
1258
+ });
1259
+ }
1232
1260
  const keyedPrepared =
1233
1261
  refreshRequested && input.idempotencyKey && !input.signedCredential
1234
1262
  ? await prepareManagedRefreshSend({
1263
+ serverSigning: true,
1235
1264
  profile,
1236
1265
  targetProfile: refreshTargetProfile!,
1237
1266
  domain,
1238
1267
  templateUri: input.templateUri,
1239
1268
  template: input.templateUri ? undefined : input.template,
1269
+ display: input.display,
1240
1270
  contractUri,
1241
1271
  templateData: input.templateData,
1242
1272
  integrationId: input.integrationId,
1243
1273
  idempotencyKey: input.idempotencyKey,
1274
+ statusPurposes: input.statusPurposes,
1244
1275
  })
1245
1276
  : undefined;
1246
1277
 
1247
1278
  if (keyedPrepared?.completed) return keyedPrepared.completed;
1248
1279
 
1280
+ // boost.send only ever builds a plaintext credential (signedCredential
1281
+ // input or a server/signing-authority-signed one below); a holder-mode
1282
+ // prepare (recipientKeyIds present) must deliver its encrypted handoff
1283
+ // through credentialRefresh.sendHolderEncryptedRefreshable instead.
1284
+ if (keyedPrepared?.recipientKeyIds) {
1285
+ throw new TRPCError({
1286
+ code: 'BAD_REQUEST',
1287
+ message:
1288
+ 'This credential refresh uses holder-mode encryption; send the encrypted handoff via credentialRefresh.sendHolderEncryptedRefreshable instead.',
1289
+ });
1290
+ }
1291
+
1249
1292
  // Check if recipient is email/phone (routes to Universal Inbox)
1250
1293
  const inboxRecipient = isInboxRecipient(input.recipient);
1251
1294
 
@@ -1282,7 +1325,15 @@ export const boostsRouter = t.router({
1282
1325
  boost = resolved;
1283
1326
  boostUri = input.templateUri;
1284
1327
  } else if (input.template) {
1285
- boost = await createInlineBoostForSend(input.template, profile, domain);
1328
+ boost =
1329
+ input.refresh || input.signedCredential
1330
+ ? await createDisplayOnlyBoost(
1331
+ input.display,
1332
+ profile,
1333
+ domain,
1334
+ input.template
1335
+ )
1336
+ : await createInlineBoostForSend(input.template, profile, domain);
1286
1337
 
1287
1338
  boostUri = getBoostUri(boost.id, domain);
1288
1339
  boostCreated = true;
@@ -1328,26 +1379,11 @@ export const boostsRouter = t.router({
1328
1379
  boost = preparedBoost;
1329
1380
  boostUri = preparedBoostUri;
1330
1381
  } else {
1331
- // Auto-create boost from the signed credential. A managed refresh
1332
- // service belongs to one holder's credential, never to a reusable
1333
- // template, so it is not stored on the boost.
1334
- const { refreshService: _refreshService, ...templateCredential } =
1335
- input.signedCredential as Record<string, unknown>;
1336
- const name =
1337
- typeof templateCredential.name === 'string'
1338
- ? templateCredential.name
1339
- : undefined;
1340
-
1341
- boost = await traceDb('createBoost:fromSignedCredential', () =>
1342
- createBoost(
1343
- (refreshRequested
1344
- ? templateCredential
1345
- : input.signedCredential!) as typeof input.signedCredential &
1346
- object,
1347
- profile,
1348
- { ...(name ? { name } : {}) },
1349
- domain
1350
- )
1382
+ boost = await createDisplayOnlyBoost(
1383
+ input.display,
1384
+ profile,
1385
+ domain,
1386
+ input.template
1351
1387
  );
1352
1388
 
1353
1389
  boostUri = getBoostUri(boost.id, domain);
@@ -1468,17 +1504,18 @@ export const boostsRouter = t.router({
1468
1504
  );
1469
1505
 
1470
1506
  if (binding.bound) {
1471
- const { uri, receipt } = await sendRefreshableCredential({
1472
- issuerProfile: profile,
1473
- refreshId,
1474
- credential: signedVc,
1475
- boostUri,
1476
- skipNotification,
1477
- domain,
1478
- activityId: binding.activityId,
1479
- integrationId: input.integrationId,
1480
- contractTerms: contractTerms ?? undefined,
1481
- });
1507
+ const { uri, receipt, statusManagement } =
1508
+ await sendRefreshableCredential({
1509
+ issuerProfile: profile,
1510
+ refreshId,
1511
+ credential: signedVc,
1512
+ boostUri,
1513
+ skipNotification,
1514
+ domain,
1515
+ activityId: binding.activityId,
1516
+ integrationId: input.integrationId,
1517
+ contractTerms: contractTerms ?? undefined,
1518
+ });
1482
1519
 
1483
1520
  const response = {
1484
1521
  type: 'boost' as const,
@@ -1486,6 +1523,7 @@ export const boostsRouter = t.router({
1486
1523
  uri: boostUri,
1487
1524
  activityId: binding.activityId ?? '',
1488
1525
  refresh: receipt,
1526
+ statusManagement,
1489
1527
  };
1490
1528
 
1491
1529
  if (intent)
@@ -1523,6 +1561,7 @@ export const boostsRouter = t.router({
1523
1561
  'prepareCredentialFromBoost:refresh',
1524
1562
  () =>
1525
1563
  prepareCredentialFromBoost(boost!, boostUri, domain, {
1564
+ credential: input.template?.credential,
1526
1565
  templateData: input.templateData as Record<
1527
1566
  string,
1528
1567
  unknown
@@ -1534,8 +1573,10 @@ export const boostsRouter = t.router({
1534
1573
  recipientName: targetProfile.displayName,
1535
1574
  })
1536
1575
  );
1537
- } catch (e) {
1538
- console.error('Failed to prepare boost credential', e);
1576
+ } catch (error) {
1577
+ if (error instanceof TRPCError && error.code === 'BAD_REQUEST')
1578
+ throw error;
1579
+ console.error('CREDENTIAL_PREPARATION_FAILED');
1539
1580
  throw new TRPCError({
1540
1581
  code: 'INTERNAL_SERVER_ERROR',
1541
1582
  message: 'Failed to prepare boost credential',
@@ -1569,6 +1610,21 @@ export const boostsRouter = t.router({
1569
1610
 
1570
1611
  refreshId = allocation.refreshId;
1571
1612
 
1613
+ const statusEntries = unsignedVc.credentialStatus
1614
+ ? undefined
1615
+ : await prepareRefreshStatusEntries(
1616
+ refreshId,
1617
+ profile.profileId,
1618
+ domain,
1619
+ input.statusPurposes
1620
+ );
1621
+ if (statusEntries)
1622
+ unsignedVc.credentialStatus =
1623
+ statusEntries.length === 1 &&
1624
+ !Array.isArray(unsignedVc.credentialStatus)
1625
+ ? statusEntries[0]
1626
+ : statusEntries;
1627
+
1572
1628
  const prepared = injectManagedRefreshService(
1573
1629
  unsignedVc,
1574
1630
  allocation.refreshService
@@ -1583,6 +1639,8 @@ export const boostsRouter = t.router({
1583
1639
  prepared,
1584
1640
  signingAuthority,
1585
1641
  domain,
1642
+ false,
1643
+ undefined,
1586
1644
  false
1587
1645
  )
1588
1646
  ).credential as VC
@@ -1609,7 +1667,10 @@ export const boostsRouter = t.router({
1609
1667
  intent,
1610
1668
  {
1611
1669
  refreshId,
1612
- refreshService: managedRefreshServiceFor(refreshId, domain),
1670
+ refreshService:
1671
+ ManagedCredentialRefreshBindingValidator.parse(
1672
+ signedVc.refreshService
1673
+ ),
1613
1674
  credentialId: signedVc.id as string,
1614
1675
  issuerDid:
1615
1676
  getCredentialIssuerId(signedVc) ??
@@ -1626,17 +1687,18 @@ export const boostsRouter = t.router({
1626
1687
  }
1627
1688
 
1628
1689
  try {
1629
- const { uri, receipt } = await sendRefreshableCredential({
1630
- issuerProfile: profile,
1631
- refreshId,
1632
- credential: signedVc,
1633
- boostUri,
1634
- skipNotification,
1635
- domain,
1636
- activityId,
1637
- integrationId: input.integrationId,
1638
- contractTerms: contractTerms ?? undefined,
1639
- });
1690
+ const { uri, receipt, statusManagement } =
1691
+ await sendRefreshableCredential({
1692
+ issuerProfile: profile,
1693
+ refreshId,
1694
+ credential: signedVc,
1695
+ boostUri,
1696
+ skipNotification,
1697
+ domain,
1698
+ activityId,
1699
+ integrationId: input.integrationId,
1700
+ contractTerms: contractTerms ?? undefined,
1701
+ });
1640
1702
 
1641
1703
  const response = {
1642
1704
  type: 'boost' as const,
@@ -1644,6 +1706,7 @@ export const boostsRouter = t.router({
1644
1706
  uri: boostUri,
1645
1707
  activityId,
1646
1708
  refresh: receipt,
1709
+ statusManagement,
1647
1710
  };
1648
1711
 
1649
1712
  if (intent) await markRefreshSendIntentDelivered(intent, response);
@@ -1714,6 +1777,9 @@ export const boostsRouter = t.router({
1714
1777
  'prepareCredentialFromBoost:inbox',
1715
1778
  () =>
1716
1779
  prepareCredentialFromBoost(boost!, boostUri, domain, {
1780
+ credential: input.refresh
1781
+ ? input.template?.credential
1782
+ : undefined,
1717
1783
  templateData: input.templateData as Record<
1718
1784
  string,
1719
1785
  unknown
@@ -1722,8 +1788,10 @@ export const boostsRouter = t.router({
1722
1788
  recipientName: inboxRecipientName,
1723
1789
  })
1724
1790
  );
1725
- } catch (e) {
1726
- console.error('Failed to prepare boost credential for inbox', e);
1791
+ } catch (error) {
1792
+ if (error instanceof TRPCError && error.code === 'BAD_REQUEST')
1793
+ throw error;
1794
+ console.error('INBOX_CREDENTIAL_PREPARATION_FAILED');
1727
1795
  throw new TRPCError({
1728
1796
  code: 'INTERNAL_SERVER_ERROR',
1729
1797
  message: 'Failed to prepare boost credential',
@@ -1749,7 +1817,10 @@ export const boostsRouter = t.router({
1749
1817
  boostUri,
1750
1818
  source: 'send',
1751
1819
  integrationId: input.integrationId,
1752
- metadata: { templateData: input.templateData },
1820
+ metadata:
1821
+ input.refresh || input.signedCredential
1822
+ ? undefined
1823
+ : { templateData: input.templateData },
1753
1824
  })
1754
1825
  );
1755
1826
 
@@ -1766,6 +1837,9 @@ export const boostsRouter = t.router({
1766
1837
  refresh: inboxRefreshRequested,
1767
1838
  idempotencyKey: inboxKey,
1768
1839
  refreshRequestDigest: inboxDigest,
1840
+ refreshRequestDigestCandidates: inboxRefreshRequested
1841
+ ? getInboxRefreshRequestDigestCandidates(input)
1842
+ : undefined,
1769
1843
  activityId,
1770
1844
  integrationId: input.integrationId,
1771
1845
  },
@@ -1858,8 +1932,10 @@ export const boostsRouter = t.router({
1858
1932
  recipientDid: input.recipient,
1859
1933
  })
1860
1934
  );
1861
- } catch (e) {
1862
- console.error('Failed to prepare boost credential', e);
1935
+ } catch (error) {
1936
+ if (error instanceof TRPCError && error.code === 'BAD_REQUEST')
1937
+ throw error;
1938
+ console.error('CREDENTIAL_PREPARATION_FAILED');
1863
1939
  throw new TRPCError({
1864
1940
  code: 'INTERNAL_SERVER_ERROR',
1865
1941
  message: 'Failed to prepare boost credential',
@@ -1895,7 +1971,9 @@ export const boostsRouter = t.router({
1895
1971
  boostUri,
1896
1972
  source: 'send',
1897
1973
  integrationId: input.integrationId,
1898
- metadata: { templateData: input.templateData },
1974
+ metadata: input.signedCredential
1975
+ ? undefined
1976
+ : { templateData: input.templateData },
1899
1977
  })
1900
1978
  );
1901
1979
 
@@ -2029,8 +2107,10 @@ export const boostsRouter = t.router({
2029
2107
  recipientName: targetProfile.displayName,
2030
2108
  })
2031
2109
  );
2032
- } catch (e) {
2033
- console.error('Failed to prepare boost credential', e);
2110
+ } catch (error) {
2111
+ if (error instanceof TRPCError && error.code === 'BAD_REQUEST')
2112
+ throw error;
2113
+ console.error('CREDENTIAL_PREPARATION_FAILED');
2034
2114
  throw new TRPCError({
2035
2115
  code: 'INTERNAL_SERVER_ERROR',
2036
2116
  message: 'Failed to prepare boost credential',
@@ -2066,7 +2146,9 @@ export const boostsRouter = t.router({
2066
2146
  boostUri,
2067
2147
  source: 'send',
2068
2148
  integrationId: input.integrationId,
2069
- metadata: { templateData: input.templateData },
2149
+ metadata: input.signedCredential
2150
+ ? undefined
2151
+ : { templateData: input.templateData },
2070
2152
  })
2071
2153
  );
2072
2154