@learncard/network-brain-service 3.19.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/.env.example +47 -82
  2. package/AGENTS.md +52 -0
  3. package/CHANGELOG.md +147 -0
  4. package/README.md +51 -2
  5. package/bootstrapLambda.test.ts +72 -0
  6. package/config/config.dev.json +29 -0
  7. package/config/config.example.json +29 -0
  8. package/config/config.json +1 -0
  9. package/config/config.production.json +29 -0
  10. package/config/config.scouts.dev.json +24 -0
  11. package/config/config.scouts.production.json +24 -0
  12. package/contractEventsLambda.ts +18 -0
  13. package/contractEventsLambdaApp.ts +19 -0
  14. package/didWebLambda.ts +13 -18
  15. package/didWebLambdaApp.ts +22 -0
  16. package/dist/accesslayer/app-manifest-version/create.d.ts +10 -0
  17. package/dist/accesslayer/app-manifest-version/create.d.ts.map +1 -0
  18. package/dist/accesslayer/app-manifest-version/read.d.ts +11 -0
  19. package/dist/accesslayer/app-manifest-version/read.d.ts.map +1 -0
  20. package/dist/accesslayer/app-manifest-version/update.d.ts +7 -0
  21. package/dist/accesslayer/app-manifest-version/update.d.ts.map +1 -0
  22. package/dist/accesslayer/app-store-listing/read.d.ts +2 -1
  23. package/dist/accesslayer/app-store-listing/read.d.ts.map +1 -1
  24. package/dist/accesslayer/app-store-listing/relationships/create.d.ts +1 -0
  25. package/dist/accesslayer/app-store-listing/relationships/create.d.ts.map +1 -1
  26. package/dist/accesslayer/app-store-listing/relationships/read.d.ts +2 -0
  27. package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
  28. package/dist/accesslayer/app-store-listing/update.d.ts +1 -0
  29. package/dist/accesslayer/app-store-listing/update.d.ts.map +1 -1
  30. package/dist/accesslayer/boost/relationships/create.d.ts +2 -1
  31. package/dist/accesslayer/boost/relationships/create.d.ts.map +1 -1
  32. package/dist/accesslayer/consentflowcontract/create.d.ts +9 -1
  33. package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
  34. package/dist/accesslayer/consentflowcontract/read.d.ts +34 -3
  35. package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
  36. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +4 -1
  37. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  38. package/dist/accesslayer/consentflowcontract/relationships/delete.d.ts.map +1 -1
  39. package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +3 -2
  40. package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
  41. package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts +12 -0
  42. package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts.map +1 -0
  43. package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts +12 -0
  44. package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts.map +1 -0
  45. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +8 -5
  46. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  47. package/dist/accesslayer/credential/create.d.ts +2 -1
  48. package/dist/accesslayer/credential/create.d.ts.map +1 -1
  49. package/dist/accesslayer/credential/relationships/create.d.ts +4 -3
  50. package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
  51. package/dist/accesslayer/inbox-credential/relationships/create.d.ts +2 -1
  52. package/dist/accesslayer/inbox-credential/relationships/create.d.ts.map +1 -1
  53. package/dist/accesslayer/inbox-credential/update.d.ts +2 -1
  54. package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
  55. package/dist/accesslayer/presentation/read.d.ts.map +1 -1
  56. package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
  57. package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
  58. package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
  59. package/dist/accesslayer/share-link/abandon.d.ts +3 -0
  60. package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
  61. package/dist/accesslayer/share-link/attachment.d.ts +37 -0
  62. package/dist/accesslayer/share-link/attachment.d.ts.map +1 -0
  63. package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
  64. package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
  65. package/dist/accesslayer/share-link/errors.d.ts +8 -0
  66. package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
  67. package/dist/accesslayer/share-link/finalize.d.ts +3 -0
  68. package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
  69. package/dist/accesslayer/share-link/helpers.d.ts +59 -0
  70. package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
  71. package/dist/accesslayer/share-link/index.d.ts +12 -0
  72. package/dist/accesslayer/share-link/index.d.ts.map +1 -0
  73. package/dist/accesslayer/share-link/list.d.ts +5 -0
  74. package/dist/accesslayer/share-link/list.d.ts.map +1 -0
  75. package/dist/accesslayer/share-link/read.d.ts +5 -0
  76. package/dist/accesslayer/share-link/read.d.ts.map +1 -0
  77. package/dist/accesslayer/share-link/receipt.d.ts +11 -0
  78. package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
  79. package/dist/accesslayer/share-link/recovery.d.ts +6 -0
  80. package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
  81. package/dist/accesslayer/share-link/reserve.d.ts +4 -0
  82. package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
  83. package/dist/accesslayer/share-link/revoke.d.ts +3 -0
  84. package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
  85. package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
  86. package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
  87. package/dist/accesslayer/share-link/transaction.d.ts +15 -0
  88. package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
  89. package/dist/accesslayer/share-link/types.d.ts +375 -0
  90. package/dist/accesslayer/share-link/types.d.ts.map +1 -0
  91. package/dist/app.d.ts +4 -0
  92. package/dist/app.d.ts.map +1 -1
  93. package/dist/cache/app-store.caches.d.ts +49 -0
  94. package/dist/cache/app-store.caches.d.ts.map +1 -1
  95. package/dist/config/applyDockerStageConfig.d.ts +2 -0
  96. package/dist/config/applyDockerStageConfig.d.ts.map +1 -0
  97. package/dist/config/environment.d.ts +14 -0
  98. package/dist/config/environment.d.ts.map +1 -1
  99. package/dist/config/serverlessFunctionEnv.test.d.ts +2 -0
  100. package/dist/config/serverlessFunctionEnv.test.d.ts.map +1 -0
  101. package/dist/config/stageConfig.d.ts +112 -0
  102. package/dist/config/stageConfig.d.ts.map +1 -0
  103. package/dist/config/stageConfig.test.d.ts +2 -0
  104. package/dist/config/stageConfig.test.d.ts.map +1 -0
  105. package/dist/constants/auth-grant.d.ts +2 -0
  106. package/dist/constants/auth-grant.d.ts.map +1 -1
  107. package/dist/didkit_wasm_bg.wasm +0 -0
  108. package/dist/docker-entry.d.ts +1 -1
  109. package/dist/docker-entry.d.ts.map +1 -1
  110. package/dist/helpers/app-manifest.helpers.d.ts +5 -0
  111. package/dist/helpers/app-manifest.helpers.d.ts.map +1 -0
  112. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  113. package/dist/helpers/claim-hooks.helpers.d.ts +2 -1
  114. package/dist/helpers/claim-hooks.helpers.d.ts.map +1 -1
  115. package/dist/helpers/consent-referral.helpers.d.ts +2 -0
  116. package/dist/helpers/consent-referral.helpers.d.ts.map +1 -0
  117. package/dist/helpers/consent-scopes.helpers.d.ts +7 -0
  118. package/dist/helpers/consent-scopes.helpers.d.ts.map +1 -0
  119. package/dist/helpers/contract-event-maintenance.helpers.d.ts +3 -0
  120. package/dist/helpers/contract-event-maintenance.helpers.d.ts.map +1 -0
  121. package/dist/helpers/contract-events.helpers.d.ts +25 -0
  122. package/dist/helpers/contract-events.helpers.d.ts.map +1 -0
  123. package/dist/helpers/contract-requests.helpers.d.ts +19 -0
  124. package/dist/helpers/contract-requests.helpers.d.ts.map +1 -0
  125. package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
  126. package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
  127. package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
  128. package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
  129. package/dist/helpers/finalize-inbox.helpers.d.ts +3 -3
  130. package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
  131. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  132. package/dist/helpers/inline-template.helpers.d.ts +45 -0
  133. package/dist/helpers/inline-template.helpers.d.ts.map +1 -0
  134. package/dist/helpers/notificationMessages.d.ts +1 -1
  135. package/dist/helpers/notificationMessages.d.ts.map +1 -1
  136. package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
  137. package/dist/helpers/notifications.helpers.d.ts +6 -0
  138. package/dist/helpers/notifications.helpers.d.ts.map +1 -1
  139. package/dist/helpers/presentation-metadata.d.ts +4 -0
  140. package/dist/helpers/presentation-metadata.d.ts.map +1 -0
  141. package/dist/helpers/presentation.helpers.d.ts +1 -1
  142. package/dist/helpers/presentation.helpers.d.ts.map +1 -1
  143. package/dist/helpers/profile.helpers.d.ts +2 -0
  144. package/dist/helpers/profile.helpers.d.ts.map +1 -1
  145. package/dist/helpers/share-content-client/adapters.d.ts +10 -0
  146. package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
  147. package/dist/helpers/share-content-client/canonical.d.ts +7 -0
  148. package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
  149. package/dist/helpers/share-content-client/client.d.ts +5 -0
  150. package/dist/helpers/share-content-client/client.d.ts.map +1 -0
  151. package/dist/helpers/share-content-client/config.d.ts +11 -0
  152. package/dist/helpers/share-content-client/config.d.ts.map +1 -0
  153. package/dist/helpers/share-content-client/index.d.ts +6 -0
  154. package/dist/helpers/share-content-client/index.d.ts.map +1 -0
  155. package/dist/helpers/share-content-client/responses.d.ts +16 -0
  156. package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
  157. package/dist/helpers/share-content-client/types.d.ts +145 -0
  158. package/dist/helpers/share-content-client/types.d.ts.map +1 -0
  159. package/dist/helpers/share-link-attachment-access.d.ts +4 -0
  160. package/dist/helpers/share-link-attachment-access.d.ts.map +1 -0
  161. package/dist/helpers/share-link-attachment.d.ts +18 -0
  162. package/dist/helpers/share-link-attachment.d.ts.map +1 -0
  163. package/dist/helpers/share-link-coordinator/attachment-stat.d.ts +5 -0
  164. package/dist/helpers/share-link-coordinator/attachment-stat.d.ts.map +1 -0
  165. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
  166. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
  167. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
  168. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
  169. package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
  170. package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
  171. package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
  172. package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
  173. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
  174. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
  175. package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
  176. package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
  177. package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
  178. package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
  179. package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
  180. package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
  181. package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
  182. package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
  183. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
  184. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
  185. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
  186. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
  187. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
  188. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
  189. package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
  190. package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
  191. package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
  192. package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
  193. package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
  194. package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
  195. package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
  196. package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
  197. package/dist/helpers/share-link-maintenance/runner.d.ts +20 -0
  198. package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
  199. package/dist/helpers/share-link-maintenance/runtime.d.ts +38 -0
  200. package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
  201. package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
  202. package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
  203. package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
  204. package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
  205. package/dist/helpers/share-link-maintenance/types.d.ts +56 -0
  206. package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
  207. package/dist/helpers/share-link-owner/config.d.ts +16 -0
  208. package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
  209. package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
  210. package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
  211. package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
  212. package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
  213. package/dist/helpers/share-link-owner-projection.d.ts +4 -0
  214. package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
  215. package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
  216. package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
  217. package/dist/helpers/share-link-passcode.d.ts +6 -0
  218. package/dist/helpers/share-link-passcode.d.ts.map +1 -0
  219. package/dist/helpers/share-link-policy/index.d.ts +4 -0
  220. package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
  221. package/dist/helpers/share-link-policy/production.d.ts +11 -0
  222. package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
  223. package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
  224. package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
  225. package/dist/helpers/share-link-policy/types.d.ts +22 -0
  226. package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
  227. package/dist/helpers/share-link-public/config.d.ts +13 -0
  228. package/dist/helpers/share-link-public/config.d.ts.map +1 -0
  229. package/dist/helpers/share-link-view-notification.d.ts +2 -0
  230. package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
  231. package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
  232. package/dist/helpers/smartResume.helpers.d.ts +3 -0
  233. package/dist/helpers/smartResume.helpers.d.ts.map +1 -0
  234. package/dist/index.js +2 -2
  235. package/dist/models/AppManifestVersion.d.ts +20 -0
  236. package/dist/models/AppManifestVersion.d.ts.map +1 -0
  237. package/dist/models/AppStoreListing.d.ts +13 -0
  238. package/dist/models/AppStoreListing.d.ts.map +1 -1
  239. package/dist/models/ConsentFlowContract.d.ts +13 -2
  240. package/dist/models/ConsentFlowContract.d.ts.map +1 -1
  241. package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
  242. package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
  243. package/dist/models/Integration.d.ts +2 -0
  244. package/dist/models/Integration.d.ts.map +1 -1
  245. package/dist/models/Presentation.d.ts +2 -0
  246. package/dist/models/Presentation.d.ts.map +1 -1
  247. package/dist/models/Profile.d.ts +2 -0
  248. package/dist/models/Profile.d.ts.map +1 -1
  249. package/dist/models/ShareLink.d.ts +46 -0
  250. package/dist/models/ShareLink.d.ts.map +1 -0
  251. package/dist/models/ShareViewReceipt.d.ts +22 -0
  252. package/dist/models/ShareViewReceipt.d.ts.map +1 -0
  253. package/dist/models/index.d.ts +2 -0
  254. package/dist/models/index.d.ts.map +1 -1
  255. package/dist/models/share-link-constraints.d.ts +3 -0
  256. package/dist/models/share-link-constraints.d.ts.map +1 -0
  257. package/dist/openapi.d.ts +1 -1
  258. package/dist/openapi.d.ts.map +1 -1
  259. package/dist/public-share-link-http.d.ts +4 -0
  260. package/dist/public-share-link-http.d.ts.map +1 -0
  261. package/dist/routes/app-store.d.ts +619 -0
  262. package/dist/routes/app-store.d.ts.map +1 -1
  263. package/dist/routes/boosts.d.ts +2 -2
  264. package/dist/routes/boosts.d.ts.map +1 -1
  265. package/dist/routes/contracts.d.ts +148 -5
  266. package/dist/routes/contracts.d.ts.map +1 -1
  267. package/dist/routes/credentials.d.ts +35 -1
  268. package/dist/routes/credentials.d.ts.map +1 -1
  269. package/dist/routes/inbox.d.ts +52 -2
  270. package/dist/routes/inbox.d.ts.map +1 -1
  271. package/dist/routes/index.d.ts +153 -0
  272. package/dist/routes/index.d.ts.map +1 -1
  273. package/dist/routes/presentations.d.ts +1 -0
  274. package/dist/routes/presentations.d.ts.map +1 -1
  275. package/dist/routes/profile-manager.d.ts.map +1 -1
  276. package/dist/routes/profiles.d.ts.map +1 -1
  277. package/dist/routes/public-share-links.d.ts +285 -0
  278. package/dist/routes/public-share-links.d.ts.map +1 -0
  279. package/dist/routes/share-links.d.ts +1005 -0
  280. package/dist/routes/share-links.d.ts.map +1 -0
  281. package/dist/routes/test.d.ts +5 -0
  282. package/dist/routes/test.d.ts.map +1 -1
  283. package/dist/routes/workflows.d.ts.map +1 -1
  284. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  285. package/dist/types/app-manifest.d.ts +339 -0
  286. package/dist/types/app-manifest.d.ts.map +1 -0
  287. package/dist/types/app-store-listing.d.ts +34 -0
  288. package/dist/types/app-store-listing.d.ts.map +1 -1
  289. package/dist/types/consentflowcontract.d.ts +31 -4
  290. package/dist/types/consentflowcontract.d.ts.map +1 -1
  291. package/dist/types/credential-refresh.d.ts +1 -1
  292. package/esbuildPlugins.cjs +1 -0
  293. package/lambda.ts +49 -192
  294. package/lambdaApp.ts +203 -0
  295. package/package.json +25 -17
  296. package/scripts/export-openapi.ts +20 -0
  297. package/scripts/offline-schema-environment.ts +18 -0
  298. package/scripts/seed-demo-persona.ts +425 -0
  299. package/scripts/verify-python-contracts.ts +72 -0
  300. package/serverless-local.yml +3 -6
  301. package/serverless.function-env.cjs +50 -0
  302. package/serverless.local.yml +0 -1
  303. package/serverless.yml +44 -55
  304. package/shareLinkMaintenanceLambda.ts +18 -0
  305. package/shareLinkMaintenanceLambdaApp.ts +50 -0
  306. package/src/accesslayer/app-manifest-version/create.ts +57 -0
  307. package/src/accesslayer/app-manifest-version/read.ts +129 -0
  308. package/src/accesslayer/app-manifest-version/update.ts +61 -0
  309. package/src/accesslayer/app-store-listing/read.ts +6 -0
  310. package/src/accesslayer/app-store-listing/relationships/create.ts +20 -0
  311. package/src/accesslayer/app-store-listing/relationships/read.ts +24 -1
  312. package/src/accesslayer/app-store-listing/update.ts +15 -0
  313. package/src/accesslayer/boost/relationships/create.ts +8 -2
  314. package/src/accesslayer/consentflowcontract/create.ts +81 -8
  315. package/src/accesslayer/consentflowcontract/read.ts +84 -10
  316. package/src/accesslayer/consentflowcontract/relationships/create.ts +125 -109
  317. package/src/accesslayer/consentflowcontract/relationships/delete.ts +1 -0
  318. package/src/accesslayer/consentflowcontract/relationships/read.ts +184 -108
  319. package/src/accesslayer/consentflowcontract/relationships/recipients.ts +125 -0
  320. package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +125 -0
  321. package/src/accesslayer/consentflowcontract/relationships/update.ts +255 -217
  322. package/src/accesslayer/credential/create.test.ts +2 -115
  323. package/src/accesslayer/credential/create.ts +11 -6
  324. package/src/accesslayer/credential/relationships/create.ts +17 -10
  325. package/src/accesslayer/inbox-credential/relationships/create.ts +70 -48
  326. package/src/accesslayer/inbox-credential/update.ts +4 -2
  327. package/src/accesslayer/presentation/read.ts +57 -25
  328. package/src/accesslayer/presentation/relationships/create.ts +54 -11
  329. package/src/accesslayer/presentation/relationships/read.ts +21 -6
  330. package/src/accesslayer/share-link/abandon.ts +120 -0
  331. package/src/accesslayer/share-link/attachment.ts +360 -0
  332. package/src/accesslayer/share-link/cleanup.ts +271 -0
  333. package/src/accesslayer/share-link/errors.ts +32 -0
  334. package/src/accesslayer/share-link/finalize.ts +313 -0
  335. package/src/accesslayer/share-link/helpers.ts +450 -0
  336. package/src/accesslayer/share-link/index.ts +11 -0
  337. package/src/accesslayer/share-link/list.ts +162 -0
  338. package/src/accesslayer/share-link/read.ts +107 -0
  339. package/src/accesslayer/share-link/receipt.ts +548 -0
  340. package/src/accesslayer/share-link/recovery.ts +565 -0
  341. package/src/accesslayer/share-link/reserve.ts +763 -0
  342. package/src/accesslayer/share-link/revoke.ts +232 -0
  343. package/src/accesslayer/share-link/transaction-execution.ts +72 -0
  344. package/src/accesslayer/share-link/transaction.ts +126 -0
  345. package/src/accesslayer/share-link/types.ts +606 -0
  346. package/src/app.ts +6 -0
  347. package/src/cache/app-store.caches.ts +35 -0
  348. package/src/config/applyDockerStageConfig.ts +6 -0
  349. package/src/config/environment.test.ts +17 -12
  350. package/src/config/environment.ts +15 -0
  351. package/src/config/serverlessFunctionEnv.test.ts +105 -0
  352. package/src/config/stageConfig.test.ts +25 -0
  353. package/src/config/stageConfig.ts +24 -0
  354. package/src/constants/auth-grant.ts +5 -0
  355. package/src/docker-entry.ts +81 -0
  356. package/src/helpers/app-manifest.helpers.ts +185 -0
  357. package/src/helpers/boost.helpers.ts +4 -31
  358. package/src/helpers/claim-hooks.helpers.ts +20 -7
  359. package/src/helpers/consent-referral.helpers.ts +6 -0
  360. package/src/helpers/consent-scopes.helpers.ts +58 -0
  361. package/src/helpers/contract-event-maintenance.helpers.ts +43 -0
  362. package/src/helpers/contract-events.helpers.ts +540 -0
  363. package/src/helpers/contract-requests.helpers.ts +211 -0
  364. package/src/helpers/credentialSubject.helpers.test.ts +114 -0
  365. package/src/helpers/credentialSubject.helpers.ts +40 -0
  366. package/src/helpers/finalize-inbox.helpers.ts +163 -44
  367. package/src/helpers/inbox.helpers.ts +58 -70
  368. package/src/helpers/inline-template.helpers.ts +560 -0
  369. package/src/helpers/notificationMessages.ts +77 -1
  370. package/src/helpers/notificationQueue.helpers.ts +23 -5
  371. package/src/helpers/notifications.helpers.ts +25 -1
  372. package/src/helpers/presentation-metadata.ts +41 -0
  373. package/src/helpers/presentation.helpers.ts +27 -20
  374. package/src/helpers/profile.helpers.ts +13 -0
  375. package/src/helpers/share-content-client/adapters.ts +55 -0
  376. package/src/helpers/share-content-client/canonical.ts +25 -0
  377. package/src/helpers/share-content-client/client.ts +440 -0
  378. package/src/helpers/share-content-client/config.ts +189 -0
  379. package/src/helpers/share-content-client/index.ts +27 -0
  380. package/src/helpers/share-content-client/responses.ts +273 -0
  381. package/src/helpers/share-content-client/types.ts +253 -0
  382. package/src/helpers/share-link-attachment-access.ts +87 -0
  383. package/src/helpers/share-link-attachment.ts +115 -0
  384. package/src/helpers/share-link-coordinator/attachment-stat.ts +41 -0
  385. package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
  386. package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
  387. package/src/helpers/share-link-coordinator/coordinator.ts +727 -0
  388. package/src/helpers/share-link-coordinator/index.ts +8 -0
  389. package/src/helpers/share-link-coordinator/recovery-runner.ts +443 -0
  390. package/src/helpers/share-link-coordinator/runtime.ts +88 -0
  391. package/src/helpers/share-link-coordinator/stat.ts +30 -0
  392. package/src/helpers/share-link-coordinator/types.ts +353 -0
  393. package/src/helpers/share-link-lifecycle/index.ts +4 -0
  394. package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
  395. package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
  396. package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
  397. package/src/helpers/share-link-lifecycle/types.ts +58 -0
  398. package/src/helpers/share-link-maintenance/budget.ts +63 -0
  399. package/src/helpers/share-link-maintenance/config.ts +312 -0
  400. package/src/helpers/share-link-maintenance/index.ts +24 -0
  401. package/src/helpers/share-link-maintenance/runner.ts +251 -0
  402. package/src/helpers/share-link-maintenance/runtime.ts +340 -0
  403. package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
  404. package/src/helpers/share-link-maintenance/telemetry.ts +73 -0
  405. package/src/helpers/share-link-maintenance/types.ts +91 -0
  406. package/src/helpers/share-link-owner/config.ts +48 -0
  407. package/src/helpers/share-link-owner/diagnostics.ts +89 -0
  408. package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
  409. package/src/helpers/share-link-owner-projection.ts +54 -0
  410. package/src/helpers/share-link-passcode-abuse.ts +36 -0
  411. package/src/helpers/share-link-passcode.ts +47 -0
  412. package/src/helpers/share-link-policy/index.ts +7 -0
  413. package/src/helpers/share-link-policy/production.ts +108 -0
  414. package/src/helpers/share-link-policy/resolver.ts +113 -0
  415. package/src/helpers/share-link-policy/types.ts +56 -0
  416. package/src/helpers/share-link-public/config.ts +41 -0
  417. package/src/helpers/share-link-view-notification.ts +18 -0
  418. package/src/helpers/signingAuthority.helpers.ts +23 -14
  419. package/src/helpers/smartResume.helpers.ts +123 -0
  420. package/src/models/AppManifestVersion.ts +43 -0
  421. package/src/models/AppStoreListing.ts +32 -0
  422. package/src/models/ConsentFlowContract.ts +34 -3
  423. package/src/models/ConsentFlowTerms.ts +10 -0
  424. package/src/models/ConsentFlowTransaction.ts +3 -0
  425. package/src/models/Integration.ts +2 -0
  426. package/src/models/Presentation.ts +2 -2
  427. package/src/models/Profile.ts +3 -2
  428. package/src/models/ShareLink.ts +134 -0
  429. package/src/models/ShareViewReceipt.ts +69 -0
  430. package/src/models/index.ts +25 -0
  431. package/src/models/share-link-constraints.ts +98 -0
  432. package/src/openapi.ts +44 -8
  433. package/src/public-share-link-http.ts +49 -0
  434. package/src/routes/app-store.ts +1056 -38
  435. package/src/routes/boosts.ts +14 -25
  436. package/src/routes/contracts.ts +384 -273
  437. package/src/routes/inbox.ts +4 -2
  438. package/src/routes/index.ts +142 -44
  439. package/src/routes/presentations.ts +19 -3
  440. package/src/routes/profile-manager.ts +29 -2
  441. package/src/routes/profiles.ts +29 -6
  442. package/src/routes/public-share-links.ts +983 -0
  443. package/src/routes/share-links.ts +709 -0
  444. package/src/routes/workflows.ts +3 -2
  445. package/src/services/delivery/adapters/postmark.adapter.ts +4 -1
  446. package/src/types/app-manifest.ts +28 -0
  447. package/src/types/app-store-listing.ts +13 -0
  448. package/src/types/consentflowcontract.ts +25 -4
  449. package/test/app-manifest.helpers.spec.ts +170 -0
  450. package/test/app-manifest.routes.spec.ts +299 -0
  451. package/test/app-store-listing-updates.spec.ts +236 -0
  452. package/test/app-store-listing.spec.ts +65 -3
  453. package/test/consent-data-boundaries.spec.ts +352 -0
  454. package/test/consent-recipients.spec.ts +513 -0
  455. package/test/consent-scopes.helpers.spec.ts +67 -0
  456. package/test/consentflow.spec.ts +63 -2
  457. package/test/contract-requests.spec.ts +1588 -0
  458. package/test/credential-storage.neo4j.spec.ts +122 -0
  459. package/test/dids.spec.ts +10 -1
  460. package/test/guardian-gated-routes.spec.ts +49 -0
  461. package/test/helpers/share-link-fixtures.ts +94 -0
  462. package/test/inbox-security.e2e.ts +26 -10
  463. package/test/inbox.spec.ts +184 -8
  464. package/test/inline-template.helpers.spec.ts +49 -0
  465. package/test/managed-recipient-inbox.spec.ts +152 -0
  466. package/test/notificationMessages.spec.ts +13 -0
  467. package/test/openapi.spec.ts +14 -53
  468. package/test/presentations.spec.ts +54 -6
  469. package/test/profiles.spec.ts +97 -1
  470. package/test/share-content-client.contract.spec.ts +365 -0
  471. package/test/share-content-client.unit.spec.ts +462 -0
  472. package/test/share-link-abuse.unit.spec.ts +65 -0
  473. package/test/share-link-attachment.neo4j.spec.ts +262 -0
  474. package/test/share-link-attachment.unit.spec.ts +244 -0
  475. package/test/share-link-coordinator.neo4j.spec.ts +684 -0
  476. package/test/share-link-coordinator.unit.spec.ts +990 -0
  477. package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
  478. package/test/share-link-lifecycle.neo4j.spec.ts +1411 -0
  479. package/test/share-link-lifecycle.unit.spec.ts +205 -0
  480. package/test/share-link-list-cursor.unit.spec.ts +86 -0
  481. package/test/share-link-list.neo4j.spec.ts +324 -0
  482. package/test/share-link-maintenance.neo4j.spec.ts +355 -0
  483. package/test/share-link-maintenance.unit.spec.ts +1140 -0
  484. package/test/share-link-owner-policy.unit.spec.ts +210 -0
  485. package/test/share-link-owner-route.unit.spec.ts +820 -0
  486. package/test/share-link-owner-runtime.unit.spec.ts +102 -0
  487. package/test/share-link-passcode.unit.spec.ts +29 -0
  488. package/test/share-link-policy.unit.spec.ts +149 -0
  489. package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
  490. package/test/share-link-production-policy.unit.spec.ts +126 -0
  491. package/test/share-link-public-config.unit.spec.ts +64 -0
  492. package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
  493. package/test/share-link-public-route.unit.spec.ts +1079 -0
  494. package/test/share-link-receipt.neo4j.spec.ts +943 -0
  495. package/test/share-link-recovery.neo4j.spec.ts +935 -0
  496. package/test/share-link-recovery.unit.spec.ts +726 -0
  497. package/test/share-link-service-policy.neo4j.spec.ts +403 -0
  498. package/test/signing-authority-encryption.spec.ts +75 -0
  499. package/test/smart-resume-consent.spec.ts +233 -0
  500. package/test/smart-resume-upload.spec.ts +65 -0
  501. package/tsconfig.build.json +1 -1
  502. package/tsconfig.json +1 -0
  503. package/vitest.config.ts +14 -0
  504. package/vitest.inbox-e2e.config.ts +2 -0
  505. package/vitest.share-links-neo4j.config.ts +19 -0
  506. package/vitest.share-links-unit.config.ts +28 -0
  507. package/workerLambda.test.ts +100 -0
@@ -0,0 +1,271 @@
1
+ import { int } from 'neo4j-driver';
2
+
3
+ import {
4
+ SHARE_LINK_CLEANUP_DEFAULT_BATCH,
5
+ SHARE_LINK_CLEANUP_DEFAULT_CLAIM_MS,
6
+ SHARE_LINK_CLEANUP_MAX_BATCH,
7
+ SHARE_LINK_CLEANUP_MAX_CLAIM_MS,
8
+ SHARE_LINK_CLEANUP_MIN_CLAIM_MS,
9
+ computeCleanupBackoffMs,
10
+ computeLeaseExpiry,
11
+ } from '@helpers/share-link-lifecycle';
12
+ import { neogma } from '@instance';
13
+
14
+ import { ensureShareLinkConstraints } from '../../models/share-link-constraints';
15
+ import { failShareLink } from './errors';
16
+ import { generateClaimToken, readNodeProperties, toShareContentCleanupJobRecord } from './helpers';
17
+ import { withShareLinkTransaction, type ShareLinkTransaction } from './transaction';
18
+ import type {
19
+ ClaimCleanupJobsInput,
20
+ ClaimCleanupJobsResult,
21
+ CompleteCleanupJobInput,
22
+ CompleteCleanupJobResult,
23
+ ShareContentCleanupJobRecord,
24
+ } from './types';
25
+
26
+ const clampLimit = (limit: number | undefined): number => {
27
+ if (!Number.isSafeInteger(limit) || (limit as number) < 1)
28
+ return SHARE_LINK_CLEANUP_DEFAULT_BATCH;
29
+
30
+ return Math.min(limit as number, SHARE_LINK_CLEANUP_MAX_BATCH);
31
+ };
32
+
33
+ const clampClaimMs = (claimMs: number | undefined): number => {
34
+ if (!Number.isSafeInteger(claimMs)) return SHARE_LINK_CLEANUP_DEFAULT_CLAIM_MS;
35
+
36
+ return Math.min(
37
+ Math.max(claimMs as number, SHARE_LINK_CLEANUP_MIN_CLAIM_MS),
38
+ SHARE_LINK_CLEANUP_MAX_CLAIM_MS
39
+ );
40
+ };
41
+
42
+ /**
43
+ * Require an explicit namespace before any graph access. This is intentionally
44
+ * only an exact, non-empty-string check: the scheduler additionally enforces the
45
+ * C1 opaque-identifier rule, while the reviewed lifecycle tests keep their
46
+ * legacy `localhost%3A3000` namespace at the repository layer.
47
+ */
48
+ const assertExplicitNamespace = (namespace: unknown): string => {
49
+ if (typeof namespace !== 'string' || namespace.length === 0) {
50
+ failShareLink('INVALID_INPUT', 'an explicit cleanup namespace is required');
51
+ }
52
+
53
+ return namespace;
54
+ };
55
+
56
+ const readCleanupJob = async (
57
+ tx: ShareLinkTransaction,
58
+ objectRef: string
59
+ ): Promise<ShareContentCleanupJobRecord | null> => {
60
+ const result = await tx.run(
61
+ 'MATCH (c:ShareContentCleanupJob {objectRef: $objectRef}) RETURN c LIMIT 1',
62
+ { objectRef }
63
+ );
64
+ const props = readNodeProperties(result, 'c');
65
+
66
+ return props ? toShareContentCleanupJobRecord(props) : null;
67
+ };
68
+
69
+ /**
70
+ * Claims a bounded batch of due cleanup jobs for one explicit namespace.
71
+ *
72
+ * The candidate scan and the post-lock reread both filter on the exact
73
+ * namespace, so a worker never claims a foreign namespace's job. Every clock read
74
+ * used for eligibility and for the new claim expiry happens *inside* the managed
75
+ * transaction after the job write lock is taken (and therefore afresh on each
76
+ * driver retry); production callers omit `now`, tests inject it.
77
+ *
78
+ * The claim is fenced: `completeCleanupJob` only accepts the unique `claimToken`
79
+ * returned here, so a claim whose bounded lease expired cannot complete a job that
80
+ * another worker has since claimed. Jobs are returned in due order; the caller
81
+ * performs the actual exact-object LearnCloud deletion and then completes the job.
82
+ */
83
+ export const claimCleanupJobs = async (
84
+ input: ClaimCleanupJobsInput
85
+ ): Promise<ClaimCleanupJobsResult> => {
86
+ const namespace = assertExplicitNamespace(input.namespace);
87
+
88
+ await ensureShareLinkConstraints();
89
+
90
+ const limit = clampLimit(input.limit);
91
+ const claimMs = clampClaimMs(input.claimMs);
92
+
93
+ return withShareLinkTransaction(
94
+ async tx => {
95
+ // Discovery prefilter only. Eligibility is re-checked against the
96
+ // post-lock clock below, so this timestamp cannot authorise a claim.
97
+ const discoveryNow = (input.now ?? new Date()).toISOString();
98
+ const candidates = await tx.run(
99
+ `MATCH (c:ShareContentCleanupJob)
100
+ WHERE c.namespace = $namespace
101
+ AND ((c.status = 'queued' AND c.nextAttemptAt <= $discoveryNow)
102
+ OR (c.status = 'claimed' AND c.claimExpiresAt <= $discoveryNow))
103
+ RETURN c.objectRef AS objectRef ORDER BY c.objectRef LIMIT $limit`,
104
+ { namespace, discoveryNow, limit: int(limit) }
105
+ );
106
+ const claimToken = generateClaimToken();
107
+ const jobs: ShareContentCleanupJobRecord[] = [];
108
+ for (const candidate of candidates.records) {
109
+ const objectRef = candidate.get('objectRef');
110
+ const locked = await tx.run(
111
+ `MATCH (c:ShareContentCleanupJob {objectRef: $objectRef})
112
+ SET c.lockTick = coalesce(c.lockTick, 0) + 1 RETURN c`,
113
+ { objectRef }
114
+ );
115
+ const props = readNodeProperties(locked, 'c');
116
+ if (!props) continue;
117
+
118
+ // Fresh clock after the lock (and on every managed retry).
119
+ const now = input.now ?? new Date();
120
+ const nowIso = now.toISOString();
121
+ const job = toShareContentCleanupJobRecord(props);
122
+ if (job.namespace !== namespace) continue;
123
+ if (!(
124
+ (job.status === 'queued' && job.nextAttemptAt <= nowIso) ||
125
+ (job.status === 'claimed' && job.claimExpiresAt && job.claimExpiresAt <= nowIso)
126
+ ))
127
+ continue;
128
+
129
+ const claimExpiresAt = computeLeaseExpiry(now, claimMs);
130
+ const updated = await tx.run(
131
+ `MATCH (c:ShareContentCleanupJob {objectRef: $objectRef, namespace: $namespace})
132
+ SET c.status = 'claimed', c.claimedBy = $claimant, c.claimToken = $claimToken,
133
+ c.claimExpiresAt = $claimExpiresAt, c.updatedAt = $now RETURN c`,
134
+ {
135
+ objectRef,
136
+ namespace,
137
+ claimant: input.claimant,
138
+ claimToken,
139
+ claimExpiresAt,
140
+ now: nowIso,
141
+ }
142
+ );
143
+ const updatedProps = readNodeProperties(updated, 'c');
144
+ if (updatedProps) jobs.push(toShareContentCleanupJobRecord(updatedProps));
145
+ }
146
+
147
+ return { claimToken, jobs };
148
+ },
149
+ { timeoutMs: input.transactionTimeoutMs, noInlineRetry: input.noInlineRetry }
150
+ );
151
+ };
152
+
153
+ /**
154
+ * Completes a claimed cleanup job (`completed`) or returns it to the queue with
155
+ * exponential backoff (`retry`). A missing/mismatched claim token yields
156
+ * `claim_lost`; this repository never deletes LearnCloud content itself.
157
+ */
158
+ export const completeCleanupJob = async (
159
+ input: CompleteCleanupJobInput
160
+ ): Promise<CompleteCleanupJobResult> => {
161
+ await ensureShareLinkConstraints();
162
+
163
+ return withShareLinkTransaction(
164
+ async tx => {
165
+ const jobProps = await (async () => {
166
+ const result = await tx.run(
167
+ `MATCH (c:ShareContentCleanupJob {objectRef: $objectRef})
168
+ SET c.lockTick = coalesce(c.lockTick, 0) + 1
169
+ RETURN c LIMIT 1`,
170
+ { objectRef: input.objectRef, claimToken: input.claimToken }
171
+ );
172
+
173
+ return readNodeProperties(result, 'c');
174
+ })();
175
+
176
+ if (!jobProps) return { outcome: 'claim_lost' };
177
+
178
+ // Sample the clock only after the job write lock is held (and afresh on
179
+ // each managed retry). A worker that was blocked on the lock past its
180
+ // claim expiry therefore observes the lapsed lease and fails completion
181
+ // instead of committing with a stale pre-lock timestamp.
182
+ const now = input.now ?? new Date();
183
+ const nowIso = now.toISOString();
184
+ const job = toShareContentCleanupJobRecord(jobProps);
185
+ if (
186
+ job.status !== 'claimed' ||
187
+ job.claimToken !== input.claimToken ||
188
+ !job.claimExpiresAt ||
189
+ job.claimExpiresAt <= nowIso
190
+ )
191
+ return { outcome: 'claim_lost' };
192
+
193
+ if (input.outcome === 'completed') {
194
+ await tx.run(
195
+ `MATCH (c:ShareContentCleanupJob {objectRef: $objectRef})
196
+ SET c.status = 'completed',
197
+ c.completedAt = $now,
198
+ c.claimToken = null,
199
+ c.claimedBy = null,
200
+ c.claimExpiresAt = null,
201
+ c.updatedAt = $now`,
202
+ { objectRef: input.objectRef, now: nowIso }
203
+ );
204
+
205
+ const updated = await readCleanupJob(tx, input.objectRef);
206
+
207
+ return { outcome: 'completed', job: updated ?? job };
208
+ }
209
+
210
+ const attempts = job.attempts + 1;
211
+ const nextAttemptAt = new Date(
212
+ now.getTime() + computeCleanupBackoffMs(job.attempts)
213
+ ).toISOString();
214
+
215
+ await tx.run(
216
+ `MATCH (c:ShareContentCleanupJob {objectRef: $objectRef})
217
+ SET c.status = 'queued',
218
+ c.attempts = $attempts,
219
+ c.nextAttemptAt = $nextAttemptAt,
220
+ c.claimToken = null,
221
+ c.claimedBy = null,
222
+ c.claimExpiresAt = null,
223
+ c.lastError = $lastError,
224
+ c.updatedAt = $now`,
225
+ {
226
+ objectRef: input.objectRef,
227
+ attempts,
228
+ nextAttemptAt,
229
+ lastError: input.errorMessage ?? null,
230
+ now: nowIso,
231
+ }
232
+ );
233
+
234
+ const updated = await readCleanupJob(tx, input.objectRef);
235
+
236
+ return { outcome: 'retry', job: updated ?? job };
237
+ },
238
+ { timeoutMs: input.transactionTimeoutMs, noInlineRetry: input.noInlineRetry }
239
+ );
240
+ };
241
+
242
+ /** Observability/test read: the durable cleanup job for one exact object. */
243
+ export const getCleanupJob = async (
244
+ objectRef: string
245
+ ): Promise<ShareContentCleanupJobRecord | null> => {
246
+ const result = await neogma.queryRunner.run(
247
+ 'MATCH (c:ShareContentCleanupJob {objectRef: $objectRef}) RETURN c LIMIT 1',
248
+ { objectRef }
249
+ );
250
+ const props = readNodeProperties(result, 'c');
251
+
252
+ return props ? toShareContentCleanupJobRecord(props) : null;
253
+ };
254
+
255
+ /** Observability/test read: all durable cleanup jobs queued for one share. */
256
+ export const listCleanupJobsForShare = async (
257
+ shareId: string
258
+ ): Promise<ShareContentCleanupJobRecord[]> => {
259
+ const result = await neogma.queryRunner.run(
260
+ 'MATCH (c:ShareContentCleanupJob {shareId: $shareId}) RETURN c ORDER BY c.createdAt ASC',
261
+ { shareId }
262
+ );
263
+
264
+ return result.records
265
+ .map(record => {
266
+ const props = (record.get('c') as { properties?: Record<string, unknown> })?.properties;
267
+
268
+ return props ? toShareContentCleanupJobRecord(props) : null;
269
+ })
270
+ .filter((job): job is ShareContentCleanupJobRecord => job !== null);
271
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Typed failures raised by the share-link lifecycle repository.
3
+ *
4
+ * Expected races are surfaced as codes rather than free-form strings so routes and
5
+ * the reconciliation worker can map them to tRPC/HTTP responses without parsing
6
+ * messages, and so tests can assert the exact failure class.
7
+ */
8
+ export type ShareLinkRepositoryErrorCode =
9
+ | 'NOT_FOUND'
10
+ | 'CONFLICT'
11
+ | 'OPERATION_IN_FLIGHT'
12
+ | 'LEASE_EXPIRED'
13
+ | 'STALE_GENERATION'
14
+ | 'PRECONDITION_FAILED'
15
+ | 'INVALID_INPUT';
16
+
17
+ export class ShareLinkRepositoryError extends Error {
18
+ readonly code: ShareLinkRepositoryErrorCode;
19
+
20
+ constructor(code: ShareLinkRepositoryErrorCode, message: string) {
21
+ super(message);
22
+ this.name = 'ShareLinkRepositoryError';
23
+ this.code = code;
24
+ }
25
+ }
26
+
27
+ export const isShareLinkRepositoryError = (error: unknown): error is ShareLinkRepositoryError =>
28
+ error instanceof ShareLinkRepositoryError;
29
+
30
+ export function failShareLink(code: ShareLinkRepositoryErrorCode, message: string): never {
31
+ throw new ShareLinkRepositoryError(code, message);
32
+ }
@@ -0,0 +1,313 @@
1
+ import { SHARE_LINK_OPERATION_RETENTION_MS, isLeaseActive } from '@helpers/share-link-lifecycle';
2
+ import { mergeShareLinkPolicyConservatively } from '@helpers/share-link-policy/resolver';
3
+ import { DEFAULT_SHARE_LINK_POLICY } from '@helpers/share-link-policy/types';
4
+ import type { ShareLinkPolicySnapshot } from '@helpers/share-link-policy/types';
5
+
6
+ import { ensureShareLinkConstraints } from '../../models/share-link-constraints';
7
+ import { failShareLink } from './errors';
8
+ import { verifyPinnedShareAttachment, queueShareAttachmentCleanup } from './attachment';
9
+ import {
10
+ deleteReservation,
11
+ enqueueCleanupJob,
12
+ lockShare,
13
+ markOperationCommitted,
14
+ readOperationByOperationId,
15
+ readReservation,
16
+ readShareById,
17
+ toShareLinkOperationRecord,
18
+ toShareLinkRecord,
19
+ toShareLinkReservationRecord,
20
+ } from './helpers';
21
+ import { withShareLinkTransaction } from './transaction';
22
+ import type { FinalizeReservationInput, FinalizeReservationResult } from './types';
23
+
24
+ const pruneAfterFrom = (now: Date): string =>
25
+ new Date(now.getTime() + SHARE_LINK_OPERATION_RETENTION_MS).toISOString();
26
+
27
+ /**
28
+ * Commits a reserved revision: consumes the reservation under the share write
29
+ * lock, updates the visible fields/version, records the exact operation result and
30
+ * enqueues cleanup for the superseded immutable object — all in ONE transaction.
31
+ *
32
+ * PRECONDITION (enforced by the caller, never by this repository): the external
33
+ * coordinator has already verified that the immutable LearnCloud object for
34
+ * `objectRef` exists and its payloadHash matches the reserved contentHash. `verifiedContentHash`
35
+ * is re-checked here as defense in depth, but this module performs no network I/O
36
+ * and cannot itself attest remote existence.
37
+ *
38
+ * A stale worker is fenced three ways: the reservation must still match the share,
39
+ * its bounded lease must be active, and its generation must equal the share's
40
+ * current generation. A metadata-only reservation leaves `activeObjectRef`
41
+ * untouched and enqueues no cleanup.
42
+ */
43
+ export const finalizeReservation = async (
44
+ input: FinalizeReservationInput
45
+ ): Promise<FinalizeReservationResult> => {
46
+ await ensureShareLinkConstraints();
47
+
48
+ const now = input.now ?? new Date();
49
+ const nowIso = now.toISOString();
50
+
51
+ return withShareLinkTransaction(
52
+ async tx => {
53
+ const lockedProps = await lockShare(tx, input.shareId);
54
+
55
+ if (!lockedProps) {
56
+ failShareLink('NOT_FOUND', 'share not found');
57
+ }
58
+
59
+ const share = toShareLinkRecord(lockedProps);
60
+
61
+ if (
62
+ share.namespace !== input.namespace ||
63
+ share.ownerProfileId !== input.ownerProfileId
64
+ ) {
65
+ failShareLink('NOT_FOUND', 'share not found');
66
+ }
67
+
68
+ if (
69
+ share.lastOperationId === input.operationId &&
70
+ (share.activeObjectRef ?? null) === (input.objectRef ?? null) &&
71
+ share.contentState === 'finalized'
72
+ ) {
73
+ return { outcome: 'already_finalized', share };
74
+ }
75
+
76
+ const reservationProps = await readReservation(tx, input.shareId);
77
+
78
+ if (!reservationProps) {
79
+ const operationProps = await readOperationByOperationId(tx, input.operationId);
80
+
81
+ if (
82
+ operationProps &&
83
+ toShareLinkOperationRecord(operationProps).status === 'committed' &&
84
+ operationProps.shareId === input.shareId &&
85
+ operationProps.namespace === input.namespace &&
86
+ operationProps.ownerProfileId === input.ownerProfileId
87
+ ) {
88
+ return { outcome: 'already_finalized', share };
89
+ }
90
+
91
+ failShareLink('CONFLICT', 'reservation is absent or already consumed');
92
+ }
93
+
94
+ const reservation = toShareLinkReservationRecord(reservationProps);
95
+
96
+ if (
97
+ reservation.operationId !== input.operationId ||
98
+ (reservation.objectRef ?? null) !== (input.objectRef ?? null) ||
99
+ reservation.generation !== input.generation ||
100
+ reservation.leaseOwner !== input.leaseOwner ||
101
+ reservation.namespace !== input.namespace ||
102
+ reservation.ownerProfileId !== input.ownerProfileId
103
+ ) {
104
+ failShareLink('CONFLICT', 'finalize does not match the in-flight reservation');
105
+ }
106
+
107
+ if (share.status === 'stopped') {
108
+ failShareLink('CONFLICT', 'cannot finalize a stopped share');
109
+ }
110
+
111
+ if (!isLeaseActive(reservation.leaseExpiresAt, input.now ?? new Date())) {
112
+ failShareLink('LEASE_EXPIRED', 'the reservation lease has expired');
113
+ }
114
+
115
+ if (share.generation !== reservation.generation) {
116
+ failShareLink('STALE_GENERATION', 'a newer reservation superseded this revision');
117
+ }
118
+
119
+ if (share.version !== reservation.baseVersion) {
120
+ failShareLink('CONFLICT', 'share version changed since the reservation');
121
+ }
122
+
123
+ if (reservation.contentVersion !== null) {
124
+ if (reservation.opKind === 'create') {
125
+ // The first revision is contentVersion 1 and is not a "+1" replacement.
126
+ if (reservation.contentVersion !== 1) {
127
+ failShareLink(
128
+ 'PRECONDITION_FAILED',
129
+ 'a create reservation must target version 1'
130
+ );
131
+ }
132
+ } else {
133
+ if (share.contentVersion !== reservation.baseContentVersion) {
134
+ failShareLink(
135
+ 'CONFLICT',
136
+ 'visible content version changed since the reservation'
137
+ );
138
+ }
139
+
140
+ if (reservation.contentVersion !== share.contentVersion + 1) {
141
+ failShareLink(
142
+ 'PRECONDITION_FAILED',
143
+ 'the reserved content version is not current + 1'
144
+ );
145
+ }
146
+ }
147
+ }
148
+
149
+ if (
150
+ input.verifiedContentHash !== undefined &&
151
+ reservation.contentHash !== null &&
152
+ input.verifiedContentHash !== reservation.contentHash
153
+ ) {
154
+ failShareLink(
155
+ 'PRECONDITION_FAILED',
156
+ 'verified content hash does not match the reservation'
157
+ );
158
+ }
159
+
160
+ const previousObjectRef = share.activeObjectRef;
161
+ if (
162
+ reservation.contentVersion !== null &&
163
+ reservation.attachmentId &&
164
+ reservation.attachmentChunkCount
165
+ ) {
166
+ if (input.verifiedAttachmentId !== reservation.attachmentId) {
167
+ failShareLink('PRECONDITION_FAILED', 'attachment was not verified');
168
+ }
169
+ await verifyPinnedShareAttachment(
170
+ tx,
171
+ {
172
+ namespace: reservation.namespace,
173
+ ownerProfileId: reservation.ownerProfileId,
174
+ shareId: reservation.shareId,
175
+ contentVersion: reservation.contentVersion,
176
+ attachmentId: reservation.attachmentId,
177
+ chunkCount: reservation.attachmentChunkCount,
178
+ },
179
+ reservation.requestHash
180
+ );
181
+ }
182
+ const nextObjectRef = reservation.objectRef ?? share.activeObjectRef;
183
+ const nextContentVersion = reservation.contentVersion ?? share.contentVersion;
184
+
185
+ // Unknown snapshots and old service restrictions may be refreshed on
186
+ // explicit edits, but only after a fresh graph-local check. The
187
+ // reservation still caps eligibility for stale replays and recovery.
188
+ const currentPolicy: ShareLinkPolicySnapshot = {
189
+ isMinor: share.minorPolicyIsMinor,
190
+ policyResolved: share.minorPolicyResolved,
191
+ defaultExpiryDays: share.minorPolicyDefaultExpiryDays === 365 ? 365 : 30,
192
+ viewCountingEnabled: share.minorPolicyViewCountingEnabled,
193
+ };
194
+ const checkedPolicy = input.resolveCurrentPolicy
195
+ ? await input
196
+ .resolveCurrentPolicy(tx, share.ownerProfileId, now)
197
+ .catch(() => ({ ...DEFAULT_SHARE_LINK_POLICY, isServiceProfile: false }))
198
+ : null;
199
+ const reservationPolicy = checkedPolicy
200
+ ? mergeShareLinkPolicyConservatively(reservation.policy, checkedPolicy)
201
+ : mergeShareLinkPolicyConservatively(reservation.policy, currentPolicy);
202
+ const mayRefreshExplicitService =
203
+ checkedPolicy?.isServiceProfile === true &&
204
+ checkedPolicy.isMinor === false &&
205
+ reservation.opKind === 'update';
206
+ const mayReplaceUnknown =
207
+ checkedPolicy !== null &&
208
+ !currentPolicy.policyResolved &&
209
+ (reservation.opKind === 'create' || reservation.opKind === 'update');
210
+ const effectivePolicy =
211
+ mayRefreshExplicitService || mayReplaceUnknown
212
+ ? reservationPolicy
213
+ : mergeShareLinkPolicyConservatively(currentPolicy, reservationPolicy);
214
+
215
+ await tx.run(
216
+ `MATCH (s:ShareLink {id: $shareId})
217
+ SET s += $props`,
218
+ {
219
+ shareId: input.shareId,
220
+ props: {
221
+ version: share.version + 1,
222
+ contentVersion: nextContentVersion,
223
+ attachmentId: reservation.objectRef
224
+ ? (reservation.attachmentId ?? null)
225
+ : (share.attachmentId ?? null),
226
+ attachmentChunkCount: reservation.objectRef
227
+ ? (reservation.attachmentChunkCount ?? null)
228
+ : (share.attachmentChunkCount ?? null),
229
+ status: 'active',
230
+ contentState: 'finalized',
231
+ activeObjectRef: nextObjectRef,
232
+ activeObjectOperationId: reservation.objectRef
233
+ ? reservation.operationId
234
+ : share.activeObjectOperationId,
235
+ activeContentHash: reservation.contentHash ?? share.activeContentHash,
236
+ activeContentBytes: reservation.contentBytes ?? share.activeContentBytes,
237
+ activeRecoveryHash: reservation.recoveryHash ?? share.activeRecoveryHash,
238
+ lastOperationId: reservation.operationId,
239
+ title: reservation.title,
240
+ note: reservation.note,
241
+ expiresAt: reservation.expiresAt,
242
+ selectedCount: reservation.selectedCount,
243
+ passcodeHash: reservation.passcodeHash,
244
+ notifyOnView:
245
+ reservation.notifyOnView && effectivePolicy.viewCountingEnabled,
246
+ minorPolicyIsMinor: effectivePolicy.isMinor,
247
+ minorPolicyResolved: effectivePolicy.policyResolved,
248
+ minorPolicyDefaultExpiryDays: effectivePolicy.defaultExpiryDays,
249
+ minorPolicyViewCountingEnabled: effectivePolicy.viewCountingEnabled,
250
+ updatedAt: nowIso,
251
+ },
252
+ }
253
+ );
254
+
255
+ const updatedProps = await readShareById(tx, input.shareId);
256
+
257
+ if (!updatedProps) {
258
+ failShareLink('CONFLICT', 'share disappeared during finalize');
259
+ }
260
+
261
+ const updatedShare = toShareLinkRecord(updatedProps);
262
+
263
+ await markOperationCommitted(tx, {
264
+ namespace: reservation.namespace,
265
+ ownerProfileId: reservation.ownerProfileId,
266
+ opKind: reservation.opKind,
267
+ clientRequestId: reservation.clientRequestId,
268
+ operationId: reservation.operationId,
269
+ resultJson: JSON.stringify(updatedShare),
270
+ resultVersion: updatedShare.version,
271
+ now: nowIso,
272
+ pruneAfter: pruneAfterFrom(now),
273
+ });
274
+
275
+ await deleteReservation(tx, input.shareId);
276
+
277
+ let cleanupQueuedFor: string | null = null;
278
+
279
+ if (previousObjectRef && previousObjectRef !== nextObjectRef) {
280
+ if (share.attachmentId && share.attachmentChunkCount) {
281
+ await queueShareAttachmentCleanup(
282
+ tx,
283
+ {
284
+ namespace: share.namespace,
285
+ ownerProfileId: share.ownerProfileId,
286
+ shareId: share.id,
287
+ contentVersion: share.contentVersion,
288
+ attachmentId: share.attachmentId,
289
+ chunkCount: share.attachmentChunkCount,
290
+ },
291
+ 'superseded',
292
+ nowIso
293
+ );
294
+ }
295
+ await enqueueCleanupJob(tx, {
296
+ objectRef: previousObjectRef,
297
+ operationId: share.activeObjectOperationId!,
298
+ namespace: reservation.namespace,
299
+ ownerProfileId: reservation.ownerProfileId,
300
+ shareId: input.shareId,
301
+ contentVersion: share.contentVersion,
302
+ reason: 'superseded',
303
+ now: nowIso,
304
+ });
305
+
306
+ cleanupQueuedFor = previousObjectRef;
307
+ }
308
+
309
+ return { outcome: 'finalized', share: updatedShare, cleanupQueuedFor };
310
+ },
311
+ { timeoutMs: input.transactionTimeoutMs, noInlineRetry: input.noInlineRetry }
312
+ );
313
+ };