@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,89 @@
1
+ /**
2
+ * Bounded, sanitized diagnostics for share-link service composition.
3
+ *
4
+ * The owner and public APIs fail closed for two states that are otherwise
5
+ * indistinguishable to an operator:
6
+ *
7
+ * - intentionally absent configuration (`status: 'disabled'`) is silent and
8
+ * inert; it is a supported deployment;
9
+ * - malformed/partial service wiring (`status: 'invalid'`) or a failed
10
+ * dependency build emits one fixed category so a misconfiguration is
11
+ * distinguishable from an unconfigured deployment.
12
+ *
13
+ * The reporter is bounded per process: each category is emitted at most once,
14
+ * so a flood of requests against a misconfigured deployment cannot flood logs.
15
+ * The event carries only a fixed allowlisted category string: never an
16
+ * environment value, URL, request body, DID key, title, note or ciphertext.
17
+ */
18
+
19
+ export const SHARE_LINK_COMPOSITION_DIAGNOSTIC = {
20
+ OWNER_CONFIGURATION_INVALID: 'share_link_owner_configuration_invalid',
21
+ OWNER_INITIALIZATION_FAILED: 'share_link_owner_initialization_failed',
22
+ PUBLIC_CONFIGURATION_INVALID: 'share_link_public_configuration_invalid',
23
+ PUBLIC_INITIALIZATION_FAILED: 'share_link_public_initialization_failed',
24
+ } as const;
25
+
26
+ export type ShareLinkCompositionDiagnosticCategory =
27
+ (typeof SHARE_LINK_COMPOSITION_DIAGNOSTIC)[keyof typeof SHARE_LINK_COMPOSITION_DIAGNOSTIC];
28
+
29
+ export type ShareLinkDiagnosticSink = (category: ShareLinkCompositionDiagnosticCategory) => void;
30
+
31
+ /** Aggregate-only sink matching the existing route reporting convention. */
32
+ export const defaultShareLinkDiagnosticSink: ShareLinkDiagnosticSink = category => {
33
+ console.warn('share_link_composition_diagnostic', { category });
34
+ };
35
+
36
+ /** Wrap a sink so each category is emitted at most once per process. */
37
+ export const createBoundedShareLinkDiagnosticReporter = (
38
+ sink: ShareLinkDiagnosticSink = defaultShareLinkDiagnosticSink
39
+ ): ShareLinkDiagnosticSink => {
40
+ const reported = new Set<ShareLinkCompositionDiagnosticCategory>();
41
+
42
+ return category => {
43
+ if (reported.has(category)) return;
44
+ reported.add(category);
45
+ sink(category);
46
+ };
47
+ };
48
+
49
+ type ShareLinkConfigStatus = { status: 'disabled' } | { status: 'invalid' } | { status: 'enabled' };
50
+
51
+ /**
52
+ * Compose the lazy getter a share-link router uses around a pure config
53
+ * resolution and a retryable initializer.
54
+ *
55
+ * Disabled configuration returns `null` silently. Invalid configuration reports
56
+ * a single sanitized diagnostic and returns `null`, preserving the anonymous
57
+ * not_found behavior. An enabled but failing build reports a single sanitized
58
+ * diagnostic and rethrows so the caller maps it to a generic error while the
59
+ * retryable initializer clears the failed attempt cache.
60
+ */
61
+ export const createShareLinkDependencyResolver = <TDependencies>(options: {
62
+ readonly resolveConfig: () => ShareLinkConfigStatus;
63
+ readonly initializeDependencies: () => Promise<TDependencies>;
64
+ readonly reportDiagnostic: ShareLinkDiagnosticSink;
65
+ readonly configurationInvalidCategory: ShareLinkCompositionDiagnosticCategory;
66
+ readonly initializationFailedCategory: ShareLinkCompositionDiagnosticCategory;
67
+ }): (() => Promise<TDependencies | null>) => {
68
+ return async () => {
69
+ const config = options.resolveConfig();
70
+
71
+ // Intentionally absent configuration: inert and silent.
72
+ if (config.status === 'disabled') return null;
73
+
74
+ // Malformed/partial wiring: one bounded diagnostic, still fail closed.
75
+ if (config.status === 'invalid') {
76
+ options.reportDiagnostic(options.configurationInvalidCategory);
77
+ return null;
78
+ }
79
+
80
+ try {
81
+ return await options.initializeDependencies();
82
+ } catch (error) {
83
+ // The initializer has already cleared its failed attempt; report the
84
+ // category once and let the caller surface a sanitized error.
85
+ options.reportDiagnostic(options.initializationFailedCategory);
86
+ throw error;
87
+ }
88
+ };
89
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Retryable lazy initialization for the owner share-link runtime.
3
+ *
4
+ * Successful and in-flight builds are shared across concurrent callers. A
5
+ * *failed* build must never be cached: a transient constraint/graph/signer
6
+ * failure would otherwise poison the owner API until the process restarts
7
+ * (the same defect previously corrected for the C7 setup cache). Only the
8
+ * failed attempt is cleared, so a later request retries while any other
9
+ * in-flight attempt keeps its own shared promise.
10
+ */
11
+ export const createRetryableLazyInitializer = <T>(build: () => Promise<T>): (() => Promise<T>) => {
12
+ let cached: Promise<T> | null = null;
13
+
14
+ return () => {
15
+ if (cached) return cached;
16
+
17
+ const attempt = build();
18
+ cached = attempt;
19
+
20
+ // Preserve the rejection for the awaiting caller, but detach it from the
21
+ // cache so the next call rebuilds. `catch` also prevents an unhandled
22
+ // rejection when nobody is awaiting this particular attempt.
23
+ attempt.catch(() => {
24
+ if (cached === attempt) cached = null;
25
+ });
26
+
27
+ return attempt;
28
+ };
29
+ };
@@ -0,0 +1,52 @@
1
+ import type { ShareLink } from '@learncard/types';
2
+
3
+ import type { ShareLinkRecord } from '../models/ShareLink';
4
+
5
+ /**
6
+ * Explicit owner projection of a `ShareLinkRecord`.
7
+ *
8
+ * This is a deliberate field-by-field mapping, never a spread: the raw record
9
+ * carries internal object refs, operation ids, leases, hashes, byte counts and
10
+ * ciphertext bindings that must never reach a caller. `contentUrl` is
11
+ * intentionally omitted here — the public resolve/content routes own the guarded
12
+ * relative URL, and a LearnCloud object URL (or a fabricated decryption key)
13
+ * must never be exposed.
14
+ *
15
+ * `viewCountingEnabled` is the FRESHLY re-resolved authoritative eligibility for
16
+ * the owner (never the stale `minorPolicyViewCountingEnabled` snapshot alone and
17
+ * never a caller field). When it is not exactly `true`, `viewCount` is omitted
18
+ * and `lastViewedAt` is nulled so a formerly eligible owner's historical counts
19
+ * stop leaking through get/mutation/status/retry.
20
+ */
21
+ export const toOwnerShareLink = (
22
+ record: ShareLinkRecord,
23
+ viewCountingEnabled?: boolean
24
+ ): ShareLink => {
25
+ const countingEnabled = viewCountingEnabled === true;
26
+
27
+ return {
28
+ id: record.id,
29
+ title: record.title,
30
+ ...(record.note === null ? {} : { note: record.note }),
31
+ selectedCount: record.selectedCount,
32
+ version: record.version,
33
+ contentVersion: record.contentVersion,
34
+ status: record.status,
35
+ contentState: record.contentState,
36
+ createdAt: record.createdAt,
37
+ updatedAt: record.updatedAt,
38
+ expiresAt: record.expiresAt,
39
+ stoppedAt: record.stoppedAt,
40
+ ...(countingEnabled
41
+ ? { lastViewedAt: record.lastViewedAt, viewCount: record.viewCount }
42
+ : { lastViewedAt: null }),
43
+ passcodeProtected: record.passcodeHash != null,
44
+ notifyOnView: record.notifyOnView === true,
45
+ minorPolicy: {
46
+ isMinor: record.minorPolicyIsMinor,
47
+ policyResolved: record.minorPolicyResolved,
48
+ defaultExpiryDays: record.minorPolicyDefaultExpiryDays === 365 ? 365 : 30,
49
+ viewCountingEnabled: record.minorPolicyViewCountingEnabled,
50
+ },
51
+ };
52
+ };
@@ -0,0 +1,36 @@
1
+ import cache from '@cache';
2
+ import { environment } from '@environment';
3
+
4
+ // The short shared window limits distributed guessing while bounding the time
5
+ // an attacker can deny access to a legitimate recipient. Per-source limits
6
+ // preserve capacity for other recipients until the shared budget is exhausted.
7
+ const WINDOW_SECONDS = 60;
8
+ // A successful recipient view checks the passcode at resolve and content, so
9
+ // it spends two attempts. Keep that in mind before lowering these budgets.
10
+ const SHARE_ATTEMPT_LIMIT = 24;
11
+ const SOURCE_ATTEMPT_LIMIT = 6;
12
+
13
+ const keys = (namespace: string, shareId: string, sourceIp?: string): [string, string] => [
14
+ `share-link-passcode-attempt:${namespace}:${shareId}`,
15
+ `share-link-passcode-attempt-source:${namespace}:${shareId}:${sourceIp ?? 'unknown'}`,
16
+ ];
17
+
18
+ export const reserveSharePasscodeAttempt = async (
19
+ namespace: string,
20
+ shareId: string,
21
+ sourceIp?: string
22
+ ): Promise<boolean> => {
23
+ // Process-local fallback counters cannot stop rotated-IP guesses across
24
+ // production workers. Protected access requires the shared Redis counter.
25
+ if (environment.NODE_ENV === 'production' && !cache.redis) return false;
26
+ const [shareKey, sourceKey] = keys(namespace, shareId, sourceIp);
27
+ // Reserve before Argon2. Redis INCR is atomic across Lambda instances, so
28
+ // simultaneous guesses cannot all read a stale budget. Count successful
29
+ // attempts too; no cross-key refund race and no stored submitted value.
30
+ const sourceCount = await cache.incr(sourceKey, WINDOW_SECONDS);
31
+ if (sourceCount === undefined || sourceCount > SOURCE_ATTEMPT_LIMIT) return false;
32
+ // Rejected requests from this source never consume another recipient's
33
+ // share-wide budget. Both reservations still precede Argon2.
34
+ const shareCount = await cache.incr(shareKey, WINDOW_SECONDS);
35
+ return shareCount !== undefined && shareCount <= SHARE_ATTEMPT_LIMIT;
36
+ };
@@ -0,0 +1,47 @@
1
+ import { randomBytes } from 'node:crypto';
2
+
3
+ import { argon2id, argon2Verify } from 'hash-wasm';
4
+
5
+ const ARGON2_MEMORY_KIB = 19_456;
6
+ const ARGON2_ITERATIONS = 2;
7
+ const ARGON2_PARALLELISM = 1;
8
+ const ARGON2_HASH_BYTES = 32;
9
+ const MAX_CONCURRENT_VERIFICATIONS = 4;
10
+ let activeVerifications = 0;
11
+
12
+ export class SharePasscodeCapacityError extends Error {
13
+ constructor() {
14
+ super('share passcode verification capacity exhausted');
15
+ this.name = 'SharePasscodeCapacityError';
16
+ }
17
+ }
18
+
19
+ /** Hash a share passcode as an Argon2id PHC string with a fresh 128-bit salt. */
20
+ export const hashSharePasscode = async (passcode: string): Promise<string> =>
21
+ argon2id({
22
+ password: passcode,
23
+ salt: randomBytes(16),
24
+ iterations: ARGON2_ITERATIONS,
25
+ memorySize: ARGON2_MEMORY_KIB,
26
+ parallelism: ARGON2_PARALLELISM,
27
+ hashLength: ARGON2_HASH_BYTES,
28
+ outputType: 'encoded',
29
+ });
30
+
31
+ /** Verify without ever logging, returning, or persisting the plaintext passcode. */
32
+ export const verifySharePasscode = async (
33
+ passcodeHash: string,
34
+ passcode: string
35
+ ): Promise<boolean> => {
36
+ // Local defense for long-lived workers; Lambda's cross-instance budget is
37
+ // reserved in Redis before this function runs. Capacity is not a wrong code.
38
+ if (activeVerifications >= MAX_CONCURRENT_VERIFICATIONS) throw new SharePasscodeCapacityError();
39
+ activeVerifications += 1;
40
+ try {
41
+ return await argon2Verify({ password: passcode, hash: passcodeHash });
42
+ } catch {
43
+ return false;
44
+ } finally {
45
+ activeVerifications -= 1;
46
+ }
47
+ };
@@ -0,0 +1,7 @@
1
+ export * from './types';
2
+ export {
3
+ composeShareLinkPolicy,
4
+ createShareLinkPolicyResolver,
5
+ resolveShareLinkExpiry,
6
+ } from './resolver';
7
+ export { createProductionShareLinkPolicySource } from './production';
@@ -0,0 +1,88 @@
1
+ import { getProfileByProfileId } from '@accesslayer/profile/read';
2
+ import { isProfileManaged } from '@accesslayer/profile/relationships/read';
3
+ import type { ShareViewEligibilitySource } from '@accesslayer/share-link/types';
4
+ import type { ShareLinkTransaction } from '@accesslayer/share-link/transaction';
5
+ import { transformProfileId } from '@helpers/profile.helpers';
6
+
7
+ import { composeShareLinkPolicy } from './resolver';
8
+ import type { ShareLinkOwnerAge, ShareLinkPolicySnapshot, ShareLinkPolicySource } from './types';
9
+
10
+ const ADULT_AGE = 18;
11
+
12
+ /** A missing or malformed birthdate never grants view tracking. */
13
+ export const ageFromPersistedProfile = (
14
+ profile: { dob?: unknown; type?: unknown } | null,
15
+ now: Date = new Date()
16
+ ): ShareLinkOwnerAge => {
17
+ if (!profile) return 'unknown';
18
+ if (profile.type === 'child') return 'minor';
19
+ if (typeof profile.dob !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(profile.dob)) {
20
+ return 'unknown';
21
+ }
22
+
23
+ const year = Number(profile.dob.slice(0, 4));
24
+ const month = Number(profile.dob.slice(5, 7));
25
+ const day = Number(profile.dob.slice(8, 10));
26
+ const birthday = new Date(Date.UTC(year, month - 1, day));
27
+ if (
28
+ birthday.getUTCFullYear() !== year ||
29
+ birthday.getUTCMonth() !== month - 1 ||
30
+ birthday.getUTCDate() !== day ||
31
+ birthday.getTime() > now.getTime()
32
+ ) {
33
+ return 'unknown';
34
+ }
35
+
36
+ const age =
37
+ now.getUTCFullYear() -
38
+ year -
39
+ (now.getUTCMonth() + 1 < month ||
40
+ (now.getUTCMonth() + 1 === month && now.getUTCDate() < day)
41
+ ? 1
42
+ : 0);
43
+
44
+ return age >= ADULT_AGE ? 'adult' : 'minor';
45
+ };
46
+
47
+ /**
48
+ * Profile birthdate is persisted server-side, but self-reported rather than
49
+ * independently verified. Unknown dates and child profiles stay restricted.
50
+ */
51
+ export const createProductionShareLinkPolicySource = (options?: {
52
+ resolveOwnerAge?: (profileId: string) => Promise<ShareLinkOwnerAge>;
53
+ }): ShareLinkPolicySource => ({
54
+ resolveOwnerAge:
55
+ options?.resolveOwnerAge ??
56
+ (async profileId => ageFromPersistedProfile(await getProfileByProfileId(profileId))),
57
+ isManaged: isProfileManaged,
58
+ });
59
+
60
+ /** Read current eligibility within the share lock; no request values or network I/O. */
61
+ export const resolveCurrentShareLinkPolicy = async (
62
+ tx: ShareLinkTransaction,
63
+ ownerProfileId: string,
64
+ now: Date
65
+ ): Promise<ShareLinkPolicySnapshot> => {
66
+ const result = await tx.run(
67
+ `MATCH (p:Profile {profileId: $profileId})
68
+ OPTIONAL MATCH (p)-[:MANAGED_BY]->(directManager:Profile)
69
+ OPTIONAL MATCH (manager:ProfileManager)-[:MANAGES]->(p)
70
+ RETURN p.dob AS dob, p.type AS profileType,
71
+ (directManager IS NOT NULL OR manager IS NOT NULL) AS isManaged
72
+ LIMIT 1`,
73
+ { profileId: transformProfileId(ownerProfileId) }
74
+ );
75
+ const record = result.records[0];
76
+ if (!record) return composeShareLinkPolicy('unknown', true);
77
+
78
+ return composeShareLinkPolicy(
79
+ ageFromPersistedProfile({ dob: record.get('dob'), type: record.get('profileType') }, now),
80
+ record.get('isManaged') !== false
81
+ );
82
+ };
83
+
84
+ export const productionShareViewEligibilitySource: ShareViewEligibilitySource = {
85
+ isEligible: async (tx, input) =>
86
+ (await resolveCurrentShareLinkPolicy(tx, input.ownerProfileId, input.now))
87
+ .viewCountingEnabled,
88
+ };
@@ -0,0 +1,108 @@
1
+ import { DEFAULT_SHARE_LINK_POLICY } from './types';
2
+ import type {
3
+ ShareLinkOwnerAge,
4
+ ShareLinkPolicyResolver,
5
+ ShareLinkPolicySnapshot,
6
+ ShareLinkPolicySource,
7
+ } from './types';
8
+
9
+ /**
10
+ * Pure policy decision table.
11
+ *
12
+ * - known minor, managed or unknown age => no views, 30 days
13
+ * - known unmanaged adult => views enabled, 365 days
14
+ *
15
+ */
16
+ export const composeShareLinkPolicy = (
17
+ age: ShareLinkOwnerAge,
18
+ isManaged: boolean
19
+ ): ShareLinkPolicySnapshot => {
20
+ const isMinor = age === 'minor' ? true : age === 'adult' ? false : null;
21
+ const policyResolved = age !== 'unknown';
22
+ const isUnmanagedAdult = age === 'adult' && !isManaged;
23
+
24
+ return {
25
+ isMinor,
26
+ policyResolved,
27
+ defaultExpiryDays: isUnmanagedAdult ? 365 : 30,
28
+ viewCountingEnabled: isUnmanagedAdult,
29
+ };
30
+ };
31
+
32
+ /**
33
+ * Build an injectable resolver over an authoritative source. Any source failure
34
+ * fails closed to the conservative default rather than assuming adult.
35
+ */
36
+ export const createShareLinkPolicyResolver = (
37
+ source: ShareLinkPolicySource
38
+ ): ShareLinkPolicyResolver => ({
39
+ resolve: async (profileId: string): Promise<ShareLinkPolicySnapshot> => {
40
+ try {
41
+ const [age, isManaged] = await Promise.all([
42
+ source.resolveOwnerAge(profileId),
43
+ source.isManaged(profileId),
44
+ ]);
45
+
46
+ return composeShareLinkPolicy(age, isManaged);
47
+ } catch {
48
+ return DEFAULT_SHARE_LINK_POLICY;
49
+ }
50
+ },
51
+ });
52
+
53
+ const DAY_MS = 24 * 60 * 60 * 1000;
54
+
55
+ /**
56
+ * Effective expiry: an explicit caller value (including explicit `null`) is
57
+ * preserved; only an omitted value receives the policy default. The default is
58
+ * derived from the policy at first reservation and then persisted, so a retry on
59
+ * a later clock replays the original value.
60
+ */
61
+ export const resolveShareLinkExpiry = (
62
+ policy: ShareLinkPolicySnapshot,
63
+ expiresAt: string | null | undefined,
64
+ now: Date
65
+ ): string | null => {
66
+ if (expiresAt !== undefined) return expiresAt;
67
+
68
+ return new Date(now.getTime() + policy.defaultExpiryDays * DAY_MS).toISOString();
69
+ };
70
+
71
+ /**
72
+ * Conservatively merge a share's currently committed policy with the policy a
73
+ * reservation intends to apply at finalization.
74
+ *
75
+ * A reservation can be created before the owner's authoritative policy changes
76
+ * and then finalized later by a replay or a recovery worker. Applying the stale
77
+ * reservation policy verbatim could re-enable view counting for an owner who has
78
+ * since become managed/minor. The merge therefore only ever tightens:
79
+ *
80
+ * - view counting stays enabled only if BOTH policies allow it;
81
+ * - a known minor on either side is a minor, an unknown on either side is
82
+ * unknown (never inferred adult);
83
+ * - the default expiry is the shorter of the two.
84
+ *
85
+ * Production finalization also rechecks current graph policy under the share
86
+ * lock before an explicit update can replace an old unknown snapshot.
87
+ */
88
+ export const mergeShareLinkPolicyConservatively = (
89
+ current: ShareLinkPolicySnapshot,
90
+ incoming: ShareLinkPolicySnapshot
91
+ ): ShareLinkPolicySnapshot => {
92
+ const isMinor =
93
+ current.isMinor === true || incoming.isMinor === true
94
+ ? true
95
+ : current.isMinor === false && incoming.isMinor === false
96
+ ? false
97
+ : null;
98
+
99
+ return {
100
+ isMinor,
101
+ policyResolved: current.policyResolved && incoming.policyResolved,
102
+ defaultExpiryDays:
103
+ current.defaultExpiryDays <= incoming.defaultExpiryDays
104
+ ? current.defaultExpiryDays
105
+ : incoming.defaultExpiryDays,
106
+ viewCountingEnabled: current.viewCountingEnabled && incoming.viewCountingEnabled,
107
+ };
108
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * LC-2187 owner share-link policy types.
3
+ *
4
+ * Policy is always derived server-side from trusted persisted state; nothing in
5
+ * this module reads the request body. The conservative default (unknown age,
6
+ * 30-day expiry, no view counting) is what an unavailable or untrustworthy age
7
+ * source produces — absence of a manager is NOT evidence of adulthood.
8
+ */
9
+
10
+ /** Result of consulting the authoritative server-side age source. */
11
+ export type ShareLinkOwnerAge = 'adult' | 'minor' | 'unknown';
12
+
13
+ /** Immutable policy snapshot persisted with the share under the write lock. */
14
+ export type ShareLinkPolicySnapshot = {
15
+ /** `true` known minor, `false` known adult, `null` unknown. */
16
+ isMinor: boolean | null;
17
+ /** `true` only when the age source returned a known adult/minor value. */
18
+ policyResolved: boolean;
19
+ defaultExpiryDays: 30 | 365;
20
+ viewCountingEnabled: boolean;
21
+ };
22
+
23
+ /**
24
+ * Authoritative server-side sources. Implementations must read persisted server
25
+ * state only; no request-derived value may be used as authority.
26
+ */
27
+ export type ShareLinkPolicySource = {
28
+ /**
29
+ * Resolve the owner's age. `unknown` is required when no trustworthy source
30
+ * exists; it must never be inferred from the absence of a manager.
31
+ */
32
+ resolveOwnerAge: (profileId: string) => Promise<ShareLinkOwnerAge>;
33
+ /** Whether the profile is currently managed by another profile. */
34
+ isManaged: (profileId: string) => Promise<boolean>;
35
+ };
36
+
37
+ /** Injectable server-side policy resolver used by the coordinator. */
38
+ export type ShareLinkPolicyResolver = {
39
+ resolve: (profileId: string) => Promise<ShareLinkPolicySnapshot>;
40
+ };
41
+
42
+ /** Fail-closed policy: no views, 30 days, age unknown. */
43
+ export const DEFAULT_SHARE_LINK_POLICY: ShareLinkPolicySnapshot = Object.freeze({
44
+ isMinor: null,
45
+ policyResolved: false,
46
+ defaultExpiryDays: 30,
47
+ viewCountingEnabled: false,
48
+ });
@@ -0,0 +1,41 @@
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 type ShareLinkPublicApiConfigResolution =
13
+ | { status: 'disabled' }
14
+ | { status: 'invalid' }
15
+ | {
16
+ status: 'enabled';
17
+ namespace: string;
18
+ origin: string;
19
+ audience: string;
20
+ allowInsecureLoopback: boolean;
21
+ };
22
+
23
+ export const resolveShareLinkPublicApiConfig = (
24
+ raw: unknown
25
+ ): ShareLinkPublicApiConfigResolution => {
26
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return { status: 'invalid' };
27
+
28
+ const source = raw as Record<string, unknown>;
29
+ if (!hasShareLinkServiceTransportConfig(source)) return { status: 'disabled' };
30
+
31
+ const transport = resolveShareLinkServiceTransportConfig(source);
32
+ if (transport.status !== 'enabled') return { status: 'invalid' };
33
+
34
+ return {
35
+ status: 'enabled',
36
+ namespace: transport.config.namespace,
37
+ origin: transport.config.origin,
38
+ audience: transport.config.audience,
39
+ allowInsecureLoopback: transport.config.allowInsecureLoopback,
40
+ };
41
+ };
@@ -0,0 +1,18 @@
1
+ import cache from '@cache';
2
+ import { environment } from '@environment';
3
+
4
+ const WINDOW_SECONDS = 10 * 60;
5
+
6
+ /** One owner alert per share per window, claimed atomically across Brain workers. */
7
+ export const claimShareViewNotification = async (
8
+ namespace: string,
9
+ shareId: string
10
+ ): Promise<boolean> => {
11
+ if (environment.NODE_ENV === 'production' && !cache.redis) return false;
12
+ const count = await cache.incr(
13
+ `share-link-view-notification:${namespace}:${shareId}`,
14
+ WINDOW_SECONDS
15
+ );
16
+ // A cache failure suppresses alerts, never privacy enforcement or view counts.
17
+ return count === 1;
18
+ };
@@ -5,6 +5,7 @@ import {
5
5
  type Context as AWSContext,
6
6
  } from 'aws-lambda';
7
7
  import { TRPCError } from '@trpc/server';
8
+ import { withInboxBatchBodyLimit } from './inbox-batch-http.helpers';
8
9
  import { type IncomingMessage, type ServerResponse } from 'http';
9
10
 
10
11
  import { OpenApiRouter } from 'trpc-to-openapi';
@@ -183,36 +184,36 @@ export const createOpenApiAwsLambdaHandler = <TRouter extends OpenApiRouter>(
183
184
  maxBodySize: undefined, // AWS Lambda handles body size limits
184
185
  });
185
186
 
186
- return async (
187
- event: AWSAPIGatewayEvent,
188
- _context: AWSContext
189
- ): Promise<APIGatewayProxyResult> => {
190
- try {
191
- const req = createRequestFromEvent(event);
192
- const { res, promise } = createResponseHandler();
193
-
194
- // Execute the handler
195
- await openApiHttpHandler(req, res);
196
-
197
- return await promise;
198
- } catch (cause) {
199
- // Handle any uncaught errors
200
- const error = cause instanceof Error ? cause : new Error('Unknown error');
201
- const statusCode =
202
- error instanceof TRPCError ? (error.code === 'NOT_FOUND' ? 404 : 500) : 500;
203
-
204
- return {
205
- statusCode,
206
- headers: {
207
- 'Content-Type': 'application/json',
208
- },
209
- body: JSON.stringify({
210
- error: {
211
- message: error.message || 'Internal server error',
212
- code: error instanceof TRPCError ? error.code : 'INTERNAL_SERVER_ERROR',
187
+ return withInboxBatchBodyLimit(
188
+ async (event: AWSAPIGatewayEvent, _context: AWSContext): Promise<APIGatewayProxyResult> => {
189
+ try {
190
+ const req = createRequestFromEvent(event);
191
+ const { res, promise } = createResponseHandler();
192
+
193
+ // Execute the handler
194
+ await openApiHttpHandler(req, res);
195
+
196
+ return await promise;
197
+ } catch (cause) {
198
+ // Handle any uncaught errors
199
+ const error = cause instanceof Error ? cause : new Error('Unknown error');
200
+ const statusCode =
201
+ error instanceof TRPCError ? (error.code === 'NOT_FOUND' ? 404 : 500) : 500;
202
+
203
+ return {
204
+ statusCode,
205
+ headers: {
206
+ 'Content-Type': 'application/json',
213
207
  },
214
- }),
215
- } satisfies APIGatewayProxyResult;
216
- }
217
- };
208
+ body: JSON.stringify({
209
+ error: {
210
+ message: error.message || 'Internal server error',
211
+ code: error instanceof TRPCError ? error.code : 'INTERNAL_SERVER_ERROR',
212
+ },
213
+ }),
214
+ } satisfies APIGatewayProxyResult;
215
+ }
216
+ },
217
+ 'openapi'
218
+ );
218
219
  };