@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
@@ -83,18 +83,23 @@ describe('credential refresh configuration', () => {
83
83
  }
84
84
  );
85
85
 
86
- it.each(['serverless.yml', 'serverless-local.yml'])(
87
- 'passes all refresh settings into functions in %s',
88
- manifestName => {
89
- const manifestPath = fileURLToPath(new URL(`../../${manifestName}`, import.meta.url));
90
- const manifest = parse(readFileSync(manifestPath, 'utf8'));
91
-
92
- expect(manifest.provider.environment).toMatchObject({
93
- CREDENTIAL_REFRESH_ENABLED: '${env:CREDENTIAL_REFRESH_ENABLED, "false"}',
94
- CREDENTIAL_REFRESH_DIGEST_SECRET: '${env:CREDENTIAL_REFRESH_DIGEST_SECRET, ""}',
95
- CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS:
96
- '${env:CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS, "24"}',
97
- });
86
+ it('resolves the deployed function environment through the shared provider file', () => {
87
+ const manifestPath = fileURLToPath(new URL('../../serverless.yml', import.meta.url));
88
+ const manifest = parse(readFileSync(manifestPath, 'utf8'));
89
+
90
+ expect(manifest.provider.environment).toBe(
91
+ '${file(./serverless.function-env.cjs):provider}'
92
+ );
93
+ });
94
+
95
+ it.each(['config.dev.json', 'config.production.json'])(
96
+ 'delivers non-secret refresh settings through checked-in stage config in %s',
97
+ stageFile => {
98
+ const configPath = fileURLToPath(new URL(`../../config/${stageFile}`, import.meta.url));
99
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
100
+
101
+ expect(config.CREDENTIAL_REFRESH_ENABLED).toBeDefined();
102
+ expect(config.CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS).toBeDefined();
98
103
  }
99
104
  );
100
105
  });
@@ -27,6 +27,7 @@ export const brainServiceEnvironmentShape = {
27
27
  CLIENT_APP_DOMAIN_NAME: optionalEnvironmentString,
28
28
  CLIENT_APP_PORT: optionalEnvironmentPort,
29
29
  LOGIN_PROVIDER_DID: optionalEnvironmentString,
30
+ SIGNING_AUTHORITY_ENDPOINT: optionalEnvironmentUrl,
30
31
  APP_STORE_ADMIN_PROFILE_IDS: optionalEnvironmentString,
31
32
  SKILL_FRAMEWORK_ADMIN_PROFILE_IDS: optionalEnvironmentString,
32
33
  SKILL_FRAMEWORK_SEED_OWNER_PROFILE_ID: optionalEnvironmentString,
@@ -77,6 +78,7 @@ export const brainServiceEnvironmentShape = {
77
78
  SKILL_SEMANTIC_SEARCH_RATE_LIMIT_PER_MIN: optionalEnvironmentPort,
78
79
  BITSTRING_STATUS_LIST_SIZE: optionalEnvironmentPort,
79
80
  CREDENTIAL_REFRESH_DIGEST_SECRET: optionalEnvironmentString,
81
+ SHARE_LINK_REQUEST_HASH_SECRET: optionalEnvironmentString,
80
82
  CREDENTIAL_REFRESH_ENABLED: optionalEnvironmentBoolean.default(false),
81
83
  CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS: credentialRefreshNotificationWindowHours,
82
84
  IS_OFFLINE: optionalEnvironmentBoolean.default(false),
@@ -211,6 +213,19 @@ export const getCredentialRefreshRuntimeEnvironment = (): CredentialRefreshRunti
211
213
  examplePath: 'services/learn-card-network/brain-service/.env.example',
212
214
  });
213
215
 
216
+ const shareLinkRequestHashRuntimeEnvironmentSchema = z.object({
217
+ NODE_ENV: brainServiceEnvironmentShape.NODE_ENV,
218
+ SHARE_LINK_REQUEST_HASH_SECRET: brainServiceEnvironmentShape.SHARE_LINK_REQUEST_HASH_SECRET,
219
+ });
220
+
221
+ /** Reads the dedicated fingerprint secret at call time for enabled share-link APIs. */
222
+ export const getShareLinkRequestHashRuntimeEnvironment = () =>
223
+ parseEnvironment(shareLinkRequestHashRuntimeEnvironmentSchema, process.env, {
224
+ project: 'brain-service share links',
225
+ source: 'process environment',
226
+ examplePath: 'services/learn-card-network/brain-service/.env.example',
227
+ });
228
+
214
229
  const inboxBatchRuntimeEnvironmentSchema = z.object({
215
230
  INBOX_QUEUE_URL: brainServiceEnvironmentShape.INBOX_QUEUE_URL,
216
231
  INBOX_DEAD_LETTER_QUEUE_URL: brainServiceEnvironmentShape.INBOX_DEAD_LETTER_QUEUE_URL,
@@ -0,0 +1,105 @@
1
+ import { createRequire } from 'node:module';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const functions: {
6
+ provider: (input?: {
7
+ options?: Record<string, unknown>;
8
+ env?: NodeJS.ProcessEnv;
9
+ }) => Record<string, unknown>;
10
+ } = require('../../serverless.function-env.cjs');
11
+
12
+ const INFRA_KEYS = {
13
+ LAMBDA_STAGE: 'dev',
14
+ PORT: '3000',
15
+ REDIS_HOST: { 'Fn::GetAtt': ['ElasticCacheCluster', 'RedisEndpoint.Address'] },
16
+ REDIS_PORT: { 'Fn::GetAtt': ['ElasticCacheCluster', 'RedisEndpoint.Port'] },
17
+ NOTIFICATIONS_QUEUE_URL: '${construct:notifications-queue.queueUrl}',
18
+ INBOX_QUEUE_URL: '${construct:inbox-queue.queueUrl}',
19
+ };
20
+
21
+ beforeEach(() => {
22
+ vi.stubEnv('RUNTIME_SECRETS_ID', '');
23
+ vi.stubEnv('GIT_SHA', '');
24
+ vi.stubEnv('SEED', 'seed');
25
+ vi.stubEnv('NEO4J_PASSWORD', 'neo4j-password');
26
+ vi.stubEnv('SHARE_LINK_REQUEST_HASH_SECRET', 'hash-secret');
27
+ vi.stubEnv('SENTRY_AUTH_TOKEN', 'build-token');
28
+ vi.stubEnv('TRUSTED_ISSUERS_WHITELIST', 'did:web:issuer');
29
+ });
30
+ afterEach(() => vi.unstubAllEnvs());
31
+
32
+ describe('brain-service serverless provider environment', () => {
33
+ it('always emits Redis and queue infrastructure as unresolved references', () => {
34
+ expect(functions.provider({ options: { stage: 'dev' } })).toMatchObject(INFRA_KEYS);
35
+ });
36
+
37
+ it('derives LAMBDA_STAGE and PORT from the CLI options, never a stage passthrough', () => {
38
+ const result = functions.provider({ options: { stage: 'production', httpPort: 4000 } });
39
+ expect(result.LAMBDA_STAGE).toBe('production');
40
+ expect(result.PORT).toBe('4000');
41
+ });
42
+
43
+ it('defaults LAMBDA_STAGE and PORT when no CLI options are supplied', () => {
44
+ const result = functions.provider();
45
+ expect(result.LAMBDA_STAGE).toBe('dev');
46
+ expect(result.PORT).toBe('3000');
47
+ });
48
+
49
+ it('emits GIT_SHA only when it is set to a non-empty value', () => {
50
+ expect(functions.provider()).not.toHaveProperty('GIT_SHA');
51
+ vi.stubEnv('GIT_SHA', 'deadbeef');
52
+ expect(functions.provider()).toMatchObject({ GIT_SHA: 'deadbeef' });
53
+ });
54
+
55
+ it('omits every fallback credential and the pointer stays set in bundle mode', () => {
56
+ vi.stubEnv('RUNTIME_SECRETS_ID', 'brain-service/dev/runtime-secrets');
57
+ const result = functions.provider({ options: { stage: 'dev' } });
58
+ expect(result).toMatchObject({
59
+ ...INFRA_KEYS,
60
+ RUNTIME_SECRETS_ID: 'brain-service/dev/runtime-secrets',
61
+ });
62
+ expect(result).not.toHaveProperty('SEED');
63
+ expect(result).not.toHaveProperty('NEO4J_PASSWORD');
64
+ expect(result).not.toHaveProperty('SHARE_LINK_REQUEST_HASH_SECRET');
65
+ });
66
+
67
+ it('retains only the approved fallback credentials when no bundle is configured', () => {
68
+ const result = functions.provider();
69
+ expect(result.SEED).toBe('seed');
70
+ expect(result.NEO4J_PASSWORD).toBe('neo4j-password');
71
+ expect(result.SHARE_LINK_REQUEST_HASH_SECRET).toBe('hash-secret');
72
+ expect(result.RUNTIME_SECRETS_ID).toBeUndefined();
73
+ });
74
+
75
+ it('never forwards Sentry build credentials or non-secret allowlists as fallbacks', () => {
76
+ const result = functions.provider();
77
+ expect(result).not.toHaveProperty('SENTRY_AUTH_TOKEN');
78
+ expect(result).not.toHaveProperty('SENTRY_ORG');
79
+ expect(result).not.toHaveProperty('SENTRY_PROJECT');
80
+ expect(result).not.toHaveProperty('TRUSTED_ISSUERS_WHITELIST');
81
+ expect(result).not.toHaveProperty('TRUSTED_BRAIN_SERVICES');
82
+ expect(result).not.toHaveProperty('BRAIN_SERVICE_REGISTRY_URL');
83
+ expect(result).not.toHaveProperty('DCC_KNOWN_REGISTRIES_URL');
84
+ });
85
+
86
+ it('resolves the provider through Serverless v3, preserving nested references in both modes', async () => {
87
+ const source = require('serverless/lib/configuration/variables/sources/file');
88
+ for (const bundle of ['', 'bundle']) {
89
+ vi.stubEnv('RUNTIME_SECRETS_ID', bundle);
90
+ const result = await source.resolve({
91
+ serviceDir: new URL('../../', import.meta.url).pathname,
92
+ params: [new URL('../../serverless.function-env.cjs', import.meta.url).pathname],
93
+ address: 'provider',
94
+ options: { stage: 'dev' },
95
+ resolveConfigurationProperty: vi.fn(),
96
+ resolveVariable: vi.fn(),
97
+ });
98
+ expect(result.value).toEqual(functions.provider({ options: { stage: 'dev' } }));
99
+ expect(result.value.NOTIFICATIONS_QUEUE_URL).toBe(
100
+ '${construct:notifications-queue.queueUrl}'
101
+ );
102
+ expect(result.value.INBOX_QUEUE_URL).toBe('${construct:inbox-queue.queueUrl}');
103
+ }
104
+ });
105
+ });
@@ -0,0 +1,25 @@
1
+ import { afterEach, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('dotenv/config', () => {
4
+ vi.stubEnv('SENTRY_ENV', 'self-hosted');
5
+ return {};
6
+ });
7
+
8
+ afterEach(() => vi.unstubAllEnvs());
9
+
10
+ it('loads local env before applying statically imported stage defaults', async () => {
11
+ const { applyBrainServiceStageConfig, stages } = await import('./stageConfig');
12
+ for (const key of Object.keys(stages.dev)) {
13
+ if (key !== 'SENTRY_ENV') vi.stubEnv(key, '');
14
+ }
15
+ applyBrainServiceStageConfig('dev');
16
+ expect(process.env.SENTRY_ENV).toBe('self-hosted');
17
+ expect(process.env.DOMAIN_NAME).toBe(stages.dev.DOMAIN_NAME);
18
+ });
19
+
20
+ it('does not select a deployed stage for an unknown local stage', async () => {
21
+ const { applyBrainServiceStageConfig } = await import('./stageConfig');
22
+ vi.stubEnv('DOMAIN_NAME', '');
23
+ applyBrainServiceStageConfig('unregistered-self-hosted-stage');
24
+ expect(process.env.DOMAIN_NAME).toBe('');
25
+ });
@@ -0,0 +1,24 @@
1
+ import 'dotenv/config';
2
+ import { applyStageConfig } from '@learncard/service-config';
3
+
4
+ import baseConfig from '../../config/config.json';
5
+ import devConfig from '../../config/config.dev.json';
6
+ import productionConfig from '../../config/config.production.json';
7
+ import scoutsDevConfig from '../../config/config.scouts.dev.json';
8
+ import scoutsProductionConfig from '../../config/config.scouts.production.json';
9
+
10
+ export const base: Record<string, string> = baseConfig;
11
+
12
+ export const stages = {
13
+ dev: devConfig,
14
+ production: productionConfig,
15
+ // ScoutPass deploys the same services and stage names; CONFIG_TENANT=scouts selects these.
16
+ 'scouts.dev': scoutsDevConfig,
17
+ 'scouts.production': scoutsProductionConfig,
18
+ } satisfies Record<string, Record<string, string>>;
19
+
20
+ // Must run before any module that reads configuration on import (e.g. @environment):
21
+ // merges base + the selected stage into process.env; explicit non-empty env still wins.
22
+ export const applyBrainServiceStageConfig = (stage: string | undefined): void => {
23
+ applyStageConfig({ base, stages, stage });
24
+ };
@@ -45,3 +45,8 @@ export const AUTH_GRANT_CREDENTIAL_MANAGEMENT_SCOPE = 'credential:* presentation
45
45
  export const AUTH_GRANT_CONTRACTS_SCOPE = 'contracts:*';
46
46
  export const AUTH_GRANT_DID_METADATA_SCOPE = 'didMetadata:*';
47
47
  export const AUTH_GRANT_AUTH_GRANTS_SCOPE = 'authGrants:*';
48
+
49
+ // LC-2187 owner share-link APIs. Least privilege: owner metadata/recovery reads
50
+ // use `shareLinks:read`; create/update/revoke/retry use `shareLinks:write`.
51
+ export const AUTH_GRANT_SHARE_LINKS_READ_SCOPE = 'shareLinks:read';
52
+ export const AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE = 'shareLinks:write';
@@ -1,3 +1,6 @@
1
+ import './config/applyDockerStageConfig';
2
+
3
+ import { dispatchContractEvents } from '@helpers/contract-events.helpers';
1
4
  import { configureInboxBatchBodyLimit } from '@helpers/inbox-batch-http.helpers';
2
5
  import { environment } from '@environment';
3
6
  import Fastify from 'fastify';
@@ -15,6 +18,8 @@ import {
15
18
 
16
19
  import { neogma } from '@instance';
17
20
  import { appRouter, type AppRouter, createContext } from './app';
21
+ import { publicShareLinkCacheControlHeaders } from './routes';
22
+ import { registerPublicShareLinkNoStore } from './public-share-link-http';
18
23
  import { openApiDocument } from './openapi';
19
24
  import { inboxBatchResponseMeta } from './helpers/inbox-batch-http.helpers';
20
25
  import { didFastifyPlugin } from './dids';
@@ -23,10 +28,45 @@ import { statusListsFastifyPlugin } from './status-lists';
23
28
  import { credentialRefreshFastifyPlugin } from './credential-refresh';
24
29
  import { deliverQueuedNotification } from '@helpers/notificationQueue.helpers';
25
30
  import { startSkillEmbeddingBackfill } from '@helpers/skill-embedding.helpers';
31
+ import {
32
+ createConsoleMaintenanceLogger,
33
+ createShareLinkMaintenanceRuntime,
34
+ createShareLinkMaintenanceScheduler,
35
+ type ShareLinkMaintenanceScheduler,
36
+ } from '@helpers/share-link-maintenance';
26
37
  import { maybeAutoSeedSkillFrameworks } from './seed/seedSkillFrameworks';
27
38
 
28
39
  const server = Fastify({ routerOptions: { maxParamLength: 5000 } });
29
40
 
41
+ /**
42
+ * The scheduled maintenance runtime is created only after the server is ready.
43
+ * When its config is disabled/invalid it is inert, so no interval and no
44
+ * graph/remote/signing dependency is initialized at all.
45
+ */
46
+ let shareLinkMaintenanceScheduler: ShareLinkMaintenanceScheduler | null = null;
47
+ let contractEventTimer: ReturnType<typeof setTimeout> | null = null;
48
+ let contractEventPass: Promise<unknown> | null = null;
49
+ let contractEventsStopped = false;
50
+
51
+ const runContractEventPass = (): void => {
52
+ if (contractEventsStopped) return;
53
+ contractEventPass = dispatchContractEvents()
54
+ .catch(() => {
55
+ console.warn('contract_events: recovery_pending');
56
+ })
57
+ .finally(() => {
58
+ contractEventPass = null;
59
+ if (!contractEventsStopped)
60
+ contractEventTimer = setTimeout(runContractEventPass, 60_000);
61
+ });
62
+ };
63
+
64
+ server.addHook('onClose', async () => {
65
+ contractEventsStopped = true;
66
+ if (contractEventTimer) clearTimeout(contractEventTimer);
67
+ await contractEventPass;
68
+ await shareLinkMaintenanceScheduler?.stop();
69
+ });
30
70
  // Register before either OpenAPI or tRPC registers its wildcard route. The hook raises the
31
71
  // parser ceiling for those shared routes, then narrows it back to 4 MiB only for batch issuance.
32
72
  // Registering this later would leave the already-created adapter routes at Fastify's 1 MiB limit.
@@ -78,6 +118,9 @@ server.register(fastifyTRPCPlugin, {
78
118
  allowMethodOverride: true,
79
119
  router: appRouter,
80
120
  createContext,
121
+ responseMeta: ({ paths }) => ({
122
+ headers: publicShareLinkCacheControlHeaders(paths),
123
+ }),
81
124
  onError({ path, error }) {
82
125
  // report to error monitoring
83
126
  console.error(`Error in tRPC handler on path '${path}':`, error);
@@ -96,6 +139,13 @@ server.register(fastifyTRPCOpenApiPlugin, {
96
139
  },
97
140
  } satisfies CreateOpenApiFastifyPluginOptions<AppRouter>);
98
141
 
142
+ /**
143
+ * LC-2187: the Fastify OpenAPI adapter has no `responseMeta` hook, so the
144
+ * no-store contract for the anonymous public share-link routes is applied at
145
+ * the adapter boundary itself. It never depends on a cache layer above.
146
+ */
147
+ registerPublicShareLinkNoStore(server);
148
+
99
149
  server.get('/docs/openapi.json', () => openApiDocument);
100
150
 
101
151
  const SCALAR_HTML = `<!DOCTYPE html>
@@ -138,12 +188,43 @@ server.register(credentialRefreshFastifyPlugin);
138
188
  } catch (error) {
139
189
  console.error('Skill embedding backfill failed', error);
140
190
  }
191
+
192
+ contractEventTimer = setTimeout(runContractEventPass, 60_000);
193
+
194
+ // Explicit startup after readiness. Disabled/invalid config stays inert.
195
+ const maintenanceRuntime = createShareLinkMaintenanceRuntime();
196
+
197
+ if (maintenanceRuntime.resolution.status === 'enabled') {
198
+ shareLinkMaintenanceScheduler = createShareLinkMaintenanceScheduler({
199
+ runOnce: () => maintenanceRuntime.runOnce(),
200
+ intervalMs: maintenanceRuntime.resolution.config.intervalMs,
201
+ logger: createConsoleMaintenanceLogger(),
202
+ });
203
+ shareLinkMaintenanceScheduler.start();
204
+ }
141
205
  } catch (err) {
142
206
  console.error(err);
143
207
  process.exit(1);
144
208
  }
145
209
  })();
146
210
 
211
+ const shutdown = async (signal: NodeJS.Signals): Promise<void> => {
212
+ console.log(`Received ${signal}; closing server and draining maintenance`);
213
+ try {
214
+ await server.close();
215
+ } catch (error) {
216
+ console.error('Server shutdown failed', error);
217
+ }
218
+ process.exit(0);
219
+ };
220
+
221
+ process.once('SIGTERM', () => {
222
+ void shutdown('SIGTERM');
223
+ });
224
+ process.once('SIGINT', () => {
225
+ void shutdown('SIGINT');
226
+ });
227
+
147
228
  const pollUrl = environment.NOTIFICATIONS_QUEUE_POLL_URL;
148
229
 
149
230
  if (pollUrl) {
@@ -0,0 +1,185 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import { canonicalConsentScopeString, canonicalJsonString } from '@learncard/partner-connect-core';
4
+ import type { NormalizedConsentScopes } from '@learncard/partner-connect-core';
5
+ import type { AppManifest, AppManifestDiff, AppManifestTemplateRef } from '@learncard/types';
6
+
7
+ const sortStrings = <T extends string>(values: T[]): T[] =>
8
+ Array.from(new Set(values)).sort() as T[];
9
+
10
+ const normalizeTemplateRecord = (template: AppManifest['templates'][number]) => ({
11
+ alias: template.alias,
12
+ template: template.template,
13
+ version: template.version,
14
+ });
15
+
16
+ const normalizeConsentRecord = (
17
+ record: AppManifest['consentRequests'][number]
18
+ ): { scopes: NormalizedConsentScopes; reason?: string } => ({
19
+ scopes: {
20
+ read: {
21
+ credentialCategories: sortStrings(record.scopes.read.credentialCategories),
22
+ personalFields: sortStrings(record.scopes.read.personalFields),
23
+ },
24
+ write: {
25
+ credentialCategories: sortStrings(record.scopes.write.credentialCategories),
26
+ },
27
+ },
28
+ ...(record.reason ? { reason: record.reason } : {}),
29
+ });
30
+
31
+ const normalizeManifestForHash = (manifest: AppManifest): Record<string, unknown> => ({
32
+ manifestVersion: manifest.manifestVersion,
33
+ appUrl: manifest.appUrl,
34
+ ...(manifest.suggestedName ? { suggestedName: manifest.suggestedName } : {}),
35
+ ...(manifest.suggestedIconUrl ? { suggestedIconUrl: manifest.suggestedIconUrl } : {}),
36
+ permissions: sortStrings(manifest.permissions),
37
+ templates: [...manifest.templates]
38
+ .map(normalizeTemplateRecord)
39
+ .sort((a, b) => a.alias.localeCompare(b.alias) || a.version - b.version),
40
+ consentRequests: [...manifest.consentRequests]
41
+ .map(normalizeConsentRecord)
42
+ .sort((a, b) =>
43
+ canonicalConsentScopeString(a.scopes).localeCompare(
44
+ canonicalConsentScopeString(b.scopes)
45
+ )
46
+ ),
47
+ featuresLaunched: sortStrings(manifest.featuresLaunched),
48
+ counterKeys: sortStrings(manifest.counterKeys),
49
+ usedLearnerContext: manifest.usedLearnerContext,
50
+ usedNotifications: manifest.usedNotifications,
51
+ });
52
+
53
+ const buildStringDiff = (current: string[], next: string[]) => {
54
+ const currentSet = new Set(current);
55
+ const nextSet = new Set(next);
56
+
57
+ return {
58
+ added: sortStrings(next.filter(value => !currentSet.has(value))),
59
+ removed: sortStrings(current.filter(value => !nextSet.has(value))),
60
+ changed: [] as string[],
61
+ };
62
+ };
63
+
64
+ const templateSignature = (template: AppManifest['templates'][number]): string => {
65
+ return canonicalJsonString({ template: template.template, version: template.version });
66
+ };
67
+
68
+ const toTemplateRef = (template: AppManifest['templates'][number]): AppManifestTemplateRef => ({
69
+ alias: template.alias,
70
+ version: template.version,
71
+ });
72
+
73
+ const getConsentExpansionSurface = (
74
+ manifest: AppManifest
75
+ ): {
76
+ readCredentialCategories: Set<string>;
77
+ readPersonalFields: Set<string>;
78
+ writeCredentialCategories: Set<string>;
79
+ } => {
80
+ const readCredentialCategories = new Set<string>();
81
+ const readPersonalFields = new Set<string>();
82
+ const writeCredentialCategories = new Set<string>();
83
+
84
+ for (const request of manifest.consentRequests) {
85
+ request.scopes.read.credentialCategories.forEach(category =>
86
+ readCredentialCategories.add(category)
87
+ );
88
+ request.scopes.read.personalFields.forEach(field => readPersonalFields.add(field));
89
+ request.scopes.write.credentialCategories.forEach(category =>
90
+ writeCredentialCategories.add(category)
91
+ );
92
+ }
93
+
94
+ return {
95
+ readCredentialCategories,
96
+ readPersonalFields,
97
+ writeCredentialCategories,
98
+ };
99
+ };
100
+
101
+ const isConsentExpanded = (current: AppManifest, next: AppManifest): boolean => {
102
+ const currentSurface = getConsentExpansionSurface(current);
103
+ const nextSurface = getConsentExpansionSurface(next);
104
+
105
+ return (
106
+ [...nextSurface.readCredentialCategories].some(
107
+ category => !currentSurface.readCredentialCategories.has(category)
108
+ ) ||
109
+ [...nextSurface.readPersonalFields].some(
110
+ field => !currentSurface.readPersonalFields.has(field)
111
+ ) ||
112
+ [...nextSurface.writeCredentialCategories].some(
113
+ category => !currentSurface.writeCredentialCategories.has(category)
114
+ )
115
+ );
116
+ };
117
+
118
+ export const canonicalizeManifest = (manifest: AppManifest): string => {
119
+ return canonicalJsonString(normalizeManifestForHash(manifest));
120
+ };
121
+
122
+ export const hashManifest = (manifest: AppManifest): string => {
123
+ return createHash('sha256').update(canonicalizeManifest(manifest)).digest('hex');
124
+ };
125
+
126
+ export const diffManifests = (current: AppManifest, next: AppManifest): AppManifestDiff => {
127
+ const currentTemplates = new Map(current.templates.map(template => [template.alias, template]));
128
+ const nextTemplates = new Map(next.templates.map(template => [template.alias, template]));
129
+
130
+ const addedTemplates: AppManifestTemplateRef[] = [];
131
+ const removedTemplates: AppManifestTemplateRef[] = [];
132
+ const changedTemplates: AppManifestDiff['templates']['changed'] = [];
133
+
134
+ for (const [alias, template] of nextTemplates.entries()) {
135
+ const existing = currentTemplates.get(alias);
136
+
137
+ if (!existing) {
138
+ addedTemplates.push(toTemplateRef(template));
139
+ continue;
140
+ }
141
+
142
+ if (templateSignature(existing) !== templateSignature(template)) {
143
+ changedTemplates.push({
144
+ alias,
145
+ fromVersion: existing.version,
146
+ toVersion: template.version,
147
+ });
148
+ }
149
+ }
150
+
151
+ for (const [alias, template] of currentTemplates.entries()) {
152
+ if (!nextTemplates.has(alias)) {
153
+ removedTemplates.push(toTemplateRef(template));
154
+ }
155
+ }
156
+
157
+ const permissionDiff = buildStringDiff(current.permissions, next.permissions);
158
+ const consentScopeDiff = buildStringDiff(
159
+ current.consentRequests.map(record =>
160
+ canonicalConsentScopeString(normalizeConsentRecord(record).scopes)
161
+ ),
162
+ next.consentRequests.map(record =>
163
+ canonicalConsentScopeString(normalizeConsentRecord(record).scopes)
164
+ )
165
+ );
166
+
167
+ return {
168
+ permissions: permissionDiff,
169
+ templates: {
170
+ added: addedTemplates.sort(
171
+ (a, b) => a.alias.localeCompare(b.alias) || a.version - b.version
172
+ ),
173
+ removed: removedTemplates.sort(
174
+ (a, b) => a.alias.localeCompare(b.alias) || a.version - b.version
175
+ ),
176
+ changed: changedTemplates.sort(
177
+ (a, b) => a.alias.localeCompare(b.alias) || a.toVersion - b.toVersion
178
+ ),
179
+ },
180
+ consentScopes: consentScopeDiff,
181
+ featurePaths: buildStringDiff(current.featuresLaunched, next.featuresLaunched),
182
+ counterKeys: buildStringDiff(current.counterKeys, next.counterKeys),
183
+ requiresReview: permissionDiff.added.length > 0 || isConsentExpanded(current, next),
184
+ };
185
+ };
@@ -22,6 +22,7 @@ import {
22
22
  parseRenderedTemplate,
23
23
  shouldAutoAppendTemplateEvidence,
24
24
  } from '@helpers/template.helpers';
25
+ import { setCredentialSubjectIds } from '@helpers/credentialSubject.helpers';
25
26
 
26
27
  import { getBoostOwner } from '@accesslayer/boost/relationships/read';
27
28
  import { BoostInstance } from '@models';
@@ -95,15 +96,7 @@ export const convertCredentialToBoostTemplateJSON = (
95
96
  delete template.proof;
96
97
  template.issuer = defaultIssuerDid ?? 'did:example:123';
97
98
 
98
- if (Array.isArray(template.credentialSubject)) {
99
- template.credentialSubject = template.credentialSubject.map(subject => {
100
- subject.id = 'did:example:123';
101
-
102
- return subject;
103
- });
104
- } else {
105
- template.credentialSubject.id = 'did:example:123';
106
- }
99
+ setCredentialSubjectIds(template, 'did:example:123');
107
100
  return JSON.stringify(template);
108
101
  };
109
102
 
@@ -262,14 +255,7 @@ export const issueClaimLinkBoost = async (
262
255
 
263
256
  boostCredential.issuer = signingAuthorityForUser.relationship.did;
264
257
 
265
- if (Array.isArray(boostCredential.credentialSubject)) {
266
- boostCredential.credentialSubject = boostCredential.credentialSubject.map(subject => ({
267
- ...subject,
268
- id: getDidWeb(domain, to.profileId),
269
- }));
270
- } else {
271
- boostCredential.credentialSubject.id = getDidWeb(domain, to.profileId);
272
- }
258
+ setCredentialSubjectIds(boostCredential, getDidWeb(domain, to.profileId));
273
259
 
274
260
  // Embed the boostURI into the boost credential for verification purposes.
275
261
  if (boostCredential.type.includes('BoostCredential')) {
@@ -362,20 +348,7 @@ export const prepareCredentialFromBoost = async (
362
348
  credential.issuer = issuerDid;
363
349
  }
364
350
 
365
- // Set recipient DID in credentialSubject if provided
366
- if (recipientDid) {
367
- if (Array.isArray(credential.credentialSubject)) {
368
- credential.credentialSubject = credential.credentialSubject.map(subject => ({
369
- ...subject,
370
- id: recipientDid,
371
- }));
372
- } else {
373
- credential.credentialSubject = {
374
- ...(credential.credentialSubject || {}),
375
- id: recipientDid,
376
- };
377
- }
378
- }
351
+ if (recipientDid) setCredentialSubjectIds(credential, recipientDid);
379
352
 
380
353
  // Embed boostId for BoostCredential types
381
354
  if (credential?.type?.includes('BoostCredential')) {