@learncard/network-brain-service 3.19.0 → 3.19.1

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 (301) hide show
  1. package/.env.example +34 -2
  2. package/CHANGELOG.md +38 -0
  3. package/README.md +42 -0
  4. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  5. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  6. package/dist/accesslayer/presentation/read.d.ts.map +1 -1
  7. package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
  8. package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
  9. package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
  10. package/dist/accesslayer/share-link/abandon.d.ts +3 -0
  11. package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
  12. package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
  13. package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
  14. package/dist/accesslayer/share-link/errors.d.ts +8 -0
  15. package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
  16. package/dist/accesslayer/share-link/finalize.d.ts +3 -0
  17. package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
  18. package/dist/accesslayer/share-link/helpers.d.ts +56 -0
  19. package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
  20. package/dist/accesslayer/share-link/index.d.ts +12 -0
  21. package/dist/accesslayer/share-link/index.d.ts.map +1 -0
  22. package/dist/accesslayer/share-link/list.d.ts +5 -0
  23. package/dist/accesslayer/share-link/list.d.ts.map +1 -0
  24. package/dist/accesslayer/share-link/read.d.ts +5 -0
  25. package/dist/accesslayer/share-link/read.d.ts.map +1 -0
  26. package/dist/accesslayer/share-link/receipt.d.ts +11 -0
  27. package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
  28. package/dist/accesslayer/share-link/recovery.d.ts +6 -0
  29. package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
  30. package/dist/accesslayer/share-link/reserve.d.ts +4 -0
  31. package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
  32. package/dist/accesslayer/share-link/revoke.d.ts +3 -0
  33. package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
  34. package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
  35. package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
  36. package/dist/accesslayer/share-link/transaction.d.ts +15 -0
  37. package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
  38. package/dist/accesslayer/share-link/types.d.ts +370 -0
  39. package/dist/accesslayer/share-link/types.d.ts.map +1 -0
  40. package/dist/app.d.ts +4 -0
  41. package/dist/app.d.ts.map +1 -1
  42. package/dist/config/environment.d.ts +9 -0
  43. package/dist/config/environment.d.ts.map +1 -1
  44. package/dist/constants/auth-grant.d.ts +2 -0
  45. package/dist/constants/auth-grant.d.ts.map +1 -1
  46. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  47. package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
  48. package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
  49. package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
  50. package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
  51. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  52. package/dist/helpers/notificationMessages.d.ts +1 -1
  53. package/dist/helpers/notificationMessages.d.ts.map +1 -1
  54. package/dist/helpers/presentation-metadata.d.ts +4 -0
  55. package/dist/helpers/presentation-metadata.d.ts.map +1 -0
  56. package/dist/helpers/presentation.helpers.d.ts +1 -1
  57. package/dist/helpers/presentation.helpers.d.ts.map +1 -1
  58. package/dist/helpers/profile.helpers.d.ts +1 -0
  59. package/dist/helpers/profile.helpers.d.ts.map +1 -1
  60. package/dist/helpers/share-content-client/adapters.d.ts +10 -0
  61. package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
  62. package/dist/helpers/share-content-client/canonical.d.ts +7 -0
  63. package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
  64. package/dist/helpers/share-content-client/client.d.ts +5 -0
  65. package/dist/helpers/share-content-client/client.d.ts.map +1 -0
  66. package/dist/helpers/share-content-client/config.d.ts +11 -0
  67. package/dist/helpers/share-content-client/config.d.ts.map +1 -0
  68. package/dist/helpers/share-content-client/index.d.ts +6 -0
  69. package/dist/helpers/share-content-client/index.d.ts.map +1 -0
  70. package/dist/helpers/share-content-client/responses.d.ts +16 -0
  71. package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
  72. package/dist/helpers/share-content-client/types.d.ts +145 -0
  73. package/dist/helpers/share-content-client/types.d.ts.map +1 -0
  74. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
  75. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
  76. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
  77. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
  78. package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
  79. package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
  80. package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
  81. package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
  82. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
  83. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
  84. package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
  85. package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
  86. package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
  87. package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
  88. package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
  89. package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
  90. package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
  91. package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
  92. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
  93. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
  94. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
  95. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
  96. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
  97. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
  98. package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
  99. package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
  100. package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
  101. package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
  102. package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
  103. package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
  104. package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
  105. package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
  106. package/dist/helpers/share-link-maintenance/runner.d.ts +19 -0
  107. package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
  108. package/dist/helpers/share-link-maintenance/runtime.d.ts +36 -0
  109. package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
  110. package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
  111. package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
  112. package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
  113. package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
  114. package/dist/helpers/share-link-maintenance/types.d.ts +53 -0
  115. package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
  116. package/dist/helpers/share-link-owner/config.d.ts +16 -0
  117. package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
  118. package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
  119. package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
  120. package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
  121. package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
  122. package/dist/helpers/share-link-owner-projection.d.ts +4 -0
  123. package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
  124. package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
  125. package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
  126. package/dist/helpers/share-link-passcode.d.ts +6 -0
  127. package/dist/helpers/share-link-passcode.d.ts.map +1 -0
  128. package/dist/helpers/share-link-policy/index.d.ts +4 -0
  129. package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
  130. package/dist/helpers/share-link-policy/production.d.ts +13 -0
  131. package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
  132. package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
  133. package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
  134. package/dist/helpers/share-link-policy/types.d.ts +16 -0
  135. package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
  136. package/dist/helpers/share-link-public/config.d.ts +13 -0
  137. package/dist/helpers/share-link-public/config.d.ts.map +1 -0
  138. package/dist/helpers/share-link-view-notification.d.ts +2 -0
  139. package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
  140. package/dist/index.js +2 -2
  141. package/dist/models/Presentation.d.ts +2 -0
  142. package/dist/models/Presentation.d.ts.map +1 -1
  143. package/dist/models/Profile.d.ts +2 -0
  144. package/dist/models/Profile.d.ts.map +1 -1
  145. package/dist/models/ShareLink.d.ts +44 -0
  146. package/dist/models/ShareLink.d.ts.map +1 -0
  147. package/dist/models/ShareViewReceipt.d.ts +22 -0
  148. package/dist/models/ShareViewReceipt.d.ts.map +1 -0
  149. package/dist/models/index.d.ts +1 -0
  150. package/dist/models/index.d.ts.map +1 -1
  151. package/dist/models/share-link-constraints.d.ts +3 -0
  152. package/dist/models/share-link-constraints.d.ts.map +1 -0
  153. package/dist/public-share-link-http.d.ts +4 -0
  154. package/dist/public-share-link-http.d.ts.map +1 -0
  155. package/dist/routes/app-store.d.ts.map +1 -1
  156. package/dist/routes/boosts.d.ts.map +1 -1
  157. package/dist/routes/contracts.d.ts +2 -2
  158. package/dist/routes/contracts.d.ts.map +1 -1
  159. package/dist/routes/credentials.d.ts +1 -1
  160. package/dist/routes/index.d.ts +153 -0
  161. package/dist/routes/index.d.ts.map +1 -1
  162. package/dist/routes/presentations.d.ts +1 -0
  163. package/dist/routes/presentations.d.ts.map +1 -1
  164. package/dist/routes/profile-manager.d.ts.map +1 -1
  165. package/dist/routes/profiles.d.ts.map +1 -1
  166. package/dist/routes/public-share-links.d.ts +230 -0
  167. package/dist/routes/public-share-links.d.ts.map +1 -0
  168. package/dist/routes/share-links.d.ts +844 -0
  169. package/dist/routes/share-links.d.ts.map +1 -0
  170. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  171. package/dist/types/consentflowcontract.d.ts +1 -1
  172. package/lambda.ts +4 -1
  173. package/package.json +22 -16
  174. package/scripts/seed-demo-persona.ts +425 -0
  175. package/serverless.yml +36 -0
  176. package/shareLinkMaintenanceLambda.ts +49 -0
  177. package/src/accesslayer/consentflowcontract/relationships/create.ts +9 -16
  178. package/src/accesslayer/consentflowcontract/relationships/update.ts +15 -28
  179. package/src/accesslayer/presentation/read.ts +57 -25
  180. package/src/accesslayer/presentation/relationships/create.ts +54 -11
  181. package/src/accesslayer/presentation/relationships/read.ts +21 -6
  182. package/src/accesslayer/share-link/abandon.ts +100 -0
  183. package/src/accesslayer/share-link/cleanup.ts +271 -0
  184. package/src/accesslayer/share-link/errors.ts +32 -0
  185. package/src/accesslayer/share-link/finalize.ts +265 -0
  186. package/src/accesslayer/share-link/helpers.ts +419 -0
  187. package/src/accesslayer/share-link/index.ts +11 -0
  188. package/src/accesslayer/share-link/list.ts +162 -0
  189. package/src/accesslayer/share-link/read.ts +107 -0
  190. package/src/accesslayer/share-link/receipt.ts +548 -0
  191. package/src/accesslayer/share-link/recovery.ts +545 -0
  192. package/src/accesslayer/share-link/reserve.ts +684 -0
  193. package/src/accesslayer/share-link/revoke.ts +197 -0
  194. package/src/accesslayer/share-link/transaction-execution.ts +72 -0
  195. package/src/accesslayer/share-link/transaction.ts +126 -0
  196. package/src/accesslayer/share-link/types.ts +601 -0
  197. package/src/app.ts +6 -0
  198. package/src/config/environment.ts +14 -0
  199. package/src/constants/auth-grant.ts +5 -0
  200. package/src/docker-entry.ts +57 -0
  201. package/src/helpers/boost.helpers.ts +4 -31
  202. package/src/helpers/credentialSubject.helpers.test.ts +114 -0
  203. package/src/helpers/credentialSubject.helpers.ts +40 -0
  204. package/src/helpers/inbox.helpers.ts +7 -4
  205. package/src/helpers/notificationMessages.ts +18 -1
  206. package/src/helpers/presentation-metadata.ts +41 -0
  207. package/src/helpers/presentation.helpers.ts +27 -20
  208. package/src/helpers/profile.helpers.ts +7 -0
  209. package/src/helpers/share-content-client/adapters.ts +55 -0
  210. package/src/helpers/share-content-client/canonical.ts +25 -0
  211. package/src/helpers/share-content-client/client.ts +440 -0
  212. package/src/helpers/share-content-client/config.ts +189 -0
  213. package/src/helpers/share-content-client/index.ts +27 -0
  214. package/src/helpers/share-content-client/responses.ts +273 -0
  215. package/src/helpers/share-content-client/types.ts +253 -0
  216. package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
  217. package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
  218. package/src/helpers/share-link-coordinator/coordinator.ts +714 -0
  219. package/src/helpers/share-link-coordinator/index.ts +8 -0
  220. package/src/helpers/share-link-coordinator/recovery-runner.ts +428 -0
  221. package/src/helpers/share-link-coordinator/runtime.ts +88 -0
  222. package/src/helpers/share-link-coordinator/stat.ts +30 -0
  223. package/src/helpers/share-link-coordinator/types.ts +353 -0
  224. package/src/helpers/share-link-lifecycle/index.ts +4 -0
  225. package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
  226. package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
  227. package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
  228. package/src/helpers/share-link-lifecycle/types.ts +58 -0
  229. package/src/helpers/share-link-maintenance/budget.ts +63 -0
  230. package/src/helpers/share-link-maintenance/config.ts +312 -0
  231. package/src/helpers/share-link-maintenance/index.ts +24 -0
  232. package/src/helpers/share-link-maintenance/runner.ts +223 -0
  233. package/src/helpers/share-link-maintenance/runtime.ts +334 -0
  234. package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
  235. package/src/helpers/share-link-maintenance/telemetry.ts +70 -0
  236. package/src/helpers/share-link-maintenance/types.ts +89 -0
  237. package/src/helpers/share-link-owner/config.ts +48 -0
  238. package/src/helpers/share-link-owner/diagnostics.ts +89 -0
  239. package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
  240. package/src/helpers/share-link-owner-projection.ts +52 -0
  241. package/src/helpers/share-link-passcode-abuse.ts +36 -0
  242. package/src/helpers/share-link-passcode.ts +47 -0
  243. package/src/helpers/share-link-policy/index.ts +7 -0
  244. package/src/helpers/share-link-policy/production.ts +88 -0
  245. package/src/helpers/share-link-policy/resolver.ts +108 -0
  246. package/src/helpers/share-link-policy/types.ts +48 -0
  247. package/src/helpers/share-link-public/config.ts +41 -0
  248. package/src/helpers/share-link-view-notification.ts +18 -0
  249. package/src/models/Presentation.ts +2 -2
  250. package/src/models/Profile.ts +3 -2
  251. package/src/models/ShareLink.ts +130 -0
  252. package/src/models/ShareViewReceipt.ts +69 -0
  253. package/src/models/index.ts +2 -0
  254. package/src/models/share-link-constraints.ts +93 -0
  255. package/src/public-share-link-http.ts +49 -0
  256. package/src/routes/app-store.ts +2 -11
  257. package/src/routes/boosts.ts +2 -11
  258. package/src/routes/contracts.ts +2 -11
  259. package/src/routes/index.ts +139 -44
  260. package/src/routes/presentations.ts +19 -3
  261. package/src/routes/profile-manager.ts +10 -2
  262. package/src/routes/profiles.ts +17 -6
  263. package/src/routes/public-share-links.ts +814 -0
  264. package/src/routes/share-links.ts +640 -0
  265. package/src/services/delivery/adapters/postmark.adapter.ts +4 -1
  266. package/test/dids.spec.ts +10 -1
  267. package/test/guardian-gated-routes.spec.ts +13 -0
  268. package/test/helpers/share-link-fixtures.ts +94 -0
  269. package/test/managed-recipient-inbox.spec.ts +130 -0
  270. package/test/notificationMessages.spec.ts +13 -0
  271. package/test/openapi.spec.ts +20 -0
  272. package/test/presentations.spec.ts +54 -6
  273. package/test/profiles.spec.ts +67 -1
  274. package/test/share-content-client.contract.spec.ts +365 -0
  275. package/test/share-content-client.unit.spec.ts +462 -0
  276. package/test/share-link-abuse.unit.spec.ts +65 -0
  277. package/test/share-link-coordinator.neo4j.spec.ts +684 -0
  278. package/test/share-link-coordinator.unit.spec.ts +990 -0
  279. package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
  280. package/test/share-link-lifecycle.neo4j.spec.ts +1268 -0
  281. package/test/share-link-lifecycle.unit.spec.ts +205 -0
  282. package/test/share-link-list-cursor.unit.spec.ts +86 -0
  283. package/test/share-link-list.neo4j.spec.ts +324 -0
  284. package/test/share-link-maintenance.neo4j.spec.ts +355 -0
  285. package/test/share-link-maintenance.unit.spec.ts +1113 -0
  286. package/test/share-link-owner-policy.unit.spec.ts +210 -0
  287. package/test/share-link-owner-route.unit.spec.ts +752 -0
  288. package/test/share-link-owner-runtime.unit.spec.ts +102 -0
  289. package/test/share-link-passcode.unit.spec.ts +29 -0
  290. package/test/share-link-policy.unit.spec.ts +135 -0
  291. package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
  292. package/test/share-link-production-policy.unit.spec.ts +77 -0
  293. package/test/share-link-public-config.unit.spec.ts +64 -0
  294. package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
  295. package/test/share-link-public-route.unit.spec.ts +800 -0
  296. package/test/share-link-receipt.neo4j.spec.ts +943 -0
  297. package/test/share-link-recovery.neo4j.spec.ts +935 -0
  298. package/test/share-link-recovery.unit.spec.ts +726 -0
  299. package/vitest.config.ts +1 -0
  300. package/vitest.share-links-neo4j.config.ts +19 -0
  301. package/vitest.share-links-unit.config.ts +28 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-links.d.ts","sourceRoot":"","sources":["../../src/routes/share-links.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EACR,0BAA0B,EAC1B,oBAAoB,EAEvB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EACR,mBAAmB,EACnB,oBAAoB,EAEvB,MAAM,+BAA+B,CAAC;AAYvC,MAAM,MAAM,2BAA2B,GAAG;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;IAM9C,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEvF,QAAQ,CAAC,0BAA0B,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAM/E,QAAQ,CAAC,8BAA8B,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzF,CAAC;AAgHF,eAAO,MAAM,sBAAsB,GAC/B,iBAAiB,MAAM,OAAO,CAAC,2BAA2B,GAAG,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuVrE,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAGzE,eAAO,MAAM,+BAA+B,GACxC,WAAW,MAAM,EACjB,gBAAgB,MAAM,KACvB,OAAO,CAAC,IAAI,CASd,CAAC;AAkBF,eAAO,MAAM,yBAAyB,mDAOhC,CAAC;AAoEP,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAAoD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"postmark.adapter.d.ts","sourceRoot":"","sources":["../../../../src/services/delivery/adapters/postmark.adapter.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAwCpE,qBAAa,eAAgB,YAAW,eAAe;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,EAAE,MAAM;IAIb,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwF5D,OAAO,CAAC,gBAAgB;CA8F3B"}
1
+ {"version":3,"file":"postmark.adapter.d.ts","sourceRoot":"","sources":["../../../../src/services/delivery/adapters/postmark.adapter.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAwCpE,qBAAa,eAAgB,YAAW,eAAe;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,EAAE,MAAM;IAIb,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwF5D,OAAO,CAAC,gBAAgB;CAiG3B"}
@@ -96,8 +96,8 @@ export type FlatDbTransactionType = Omit<DbTransactionType, 'guardianApproval' |
96
96
  export declare const DbTermsValidator: z.ZodObject<{
97
97
  id: z.ZodString;
98
98
  status: z.ZodEnum<{
99
- live: "live";
100
99
  stale: "stale";
100
+ live: "live";
101
101
  withdrawn: "withdrawn";
102
102
  }>;
103
103
  terms: z.ZodPrefault<z.ZodObject<{
package/lambda.ts CHANGED
@@ -14,6 +14,7 @@ import skillsViewerApp from './src/skills-viewer';
14
14
  import statusListsApp from './src/status-lists';
15
15
  import credentialRefreshApp from './src/credential-refresh';
16
16
  import { appRouter, createContext } from './src/app';
17
+ import { publicShareLinkCacheControlHeaders } from './src/routes';
17
18
  import { deliverQueuedNotification } from './src/helpers/notificationQueue.helpers';
18
19
  import { startSkillEmbeddingBackfill } from './src/helpers/skill-embedding.helpers';
19
20
  import { createOpenApiAwsLambdaHandler } from './src/helpers/shim';
@@ -72,6 +73,7 @@ export const _openApiHandler = createOpenApiAwsLambdaHandler({
72
73
  'Access-Control-Allow-Origin': '*',
73
74
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
74
75
  'Access-Control-Allow-Headers': 'Authorization, Content-Type',
76
+ ...publicShareLinkCacheControlHeaders(meta.paths),
75
77
  },
76
78
  };
77
79
  },
@@ -85,12 +87,13 @@ export const _trpcHandler = withInboxBatchBodyLimit(
85
87
  router: appRouter,
86
88
  createContext,
87
89
  onError: handleTrpcError,
88
- responseMeta: () => {
90
+ responseMeta: ({ paths }) => {
89
91
  return {
90
92
  headers: {
91
93
  'Access-Control-Allow-Origin': '*',
92
94
  'Access-Control-Allow-Methods': '*',
93
95
  'Access-Control-Allow-Headers': 'authorization',
96
+ ...publicShareLinkCacheControlHeaders(paths),
94
97
  },
95
98
  };
96
99
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@learncard/network-brain-service",
3
- "version": "3.19.0",
3
+ "version": "3.19.1",
4
4
  "description": "",
5
5
  "engines": {
6
6
  "node": ">=24.12 <25"
@@ -19,6 +19,7 @@
19
19
  "test:integration:watch": "vitest --config vitest.integration.config.ts",
20
20
  "test:docker": "vitest run --config vitest.integration.config.ts",
21
21
  "seed:dev-app": "bun scripts/seed-dev-app.ts",
22
+ "seed:demo-persona": "bun --conditions=development scripts/seed-demo-persona.ts",
22
23
  "skill-frameworks": "bun scripts/skill-frameworks.ts",
23
24
  "build": "shx rm -rf dist && node bundle.mjs && tsc --p tsconfig.build.json && tsc --showConfig --p tsconfig.build.json > tsconfig.temp.json && tsc-alias --replacer ./tsc-alias-replacer.cjs -p tsconfig.temp.json && shx rm tsconfig.temp.json",
24
25
  "typecheck": "tsc --noEmit -p tsconfig.build.json && tsc --noEmit -p tsconfig.entrypoints.json",
@@ -61,27 +62,30 @@
61
62
  "@digitalcredentials/issuer-registry-client": "^3.2.0-beta.5",
62
63
  "@fastify/cors": "^11.2.0",
63
64
  "@google/genai": "^1.27.0",
64
- "@learncard/core": "9.4.36",
65
- "@learncard/crypto-plugin": "1.1.36",
66
- "@learncard/did-web-plugin": "1.1.36",
67
- "@learncard/didkey-plugin": "1.1.36",
68
- "@learncard/didkit-plugin": "1.10.1",
69
- "@learncard/didkit-plugin-node": "0.3.1",
70
- "@learncard/dynamic-loader-plugin": "1.1.36",
71
- "@learncard/encryption-plugin": "1.1.36",
72
- "@learncard/expiration-plugin": "1.2.36",
73
- "@learncard/email-templates": "1.1.0",
74
- "@learncard/helpers": "1.6.0",
75
- "@learncard/learn-card-plugin": "1.2.36",
76
- "@learncard/types": "5.21.0",
77
- "@learncard/vc-plugin": "1.6.2",
78
- "@learncard/vc-templates-plugin": "1.1.36",
65
+ "@learncard/credential-library": "2.0.5",
66
+ "@learncard/core": "9.4.37",
67
+ "@learncard/crypto-plugin": "1.1.37",
68
+ "@learncard/did-web-plugin": "1.1.37",
69
+ "@learncard/didkey-plugin": "1.1.37",
70
+ "@learncard/didkit-plugin": "1.10.2",
71
+ "@learncard/didkit-plugin-node": "0.3.2",
72
+ "@learncard/dynamic-loader-plugin": "1.1.37",
73
+ "@learncard/encryption-plugin": "1.1.37",
74
+ "@learncard/expiration-plugin": "1.2.37",
75
+ "@learncard/email-templates": "1.2.0",
76
+ "@learncard/helpers": "1.6.1",
77
+ "@learncard/learn-card-plugin": "1.2.37",
78
+ "@learncard/types": "5.22.0",
79
+ "@learncard/vc-plugin": "1.6.3",
80
+ "@learncard/vc-templates-plugin": "1.1.37",
79
81
  "@sentry/serverless": "7.61.0",
80
82
  "@trpc/server": "11.8.0",
81
83
  "@types/lodash": "^4.14.191",
82
84
  "base64url": "^3.0.1",
85
+ "bs58": "^5.0.0",
83
86
  "cors": "^2.8.5",
84
87
  "dotenv": "^16.0.3",
88
+ "hash-wasm": "^4.12.0",
85
89
  "express": "^4.18.2",
86
90
  "fastify": "^5.8.5",
87
91
  "fastify-plugin": "^5.1.0",
@@ -93,6 +97,7 @@
93
97
  "lodash": "^4.17.21",
94
98
  "messagebird": "^4.0.1",
95
99
  "multiformats": "^11.0.1",
100
+ "mongodb": "^6.2.0",
96
101
  "mustache": "^4.2.0",
97
102
  "neo4j-driver": "5.18.0",
98
103
  "neogma": "1.13.0",
@@ -103,6 +108,7 @@
103
108
  "tsc-alias": "^1.8.10",
104
109
  "twilio": "^5.7.1",
105
110
  "undici": "^6",
111
+ "tweetnacl": "^1.0.3",
106
112
  "uuid": "^14.0.0",
107
113
  "zod": "4.4.3",
108
114
  "zod-openapi": "^5.0.1"
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Publish a credential-library persona as a ConsentFlow contract with auto-boosts.
4
+ *
5
+ * Usage (from services/learn-card-network/brain-service):
6
+ * bun run seed:demo-persona student
7
+ *
8
+ * The script is idempotent: profile, signing authority, Boost, contract, and
9
+ * AUTO_RECEIVE identifiers are stable. Re-running updates fixture content and
10
+ * replaces the contract's auto-boost set without changing its URI.
11
+ *
12
+ * Every target requires explicit database, domain, signing-authority endpoint,
13
+ * and 64-character signing seed configuration.
14
+ */
15
+
16
+ import { fileURLToPath } from 'url';
17
+ import { createHmac } from 'node:crypto';
18
+
19
+ import * as bs58 from 'bs58';
20
+ import * as dotenv from 'dotenv';
21
+ import { MongoClient } from 'mongodb';
22
+ import { Neogma } from 'neogma';
23
+ import Redis from 'ioredis';
24
+ import * as nacl from 'tweetnacl';
25
+ import { v4 as uuid, v5 as uuidv5 } from 'uuid';
26
+
27
+ import {
28
+ getBundle,
29
+ getFixture,
30
+ prepareFixture,
31
+ type CredentialBundleEntry,
32
+ type CredentialBundleIssuer,
33
+ } from '@learncard/credential-library';
34
+ import type { UnsignedVC } from '@learncard/types';
35
+ import { flattenObject } from '../src/helpers/objects.helpers';
36
+
37
+ dotenv.config({ path: fileURLToPath(new URL('../.env', import.meta.url)) });
38
+
39
+ const PERSONA_NAMESPACE = '5c4bb193-6e65-43d9-940d-d85b758a94f2';
40
+ const SIGNING_AUTHORITY_NAME = 'sample-personas';
41
+
42
+ const NEO4J_URI = process.env.NEO4J_URI ?? 'bolt://localhost:7687';
43
+ const NEO4J_USERNAME = process.env.NEO4J_USERNAME ?? 'neo4j';
44
+ const NEO4J_PASSWORD = process.env.NEO4J_PASSWORD ?? 'this-is-the-password';
45
+ const MONGO_URI = process.env.MONGO_URI ?? 'mongodb://localhost:27017/?replicaSet=rs0';
46
+ const MONGO_DB_NAME = process.env.MONGO_DB_NAME ?? 'lca-api';
47
+ const REDIS_HOST = process.env.REDIS_HOST ?? 'localhost';
48
+ const REDIS_PORT = Number.parseInt(process.env.REDIS_PORT ?? '6379', 10);
49
+ const BRAIN_DOMAIN = process.env.DOMAIN_NAME || 'localhost%3A4000';
50
+ const SIGNING_AUTHORITY_ENDPOINT = process.env.DEMO_PERSONA_SIGNING_AUTHORITY_ENDPOINT;
51
+ const SIGNING_AUTHORITY_SEED = process.env.DEMO_PERSONA_SA_SEED;
52
+
53
+ const personaId = process.argv[2];
54
+
55
+ if (!personaId) {
56
+ throw new Error('Usage: bun scripts/seed-demo-persona.ts <personaId>');
57
+ }
58
+
59
+ if (!SIGNING_AUTHORITY_SEED || !SIGNING_AUTHORITY_ENDPOINT) {
60
+ throw new Error(
61
+ 'DEMO_PERSONA_SA_SEED and DEMO_PERSONA_SIGNING_AUTHORITY_ENDPOINT are required.'
62
+ );
63
+ }
64
+
65
+ if (!/^[0-9a-f]{64}$/i.test(SIGNING_AUTHORITY_SEED)) {
66
+ throw new Error('DEMO_PERSONA_SA_SEED must contain exactly 64 hexadecimal characters.');
67
+ }
68
+
69
+ const transformProfileId = (raw: string): string => raw.toLowerCase().replace(/:/g, '%3A');
70
+
71
+ const getDidWeb = (domain: string, profileId: string): string =>
72
+ `did:web:${domain}:users:${profileId}`;
73
+
74
+ const constructContractUri = (contractId: string): string => {
75
+ const encodedDomain = BRAIN_DOMAIN.includes('localhost')
76
+ ? BRAIN_DOMAIN.replace(/:/g, '%3A')
77
+ : BRAIN_DOMAIN.replace(/:/g, '/');
78
+
79
+ return `lc:network:${encodedDomain.replace(/\/trpc$/, '')}/trpc:contract:${contractId}`;
80
+ };
81
+
82
+ const deriveDidKeyFromSeed = (hexSeed: string): string => {
83
+ const seedBytes = Buffer.from(hexSeed, 'hex');
84
+ const keyPair = nacl.sign.keyPair.fromSeed(seedBytes);
85
+ const multicodec = Buffer.concat([Buffer.from('ed01', 'hex'), Buffer.from(keyPair.publicKey)]);
86
+
87
+ return `did:key:z${bs58.encode(multicodec)}`;
88
+ };
89
+ const deriveSigningAuthoritySeed = (profileId: string): string =>
90
+ createHmac('sha256', Buffer.from(SIGNING_AUTHORITY_SEED, 'hex'))
91
+ .update(`learncard:sample-persona:${profileId}`)
92
+ .digest('hex');
93
+
94
+ const clearProfileDidDocumentCache = async (profileId: string): Promise<void> => {
95
+ const redis = new Redis({
96
+ host: REDIS_HOST,
97
+ port: REDIS_PORT,
98
+ lazyConnect: true,
99
+ connectTimeout: 2_000,
100
+ retryStrategy: () => null,
101
+ });
102
+
103
+ try {
104
+ await redis.connect();
105
+ await redis.del(`did-doc:${profileId}`);
106
+ } catch {
107
+ console.warn(
108
+ `Could not clear did-doc:${profileId} at ${REDIS_HOST}:${REDIS_PORT}; delete that Redis key manually, or restart the brain service if it uses an in-memory cache.`
109
+ );
110
+ } finally {
111
+ redis.disconnect();
112
+ }
113
+ };
114
+
115
+ const shiftDate = (offsetDays: number): string => {
116
+ const date = new Date();
117
+ date.setUTCDate(date.getUTCDate() + offsetDays);
118
+ return date.toISOString();
119
+ };
120
+
121
+ const prepareBundleCredential = (entry: CredentialBundleEntry, issuerDid: string): UnsignedVC => {
122
+ const fixture = getFixture(entry.fixtureId);
123
+ const prepared = prepareFixture(fixture, {
124
+ issuerDid,
125
+ subjectDid: 'did:example:123',
126
+ validFrom: shiftDate(entry.validFromOffsetDays),
127
+ freshIds: false,
128
+ });
129
+
130
+ if (entry.name) prepared.name = entry.name;
131
+
132
+ return prepared;
133
+ };
134
+
135
+ const toBoostTemplate = (credential: UnsignedVC, issuerDid: string): string => {
136
+ const template = structuredClone(credential) as Record<string, unknown>;
137
+ delete template.id;
138
+ delete template.proof;
139
+ template.issuer = issuerDid;
140
+
141
+ const subjects = Array.isArray(template.credentialSubject)
142
+ ? template.credentialSubject
143
+ : [template.credentialSubject];
144
+ for (const subject of subjects) {
145
+ if (subject && typeof subject === 'object') {
146
+ Object.assign(subject, { id: 'did:example:123' });
147
+ }
148
+ }
149
+
150
+ return JSON.stringify(template);
151
+ };
152
+
153
+ const ensureSigningAuthority = async (
154
+ ownerDid: string,
155
+ did: string,
156
+ seed: string
157
+ ): Promise<void> => {
158
+ const client = new MongoClient(MONGO_URI);
159
+
160
+ try {
161
+ await client.connect();
162
+ await client
163
+ .db(MONGO_DB_NAME)
164
+ .collection('signingauthorities')
165
+ .updateOne(
166
+ { ownerDid, name: SIGNING_AUTHORITY_NAME },
167
+ {
168
+ $set: { seed, did },
169
+ $setOnInsert: { _id: uuid(), ownerDid, name: SIGNING_AUTHORITY_NAME },
170
+ },
171
+ { upsert: true }
172
+ );
173
+ } finally {
174
+ await client.close();
175
+ }
176
+ };
177
+
178
+ const main = async (): Promise<void> => {
179
+ const bundle = getBundle(personaId);
180
+ const [firstEntry] = bundle.entries;
181
+ if (!firstEntry) {
182
+ throw new Error(`Credential bundle ${bundle.id} has no entries.`);
183
+ }
184
+
185
+ const issuerProfiles = new Map<
186
+ string,
187
+ CredentialBundleIssuer & {
188
+ profileId: `sample-${string}`;
189
+ did: string;
190
+ signingAuthorityDid: string;
191
+ signingAuthoritySeed: string;
192
+ }
193
+ >();
194
+ for (const entry of bundle.entries) {
195
+ const profileId = transformProfileId(entry.issuer.profileId);
196
+ if (!profileId.startsWith('sample-')) {
197
+ throw new Error(`Refusing to seed non-sample issuer profile: ${profileId}`);
198
+ }
199
+ const existingIssuer = issuerProfiles.get(profileId);
200
+
201
+ if (
202
+ existingIssuer &&
203
+ (existingIssuer.displayName !== entry.issuer.displayName ||
204
+ existingIssuer.image !== entry.issuer.image)
205
+ ) {
206
+ throw new Error(`Conflicting metadata configured for sample issuer ${profileId}.`);
207
+ }
208
+
209
+ const signingAuthoritySeed = deriveSigningAuthoritySeed(profileId);
210
+ const signingAuthorityDid = deriveDidKeyFromSeed(signingAuthoritySeed);
211
+ issuerProfiles.set(profileId, {
212
+ ...entry.issuer,
213
+ profileId: profileId as `sample-${string}`,
214
+ did: getDidWeb(BRAIN_DOMAIN, profileId),
215
+ signingAuthorityDid,
216
+ signingAuthoritySeed,
217
+ });
218
+ }
219
+
220
+ const contractOwnerProfileId = transformProfileId(firstEntry.issuer.profileId);
221
+ const contractId = uuidv5(`persona:${bundle.id}:contract`, PERSONA_NAMESPACE);
222
+ const neogma = new Neogma({
223
+ url: NEO4J_URI,
224
+ username: NEO4J_USERNAME,
225
+ password: NEO4J_PASSWORD,
226
+ });
227
+ const run = neogma.queryRunner.run.bind(neogma.queryRunner);
228
+
229
+ try {
230
+ for (const issuer of issuerProfiles.values()) {
231
+ const profileResult = await run(
232
+ `MERGE (p:Profile {profileId: $profileId})
233
+ ON CREATE SET p.isSamplePersona = true
234
+ WITH p
235
+ WHERE p.isSamplePersona = true
236
+ AND (p.did IS NULL OR p.did = $did)
237
+ SET p.displayName = $displayName,
238
+ p.shortBio = $shortBio,
239
+ p.image = $image,
240
+ p.did = $did
241
+ RETURN p.did AS did`,
242
+ {
243
+ profileId: issuer.profileId,
244
+ displayName: issuer.displayName,
245
+ shortBio: 'Issuer for LearnCard sample credentials',
246
+ image: issuer.image ?? '',
247
+ did: issuer.signingAuthorityDid,
248
+ }
249
+ );
250
+ const storedSigningAuthorityDid = profileResult.records[0]?.get('did');
251
+ if (!storedSigningAuthorityDid) {
252
+ throw new Error(
253
+ `Refusing to overwrite existing non-sample profile ${issuer.profileId}.`
254
+ );
255
+ }
256
+ if (storedSigningAuthorityDid !== issuer.signingAuthorityDid) {
257
+ throw new Error(
258
+ `Profile ${issuer.profileId} did not retain its signing authority DID.`
259
+ );
260
+ }
261
+
262
+ await run(
263
+ `MERGE (sa:SigningAuthority {endpoint: $endpoint})
264
+ WITH sa
265
+ MATCH (p:Profile {profileId: $profileId})
266
+ MERGE (p)-[r:USES_SIGNING_AUTHORITY {name: $name}]->(sa)
267
+ SET r.did = $did, r.isPrimary = true
268
+ RETURN r`,
269
+ {
270
+ endpoint: SIGNING_AUTHORITY_ENDPOINT,
271
+ profileId: issuer.profileId,
272
+ name: SIGNING_AUTHORITY_NAME,
273
+ did: issuer.signingAuthorityDid,
274
+ }
275
+ );
276
+ await clearProfileDidDocumentCache(issuer.profileId);
277
+ await ensureSigningAuthority(
278
+ issuer.did,
279
+ issuer.signingAuthorityDid,
280
+ issuer.signingAuthoritySeed
281
+ );
282
+ }
283
+
284
+ const autoBoosts: Array<{ boostId: string; issuerProfileId: string }> = [];
285
+ for (const entry of bundle.entries) {
286
+ const fixture = getFixture(entry.fixtureId);
287
+ const profileId = transformProfileId(entry.issuer.profileId);
288
+ const issuer = issuerProfiles.get(profileId);
289
+ if (!issuer) {
290
+ throw new Error(`Missing seeded issuer profile ${profileId}.`);
291
+ }
292
+
293
+ const boostId = uuidv5(
294
+ `persona:${bundle.id}:fixture:${entry.fixtureId}`,
295
+ PERSONA_NAMESPACE
296
+ );
297
+ const credential = prepareBundleCredential(entry, issuer.did);
298
+ const boost = toBoostTemplate(credential, issuer.did);
299
+ autoBoosts.push({ boostId, issuerProfileId: profileId });
300
+ const boostProperties = flattenObject({
301
+ id: boostId,
302
+ boost,
303
+ name: entry.name ?? fixture.name,
304
+ type: fixture.profile,
305
+ category: 'Achievement',
306
+ status: 'LIVE',
307
+ meta: { personaId: bundle.id, fixtureId: fixture.id },
308
+ });
309
+
310
+ const boostResult = await run(
311
+ `MERGE (b:Boost {id: $boostId})
312
+ SET b = $properties
313
+ WITH b
314
+ OPTIONAL MATCH (b)-[oldCreated:CREATED_BY]->(:Profile)
315
+ DELETE oldCreated
316
+ WITH DISTINCT b
317
+ OPTIONAL MATCH (:Profile)-[oldRole:HAS_ROLE {roleId: '__creator__'}]->(b)
318
+ DELETE oldRole
319
+ WITH DISTINCT b
320
+ MATCH (p:Profile {profileId: $profileId})
321
+ MERGE (b)-[created:CREATED_BY]->(p)
322
+ SET created.date = $date
323
+ MERGE (p)-[:HAS_ROLE {roleId: '__creator__'}]->(b)
324
+ WITH b
325
+ MATCH (b)-[:CREATED_BY]->(creator:Profile)
326
+ MATCH (roleOwner:Profile)-[:HAS_ROLE {roleId: '__creator__'}]->(b)
327
+ RETURN collect(creator.profileId) AS creatorProfileIds,
328
+ collect(roleOwner.profileId) AS roleOwnerProfileIds`,
329
+ {
330
+ boostId,
331
+ properties: boostProperties,
332
+ profileId,
333
+ date: new Date().toISOString(),
334
+ }
335
+ );
336
+ const creatorProfileIds = boostResult.records[0]?.get('creatorProfileIds');
337
+ const roleOwnerProfileIds = boostResult.records[0]?.get('roleOwnerProfileIds');
338
+ if (
339
+ creatorProfileIds?.length !== 1 ||
340
+ creatorProfileIds[0] !== profileId ||
341
+ roleOwnerProfileIds?.length !== 1 ||
342
+ roleOwnerProfileIds[0] !== profileId
343
+ ) {
344
+ throw new Error(`Boost ${boostId} has ambiguous profile ownership.`);
345
+ }
346
+ }
347
+
348
+ const contract = {
349
+ read: {
350
+ personal: { name: { required: false } },
351
+ credentials: { categories: { Achievement: { required: false } } },
352
+ },
353
+ write: {
354
+ personal: {},
355
+ credentials: {
356
+ categories: { Achievement: { required: false, defaultEnabled: true } },
357
+ },
358
+ },
359
+ };
360
+ const now = new Date().toISOString();
361
+ const contractProperties = flattenObject({
362
+ id: contractId,
363
+ name: `${bundle.displayName} Sample Credentials`,
364
+ subtitle: bundle.blurb,
365
+ description: bundle.blurb,
366
+ reasonForAccessing: '',
367
+ needsGuardianConsent: false,
368
+ redirectUrl: '',
369
+ frontDoorBoostUri: '',
370
+ image: '',
371
+ contract,
372
+ updatedAt: now,
373
+ });
374
+
375
+ await run(
376
+ `MERGE (c:ConsentFlowContract {id: $contractId})
377
+ ON CREATE SET c.createdAt = $createdAt
378
+ WITH c, c.createdAt AS createdAt
379
+ SET c = $properties,
380
+ c.createdAt = createdAt
381
+ WITH c
382
+ OPTIONAL MATCH (c)-[oldOwner:CREATED_BY]->(:Profile)
383
+ DELETE oldOwner
384
+ WITH DISTINCT c
385
+ MATCH (p:Profile {profileId: $profileId})
386
+ MERGE (c)-[:CREATED_BY]->(p)
387
+ RETURN c`,
388
+ {
389
+ contractId,
390
+ createdAt: now,
391
+ profileId: contractOwnerProfileId,
392
+ properties: contractProperties,
393
+ }
394
+ );
395
+
396
+ await run(
397
+ `MATCH (c:ConsentFlowContract {id: $contractId})
398
+ OPTIONAL MATCH (c)-[old:AUTO_RECEIVE]->(:Boost)
399
+ DELETE old
400
+ WITH DISTINCT c
401
+ UNWIND $autoBoosts AS autoBoost
402
+ MATCH (b:Boost {id: autoBoost.boostId})
403
+ CREATE (c)-[:AUTO_RECEIVE {
404
+ signingAuthorityEndpoint: $endpoint,
405
+ signingAuthorityName: $name,
406
+ issuer: autoBoost.issuerProfileId
407
+ }]->(b)`,
408
+ {
409
+ contractId,
410
+ autoBoosts,
411
+ endpoint: SIGNING_AUTHORITY_ENDPOINT,
412
+ name: SIGNING_AUTHORITY_NAME,
413
+ }
414
+ );
415
+
416
+ console.log(constructContractUri(contractId));
417
+ } finally {
418
+ await neogma.driver.close();
419
+ }
420
+ };
421
+
422
+ main().catch(error => {
423
+ console.error(error);
424
+ process.exitCode = 1;
425
+ });
package/serverless.yml CHANGED
@@ -193,6 +193,25 @@ provider:
193
193
  CREDENTIAL_REFRESH_ENABLED: ${env:CREDENTIAL_REFRESH_ENABLED, "false"}
194
194
  CREDENTIAL_REFRESH_DIGEST_SECRET: ${env:CREDENTIAL_REFRESH_DIGEST_SECRET, ""}
195
195
  CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS: ${env:CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS, "24"}
196
+ # Stable HMAC key for passcode-bearing share-link request fingerprints.
197
+ SHARE_LINK_REQUEST_HASH_SECRET: ${env:SHARE_LINK_REQUEST_HASH_SECRET, ""}
198
+ # LC-2187 share-link service wiring and automatic maintenance. Every value is
199
+ # bounded and re-validated fail-closed at runtime. Never read from the
200
+ # scheduled event payload.
201
+ SHARE_LINK_MAINTENANCE_NAMESPACE: ${env:SHARE_LINK_MAINTENANCE_NAMESPACE, ""}
202
+ SHARE_LINK_MAINTENANCE_ORIGIN: ${env:SHARE_LINK_MAINTENANCE_ORIGIN, ""}
203
+ SHARE_LINK_MAINTENANCE_AUDIENCE: ${env:SHARE_LINK_MAINTENANCE_AUDIENCE, ""}
204
+ SHARE_LINK_MAINTENANCE_ALLOW_INSECURE_LOOPBACK: ${env:SHARE_LINK_MAINTENANCE_ALLOW_INSECURE_LOOPBACK, "false"}
205
+ SHARE_LINK_MAINTENANCE_CLAIMANT: ${env:SHARE_LINK_MAINTENANCE_CLAIMANT, "brain-share-link-maintenance"}
206
+ SHARE_LINK_MAINTENANCE_RECOVERY_LIMIT: ${env:SHARE_LINK_MAINTENANCE_RECOVERY_LIMIT, "25"}
207
+ SHARE_LINK_MAINTENANCE_CLEANUP_LIMIT: ${env:SHARE_LINK_MAINTENANCE_CLEANUP_LIMIT, "25"}
208
+ SHARE_LINK_MAINTENANCE_LEASE_MS: ${env:SHARE_LINK_MAINTENANCE_LEASE_MS, "120000"}
209
+ SHARE_LINK_MAINTENANCE_CLAIM_MS: ${env:SHARE_LINK_MAINTENANCE_CLAIM_MS, "300000"}
210
+ SHARE_LINK_MAINTENANCE_BUDGET_MS: ${env:SHARE_LINK_MAINTENANCE_BUDGET_MS, "60000"}
211
+ SHARE_LINK_MAINTENANCE_INTERVAL_MS: ${env:SHARE_LINK_MAINTENANCE_INTERVAL_MS, "300000"}
212
+ SHARE_LINK_MAINTENANCE_REQUEST_TIMEOUT_MS: ${env:SHARE_LINK_MAINTENANCE_REQUEST_TIMEOUT_MS, "10000"}
213
+ SHARE_LINK_MAINTENANCE_FINALIZE_RESERVE_MS: ${env:SHARE_LINK_MAINTENANCE_FINALIZE_RESERVE_MS, "5000"}
214
+ SHARE_LINK_MAINTENANCE_GRAPH_TIMEOUT_MS: ${env:SHARE_LINK_MAINTENANCE_GRAPH_TIMEOUT_MS, "15000"}
196
215
  tracing:
197
216
  apiGateway: true
198
217
  lambda: true
@@ -244,6 +263,23 @@ functions:
244
263
  rate: rate(1 day)
245
264
  enabled: true
246
265
 
266
+ # LC-2187 share-link recovery + cleanup. Dedicated entrypoint (no lambda.ts
267
+ # import-time work). Runs automatically with valid service configuration;
268
+ # absent wiring remains inert and partial wiring fails closed. The cadence
269
+ # matches the default Docker maintenance interval (5 minutes).
270
+ shareLinkMaintenance:
271
+ handler: shareLinkMaintenanceLambda.shareLinkMaintenanceHandler
272
+ timeout: 300
273
+ vpc:
274
+ securityGroupIds:
275
+ - 'Fn::GetAtt': [ServerlessSecurityGroup, GroupId]
276
+ subnetIds:
277
+ - Ref: PrivateSubnetA
278
+ events:
279
+ - schedule:
280
+ rate: rate(5 minutes)
281
+ enabled: true
282
+
247
283
  trpc:
248
284
  handler: lambda.trpcHandler
249
285
  vpc:
@@ -0,0 +1,49 @@
1
+ import type { Context } from 'aws-lambda';
2
+
3
+ import {
4
+ createShareLinkMaintenanceRuntime,
5
+ type ShareLinkMaintenanceRuntime,
6
+ } from './src/helpers/share-link-maintenance';
7
+
8
+ /**
9
+ * Dedicated LC-2187 share-link maintenance Lambda entrypoint.
10
+ *
11
+ * It deliberately does NOT import `lambda.ts`, so no unrelated embedding
12
+ * backfill, tRPC router, Sentry input capture or inbox handler runs at import
13
+ * time. The only side effect is resolving the service and maintenance
14
+ * configuration; when it is absent or malformed the runtime is inert and the
15
+ * handler performs no graph, remote or signing work.
16
+ *
17
+ * The scheduled event payload is ignored entirely: it can never override the
18
+ * namespace, origin, audience or any other configuration, and it is never
19
+ * logged.
20
+ */
21
+
22
+ let runtime: ShareLinkMaintenanceRuntime | null = null;
23
+
24
+ const getRuntime = (): ShareLinkMaintenanceRuntime => {
25
+ if (runtime === null) {
26
+ runtime = createShareLinkMaintenanceRuntime({
27
+ rawEnvironment: process.env as Record<string, unknown>,
28
+ });
29
+ }
30
+
31
+ return runtime;
32
+ };
33
+
34
+ export const shareLinkMaintenanceHandler = async (
35
+ _event: unknown,
36
+ context?: Pick<Context, 'getRemainingTimeInMillis'>
37
+ ): Promise<{ status: string }> => {
38
+ const hasRemainingTime = typeof context?.getRemainingTimeInMillis === 'function';
39
+
40
+ const summary = await getRuntime().runOnce({
41
+ // Preserve the LIVE Lambda remaining-time source: the runtime samples it
42
+ // once, before dependency initialization, and anchors the invocation
43
+ // budget to that instant.
44
+ remainingTimeMs: hasRemainingTime ? () => context!.getRemainingTimeInMillis() : undefined,
45
+ });
46
+
47
+ // The runtime already emitted one aggregate-only allowlisted event.
48
+ return { status: summary.status };
49
+ };