@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,211 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { BindParam, QueryBuilder } from 'neogma';
3
+ import { TRPCError } from '@trpc/server';
4
+ import type { SendContractRequest } from '@learncard/types';
5
+ import {
6
+ getContractByUri,
7
+ getContractRequestAccess,
8
+ getStoredContractRequest,
9
+ } from '@accesslayer/consentflowcontract/read';
10
+ import { getProfileByProfileId } from '@accesslayer/profile/read';
11
+ import { getWritersForContract } from '@accesslayer/consentflowcontract/relationships/read';
12
+ import {
13
+ canReadContractData,
14
+ lockContractAudience,
15
+ runAudienceMutation,
16
+ } from '@accesslayer/consentflowcontract/relationships/recipients';
17
+ import { getProfileIdFromString } from './did.helpers';
18
+ import { userHasRequiredScopes } from './auth-grant.helpers';
19
+ import { enforceRateLimits } from './rateLimit.helpers';
20
+ import { requestEventCypher, tryDispatchContractEvent } from './contract-events.helpers';
21
+ import type { ProfileType } from 'types/profile';
22
+ import type { DbContractType } from 'types/consentflowcontract';
23
+
24
+ /** Resolve shared request inputs without granting writers access to consented data. */
25
+ export const resolveContractRequest = async (
26
+ contractUri: string,
27
+ targetIdentifier: string,
28
+ profile: ProfileType,
29
+ domain: string,
30
+ allowRecipients = true
31
+ ): Promise<{ contract: DbContractType; target: ProfileType }> => {
32
+ const contract = await getContractByUri(contractUri);
33
+ if (!contract) throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
34
+ if (!(await canManageContractRequests(contract, profile.profileId, allowRecipients)))
35
+ throw new TRPCError({
36
+ code: 'UNAUTHORIZED',
37
+ message: 'You do not have permission to send requests for this contract.',
38
+ });
39
+ const targetId = await getProfileIdFromString(targetIdentifier, domain);
40
+ const target = targetId ? await getProfileByProfileId(targetId) : null;
41
+ if (!target) throw new TRPCError({ code: 'NOT_FOUND', message: 'Target profile not found' });
42
+ return { contract, target };
43
+ };
44
+
45
+ export const canManageContractRequests = async (
46
+ contract: DbContractType,
47
+ profileId: string,
48
+ allowRecipients = false
49
+ ): Promise<boolean> => {
50
+ const access = await getContractRequestAccess(contract.id, profileId);
51
+ return access.isManager || (allowRecipients && access.isRecipient);
52
+ };
53
+
54
+ /** One request per contract/target in v1. Exact pending retries do not create another event. */
55
+ export const sendGenericContractRequest = async (
56
+ input: SendContractRequest,
57
+ profile: ProfileType,
58
+ domain: string
59
+ ): Promise<boolean> => {
60
+ const { contract, target } = await resolveContractRequest(
61
+ input.contractUri,
62
+ input.targetProfileId,
63
+ profile,
64
+ domain
65
+ );
66
+ if (target.profileId === profile.profileId)
67
+ throw new TRPCError({
68
+ code: 'BAD_REQUEST',
69
+ message: 'You cannot send a request to yourself.',
70
+ });
71
+ await enforceRateLimits([
72
+ {
73
+ key: `contract-request-rate:${contract.id}:${profile.profileId}`,
74
+ limit: 500,
75
+ windowSeconds: 3600,
76
+ description: '500 contract requests per hour',
77
+ },
78
+ ]);
79
+ const requestId = randomUUID();
80
+ const query = lockContractAudience(
81
+ new QueryBuilder(
82
+ new BindParam({
83
+ targetProfileId: target.profileId,
84
+ senderProfileId: profile.profileId,
85
+ requestId,
86
+ externalReferenceId: input.externalReferenceId ?? null,
87
+ message: input.message ?? null,
88
+ now: new Date().toISOString(),
89
+ domain,
90
+ eventFrom: profile.profileId,
91
+ eventKind: 'request_sent',
92
+ })
93
+ ),
94
+ contract.id
95
+ ).raw(`
96
+ MATCH (target:Profile {profileId: $targetProfileId})
97
+ WITH contract, target WHERE $targetProfileId <> $senderProfileId AND EXISTS {
98
+ MATCH (contract)-[:CREATED_BY|CAN_WRITE|SHARES_DATA_WITH]->(:Profile {profileId: $senderProfileId})
99
+ } AND CASE WHEN contract.expiresAt IS NULL OR trim(contract.expiresAt) = '' THEN true ELSE datetime(contract.expiresAt) > datetime($now) END
100
+ AND NOT EXISTS {
101
+ MATCH (target)-[:CREATED_BY]->(terms:ConsentFlowTerms)-[:CONSENTS_TO]->(contract)
102
+ WHERE (terms.status = 'live' OR (terms.status = 'stale' AND terms.oneTime = true)) AND
103
+ CASE WHEN terms.expiresAt IS NULL OR trim(terms.expiresAt) = '' THEN true ELSE datetime(terms.expiresAt) > datetime($now) END
104
+ }
105
+ OPTIONAL MATCH (contract)-[existing:REQUESTED_FOR]->(target)
106
+ WITH contract, target, existing WHERE existing IS NULL OR
107
+ (existing.status = 'pending' AND existing.requestId IS NOT NULL AND
108
+ existing.requestedBy = $senderProfileId AND
109
+ coalesce(existing.externalReferenceId, '') = coalesce($externalReferenceId, '') AND
110
+ coalesce(existing.message, '') = coalesce($message, ''))
111
+ MERGE (contract)-[request:REQUESTED_FOR]->(target)
112
+ ON CREATE SET request += {requestId: $requestId, requestedBy: $senderProfileId,
113
+ externalReferenceId: $externalReferenceId, message: $message,
114
+ requestedAt: $now, status: 'pending', readStatus: 'unseen'}
115
+ ${requestEventCypher}
116
+ `);
117
+ const result = await runAudienceMutation(query);
118
+ if (!result.records.length)
119
+ throw new TRPCError({
120
+ code: 'CONFLICT',
121
+ message:
122
+ 'Active consent or a conflicting/terminal request already exists, or the contract changed.',
123
+ });
124
+ await tryDispatchContractEvent(result.records[0]!.get('eventId'));
125
+ return true;
126
+ };
127
+
128
+ /** Target-only denial; cancellation also permits owner/writers and the requesting recipient. */
129
+ export const decideGenericContractRequest = async ({
130
+ contract,
131
+ targetProfileId,
132
+ actorProfileId,
133
+ status,
134
+ domain,
135
+ }: {
136
+ contract: DbContractType;
137
+ targetProfileId: string;
138
+ actorProfileId: string;
139
+ status: 'denied' | 'cancelled';
140
+ domain: string;
141
+ }): Promise<boolean> => {
142
+ const request = await getStoredContractRequest(contract.id, targetProfileId);
143
+ if (!request?.requestId)
144
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract request not found' });
145
+ const isTarget = actorProfileId === targetProfileId;
146
+ const writers = await getWritersForContract(contract);
147
+ const authorized =
148
+ isTarget ||
149
+ (status === 'cancelled' &&
150
+ (writers.some(profile => profile.profileId === actorProfileId) ||
151
+ (request.requestedBy === actorProfileId &&
152
+ (await canReadContractData(contract.id, actorProfileId)))));
153
+ if (!authorized)
154
+ throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You cannot change this request' });
155
+ const result = await runAudienceMutation(
156
+ lockContractAudience(
157
+ new QueryBuilder(
158
+ new BindParam({
159
+ targetProfileId,
160
+ actorProfileId,
161
+ status,
162
+ domain,
163
+ now: new Date().toISOString(),
164
+ eventKind: status === 'denied' ? 'request_denied' : 'request_cancelled',
165
+ eventFrom: actorProfileId,
166
+ })
167
+ ),
168
+ contract.id
169
+ ).raw(`
170
+ MATCH (contract)-[request:REQUESTED_FOR]->(target:Profile {profileId: $targetProfileId})
171
+ WHERE request.requestId IS NOT NULL AND request.status IN ['pending', $status]
172
+ AND ($actorProfileId = $targetProfileId OR ($status = 'cancelled' AND
173
+ (EXISTS { MATCH (contract)-[:CREATED_BY|CAN_WRITE]->(:Profile {profileId: $actorProfileId}) } OR
174
+ (request.requestedBy = $actorProfileId AND EXISTS {
175
+ MATCH (contract)-[:SHARES_DATA_WITH]->(:Profile {profileId: $actorProfileId}) }))))
176
+ SET request.status = $status
177
+ ${requestEventCypher}
178
+ `)
179
+ );
180
+ if (!result.records.length)
181
+ throw new TRPCError({
182
+ code: 'CONFLICT',
183
+ message: 'Request already decided or permission changed',
184
+ });
185
+ await tryDispatchContractEvent(result.records[0]!.get('eventId'));
186
+ return true;
187
+ };
188
+
189
+ export const markGenericContractRequestSeen = async (
190
+ contractId: string,
191
+ targetProfileId: string
192
+ ): Promise<boolean> => {
193
+ const result = await runAudienceMutation(
194
+ lockContractAudience(new QueryBuilder(new BindParam({ targetProfileId })), contractId)
195
+ .raw(`MATCH (contract)-[request:REQUESTED_FOR]->(:Profile {profileId: $targetProfileId})
196
+ WHERE request.requestId IS NOT NULL
197
+ SET request.readStatus = 'seen' RETURN request`)
198
+ );
199
+ if (!result.records.length)
200
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Request not found' });
201
+ return true;
202
+ };
203
+
204
+ /** Legacy request mutations retain their scope behavior; attributed requests require write. */
205
+ export const assertGenericRequestWriteScope = (scope?: string): void => {
206
+ if (!userHasRequiredScopes(scope ?? '', 'contracts:write'))
207
+ throw new TRPCError({
208
+ code: 'UNAUTHORIZED',
209
+ message: 'This operation requires contracts:write scope',
210
+ });
211
+ };
@@ -0,0 +1,114 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { UnsignedVC } from '@learncard/types';
3
+
4
+ import { setCredentialSubjectIds } from './credentialSubject.helpers';
5
+
6
+ const collectSubjectIds = (credential: Record<string, unknown>): string[] => {
7
+ const subject = credential.credentialSubject;
8
+ const subjects = Array.isArray(subject) ? subject : [subject];
9
+
10
+ return subjects.flatMap(currentSubject => {
11
+ if (!currentSubject || typeof currentSubject !== 'object') return [];
12
+
13
+ const subjectRecord = currentSubject as Record<string, unknown>;
14
+ const embedded = subjectRecord.verifiableCredential;
15
+ const embeddedCredentials = Array.isArray(embedded) ? embedded : embedded ? [embedded] : [];
16
+
17
+ return [
18
+ subjectRecord.id as string,
19
+ ...embeddedCredentials.flatMap(nested =>
20
+ collectSubjectIds(nested as Record<string, unknown>)
21
+ ),
22
+ ];
23
+ });
24
+ };
25
+
26
+ describe('setCredentialSubjectIds', () => {
27
+ it('patches every CLR learner subject without rewriting achievement identities', () => {
28
+ const credential = {
29
+ credentialSubject: {
30
+ id: 'did:example:old-root',
31
+ achievement: {
32
+ id: 'https://example.edu/transcript',
33
+ creator: { id: 'https://example.edu' },
34
+ },
35
+ verifiableCredential: [
36
+ {
37
+ credentialSubject: {
38
+ id: 'did:example:old-course',
39
+ achievement: {
40
+ id: 'https://example.edu/courses/imd-120',
41
+ creator: { id: 'https://example.edu/departments/design' },
42
+ },
43
+ verifiableCredential: {
44
+ credentialSubject: { id: 'did:example:old-assessment' },
45
+ },
46
+ },
47
+ },
48
+ ],
49
+ },
50
+ } as unknown as UnsignedVC;
51
+
52
+ setCredentialSubjectIds(credential, 'did:example:learner');
53
+
54
+ expect(collectSubjectIds(credential as unknown as Record<string, unknown>)).toEqual([
55
+ 'did:example:learner',
56
+ 'did:example:learner',
57
+ 'did:example:learner',
58
+ ]);
59
+ expect(credential.credentialSubject).toMatchObject({
60
+ achievement: {
61
+ id: 'https://example.edu/transcript',
62
+ creator: { id: 'https://example.edu' },
63
+ },
64
+ verifiableCredential: [
65
+ {
66
+ credentialSubject: {
67
+ achievement: {
68
+ id: 'https://example.edu/courses/imd-120',
69
+ creator: { id: 'https://example.edu/departments/design' },
70
+ },
71
+ },
72
+ },
73
+ ],
74
+ });
75
+ });
76
+
77
+ it('preserves signed embedded credentials while patching unsigned subjects', () => {
78
+ const credential = {
79
+ credentialSubject: {
80
+ id: 'did:example:old-root',
81
+ verifiableCredential: [
82
+ {
83
+ credentialSubject: { id: 'did:example:signed-subject' },
84
+ proof: { type: 'DataIntegrityProof', proofValue: 'signed-value' },
85
+ },
86
+ {
87
+ credentialSubject: { id: 'did:example:unsigned-subject' },
88
+ },
89
+ ],
90
+ },
91
+ } as unknown as UnsignedVC;
92
+
93
+ setCredentialSubjectIds(credential, 'did:example:learner');
94
+
95
+ const subject = credential.credentialSubject as Record<string, unknown>;
96
+ const embedded = subject.verifiableCredential as Array<Record<string, unknown>>;
97
+
98
+ expect(subject.id).toBe('did:example:learner');
99
+ expect(embedded[0]?.credentialSubject).toEqual({
100
+ id: 'did:example:signed-subject',
101
+ });
102
+ expect(embedded[1]?.credentialSubject).toEqual({
103
+ id: 'did:example:learner',
104
+ });
105
+ });
106
+
107
+ it('creates an outer credential subject when a legacy template omits it', () => {
108
+ const credential = {} as UnsignedVC;
109
+
110
+ setCredentialSubjectIds(credential, 'did:example:learner');
111
+
112
+ expect(credential.credentialSubject).toEqual({ id: 'did:example:learner' });
113
+ });
114
+ });
@@ -0,0 +1,40 @@
1
+ import type { UnsignedVC, VC } from '@learncard/types';
2
+
3
+ const setSubjectIds = (subject: unknown, subjectDid: string): void => {
4
+ if (Array.isArray(subject)) {
5
+ subject.forEach(nestedSubject => setSubjectIds(nestedSubject, subjectDid));
6
+ return;
7
+ }
8
+
9
+ if (!subject || typeof subject !== 'object') return;
10
+
11
+ const mutableSubject = subject as Record<string, unknown>;
12
+ mutableSubject.id = subjectDid;
13
+
14
+ const embeddedCredentials = mutableSubject.verifiableCredential;
15
+ const credentials = Array.isArray(embeddedCredentials)
16
+ ? embeddedCredentials
17
+ : [embeddedCredentials];
18
+
19
+ credentials.forEach(credential => {
20
+ if (!credential || typeof credential !== 'object' || Array.isArray(credential)) return;
21
+
22
+ const credentialRecord = credential as Record<string, unknown>;
23
+ if (credentialRecord.proof !== undefined) return;
24
+
25
+ setSubjectIds(credentialRecord.credentialSubject, subjectDid);
26
+ });
27
+ };
28
+
29
+ /**
30
+ * Sets the recipient DID on the outer credential and unsigned credentials nested in a CLR.
31
+ * Signed embedded credentials are immutable because changing their subject invalidates the proof.
32
+ */
33
+ export const setCredentialSubjectIds = (credential: UnsignedVC | VC, subjectDid: string): void => {
34
+ if (!credential.credentialSubject) {
35
+ credential.credentialSubject = { id: subjectDid };
36
+ return;
37
+ }
38
+
39
+ setSubjectIds(credential.credentialSubject, subjectDid);
40
+ };
@@ -3,7 +3,11 @@ import {
3
3
  LCNNotificationTypeEnumValidator,
4
4
  LCNInboxStatusEnumValidator,
5
5
  VC,
6
+ JWE,
6
7
  UnsignedVC,
8
+ VCValidator,
9
+ JWEValidator,
10
+ InboxCredentialType,
7
11
  } from '@learncard/types';
8
12
  import { ProfileType } from 'types/profile';
9
13
  import { getContactMethodsForProfile } from '@accesslayer/contact-method/read';
@@ -21,6 +25,113 @@ import { getLearnCard, getEmptyLearnCard } from '@helpers/learnCard.helpers';
21
25
  import { logCredentialClaimed, logCredentialFailed } from '@helpers/activity.helpers';
22
26
  import { handleConnectionPromptsForCredentialClaim } from '@helpers/connectionPrompt.helpers';
23
27
  import { decryptInboxCredential } from '@helpers/inbox-encryption.helpers';
28
+ import { setCredentialSubjectIds } from '@helpers/credentialSubject.helpers';
29
+ import { getBitstringStatusListEntries } from '@learncard/helpers';
30
+ import type { IssuedCredential } from 'types/credential';
31
+ import { getBoostByUri } from '@accesslayer/boost/read';
32
+ import { storeCredential } from '@accesslayer/credential/create';
33
+ import { createBoostInstanceOfRelationship } from '@accesslayer/boost/relationships/create';
34
+ import {
35
+ createSentCredentialRelationship,
36
+ createReceivedCredentialRelationship,
37
+ setDefaultClaimedRole,
38
+ } from '@accesslayer/credential/relationships/create';
39
+ import { neogma } from '@instance';
40
+ import { processClaimHooks } from '@helpers/claim-hooks.helpers';
41
+ import { clearDidWebCacheForChildProfileManagers } from '@accesslayer/boost/relationships/update';
42
+
43
+ /** Graph-only completion: a failed write retains pending escrow for the next attempt. */
44
+ const finalizeAcceptedDelivery = async (
45
+ inboxCredential: InboxCredentialType,
46
+ issued: IssuedCredential,
47
+ recipient: ProfileType,
48
+ sender: ProfileType
49
+ ): Promise<boolean> => {
50
+ const boost = inboxCredential.boostUri
51
+ ? await getBoostByUri(inboxCredential.boostUri)
52
+ : undefined;
53
+ const session = neogma.driver.session();
54
+ const transaction = session.beginTransaction({ timeout: 30_000 });
55
+ // Signing and status-index allocation happen before this bounded graph transaction;
56
+ // a retry or lost claim race may leave unused indices, intentionally, rather than
57
+ // hold Neo4j locks across external signing.
58
+ try {
59
+ const finalized = await finalizeAndWipeInboxCredential(
60
+ inboxCredential.id,
61
+ { recipientDid: recipient.did, credential: JWEValidator.parse(issued.credential) },
62
+ {},
63
+ transaction
64
+ );
65
+ if (!finalized) {
66
+ // Another claim or expiry won the lock. It is not a new issuance failure.
67
+ await transaction.rollback();
68
+ return false;
69
+ }
70
+ const instance = await storeCredential(issued, transaction);
71
+ if (boost) await createBoostInstanceOfRelationship(instance, boost, transaction);
72
+ await createSentCredentialRelationship(
73
+ { type: 'profile', profile: sender },
74
+ recipient,
75
+ instance,
76
+ undefined,
77
+ inboxCredential.activityId,
78
+ inboxCredential.integrationId,
79
+ transaction
80
+ );
81
+ await createReceivedCredentialRelationship(
82
+ recipient,
83
+ sender,
84
+ instance,
85
+ undefined,
86
+ transaction
87
+ );
88
+ await processClaimHooks(recipient, instance, transaction);
89
+ await setDefaultClaimedRole(recipient, instance, transaction);
90
+ if (
91
+ !(await createClaimedRelationship(
92
+ recipient.profileId,
93
+ inboxCredential.id,
94
+ 'finalize',
95
+ transaction
96
+ ))
97
+ ) {
98
+ throw new Error('Inbox claim recipient no longer exists');
99
+ }
100
+ await transaction.commit();
101
+ } catch (error) {
102
+ await transaction
103
+ .rollback()
104
+ .catch(rollbackError =>
105
+ console.error(
106
+ 'Failed to roll back inbox finalization',
107
+ inboxCredential.id,
108
+ rollbackError
109
+ )
110
+ );
111
+ throw error;
112
+ } finally {
113
+ // Closing the session cannot turn a committed delivery into a pending failure.
114
+ await session
115
+ .close()
116
+ .catch(error =>
117
+ console.error(
118
+ 'Failed to close inbox finalization session',
119
+ inboxCredential.id,
120
+ error
121
+ )
122
+ );
123
+ }
124
+ if (boost) {
125
+ await clearDidWebCacheForChildProfileManagers(boost.id).catch(error =>
126
+ console.error(
127
+ 'Could not clear did:web cache after inbox claim',
128
+ inboxCredential.id,
129
+ error
130
+ )
131
+ );
132
+ }
133
+ return true;
134
+ };
24
135
 
25
136
  export async function finalizeInboxCredentialsForProfile(
26
137
  profile: ProfileType,
@@ -30,8 +141,8 @@ export async function finalizeInboxCredentialsForProfile(
30
141
  claimed: number;
31
142
  errors: number;
32
143
  guardianPending: number;
33
- verifiableCredentials: VC[];
34
- deliveries: { id: string; credential: VC }[];
144
+ verifiableCredentials: (VC | JWE)[];
145
+ deliveries: { id: string; credential: VC | JWE }[];
35
146
  }> {
36
147
  const contactMethods = await getContactMethodsForProfile(profile.did);
37
148
  const verifiedContacts = contactMethods.filter(cm => cm.isVerified);
@@ -48,8 +159,8 @@ export async function finalizeInboxCredentialsForProfile(
48
159
  lcDid = lc.id.did();
49
160
  } catch {}
50
161
 
51
- const verifiableCredentials: VC[] = [];
52
- const deliveries: { id: string; credential: VC }[] = [];
162
+ const verifiableCredentials: (VC | JWE)[] = [];
163
+ const deliveries: { id: string; credential: VC | JWE }[] = [];
53
164
 
54
165
  for (const cm of verifiedContacts) {
55
166
  const pending = await getAcceptedPendingInboxCredentialsForContactMethodId(cm.id);
@@ -79,7 +190,8 @@ export async function finalizeInboxCredentialsForProfile(
79
190
  }
80
191
 
81
192
  try {
82
- let finalCredential: VC;
193
+ let finalCredential: VC | JWE;
194
+ let issued: IssuedCredential | undefined;
83
195
  const credentialPayload = inboxCredential.refreshId
84
196
  ? undefined
85
197
  : await decryptInboxCredential(inboxCredential.credential);
@@ -102,29 +214,16 @@ export async function finalizeInboxCredentialsForProfile(
102
214
  if (!endpoint || !name)
103
215
  throw new Error('Inbox credential missing signing authority info');
104
216
 
105
- const issuerProfile = await getProfileByDid(inboxCredential.issuerDid);
106
- if (!issuerProfile) throw new Error('Issuer profile not found');
217
+ if (!senderProfile) throw new Error('Issuer profile not found');
107
218
 
108
219
  const signingAuthorityForUser = await getSigningAuthorityForUserByName(
109
- issuerProfile,
220
+ senderProfile,
110
221
  endpoint,
111
222
  name
112
223
  );
113
224
  if (!signingAuthorityForUser) throw new Error('Signing authority not found');
114
225
 
115
- // Set subject DID to the authenticated user's DID
116
- if (Array.isArray(unsignedCredential.credentialSubject)) {
117
- unsignedCredential.credentialSubject =
118
- unsignedCredential.credentialSubject.map(sub => ({
119
- ...sub,
120
- id: (sub as any).did || (sub as any).id || profile.did,
121
- }));
122
- } else {
123
- (unsignedCredential.credentialSubject as any).id =
124
- (unsignedCredential as any).credentialSubject?.did ||
125
- (unsignedCredential as any).credentialSubject?.id ||
126
- profile.did;
127
- }
226
+ setCredentialSubjectIds(unsignedCredential, profile.did);
128
227
 
129
228
  // Set issuer from signing authority
130
229
  unsignedCredential.issuer = signingAuthorityForUser.relationship.did;
@@ -136,36 +235,56 @@ export async function finalizeInboxCredentialsForProfile(
136
235
  ? getAppDidWeb(domain, listingSlug)
137
236
  : undefined;
138
237
 
139
- finalCredential = (
140
- await issueCredentialWithSigningAuthority(
141
- { type: 'profile', profile: issuerProfile },
142
- unsignedCredential,
143
- signingAuthorityForUser,
144
- domain,
145
- false,
146
- ownerDidOverride
147
- )
148
- ).credential as VC;
238
+ issued = await issueCredentialWithSigningAuthority(
239
+ { type: 'profile', profile: senderProfile },
240
+ unsignedCredential,
241
+ signingAuthorityForUser,
242
+ domain,
243
+ undefined,
244
+ ownerDidOverride,
245
+ undefined,
246
+ // Preserve issuer did:key access alongside the managed/app did:web reader.
247
+ [senderProfile.did]
248
+ );
249
+ finalCredential = issued.credential;
149
250
  } else {
150
251
  finalCredential = JSON.parse(credentialPayload!) as VC;
151
252
  }
152
253
 
153
254
  if (!inboxCredential.refreshId) {
154
- // The seeded encryption plugin adds the service DID; use explicit recipients.
155
- const learnCard = await getEmptyLearnCard();
156
- const recoveryCredential = await learnCard.invoke.createDagJwe(
157
- finalCredential,
158
- [profile.did]
159
- );
160
- const finalized = await finalizeAndWipeInboxCredential(inboxCredential.id, {
161
- recipientDid: profile.did,
162
- credential: recoveryCredential,
163
- });
164
- if (!finalized) throw new Error('Inbox credential is no longer pending');
255
+ if (!senderProfile) throw new Error('Issuer profile not found');
256
+ if (!issued) {
257
+ const signedCredential = VCValidator.parse(finalCredential);
258
+ const learnCard = await getEmptyLearnCard();
259
+ issued = {
260
+ kind: 'issued-credential',
261
+ credential: await learnCard.invoke.createDagJwe(signedCredential, [
262
+ profile.did,
263
+ inboxCredential.issuerDid,
264
+ ]),
265
+ statusEntries: getBitstringStatusListEntries(signedCredential),
266
+ };
267
+ }
268
+ if (
269
+ !(await finalizeAcceptedDelivery(
270
+ inboxCredential,
271
+ issued,
272
+ profile,
273
+ senderProfile
274
+ ))
275
+ )
276
+ continue;
277
+ finalCredential = issued.credential;
165
278
  }
166
279
 
167
- // Only write a claim audit edge once the record is actually finalized.
168
- await createClaimedRelationship(profile.profileId, inboxCredential.id, 'finalize');
280
+ // Managed refresh retains its separate lifecycle; ordinary claims audit in the transaction.
281
+ if (inboxCredential.refreshId) {
282
+ await createClaimedRelationship(
283
+ profile.profileId,
284
+ inboxCredential.id,
285
+ 'finalize'
286
+ );
287
+ }
169
288
 
170
289
  if (
171
290
  senderProfile &&