@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
@@ -22,6 +22,14 @@ import {
22
22
  SigningAuthority,
23
23
  } from '@models';
24
24
  import { testUnsignedBoost } from './helpers/send';
25
+ import { BindParam, QueryBuilder } from 'neogma';
26
+ import {
27
+ getContractTermsByUri,
28
+ getTransactionsForTerms,
29
+ hasGuardianApprovalHistory,
30
+ } from '@accesslayer/consentflowcontract/relationships/read';
31
+ import { consentToContract } from '@accesslayer/consentflowcontract/relationships/create';
32
+ import { updateTerms } from '@accesslayer/consentflowcontract/relationships/update';
25
33
 
26
34
  const noAuthClient = getClient();
27
35
  let userA: Awaited<ReturnType<typeof getUser>>;
@@ -204,9 +212,8 @@ describe('Consent Flow Contracts', () => {
204
212
  };
205
213
 
206
214
  // Simulating contract creation
207
- const contractUri = await userA.clients.fullAuth.contracts.createConsentFlowContract(
208
- contractData
209
- );
215
+ const contractUri =
216
+ await userA.clients.fullAuth.contracts.createConsentFlowContract(contractData);
210
217
 
211
218
  // Fetching the created contract
212
219
  const contract = await userA.clients.fullAuth.contracts.getConsentFlowContract({
@@ -225,9 +232,8 @@ describe('Consent Flow Contracts', () => {
225
232
  contract: minimalContract,
226
233
  };
227
234
 
228
- const contractUri = await userA.clients.fullAuth.contracts.createConsentFlowContract(
229
- contractData
230
- );
235
+ const contractUri =
236
+ await userA.clients.fullAuth.contracts.createConsentFlowContract(contractData);
231
237
 
232
238
  const contract = await userA.clients.fullAuth.contracts.getConsentFlowContract({
233
239
  uri: contractUri,
@@ -244,9 +250,8 @@ describe('Consent Flow Contracts', () => {
244
250
  contract: minimalContract,
245
251
  };
246
252
 
247
- const contractUri = await userA.clients.fullAuth.contracts.createConsentFlowContract(
248
- contractData
249
- );
253
+ const contractUri =
254
+ await userA.clients.fullAuth.contracts.createConsentFlowContract(contractData);
250
255
 
251
256
  const contract = await userA.clients.fullAuth.contracts.getConsentFlowContract({
252
257
  uri: contractUri,
@@ -263,9 +268,8 @@ describe('Consent Flow Contracts', () => {
263
268
  contract: minimalContract,
264
269
  };
265
270
 
266
- const contractUri = await userA.clients.fullAuth.contracts.createConsentFlowContract(
267
- contractData
268
- );
271
+ const contractUri =
272
+ await userA.clients.fullAuth.contracts.createConsentFlowContract(contractData);
269
273
 
270
274
  const contract = await userA.clients.fullAuth.contracts.getConsentFlowContract({
271
275
  uri: contractUri,
@@ -282,9 +286,8 @@ describe('Consent Flow Contracts', () => {
282
286
  contract: minimalContract,
283
287
  };
284
288
 
285
- const contractUri = await userA.clients.fullAuth.contracts.createConsentFlowContract(
286
- contractData
287
- );
289
+ const contractUri =
290
+ await userA.clients.fullAuth.contracts.createConsentFlowContract(contractData);
288
291
 
289
292
  const contract = await userA.clients.fullAuth.contracts.getConsentFlowContract({
290
293
  uri: contractUri,
@@ -300,9 +303,8 @@ describe('Consent Flow Contracts', () => {
300
303
  contract: minimalContract,
301
304
  };
302
305
 
303
- const contractUri = await userA.clients.fullAuth.contracts.createConsentFlowContract(
304
- contractData
305
- );
306
+ const contractUri =
307
+ await userA.clients.fullAuth.contracts.createConsentFlowContract(contractData);
306
308
 
307
309
  const contract = await userA.clients.fullAuth.contracts.getConsentFlowContract({
308
310
  uri: contractUri,
@@ -319,9 +321,8 @@ describe('Consent Flow Contracts', () => {
319
321
  };
320
322
 
321
323
  // Simulating contract creation
322
- const contractUri = await userA.clients.fullAuth.contracts.createConsentFlowContract(
323
- contractData
324
- );
324
+ const contractUri =
325
+ await userA.clients.fullAuth.contracts.createConsentFlowContract(contractData);
325
326
 
326
327
  // Fetching the created contract
327
328
  const contract = await userA.clients.fullAuth.contracts.getConsentFlowContract({
@@ -954,6 +955,432 @@ describe('Consent Flow Contracts', () => {
954
955
  expect(data.records[0]?.credentials).toHaveLength(4);
955
956
  expect(data.records[0]?.personal).toEqual(normalFullTerms.read.personal);
956
957
  expect(data.records[0]?.contractUri).toEqual(contractUri);
958
+ expect(data.records[0]?.status).toBe('live');
959
+ expect(data.records[0]?.termsUri).toMatch(/^lc:network:/);
960
+ expect(data.records[0]?.terms).toEqual(normalFullTerms);
961
+ });
962
+
963
+ it('should omit withdrawn consent before returning provider-facing data', async () => {
964
+ const activeData = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
965
+ did: userBDid,
966
+ });
967
+ const termsUri = activeData.records[0]?.termsUri;
968
+
969
+ expect(termsUri).toBeDefined();
970
+ if (!termsUri) throw new Error('Expected active consent terms URI');
971
+
972
+ await userB.clients.fullAuth.contracts.withdrawConsent({ uri: termsUri });
973
+ const withdrawnData = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
974
+ did: userBDid,
975
+ });
976
+
977
+ expect(withdrawnData.records).toEqual([]);
978
+ });
979
+
980
+ it('returns one-time selected data while stale, but not after withdrawal', async () => {
981
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
982
+ did: userBDid,
983
+ });
984
+ const termsUri = initial.records[0]!.termsUri;
985
+ await userB.clients.fullAuth.contracts.withdrawConsent({ uri: termsUri });
986
+ const selectedTerms = structuredClone(normalFullTerms);
987
+ selectedTerms.read.credentials.shareAll = false;
988
+ selectedTerms.read.credentials.categories.Achievement!.shareAll = false;
989
+ selectedTerms.read.credentials.categories.Achievement!.shared = ['achievement2'];
990
+ selectedTerms.read.credentials.categories.ID!.shareUntil = '2000-01-01T00:00:00.000Z';
991
+ await userB.clients.fullAuth.contracts.consentToContract({
992
+ contractUri,
993
+ terms: selectedTerms,
994
+ oneTime: true,
995
+ });
996
+ expect((await getContractTermsByUri(termsUri))?.terms.status).toBe('stale');
997
+ const shared = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
998
+ did: userBDid,
999
+ });
1000
+ expect(shared.records).toEqual([
1001
+ expect.objectContaining({
1002
+ termsUri,
1003
+ status: 'stale',
1004
+ credentials: [{ category: 'Achievement', uri: 'achievement2' }],
1005
+ personal: selectedTerms.read.personal,
1006
+ }),
1007
+ ]);
1008
+
1009
+ await userB.clients.fullAuth.contracts.withdrawConsent({ uri: termsUri });
1010
+ expect(
1011
+ (await userA.clients.fullAuth.contracts.getConsentedDataForDid({ did: userBDid }))
1012
+ .records
1013
+ ).toEqual([]);
1014
+ });
1015
+
1016
+ it.each(['terms', 'contract'] as const)(
1017
+ 'omits one-time consent after %s expiry',
1018
+ async target => {
1019
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1020
+ did: userBDid,
1021
+ });
1022
+ const termsUri = initial.records[0]!.termsUri;
1023
+ await userB.clients.fullAuth.contracts.updateConsentedContractTerms({
1024
+ uri: termsUri,
1025
+ terms: normalFullTerms,
1026
+ oneTime: true,
1027
+ });
1028
+ const relationship = (await getContractTermsByUri(termsUri))!;
1029
+ expect(
1030
+ (
1031
+ await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1032
+ did: userBDid,
1033
+ })
1034
+ ).records[0]?.status
1035
+ ).toBe('stale');
1036
+ const model = target === 'terms' ? ConsentFlowTerms : ConsentFlowContract;
1037
+ await new QueryBuilder()
1038
+ .match({ model, identifier: 'record', where: { id: relationship[target].id } })
1039
+ .set("record.expiresAt = '2000-01-01T00:00:00.000Z'")
1040
+ .run();
1041
+ expect(
1042
+ (
1043
+ await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1044
+ did: userBDid,
1045
+ })
1046
+ ).records
1047
+ ).toEqual([]);
1048
+ }
1049
+ );
1050
+
1051
+ it('does not treat other stale terms as a one-time grant', async () => {
1052
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1053
+ did: userBDid,
1054
+ });
1055
+ const relationship = (await getContractTermsByUri(initial.records[0]!.termsUri))!;
1056
+ await ConsentFlowTerms.update(
1057
+ { status: 'stale' },
1058
+ { where: { id: relationship.terms.id } }
1059
+ );
1060
+ expect(
1061
+ (await userA.clients.fullAuth.contracts.getConsentedDataForDid({ did: userBDid }))
1062
+ .records
1063
+ ).toEqual([]);
1064
+ });
1065
+
1066
+ it.each([
1067
+ { target: 'terms', properties: { 'guardianApproval.guardianProfileId': 'userc' } },
1068
+ { target: 'terms', properties: { 'terms.read.credentials.sharing': 'invalid' } },
1069
+ { target: 'contract', properties: { name: 42 } },
1070
+ ] as const)(
1071
+ 'omits malformed stored $target without hiding another valid owner grant',
1072
+ async ({ target, properties }) => {
1073
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1074
+ did: userBDid,
1075
+ });
1076
+ const relationship = (await getContractTermsByUri(initial.records[0]!.termsUri))!;
1077
+ const otherContract =
1078
+ await userA.clients.fullAuth.contracts.createConsentFlowContract({
1079
+ contract: normalContract,
1080
+ name: 'Valid independent grant',
1081
+ });
1082
+ const valid = await userB.clients.fullAuth.contracts.consentToContract({
1083
+ contractUri: otherContract,
1084
+ terms: normalAchievementOnlyTerms,
1085
+ });
1086
+ // Sort the malformed row before the valid one so omission must fill the page.
1087
+ await ConsentFlowTerms.update(
1088
+ { updatedAt: '2999-01-01T00:00:00.000Z' },
1089
+ { where: { id: relationship.terms.id } }
1090
+ );
1091
+ const model = target === 'terms' ? ConsentFlowTerms : ConsentFlowContract;
1092
+ await new QueryBuilder(new BindParam({ properties }))
1093
+ .match({ model, identifier: 'record', where: { id: relationship[target].id } })
1094
+ .set('record += $properties')
1095
+ .run();
1096
+ const data = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1097
+ did: userBDid,
1098
+ limit: 1,
1099
+ });
1100
+ expect(data.records).toEqual([
1101
+ expect.objectContaining({
1102
+ termsUri: valid.termsUri,
1103
+ terms: normalAchievementOnlyTerms,
1104
+ credentials: [
1105
+ { category: 'Achievement', uri: 'achievement1' },
1106
+ { category: 'Achievement', uri: 'achievement2' },
1107
+ ],
1108
+ }),
1109
+ ]);
1110
+ expect(data.hasMore).toBe(false);
1111
+ }
1112
+ );
1113
+
1114
+ it('honors category grants without a root sharing flag and rejects malformed category expiry', async () => {
1115
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1116
+ did: userBDid,
1117
+ });
1118
+ const terms = structuredClone(normalFullTerms);
1119
+ delete terms.read.credentials.sharing;
1120
+ terms.read.credentials.categories.Achievement!.shareUntil = '';
1121
+ terms.read.credentials.categories.ID!.shareUntil = 'not-a-date';
1122
+ await userB.clients.fullAuth.contracts.updateConsentedContractTerms({
1123
+ uri: initial.records[0]!.termsUri,
1124
+ terms,
1125
+ });
1126
+ const data = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1127
+ did: userBDid,
1128
+ });
1129
+ expect(data.records[0]?.credentials).toEqual([
1130
+ { category: 'Achievement', uri: 'achievement1' },
1131
+ { category: 'Achievement', uri: 'achievement2' },
1132
+ ]);
1133
+ });
1134
+
1135
+ it('stores initial guardian approval with its consent snapshot and clears it on unapproved reconsent', async () => {
1136
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1137
+ did: userBDid,
1138
+ });
1139
+ const relationship = (await getContractTermsByUri(initial.records[0]!.termsUri))!;
1140
+ await ConsentFlowTerms.delete({ detach: true, where: { id: relationship.terms.id } });
1141
+ await Profile.relateTo({
1142
+ alias: 'managedBy',
1143
+ where: { source: { profileId: 'userb' }, target: { profileId: 'userc' } },
1144
+ });
1145
+ const approval = {
1146
+ guardianProfileId: 'userc',
1147
+ guardianDid: userC.learnCard.id.did(),
1148
+ approvedAt: new Date().toISOString(),
1149
+ contractUpdatedAt: relationship.contract.updatedAt,
1150
+ };
1151
+ await consentToContract(
1152
+ relationship.consenter,
1153
+ relationship,
1154
+ { terms: normalFullTerms, guardianApproval: approval },
1155
+ 'localhost%3A3000'
1156
+ );
1157
+ const approved = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1158
+ did: userBDid,
1159
+ });
1160
+ expect(approved.records[0]?.guardian).toEqual({
1161
+ required: true,
1162
+ approved: true,
1163
+ approval,
1164
+ });
1165
+ const current = (await getContractTermsByUri(approved.records[0]!.termsUri))!;
1166
+ expect(current.terms.guardianApproval).toEqual(approval);
1167
+ const transactions = await getTransactionsForTerms(current.terms.id, { limit: 10 });
1168
+ expect(transactions[0]).toMatchObject({
1169
+ action: 'consent',
1170
+ guardianApproval: approval,
1171
+ terms: normalFullTerms,
1172
+ });
1173
+
1174
+ await consentToContract(
1175
+ current.consenter,
1176
+ current,
1177
+ { terms: normalAchievementOnlyTerms },
1178
+ 'localhost%3A3000'
1179
+ );
1180
+ const reconsented = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1181
+ did: userBDid,
1182
+ });
1183
+ expect(reconsented.records[0]?.guardian).toEqual({ required: true, approved: false });
1184
+ expect(reconsented.records[0]?.terms).toEqual(normalAchievementOnlyTerms);
1185
+ expect(reconsented.records[0]?.createdAt).toBe(approved.records[0]?.createdAt);
1186
+ });
1187
+
1188
+ it.each(['terms', 'contract'] as const)(
1189
+ 'filters %s expiry without treating empty expiry as an expired grant',
1190
+ async target => {
1191
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1192
+ did: userBDid,
1193
+ });
1194
+ const relationship = (await getContractTermsByUri(initial.records[0]!.termsUri))!;
1195
+ const model = target === 'terms' ? ConsentFlowTerms : ConsentFlowContract;
1196
+ const id = relationship[target].id;
1197
+
1198
+ for (const [expiresAt, active] of [
1199
+ [null, true],
1200
+ ['', true],
1201
+ [' ', true],
1202
+ ['2999-01-01T00:00:00.000Z', true],
1203
+ ['2000-01-01T00:00:00.000Z', false],
1204
+ ['not-a-date', false],
1205
+ ] as const) {
1206
+ await new QueryBuilder(new BindParam({ expiresAt }))
1207
+ .match({ model, identifier: 'record', where: { id } })
1208
+ .set('record.expiresAt = $expiresAt')
1209
+ .run();
1210
+ const data = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1211
+ did: userBDid,
1212
+ });
1213
+ expect(data.records).toHaveLength(active ? 1 : 0);
1214
+ if (active && !expiresAt?.trim()) {
1215
+ expect(data.records[0]?.expiresAt).toBeUndefined();
1216
+ expect(data.records[0]?.contractExpiresAt).toBeUndefined();
1217
+ }
1218
+ }
1219
+ }
1220
+ );
1221
+
1222
+ it('preserves original adult grant metadata through permission updates and legacy timestamps', async () => {
1223
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1224
+ did: userBDid,
1225
+ });
1226
+ const original = initial.records[0]!;
1227
+ const relationship = (await getContractTermsByUri(original.termsUri))!;
1228
+ await ConsentFlowContract.update(
1229
+ {
1230
+ reasonForAccessing: 'Personalized learning',
1231
+ expiresAt: '2999-01-01T00:00:00.000Z',
1232
+ },
1233
+ { where: { id: relationship.contract.id } }
1234
+ );
1235
+ await userB.clients.fullAuth.contracts.updateConsentedContractTerms({
1236
+ uri: original.termsUri,
1237
+ terms: normalAchievementOnlyTerms,
1238
+ });
1239
+ const updated = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1240
+ did: userBDid,
1241
+ });
1242
+ expect(updated.records[0]).toMatchObject({
1243
+ createdAt: original.createdAt,
1244
+ contractUpdatedAt: relationship.contract.updatedAt,
1245
+ contractExpiresAt: '2999-01-01T00:00:00.000Z',
1246
+ reasonForAccessing: 'Personalized learning',
1247
+ guardian: { required: false, approved: false },
1248
+ terms: normalAchievementOnlyTerms,
1249
+ });
1250
+ expect(Date.parse(updated.records[0]!.date)).toBeGreaterThanOrEqual(
1251
+ Date.parse(original.date)
1252
+ );
1253
+
1254
+ await new QueryBuilder()
1255
+ .match({
1256
+ model: ConsentFlowTerms,
1257
+ identifier: 'terms',
1258
+ where: { id: relationship.terms.id },
1259
+ })
1260
+ .set('terms.createdAt = null')
1261
+ .run();
1262
+ const legacy = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1263
+ did: userBDid,
1264
+ });
1265
+ expect(legacy.records[0]?.createdAt).toBe(original.createdAt);
1266
+ expect(legacy.records[0]?.guardian).toEqual({ required: false, approved: false });
1267
+ });
1268
+
1269
+ it('does not invent guardian approval for legacy managed grants or accept it in client terms', async () => {
1270
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1271
+ did: userBDid,
1272
+ });
1273
+ const original = initial.records[0]!;
1274
+ const termsWithClientMetadata = {
1275
+ ...normalFullTerms,
1276
+ guardianApproval: {
1277
+ guardianProfileId: 'userc',
1278
+ guardianDid: userC.learnCard.id.did(),
1279
+ approvedAt: new Date().toISOString(),
1280
+ contractUpdatedAt: original.contractUpdatedAt,
1281
+ },
1282
+ };
1283
+ await userB.clients.fullAuth.contracts.updateConsentedContractTerms({
1284
+ uri: original.termsUri,
1285
+ terms: termsWithClientMetadata,
1286
+ });
1287
+ await Profile.relateTo({
1288
+ alias: 'managedBy',
1289
+ where: { source: { profileId: 'userb' }, target: { profileId: 'userc' } },
1290
+ });
1291
+ const managed = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1292
+ did: userBDid,
1293
+ });
1294
+ expect(managed.records[0]?.guardian).toEqual({ required: true, approved: false });
1295
+ expect(
1296
+ (await getContractTermsByUri(original.termsUri))?.terms.guardianApproval
1297
+ ).toBeUndefined();
1298
+
1299
+ const adult = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1300
+ did: (await userC.clients.fullAuth.profile.getProfile())!.did,
1301
+ });
1302
+ expect(adult.records[0]?.guardian).toEqual({ required: false, approved: false });
1303
+ });
1304
+
1305
+ it('snapshots guardian consent, invalidates changed contracts and removed managers, and retains child history when cleared', async () => {
1306
+ const initial = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1307
+ did: userBDid,
1308
+ });
1309
+ const relationship = (await getContractTermsByUri(initial.records[0]!.termsUri))!;
1310
+ await Profile.relateTo({
1311
+ alias: 'managedBy',
1312
+ where: { source: { profileId: 'userb' }, target: { profileId: 'userc' } },
1313
+ });
1314
+ const approval = {
1315
+ guardianProfileId: 'userc',
1316
+ guardianDid: userC.learnCard.id.did(),
1317
+ approvedAt: new Date().toISOString(),
1318
+ contractUpdatedAt: relationship.contract.updatedAt,
1319
+ };
1320
+ await consentToContract(
1321
+ relationship.consenter,
1322
+ relationship,
1323
+ { terms: normalFullTerms, guardianApproval: approval },
1324
+ 'localhost%3A3000'
1325
+ );
1326
+ const approved = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1327
+ did: userBDid,
1328
+ });
1329
+ expect(approved.records[0]?.guardian).toEqual({
1330
+ required: true,
1331
+ approved: true,
1332
+ approval,
1333
+ });
1334
+ const transactions = await getTransactionsForTerms(relationship.terms.id, {
1335
+ limit: 10,
1336
+ });
1337
+ expect(
1338
+ transactions.find(transaction => transaction.guardianApproval)?.guardianApproval
1339
+ ).toEqual(approval);
1340
+
1341
+ await ConsentFlowContract.update(
1342
+ { updatedAt: '2999-01-01T00:00:00.000Z' },
1343
+ { where: { id: relationship.contract.id } }
1344
+ );
1345
+ const revised = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1346
+ did: userBDid,
1347
+ });
1348
+ expect(revised.records[0]?.guardian).toEqual({
1349
+ required: true,
1350
+ approved: false,
1351
+ approval,
1352
+ });
1353
+
1354
+ await ConsentFlowContract.update(
1355
+ { updatedAt: relationship.contract.updatedAt },
1356
+ { where: { id: relationship.contract.id } }
1357
+ );
1358
+ await Profile.delete({ detach: true, where: { profileId: 'userc' } });
1359
+ const unmanaged = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1360
+ did: userBDid,
1361
+ });
1362
+ expect(unmanaged.records[0]?.guardian).toEqual({
1363
+ required: true,
1364
+ approved: false,
1365
+ approval,
1366
+ });
1367
+
1368
+ const current = (await getContractTermsByUri(initial.records[0]!.termsUri))!;
1369
+ await updateTerms(current, { terms: normalAchievementOnlyTerms }, 'localhost%3A3000');
1370
+ const cleared = (await getContractTermsByUri(initial.records[0]!.termsUri))!;
1371
+ expect(cleared.terms.guardianApproval).toBeUndefined();
1372
+ expect(await hasGuardianApprovalHistory(cleared.terms)).toBe(true);
1373
+ const historical = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1374
+ did: userBDid,
1375
+ });
1376
+ expect(historical.records[0]?.guardian).toEqual({ required: true, approved: false });
1377
+ const history = await getTransactionsForTerms(relationship.terms.id, { limit: 10 });
1378
+ expect(
1379
+ history.find(transaction => transaction.action === 'update')?.guardianApproval
1380
+ ).toBeUndefined();
1381
+ expect(
1382
+ history.find(transaction => transaction.guardianApproval)?.guardianApproval
1383
+ ).toEqual(approval);
957
1384
  });
958
1385
 
959
1386
  it('should error for non-existent did', async () => {
@@ -1971,7 +2398,7 @@ describe('Consent Flow Contracts', () => {
1971
2398
  contractUri,
1972
2399
  terms: normalFullTerms, // No deniedWriters
1973
2400
  });
1974
- let credentialsAfterInitialConsent =
2401
+ const credentialsAfterInitialConsent =
1975
2402
  await userB.clients.fullAuth.contracts.getCredentialsForContract({
1976
2403
  termsUri: initialTermsUri,
1977
2404
  });
@@ -2086,7 +2513,7 @@ describe('Consent Flow Contracts', () => {
2086
2513
  contractUri,
2087
2514
  terms: normalFullTerms, // No deniedWriters
2088
2515
  });
2089
- let credentialsAfterInitialConsent =
2516
+ const credentialsAfterInitialConsent =
2090
2517
  await userB.clients.fullAuth.contracts.getCredentialsForContract({ termsUri });
2091
2518
  // Should have boosts from both A and C
2092
2519
  expect(credentialsAfterInitialConsent.records).toHaveLength(2);
@@ -2231,7 +2658,7 @@ describe('Consent Flow Contracts', () => {
2231
2658
  )
2232
2659
  );
2233
2660
 
2234
- for (let contractUri of uris) {
2661
+ for (const contractUri of uris) {
2235
2662
  await userB.clients.fullAuth.contracts.consentToContract({
2236
2663
  contractUri,
2237
2664
  terms: minimalTerms,
@@ -2498,8 +2925,8 @@ describe('Consent Flow Contracts', () => {
2498
2925
  Achievement: {
2499
2926
  ...normalFullTerms.read.credentials.categories.Achievement,
2500
2927
  shared: [
2501
- ...normalFullTerms.read.credentials.categories.Achievement
2502
- ?.shared!,
2928
+ ...(normalFullTerms.read.credentials.categories.Achievement
2929
+ ?.shared ?? []),
2503
2930
  'another!',
2504
2931
  ],
2505
2932
  },