@learncard/network-brain-service 3.18.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 (478) hide show
  1. package/.env.example +38 -2
  2. package/CHANGELOG.md +118 -0
  3. package/README.md +42 -0
  4. package/compose.inbox.yml +7 -0
  5. package/dist/accesslayer/auth-grant/read.d.ts +1 -0
  6. package/dist/accesslayer/auth-grant/read.d.ts.map +1 -1
  7. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  8. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  9. package/dist/accesslayer/credential-activity/create.d.ts.map +1 -1
  10. package/dist/accesslayer/credential-refresh/email-delivery.d.ts +18 -0
  11. package/dist/accesslayer/credential-refresh/email-delivery.d.ts.map +1 -0
  12. package/dist/accesslayer/credential-refresh/index.d.ts +1 -0
  13. package/dist/accesslayer/credential-refresh/index.d.ts.map +1 -1
  14. package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
  15. package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
  16. package/dist/accesslayer/inbox-batch/store.d.ts +33 -0
  17. package/dist/accesslayer/inbox-batch/store.d.ts.map +1 -0
  18. package/dist/accesslayer/inbox-credential/create.d.ts +5 -0
  19. package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
  20. package/dist/accesslayer/inbox-credential/refresh.d.ts +71 -0
  21. package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -0
  22. package/dist/accesslayer/inbox-credential/update.d.ts +1 -1
  23. package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
  24. package/dist/accesslayer/presentation/read.d.ts.map +1 -1
  25. package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
  26. package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
  27. package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
  28. package/dist/accesslayer/share-link/abandon.d.ts +3 -0
  29. package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
  30. package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
  31. package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
  32. package/dist/accesslayer/share-link/errors.d.ts +8 -0
  33. package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
  34. package/dist/accesslayer/share-link/finalize.d.ts +3 -0
  35. package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
  36. package/dist/accesslayer/share-link/helpers.d.ts +56 -0
  37. package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
  38. package/dist/accesslayer/share-link/index.d.ts +12 -0
  39. package/dist/accesslayer/share-link/index.d.ts.map +1 -0
  40. package/dist/accesslayer/share-link/list.d.ts +5 -0
  41. package/dist/accesslayer/share-link/list.d.ts.map +1 -0
  42. package/dist/accesslayer/share-link/read.d.ts +5 -0
  43. package/dist/accesslayer/share-link/read.d.ts.map +1 -0
  44. package/dist/accesslayer/share-link/receipt.d.ts +11 -0
  45. package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
  46. package/dist/accesslayer/share-link/recovery.d.ts +6 -0
  47. package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
  48. package/dist/accesslayer/share-link/reserve.d.ts +4 -0
  49. package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
  50. package/dist/accesslayer/share-link/revoke.d.ts +3 -0
  51. package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
  52. package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
  53. package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
  54. package/dist/accesslayer/share-link/transaction.d.ts +15 -0
  55. package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
  56. package/dist/accesslayer/share-link/types.d.ts +370 -0
  57. package/dist/accesslayer/share-link/types.d.ts.map +1 -0
  58. package/dist/app.d.ts +4 -0
  59. package/dist/app.d.ts.map +1 -1
  60. package/dist/cache/atomic-counter.test-helpers.d.ts +3 -0
  61. package/dist/cache/atomic-counter.test-helpers.d.ts.map +1 -0
  62. package/dist/cache/atomic-counter.test.d.ts +2 -0
  63. package/dist/cache/atomic-counter.test.d.ts.map +1 -0
  64. package/dist/cache/index.d.ts +1 -1
  65. package/dist/cache/index.d.ts.map +1 -1
  66. package/dist/config/environment.d.ts +55 -0
  67. package/dist/config/environment.d.ts.map +1 -1
  68. package/dist/constants/auth-grant.d.ts +2 -0
  69. package/dist/constants/auth-grant.d.ts.map +1 -1
  70. package/dist/credential-refresh.d.ts.map +1 -1
  71. package/dist/helpers/activity.helpers.d.ts +4 -0
  72. package/dist/helpers/activity.helpers.d.ts.map +1 -1
  73. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  74. package/dist/helpers/contact-method.helpers.d.ts.map +1 -1
  75. package/dist/helpers/contact-method.helpers.test.d.ts +2 -0
  76. package/dist/helpers/contact-method.helpers.test.d.ts.map +1 -0
  77. package/dist/helpers/credential-refresh-email-content.helpers.d.ts +44 -0
  78. package/dist/helpers/credential-refresh-email-content.helpers.d.ts.map +1 -0
  79. package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts +2 -0
  80. package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts.map +1 -0
  81. package/dist/helpers/credential-refresh-email.helpers.d.ts +17 -0
  82. package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -0
  83. package/dist/helpers/credential-refresh-proof.helpers.d.ts +4 -0
  84. package/dist/helpers/credential-refresh-proof.helpers.d.ts.map +1 -0
  85. package/dist/helpers/credential-refresh-proof.helpers.test.d.ts +2 -0
  86. package/dist/helpers/credential-refresh-proof.helpers.test.d.ts.map +1 -0
  87. package/dist/helpers/credential-refresh.helpers.d.ts +45 -2
  88. package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
  89. package/dist/helpers/credential.helpers.d.ts +2 -1
  90. package/dist/helpers/credential.helpers.d.ts.map +1 -1
  91. package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
  92. package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
  93. package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
  94. package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
  95. package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
  96. package/dist/helpers/inbox-batch-http.helpers.d.ts +15 -0
  97. package/dist/helpers/inbox-batch-http.helpers.d.ts.map +1 -0
  98. package/dist/helpers/inbox-batch-http.helpers.test.d.ts +2 -0
  99. package/dist/helpers/inbox-batch-http.helpers.test.d.ts.map +1 -0
  100. package/dist/helpers/inbox-batch-status.helpers.d.ts +3 -0
  101. package/dist/helpers/inbox-batch-status.helpers.d.ts.map +1 -0
  102. package/dist/helpers/inbox-batch.helpers.d.ts +10 -0
  103. package/dist/helpers/inbox-batch.helpers.d.ts.map +1 -0
  104. package/dist/helpers/inbox-batch.helpers.test.d.ts +2 -0
  105. package/dist/helpers/inbox-batch.helpers.test.d.ts.map +1 -0
  106. package/dist/helpers/inbox-issuance-error.helpers.d.ts +13 -0
  107. package/dist/helpers/inbox-issuance-error.helpers.d.ts.map +1 -0
  108. package/dist/helpers/inbox-queue.helpers.d.ts +11 -0
  109. package/dist/helpers/inbox-queue.helpers.d.ts.map +1 -0
  110. package/dist/helpers/inbox-queue.helpers.test.d.ts +2 -0
  111. package/dist/helpers/inbox-queue.helpers.test.d.ts.map +1 -0
  112. package/dist/helpers/inbox-refresh.helpers.d.ts +186 -0
  113. package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -0
  114. package/dist/helpers/inbox.helpers.d.ts +9 -1
  115. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  116. package/dist/helpers/notificationMessages.d.ts +1 -1
  117. package/dist/helpers/notificationMessages.d.ts.map +1 -1
  118. package/dist/helpers/notifications.helpers.d.ts.map +1 -1
  119. package/dist/helpers/presentation-metadata.d.ts +4 -0
  120. package/dist/helpers/presentation-metadata.d.ts.map +1 -0
  121. package/dist/helpers/presentation.helpers.d.ts +1 -1
  122. package/dist/helpers/presentation.helpers.d.ts.map +1 -1
  123. package/dist/helpers/profile.helpers.d.ts +1 -0
  124. package/dist/helpers/profile.helpers.d.ts.map +1 -1
  125. package/dist/helpers/rateLimit.helpers.d.ts.map +1 -1
  126. package/dist/helpers/refresh-send-intent.helpers.d.ts +51 -0
  127. package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -0
  128. package/dist/helpers/share-content-client/adapters.d.ts +10 -0
  129. package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
  130. package/dist/helpers/share-content-client/canonical.d.ts +7 -0
  131. package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
  132. package/dist/helpers/share-content-client/client.d.ts +5 -0
  133. package/dist/helpers/share-content-client/client.d.ts.map +1 -0
  134. package/dist/helpers/share-content-client/config.d.ts +11 -0
  135. package/dist/helpers/share-content-client/config.d.ts.map +1 -0
  136. package/dist/helpers/share-content-client/index.d.ts +6 -0
  137. package/dist/helpers/share-content-client/index.d.ts.map +1 -0
  138. package/dist/helpers/share-content-client/responses.d.ts +16 -0
  139. package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
  140. package/dist/helpers/share-content-client/types.d.ts +145 -0
  141. package/dist/helpers/share-content-client/types.d.ts.map +1 -0
  142. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
  143. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
  144. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
  145. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
  146. package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
  147. package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
  148. package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
  149. package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
  150. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
  151. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
  152. package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
  153. package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
  154. package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
  155. package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
  156. package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
  157. package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
  158. package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
  159. package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
  160. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
  161. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
  162. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
  163. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
  164. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
  165. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
  166. package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
  167. package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
  168. package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
  169. package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
  170. package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
  171. package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
  172. package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
  173. package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
  174. package/dist/helpers/share-link-maintenance/runner.d.ts +19 -0
  175. package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
  176. package/dist/helpers/share-link-maintenance/runtime.d.ts +36 -0
  177. package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
  178. package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
  179. package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
  180. package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
  181. package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
  182. package/dist/helpers/share-link-maintenance/types.d.ts +53 -0
  183. package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
  184. package/dist/helpers/share-link-owner/config.d.ts +16 -0
  185. package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
  186. package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
  187. package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
  188. package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
  189. package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
  190. package/dist/helpers/share-link-owner-projection.d.ts +4 -0
  191. package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
  192. package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
  193. package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
  194. package/dist/helpers/share-link-passcode.d.ts +6 -0
  195. package/dist/helpers/share-link-passcode.d.ts.map +1 -0
  196. package/dist/helpers/share-link-policy/index.d.ts +4 -0
  197. package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
  198. package/dist/helpers/share-link-policy/production.d.ts +13 -0
  199. package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
  200. package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
  201. package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
  202. package/dist/helpers/share-link-policy/types.d.ts +16 -0
  203. package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
  204. package/dist/helpers/share-link-public/config.d.ts +13 -0
  205. package/dist/helpers/share-link-public/config.d.ts.map +1 -0
  206. package/dist/helpers/share-link-view-notification.d.ts +2 -0
  207. package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
  208. package/dist/helpers/shim.d.ts +1 -1
  209. package/dist/helpers/shim.d.ts.map +1 -1
  210. package/dist/inbox-queue-local.d.ts +2 -0
  211. package/dist/inbox-queue-local.d.ts.map +1 -0
  212. package/dist/index.js +2 -2
  213. package/dist/models/AuthGrant.d.ts.map +1 -1
  214. package/dist/models/CredentialActivity.d.ts +1 -0
  215. package/dist/models/CredentialActivity.d.ts.map +1 -1
  216. package/dist/models/CredentialRefresh.d.ts +5 -2
  217. package/dist/models/CredentialRefresh.d.ts.map +1 -1
  218. package/dist/models/InboxCredential.d.ts +1 -0
  219. package/dist/models/InboxCredential.d.ts.map +1 -1
  220. package/dist/models/Presentation.d.ts +2 -0
  221. package/dist/models/Presentation.d.ts.map +1 -1
  222. package/dist/models/Profile.d.ts +2 -0
  223. package/dist/models/Profile.d.ts.map +1 -1
  224. package/dist/models/ShareLink.d.ts +44 -0
  225. package/dist/models/ShareLink.d.ts.map +1 -0
  226. package/dist/models/ShareViewReceipt.d.ts +22 -0
  227. package/dist/models/ShareViewReceipt.d.ts.map +1 -0
  228. package/dist/models/credential-refresh-constraints.d.ts.map +1 -1
  229. package/dist/models/index.d.ts +1 -0
  230. package/dist/models/index.d.ts.map +1 -1
  231. package/dist/models/share-link-constraints.d.ts +3 -0
  232. package/dist/models/share-link-constraints.d.ts.map +1 -0
  233. package/dist/openapi.d.ts.map +1 -1
  234. package/dist/public-share-link-http.d.ts +4 -0
  235. package/dist/public-share-link-http.d.ts.map +1 -0
  236. package/dist/routes/act-as.test.d.ts +2 -0
  237. package/dist/routes/act-as.test.d.ts.map +1 -0
  238. package/dist/routes/activity.d.ts +3 -0
  239. package/dist/routes/activity.d.ts.map +1 -1
  240. package/dist/routes/app-store.d.ts.map +1 -1
  241. package/dist/routes/auth-grants.d.ts +10 -0
  242. package/dist/routes/auth-grants.d.ts.map +1 -1
  243. package/dist/routes/boosts.d.ts +494 -0
  244. package/dist/routes/boosts.d.ts.map +1 -1
  245. package/dist/routes/contracts.d.ts +2 -2
  246. package/dist/routes/contracts.d.ts.map +1 -1
  247. package/dist/routes/credential-refreshes.d.ts.map +1 -1
  248. package/dist/routes/credentials.d.ts +1 -1
  249. package/dist/routes/credentials.d.ts.map +1 -1
  250. package/dist/routes/inbox.d.ts +953 -11
  251. package/dist/routes/inbox.d.ts.map +1 -1
  252. package/dist/routes/index.d.ts +154 -85
  253. package/dist/routes/index.d.ts.map +1 -1
  254. package/dist/routes/presentations.d.ts +1 -0
  255. package/dist/routes/presentations.d.ts.map +1 -1
  256. package/dist/routes/profile-manager.d.ts.map +1 -1
  257. package/dist/routes/profiles.d.ts.map +1 -1
  258. package/dist/routes/public-share-links.d.ts +230 -0
  259. package/dist/routes/public-share-links.d.ts.map +1 -0
  260. package/dist/routes/share-links.d.ts +844 -0
  261. package/dist/routes/share-links.d.ts.map +1 -0
  262. package/dist/routes/workflows.d.ts.map +1 -1
  263. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  264. package/dist/services/delivery/adapters/postmark.adapter.test.d.ts +2 -0
  265. package/dist/services/delivery/adapters/postmark.adapter.test.d.ts.map +1 -0
  266. package/dist/services/registry/registry.factory.d.ts.map +1 -1
  267. package/dist/types/activity.d.ts +4 -0
  268. package/dist/types/activity.d.ts.map +1 -1
  269. package/dist/types/consentflowcontract.d.ts +1 -1
  270. package/dist/types/credential-refresh.d.ts +10 -1
  271. package/dist/types/credential-refresh.d.ts.map +1 -1
  272. package/dist/types/inbox-batch.d.ts +38 -0
  273. package/dist/types/inbox-batch.d.ts.map +1 -0
  274. package/inbox-e2e-setup.ts +53 -1
  275. package/inbox-elasticmq.conf +10 -0
  276. package/lambda.ts +64 -16
  277. package/package.json +24 -16
  278. package/scripts/seed-demo-persona.ts +425 -0
  279. package/scripts/setup-inbox-queues.ts +60 -0
  280. package/serverless-local.yml +2 -0
  281. package/serverless.yml +111 -0
  282. package/shareLinkMaintenanceLambda.ts +49 -0
  283. package/src/accesslayer/auth-grant/read.ts +2 -1
  284. package/src/accesslayer/consentflowcontract/relationships/create.ts +9 -16
  285. package/src/accesslayer/consentflowcontract/relationships/update.ts +15 -28
  286. package/src/accesslayer/credential-activity/create.ts +1 -0
  287. package/src/accesslayer/credential-refresh/email-delivery.ts +116 -0
  288. package/src/accesslayer/credential-refresh/index.ts +1 -0
  289. package/src/accesslayer/credential-refresh/read.ts +8 -2
  290. package/src/accesslayer/credential-refresh/update.ts +4 -0
  291. package/src/accesslayer/inbox-batch/store.ts +469 -0
  292. package/src/accesslayer/inbox-credential/create.ts +51 -19
  293. package/src/accesslayer/inbox-credential/refresh.ts +221 -0
  294. package/src/accesslayer/inbox-credential/update.ts +1 -1
  295. package/src/accesslayer/presentation/read.ts +57 -25
  296. package/src/accesslayer/presentation/relationships/create.ts +54 -11
  297. package/src/accesslayer/presentation/relationships/read.ts +21 -6
  298. package/src/accesslayer/share-link/abandon.ts +100 -0
  299. package/src/accesslayer/share-link/cleanup.ts +271 -0
  300. package/src/accesslayer/share-link/errors.ts +32 -0
  301. package/src/accesslayer/share-link/finalize.ts +265 -0
  302. package/src/accesslayer/share-link/helpers.ts +419 -0
  303. package/src/accesslayer/share-link/index.ts +11 -0
  304. package/src/accesslayer/share-link/list.ts +162 -0
  305. package/src/accesslayer/share-link/read.ts +107 -0
  306. package/src/accesslayer/share-link/receipt.ts +548 -0
  307. package/src/accesslayer/share-link/recovery.ts +545 -0
  308. package/src/accesslayer/share-link/reserve.ts +684 -0
  309. package/src/accesslayer/share-link/revoke.ts +197 -0
  310. package/src/accesslayer/share-link/transaction-execution.ts +72 -0
  311. package/src/accesslayer/share-link/transaction.ts +126 -0
  312. package/src/accesslayer/share-link/types.ts +601 -0
  313. package/src/app.ts +6 -0
  314. package/src/cache/atomic-counter.test-helpers.ts +27 -0
  315. package/src/cache/atomic-counter.test.ts +6 -0
  316. package/src/cache/index.ts +25 -13
  317. package/src/config/environment.ts +40 -0
  318. package/src/constants/auth-grant.ts +5 -0
  319. package/src/credential-refresh.ts +5 -1
  320. package/src/docker-entry.ts +65 -0
  321. package/src/helpers/activity.helpers.ts +4 -0
  322. package/src/helpers/app-counter.helpers.ts +1 -1
  323. package/src/helpers/boost.helpers.ts +4 -31
  324. package/src/helpers/contact-method.helpers.test.ts +43 -0
  325. package/src/helpers/contact-method.helpers.ts +3 -4
  326. package/src/helpers/credential-refresh-email-content.helpers.test.ts +222 -0
  327. package/src/helpers/credential-refresh-email-content.helpers.ts +155 -0
  328. package/src/helpers/credential-refresh-email.helpers.ts +201 -0
  329. package/src/helpers/credential-refresh-proof.helpers.test.ts +103 -0
  330. package/src/helpers/credential-refresh-proof.helpers.ts +43 -0
  331. package/src/helpers/credential-refresh.helpers.ts +375 -99
  332. package/src/helpers/credential.helpers.ts +4 -1
  333. package/src/helpers/credentialSubject.helpers.test.ts +114 -0
  334. package/src/helpers/credentialSubject.helpers.ts +40 -0
  335. package/src/helpers/finalize-inbox.helpers.ts +29 -14
  336. package/src/helpers/inbox-batch-http.helpers.test.ts +188 -0
  337. package/src/helpers/inbox-batch-http.helpers.ts +150 -0
  338. package/src/helpers/inbox-batch-status.helpers.ts +11 -0
  339. package/src/helpers/inbox-batch.helpers.test.ts +668 -0
  340. package/src/helpers/inbox-batch.helpers.ts +374 -0
  341. package/src/helpers/inbox-issuance-error.helpers.ts +25 -0
  342. package/src/helpers/inbox-queue.helpers.test.ts +253 -0
  343. package/src/helpers/inbox-queue.helpers.ts +372 -0
  344. package/src/helpers/inbox-refresh.helpers.ts +432 -0
  345. package/src/helpers/inbox.helpers.ts +275 -36
  346. package/src/helpers/notificationMessages.ts +18 -1
  347. package/src/helpers/notifications.helpers.ts +4 -2
  348. package/src/helpers/presentation-metadata.ts +41 -0
  349. package/src/helpers/presentation.helpers.ts +27 -20
  350. package/src/helpers/profile.helpers.ts +7 -0
  351. package/src/helpers/rateLimit.helpers.ts +2 -5
  352. package/src/helpers/refresh-send-intent.helpers.ts +273 -0
  353. package/src/helpers/share-content-client/adapters.ts +55 -0
  354. package/src/helpers/share-content-client/canonical.ts +25 -0
  355. package/src/helpers/share-content-client/client.ts +440 -0
  356. package/src/helpers/share-content-client/config.ts +189 -0
  357. package/src/helpers/share-content-client/index.ts +27 -0
  358. package/src/helpers/share-content-client/responses.ts +273 -0
  359. package/src/helpers/share-content-client/types.ts +253 -0
  360. package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
  361. package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
  362. package/src/helpers/share-link-coordinator/coordinator.ts +714 -0
  363. package/src/helpers/share-link-coordinator/index.ts +8 -0
  364. package/src/helpers/share-link-coordinator/recovery-runner.ts +428 -0
  365. package/src/helpers/share-link-coordinator/runtime.ts +88 -0
  366. package/src/helpers/share-link-coordinator/stat.ts +30 -0
  367. package/src/helpers/share-link-coordinator/types.ts +353 -0
  368. package/src/helpers/share-link-lifecycle/index.ts +4 -0
  369. package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
  370. package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
  371. package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
  372. package/src/helpers/share-link-lifecycle/types.ts +58 -0
  373. package/src/helpers/share-link-maintenance/budget.ts +63 -0
  374. package/src/helpers/share-link-maintenance/config.ts +312 -0
  375. package/src/helpers/share-link-maintenance/index.ts +24 -0
  376. package/src/helpers/share-link-maintenance/runner.ts +223 -0
  377. package/src/helpers/share-link-maintenance/runtime.ts +334 -0
  378. package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
  379. package/src/helpers/share-link-maintenance/telemetry.ts +70 -0
  380. package/src/helpers/share-link-maintenance/types.ts +89 -0
  381. package/src/helpers/share-link-owner/config.ts +48 -0
  382. package/src/helpers/share-link-owner/diagnostics.ts +89 -0
  383. package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
  384. package/src/helpers/share-link-owner-projection.ts +52 -0
  385. package/src/helpers/share-link-passcode-abuse.ts +36 -0
  386. package/src/helpers/share-link-passcode.ts +47 -0
  387. package/src/helpers/share-link-policy/index.ts +7 -0
  388. package/src/helpers/share-link-policy/production.ts +88 -0
  389. package/src/helpers/share-link-policy/resolver.ts +108 -0
  390. package/src/helpers/share-link-policy/types.ts +48 -0
  391. package/src/helpers/share-link-public/config.ts +41 -0
  392. package/src/helpers/share-link-view-notification.ts +18 -0
  393. package/src/helpers/shim.ts +32 -31
  394. package/src/inbox-queue-local.ts +28 -0
  395. package/src/models/AuthGrant.ts +1 -0
  396. package/src/models/CredentialActivity.ts +3 -5
  397. package/src/models/CredentialRefresh.ts +4 -2
  398. package/src/models/InboxCredential.ts +2 -0
  399. package/src/models/Presentation.ts +2 -2
  400. package/src/models/Profile.ts +3 -2
  401. package/src/models/ShareLink.ts +130 -0
  402. package/src/models/ShareViewReceipt.ts +69 -0
  403. package/src/models/credential-refresh-constraints.test.ts +8 -3
  404. package/src/models/credential-refresh-constraints.ts +5 -0
  405. package/src/models/index.ts +2 -0
  406. package/src/models/share-link-constraints.ts +93 -0
  407. package/src/openapi.ts +8 -0
  408. package/src/public-share-link-http.ts +49 -0
  409. package/src/routes/act-as.test.ts +239 -0
  410. package/src/routes/app-store.ts +4 -13
  411. package/src/routes/auth-grants.ts +13 -11
  412. package/src/routes/boosts.ts +916 -103
  413. package/src/routes/contracts.ts +2 -11
  414. package/src/routes/credential-refreshes.test.ts +8 -2
  415. package/src/routes/credential-refreshes.ts +4 -1
  416. package/src/routes/credentials.ts +2 -1
  417. package/src/routes/inbox.ts +95 -49
  418. package/src/routes/index.ts +216 -46
  419. package/src/routes/presentations.ts +19 -3
  420. package/src/routes/profile-manager.ts +10 -2
  421. package/src/routes/profiles.ts +17 -6
  422. package/src/routes/public-share-links.ts +814 -0
  423. package/src/routes/share-links.ts +640 -0
  424. package/src/routes/skills.ts +1 -1
  425. package/src/routes/workflows.ts +43 -24
  426. package/src/services/delivery/adapters/postmark.adapter.test.ts +54 -0
  427. package/src/services/delivery/adapters/postmark.adapter.ts +16 -1
  428. package/src/services/registry/registry.factory.ts +2 -1
  429. package/src/types/activity.ts +2 -0
  430. package/src/types/credential-refresh.ts +21 -2
  431. package/src/types/inbox-batch.ts +46 -0
  432. package/test/atomic-counter.e2e.ts +12 -0
  433. package/test/auth-grant.spec.ts +21 -21
  434. package/test/credential-refresh-email.spec.ts +663 -0
  435. package/test/credential-refresh-publication.spec.ts +2 -0
  436. package/test/dids.spec.ts +10 -1
  437. package/test/guardian-gated-routes.spec.ts +13 -0
  438. package/test/helpers/inbox-e2e-environment.ts +6 -0
  439. package/test/helpers/share-link-fixtures.ts +94 -0
  440. package/test/inbox-batch.spec.ts +1338 -0
  441. package/test/inbox-refresh.spec.ts +534 -0
  442. package/test/managed-recipient-inbox.spec.ts +130 -0
  443. package/test/notificationMessages.spec.ts +13 -0
  444. package/test/openapi.spec.ts +61 -1
  445. package/test/presentations.spec.ts +54 -6
  446. package/test/profiles.spec.ts +67 -1
  447. package/test/refresh-send.spec.ts +1799 -0
  448. package/test/registry.spec.ts +56 -11
  449. package/test/share-content-client.contract.spec.ts +365 -0
  450. package/test/share-content-client.unit.spec.ts +462 -0
  451. package/test/share-link-abuse.unit.spec.ts +65 -0
  452. package/test/share-link-coordinator.neo4j.spec.ts +684 -0
  453. package/test/share-link-coordinator.unit.spec.ts +990 -0
  454. package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
  455. package/test/share-link-lifecycle.neo4j.spec.ts +1268 -0
  456. package/test/share-link-lifecycle.unit.spec.ts +205 -0
  457. package/test/share-link-list-cursor.unit.spec.ts +86 -0
  458. package/test/share-link-list.neo4j.spec.ts +324 -0
  459. package/test/share-link-maintenance.neo4j.spec.ts +355 -0
  460. package/test/share-link-maintenance.unit.spec.ts +1113 -0
  461. package/test/share-link-owner-policy.unit.spec.ts +210 -0
  462. package/test/share-link-owner-route.unit.spec.ts +752 -0
  463. package/test/share-link-owner-runtime.unit.spec.ts +102 -0
  464. package/test/share-link-passcode.unit.spec.ts +29 -0
  465. package/test/share-link-policy.unit.spec.ts +135 -0
  466. package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
  467. package/test/share-link-production-policy.unit.spec.ts +77 -0
  468. package/test/share-link-public-config.unit.spec.ts +64 -0
  469. package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
  470. package/test/share-link-public-route.unit.spec.ts +800 -0
  471. package/test/share-link-receipt.neo4j.spec.ts +943 -0
  472. package/test/share-link-recovery.neo4j.spec.ts +935 -0
  473. package/test/share-link-recovery.unit.spec.ts +726 -0
  474. package/vitest.config.ts +10 -0
  475. package/vitest.inbox-e2e.config.ts +5 -1
  476. package/vitest.integration.config.ts +2 -0
  477. package/vitest.share-links-neo4j.config.ts +19 -0
  478. package/vitest.share-links-unit.config.ts +28 -0
@@ -0,0 +1,334 @@
1
+ import type {
2
+ CleanupRunnerDependencies,
3
+ RecoveryRunnerDependencies,
4
+ } from '../share-link-coordinator';
5
+ import type { PruneShareViewReceiptsInput } from '../../accesslayer/share-link/types';
6
+ import type { ShareContentClient } from '../share-content-client/types';
7
+ import { createMaintenancePassBudget, resolveMaintenanceBudgetMs } from './budget';
8
+ import { resolveShareLinkMaintenanceConfig } from './config';
9
+ import { runShareLinkMaintenancePass } from './runner';
10
+ import { safeLogMaintenance } from './telemetry';
11
+ import type {
12
+ ShareLinkMaintenanceConfigResolution,
13
+ ShareLinkMaintenanceLogger,
14
+ ShareLinkMaintenanceRunSummary,
15
+ } from './types';
16
+
17
+ type MaintenanceClient = Pick<ShareContentClient, 'stat' | 'delete'>;
18
+
19
+ type ResolvedRunnerDependencies = {
20
+ recoveryRepository: RecoveryRunnerDependencies['repository'];
21
+ cleanupRepository: CleanupRunnerDependencies['repository'];
22
+ pruneReceipts: (input: PruneShareViewReceiptsInput) => Promise<number>;
23
+ client: MaintenanceClient;
24
+ };
25
+
26
+ type DependencySetup =
27
+ | { status: 'ready'; dependencies: ResolvedRunnerDependencies }
28
+ | { status: 'failed' }
29
+ | { status: 'expired' };
30
+
31
+ export type ShareLinkMaintenanceRuntime = {
32
+ /** Config outcome fixed at construction; never contains raw values. */
33
+ readonly resolution: ShareLinkMaintenanceConfigResolution;
34
+ runOnce: (options?: {
35
+ /**
36
+ * Live Lambda remaining-time source. A numeric value is accepted as a
37
+ * backward-compatible test seam; production passes the live
38
+ * `getRemainingTimeInMillis` callback.
39
+ */
40
+ remainingTimeMs?: number | (() => number);
41
+ }) => Promise<ShareLinkMaintenanceRunSummary>;
42
+ };
43
+
44
+ export type CreateShareLinkMaintenanceRuntimeOptions = {
45
+ rawEnvironment?: Record<string, unknown>;
46
+ logger?: ShareLinkMaintenanceLogger;
47
+ now?: () => Date;
48
+ monotonicNow?: () => number;
49
+ /** Test seam: resolve the transport client without dynamic imports. */
50
+ createClient?: (
51
+ config: Extract<ShareLinkMaintenanceConfigResolution, { status: 'enabled' }>['config']
52
+ ) => Promise<MaintenanceClient>;
53
+ /** Test seam: resolve the production repositories without loading Neo4j. */
54
+ createRepositories?: (
55
+ client: MaintenanceClient,
56
+ config: Extract<ShareLinkMaintenanceConfigResolution, { status: 'enabled' }>['config']
57
+ ) => Promise<{
58
+ recoveryRepository: RecoveryRunnerDependencies['repository'];
59
+ cleanupRepository: CleanupRunnerDependencies['repository'];
60
+ pruneReceipts: ResolvedRunnerDependencies['pruneReceipts'];
61
+ }>;
62
+ };
63
+
64
+ const inertSummary = (
65
+ status: 'disabled' | 'invalid_configuration',
66
+ categories: ShareLinkMaintenanceRunSummary['categories']
67
+ ): ShareLinkMaintenanceRunSummary => ({
68
+ status,
69
+ durationMs: 0,
70
+ budgetMs: 0,
71
+ recovery: null,
72
+ cleanup: null,
73
+ receiptPrune: null,
74
+ categories,
75
+ });
76
+
77
+ const deadlineSummary = (
78
+ budget: ReturnType<typeof createMaintenancePassBudget>
79
+ ): ShareLinkMaintenanceRunSummary => ({
80
+ status: 'deadline_exhausted',
81
+ durationMs: budget.elapsedMs(),
82
+ budgetMs: budget.configuredMs,
83
+ recovery: null,
84
+ cleanup: null,
85
+ receiptPrune: null,
86
+ categories: {},
87
+ });
88
+
89
+ const initializationFailedSummary = (): ShareLinkMaintenanceRunSummary => ({
90
+ status: 'initialization_failed',
91
+ durationMs: 0,
92
+ budgetMs: 0,
93
+ recovery: null,
94
+ cleanup: null,
95
+ receiptPrune: null,
96
+ categories: { share_link_maintenance_initialization_failed: 1 },
97
+ });
98
+
99
+ /** Sample a live remaining-time source once; fail closed on a bad value. */
100
+ const sampleRemainingMs = (source: number | (() => number) | undefined): number | undefined => {
101
+ if (source === undefined) return undefined;
102
+
103
+ let value: unknown;
104
+
105
+ try {
106
+ value = typeof source === 'function' ? source() : source;
107
+ } catch {
108
+ // A throwing Lambda context must not start work; fail closed.
109
+ return 0;
110
+ }
111
+
112
+ if (typeof value !== 'number' || !Number.isFinite(value)) return 0;
113
+
114
+ return value;
115
+ };
116
+
117
+ /**
118
+ * Production composition for the share-link maintenance pass.
119
+ *
120
+ * Disabled or malformed configuration resolves to an inert runtime: `runOnce`
121
+ * returns a fixed summary and never constructs the client, signer, repositories
122
+ * or any graph/remote dependency. When enabled, composition is lazy on the first
123
+ * invocation and reuses the existing did:web signing adapter with an explicitly
124
+ * configured trusted origin/audience/opaque namespace. There is no unsigned or
125
+ * fresh-key path.
126
+ *
127
+ * The invocation budget starts BEFORE dependency initialization: a cold start
128
+ * consumes the same deadline as the work, and an already-exhausted invocation
129
+ * performs no dependency/signer/graph setup at all. A successful setup is cached;
130
+ * a failed or budget-aborted setup is cleared so the next cadence retries.
131
+ */
132
+ export const createShareLinkMaintenanceRuntime = (
133
+ options: CreateShareLinkMaintenanceRuntimeOptions = {}
134
+ ): ShareLinkMaintenanceRuntime => {
135
+ const resolution = resolveShareLinkMaintenanceConfig(
136
+ options.rawEnvironment ?? (process.env as Record<string, unknown>)
137
+ );
138
+ const logger = options.logger ?? consoleLogger;
139
+
140
+ if (resolution.status === 'disabled') {
141
+ const summary = inertSummary('disabled', {});
142
+ return {
143
+ resolution,
144
+ runOnce: async () => {
145
+ safeLogMaintenance(logger, summary);
146
+
147
+ return summary;
148
+ },
149
+ };
150
+ }
151
+
152
+ if (resolution.status === 'invalid') {
153
+ const summary = inertSummary('invalid_configuration', {
154
+ share_link_maintenance_configuration_invalid: 1,
155
+ });
156
+ return {
157
+ resolution,
158
+ runOnce: async () => {
159
+ safeLogMaintenance(logger, summary);
160
+
161
+ return summary;
162
+ },
163
+ };
164
+ }
165
+
166
+ const { config } = resolution;
167
+ let cached: Promise<DependencySetup> | null = null;
168
+
169
+ const buildDependencies = async (
170
+ budget: ReturnType<typeof createMaintenancePassBudget>
171
+ ): Promise<DependencySetup> => {
172
+ try {
173
+ const client = options.createClient
174
+ ? await options.createClient(config)
175
+ : await buildRuntimeClient(config);
176
+
177
+ if (budget.exhausted()) return { status: 'expired' };
178
+
179
+ const repositories = options.createRepositories
180
+ ? await options.createRepositories(client, config)
181
+ : await buildRuntimeRepositories(client, config);
182
+
183
+ if (budget.exhausted()) return { status: 'expired' };
184
+
185
+ return { status: 'ready', dependencies: { client, ...repositories } };
186
+ } catch {
187
+ return { status: 'failed' };
188
+ }
189
+ };
190
+
191
+ const resolveDependencies = async (
192
+ budget: ReturnType<typeof createMaintenancePassBudget>
193
+ ): Promise<DependencySetup> => {
194
+ if (!cached) {
195
+ const pending = buildDependencies(budget);
196
+ cached = pending;
197
+
198
+ const result = await pending;
199
+
200
+ // Keep a successful/in-flight setup cached; clear a failed or
201
+ // budget-aborted one so the next cadence can retry.
202
+ if (result.status !== 'ready' && cached === pending) cached = null;
203
+
204
+ return result;
205
+ }
206
+
207
+ return cached;
208
+ };
209
+
210
+ return {
211
+ resolution,
212
+ runOnce: async runOptions => {
213
+ const requestedRemaining = sampleRemainingMs(runOptions?.remainingTimeMs);
214
+ const budget = createMaintenancePassBudget({
215
+ budgetMs: resolveMaintenanceBudgetMs(config.budgetMs, requestedRemaining),
216
+ finalizeReserveMs: config.finalizeReserveMs,
217
+ monotonicNow: options.monotonicNow,
218
+ });
219
+
220
+ if (budget.exhausted()) {
221
+ const summary = deadlineSummary(budget);
222
+ safeLogMaintenance(logger, summary);
223
+
224
+ return summary;
225
+ }
226
+
227
+ const setup = await resolveDependencies(budget);
228
+
229
+ if (setup.status === 'failed') {
230
+ const summary = initializationFailedSummary();
231
+ safeLogMaintenance(logger, summary);
232
+
233
+ return summary;
234
+ }
235
+
236
+ if (setup.status === 'expired' || budget.exhausted()) {
237
+ const summary = deadlineSummary(budget);
238
+ safeLogMaintenance(logger, summary);
239
+
240
+ return summary;
241
+ }
242
+
243
+ return runShareLinkMaintenancePass({
244
+ config,
245
+ recoveryRepository: setup.dependencies.recoveryRepository,
246
+ cleanupRepository: setup.dependencies.cleanupRepository,
247
+ pruneReceipts: setup.dependencies.pruneReceipts,
248
+ client: setup.dependencies.client,
249
+ logger,
250
+ monotonicNow: options.monotonicNow,
251
+ now: options.now,
252
+ budget,
253
+ });
254
+ },
255
+ };
256
+ };
257
+
258
+ /** Lazy production transport: loads the did:web signer only when enabled. */
259
+ const buildRuntimeClient = async (
260
+ config: Extract<ShareLinkMaintenanceConfigResolution, { status: 'enabled' }>['config']
261
+ ): Promise<MaintenanceClient> => {
262
+ const [{ getServerDidWebDID }, { createDidWebLearnCardTokenSigner }, clientModule] =
263
+ await Promise.all([
264
+ import('../learnCard.helpers'),
265
+ import('../share-content-client/adapters'),
266
+ import('../share-content-client'),
267
+ ]);
268
+
269
+ const signerDid = getServerDidWebDID();
270
+ const clientConfig = clientModule.resolveShareContentClientConfig({
271
+ enabled: true,
272
+ origin: config.origin,
273
+ namespace: config.namespace,
274
+ audience: config.audience,
275
+ signerDid,
276
+ allowInsecureLoopback: config.allowInsecureLoopback,
277
+ signer: createDidWebLearnCardTokenSigner(),
278
+ requestTimeoutMs: config.requestTimeoutMs,
279
+ // Maintenance is one bounded attempt per unit; the scheduler/cadence owns
280
+ // retry, so a transient failure is re-queued rather than retried inline.
281
+ maxAttempts: 1,
282
+ retryBackoffMs: 0,
283
+ });
284
+
285
+ if (!clientConfig.enabled) {
286
+ throw new Error('share-link maintenance client configuration was rejected');
287
+ }
288
+
289
+ return clientModule.createShareContentClient(clientConfig);
290
+ };
291
+
292
+ /** Lazy production repositories: imports the Neo4j-backed wiring only when enabled. */
293
+ const buildRuntimeRepositories = async (
294
+ client: MaintenanceClient,
295
+ config: Extract<ShareLinkMaintenanceConfigResolution, { status: 'enabled' }>['config']
296
+ ): Promise<{
297
+ recoveryRepository: RecoveryRunnerDependencies['repository'];
298
+ cleanupRepository: CleanupRunnerDependencies['repository'];
299
+ pruneReceipts: ResolvedRunnerDependencies['pruneReceipts'];
300
+ }> => {
301
+ const runtimeModule = await import('../share-link-coordinator/runtime');
302
+
303
+ const recoveryDependencies = runtimeModule.createRuntimeRecoveryRunnerDependencies({
304
+ client,
305
+ claimant: config.claimant,
306
+ namespace: config.namespace,
307
+ });
308
+ const cleanupDependencies = runtimeModule.createRuntimeCleanupRunnerDependencies({
309
+ client,
310
+ claimant: config.claimant,
311
+ namespace: config.namespace,
312
+ });
313
+
314
+ // Preinitialize the idempotent share-link constraint/index gate once, outside
315
+ // the per-unit budget, so a maintenance transaction never pays the one-time
316
+ // schema setup. It is cached in-process and is not a per-pass cost.
317
+ const { ensureShareLinkConstraints } = await import('../../models/share-link-constraints');
318
+ await ensureShareLinkConstraints();
319
+
320
+ // Load the receipt-prune repository last: it pulls the Neo4j `@instance`, so
321
+ // it must only be imported when an enabled runtime actually composes work.
322
+ const { pruneShareViewReceipts } = await import('../../accesslayer/share-link/receipt');
323
+
324
+ return {
325
+ recoveryRepository: recoveryDependencies.repository,
326
+ cleanupRepository: cleanupDependencies.repository,
327
+ pruneReceipts: pruneShareViewReceipts,
328
+ };
329
+ };
330
+
331
+ const consoleLogger: ShareLinkMaintenanceLogger = {
332
+ info: event => console.log('[share-link-maintenance]', event),
333
+ error: event => console.error('[share-link-maintenance]', event),
334
+ };
@@ -0,0 +1,126 @@
1
+ import { safeLogMaintenance } from './telemetry';
2
+ import type { ShareLinkMaintenanceLogger, ShareLinkMaintenanceRunSummary } from './types';
3
+
4
+ export type ShareLinkMaintenanceSchedulerDependencies = {
5
+ runOnce: (options?: { remainingTimeMs?: number }) => Promise<unknown>;
6
+ intervalMs: number;
7
+ logger: ShareLinkMaintenanceLogger;
8
+ setTimeoutFn?: (handler: () => void, timeoutMs: number) => unknown;
9
+ clearTimeoutFn?: (handle: unknown) => void;
10
+ };
11
+
12
+ export type ShareLinkMaintenanceScheduler = {
13
+ /** Idempotent; schedules the first bounded pass one interval from now. */
14
+ start: () => void;
15
+ /** Stops scheduling and drains an in-flight pass. */
16
+ stop: () => Promise<void>;
17
+ /** Runs one pass now, coalescing with an in-flight pass (no overlap). */
18
+ trigger: () => Promise<void>;
19
+ isRunning: () => boolean;
20
+ };
21
+
22
+ const defaultSetTimeout = (handler: () => void, timeoutMs: number): unknown =>
23
+ setTimeout(handler, timeoutMs);
24
+
25
+ const defaultClearTimeout = (handle: unknown): void => {
26
+ clearTimeout(handle as ReturnType<typeof setTimeout>);
27
+ };
28
+
29
+ const fixedFailureSummary = (intervalMs: number): ShareLinkMaintenanceRunSummary => ({
30
+ status: 'failed',
31
+ durationMs: 0,
32
+ budgetMs: intervalMs,
33
+ recovery: null,
34
+ cleanup: null,
35
+ receiptPrune: null,
36
+ categories: { share_link_maintenance_pass_failed: 1 },
37
+ });
38
+
39
+ /**
40
+ * Non-overlapping maintenance loop.
41
+ *
42
+ * The next tick is scheduled only *after* the awaited pass settles, so a slow
43
+ * pass can never stack timers. `trigger` coalesces with an in-flight pass, which
44
+ * is the per-process overlap guard; other processes are fenced by the durable
45
+ * repository claims. `stop` is terminal: it clears the pending tick, awaits the
46
+ * in-flight pass, and rejects any later explicit trigger or already-queued timer
47
+ * callback. Nothing here registers a timer at module import, and a logger failure
48
+ * cannot corrupt the loop state.
49
+ */
50
+ export const createShareLinkMaintenanceScheduler = (
51
+ dependencies: ShareLinkMaintenanceSchedulerDependencies
52
+ ): ShareLinkMaintenanceScheduler => {
53
+ const setTimeoutFn = dependencies.setTimeoutFn ?? defaultSetTimeout;
54
+ const clearTimeoutFn = dependencies.clearTimeoutFn ?? defaultClearTimeout;
55
+
56
+ let started = false;
57
+ let stopped = false;
58
+ let timer: unknown = null;
59
+ let inFlight: Promise<void> | null = null;
60
+
61
+ const scheduleNext = (): void => {
62
+ if (stopped || !started) return;
63
+
64
+ timer = setTimeoutFn(() => {
65
+ void tick();
66
+ }, dependencies.intervalMs);
67
+ };
68
+
69
+ const tick = async (): Promise<void> => {
70
+ await trigger();
71
+ scheduleNext();
72
+ };
73
+
74
+ const trigger = (): Promise<void> => {
75
+ // Stop is terminal: nothing, including an explicit trigger or an already
76
+ // queued timer callback, may start work after `stop()`.
77
+ if (stopped) return Promise.resolve();
78
+
79
+ if (inFlight) return inFlight;
80
+
81
+ // An `async` wrapper converts a synchronous `runOnce` throw into a
82
+ // rejected promise, and `.finally` always settles on a microtask, so the
83
+ // assignment below can never race the clearing callback and leave
84
+ // `inFlight` stuck on an already-settled promise.
85
+ const run = async (): Promise<void> => {
86
+ try {
87
+ await dependencies.runOnce();
88
+ } catch {
89
+ safeLogMaintenance(
90
+ dependencies.logger,
91
+ fixedFailureSummary(dependencies.intervalMs)
92
+ );
93
+ }
94
+ };
95
+
96
+ const pass = run().finally(() => {
97
+ if (inFlight === pass) inFlight = null;
98
+ });
99
+
100
+ inFlight = pass;
101
+
102
+ return pass;
103
+ };
104
+
105
+ return {
106
+ start: () => {
107
+ if (started || stopped) return;
108
+
109
+ started = true;
110
+ scheduleNext();
111
+ },
112
+ stop: async () => {
113
+ stopped = true;
114
+ started = false;
115
+
116
+ if (timer !== null) {
117
+ clearTimeoutFn(timer);
118
+ timer = null;
119
+ }
120
+
121
+ if (inFlight) await inFlight;
122
+ },
123
+ trigger,
124
+ isRunning: () => inFlight !== null,
125
+ };
126
+ };
@@ -0,0 +1,70 @@
1
+ import type { CleanupRunSummary, ShareRecoveryRunSummary } from '../share-link-coordinator';
2
+ import type {
3
+ ShareLinkMaintenanceLogEvent,
4
+ ShareLinkMaintenanceLogger,
5
+ ShareLinkMaintenanceRunSummary,
6
+ } from './types';
7
+
8
+ /**
9
+ * Fixed, allowlisted aggregate counters. This is the ONLY surface a maintenance
10
+ * log may contain: no object ref, share id, owner id, namespace value, title,
11
+ * note, URL, fragment, key, ciphertext, recovery blob or exception object.
12
+ */
13
+ const recoveryCounts = (recovery: ShareRecoveryRunSummary | null): Record<string, number> => ({
14
+ recoveryDiscovered: recovery?.discovered ?? 0,
15
+ recoveryClaimed: recovery?.claimed ?? 0,
16
+ recoveryFinalized: recovery?.finalized ?? 0,
17
+ recoveryAbandoned: recovery?.abandoned ?? 0,
18
+ recoveryDeferred: recovery?.deferred ?? 0,
19
+ recoveryClaimLost: recovery?.claimLost ?? 0,
20
+ recoveryAnomalies: recovery?.anomalies ?? 0,
21
+ });
22
+
23
+ const cleanupCounts = (cleanup: CleanupRunSummary | null): Record<string, number> => ({
24
+ cleanupClaimed: cleanup?.claimed ?? 0,
25
+ cleanupCompleted: cleanup?.completed ?? 0,
26
+ cleanupRetried: cleanup?.retried ?? 0,
27
+ cleanupClaimLost: cleanup?.claimLost ?? 0,
28
+ cleanupSkipped: cleanup?.skipped ?? 0,
29
+ });
30
+
31
+ export const toMaintenanceLogEvent = (
32
+ summary: ShareLinkMaintenanceRunSummary
33
+ ): ShareLinkMaintenanceLogEvent => ({
34
+ event: 'share_link_maintenance',
35
+ status: summary.status,
36
+ durationMs: Math.max(0, Math.trunc(summary.durationMs)),
37
+ budgetMs: Math.max(0, Math.trunc(summary.budgetMs)),
38
+ counts: {
39
+ ...recoveryCounts(summary.recovery),
40
+ ...cleanupCounts(summary.cleanup),
41
+ receiptsPruned: summary.receiptPrune?.pruned ?? 0,
42
+ },
43
+ categories: {
44
+ ...(summary.recovery?.categories ?? {}),
45
+ ...(summary.cleanup?.categories ?? {}),
46
+ ...summary.categories,
47
+ },
48
+ });
49
+
50
+ /**
51
+ * Emit one allowlisted event. A throwing logger is swallowed: telemetry must
52
+ * never corrupt lifecycle state or turn a completed pass into a failure.
53
+ */
54
+ export const safeLogMaintenance = (
55
+ logger: ShareLinkMaintenanceLogger,
56
+ summary: ShareLinkMaintenanceRunSummary
57
+ ): void => {
58
+ try {
59
+ const event = toMaintenanceLogEvent(summary);
60
+ if (summary.status === 'failed') logger.error(event);
61
+ else logger.info(event);
62
+ } catch {
63
+ // Intentionally ignored: lifecycle state is authoritative.
64
+ }
65
+ };
66
+
67
+ export const createConsoleMaintenanceLogger = (): ShareLinkMaintenanceLogger => ({
68
+ info: event => console.log('[share-link-maintenance]', event),
69
+ error: event => console.error('[share-link-maintenance]', event),
70
+ });
@@ -0,0 +1,89 @@
1
+ import type { ShareRecoveryRunSummary, CleanupRunSummary } from '../share-link-coordinator';
2
+
3
+ /**
4
+ * LC-2187 share-link maintenance (scheduling + cleanup integration).
5
+ *
6
+ * This module owns the automatically configured, namespace-scoped maintenance pass. It
7
+ * is deliberately separate from the coordinator barrel so unit tests never load
8
+ * the Neo4j `@instance` or the LearnCard signing graph.
9
+ */
10
+
11
+ /** A fully validated, enabled maintenance configuration. */
12
+ export type ShareLinkMaintenanceConfig = {
13
+ namespace: string;
14
+ origin: string;
15
+ audience: string;
16
+ allowInsecureLoopback: boolean;
17
+ claimant: string;
18
+ recoveryLimit: number;
19
+ cleanupLimit: number;
20
+ receiptPruneLimit: number;
21
+ leaseMs: number;
22
+ claimMs: number;
23
+ budgetMs: number;
24
+ intervalMs: number;
25
+ requestTimeoutMs: number;
26
+ finalizeReserveMs: number;
27
+ graphTimeoutMs: number;
28
+ };
29
+
30
+ /**
31
+ * Fixed, non-sensitive configuration outcome. `invalid` never carries the bad
32
+ * value or an exception, so it is safe to log verbatim.
33
+ */
34
+ export type ShareLinkMaintenanceConfigResolution =
35
+ | { status: 'enabled'; config: ShareLinkMaintenanceConfig }
36
+ | { status: 'disabled' }
37
+ | { status: 'invalid'; category: 'share_link_maintenance_configuration_invalid' };
38
+
39
+ /** Fixed pass status. Never carries request/record data. */
40
+ export type ShareLinkMaintenanceStatus =
41
+ | 'disabled'
42
+ | 'invalid_configuration'
43
+ | 'initialization_failed'
44
+ | 'completed'
45
+ | 'deadline_exhausted'
46
+ | 'failed';
47
+
48
+ /** Fixed failure categories allowed in a maintenance summary/log. */
49
+ export type ShareLinkMaintenanceFailureCategory =
50
+ | 'share_link_maintenance_pass_failed'
51
+ | 'share_link_maintenance_recovery_failed'
52
+ | 'share_link_maintenance_cleanup_failed'
53
+ | 'share_link_maintenance_receipt_prune_failed'
54
+ | 'share_link_maintenance_initialization_failed'
55
+ | 'share_link_maintenance_configuration_invalid';
56
+
57
+ /**
58
+ * Aggregate-only result of one maintenance invocation. It contains counts,
59
+ * durations and fixed categories; it never contains object/share/owner ids,
60
+ * URLs, keys, ciphertext, recovery, titles, notes or exception objects.
61
+ */
62
+ export type ShareLinkMaintenanceRunSummary = {
63
+ status: ShareLinkMaintenanceStatus;
64
+ durationMs: number;
65
+ budgetMs: number;
66
+ recovery: ShareRecoveryRunSummary | null;
67
+ cleanup: CleanupRunSummary | null;
68
+ /** Namespace-scoped expired/consumed receipt nodes deleted this pass. */
69
+ receiptPrune: { pruned: number } | null;
70
+ categories: Partial<Record<ShareLinkMaintenanceFailureCategory, number>>;
71
+ };
72
+
73
+ /**
74
+ * Allowlisted log event shape. The logger is called with this object only; a
75
+ * throwing logger must never corrupt lifecycle state.
76
+ */
77
+ export type ShareLinkMaintenanceLogEvent = {
78
+ event: 'share_link_maintenance';
79
+ status: ShareLinkMaintenanceStatus;
80
+ durationMs: number;
81
+ budgetMs: number;
82
+ counts: Record<string, number>;
83
+ categories: Record<string, number>;
84
+ };
85
+
86
+ export type ShareLinkMaintenanceLogger = {
87
+ info: (event: ShareLinkMaintenanceLogEvent) => void;
88
+ error: (event: ShareLinkMaintenanceLogEvent) => void;
89
+ };
@@ -0,0 +1,48 @@
1
+ import {
2
+ hasShareLinkServiceTransportConfig,
3
+ resolveShareLinkServiceTransportConfig,
4
+ } from '../share-link-maintenance/config';
5
+
6
+ /**
7
+ * Available automatically once trusted service configuration is complete.
8
+ * Namespace, origin and audience come only from server configuration. Missing
9
+ * wiring is inactive; partial or invalid wiring fails closed.
10
+ */
11
+
12
+ export const SHARE_LINK_OWNER_API_ENV = {
13
+ NAMESPACE: 'SHARE_LINK_OWNER_API_NAMESPACE',
14
+ } as const;
15
+
16
+ export type ShareLinkOwnerApiConfigResolution =
17
+ | { status: 'disabled' }
18
+ | { status: 'invalid' }
19
+ | {
20
+ status: 'enabled';
21
+ namespace: string;
22
+ origin: string;
23
+ audience: string;
24
+ allowInsecureLoopback: boolean;
25
+ };
26
+
27
+ export const resolveShareLinkOwnerApiConfig = (raw: unknown): ShareLinkOwnerApiConfigResolution => {
28
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return { status: 'invalid' };
29
+
30
+ const source = raw as Record<string, unknown>;
31
+ if (!hasShareLinkServiceTransportConfig(source)) return { status: 'disabled' };
32
+
33
+ const transport = resolveShareLinkServiceTransportConfig(source);
34
+ if (transport.status !== 'enabled') return { status: 'invalid' };
35
+
36
+ const ownerNamespace = source[SHARE_LINK_OWNER_API_ENV.NAMESPACE];
37
+ if (ownerNamespace !== undefined && ownerNamespace !== transport.config.namespace) {
38
+ return { status: 'invalid' };
39
+ }
40
+
41
+ return {
42
+ status: 'enabled',
43
+ namespace: transport.config.namespace,
44
+ origin: transport.config.origin,
45
+ audience: transport.config.audience,
46
+ allowInsecureLoopback: transport.config.allowInsecureLoopback,
47
+ };
48
+ };