@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
@@ -2,15 +2,26 @@ import { inflateObject } from '@helpers/objects.helpers';
2
2
  import { Presentation, PresentationInstance, Profile, ProfileRelationships } from '@models';
3
3
  import { ProfileType } from 'types/profile';
4
4
 
5
+ const relationshipProperties = (value: unknown): Record<string, unknown> => {
6
+ if (value && typeof value === 'object' && 'dataValues' in value) {
7
+ const { dataValues } = value as { dataValues?: unknown };
8
+ if (dataValues && typeof dataValues === 'object') {
9
+ return dataValues as Record<string, unknown>;
10
+ }
11
+ }
12
+
13
+ return value as Record<string, unknown>;
14
+ };
15
+
5
16
  export const getPresentationSentToProfile = async (
6
17
  id: string,
7
18
  to: ProfileType
8
19
  ): Promise<
9
20
  | {
10
- source: ProfileType;
11
- relationship: ProfileRelationships['presentationSent']['RelationshipProperties'];
12
- target: PresentationInstance;
13
- }
21
+ source: ProfileType;
22
+ relationship: ProfileRelationships['presentationSent']['RelationshipProperties'];
23
+ target: PresentationInstance;
24
+ }
14
25
  | undefined
15
26
  > => {
16
27
  const data = (
@@ -22,7 +33,11 @@ export const getPresentationSentToProfile = async (
22
33
 
23
34
  if (!data) return undefined;
24
35
 
25
- return { ...data, source: inflateObject(data.source.dataValues as any) };
36
+ return {
37
+ ...data,
38
+ source: inflateObject(data.source.dataValues as unknown as Record<string, unknown>),
39
+ relationship: inflateObject(relationshipProperties(data.relationship)),
40
+ };
26
41
  };
27
42
 
28
43
  export const getPresentationOwner = async (
@@ -39,7 +54,7 @@ export const getPresentationOwner = async (
39
54
 
40
55
  if (!owner) return undefined;
41
56
 
42
- return inflateObject<ProfileType>(owner.dataValues as any);
57
+ return inflateObject(owner.dataValues as unknown as Record<string, unknown>) as ProfileType;
43
58
  };
44
59
 
45
60
  export const getPresentationReceivedByProfile = async (
@@ -0,0 +1,120 @@
1
+ import { ensureShareLinkConstraints } from '../../models/share-link-constraints';
2
+ import { failShareLink } from './errors';
3
+ import { queueShareAttachmentCleanup } from './attachment';
4
+ import {
5
+ deleteReservation,
6
+ enqueueCleanupJob,
7
+ lockShare,
8
+ markOperationAbandoned,
9
+ readReservation,
10
+ toShareLinkRecord,
11
+ toShareLinkReservationRecord,
12
+ } from './helpers';
13
+ import { withShareLinkTransaction } from './transaction';
14
+ import type { AbandonReservationInput, AbandonReservationResult } from './types';
15
+
16
+ /**
17
+ * Irreversibly abandons a reservation.
18
+ *
19
+ * The operation transitions to cleanup-only: its exact immutable object is queued
20
+ * for tracked cleanup and the reservation is deleted, so a late finalize for the
21
+ * same operation is rejected. A stale worker can never abandon a newer reservation
22
+ * because the operation id (and, when supplied, generation/lease owner) must match.
23
+ *
24
+ * When the reservation is already gone but the share records this operation as the
25
+ * committed one, abandon reports `already_finalized` instead of pretending to have
26
+ * cleaned up live content.
27
+ */
28
+ export const abandonReservation = async (
29
+ input: AbandonReservationInput
30
+ ): Promise<AbandonReservationResult> => {
31
+ await ensureShareLinkConstraints();
32
+
33
+ const now = input.now ?? new Date();
34
+ const nowIso = now.toISOString();
35
+
36
+ return withShareLinkTransaction(async tx => {
37
+ const lockedProps = await lockShare(tx, input.shareId);
38
+
39
+ if (!lockedProps) {
40
+ failShareLink('NOT_FOUND', 'share not found');
41
+ }
42
+
43
+ const share = toShareLinkRecord(lockedProps);
44
+
45
+ if (share.namespace !== input.namespace || share.ownerProfileId !== input.ownerProfileId) {
46
+ failShareLink('NOT_FOUND', 'share not found');
47
+ }
48
+
49
+ const reservationProps = await readReservation(tx, input.shareId);
50
+
51
+ if (!reservationProps) {
52
+ if (share.lastOperationId === input.operationId) {
53
+ return { outcome: 'already_finalized', share };
54
+ }
55
+
56
+ return { outcome: 'already_absent' };
57
+ }
58
+
59
+ const reservation = toShareLinkReservationRecord(reservationProps);
60
+
61
+ if (reservation.operationId !== input.operationId) {
62
+ return { outcome: 'already_absent' };
63
+ }
64
+
65
+ if (input.generation !== undefined && reservation.generation !== input.generation) {
66
+ return { outcome: 'already_absent' };
67
+ }
68
+
69
+ if (input.leaseOwner !== undefined && reservation.leaseOwner !== input.leaseOwner) {
70
+ return { outcome: 'already_absent' };
71
+ }
72
+
73
+ let cleanupQueuedFor: string | null = null;
74
+ if (
75
+ reservation.attachmentId &&
76
+ reservation.attachmentChunkCount &&
77
+ reservation.contentVersion
78
+ ) {
79
+ await queueShareAttachmentCleanup(
80
+ tx,
81
+ {
82
+ namespace: reservation.namespace,
83
+ ownerProfileId: reservation.ownerProfileId,
84
+ shareId: reservation.shareId,
85
+ contentVersion: reservation.contentVersion,
86
+ attachmentId: reservation.attachmentId,
87
+ chunkCount: reservation.attachmentChunkCount,
88
+ },
89
+ 'abandoned',
90
+ nowIso
91
+ );
92
+ }
93
+
94
+ if (reservation.objectRef) {
95
+ await enqueueCleanupJob(tx, {
96
+ objectRef: reservation.objectRef,
97
+ operationId: reservation.operationId,
98
+ namespace: reservation.namespace,
99
+ ownerProfileId: reservation.ownerProfileId,
100
+ shareId: reservation.shareId,
101
+ contentVersion: reservation.contentVersion ?? reservation.baseContentVersion,
102
+ reason: 'abandoned',
103
+ now: nowIso,
104
+ });
105
+
106
+ cleanupQueuedFor = reservation.objectRef;
107
+ }
108
+
109
+ await markOperationAbandoned(tx, {
110
+ namespace: reservation.namespace,
111
+ ownerProfileId: reservation.ownerProfileId,
112
+ opKind: reservation.opKind,
113
+ clientRequestId: reservation.clientRequestId,
114
+ now: nowIso,
115
+ });
116
+ await deleteReservation(tx, input.shareId);
117
+
118
+ return { outcome: 'abandoned', cleanupQueuedFor };
119
+ });
120
+ };
@@ -0,0 +1,360 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { int } from 'neo4j-driver';
3
+ import type { ShareLinkAttachment } from '@learncard/types';
4
+ import { ensureShareLinkConstraints } from '../../models/share-link-constraints';
5
+ import { failShareLink } from './errors';
6
+ import {
7
+ enqueueCleanupJob,
8
+ lockShare,
9
+ readNodeProperties,
10
+ readReservation,
11
+ toShareLinkRecord,
12
+ toShareLinkReservationRecord,
13
+ normalizeNeo4jValue,
14
+ } from './helpers';
15
+ import {
16
+ withShareLinkTransaction,
17
+ type ShareLinkTransaction,
18
+ type ShareLinkTransactionOptions,
19
+ } from './transaction';
20
+
21
+ export type ShareAttachmentBinding = {
22
+ namespace: string;
23
+ ownerProfileId: string;
24
+ shareId: string;
25
+ contentVersion: number;
26
+ attachmentId: string;
27
+ chunkCount: number;
28
+ };
29
+
30
+ export const shareAttachmentObjectId = (attachmentId: string, chunkIndex: number): string =>
31
+ `resume-pdf-${attachmentId}-${chunkIndex}`;
32
+ export const shareAttachmentTuple = (binding: ShareAttachmentBinding, chunkIndex: number) => ({
33
+ namespace: binding.namespace,
34
+ ownerProfileId: binding.ownerProfileId,
35
+ shareId: binding.shareId,
36
+ contentVersion: binding.contentVersion,
37
+ objectId: shareAttachmentObjectId(binding.attachmentId, chunkIndex),
38
+ operationId: binding.attachmentId,
39
+ });
40
+
41
+ const stageKey = (binding: ShareAttachmentBinding): string =>
42
+ createHash('sha256')
43
+ .update(
44
+ JSON.stringify([
45
+ binding.namespace,
46
+ binding.ownerProfileId,
47
+ binding.shareId,
48
+ binding.contentVersion,
49
+ binding.attachmentId,
50
+ ])
51
+ )
52
+ .digest('hex');
53
+
54
+ const lockStage = async (tx: ShareLinkTransaction, binding: ShareAttachmentBinding) => {
55
+ const result = await tx.run(
56
+ `MATCH (a:ShareLinkAttachmentStage {key: $key})
57
+ SET a.lockTick = coalesce(a.lockTick, 0) + 1 RETURN a`,
58
+ { key: stageKey(binding) }
59
+ );
60
+ const props = readNodeProperties(result, 'a');
61
+ if (props && props.chunkCount !== binding.chunkCount) {
62
+ failShareLink('CONFLICT', 'attachment staging conflict');
63
+ }
64
+ return props;
65
+ };
66
+
67
+ /** Stage guard is durable; remote I/O happens only after this transaction closes. */
68
+ export const beginShareAttachmentChunk = async (
69
+ binding: ShareAttachmentBinding,
70
+ chunkIndex: number
71
+ ): Promise<void> => {
72
+ if (
73
+ !Number.isInteger(binding.chunkCount) ||
74
+ binding.chunkCount < 1 ||
75
+ binding.chunkCount > 16 ||
76
+ !Number.isInteger(chunkIndex) ||
77
+ chunkIndex < 0 ||
78
+ chunkIndex >= binding.chunkCount
79
+ )
80
+ failShareLink('INVALID_INPUT', 'invalid attachment chunk');
81
+ await ensureShareLinkConstraints();
82
+ await withShareLinkTransaction(async tx => {
83
+ const shareProps = await lockShare(tx, binding.shareId);
84
+ if (shareProps) {
85
+ const share = toShareLinkRecord(shareProps);
86
+ if (
87
+ share.namespace !== binding.namespace ||
88
+ share.ownerProfileId !== binding.ownerProfileId
89
+ ) {
90
+ failShareLink('NOT_FOUND', 'share not found');
91
+ }
92
+ if (
93
+ share.status === 'stopped' ||
94
+ !(
95
+ (share.status === 'pending' && binding.contentVersion === 1) ||
96
+ binding.contentVersion === share.contentVersion + 1 ||
97
+ (binding.contentVersion === share.contentVersion &&
98
+ share.attachmentId === binding.attachmentId &&
99
+ share.attachmentChunkCount === binding.chunkCount)
100
+ )
101
+ )
102
+ failShareLink('CONFLICT', 'attachment version conflict');
103
+ const pending = await readReservation(tx, binding.shareId);
104
+ if (pending) {
105
+ const reservation = toShareLinkReservationRecord(pending);
106
+ if (
107
+ reservation.contentVersion !== binding.contentVersion ||
108
+ reservation.attachmentId !== binding.attachmentId ||
109
+ reservation.attachmentChunkCount !== binding.chunkCount
110
+ ) {
111
+ failShareLink('CONFLICT', 'another publication is pending');
112
+ }
113
+ }
114
+ } else if (binding.contentVersion !== 1) {
115
+ failShareLink('NOT_FOUND', 'share not found');
116
+ }
117
+ // Serialize pre-create stages by owner as well as by share, so simultaneous
118
+ // random IDs cannot exceed the bounded uncommitted storage allowance.
119
+ const quotaKey = createHash('sha256')
120
+ .update(JSON.stringify([binding.namespace, binding.ownerProfileId]))
121
+ .digest('hex');
122
+ await tx.run(
123
+ `MERGE (q:ShareAttachmentOwnerQuota {key: $quotaKey})
124
+ ON CREATE SET q.namespace = $namespace, q.ownerProfileId = $ownerProfileId
125
+ SET q.lockTick = coalesce(q.lockTick, 0) + 1`,
126
+ {
127
+ quotaKey,
128
+ namespace: binding.namespace,
129
+ ownerProfileId: binding.ownerProfileId,
130
+ }
131
+ );
132
+ const existing = await lockStage(tx, binding);
133
+ if (!existing) {
134
+ const result = await tx.run(
135
+ `MATCH (a:ShareLinkAttachmentStage)
136
+ WHERE a.namespace = $namespace AND a.ownerProfileId = $ownerProfileId
137
+ AND a.status = 'staging' AND a.expiresAt > $now
138
+ RETURN count(a) AS count`,
139
+ {
140
+ namespace: binding.namespace,
141
+ ownerProfileId: binding.ownerProfileId,
142
+ now: new Date().toISOString(),
143
+ }
144
+ );
145
+ const count = normalizeNeo4jValue(result.records[0]?.get('count'));
146
+ if (typeof count !== 'number' || count >= 8)
147
+ failShareLink('PRECONDITION_FAILED', 'attachment staging limit reached');
148
+ }
149
+ await tx.run(
150
+ `MERGE (a:ShareLinkAttachmentStage {key: $key})
151
+ ON CREATE SET a += $props
152
+ SET a.lockTick = coalesce(a.lockTick, 0) + 1 RETURN a`,
153
+ {
154
+ key: stageKey(binding),
155
+ props: {
156
+ ...binding,
157
+ status: 'staging',
158
+ completedChunks: [],
159
+ createdAt: new Date().toISOString(),
160
+ expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
161
+ },
162
+ }
163
+ );
164
+ const stage = await lockStage(tx, binding);
165
+ if (!stage || stage.status === 'deleted')
166
+ failShareLink('CONFLICT', 'attachment staging conflict');
167
+ if (
168
+ stage.status === 'staging' &&
169
+ typeof stage.expiresAt === 'string' &&
170
+ stage.expiresAt <= new Date().toISOString()
171
+ ) {
172
+ failShareLink('CONFLICT', 'attachment staging expired');
173
+ }
174
+ });
175
+ };
176
+
177
+ /**
178
+ * Record successful immutable puts even if staging expired during remote I/O.
179
+ * This is bookkeeping, not publication authority: pin/finalize enforce expiry,
180
+ * and maintenance cleans up an expired, uncommitted stage.
181
+ */
182
+ export const completeShareAttachmentChunk = async (
183
+ binding: ShareAttachmentBinding,
184
+ chunkIndex: number
185
+ ): Promise<void> => {
186
+ await withShareLinkTransaction(async tx => {
187
+ const stage = await lockStage(tx, binding);
188
+ if (!stage || stage.status === 'deleted')
189
+ failShareLink('CONFLICT', 'attachment staging conflict');
190
+ await tx.run(
191
+ `MATCH (a:ShareLinkAttachmentStage {key: $key})
192
+ SET a.completedChunks = CASE WHEN $chunkIndex IN a.completedChunks THEN a.completedChunks
193
+ ELSE a.completedChunks + $chunkIndex END`,
194
+ { key: stageKey(binding), chunkIndex }
195
+ );
196
+ });
197
+ };
198
+
199
+ /** Called while the share lock is held; pins complete staged chunks to request intent. */
200
+ export const pinShareAttachment = async (
201
+ tx: ShareLinkTransaction,
202
+ binding: ShareAttachmentBinding,
203
+ requestHash: string
204
+ ): Promise<void> => {
205
+ const stage = await lockStage(tx, binding);
206
+ if (
207
+ !stage ||
208
+ stage.status === 'deleted' ||
209
+ (stage.requestHash !== undefined &&
210
+ stage.requestHash !== null &&
211
+ stage.requestHash !== requestHash)
212
+ ) {
213
+ failShareLink('PRECONDITION_FAILED', 'attachment is not ready');
214
+ }
215
+ if (
216
+ stage.status === 'staging' &&
217
+ typeof stage.expiresAt === 'string' &&
218
+ stage.expiresAt <= new Date().toISOString()
219
+ ) {
220
+ failShareLink('PRECONDITION_FAILED', 'attachment staging expired');
221
+ }
222
+ const completed = stage.completedChunks;
223
+ if (
224
+ !Array.isArray(completed) ||
225
+ !Array.from({ length: binding.chunkCount }, (_, index) => index).every(index =>
226
+ completed.includes(index)
227
+ )
228
+ )
229
+ failShareLink('PRECONDITION_FAILED', 'attachment is incomplete');
230
+ await tx.run(
231
+ `MATCH (a:ShareLinkAttachmentStage {key: $key}) SET a.status = 'pinned',
232
+ a.requestHash = $requestHash`,
233
+ { key: stageKey(binding), requestHash }
234
+ );
235
+ };
236
+
237
+ export const verifyPinnedShareAttachment = async (
238
+ tx: ShareLinkTransaction,
239
+ binding: ShareAttachmentBinding,
240
+ requestHash: string
241
+ ): Promise<void> => {
242
+ const stage = await lockStage(tx, binding);
243
+ if (!stage || stage.status !== 'pinned' || stage.requestHash !== requestHash) {
244
+ failShareLink('PRECONDITION_FAILED', 'attachment is not ready');
245
+ }
246
+ };
247
+
248
+ /** Queues only exact tuples, after withdrawing graph authority to commit those bytes. */
249
+ export const queueShareAttachmentCleanup = async (
250
+ tx: ShareLinkTransaction,
251
+ binding: ShareAttachmentBinding,
252
+ reason: 'superseded' | 'abandoned' | 'stopped',
253
+ now: string
254
+ ): Promise<void> => {
255
+ const stage = await lockStage(tx, binding);
256
+ if (stage)
257
+ await tx.run(`MATCH (a:ShareLinkAttachmentStage {key: $key}) SET a.status = 'deleted'`, {
258
+ key: stageKey(binding),
259
+ });
260
+ for (let chunkIndex = 0; chunkIndex < binding.chunkCount; chunkIndex++) {
261
+ const tuple = shareAttachmentTuple(binding, chunkIndex);
262
+ await enqueueCleanupJob(tx, { ...tuple, objectRef: tuple.objectId, reason, now });
263
+ }
264
+ };
265
+
266
+ /** Refuses active or in-flight assets; a tombstoned stage can never be repinned. */
267
+ export const deleteUnreferencedShareAttachment = async (
268
+ binding: ShareAttachmentBinding
269
+ ): Promise<boolean> => {
270
+ await ensureShareLinkConstraints();
271
+ return withShareLinkTransaction(async tx => {
272
+ const shareProps = await lockShare(tx, binding.shareId);
273
+ if (shareProps) {
274
+ const share = toShareLinkRecord(shareProps);
275
+ if (
276
+ share.namespace !== binding.namespace ||
277
+ share.ownerProfileId !== binding.ownerProfileId
278
+ ) {
279
+ failShareLink('NOT_FOUND', 'share not found');
280
+ }
281
+ if (
282
+ share.attachmentId === binding.attachmentId &&
283
+ share.contentVersion === binding.contentVersion
284
+ )
285
+ return false;
286
+ const props = await readReservation(tx, binding.shareId);
287
+ const reservation = props ? toShareLinkReservationRecord(props) : null;
288
+ if (
289
+ reservation?.attachmentId === binding.attachmentId &&
290
+ reservation.contentVersion === binding.contentVersion
291
+ )
292
+ return false;
293
+ }
294
+ const stage = await lockStage(tx, binding);
295
+ if (!stage) return false;
296
+ if (stage.status === 'pinned') return false;
297
+ await queueShareAttachmentCleanup(tx, binding, 'abandoned', new Date().toISOString());
298
+ return true;
299
+ });
300
+ };
301
+
302
+ export const attachmentBindingFor = (
303
+ scope: { namespace: string; ownerProfileId: string; shareId: string },
304
+ contentVersion: number,
305
+ attachment: ShareLinkAttachment
306
+ ): ShareAttachmentBinding => ({
307
+ ...scope,
308
+ contentVersion,
309
+ attachmentId: attachment.id,
310
+ chunkCount: attachment.chunkCount,
311
+ });
312
+
313
+ /** Bounded periodic collection of never-committed stages after their 24-hour retry window. */
314
+ export const expireShareAttachmentStages = async (input: {
315
+ namespace: string;
316
+ limit?: number;
317
+ now?: Date;
318
+ transaction?: ShareLinkTransactionOptions;
319
+ }): Promise<number> => {
320
+ if (!input.namespace) failShareLink('INVALID_INPUT', 'namespace is required');
321
+ const limit = Math.max(1, Math.min(10, input.limit ?? 10));
322
+ return withShareLinkTransaction(async tx => {
323
+ const candidates = await tx.run(
324
+ `MATCH (a:ShareLinkAttachmentStage)
325
+ WHERE a.namespace = $namespace AND a.status = 'staging' AND a.expiresAt <= $now
326
+ RETURN a ORDER BY a.expiresAt LIMIT $limit`,
327
+ {
328
+ namespace: input.namespace,
329
+ now: (input.now ?? new Date()).toISOString(),
330
+ limit: int(limit),
331
+ }
332
+ );
333
+ let queued = 0;
334
+ for (let index = 0; index < candidates.records.length; index++) {
335
+ const props = readNodeProperties(candidates, 'a', index);
336
+ if (!props) continue;
337
+ const binding: ShareAttachmentBinding = {
338
+ namespace: String(props.namespace),
339
+ ownerProfileId: String(props.ownerProfileId),
340
+ shareId: String(props.shareId),
341
+ attachmentId: String(props.attachmentId),
342
+ contentVersion: Number(props.contentVersion),
343
+ chunkCount: Number(props.chunkCount),
344
+ };
345
+ await lockShare(tx, binding.shareId);
346
+ const stage = await lockStage(tx, binding);
347
+ const now = (input.now ?? new Date()).toISOString();
348
+ if (
349
+ !stage ||
350
+ stage.status !== 'staging' ||
351
+ typeof stage.expiresAt !== 'string' ||
352
+ stage.expiresAt > now
353
+ )
354
+ continue;
355
+ await queueShareAttachmentCleanup(tx, binding, 'abandoned', now);
356
+ queued++;
357
+ }
358
+ return queued;
359
+ }, input.transaction);
360
+ };