@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
@@ -1,7 +1,5 @@
1
- import { environment } from '@environment';
2
- import isEqual from 'lodash/isEqual';
3
1
  import cloneDeep from 'lodash/cloneDeep';
4
- import { v4 as uuidv4 } from 'uuid';
2
+ import { isEncrypted, isVC2Format } from '@learncard/helpers';
5
3
  import { TRPCError } from '@trpc/server';
6
4
  import {
7
5
  VC,
@@ -10,7 +8,6 @@ import {
10
8
  LCNNotificationTypeEnumValidator,
11
9
  ContactMethodQueryType,
12
10
  } from '@learncard/types';
13
- import { isEncrypted, isVC2Format } from '@learncard/helpers';
14
11
  import { ProfileType, SigningAuthorityForUserType } from 'types/profile';
15
12
  import {
16
13
  CredentialIssuer,
@@ -18,13 +15,9 @@ import {
18
15
  getIssuerOwnerProfile,
19
16
  getIssuerProfileId,
20
17
  } from '../types/issuer';
21
- import { trace, traceDb, traceCrypto, traceInternal } from '@tracing';
18
+ import { trace, traceDb } from '@tracing';
19
+ import { injectObv3AlignmentsIntoCredentialForBoost } from '@services/skills-provider/inject';
22
20
  import {
23
- injectObv3AlignmentsIntoCredentialForBoost,
24
- buildObv3AlignmentsForBoost,
25
- } from '@services/skills-provider/inject';
26
- import {
27
- hasMustacheVariables,
28
21
  renderBoostTemplate,
29
22
  parseRenderedTemplate,
30
23
  shouldAutoAppendTemplateEvidence,
@@ -41,7 +34,6 @@ import {
41
34
  createListingSentCredentialRelationship,
42
35
  } from '@accesslayer/credential/relationships/create';
43
36
  import { acceptCredential, getCredentialUri } from './credential.helpers';
44
- import { getLearnCard } from './learnCard.helpers';
45
37
  import { issueCredentialWithSigningAuthority } from './signingAuthority.helpers';
46
38
  import { addNotificationToQueue } from './notifications.helpers';
47
39
  import { getNotificationMessage } from './notificationMessages';
@@ -50,6 +42,8 @@ import { BoostStatus, getBoostOwnerProfile } from 'types/boost';
50
42
  import { getDidWeb } from './did.helpers';
51
43
  import { DbTermsType } from 'types/consentflowcontract';
52
44
  import { appendBitstringStatusListEntries } from './status-list.helpers';
45
+ import { getIssuedCredentialPayload } from './issuedCredentialStatus.helpers';
46
+ import type { IssuedCredential } from '../types/credential';
53
47
 
54
48
  export const getBoostUri = (id: string, domain: string): string =>
55
49
  constructUri('boost', id, domain);
@@ -113,274 +107,6 @@ export const convertCredentialToBoostTemplateJSON = (
113
107
  return JSON.stringify(template);
114
108
  };
115
109
 
116
- export const verifyCredentialIsDerivedFromBoost = async (
117
- boost: BoostInstance,
118
- credential: VC,
119
- domain: string
120
- ): Promise<boolean> => {
121
- if (!boost || !credential) {
122
- console.error('Either boost or credential do not exist.', credential, credential);
123
- return false;
124
- }
125
-
126
- const boostCredential = JSON.parse(boost?.dataValues?.boost);
127
- const boostId = boost?.dataValues?.id;
128
- const boostURI = getBoostUri(boostId, domain);
129
-
130
- if (!boostCredential) {
131
- console.error('No credential template attached to boost');
132
- return false;
133
- }
134
-
135
- if (!isEqual(credential.boostId, boostURI)) {
136
- console.error('Credential boostId !== boost id', credential.boostId, boostURI);
137
- return false;
138
- }
139
-
140
- // If the boost template contains Mustache variables, we use a more lenient verification.
141
- // We only verify that the credential type matches the template type, since other fields
142
- // may have been dynamically substituted at issuance time.
143
- const boostTemplateString = boost?.dataValues?.boost;
144
- const isTemplatedBoost = hasMustacheVariables(boostTemplateString);
145
-
146
- if (isTemplatedBoost) {
147
- // For templated boosts, only verify the type matches
148
- if (!isEqual(credential.type, boostCredential.type)) {
149
- console.error(
150
- 'Credential type !== boost credential type (templated boost)',
151
- credential.type,
152
- boostCredential.type
153
- );
154
- return false;
155
- }
156
- // Templated boost passed basic verification
157
- return true;
158
- }
159
-
160
- /// Simplify Comparison
161
- // if (
162
- // !isEqual(credential.boostId, boostId) ||
163
- // !isEqual(credential.type, boostCredential.type) ||
164
- // !isEqual(
165
- // credential.credentialSubject?.achievement,
166
- // boostCredential.credentialSubject?.achievement
167
- // ) ||
168
- // !isEqual(credential.display, boostCredential.display) ||
169
- // isEqual(credential.image, boostCredential.image) ||
170
- // !isEqual(credential.attachments, boostCredential.attachments)
171
- // ) {
172
- // return false;
173
- // }
174
-
175
- if (!isEqual(credential.type, boostCredential.type)) {
176
- console.error(
177
- 'Credential type !== boost credential type',
178
- credential.type,
179
- boostCredential.type
180
- );
181
- return false;
182
- }
183
-
184
- // Mirror plugin behavior: inject OBv3 alignments into the expected template before comparison.
185
- // Additionally, warn (but allow) if the credential would match the base template without alignments.
186
- if (
187
- credential.credentialSubject &&
188
- 'achievement' in credential.credentialSubject &&
189
- typeof (credential as any).credentialSubject.achievement === 'object'
190
- ) {
191
- try {
192
- // Build expected boost credential with OBv3 alignments injected (JSON-LD typed as in plugin)
193
- const baseExpected = boostCredential as VC | UnsignedVC;
194
- const expectedWithAlignments = cloneDeep(baseExpected);
195
-
196
- const rawAlignments = await buildObv3AlignmentsForBoost(boost, domain);
197
- const jsonLdAlignments = (rawAlignments || []).map(a => ({
198
- ...a,
199
- type: ['Alignment'],
200
- }));
201
-
202
- const addAlignments = (subject: any) => {
203
- if (!subject) return;
204
-
205
- if (jsonLdAlignments.length === 0) return;
206
-
207
- if (subject.achievement) {
208
- const ach = subject.achievement;
209
- if (!Array.isArray(ach.alignment))
210
- ach.alignment = Array.isArray(ach.alignment) ? ach.alignment : [];
211
- ach.alignment = [...ach.alignment, ...jsonLdAlignments];
212
- return;
213
- }
214
- if (!Array.isArray(subject.alignment))
215
- subject.alignment = Array.isArray(subject.alignment) ? subject.alignment : [];
216
- subject.alignment = [...subject.alignment, ...jsonLdAlignments];
217
- };
218
-
219
- if (Array.isArray((expectedWithAlignments as any).credentialSubject)) {
220
- (expectedWithAlignments as any).credentialSubject.forEach(addAlignments);
221
- } else {
222
- addAlignments((expectedWithAlignments as any).credentialSubject);
223
- }
224
-
225
- const credAch = (credential as any).credentialSubject.achievement;
226
- const expectedAch = (expectedWithAlignments as any).credentialSubject?.achievement;
227
- const baseAch = (baseExpected as any).credentialSubject?.achievement;
228
-
229
- const matchWithAlignments = isEqual(credAch, expectedAch);
230
- const matchWithoutAlignments = isEqual(credAch, baseAch);
231
-
232
- if (!matchWithAlignments) {
233
- if (matchWithoutAlignments && jsonLdAlignments.length > 0) {
234
- // Special warning: Accept but inform client to include alignments next time
235
- console.warn(
236
- '[OBV3 ALIGNMENTS WARNING] Credential matches boost template without OBv3 alignments. Please update the client to include alignments before issuing.'
237
- );
238
- } else {
239
- console.error(
240
- 'Credential achievement !== expected boost credential achievement (after alignment injection)',
241
- credAch,
242
- expectedAch
243
- );
244
- return false;
245
- }
246
- }
247
- } catch (_e) {
248
- // Non-fatal: fallback to legacy comparison
249
- if (
250
- !isEqual(
251
- (credential as any).credentialSubject?.achievement,
252
- (boostCredential as any).credentialSubject?.achievement
253
- )
254
- ) {
255
- console.error(
256
- 'Credential achievement !== boost credential achievement (legacy path)',
257
- (credential as any).credentialSubject?.achievement,
258
- (boostCredential as any).credentialSubject?.achievement
259
- );
260
- return false;
261
- }
262
- }
263
- }
264
-
265
- if (!isEqual(credential.display, boostCredential.display)) {
266
- console.error(
267
- 'Credential display !== boost credential display',
268
- credential.display,
269
- boostCredential.display
270
- );
271
- return false;
272
- }
273
-
274
- if (!isEqual(credential.image, boostCredential.image)) {
275
- console.error(
276
- 'Credential image !== boost credential image',
277
- credential.image,
278
- boostCredential.image
279
- );
280
- return false;
281
- }
282
-
283
- if (!isEqual(credential.attachments, boostCredential.attachments)) {
284
- console.error(
285
- 'Credential attachments !== boost credential attachments',
286
- credential.attachments,
287
- boostCredential.attachments
288
- );
289
- return false;
290
- }
291
- if (boost && credential) {
292
- return true;
293
- }
294
- return false;
295
- };
296
-
297
- export const issueCertifiedBoost = async (
298
- boost: BoostInstance,
299
- credential: VC,
300
- domain: string,
301
- ownerProfileId: string
302
- ): Promise<VC | JWE | false> => {
303
- return trace('certification', 'issueCertifiedBoost', async () => {
304
- const learnCard = await trace('init', 'getLearnCard', () => getLearnCard(undefined, true));
305
-
306
- let lcnDID = `did:web:${domain}`;
307
-
308
- try {
309
- const didDoc = await trace('did', 'resolveDid', () =>
310
- learnCard.invoke.resolveDid(lcnDID)
311
- );
312
-
313
- if (!didDoc) {
314
- lcnDID = learnCard.id.did();
315
- }
316
- } catch (error) {
317
- if (environment.NODE_ENV !== 'test') {
318
- console.warn(
319
- 'LCN DID Document is unable to resolve while issuing Certified Boost. Reverting to did:key. Is this a test environment?',
320
- lcnDID,
321
- error
322
- );
323
- }
324
- lcnDID = learnCard.id.did();
325
- }
326
-
327
- try {
328
- const isValid = await traceInternal('verifyCredentialIsDerivedFromBoost', () =>
329
- verifyCredentialIsDerivedFromBoost(boost, credential, domain)
330
- );
331
-
332
- if (isValid) {
333
- const unsignedCertifiedBoost = await traceInternal(
334
- 'constructCertifiedBoostCredential',
335
- () => constructCertifiedBoostCredential(boost, credential, domain, lcnDID)
336
- );
337
-
338
- await traceInternal('appendBitstringStatusListEntries:certifiedBoost', () =>
339
- appendBitstringStatusListEntries(unsignedCertifiedBoost, ownerProfileId, domain)
340
- );
341
-
342
- // TODO: Encrypt Boost Credential
343
- const certifiedBoost = await traceCrypto('issueCredential', () =>
344
- learnCard.invoke.issueCredential(unsignedCertifiedBoost)
345
- );
346
-
347
- return certifiedBoost;
348
- } else {
349
- console.warn(
350
- 'Credential is not derived from boost',
351
- boost.dataValues.boost,
352
- credential
353
- );
354
- }
355
- } catch (error) {
356
- console.warn('Could not issue certified boost', error);
357
- }
358
-
359
- return false;
360
- });
361
- };
362
-
363
- export const decryptCredential = async (credential: VC | JWE): Promise<VC | false> => {
364
- if (!isEncrypted(credential)) {
365
- return credential;
366
- }
367
-
368
- return traceCrypto('decryptCredential', async () => {
369
- const learnCard = await getLearnCard();
370
-
371
- try {
372
- const decrypted = await learnCard.invoke.decryptDagJwe<VC>(credential as JWE, [
373
- learnCard.id.keypair(),
374
- ]);
375
-
376
- return decrypted || false;
377
- } catch (error) {
378
- console.warn('Could not decrypt Boost Credential!', error);
379
- return false;
380
- }
381
- });
382
- };
383
-
384
110
  export const sendBoost = async ({
385
111
  from,
386
112
  to,
@@ -389,7 +115,6 @@ export const sendBoost = async ({
389
115
  domain,
390
116
  skipNotification = false,
391
117
  autoAcceptCredential = false,
392
- skipCertification = false,
393
118
  contractTerms,
394
119
  metadata,
395
120
  activityId,
@@ -399,11 +124,10 @@ export const sendBoost = async ({
399
124
  from: CredentialIssuer;
400
125
  to: ProfileType;
401
126
  boost: BoostInstance;
402
- credential: VC | JWE;
127
+ credential: VC | JWE | IssuedCredential;
403
128
  domain: string;
404
129
  skipNotification?: boolean;
405
130
  autoAcceptCredential?: boolean;
406
- skipCertification?: boolean;
407
131
  contractTerms?: DbTermsType;
408
132
  metadata?: Record<string, unknown>;
409
133
  activityId?: string;
@@ -414,137 +138,70 @@ export const sendBoost = async ({
414
138
  'boost',
415
139
  'sendBoost',
416
140
  async () => {
417
- const decryptedCredential = skipCertification
418
- ? false
419
- : await decryptCredential(credential);
420
- let boostUri: string | undefined;
421
141
  const sourceBoostUri = getBoostUri(boost.dataValues.id, domain);
422
142
  const fromProfile = getIssuerOwnerProfile(from);
423
143
 
424
- // Skip certification if requested or if credential can't be decrypted or if it's not a boost credential
425
- let _skipCertification =
426
- skipCertification ||
427
- !decryptedCredential ||
428
- !decryptedCredential?.type?.includes('BoostCredential');
429
-
430
- if (!_skipCertification && decryptedCredential) {
431
- const certifiedBoost = await issueCertifiedBoost(
432
- boost,
433
- decryptedCredential,
434
- domain,
435
- fromProfile.profileId
436
- );
144
+ const payload = getIssuedCredentialPayload(credential);
145
+ // Pre-signed credentials may predate their network Boost (send can create
146
+ // one from the signed payload). Preserve them without inventing a signed
147
+ // association; only an explicit conflicting claim is rejected.
148
+ if (
149
+ !isEncrypted(payload) &&
150
+ (payload as VC).boostId !== undefined &&
151
+ (payload as VC).boostId !== sourceBoostUri
152
+ ) {
153
+ throw new TRPCError({
154
+ code: 'BAD_REQUEST',
155
+ message: 'Credential boostId must match the Boost being sent.',
156
+ });
157
+ }
437
158
 
438
- if (certifiedBoost) {
439
- const credentialInstance = await traceDb('storeCredential', () =>
440
- storeCredential(certifiedBoost)
441
- );
442
- const tasks = [
443
- createBoostInstanceOfRelationship(credentialInstance, boost),
444
- createSentCredentialRelationship(
445
- from,
446
- to,
447
- credentialInstance,
448
- metadata,
449
- activityId,
450
- integrationId
451
- ),
452
- ];
453
-
454
- if (listingId) {
455
- tasks.push(
456
- createListingSentCredentialRelationship(
457
- listingId,
458
- to,
459
- credentialInstance,
460
- metadata,
461
- activityId,
462
- integrationId
463
- )
464
- );
465
- }
466
-
467
- // If this credential is being issued via a contract, create that relationship
468
- if (contractTerms) {
469
- tasks.push(
470
- createCredentialIssuedViaContractRelationship(
471
- credentialInstance,
472
- contractTerms
473
- )
474
- );
475
- }
476
-
477
- await traceDb('createRelationships', () => Promise.all(tasks));
478
-
479
- if (autoAcceptCredential) {
480
- await acceptCredential(
481
- to,
482
- getCredentialUri(credentialInstance.id, domain),
483
- {
484
- skipNotification,
485
- }
486
- );
487
- }
488
-
489
- boostUri = getCredentialUri(credentialInstance.id, domain);
490
-
491
- if (environment.NODE_ENV !== 'test') {
492
- console.log('🚀 sendBoost:boost certified', boostUri);
493
- }
494
- } else {
495
- throw new Error('Credential does not match boost template.');
496
- }
497
- } else {
498
- // TODO: Should we warn them if they send a credential that can't be decrypted?
499
- const credentialInstance = await traceDb('storeCredential', () =>
500
- storeCredential(credential)
501
- );
159
+ // Preserve the issuer's payload without decrypting or counter-signing it.
160
+ const credentialInstance = await traceDb('storeCredential', () =>
161
+ storeCredential(credential)
162
+ );
502
163
 
503
- const tasks = [
504
- createBoostInstanceOfRelationship(credentialInstance, boost),
505
- createSentCredentialRelationship(
506
- from,
164
+ const tasks = [
165
+ createBoostInstanceOfRelationship(credentialInstance, boost),
166
+ createSentCredentialRelationship(
167
+ from,
168
+ to,
169
+ credentialInstance,
170
+ metadata,
171
+ activityId,
172
+ integrationId
173
+ ),
174
+ ];
175
+
176
+ if (listingId) {
177
+ tasks.push(
178
+ createListingSentCredentialRelationship(
179
+ listingId,
507
180
  to,
508
181
  credentialInstance,
509
182
  metadata,
510
183
  activityId,
511
184
  integrationId
512
- ),
513
- ];
514
-
515
- if (listingId) {
516
- tasks.push(
517
- createListingSentCredentialRelationship(
518
- listingId,
519
- to,
520
- credentialInstance,
521
- metadata,
522
- activityId,
523
- integrationId
524
- )
525
- );
526
- }
185
+ )
186
+ );
187
+ }
527
188
 
528
- if (contractTerms) {
529
- tasks.push(
530
- createCredentialIssuedViaContractRelationship(
531
- credentialInstance,
532
- contractTerms
533
- )
534
- );
535
- }
189
+ if (contractTerms) {
190
+ tasks.push(
191
+ createCredentialIssuedViaContractRelationship(credentialInstance, contractTerms)
192
+ );
193
+ }
536
194
 
537
- await traceDb('createRelationships', () => Promise.all(tasks));
195
+ await traceDb('createRelationships', () => Promise.all(tasks));
538
196
 
539
- if (autoAcceptCredential) {
540
- await acceptCredential(to, getCredentialUri(credentialInstance.id, domain), {
541
- skipNotification,
542
- });
543
- }
544
-
545
- boostUri = getCredentialUri(credentialInstance.id, domain);
197
+ if (autoAcceptCredential) {
198
+ await acceptCredential(to, getCredentialUri(credentialInstance.id, domain), {
199
+ skipNotification,
200
+ });
546
201
  }
547
202
 
203
+ const boostUri = getCredentialUri(credentialInstance.id, domain);
204
+
548
205
  if (typeof boostUri === 'string') {
549
206
  if (!skipNotification) {
550
207
  // LC-1644: fire-and-forget the BOOST_RECEIVED notification enqueue.
@@ -591,36 +248,6 @@ export const sendBoost = async ({
591
248
  );
592
249
  };
593
250
 
594
- export const constructCertifiedBoostCredential = async (
595
- boost: BoostInstance,
596
- credential: VC | JWE,
597
- domain: string,
598
- issuerDid: string
599
- ): Promise<UnsignedVC> => {
600
- const currentDate = new Date()?.toISOString();
601
-
602
- const boostId = boost?.dataValues?.id;
603
- const boostURI = getBoostUri(boostId, domain);
604
-
605
- const isVC2 = isVC2Format(credential);
606
-
607
- return {
608
- '@context': [
609
- isVC2
610
- ? 'https://www.w3.org/ns/credentials/v2'
611
- : 'https://www.w3.org/2018/credentials/v1',
612
- 'https://ctx.learncard.com/boosts/1.0.1.json',
613
- ],
614
- id: `urn:uuid:${uuidv4()}`,
615
- type: ['VerifiableCredential', 'CertifiedBoostCredential'],
616
- issuer: issuerDid,
617
- ...(isVC2 ? { validFrom: currentDate } : { issuanceDate: currentDate }),
618
- credentialSubject: { id: issuerDid },
619
- boostId: boostURI,
620
- boostCredential: credential,
621
- };
622
- };
623
-
624
251
  export const issueClaimLinkBoost = async (
625
252
  boost: BoostInstance,
626
253
  domain: string,
@@ -638,14 +265,14 @@ export const issueClaimLinkBoost = async (
638
265
  if (Array.isArray(boostCredential.credentialSubject)) {
639
266
  boostCredential.credentialSubject = boostCredential.credentialSubject.map(subject => ({
640
267
  ...subject,
641
- id: subject.did,
268
+ id: getDidWeb(domain, to.profileId),
642
269
  }));
643
270
  } else {
644
271
  boostCredential.credentialSubject.id = getDidWeb(domain, to.profileId);
645
272
  }
646
273
 
647
274
  // Embed the boostURI into the boost credential for verification purposes.
648
- if (boostCredential?.type?.includes('BoostCredential')) {
275
+ if (boostCredential.type.includes('BoostCredential')) {
649
276
  boostCredential.boostId = boostURI;
650
277
  }
651
278
 
@@ -657,14 +284,8 @@ export const issueClaimLinkBoost = async (
657
284
  await appendBitstringStatusListEntries(boostCredential, fromProfile.profileId, domain),
658
285
  signingAuthorityForUser,
659
286
  domain,
660
- false
287
+ true
661
288
  );
662
- // TODO: encrypt vc?
663
-
664
- // const lcnDid = await client.utilities.getDid.query();
665
-
666
- // const credential = await _learnCard.invoke
667
- // .createDagJwe(vc, [userData.did, targetProfile.did, lcnDid]);
668
289
 
669
290
  return sendBoost({
670
291
  from,
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ const environment = vi.hoisted(() => ({
4
+ CLIENT_APP_DOMAIN_NAME: undefined as string | undefined,
5
+ CLIENT_APP_PORT: undefined as number | undefined,
6
+ PORT: 4000,
7
+ IS_OFFLINE: true,
8
+ }));
9
+ vi.mock('@environment', () => ({ environment }));
10
+ vi.mock('@cache', () => ({ default: {} }));
11
+
12
+ import { generateClaimUrl } from './contact-method.helpers';
13
+
14
+ describe('inbox claim links', () => {
15
+ it('opens the default frontend instead of the backend port', () => {
16
+ expect(generateClaimUrl('test-token')).toBe(
17
+ 'http://localhost:3000/interactions/inbox-claim/test-token?iuv=1'
18
+ );
19
+ });
20
+
21
+ it('uses the configured frontend port locally', () => {
22
+ environment.CLIENT_APP_PORT = 3100;
23
+ try {
24
+ expect(generateClaimUrl('test-token')).toContain('http://localhost:3100/');
25
+ } finally {
26
+ environment.CLIENT_APP_PORT = undefined;
27
+ }
28
+ });
29
+
30
+ it('honors a configured app domain locally and keeps production HTTPS', () => {
31
+ environment.CLIENT_APP_DOMAIN_NAME = 'app.example.org';
32
+ try {
33
+ expect(generateClaimUrl('test-token')).toContain('http://app.example.org/');
34
+ environment.IS_OFFLINE = false;
35
+ expect(generateClaimUrl('test-token')).toBe(
36
+ 'https://app.example.org/interactions/inbox-claim/test-token?iuv=1'
37
+ );
38
+ } finally {
39
+ environment.IS_OFFLINE = true;
40
+ environment.CLIENT_APP_DOMAIN_NAME = undefined;
41
+ }
42
+ });
43
+ });
@@ -110,10 +110,9 @@ export const markInboxClaimTokenAsUsed = async (token: string): Promise<boolean>
110
110
 
111
111
  export const generateClaimUrl = (token: string): string => {
112
112
  const domainName = environment.CLIENT_APP_DOMAIN_NAME;
113
- const domain =
114
- !domainName || environment.IS_OFFLINE
115
- ? `localhost:${environment.PORT || 3000}`
116
- : domainName;
113
+ // Claim links open the frontend, not the brain-service HTTP port. Honor an
114
+ // explicitly configured app domain in local development as well.
115
+ const domain = domainName || `localhost:${environment.CLIENT_APP_PORT || 3000}`;
117
116
 
118
117
  const protocol = environment.IS_OFFLINE ? 'http' : 'https';
119
118