@learncard/network-brain-service 3.19.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/.env.example +47 -82
  2. package/AGENTS.md +52 -0
  3. package/CHANGELOG.md +147 -0
  4. package/README.md +51 -2
  5. package/bootstrapLambda.test.ts +72 -0
  6. package/config/config.dev.json +29 -0
  7. package/config/config.example.json +29 -0
  8. package/config/config.json +1 -0
  9. package/config/config.production.json +29 -0
  10. package/config/config.scouts.dev.json +24 -0
  11. package/config/config.scouts.production.json +24 -0
  12. package/contractEventsLambda.ts +18 -0
  13. package/contractEventsLambdaApp.ts +19 -0
  14. package/didWebLambda.ts +13 -18
  15. package/didWebLambdaApp.ts +22 -0
  16. package/dist/accesslayer/app-manifest-version/create.d.ts +10 -0
  17. package/dist/accesslayer/app-manifest-version/create.d.ts.map +1 -0
  18. package/dist/accesslayer/app-manifest-version/read.d.ts +11 -0
  19. package/dist/accesslayer/app-manifest-version/read.d.ts.map +1 -0
  20. package/dist/accesslayer/app-manifest-version/update.d.ts +7 -0
  21. package/dist/accesslayer/app-manifest-version/update.d.ts.map +1 -0
  22. package/dist/accesslayer/app-store-listing/read.d.ts +2 -1
  23. package/dist/accesslayer/app-store-listing/read.d.ts.map +1 -1
  24. package/dist/accesslayer/app-store-listing/relationships/create.d.ts +1 -0
  25. package/dist/accesslayer/app-store-listing/relationships/create.d.ts.map +1 -1
  26. package/dist/accesslayer/app-store-listing/relationships/read.d.ts +2 -0
  27. package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
  28. package/dist/accesslayer/app-store-listing/update.d.ts +1 -0
  29. package/dist/accesslayer/app-store-listing/update.d.ts.map +1 -1
  30. package/dist/accesslayer/boost/relationships/create.d.ts +2 -1
  31. package/dist/accesslayer/boost/relationships/create.d.ts.map +1 -1
  32. package/dist/accesslayer/consentflowcontract/create.d.ts +9 -1
  33. package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
  34. package/dist/accesslayer/consentflowcontract/read.d.ts +34 -3
  35. package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
  36. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +4 -1
  37. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  38. package/dist/accesslayer/consentflowcontract/relationships/delete.d.ts.map +1 -1
  39. package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +3 -2
  40. package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
  41. package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts +12 -0
  42. package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts.map +1 -0
  43. package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts +12 -0
  44. package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts.map +1 -0
  45. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +8 -5
  46. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  47. package/dist/accesslayer/credential/create.d.ts +2 -1
  48. package/dist/accesslayer/credential/create.d.ts.map +1 -1
  49. package/dist/accesslayer/credential/relationships/create.d.ts +4 -3
  50. package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
  51. package/dist/accesslayer/inbox-credential/relationships/create.d.ts +2 -1
  52. package/dist/accesslayer/inbox-credential/relationships/create.d.ts.map +1 -1
  53. package/dist/accesslayer/inbox-credential/update.d.ts +2 -1
  54. package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
  55. package/dist/accesslayer/presentation/read.d.ts.map +1 -1
  56. package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
  57. package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
  58. package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
  59. package/dist/accesslayer/share-link/abandon.d.ts +3 -0
  60. package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
  61. package/dist/accesslayer/share-link/attachment.d.ts +37 -0
  62. package/dist/accesslayer/share-link/attachment.d.ts.map +1 -0
  63. package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
  64. package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
  65. package/dist/accesslayer/share-link/errors.d.ts +8 -0
  66. package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
  67. package/dist/accesslayer/share-link/finalize.d.ts +3 -0
  68. package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
  69. package/dist/accesslayer/share-link/helpers.d.ts +59 -0
  70. package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
  71. package/dist/accesslayer/share-link/index.d.ts +12 -0
  72. package/dist/accesslayer/share-link/index.d.ts.map +1 -0
  73. package/dist/accesslayer/share-link/list.d.ts +5 -0
  74. package/dist/accesslayer/share-link/list.d.ts.map +1 -0
  75. package/dist/accesslayer/share-link/read.d.ts +5 -0
  76. package/dist/accesslayer/share-link/read.d.ts.map +1 -0
  77. package/dist/accesslayer/share-link/receipt.d.ts +11 -0
  78. package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
  79. package/dist/accesslayer/share-link/recovery.d.ts +6 -0
  80. package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
  81. package/dist/accesslayer/share-link/reserve.d.ts +4 -0
  82. package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
  83. package/dist/accesslayer/share-link/revoke.d.ts +3 -0
  84. package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
  85. package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
  86. package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
  87. package/dist/accesslayer/share-link/transaction.d.ts +15 -0
  88. package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
  89. package/dist/accesslayer/share-link/types.d.ts +375 -0
  90. package/dist/accesslayer/share-link/types.d.ts.map +1 -0
  91. package/dist/app.d.ts +4 -0
  92. package/dist/app.d.ts.map +1 -1
  93. package/dist/cache/app-store.caches.d.ts +49 -0
  94. package/dist/cache/app-store.caches.d.ts.map +1 -1
  95. package/dist/config/applyDockerStageConfig.d.ts +2 -0
  96. package/dist/config/applyDockerStageConfig.d.ts.map +1 -0
  97. package/dist/config/environment.d.ts +14 -0
  98. package/dist/config/environment.d.ts.map +1 -1
  99. package/dist/config/serverlessFunctionEnv.test.d.ts +2 -0
  100. package/dist/config/serverlessFunctionEnv.test.d.ts.map +1 -0
  101. package/dist/config/stageConfig.d.ts +112 -0
  102. package/dist/config/stageConfig.d.ts.map +1 -0
  103. package/dist/config/stageConfig.test.d.ts +2 -0
  104. package/dist/config/stageConfig.test.d.ts.map +1 -0
  105. package/dist/constants/auth-grant.d.ts +2 -0
  106. package/dist/constants/auth-grant.d.ts.map +1 -1
  107. package/dist/didkit_wasm_bg.wasm +0 -0
  108. package/dist/docker-entry.d.ts +1 -1
  109. package/dist/docker-entry.d.ts.map +1 -1
  110. package/dist/helpers/app-manifest.helpers.d.ts +5 -0
  111. package/dist/helpers/app-manifest.helpers.d.ts.map +1 -0
  112. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  113. package/dist/helpers/claim-hooks.helpers.d.ts +2 -1
  114. package/dist/helpers/claim-hooks.helpers.d.ts.map +1 -1
  115. package/dist/helpers/consent-referral.helpers.d.ts +2 -0
  116. package/dist/helpers/consent-referral.helpers.d.ts.map +1 -0
  117. package/dist/helpers/consent-scopes.helpers.d.ts +7 -0
  118. package/dist/helpers/consent-scopes.helpers.d.ts.map +1 -0
  119. package/dist/helpers/contract-event-maintenance.helpers.d.ts +3 -0
  120. package/dist/helpers/contract-event-maintenance.helpers.d.ts.map +1 -0
  121. package/dist/helpers/contract-events.helpers.d.ts +25 -0
  122. package/dist/helpers/contract-events.helpers.d.ts.map +1 -0
  123. package/dist/helpers/contract-requests.helpers.d.ts +19 -0
  124. package/dist/helpers/contract-requests.helpers.d.ts.map +1 -0
  125. package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
  126. package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
  127. package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
  128. package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
  129. package/dist/helpers/finalize-inbox.helpers.d.ts +3 -3
  130. package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
  131. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  132. package/dist/helpers/inline-template.helpers.d.ts +45 -0
  133. package/dist/helpers/inline-template.helpers.d.ts.map +1 -0
  134. package/dist/helpers/notificationMessages.d.ts +1 -1
  135. package/dist/helpers/notificationMessages.d.ts.map +1 -1
  136. package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
  137. package/dist/helpers/notifications.helpers.d.ts +6 -0
  138. package/dist/helpers/notifications.helpers.d.ts.map +1 -1
  139. package/dist/helpers/presentation-metadata.d.ts +4 -0
  140. package/dist/helpers/presentation-metadata.d.ts.map +1 -0
  141. package/dist/helpers/presentation.helpers.d.ts +1 -1
  142. package/dist/helpers/presentation.helpers.d.ts.map +1 -1
  143. package/dist/helpers/profile.helpers.d.ts +2 -0
  144. package/dist/helpers/profile.helpers.d.ts.map +1 -1
  145. package/dist/helpers/share-content-client/adapters.d.ts +10 -0
  146. package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
  147. package/dist/helpers/share-content-client/canonical.d.ts +7 -0
  148. package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
  149. package/dist/helpers/share-content-client/client.d.ts +5 -0
  150. package/dist/helpers/share-content-client/client.d.ts.map +1 -0
  151. package/dist/helpers/share-content-client/config.d.ts +11 -0
  152. package/dist/helpers/share-content-client/config.d.ts.map +1 -0
  153. package/dist/helpers/share-content-client/index.d.ts +6 -0
  154. package/dist/helpers/share-content-client/index.d.ts.map +1 -0
  155. package/dist/helpers/share-content-client/responses.d.ts +16 -0
  156. package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
  157. package/dist/helpers/share-content-client/types.d.ts +145 -0
  158. package/dist/helpers/share-content-client/types.d.ts.map +1 -0
  159. package/dist/helpers/share-link-attachment-access.d.ts +4 -0
  160. package/dist/helpers/share-link-attachment-access.d.ts.map +1 -0
  161. package/dist/helpers/share-link-attachment.d.ts +18 -0
  162. package/dist/helpers/share-link-attachment.d.ts.map +1 -0
  163. package/dist/helpers/share-link-coordinator/attachment-stat.d.ts +5 -0
  164. package/dist/helpers/share-link-coordinator/attachment-stat.d.ts.map +1 -0
  165. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
  166. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
  167. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
  168. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
  169. package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
  170. package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
  171. package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
  172. package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
  173. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
  174. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
  175. package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
  176. package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
  177. package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
  178. package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
  179. package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
  180. package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
  181. package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
  182. package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
  183. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
  184. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
  185. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
  186. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
  187. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
  188. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
  189. package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
  190. package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
  191. package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
  192. package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
  193. package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
  194. package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
  195. package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
  196. package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
  197. package/dist/helpers/share-link-maintenance/runner.d.ts +20 -0
  198. package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
  199. package/dist/helpers/share-link-maintenance/runtime.d.ts +38 -0
  200. package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
  201. package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
  202. package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
  203. package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
  204. package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
  205. package/dist/helpers/share-link-maintenance/types.d.ts +56 -0
  206. package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
  207. package/dist/helpers/share-link-owner/config.d.ts +16 -0
  208. package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
  209. package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
  210. package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
  211. package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
  212. package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
  213. package/dist/helpers/share-link-owner-projection.d.ts +4 -0
  214. package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
  215. package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
  216. package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
  217. package/dist/helpers/share-link-passcode.d.ts +6 -0
  218. package/dist/helpers/share-link-passcode.d.ts.map +1 -0
  219. package/dist/helpers/share-link-policy/index.d.ts +4 -0
  220. package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
  221. package/dist/helpers/share-link-policy/production.d.ts +11 -0
  222. package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
  223. package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
  224. package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
  225. package/dist/helpers/share-link-policy/types.d.ts +22 -0
  226. package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
  227. package/dist/helpers/share-link-public/config.d.ts +13 -0
  228. package/dist/helpers/share-link-public/config.d.ts.map +1 -0
  229. package/dist/helpers/share-link-view-notification.d.ts +2 -0
  230. package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
  231. package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
  232. package/dist/helpers/smartResume.helpers.d.ts +3 -0
  233. package/dist/helpers/smartResume.helpers.d.ts.map +1 -0
  234. package/dist/index.js +2 -2
  235. package/dist/models/AppManifestVersion.d.ts +20 -0
  236. package/dist/models/AppManifestVersion.d.ts.map +1 -0
  237. package/dist/models/AppStoreListing.d.ts +13 -0
  238. package/dist/models/AppStoreListing.d.ts.map +1 -1
  239. package/dist/models/ConsentFlowContract.d.ts +13 -2
  240. package/dist/models/ConsentFlowContract.d.ts.map +1 -1
  241. package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
  242. package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
  243. package/dist/models/Integration.d.ts +2 -0
  244. package/dist/models/Integration.d.ts.map +1 -1
  245. package/dist/models/Presentation.d.ts +2 -0
  246. package/dist/models/Presentation.d.ts.map +1 -1
  247. package/dist/models/Profile.d.ts +2 -0
  248. package/dist/models/Profile.d.ts.map +1 -1
  249. package/dist/models/ShareLink.d.ts +46 -0
  250. package/dist/models/ShareLink.d.ts.map +1 -0
  251. package/dist/models/ShareViewReceipt.d.ts +22 -0
  252. package/dist/models/ShareViewReceipt.d.ts.map +1 -0
  253. package/dist/models/index.d.ts +2 -0
  254. package/dist/models/index.d.ts.map +1 -1
  255. package/dist/models/share-link-constraints.d.ts +3 -0
  256. package/dist/models/share-link-constraints.d.ts.map +1 -0
  257. package/dist/openapi.d.ts +1 -1
  258. package/dist/openapi.d.ts.map +1 -1
  259. package/dist/public-share-link-http.d.ts +4 -0
  260. package/dist/public-share-link-http.d.ts.map +1 -0
  261. package/dist/routes/app-store.d.ts +619 -0
  262. package/dist/routes/app-store.d.ts.map +1 -1
  263. package/dist/routes/boosts.d.ts +2 -2
  264. package/dist/routes/boosts.d.ts.map +1 -1
  265. package/dist/routes/contracts.d.ts +148 -5
  266. package/dist/routes/contracts.d.ts.map +1 -1
  267. package/dist/routes/credentials.d.ts +35 -1
  268. package/dist/routes/credentials.d.ts.map +1 -1
  269. package/dist/routes/inbox.d.ts +52 -2
  270. package/dist/routes/inbox.d.ts.map +1 -1
  271. package/dist/routes/index.d.ts +153 -0
  272. package/dist/routes/index.d.ts.map +1 -1
  273. package/dist/routes/presentations.d.ts +1 -0
  274. package/dist/routes/presentations.d.ts.map +1 -1
  275. package/dist/routes/profile-manager.d.ts.map +1 -1
  276. package/dist/routes/profiles.d.ts.map +1 -1
  277. package/dist/routes/public-share-links.d.ts +285 -0
  278. package/dist/routes/public-share-links.d.ts.map +1 -0
  279. package/dist/routes/share-links.d.ts +1005 -0
  280. package/dist/routes/share-links.d.ts.map +1 -0
  281. package/dist/routes/test.d.ts +5 -0
  282. package/dist/routes/test.d.ts.map +1 -1
  283. package/dist/routes/workflows.d.ts.map +1 -1
  284. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  285. package/dist/types/app-manifest.d.ts +339 -0
  286. package/dist/types/app-manifest.d.ts.map +1 -0
  287. package/dist/types/app-store-listing.d.ts +34 -0
  288. package/dist/types/app-store-listing.d.ts.map +1 -1
  289. package/dist/types/consentflowcontract.d.ts +31 -4
  290. package/dist/types/consentflowcontract.d.ts.map +1 -1
  291. package/dist/types/credential-refresh.d.ts +1 -1
  292. package/esbuildPlugins.cjs +1 -0
  293. package/lambda.ts +49 -192
  294. package/lambdaApp.ts +203 -0
  295. package/package.json +25 -17
  296. package/scripts/export-openapi.ts +20 -0
  297. package/scripts/offline-schema-environment.ts +18 -0
  298. package/scripts/seed-demo-persona.ts +425 -0
  299. package/scripts/verify-python-contracts.ts +72 -0
  300. package/serverless-local.yml +3 -6
  301. package/serverless.function-env.cjs +50 -0
  302. package/serverless.local.yml +0 -1
  303. package/serverless.yml +44 -55
  304. package/shareLinkMaintenanceLambda.ts +18 -0
  305. package/shareLinkMaintenanceLambdaApp.ts +50 -0
  306. package/src/accesslayer/app-manifest-version/create.ts +57 -0
  307. package/src/accesslayer/app-manifest-version/read.ts +129 -0
  308. package/src/accesslayer/app-manifest-version/update.ts +61 -0
  309. package/src/accesslayer/app-store-listing/read.ts +6 -0
  310. package/src/accesslayer/app-store-listing/relationships/create.ts +20 -0
  311. package/src/accesslayer/app-store-listing/relationships/read.ts +24 -1
  312. package/src/accesslayer/app-store-listing/update.ts +15 -0
  313. package/src/accesslayer/boost/relationships/create.ts +8 -2
  314. package/src/accesslayer/consentflowcontract/create.ts +81 -8
  315. package/src/accesslayer/consentflowcontract/read.ts +84 -10
  316. package/src/accesslayer/consentflowcontract/relationships/create.ts +125 -109
  317. package/src/accesslayer/consentflowcontract/relationships/delete.ts +1 -0
  318. package/src/accesslayer/consentflowcontract/relationships/read.ts +184 -108
  319. package/src/accesslayer/consentflowcontract/relationships/recipients.ts +125 -0
  320. package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +125 -0
  321. package/src/accesslayer/consentflowcontract/relationships/update.ts +255 -217
  322. package/src/accesslayer/credential/create.test.ts +2 -115
  323. package/src/accesslayer/credential/create.ts +11 -6
  324. package/src/accesslayer/credential/relationships/create.ts +17 -10
  325. package/src/accesslayer/inbox-credential/relationships/create.ts +70 -48
  326. package/src/accesslayer/inbox-credential/update.ts +4 -2
  327. package/src/accesslayer/presentation/read.ts +57 -25
  328. package/src/accesslayer/presentation/relationships/create.ts +54 -11
  329. package/src/accesslayer/presentation/relationships/read.ts +21 -6
  330. package/src/accesslayer/share-link/abandon.ts +120 -0
  331. package/src/accesslayer/share-link/attachment.ts +360 -0
  332. package/src/accesslayer/share-link/cleanup.ts +271 -0
  333. package/src/accesslayer/share-link/errors.ts +32 -0
  334. package/src/accesslayer/share-link/finalize.ts +313 -0
  335. package/src/accesslayer/share-link/helpers.ts +450 -0
  336. package/src/accesslayer/share-link/index.ts +11 -0
  337. package/src/accesslayer/share-link/list.ts +162 -0
  338. package/src/accesslayer/share-link/read.ts +107 -0
  339. package/src/accesslayer/share-link/receipt.ts +548 -0
  340. package/src/accesslayer/share-link/recovery.ts +565 -0
  341. package/src/accesslayer/share-link/reserve.ts +763 -0
  342. package/src/accesslayer/share-link/revoke.ts +232 -0
  343. package/src/accesslayer/share-link/transaction-execution.ts +72 -0
  344. package/src/accesslayer/share-link/transaction.ts +126 -0
  345. package/src/accesslayer/share-link/types.ts +606 -0
  346. package/src/app.ts +6 -0
  347. package/src/cache/app-store.caches.ts +35 -0
  348. package/src/config/applyDockerStageConfig.ts +6 -0
  349. package/src/config/environment.test.ts +17 -12
  350. package/src/config/environment.ts +15 -0
  351. package/src/config/serverlessFunctionEnv.test.ts +105 -0
  352. package/src/config/stageConfig.test.ts +25 -0
  353. package/src/config/stageConfig.ts +24 -0
  354. package/src/constants/auth-grant.ts +5 -0
  355. package/src/docker-entry.ts +81 -0
  356. package/src/helpers/app-manifest.helpers.ts +185 -0
  357. package/src/helpers/boost.helpers.ts +4 -31
  358. package/src/helpers/claim-hooks.helpers.ts +20 -7
  359. package/src/helpers/consent-referral.helpers.ts +6 -0
  360. package/src/helpers/consent-scopes.helpers.ts +58 -0
  361. package/src/helpers/contract-event-maintenance.helpers.ts +43 -0
  362. package/src/helpers/contract-events.helpers.ts +540 -0
  363. package/src/helpers/contract-requests.helpers.ts +211 -0
  364. package/src/helpers/credentialSubject.helpers.test.ts +114 -0
  365. package/src/helpers/credentialSubject.helpers.ts +40 -0
  366. package/src/helpers/finalize-inbox.helpers.ts +163 -44
  367. package/src/helpers/inbox.helpers.ts +58 -70
  368. package/src/helpers/inline-template.helpers.ts +560 -0
  369. package/src/helpers/notificationMessages.ts +77 -1
  370. package/src/helpers/notificationQueue.helpers.ts +23 -5
  371. package/src/helpers/notifications.helpers.ts +25 -1
  372. package/src/helpers/presentation-metadata.ts +41 -0
  373. package/src/helpers/presentation.helpers.ts +27 -20
  374. package/src/helpers/profile.helpers.ts +13 -0
  375. package/src/helpers/share-content-client/adapters.ts +55 -0
  376. package/src/helpers/share-content-client/canonical.ts +25 -0
  377. package/src/helpers/share-content-client/client.ts +440 -0
  378. package/src/helpers/share-content-client/config.ts +189 -0
  379. package/src/helpers/share-content-client/index.ts +27 -0
  380. package/src/helpers/share-content-client/responses.ts +273 -0
  381. package/src/helpers/share-content-client/types.ts +253 -0
  382. package/src/helpers/share-link-attachment-access.ts +87 -0
  383. package/src/helpers/share-link-attachment.ts +115 -0
  384. package/src/helpers/share-link-coordinator/attachment-stat.ts +41 -0
  385. package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
  386. package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
  387. package/src/helpers/share-link-coordinator/coordinator.ts +727 -0
  388. package/src/helpers/share-link-coordinator/index.ts +8 -0
  389. package/src/helpers/share-link-coordinator/recovery-runner.ts +443 -0
  390. package/src/helpers/share-link-coordinator/runtime.ts +88 -0
  391. package/src/helpers/share-link-coordinator/stat.ts +30 -0
  392. package/src/helpers/share-link-coordinator/types.ts +353 -0
  393. package/src/helpers/share-link-lifecycle/index.ts +4 -0
  394. package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
  395. package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
  396. package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
  397. package/src/helpers/share-link-lifecycle/types.ts +58 -0
  398. package/src/helpers/share-link-maintenance/budget.ts +63 -0
  399. package/src/helpers/share-link-maintenance/config.ts +312 -0
  400. package/src/helpers/share-link-maintenance/index.ts +24 -0
  401. package/src/helpers/share-link-maintenance/runner.ts +251 -0
  402. package/src/helpers/share-link-maintenance/runtime.ts +340 -0
  403. package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
  404. package/src/helpers/share-link-maintenance/telemetry.ts +73 -0
  405. package/src/helpers/share-link-maintenance/types.ts +91 -0
  406. package/src/helpers/share-link-owner/config.ts +48 -0
  407. package/src/helpers/share-link-owner/diagnostics.ts +89 -0
  408. package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
  409. package/src/helpers/share-link-owner-projection.ts +54 -0
  410. package/src/helpers/share-link-passcode-abuse.ts +36 -0
  411. package/src/helpers/share-link-passcode.ts +47 -0
  412. package/src/helpers/share-link-policy/index.ts +7 -0
  413. package/src/helpers/share-link-policy/production.ts +108 -0
  414. package/src/helpers/share-link-policy/resolver.ts +113 -0
  415. package/src/helpers/share-link-policy/types.ts +56 -0
  416. package/src/helpers/share-link-public/config.ts +41 -0
  417. package/src/helpers/share-link-view-notification.ts +18 -0
  418. package/src/helpers/signingAuthority.helpers.ts +23 -14
  419. package/src/helpers/smartResume.helpers.ts +123 -0
  420. package/src/models/AppManifestVersion.ts +43 -0
  421. package/src/models/AppStoreListing.ts +32 -0
  422. package/src/models/ConsentFlowContract.ts +34 -3
  423. package/src/models/ConsentFlowTerms.ts +10 -0
  424. package/src/models/ConsentFlowTransaction.ts +3 -0
  425. package/src/models/Integration.ts +2 -0
  426. package/src/models/Presentation.ts +2 -2
  427. package/src/models/Profile.ts +3 -2
  428. package/src/models/ShareLink.ts +134 -0
  429. package/src/models/ShareViewReceipt.ts +69 -0
  430. package/src/models/index.ts +25 -0
  431. package/src/models/share-link-constraints.ts +98 -0
  432. package/src/openapi.ts +44 -8
  433. package/src/public-share-link-http.ts +49 -0
  434. package/src/routes/app-store.ts +1056 -38
  435. package/src/routes/boosts.ts +14 -25
  436. package/src/routes/contracts.ts +384 -273
  437. package/src/routes/inbox.ts +4 -2
  438. package/src/routes/index.ts +142 -44
  439. package/src/routes/presentations.ts +19 -3
  440. package/src/routes/profile-manager.ts +29 -2
  441. package/src/routes/profiles.ts +29 -6
  442. package/src/routes/public-share-links.ts +983 -0
  443. package/src/routes/share-links.ts +709 -0
  444. package/src/routes/workflows.ts +3 -2
  445. package/src/services/delivery/adapters/postmark.adapter.ts +4 -1
  446. package/src/types/app-manifest.ts +28 -0
  447. package/src/types/app-store-listing.ts +13 -0
  448. package/src/types/consentflowcontract.ts +25 -4
  449. package/test/app-manifest.helpers.spec.ts +170 -0
  450. package/test/app-manifest.routes.spec.ts +299 -0
  451. package/test/app-store-listing-updates.spec.ts +236 -0
  452. package/test/app-store-listing.spec.ts +65 -3
  453. package/test/consent-data-boundaries.spec.ts +352 -0
  454. package/test/consent-recipients.spec.ts +513 -0
  455. package/test/consent-scopes.helpers.spec.ts +67 -0
  456. package/test/consentflow.spec.ts +63 -2
  457. package/test/contract-requests.spec.ts +1588 -0
  458. package/test/credential-storage.neo4j.spec.ts +122 -0
  459. package/test/dids.spec.ts +10 -1
  460. package/test/guardian-gated-routes.spec.ts +49 -0
  461. package/test/helpers/share-link-fixtures.ts +94 -0
  462. package/test/inbox-security.e2e.ts +26 -10
  463. package/test/inbox.spec.ts +184 -8
  464. package/test/inline-template.helpers.spec.ts +49 -0
  465. package/test/managed-recipient-inbox.spec.ts +152 -0
  466. package/test/notificationMessages.spec.ts +13 -0
  467. package/test/openapi.spec.ts +14 -53
  468. package/test/presentations.spec.ts +54 -6
  469. package/test/profiles.spec.ts +97 -1
  470. package/test/share-content-client.contract.spec.ts +365 -0
  471. package/test/share-content-client.unit.spec.ts +462 -0
  472. package/test/share-link-abuse.unit.spec.ts +65 -0
  473. package/test/share-link-attachment.neo4j.spec.ts +262 -0
  474. package/test/share-link-attachment.unit.spec.ts +244 -0
  475. package/test/share-link-coordinator.neo4j.spec.ts +684 -0
  476. package/test/share-link-coordinator.unit.spec.ts +990 -0
  477. package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
  478. package/test/share-link-lifecycle.neo4j.spec.ts +1411 -0
  479. package/test/share-link-lifecycle.unit.spec.ts +205 -0
  480. package/test/share-link-list-cursor.unit.spec.ts +86 -0
  481. package/test/share-link-list.neo4j.spec.ts +324 -0
  482. package/test/share-link-maintenance.neo4j.spec.ts +355 -0
  483. package/test/share-link-maintenance.unit.spec.ts +1140 -0
  484. package/test/share-link-owner-policy.unit.spec.ts +210 -0
  485. package/test/share-link-owner-route.unit.spec.ts +820 -0
  486. package/test/share-link-owner-runtime.unit.spec.ts +102 -0
  487. package/test/share-link-passcode.unit.spec.ts +29 -0
  488. package/test/share-link-policy.unit.spec.ts +149 -0
  489. package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
  490. package/test/share-link-production-policy.unit.spec.ts +126 -0
  491. package/test/share-link-public-config.unit.spec.ts +64 -0
  492. package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
  493. package/test/share-link-public-route.unit.spec.ts +1079 -0
  494. package/test/share-link-receipt.neo4j.spec.ts +943 -0
  495. package/test/share-link-recovery.neo4j.spec.ts +935 -0
  496. package/test/share-link-recovery.unit.spec.ts +726 -0
  497. package/test/share-link-service-policy.neo4j.spec.ts +403 -0
  498. package/test/signing-authority-encryption.spec.ts +75 -0
  499. package/test/smart-resume-consent.spec.ts +233 -0
  500. package/test/smart-resume-upload.spec.ts +65 -0
  501. package/tsconfig.build.json +1 -1
  502. package/tsconfig.json +1 -0
  503. package/vitest.config.ts +14 -0
  504. package/vitest.inbox-e2e.config.ts +2 -0
  505. package/vitest.share-links-neo4j.config.ts +19 -0
  506. package/vitest.share-links-unit.config.ts +28 -0
  507. package/workerLambda.test.ts +100 -0
@@ -0,0 +1,513 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { getUser } from './helpers/getClient';
4
+ import { normalContract, normalFullTerms } from './helpers/contract';
5
+ import { Profile, ConsentFlowContract } from '@models';
6
+ import { createConsentFlowContract } from '@accesslayer/consentflowcontract/create';
7
+ import { getIdFromUri } from '@helpers/uri.helpers';
8
+ import {
9
+ getContractTermsByUri,
10
+ getConsentedDataForContract,
11
+ } from '@accesslayer/consentflowcontract/relationships/read';
12
+ import { syncCredentialsToContract } from '@accesslayer/consentflowcontract/relationships/update';
13
+ import { testUnsignedBoost, testVc } from './helpers/send';
14
+ import { openApiDocument } from '../src/openapi';
15
+ import { neogma } from '@instance';
16
+
17
+ const actor = async (role: string) => {
18
+ const user = await getUser(randomBytes(32).toString('hex'));
19
+ const profileId = `audience-${role}-${randomBytes(5).toString('hex')}`;
20
+ await user.clients.fullAuth.profile.createProfile({ profileId });
21
+ return { ...user, profileId, did: (await user.clients.fullAuth.profile.getProfile())!.did };
22
+ };
23
+
24
+ describe('contract data recipients', () => {
25
+ let owner: Awaited<ReturnType<typeof actor>>;
26
+ let recipient: Awaited<ReturnType<typeof actor>>;
27
+ let learner: Awaited<ReturnType<typeof actor>>;
28
+ let outsider: Awaited<ReturnType<typeof actor>>;
29
+ beforeEach(async () => {
30
+ [owner, recipient, learner, outsider] = await Promise.all([
31
+ actor('owner'),
32
+ actor('recipient'),
33
+ actor('learner'),
34
+ actor('outsider'),
35
+ ]);
36
+ });
37
+ const create = (recipients: string[] = []) =>
38
+ owner.clients.fullAuth.contracts.createConsentFlowContract({
39
+ name: 'Synthetic audience contract',
40
+ contract: normalContract,
41
+ recipients,
42
+ });
43
+ const accept = async (contractUri: string, audienceVersion?: number) =>
44
+ learner.clients.fullAuth.contracts.consentToContract({
45
+ contractUri,
46
+ terms: normalFullTerms,
47
+ audienceVersion,
48
+ });
49
+ const details = (uri: string) =>
50
+ owner.clients.fullAuth.contracts.getConsentFlowContract({ uri });
51
+
52
+ it('deduplicates recipients, excludes the owner, resolves DIDs and strips private profile fields', async () => {
53
+ await Profile.update(
54
+ { email: 'synthetic-private@example.com' },
55
+ { where: { profileId: recipient.profileId } }
56
+ );
57
+ const uri = await create([recipient.profileId, recipient.did, owner.profileId]);
58
+ const result = await details(uri);
59
+ expect(result.recipients?.map(profile => profile.profileId)).toEqual([recipient.profileId]);
60
+ expect(result.audienceVersion).toBe(1);
61
+ expect(result.recipients![0]).not.toHaveProperty('email');
62
+ });
63
+
64
+ it('validates writers and autoboosts before creating any contract or audience', async () => {
65
+ const name = `atomic-validation-${owner.profileId}`;
66
+ for (const configuration of [
67
+ { writers: ['missing-writer'] },
68
+ {
69
+ autoboosts: [
70
+ {
71
+ boostUri: 'lc:boost:localhost:missing-boost',
72
+ signingAuthority: { endpoint: 'https://example.com', name: 'missing' },
73
+ },
74
+ ],
75
+ },
76
+ ]) {
77
+ await expect(
78
+ owner.clients.fullAuth.contracts.createConsentFlowContract({
79
+ name,
80
+ contract: normalContract,
81
+ recipients: [recipient.profileId],
82
+ ...configuration,
83
+ })
84
+ ).rejects.toThrow();
85
+ expect(await ConsentFlowContract.findMany({ where: { name } })).toHaveLength(0);
86
+ }
87
+ });
88
+
89
+ it('creates no partial contract if a referenced recipient disappears before the atomic write', async () => {
90
+ const name = `atomic-missing-${owner.profileId}`;
91
+ await expect(
92
+ createConsentFlowContract({
93
+ name,
94
+ contract: normalContract,
95
+ ownerProfileId: owner.profileId,
96
+ recipientIds: [recipient.profileId, 'missing-recipient'],
97
+ })
98
+ ).rejects.toMatchObject({ code: 'BAD_REQUEST' });
99
+ expect(await ConsentFlowContract.findMany({ where: { name } })).toHaveLength(0);
100
+ });
101
+
102
+ it('rolls back the contract and all relationships if a later configuration write fails', async () => {
103
+ const boostUri = await owner.clients.fullAuth.boost.createBoost({
104
+ credential: testUnsignedBoost,
105
+ });
106
+ const name = `atomic-rollback-${owner.profileId}`;
107
+ await expect(
108
+ createConsentFlowContract({
109
+ name,
110
+ contract: normalContract,
111
+ ownerProfileId: owner.profileId,
112
+ recipientIds: [recipient.profileId],
113
+ writerIds: [outsider.profileId],
114
+ autoboosts: [
115
+ {
116
+ id: getIdFromUri(boostUri),
117
+ signingAuthorityName: 'synthetic',
118
+ // Neo4j rejects map-valued properties after the preceding writes.
119
+ signingAuthorityEndpoint: { invalid: true } as unknown as string,
120
+ },
121
+ ],
122
+ })
123
+ ).rejects.toThrow();
124
+ expect(await ConsentFlowContract.findMany({ where: { name } })).toHaveLength(0);
125
+ });
126
+
127
+ it('keeps audience versions monotonic when a recipient is added and removed before consent', async () => {
128
+ const uri = await create();
129
+ await owner.clients.fullAuth.contracts.addContractRecipient({
130
+ contractUri: uri,
131
+ recipient: recipient.profileId,
132
+ });
133
+ await owner.clients.fullAuth.contracts.removeContractRecipient({
134
+ contractUri: uri,
135
+ recipient: recipient.profileId,
136
+ });
137
+ expect((await details(uri)).recipients).toEqual([]);
138
+ expect((await details(uri)).audienceVersion).toBe(2);
139
+ await expect(accept(uri)).rejects.toMatchObject({ code: 'CONFLICT' });
140
+ await accept(uri, 2);
141
+ });
142
+
143
+ it('includes public recipients in consent listings, including empty legacy audiences', async () => {
144
+ await Profile.update(
145
+ { email: 'synthetic-private@example.com' },
146
+ { where: { profileId: recipient.profileId } }
147
+ );
148
+ const uri = await create([recipient.profileId]);
149
+ await accept(uri, 1);
150
+ const legacyUri = await create();
151
+ await accept(legacyUri);
152
+ const records = (await learner.clients.fullAuth.contracts.getConsentedContracts()).records;
153
+ const listed = records.find(record => record.contract.uri === uri)!.contract;
154
+ expect(listed.recipients?.map(profile => profile.profileId)).toEqual([recipient.profileId]);
155
+ expect(listed.recipients![0]).not.toHaveProperty('email');
156
+ expect(
157
+ records.find(record => record.contract.uri === legacyUri)!.contract.recipients
158
+ ).toEqual([]);
159
+ });
160
+
161
+ it('exports empty recipients and version zero for legacy contracts without an audience version', async () => {
162
+ const uri = await create();
163
+ await neogma.queryRunner.run(
164
+ 'MATCH (contract:ConsentFlowContract {id: $id}) REMOVE contract.audienceVersion',
165
+ { id: getIdFromUri(uri) }
166
+ );
167
+ await accept(uri);
168
+ const exported = (await learner.clients.fullAuth.credential.getHolderExportMetadata())
169
+ .consentRecords[0]!;
170
+ expect(exported.contract.recipients).toEqual([]);
171
+ expect(exported.contract.audienceVersion).toBe(0);
172
+ expect(exported.contract.owner.did).toBe(owner.did);
173
+ });
174
+
175
+ it('exports the current public audience and version while preserving holder history', async () => {
176
+ await Profile.update(
177
+ { email: 'synthetic-private@example.com' },
178
+ { where: { profileId: recipient.profileId } }
179
+ );
180
+ const uri = await create([recipient.profileId]);
181
+ const { termsUri } = await accept(uri, 1);
182
+ const exported = (await learner.clients.fullAuth.credential.getHolderExportMetadata())
183
+ .consentRecords[0]!;
184
+ expect(exported.contract.owner.did).toBe(owner.did);
185
+ expect(exported.contract.audienceVersion).toBe(1);
186
+ expect(exported.contract.recipients).toEqual([
187
+ expect.objectContaining({ profileId: recipient.profileId, did: recipient.did }),
188
+ ]);
189
+ expect(exported.contract.recipients![0]).not.toHaveProperty('email');
190
+ await owner.clients.fullAuth.contracts.removeContractRecipient({
191
+ contractUri: uri,
192
+ recipient: recipient.profileId,
193
+ });
194
+ await learner.clients.fullAuth.contracts.withdrawConsent({ uri: termsUri });
195
+ const later = (await learner.clients.fullAuth.credential.getHolderExportMetadata())
196
+ .consentRecords[0]!;
197
+ expect(later.contract.recipients).toEqual([]);
198
+ expect(later.contract.audienceVersion).toBe(2);
199
+ expect(later.status).toBe('withdrawn');
200
+ expect(later.transactions).toEqual(
201
+ expect.arrayContaining([
202
+ expect.objectContaining({ action: 'consent' }),
203
+ expect.objectContaining({ action: 'withdraw' }),
204
+ ])
205
+ );
206
+ });
207
+
208
+ it('collapses repeated autoboosts after normalizing signing-authority names', async () => {
209
+ const endpoint = 'https://example.com/synthetic-signer';
210
+ await owner.clients.fullAuth.profile.registerSigningAuthority({
211
+ endpoint,
212
+ name: 'synthetic',
213
+ did: owner.did,
214
+ });
215
+ const boostUri = await owner.clients.fullAuth.boost.createBoost({
216
+ credential: testUnsignedBoost,
217
+ });
218
+ const uri = await owner.clients.fullAuth.contracts.createConsentFlowContract({
219
+ name: 'Duplicate autoboost configuration',
220
+ contract: normalContract,
221
+ autoboosts: ['synthetic', 'Synthetic', 'synthetic'].map(name => ({
222
+ boostUri,
223
+ signingAuthority: { endpoint, name },
224
+ })),
225
+ });
226
+ expect((await details(uri)).autoBoosts).toEqual([boostUri]);
227
+ });
228
+
229
+ it('rejects conflicting signers for the same autoboost before creating a contract', async () => {
230
+ const endpoint = 'https://example.com/synthetic-signer';
231
+ for (const name of ['first', 'second']) {
232
+ await owner.clients.fullAuth.profile.registerSigningAuthority({
233
+ endpoint,
234
+ name,
235
+ did: owner.did,
236
+ });
237
+ }
238
+ const boostUri = await owner.clients.fullAuth.boost.createBoost({
239
+ credential: testUnsignedBoost,
240
+ });
241
+ const name = `autoboost-conflict-${owner.profileId}`;
242
+ await expect(
243
+ owner.clients.fullAuth.contracts.createConsentFlowContract({
244
+ name,
245
+ contract: normalContract,
246
+ autoboosts: ['first', 'second'].map(name => ({
247
+ boostUri,
248
+ signingAuthority: { endpoint, name },
249
+ })),
250
+ })
251
+ ).rejects.toMatchObject({
252
+ code: 'BAD_REQUEST',
253
+ message: expect.stringContaining('Conflicting signing authorities'),
254
+ });
255
+ expect(await ConsentFlowContract.findMany({ where: { name } })).toHaveLength(0);
256
+ });
257
+
258
+ it('rejects missing and stale acknowledgments without recording consent', async () => {
259
+ const uri = await create([recipient.profileId]);
260
+ await expect(accept(uri)).rejects.toMatchObject({ code: 'CONFLICT' });
261
+ await expect(accept(uri, 0)).rejects.toMatchObject({ code: 'CONFLICT' });
262
+ expect((await learner.clients.fullAuth.contracts.getConsentedContracts()).records).toEqual(
263
+ []
264
+ );
265
+ await accept(uri, 1);
266
+ });
267
+
268
+ it('allows permitted data reads for recipients but grants no admin or writer rights', async () => {
269
+ const uri = await create([recipient.profileId]);
270
+ await accept(uri, 1);
271
+ const routes = recipient.clients.fullAuth.contracts;
272
+ const query = { id: uri.split(':').at(-1)! };
273
+ expect((await routes.getConsentedDataForContract({ uri })).records).toHaveLength(1);
274
+ expect(
275
+ (await routes.getConsentedDataForDid({ did: learner.did, query })).records
276
+ ).toHaveLength(1);
277
+ expect((await routes.getConsentedData()).records).toHaveLength(1);
278
+ await expect(
279
+ routes.addContractRecipient({ contractUri: uri, recipient: outsider.profileId })
280
+ ).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
281
+ await expect(
282
+ routes.removeContractRecipient({ contractUri: uri, recipient: recipient.profileId })
283
+ ).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
284
+ await expect(routes.deleteConsentFlowContract({ uri })).rejects.toMatchObject({
285
+ code: 'UNAUTHORIZED',
286
+ });
287
+ const boostUri = await recipient.clients.fullAuth.boost.createBoost({
288
+ credential: testUnsignedBoost,
289
+ });
290
+ await expect(
291
+ routes.writeCredentialToContract({
292
+ did: learner.did,
293
+ contractUri: uri,
294
+ boostUri,
295
+ credential: testVc,
296
+ })
297
+ ).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
298
+ await expect(
299
+ outsider.clients.fullAuth.contracts.getConsentedDataForContract({ uri })
300
+ ).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
301
+ expect(
302
+ (await outsider.clients.fullAuth.contracts.getConsentedDataForDid({ did: learner.did }))
303
+ .records
304
+ ).toEqual([]);
305
+ expect((await outsider.clients.fullAuth.contracts.getConsentedData()).records).toEqual([]);
306
+ });
307
+
308
+ it('keeps explicitly added writers out of the data audience', async () => {
309
+ const uri = await owner.clients.fullAuth.contracts.createConsentFlowContract({
310
+ name: 'Writer isolation',
311
+ contract: normalContract,
312
+ writers: [outsider.profileId],
313
+ recipients: [recipient.profileId],
314
+ });
315
+ await accept(uri, 1);
316
+ await expect(
317
+ outsider.clients.fullAuth.contracts.getConsentedDataForContract({ uri })
318
+ ).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
319
+ expect((await outsider.clients.fullAuth.contracts.getConsentedData()).records).toEqual([]);
320
+ });
321
+
322
+ it('removal stops recipient discovery and rejects old client updates and syncs while owner access remains', async () => {
323
+ const uri = await create([recipient.profileId]);
324
+ const { termsUri } = await accept(uri, 1);
325
+ await owner.clients.fullAuth.contracts.removeContractRecipient({
326
+ contractUri: uri,
327
+ recipient: recipient.profileId,
328
+ });
329
+ expect((await details(uri)).audienceVersion).toBe(2);
330
+ await expect(
331
+ recipient.clients.fullAuth.contracts.getConsentedDataForContract({ uri })
332
+ ).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
333
+ expect(
334
+ (
335
+ await recipient.clients.fullAuth.contracts.getConsentedDataForDid({
336
+ did: learner.did,
337
+ })
338
+ ).records
339
+ ).toEqual([]);
340
+ expect((await recipient.clients.fullAuth.contracts.getConsentedData()).records).toEqual([]);
341
+ expect(
342
+ (await owner.clients.fullAuth.contracts.getConsentedDataForContract({ uri })).records
343
+ ).toHaveLength(1);
344
+ for (const audienceVersion of [undefined, 1]) {
345
+ await expect(
346
+ learner.clients.fullAuth.contracts.updateConsentedContractTerms({
347
+ uri: termsUri,
348
+ terms: normalFullTerms,
349
+ audienceVersion,
350
+ })
351
+ ).rejects.toMatchObject({ code: 'CONFLICT' });
352
+ await expect(
353
+ learner.clients.fullAuth.contracts.syncCredentialsToContract({
354
+ termsUri,
355
+ categories: { Achievement: ['urn:stale:copy'] },
356
+ audienceVersion,
357
+ })
358
+ ).rejects.toMatchObject({ code: 'CONFLICT' });
359
+ }
360
+ await learner.clients.fullAuth.contracts.syncCredentialsToContract({
361
+ termsUri,
362
+ categories: { Achievement: ['urn:current:copy'] },
363
+ audienceVersion: 2,
364
+ });
365
+ expect(
366
+ JSON.stringify(
367
+ await owner.clients.fullAuth.contracts.getConsentedDataForContract({ uri })
368
+ )
369
+ ).not.toContain('urn:stale:copy');
370
+ });
371
+
372
+ it('freezes additions after first consent even following withdrawal and all recipient removals', async () => {
373
+ const uri = await create([recipient.profileId]);
374
+ const { termsUri } = await accept(uri, 1);
375
+ await learner.clients.fullAuth.contracts.withdrawConsent({ uri: termsUri });
376
+ await owner.clients.fullAuth.contracts.removeContractRecipient({
377
+ contractUri: uri,
378
+ recipient: recipient.profileId,
379
+ });
380
+ await expect(
381
+ owner.clients.fullAuth.contracts.addContractRecipient({
382
+ contractUri: uri,
383
+ recipient: outsider.profileId,
384
+ })
385
+ ).rejects.toMatchObject({ code: 'CONFLICT' });
386
+ expect((await details(uri)).recipients).toEqual([]);
387
+ });
388
+
389
+ it('pre-consent addition is idempotent and invalidates the previous version', async () => {
390
+ const uri = await create();
391
+ await owner.clients.fullAuth.contracts.addContractRecipient({
392
+ contractUri: uri,
393
+ recipient: recipient.profileId,
394
+ });
395
+ await owner.clients.fullAuth.contracts.addContractRecipient({
396
+ contractUri: uri,
397
+ recipient: recipient.profileId,
398
+ });
399
+ expect((await details(uri)).audienceVersion).toBe(1);
400
+ await expect(accept(uri, 0)).rejects.toMatchObject({ code: 'CONFLICT' });
401
+ await accept(uri, 1);
402
+ });
403
+
404
+ it.each([1, 2, 3, 4, 5])(
405
+ 'serializes first consent versus recipient addition (attempt %s)',
406
+ async () => {
407
+ const uri = await create();
408
+ const results = await Promise.allSettled([
409
+ accept(uri, 0),
410
+ owner.clients.fullAuth.contracts.addContractRecipient({
411
+ contractUri: uri,
412
+ recipient: recipient.profileId,
413
+ }),
414
+ ]);
415
+ expect(results.filter(result => result.status === 'fulfilled')).toHaveLength(1);
416
+ const audience = await details(uri);
417
+ const history = await learner.clients.fullAuth.contracts.getConsentedContracts();
418
+ if (results[0]!.status === 'fulfilled') {
419
+ expect(audience.recipients).toEqual([]);
420
+ expect(history.records).toHaveLength(1);
421
+ } else {
422
+ expect(audience.recipients).toHaveLength(1);
423
+ expect(history.records).toEqual([]);
424
+ }
425
+ }
426
+ );
427
+
428
+ it('rejects a sync snapshot prepared before a consent edit, without restoring disabled sharing', async () => {
429
+ const uri = await create([recipient.profileId]);
430
+ const { termsUri } = await accept(uri, 1);
431
+ const snapshot = (await getContractTermsByUri(termsUri))!;
432
+ const terms = structuredClone(normalFullTerms);
433
+ terms.read.credentials.categories.Achievement!.sharing = false;
434
+ await learner.clients.fullAuth.contracts.updateConsentedContractTerms({
435
+ uri: termsUri,
436
+ terms,
437
+ audienceVersion: 1,
438
+ });
439
+ await expect(
440
+ syncCredentialsToContract(snapshot, { Achievement: ['urn:stale:snapshot'] }, 1)
441
+ ).rejects.toMatchObject({ code: 'CONFLICT' });
442
+ const current = (await getContractTermsByUri(termsUri))!;
443
+ expect(current.terms.terms.read.credentials.categories.Achievement!.sharing).toBe(false);
444
+ expect(
445
+ JSON.stringify(
446
+ await recipient.clients.fullAuth.contracts.getConsentedDataForContract({ uri })
447
+ )
448
+ ).not.toContain('urn:stale:snapshot');
449
+ });
450
+
451
+ it('rechecks membership in the data query after an earlier authorization decision', async () => {
452
+ const uri = await create([recipient.profileId]);
453
+ await accept(uri, 1);
454
+ const id = uri.split(':').at(-1)!;
455
+ expect(
456
+ await getConsentedDataForContract(id, recipient.profileId, { limit: 10 })
457
+ ).toHaveLength(1);
458
+ await owner.clients.fullAuth.contracts.removeContractRecipient({
459
+ contractUri: uri,
460
+ recipient: recipient.profileId,
461
+ });
462
+ expect(await getConsentedDataForContract(id, recipient.profileId, { limit: 10 })).toEqual(
463
+ []
464
+ );
465
+ expect(await getConsentedDataForContract(id, owner.profileId, { limit: 10 })).toHaveLength(
466
+ 1
467
+ );
468
+ });
469
+
470
+ it('includes recipient endpoints and audience acknowledgements in generated OpenAPI', () => {
471
+ const schema = JSON.stringify(openApiDocument);
472
+ expect(openApiDocument.paths?.['/consent-flow-contract/recipient/add']?.post).toBeTruthy();
473
+ expect(
474
+ openApiDocument.paths?.['/consent-flow-contract/recipient/remove']?.post
475
+ ).toBeTruthy();
476
+ expect(schema).toContain('audienceVersion');
477
+ expect(schema).toContain('recipients');
478
+ });
479
+
480
+ it('recipient access also stops on withdrawal', async () => {
481
+ const uri = await create([recipient.profileId]);
482
+ const { termsUri } = await accept(uri, 1);
483
+ await learner.clients.fullAuth.contracts.withdrawConsent({ uri: termsUri });
484
+ expect(
485
+ (await recipient.clients.fullAuth.contracts.getConsentedDataForContract({ uri }))
486
+ .records
487
+ ).toEqual([]);
488
+ expect(
489
+ (
490
+ await recipient.clients.fullAuth.contracts.getConsentedDataForDid({
491
+ did: learner.did,
492
+ })
493
+ ).records
494
+ ).toEqual([]);
495
+ expect((await recipient.clients.fullAuth.contracts.getConsentedData()).records).toEqual([]);
496
+ });
497
+
498
+ it('encrypts real data for owner and recipient; an outsider and removed recipient cannot decrypt new copies', async () => {
499
+ const payload = { id: 'urn:synthetic:audience', value: 'Synthetic outcome' };
500
+ const shared = await learner.learnCard.invoke.createDagJwe(payload, [
501
+ owner.learnCard.id.did(),
502
+ recipient.learnCard.id.did(),
503
+ ]);
504
+ expect(await owner.learnCard.invoke.decryptDagJwe(shared)).toEqual(payload);
505
+ expect(await recipient.learnCard.invoke.decryptDagJwe(shared)).toEqual(payload);
506
+ await expect(outsider.learnCard.invoke.decryptDagJwe(shared)).resolves.toBe('');
507
+ const afterRemoval = await learner.learnCard.invoke.createDagJwe(payload, [
508
+ owner.learnCard.id.did(),
509
+ ]);
510
+ expect(await owner.learnCard.invoke.decryptDagJwe(afterRemoval)).toEqual(payload);
511
+ await expect(recipient.learnCard.invoke.decryptDagJwe(afterRemoval)).resolves.toBe('');
512
+ });
513
+ });
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ buildConsentFlowContractFromScopes,
5
+ computeConsentScopeHash,
6
+ normalizeConsentScopes,
7
+ } from '../src/helpers/consent-scopes.helpers';
8
+
9
+ describe('consent-scopes.helpers', () => {
10
+ it('maps normalized consent scopes to immutable contract terms', () => {
11
+ const scopes = normalizeConsentScopes({
12
+ read: {
13
+ credentialCategories: ['Skill', 'Achievement'],
14
+ personalFields: ['email', 'name'],
15
+ },
16
+ write: {
17
+ credentialCategories: ['Achievement'],
18
+ },
19
+ reason: 'ignored for mapping',
20
+ });
21
+
22
+ expect(buildConsentFlowContractFromScopes(scopes)).toEqual({
23
+ read: {
24
+ credentials: {
25
+ categories: {
26
+ Achievement: { required: false, defaultEnabled: true },
27
+ Skill: { required: false, defaultEnabled: true },
28
+ },
29
+ },
30
+ personal: {
31
+ email: { required: false, defaultEnabled: true },
32
+ name: { required: false, defaultEnabled: true },
33
+ },
34
+ },
35
+ write: {
36
+ credentials: {
37
+ categories: {
38
+ Achievement: { required: false, defaultEnabled: true },
39
+ },
40
+ },
41
+ personal: {},
42
+ },
43
+ });
44
+ });
45
+
46
+ it('hashes scope sets deterministically and ignores reason/order/duplicates', () => {
47
+ const first = normalizeConsentScopes({
48
+ read: {
49
+ credentialCategories: ['Skill', 'Achievement', 'Skill'],
50
+ personalFields: ['name'],
51
+ },
52
+ write: { credentialCategories: ['Achievement'] },
53
+ reason: 'First reason',
54
+ });
55
+
56
+ const second = normalizeConsentScopes({
57
+ read: {
58
+ credentialCategories: ['Achievement', 'Skill'],
59
+ personalFields: ['name'],
60
+ },
61
+ write: { credentialCategories: ['Achievement'] },
62
+ reason: 'Different reason',
63
+ });
64
+
65
+ expect(computeConsentScopeHash(first)).toBe(computeConsentScopeHash(second));
66
+ });
67
+ });
@@ -960,6 +960,47 @@ describe('Consent Flow Contracts', () => {
960
960
  expect(data.records[0]?.terms).toEqual(normalFullTerms);
961
961
  });
962
962
 
963
+ it('does not require guardian approval for managed service profiles, but does for managed children', async () => {
964
+ await Profile.relateTo({
965
+ alias: 'managedBy',
966
+ where: { source: { profileId: 'userb' }, target: { profileId: 'userc' } },
967
+ });
968
+
969
+ const managedChildData = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
970
+ did: userBDid,
971
+ });
972
+ expect(managedChildData.records[0]?.guardian.required).toBe(true);
973
+
974
+ await Profile.update({ isServiceProfile: true }, { where: { profileId: 'userb' } });
975
+ const serviceProfileData =
976
+ await userA.clients.fullAuth.contracts.getConsentedDataForDid({
977
+ did: userBDid,
978
+ });
979
+ expect(serviceProfileData.records[0]?.guardian).toEqual({
980
+ required: false,
981
+ approved: false,
982
+ });
983
+ });
984
+
985
+ it('keeps guardian approval required for legacy service-flagged children', async () => {
986
+ await Profile.relateTo({
987
+ alias: 'managedBy',
988
+ where: { source: { profileId: 'userb' }, target: { profileId: 'userc' } },
989
+ });
990
+ await Profile.update(
991
+ { type: 'child', isServiceProfile: true },
992
+ { where: { profileId: 'userb' } }
993
+ );
994
+
995
+ const data = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
996
+ did: userBDid,
997
+ });
998
+ expect(data.records[0]?.guardian).toEqual({
999
+ required: true,
1000
+ approved: false,
1001
+ });
1002
+ });
1003
+
963
1004
  it('should omit withdrawn consent before returning provider-facing data', async () => {
964
1005
  const activeData = await userA.clients.fullAuth.contracts.getConsentedDataForDid({
965
1006
  did: userBDid,
@@ -1162,6 +1203,17 @@ describe('Consent Flow Contracts', () => {
1162
1203
  approved: true,
1163
1204
  approval,
1164
1205
  });
1206
+ await Profile.update({ isServiceProfile: true }, { where: { profileId: 'userb' } });
1207
+ const serviceProfileWithApproval =
1208
+ await userA.clients.fullAuth.contracts.getConsentedDataForDid({
1209
+ did: userBDid,
1210
+ });
1211
+ expect(serviceProfileWithApproval.records[0]?.guardian).toEqual({
1212
+ required: true,
1213
+ approved: true,
1214
+ approval,
1215
+ });
1216
+
1165
1217
  const current = (await getContractTermsByUri(approved.records[0]!.termsUri))!;
1166
1218
  expect(current.terms.guardianApproval).toEqual(approval);
1167
1219
  const transactions = await getTransactionsForTerms(current.terms.id, { limit: 10 });
@@ -3343,7 +3395,7 @@ describe('Consent Flow Contracts', () => {
3343
3395
  contractUri = await userA.clients.fullAuth.contracts.createConsentFlowContract({
3344
3396
  contract: minimalContract,
3345
3397
  name: 'a',
3346
- expiresAt: new Date(Date.UTC(2024, 3, 25)).toISOString(), // Set explicit expiration
3398
+ expiresAt: '2999-01-01T00:00:00.000Z', // New consent uses Neo4j's real clock.
3347
3399
  });
3348
3400
  const { termsUri: _termsUri } =
3349
3401
  await userB.clients.fullAuth.contracts.consentToContract({
@@ -3351,6 +3403,11 @@ describe('Consent Flow Contracts', () => {
3351
3403
  terms: minimalTerms,
3352
3404
  });
3353
3405
  termsUri = _termsUri;
3406
+ // Verification's fake JS clock can still inspect an existing historical consent.
3407
+ await ConsentFlowContract.update(
3408
+ { expiresAt: new Date(Date.UTC(2024, 3, 25)).toISOString() },
3409
+ { where: { id: contractUri.split(':').at(-1)! } }
3410
+ );
3354
3411
  });
3355
3412
 
3356
3413
  afterAll(async () => {
@@ -3420,7 +3477,7 @@ describe('Consent Flow Contracts', () => {
3420
3477
  {
3421
3478
  contract: minimalContract,
3422
3479
  name: 'b',
3423
- expiresAt: new Date(2024, 4, 19).toISOString(),
3480
+ expiresAt: '2999-01-01T00:00:00.000Z',
3424
3481
  }
3425
3482
  );
3426
3483
 
@@ -3428,6 +3485,10 @@ describe('Consent Flow Contracts', () => {
3428
3485
  contractUri: newContractUri,
3429
3486
  terms: minimalTerms,
3430
3487
  });
3488
+ await ConsentFlowContract.update(
3489
+ { expiresAt: new Date(2024, 4, 19).toISOString() },
3490
+ { where: { id: newContractUri.split(':').at(-1)! } }
3491
+ );
3431
3492
 
3432
3493
  expect(
3433
3494
  await userA.clients.fullAuth.contracts.verifyConsent({