@learncard/network-brain-service 3.17.0 → 3.19.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 (274) hide show
  1. package/.env.example +4 -0
  2. package/AGENTS.md +177 -159
  3. package/CHANGELOG.md +148 -0
  4. package/README.md +26 -8
  5. package/compose.inbox.yml +7 -0
  6. package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
  7. package/dist/accesslayer/auth-grant/read.d.ts +1 -0
  8. package/dist/accesslayer/auth-grant/read.d.ts.map +1 -1
  9. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +3 -2
  10. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  11. package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +1 -0
  12. package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
  13. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +5 -3
  14. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  15. package/dist/accesslayer/credential/create.d.ts +2 -1
  16. package/dist/accesslayer/credential/create.d.ts.map +1 -1
  17. package/dist/accesslayer/credential/create.test.d.ts +2 -0
  18. package/dist/accesslayer/credential/create.test.d.ts.map +1 -0
  19. package/dist/accesslayer/credential/relationships/read.d.ts +1 -0
  20. package/dist/accesslayer/credential/relationships/read.d.ts.map +1 -1
  21. package/dist/accesslayer/credential-activity/create.d.ts.map +1 -1
  22. package/dist/accesslayer/credential-refresh/email-delivery.d.ts +18 -0
  23. package/dist/accesslayer/credential-refresh/email-delivery.d.ts.map +1 -0
  24. package/dist/accesslayer/credential-refresh/index.d.ts +1 -0
  25. package/dist/accesslayer/credential-refresh/index.d.ts.map +1 -1
  26. package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
  27. package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
  28. package/dist/accesslayer/inbox-batch/store.d.ts +33 -0
  29. package/dist/accesslayer/inbox-batch/store.d.ts.map +1 -0
  30. package/dist/accesslayer/inbox-credential/create.d.ts +5 -0
  31. package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
  32. package/dist/accesslayer/inbox-credential/refresh.d.ts +71 -0
  33. package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -0
  34. package/dist/accesslayer/inbox-credential/update.d.ts +1 -1
  35. package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
  36. package/dist/cache/atomic-counter.test-helpers.d.ts +3 -0
  37. package/dist/cache/atomic-counter.test-helpers.d.ts.map +1 -0
  38. package/dist/cache/atomic-counter.test.d.ts +2 -0
  39. package/dist/cache/atomic-counter.test.d.ts.map +1 -0
  40. package/dist/cache/index.d.ts +1 -1
  41. package/dist/cache/index.d.ts.map +1 -1
  42. package/dist/config/environment.d.ts +46 -0
  43. package/dist/config/environment.d.ts.map +1 -1
  44. package/dist/credential-refresh.d.ts.map +1 -1
  45. package/dist/didkit_wasm_bg.wasm +0 -0
  46. package/dist/helpers/activity.helpers.d.ts +4 -0
  47. package/dist/helpers/activity.helpers.d.ts.map +1 -1
  48. package/dist/helpers/boost-storage.helpers.test.d.ts +2 -0
  49. package/dist/helpers/boost-storage.helpers.test.d.ts.map +1 -0
  50. package/dist/helpers/boost.helpers.d.ts +3 -7
  51. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  52. package/dist/helpers/contact-method.helpers.d.ts.map +1 -1
  53. package/dist/helpers/contact-method.helpers.test.d.ts +2 -0
  54. package/dist/helpers/contact-method.helpers.test.d.ts.map +1 -0
  55. package/dist/helpers/credential-refresh-email-content.helpers.d.ts +44 -0
  56. package/dist/helpers/credential-refresh-email-content.helpers.d.ts.map +1 -0
  57. package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts +2 -0
  58. package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts.map +1 -0
  59. package/dist/helpers/credential-refresh-email.helpers.d.ts +17 -0
  60. package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -0
  61. package/dist/helpers/credential-refresh-proof.helpers.d.ts +4 -0
  62. package/dist/helpers/credential-refresh-proof.helpers.d.ts.map +1 -0
  63. package/dist/helpers/credential-refresh-proof.helpers.test.d.ts +2 -0
  64. package/dist/helpers/credential-refresh-proof.helpers.test.d.ts.map +1 -0
  65. package/dist/helpers/credential-refresh.helpers.d.ts +45 -2
  66. package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
  67. package/dist/helpers/credential.helpers.d.ts +4 -2
  68. package/dist/helpers/credential.helpers.d.ts.map +1 -1
  69. package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
  70. package/dist/helpers/inbox-batch-http.helpers.d.ts +15 -0
  71. package/dist/helpers/inbox-batch-http.helpers.d.ts.map +1 -0
  72. package/dist/helpers/inbox-batch-http.helpers.test.d.ts +2 -0
  73. package/dist/helpers/inbox-batch-http.helpers.test.d.ts.map +1 -0
  74. package/dist/helpers/inbox-batch-status.helpers.d.ts +3 -0
  75. package/dist/helpers/inbox-batch-status.helpers.d.ts.map +1 -0
  76. package/dist/helpers/inbox-batch.helpers.d.ts +10 -0
  77. package/dist/helpers/inbox-batch.helpers.d.ts.map +1 -0
  78. package/dist/helpers/inbox-batch.helpers.test.d.ts +2 -0
  79. package/dist/helpers/inbox-batch.helpers.test.d.ts.map +1 -0
  80. package/dist/helpers/inbox-issuance-error.helpers.d.ts +13 -0
  81. package/dist/helpers/inbox-issuance-error.helpers.d.ts.map +1 -0
  82. package/dist/helpers/inbox-queue.helpers.d.ts +11 -0
  83. package/dist/helpers/inbox-queue.helpers.d.ts.map +1 -0
  84. package/dist/helpers/inbox-queue.helpers.test.d.ts +2 -0
  85. package/dist/helpers/inbox-queue.helpers.test.d.ts.map +1 -0
  86. package/dist/helpers/inbox-refresh.helpers.d.ts +186 -0
  87. package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -0
  88. package/dist/helpers/inbox.helpers.d.ts +9 -1
  89. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  90. package/dist/helpers/issuedCredentialStatus.helpers.d.ts +5 -0
  91. package/dist/helpers/issuedCredentialStatus.helpers.d.ts.map +1 -0
  92. package/dist/helpers/notifications.helpers.d.ts.map +1 -1
  93. package/dist/helpers/rateLimit.helpers.d.ts.map +1 -1
  94. package/dist/helpers/refresh-send-intent.helpers.d.ts +51 -0
  95. package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -0
  96. package/dist/helpers/shim.d.ts +1 -1
  97. package/dist/helpers/shim.d.ts.map +1 -1
  98. package/dist/helpers/signingAuthority.helpers.d.ts +3 -2
  99. package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
  100. package/dist/helpers/signingAuthority.helpers.test.d.ts +2 -0
  101. package/dist/helpers/signingAuthority.helpers.test.d.ts.map +1 -0
  102. package/dist/helpers/status-list.helpers.d.ts.map +1 -1
  103. package/dist/helpers/status-list.helpers.test.d.ts +2 -0
  104. package/dist/helpers/status-list.helpers.test.d.ts.map +1 -0
  105. package/dist/inbox-queue-local.d.ts +2 -0
  106. package/dist/inbox-queue-local.d.ts.map +1 -0
  107. package/dist/index.js +2 -2
  108. package/dist/models/AuthGrant.d.ts.map +1 -1
  109. package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
  110. package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
  111. package/dist/models/Credential.d.ts +1 -0
  112. package/dist/models/Credential.d.ts.map +1 -1
  113. package/dist/models/CredentialActivity.d.ts +1 -0
  114. package/dist/models/CredentialActivity.d.ts.map +1 -1
  115. package/dist/models/CredentialRefresh.d.ts +5 -2
  116. package/dist/models/CredentialRefresh.d.ts.map +1 -1
  117. package/dist/models/InboxCredential.d.ts +1 -0
  118. package/dist/models/InboxCredential.d.ts.map +1 -1
  119. package/dist/models/credential-refresh-constraints.d.ts.map +1 -1
  120. package/dist/openapi.d.ts.map +1 -1
  121. package/dist/routes/act-as.test.d.ts +2 -0
  122. package/dist/routes/act-as.test.d.ts.map +1 -0
  123. package/dist/routes/activity.d.ts +3 -0
  124. package/dist/routes/activity.d.ts.map +1 -1
  125. package/dist/routes/app-store.d.ts.map +1 -1
  126. package/dist/routes/auth-grants.d.ts +10 -0
  127. package/dist/routes/auth-grants.d.ts.map +1 -1
  128. package/dist/routes/boosts.d.ts +506 -12
  129. package/dist/routes/boosts.d.ts.map +1 -1
  130. package/dist/routes/contracts.d.ts +48 -2
  131. package/dist/routes/contracts.d.ts.map +1 -1
  132. package/dist/routes/credential-refreshes.d.ts +4 -4
  133. package/dist/routes/credential-refreshes.d.ts.map +1 -1
  134. package/dist/routes/credentials.d.ts +8 -2
  135. package/dist/routes/credentials.d.ts.map +1 -1
  136. package/dist/routes/did-metadata.d.ts +8 -8
  137. package/dist/routes/federation.d.ts +2 -2
  138. package/dist/routes/inbox.d.ts +973 -31
  139. package/dist/routes/inbox.d.ts.map +1 -1
  140. package/dist/routes/index.d.ts +47 -127
  141. package/dist/routes/index.d.ts.map +1 -1
  142. package/dist/routes/presentations.d.ts +6 -6
  143. package/dist/routes/storage.d.ts +63 -63
  144. package/dist/routes/test.d.ts +6 -0
  145. package/dist/routes/test.d.ts.map +1 -1
  146. package/dist/routes/workflows.d.ts +14 -14
  147. package/dist/routes/workflows.d.ts.map +1 -1
  148. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  149. package/dist/services/delivery/adapters/postmark.adapter.test.d.ts +2 -0
  150. package/dist/services/delivery/adapters/postmark.adapter.test.d.ts.map +1 -0
  151. package/dist/services/registry/registry.factory.d.ts.map +1 -1
  152. package/dist/types/activity.d.ts +4 -0
  153. package/dist/types/activity.d.ts.map +1 -1
  154. package/dist/types/consentflowcontract.d.ts +22 -8
  155. package/dist/types/consentflowcontract.d.ts.map +1 -1
  156. package/dist/types/credential-refresh.d.ts +10 -1
  157. package/dist/types/credential-refresh.d.ts.map +1 -1
  158. package/dist/types/credential.d.ts +7 -0
  159. package/dist/types/credential.d.ts.map +1 -1
  160. package/dist/types/did-metadata.d.ts +2 -2
  161. package/dist/types/inbox-batch.d.ts +38 -0
  162. package/dist/types/inbox-batch.d.ts.map +1 -0
  163. package/inbox-e2e-setup.ts +53 -1
  164. package/inbox-elasticmq.conf +10 -0
  165. package/lambda.ts +61 -16
  166. package/package.json +18 -16
  167. package/scripts/setup-inbox-queues.ts +60 -0
  168. package/serverless-local.yml +2 -0
  169. package/serverless.yml +75 -0
  170. package/src/accesslayer/app-store-listing/relationships/read.ts +4 -3
  171. package/src/accesslayer/auth-grant/read.ts +2 -1
  172. package/src/accesslayer/consentflowcontract/relationships/create.ts +15 -5
  173. package/src/accesslayer/consentflowcontract/relationships/read.ts +161 -63
  174. package/src/accesslayer/consentflowcontract/relationships/update.ts +43 -10
  175. package/src/accesslayer/credential/create.test.ts +141 -0
  176. package/src/accesslayer/credential/create.ts +27 -3
  177. package/src/accesslayer/credential-activity/create.ts +1 -0
  178. package/src/accesslayer/credential-refresh/email-delivery.ts +116 -0
  179. package/src/accesslayer/credential-refresh/index.ts +1 -0
  180. package/src/accesslayer/credential-refresh/read.ts +8 -2
  181. package/src/accesslayer/credential-refresh/update.ts +4 -0
  182. package/src/accesslayer/inbox-batch/store.ts +469 -0
  183. package/src/accesslayer/inbox-credential/create.ts +51 -19
  184. package/src/accesslayer/inbox-credential/refresh.ts +221 -0
  185. package/src/accesslayer/inbox-credential/update.ts +1 -1
  186. package/src/cache/atomic-counter.test-helpers.ts +27 -0
  187. package/src/cache/atomic-counter.test.ts +6 -0
  188. package/src/cache/index.ts +25 -13
  189. package/src/config/environment.ts +26 -0
  190. package/src/credential-refresh.ts +5 -1
  191. package/src/docker-entry.ts +8 -0
  192. package/src/helpers/activity.helpers.ts +4 -0
  193. package/src/helpers/app-counter.helpers.ts +1 -1
  194. package/src/helpers/boost-storage.helpers.test.ts +242 -0
  195. package/src/helpers/boost.helpers.ts +58 -437
  196. package/src/helpers/contact-method.helpers.test.ts +43 -0
  197. package/src/helpers/contact-method.helpers.ts +3 -4
  198. package/src/helpers/credential-refresh-email-content.helpers.test.ts +222 -0
  199. package/src/helpers/credential-refresh-email-content.helpers.ts +155 -0
  200. package/src/helpers/credential-refresh-email.helpers.ts +201 -0
  201. package/src/helpers/credential-refresh-proof.helpers.test.ts +103 -0
  202. package/src/helpers/credential-refresh-proof.helpers.ts +43 -0
  203. package/src/helpers/credential-refresh.helpers.ts +385 -107
  204. package/src/helpers/credential.helpers.ts +6 -2
  205. package/src/helpers/finalize-inbox.helpers.ts +39 -22
  206. package/src/helpers/inbox-batch-http.helpers.test.ts +188 -0
  207. package/src/helpers/inbox-batch-http.helpers.ts +150 -0
  208. package/src/helpers/inbox-batch-status.helpers.ts +11 -0
  209. package/src/helpers/inbox-batch.helpers.test.ts +668 -0
  210. package/src/helpers/inbox-batch.helpers.ts +374 -0
  211. package/src/helpers/inbox-issuance-error.helpers.ts +25 -0
  212. package/src/helpers/inbox-queue.helpers.test.ts +253 -0
  213. package/src/helpers/inbox-queue.helpers.ts +372 -0
  214. package/src/helpers/inbox-refresh.helpers.ts +432 -0
  215. package/src/helpers/inbox.helpers.ts +301 -50
  216. package/src/helpers/issuedCredentialStatus.helpers.ts +11 -0
  217. package/src/helpers/notifications.helpers.ts +4 -2
  218. package/src/helpers/rateLimit.helpers.ts +2 -5
  219. package/src/helpers/refresh-send-intent.helpers.ts +273 -0
  220. package/src/helpers/shim.ts +32 -31
  221. package/src/helpers/signingAuthority.helpers.test.ts +198 -0
  222. package/src/helpers/signingAuthority.helpers.ts +41 -18
  223. package/src/helpers/status-list.helpers.test.ts +77 -0
  224. package/src/helpers/status-list.helpers.ts +31 -8
  225. package/src/inbox-queue-local.ts +28 -0
  226. package/src/models/AuthGrant.ts +1 -0
  227. package/src/models/ConsentFlowTerms.ts +4 -0
  228. package/src/models/ConsentFlowTransaction.ts +4 -0
  229. package/src/models/Credential.ts +1 -0
  230. package/src/models/CredentialActivity.ts +3 -5
  231. package/src/models/CredentialRefresh.ts +4 -2
  232. package/src/models/InboxCredential.ts +2 -0
  233. package/src/models/credential-refresh-constraints.test.ts +8 -3
  234. package/src/models/credential-refresh-constraints.ts +5 -0
  235. package/src/openapi.ts +8 -0
  236. package/src/routes/act-as.test.ts +239 -0
  237. package/src/routes/app-store.ts +69 -52
  238. package/src/routes/auth-grants.ts +13 -11
  239. package/src/routes/boosts.ts +943 -120
  240. package/src/routes/contracts.ts +55 -7
  241. package/src/routes/credential-refreshes.test.ts +8 -2
  242. package/src/routes/credential-refreshes.ts +4 -1
  243. package/src/routes/credentials.ts +2 -1
  244. package/src/routes/inbox.ts +95 -49
  245. package/src/routes/index.ts +164 -88
  246. package/src/routes/skills.ts +1 -1
  247. package/src/routes/workflows.ts +80 -31
  248. package/src/services/delivery/adapters/postmark.adapter.test.ts +54 -0
  249. package/src/services/delivery/adapters/postmark.adapter.ts +12 -0
  250. package/src/services/registry/registry.factory.ts +2 -1
  251. package/src/types/activity.ts +2 -0
  252. package/src/types/consentflowcontract.ts +21 -3
  253. package/src/types/credential-refresh.ts +21 -2
  254. package/src/types/credential.ts +13 -1
  255. package/src/types/inbox-batch.ts +46 -0
  256. package/test/app-store-listing.spec.ts +66 -30
  257. package/test/atomic-counter.e2e.ts +12 -0
  258. package/test/auth-grant.spec.ts +21 -21
  259. package/test/bitstring-status-list.spec.ts +12 -9
  260. package/test/consentflow.spec.ts +453 -26
  261. package/test/credential-refresh-email.spec.ts +663 -0
  262. package/test/credential-refresh-publication.spec.ts +13 -5
  263. package/test/guardian-gated-routes.spec.ts +337 -180
  264. package/test/helpers/inbox-e2e-environment.ts +6 -0
  265. package/test/inbox-batch.spec.ts +1338 -0
  266. package/test/inbox-refresh.spec.ts +534 -0
  267. package/test/inbox-security.e2e.ts +48 -3
  268. package/test/openapi.spec.ts +41 -1
  269. package/test/refresh-send.spec.ts +1799 -0
  270. package/test/registry.spec.ts +56 -11
  271. package/test/revoke-suspend-per-instance.spec.ts +9 -1
  272. package/vitest.config.ts +13 -0
  273. package/vitest.inbox-e2e.config.ts +5 -1
  274. package/vitest.integration.config.ts +2 -0
@@ -24,8 +24,6 @@ import {
24
24
  VCValidator,
25
25
  JWEValidator,
26
26
  UnsignedVC,
27
- VC,
28
- JWE,
29
27
  AutoBoostConfigValidator,
30
28
  LCNNotificationTypeEnumValidator,
31
29
  LCNProfileValidator,
@@ -45,6 +43,7 @@ import {
45
43
  getContractTermsForProfile,
46
44
  getTransactionsForTerms,
47
45
  hasProfileConsentedToContract,
46
+ hasGuardianApprovalHistory,
48
47
  isProfileConsentFlowContractAdmin,
49
48
  getWritersForContract,
50
49
  } from '@accesslayer/consentflowcontract/relationships/read';
@@ -89,6 +88,7 @@ import { getCredentialUri } from '@helpers/credential.helpers';
89
88
  import { getSigningAuthorityForUserByName } from '@accesslayer/signing-authority/relationships/read';
90
89
  import { getDidWeb } from '@helpers/did.helpers';
91
90
  import { issueCredentialWithSigningAuthority } from '@helpers/signingAuthority.helpers';
91
+ import type { IssuedCredential } from '../types/credential';
92
92
  import { getProfilesByProfileIds } from '@accesslayer/profile/read';
93
93
  import { getProfilesThatManageAProfile } from '@accesslayer/profile/relationships/read';
94
94
  import { resolveAndValidateDeniedWriters } from '@helpers/consentflow.helpers';
@@ -854,7 +854,7 @@ export const contractsRouter = t.router({
854
854
  }
855
855
 
856
856
  // Issue VC with signing authority
857
- let credential: VC | JWE;
857
+ let credential: IssuedCredential;
858
858
  try {
859
859
  credential = await issueCredentialWithSigningAuthority(
860
860
  { type: 'profile', profile },
@@ -907,7 +907,7 @@ export const contractsRouter = t.router({
907
907
  .output(z.object({ termsUri: z.string(), redirectUrl: z.string().optional() }))
908
908
  .mutation(async ({ input, ctx }) => {
909
909
  const { profile } = ctx.user;
910
- const { isChildAccount, hasGuardianApproval } = ctx;
910
+ const { isChildAccount, hasGuardianApproval, guardianIdentity } = ctx;
911
911
 
912
912
  if (isChildAccount && !hasGuardianApproval) {
913
913
  throw new TRPCError({
@@ -938,6 +938,19 @@ export const contractsRouter = t.router({
938
938
  });
939
939
  }
940
940
 
941
+ if (!guardianIdentity) {
942
+ const previousTerms = await getContractTermsForProfile(
943
+ profile,
944
+ contractDetails.contract
945
+ );
946
+ if (previousTerms && (await hasGuardianApprovalHistory(previousTerms))) {
947
+ throw new TRPCError({
948
+ code: 'FORBIDDEN',
949
+ message: 'This consent requires approval from a current guardian',
950
+ });
951
+ }
952
+ }
953
+
941
954
  if (!areTermsValid(terms, contractDetails.contract.contract)) {
942
955
  throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid Terms for Contract' });
943
956
  }
@@ -1077,7 +1090,19 @@ export const contractsRouter = t.router({
1077
1090
  await consentToContract(
1078
1091
  profile,
1079
1092
  contractDetails,
1080
- { terms, expiresAt, oneTime },
1093
+ {
1094
+ terms,
1095
+ expiresAt,
1096
+ oneTime,
1097
+ guardianApproval: guardianIdentity
1098
+ ? {
1099
+ guardianProfileId: guardianIdentity.profileId,
1100
+ guardianDid: guardianIdentity.did,
1101
+ approvedAt: new Date().toISOString(),
1102
+ contractUpdatedAt: contractDetails.contract.updatedAt,
1103
+ }
1104
+ : undefined,
1105
+ },
1081
1106
  ctx.domain
1082
1107
  );
1083
1108
 
@@ -1196,7 +1221,7 @@ export const contractsRouter = t.router({
1196
1221
  .output(z.boolean())
1197
1222
  .mutation(async ({ ctx, input }) => {
1198
1223
  const { profile } = ctx.user;
1199
- const { isChildAccount, hasGuardianApproval } = ctx;
1224
+ const { isChildAccount, hasGuardianApproval, guardianIdentity } = ctx;
1200
1225
 
1201
1226
  if (isChildAccount && !hasGuardianApproval) {
1202
1227
  throw new TRPCError({
@@ -1225,6 +1250,13 @@ export const contractsRouter = t.router({
1225
1250
  });
1226
1251
  }
1227
1252
 
1253
+ if (!guardianIdentity && (await hasGuardianApprovalHistory(relationship.terms))) {
1254
+ throw new TRPCError({
1255
+ code: 'FORBIDDEN',
1256
+ message: 'This consent requires approval from a current guardian',
1257
+ });
1258
+ }
1259
+
1228
1260
  if (!areTermsValid(terms, relationship.contract.contract)) {
1229
1261
  throw new TRPCError({
1230
1262
  code: 'BAD_REQUEST',
@@ -1242,7 +1274,23 @@ export const contractsRouter = t.router({
1242
1274
  }
1243
1275
 
1244
1276
  await Promise.all([
1245
- updateTerms(relationship, { terms, expiresAt, oneTime }, ctx.domain),
1277
+ updateTerms(
1278
+ relationship,
1279
+ {
1280
+ terms,
1281
+ expiresAt,
1282
+ oneTime,
1283
+ guardianApproval: guardianIdentity
1284
+ ? {
1285
+ guardianProfileId: guardianIdentity.profileId,
1286
+ guardianDid: guardianIdentity.did,
1287
+ approvedAt: new Date().toISOString(),
1288
+ contractUpdatedAt: relationship.contract.updatedAt,
1289
+ }
1290
+ : undefined,
1291
+ },
1292
+ ctx.domain
1293
+ ),
1246
1294
  deleteStorageForUri(uri),
1247
1295
  ]);
1248
1296
 
@@ -116,7 +116,10 @@ describe('credential refresh issuer route gating', () => {
116
116
  markConstraintsReady = resolve;
117
117
  })
118
118
  );
119
- mocks.sendRefreshableCredential.mockResolvedValue('lc:network:credential:root');
119
+ mocks.sendRefreshableCredential.mockResolvedValue({
120
+ uri: 'lc:network:credential:root',
121
+ receipt: { refreshId: 'opaque-refresh-id' },
122
+ });
120
123
 
121
124
  const caller = credentialRefreshesRouter.createCaller({
122
125
  domain: 'network.example.com',
@@ -174,7 +177,10 @@ describe('credential refresh issuer route gating', () => {
174
177
 
175
178
  it('forwards initial notification suppression to managed delivery', async () => {
176
179
  process.env.CREDENTIAL_REFRESH_ENABLED = 'true';
177
- mocks.sendRefreshableCredential.mockResolvedValue('lc:network:credential:root');
180
+ mocks.sendRefreshableCredential.mockResolvedValue({
181
+ uri: 'lc:network:credential:root',
182
+ receipt: { refreshId: 'opaque-refresh-id' },
183
+ });
178
184
 
179
185
  const caller = credentialRefreshesRouter.createCaller({
180
186
  domain: 'network.example.com',
@@ -124,7 +124,7 @@ export const credentialRefreshesRouter = t.router({
124
124
  .mutation(async ({ ctx, input }) => {
125
125
  const { profile } = ctx.user;
126
126
 
127
- return sendRefreshableCredential({
127
+ const { uri } = await sendRefreshableCredential({
128
128
  issuerProfile: profile,
129
129
  refreshId: input.refreshId,
130
130
  credential: input.credential,
@@ -132,6 +132,8 @@ export const credentialRefreshesRouter = t.router({
132
132
  skipNotification: input.skipNotification,
133
133
  domain: ctx.domain,
134
134
  });
135
+
136
+ return uri;
135
137
  }),
136
138
 
137
139
  publishCredentialRefresh: credentialRefreshRoute
@@ -156,6 +158,7 @@ export const credentialRefreshesRouter = t.router({
156
158
  issuerProfile: profile,
157
159
  input: input as PublishCredentialRefreshInput,
158
160
  domain: ctx.domain,
161
+ branding: ctx.tenant?.emailBranding,
159
162
  });
160
163
  }),
161
164
 
@@ -71,6 +71,7 @@ export const credentialsRouter = t.router({
71
71
  // Log credential activity FIRST to get activityId
72
72
  const activityId = await logCredentialSent({
73
73
  actorProfileId: profile.profileId,
74
+ onBehalfOf: ctx.user.onBehalfOf,
74
75
  recipientType: 'profile',
75
76
  recipientIdentifier: targetProfile.profileId,
76
77
  recipientProfileId: targetProfile.profileId,
@@ -118,7 +119,7 @@ export const credentialsRouter = t.router({
118
119
  const { profile } = ctx.user;
119
120
  const { uri } = input;
120
121
 
121
- return acceptCredential(profile, uri, input?.options ?? {});
122
+ return acceptCredential(profile, uri, input?.options ?? {}, ctx.tenant?.emailBranding);
122
123
  }),
123
124
 
124
125
  receivedCredentials: profileRoute
@@ -1,3 +1,12 @@
1
+ import { trace } from '@tracing';
2
+ import { submitInboxBatch, getInboxBatch } from '@helpers/inbox-queue.helpers';
3
+ import {
4
+ assertInboxRefreshEnabled,
5
+ inboxRefreshRequestDigest,
6
+ getInboxRefreshReplay,
7
+ getInboxRefreshReceipt,
8
+ resumeInboxRefreshDelivery,
9
+ } from '@helpers/inbox-refresh.helpers';
1
10
  import { getDidWeb } from '@helpers/did.helpers';
2
11
  import { TRPCError } from '@trpc/server';
3
12
  import { z } from 'zod';
@@ -6,6 +15,9 @@ import { t, profileRoute, openRoute, verifiedContactRoute, scopedRoute } from '@
6
15
  import {
7
16
  PaginationOptionsValidator,
8
17
  IssueInboxCredentialValidator,
18
+ IssueInboxCredentialBatchValidator,
19
+ InboxBatchReceiptValidator,
20
+ InboxBatchStatusValidator,
9
21
  IssueInboxCredentialResponseValidator,
10
22
  InboxCredentialValidator,
11
23
  PaginatedInboxCredentialsValidator,
@@ -18,15 +30,14 @@ import {
18
30
  JWEValidator,
19
31
  } from '@learncard/types';
20
32
  import { getInboxCredentialMeta } from '@helpers/credential-meta.helpers';
21
- import { claimIntoInbox, issueToInbox } from '@helpers/inbox.helpers';
22
- import { prepareCredentialFromBoost, getBoostUri } from '@helpers/boost.helpers';
33
+ import { claimIntoInbox, issueToInbox, resolveInboxCredentialInput } from '@helpers/inbox.helpers';
23
34
  import {
24
35
  hasMustacheVariables,
25
36
  renderBoostTemplate,
26
37
  parseRenderedTemplate,
27
38
  } from '@helpers/template.helpers';
28
39
  import { getProfileByVerifiedContactMethod } from '@accesslayer/contact-method/relationships/read';
29
- import { getBoostByUri, getBoostsForProfile } from '@accesslayer/boost/read';
40
+ import { getBoostsForProfile } from '@accesslayer/boost/read';
30
41
  import {
31
42
  generateGuardianApprovalToken,
32
43
  generateGuardianApprovalUrl,
@@ -447,55 +458,36 @@ export const inboxRouter = t.router({
447
458
  .output(IssueInboxCredentialResponseValidator)
448
459
  .mutation(async ({ ctx, input }) => {
449
460
  const { profile } = ctx.user;
450
- const { recipient, credential: inputCredential, templateUri, configuration } = input;
451
-
452
- // Resolve credential from templateUri if provided
453
- let credential = inputCredential;
454
- let resolvedBoostUri: string | undefined;
455
-
456
- if (templateUri && !credential) {
457
- const boostInstance = await getBoostByUri(templateUri);
458
-
459
- if (!boostInstance) {
460
- throw new TRPCError({
461
- code: 'NOT_FOUND',
462
- message: `Boost not found: ${templateUri}`,
463
- });
464
- }
465
-
466
- if (!boostInstance.dataValues.boost) {
467
- throw new TRPCError({
468
- code: 'BAD_REQUEST',
469
- message: `Boost does not contain a credential template: ${templateUri}`,
470
- });
471
- }
461
+ const { recipient, configuration } = input;
472
462
 
473
- try {
474
- // Use shared helper to prepare credential with templateData rendering,
475
- // issuance date, boostId injection, and OBv3 alignments
476
- resolvedBoostUri = getBoostUri(boostInstance.id, ctx.domain);
477
-
478
- credential = await prepareCredentialFromBoost(
479
- boostInstance,
480
- resolvedBoostUri,
481
- ctx.domain,
482
- { templateData: configuration?.templateData as Record<string, unknown> }
463
+ const refreshDigest = input.refresh ? inboxRefreshRequestDigest(input) : undefined;
464
+ if (input.refresh) {
465
+ await assertInboxRefreshEnabled(ctx.user.scope);
466
+ const replay = await getInboxRefreshReplay(
467
+ profile.profileId,
468
+ input.idempotencyKey,
469
+ refreshDigest!
470
+ );
471
+ if (replay && (replay.claimUrl || replay.inbox.currentStatus !== 'PENDING')) {
472
+ await resumeInboxRefreshDelivery(replay.inbox.refreshId!, ctx.domain);
473
+ const receipt = await getInboxRefreshReceipt(
474
+ replay.inbox.refreshId!,
475
+ ctx.domain
483
476
  );
484
- } catch (e) {
485
- console.error('Failed to prepare boost credential', e);
486
- throw new TRPCError({
487
- code: 'BAD_REQUEST',
488
- message: `Failed to prepare boost credential template: ${templateUri}`,
489
- });
477
+ return {
478
+ issuanceId: replay.inbox.id,
479
+ status: replay.inbox.currentStatus,
480
+ recipient,
481
+ claimUrl: replay.claimUrl,
482
+ refresh: receipt,
483
+ ...(replay.inbox.currentStatus === 'ISSUED'
484
+ ? { recipientDid: receipt.holderDid }
485
+ : {}),
486
+ };
490
487
  }
491
488
  }
492
489
 
493
- if (!credential) {
494
- throw new TRPCError({
495
- code: 'BAD_REQUEST',
496
- message: 'Either credential or templateUri must be provided',
497
- });
498
- }
490
+ const { credential, resolvedBoostUri } = await resolveInboxCredentialInput(input, ctx);
499
491
 
500
492
  // Normalize signing authority name if provided
501
493
  const normalizedConfiguration = configuration?.signingAuthority
@@ -513,11 +505,18 @@ export const inboxRouter = t.router({
513
505
  profile,
514
506
  recipient,
515
507
  credential,
516
- normalizedConfiguration,
508
+ {
509
+ ...normalizedConfiguration,
510
+ boostUri: resolvedBoostUri,
511
+ refresh: input.refresh,
512
+ idempotencyKey: input.idempotencyKey,
513
+ refreshRequestDigest: refreshDigest,
514
+ },
517
515
  ctx
518
516
  );
519
517
 
520
518
  return {
519
+ refresh: result.refresh,
521
520
  issuanceId: result.inboxCredential.id,
522
521
  status: result.status,
523
522
  recipient,
@@ -536,6 +535,42 @@ export const inboxRouter = t.router({
536
535
  }
537
536
  }),
538
537
 
538
+ issueBatch: profileRoute
539
+ .meta({
540
+ openapi: {
541
+ protect: true,
542
+ method: 'POST',
543
+ path: '/inbox/issue-batch',
544
+ tags: ['Universal Inbox'],
545
+ summary: 'Issue Credentials to Universal Inbox (Batch)',
546
+ description:
547
+ 'Queue 1–100 credentials for background issuance. Returns a durable batch ID; poll GET /inbox/batches/{batchId} for ordered results. Request and item idempotency keys are issuer-scoped for 24 hours. Maximum JSON payload: 4 MiB.',
548
+ },
549
+ requiredScope: 'inbox:write',
550
+ })
551
+ .input(IssueInboxCredentialBatchValidator)
552
+ .output(InboxBatchReceiptValidator)
553
+ .mutation(({ ctx, input }) =>
554
+ trace('route', 'issueBatch', () => submitInboxBatch(ctx.user.profile, input, ctx), {
555
+ itemCount: input.items.length,
556
+ })
557
+ ),
558
+
559
+ getBatch: profileRoute
560
+ .meta({
561
+ openapi: {
562
+ protect: true,
563
+ method: 'GET',
564
+ path: '/inbox/batches/{batchId}',
565
+ tags: ['Universal Inbox'],
566
+ summary: 'Get Inbox Batch Progress',
567
+ },
568
+ requiredScope: 'inbox:read',
569
+ })
570
+ .input(z.object({ batchId: z.string() }))
571
+ .output(InboxBatchStatusValidator)
572
+ .query(({ ctx, input }) => getInboxBatch(ctx.user.profile.profileId, input.batchId)),
573
+
539
574
  claim: verifiedContactRoute
540
575
  .meta({
541
576
  openapi: {
@@ -682,6 +717,7 @@ export const inboxRouter = t.router({
682
717
  // Log initial activity so embed claims appear in the dashboard
683
718
  const activityId = await logCredentialSent({
684
719
  actorProfileId: issuerProfile.profileId,
720
+ onBehalfOf: ctx.user?.onBehalfOf,
685
721
  recipientType: contactMethod.type as 'email' | 'phone',
686
722
  recipientIdentifier: contactMethod.value,
687
723
  integrationId: integration.id,
@@ -825,7 +861,17 @@ export const inboxRouter = t.router({
825
861
  });
826
862
  }
827
863
 
828
- return inboxCredential;
864
+ return {
865
+ ...inboxCredential,
866
+ ...(inboxCredential.refreshId
867
+ ? {
868
+ refresh: await getInboxRefreshReceipt(
869
+ inboxCredential.refreshId,
870
+ ctx.domain
871
+ ),
872
+ }
873
+ : {}),
874
+ };
829
875
  }),
830
876
 
831
877
  // ─── Guardian Credential Approval Routes ─────────────────────────────────────