@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,262 @@
1
+ import { randomBytes, randomUUID } from 'node:crypto';
2
+ import { beforeAll, beforeEach, afterAll, describe, expect, it } from 'vitest';
3
+ import { neogma } from '@instance';
4
+ import { ensureShareLinkConstraints } from '../src/models/share-link-constraints';
5
+ import {
6
+ beginShareAttachmentChunk,
7
+ completeShareAttachmentChunk,
8
+ deleteUnreferencedShareAttachment,
9
+ expireShareAttachmentStages,
10
+ shareAttachmentObjectId,
11
+ type ShareAttachmentBinding,
12
+ } from '../src/accesslayer/share-link/attachment';
13
+ import {
14
+ reserveCreate,
15
+ reserveReplacement,
16
+ finalizeReservation,
17
+ revokeShareLink,
18
+ abandonReservation,
19
+ getShareLink,
20
+ listCleanupJobsForShare,
21
+ } from '../src/accesslayer/share-link';
22
+ import { contentBinding } from './helpers/share-link-fixtures';
23
+ import type { ShareLinkReservationRecord } from '../src/accesslayer/share-link/types';
24
+
25
+ // Only this suite's random namespace is cleaned; no shared/user graph is touched.
26
+ const namespace = `attachment-tests-${randomUUID()}`;
27
+ const clear = async (): Promise<void> => {
28
+ await neogma.queryRunner.run('MATCH (n) WHERE n.namespace = $namespace DETACH DELETE n', {
29
+ namespace,
30
+ });
31
+ };
32
+ beforeAll(async () => {
33
+ await ensureShareLinkConstraints();
34
+ });
35
+ beforeEach(clear);
36
+ afterAll(async () => {
37
+ await clear();
38
+ await neogma.queryRunner.getDriver().close();
39
+ });
40
+
41
+ const binding = (count = 2, owner = 'owner'): ShareAttachmentBinding => ({
42
+ namespace,
43
+ ownerProfileId: owner,
44
+ shareId: randomBytes(16).toString('base64url'),
45
+ contentVersion: 1,
46
+ attachmentId: randomUUID(),
47
+ chunkCount: count,
48
+ });
49
+ const stage = async (
50
+ value: ShareAttachmentBinding,
51
+ indices = Array.from({ length: value.chunkCount }, (_, i) => i)
52
+ ) => {
53
+ for (const index of indices) {
54
+ await beginShareAttachmentChunk(value, index);
55
+ await completeShareAttachmentChunk(value, index);
56
+ }
57
+ };
58
+ const createInput = (value: ShareAttachmentBinding) => ({
59
+ ...value,
60
+ clientRequestId: randomUUID(),
61
+ title: 'Synthetic resume',
62
+ selectedCount: 1,
63
+ content: contentBinding(value.attachmentId),
64
+ attachment: { id: value.attachmentId, chunkCount: value.chunkCount },
65
+ requestHash: 'a'.repeat(64),
66
+ leaseOwner: 'worker',
67
+ });
68
+ const reserve = async (value: ShareAttachmentBinding): Promise<ShareLinkReservationRecord> => {
69
+ const result = await reserveCreate(createInput(value));
70
+ if (result.outcome !== 'reserved') throw new Error('expected reserved operation');
71
+ return result.reservation;
72
+ };
73
+ const finish = async (record: ShareLinkReservationRecord, verified = true) =>
74
+ finalizeReservation({
75
+ namespace,
76
+ ownerProfileId: record.ownerProfileId,
77
+ shareId: record.shareId,
78
+ operationId: record.operationId,
79
+ objectRef: record.objectRef,
80
+ generation: record.generation,
81
+ leaseOwner: record.leaseOwner,
82
+ verifiedContentHash: record.contentHash ?? undefined,
83
+ verifiedAttachmentId: verified ? (record.attachmentId ?? undefined) : undefined,
84
+ });
85
+
86
+ describe('managed attachment graph fences', () => {
87
+ it.each([
88
+ [0, 0],
89
+ [17, 0],
90
+ [2.5, 0],
91
+ [2, -1],
92
+ [2, 2],
93
+ [2, 0.5],
94
+ [2, NaN],
95
+ ])(
96
+ 'rejects invalid chunk bounds before persisting staging/quota nodes: %#',
97
+ async (count, index) => {
98
+ await expect(beginShareAttachmentChunk(binding(count), index)).rejects.toMatchObject({
99
+ code: 'INVALID_INPUT',
100
+ });
101
+ const result = await neogma.queryRunner.run(
102
+ 'MATCH (n) WHERE n.namespace = $namespace RETURN count(n) AS count',
103
+ { namespace }
104
+ );
105
+ expect(result.records[0].get('count').toNumber()).toBe(0);
106
+ }
107
+ );
108
+ it('refuses incomplete staged PDFs and rolls back the pending share', async () => {
109
+ const value = binding();
110
+ await stage(value, [0]);
111
+ await expect(reserve(value)).rejects.toMatchObject({ code: 'PRECONDITION_FAILED' });
112
+ expect(
113
+ await getShareLink({
114
+ shareId: value.shareId,
115
+ namespace,
116
+ ownerProfileId: value.ownerProfileId,
117
+ })
118
+ ).toBeNull();
119
+ await completeShareAttachmentChunk(value, 1);
120
+ const pending = await reserve(value);
121
+ await expect(finish(pending, false)).rejects.toMatchObject({ code: 'PRECONDITION_FAILED' });
122
+ const committed = await finish(pending);
123
+ expect(committed.share.attachmentId).toBe(value.attachmentId);
124
+ expect(committed.share.attachmentChunkCount).toBe(2);
125
+ });
126
+
127
+ it('supports idempotent same asset staging but rejects foreign owners and mismatched count', async () => {
128
+ const value = binding();
129
+ await stage(value);
130
+ const pending = await reserve(value);
131
+ await finish(pending);
132
+ await beginShareAttachmentChunk(value, 0);
133
+ await completeShareAttachmentChunk(value, 0);
134
+ await expect(
135
+ beginShareAttachmentChunk({ ...value, ownerProfileId: 'foreign' }, 0)
136
+ ).rejects.toMatchObject({ code: 'NOT_FOUND' });
137
+ await expect(
138
+ beginShareAttachmentChunk({ ...value, chunkCount: 1 }, 0)
139
+ ).rejects.toMatchObject({ code: 'CONFLICT' });
140
+ expect(await deleteUnreferencedShareAttachment(value)).toBe(false);
141
+ });
142
+
143
+ it('serializes cleanup versus pinning so exactly one can acquire the asset', async () => {
144
+ const value = binding();
145
+ await stage(value);
146
+ const [reserved, deleted] = await Promise.allSettled([
147
+ reserve(value),
148
+ deleteUnreferencedShareAttachment(value),
149
+ ]);
150
+ expect(deleted.status).toBe('fulfilled');
151
+ if (reserved.status === 'fulfilled') {
152
+ expect(deleted.status === 'fulfilled' && deleted.value).toBe(false);
153
+ await finish(reserved.value);
154
+ } else {
155
+ expect(deleted.status === 'fulfilled' && deleted.value).toBe(true);
156
+ expect(reserved.reason.code).toBe('PRECONDITION_FAILED');
157
+ await expect(beginShareAttachmentChunk(value, 0)).rejects.toMatchObject({
158
+ code: 'CONFLICT',
159
+ });
160
+ }
161
+ });
162
+
163
+ it('abandonment permanently withdraws asset commit authority and queues every exact chunk', async () => {
164
+ const value = binding(3);
165
+ await stage(value);
166
+ const pending = await reserve(value);
167
+ const abandoned = await abandonReservation({
168
+ namespace,
169
+ ownerProfileId: value.ownerProfileId,
170
+ shareId: value.shareId,
171
+ operationId: pending.operationId,
172
+ });
173
+ expect(abandoned.outcome).toBe('abandoned');
174
+ await expect(finish(pending)).rejects.toMatchObject({ code: 'CONFLICT' });
175
+ await expect(beginShareAttachmentChunk(value, 0)).rejects.toMatchObject({
176
+ code: 'CONFLICT',
177
+ });
178
+ const jobs = await listCleanupJobsForShare(value.shareId);
179
+ expect(
180
+ jobs
181
+ .filter(job => job.operationId === value.attachmentId)
182
+ .map(job => job.objectRef)
183
+ .sort()
184
+ ).toEqual(
185
+ [0, 1, 2].map(index => shareAttachmentObjectId(value.attachmentId, index)).sort()
186
+ );
187
+ expect(
188
+ jobs.every(
189
+ job => job.ownerProfileId === value.ownerProfileId && job.namespace === namespace
190
+ )
191
+ ).toBe(true);
192
+ });
193
+
194
+ it('content replacement preserves the share and queues old chunks, then stopping queues current chunks', async () => {
195
+ const old = binding();
196
+ await stage(old);
197
+ const first = await finish(await reserve(old));
198
+ const next = { ...old, attachmentId: randomUUID(), contentVersion: 2, chunkCount: 3 };
199
+ await stage(next);
200
+ const updated = await reserveReplacement({
201
+ namespace,
202
+ ownerProfileId: old.ownerProfileId,
203
+ shareId: old.shareId,
204
+ expectedVersion: first.share.version,
205
+ clientRequestId: randomUUID(),
206
+ requestHash: 'b'.repeat(64),
207
+ leaseOwner: 'worker',
208
+ content: {
209
+ ...contentBinding(next.attachmentId),
210
+ contentVersion: 2,
211
+ selectedCount: 1,
212
+ attachment: { id: next.attachmentId, chunkCount: next.chunkCount },
213
+ },
214
+ });
215
+ if (updated.outcome !== 'reserved') throw new Error('expected update');
216
+ const second = await finish(updated.reservation);
217
+ expect(second.share.id).toBe(first.share.id);
218
+ expect(second.share.contentVersion).toBe(2);
219
+ expect(second.share.attachmentId).toBe(next.attachmentId);
220
+ let jobs = await listCleanupJobsForShare(old.shareId);
221
+ expect(jobs.filter(job => job.operationId === old.attachmentId)).toHaveLength(2);
222
+ expect(jobs.filter(job => job.operationId === next.attachmentId)).toHaveLength(0);
223
+ await revokeShareLink({
224
+ namespace,
225
+ ownerProfileId: old.ownerProfileId,
226
+ shareId: old.shareId,
227
+ });
228
+ jobs = await listCleanupJobsForShare(old.shareId);
229
+ expect(jobs.filter(job => job.operationId === next.attachmentId)).toHaveLength(3);
230
+ expect(jobs.filter(job => job.operationId === old.attachmentId)).toHaveLength(2);
231
+ });
232
+
233
+ it('bounds concurrent never-committed stages to eight and collects expired orphans', async () => {
234
+ const stages = Array.from({ length: 9 }, () => binding(1));
235
+ const results = await Promise.allSettled(
236
+ stages.map(value => beginShareAttachmentChunk(value, 0))
237
+ );
238
+ expect(results.filter(result => result.status === 'fulfilled')).toHaveLength(8);
239
+ expect(results.filter(result => result.status === 'rejected')).toHaveLength(1);
240
+ const queued = await expireShareAttachmentStages({
241
+ namespace,
242
+ now: new Date(Date.now() + 25 * 60 * 60 * 1000),
243
+ });
244
+ expect(queued).toBe(8);
245
+ const completed = stages[results.findIndex(result => result.status === 'fulfilled')];
246
+ await expect(completeShareAttachmentChunk(completed!, 0)).rejects.toMatchObject({
247
+ code: 'CONFLICT',
248
+ });
249
+ await expect(beginShareAttachmentChunk(binding(1), 0)).resolves.toBeUndefined();
250
+ }, 60_000);
251
+
252
+ it('prevents cross-owner attachment UUID reuse from poisoning global immutable object cleanup', async () => {
253
+ const first = binding(1);
254
+ await stage(first);
255
+ const attacker = { ...binding(1, 'attacker'), attachmentId: first.attachmentId };
256
+ await expect(beginShareAttachmentChunk(attacker, 0)).rejects.toMatchObject({
257
+ code: 'CONFLICT',
258
+ });
259
+ expect(await deleteUnreferencedShareAttachment(attacker)).toBe(false);
260
+ expect(await listCleanupJobsForShare(first.shareId)).toHaveLength(0);
261
+ });
262
+ });
@@ -0,0 +1,244 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { createShareLinkAttachmentOwnerApi } from '../src/helpers/share-link-attachment';
4
+ import { verifyShareAttachmentStats } from '../src/helpers/share-link-coordinator/attachment-stat';
5
+ import {
6
+ mintShareAttachmentAccessToken,
7
+ verifyShareAttachmentAccessToken,
8
+ } from '../src/helpers/share-link-attachment-access';
9
+ import { computeShareLinkRequestHash } from '../src/helpers/share-link-lifecycle';
10
+ import type { ShareLinkReservationRecord } from '../src/accesslayer/share-link/types';
11
+ import type { ShareLinkRecord } from '../src/models/ShareLink';
12
+ import type { ShareContentTuple } from '../src/helpers/share-content-client/types';
13
+ import { ShareLinkRepositoryError } from '../src/accesslayer/share-link/errors';
14
+
15
+ const shareId = Buffer.alloc(16, 7).toString('base64url');
16
+ const attachmentId = randomUUID();
17
+ const owner = { namespace: 'deployment', ownerProfileId: 'owner' };
18
+ const envelope = {
19
+ v: 1 as const,
20
+ alg: 'A256GCM' as const,
21
+ iv: Buffer.alloc(12, 1).toString('base64url'),
22
+ ct: Buffer.alloc(32, 2).toString('base64url'),
23
+ };
24
+ const input = {
25
+ id: shareId,
26
+ contentVersion: 1,
27
+ attachmentId,
28
+ chunkIndex: 0,
29
+ chunkCount: 1,
30
+ envelope,
31
+ ownerEncryptedRecovery: { protected: 'p', iv: 'i', ciphertext: 'c', tag: 't' },
32
+ };
33
+ const summary = (tuple: ShareContentTuple) => ({
34
+ kind: 'active' as const,
35
+ ...tuple,
36
+ contentHash: 'a'.repeat(64),
37
+ payloadHash: 'b'.repeat(64),
38
+ ciphertextBytes: 32,
39
+ recoveryBytes: 4,
40
+ createdAt: '2026-10-03T00:00:00Z',
41
+ });
42
+
43
+ describe('attachment owner transport and immutable commit binding', () => {
44
+ it('retains incomplete stage after lost put acknowledgement and safely retries the same tuple', async () => {
45
+ const repository = {
46
+ begin: vi.fn(async () => undefined),
47
+ complete: vi.fn(async () => undefined),
48
+ deleteUnreferenced: vi.fn(async () => true),
49
+ };
50
+ const client = {
51
+ put: vi.fn(async (value: ShareContentTuple) => ({
52
+ ok: true as const,
53
+ value: { status: 'idempotent' as const, record: summary(value) },
54
+ })),
55
+ delete: vi.fn(async (tuple: ShareContentTuple) => ({
56
+ ok: true as const,
57
+ value: {
58
+ kind: 'tombstone' as const,
59
+ ...tuple,
60
+ deletedAt: '2026-10-03T00:00:00Z',
61
+ },
62
+ })),
63
+ };
64
+ client.put.mockResolvedValueOnce({ ok: false, error: 'NETWORK_ERROR' } as never);
65
+ const api = createShareLinkAttachmentOwnerApi(client, repository);
66
+ await expect(api.put(input, owner)).rejects.toMatchObject({ code: 'UNAVAILABLE' });
67
+ expect(repository.complete).not.toHaveBeenCalled();
68
+ await expect(api.put(input, owner)).resolves.toEqual({ ok: true });
69
+ expect(client.put).toHaveBeenCalledTimes(2);
70
+ expect(client.put.mock.calls[0]![0]).toEqual(client.put.mock.calls[1]![0]);
71
+ expect(repository.complete).toHaveBeenCalledOnce();
72
+ });
73
+
74
+ it('preserves definitive staging failures so callers can discard an expired attempt', async () => {
75
+ const repository = {
76
+ begin: vi
77
+ .fn()
78
+ .mockRejectedValue(
79
+ new ShareLinkRepositoryError('PRECONDITION_FAILED', 'private detail')
80
+ ),
81
+ complete: vi.fn(),
82
+ deleteUnreferenced: vi.fn(),
83
+ };
84
+ const client = { put: vi.fn(), delete: vi.fn() };
85
+ const api = createShareLinkAttachmentOwnerApi(client, repository);
86
+ await expect(api.put(input, owner)).rejects.toMatchObject({
87
+ code: 'PRECONDITION_FAILED',
88
+ message: 'attachment upload unavailable',
89
+ });
90
+ expect(client.put).not.toHaveBeenCalled();
91
+ });
92
+
93
+ it('never deletes a referenced/ambiguous stage and reports physical cleanup failure while durable jobs remain', async () => {
94
+ const client = {
95
+ put: vi.fn(),
96
+ delete: vi.fn().mockResolvedValue({ ok: false, error: 'UNAVAILABLE' }),
97
+ };
98
+ const repository = {
99
+ begin: vi.fn(),
100
+ complete: vi.fn(),
101
+ deleteUnreferenced: vi.fn(async () => false),
102
+ };
103
+ const api = createShareLinkAttachmentOwnerApi(client, repository);
104
+ const key = { id: shareId, contentVersion: 1, attachmentId, chunkCount: 2 };
105
+ await expect(api.delete(key, owner)).resolves.toEqual({ ok: false });
106
+ expect(client.delete).not.toHaveBeenCalled();
107
+ repository.deleteUnreferenced.mockResolvedValueOnce(true);
108
+ await expect(api.delete(key, owner)).resolves.toEqual({ ok: false });
109
+ expect(client.delete).toHaveBeenCalledTimes(2);
110
+ expect(client.delete.mock.calls[1]![0]).toEqual({
111
+ ...owner,
112
+ shareId,
113
+ contentVersion: 1,
114
+ objectId: `resume-pdf-${attachmentId}-1`,
115
+ operationId: attachmentId,
116
+ });
117
+ });
118
+
119
+ it('requires all staged objects stat-present under exactly the committed owner/version/asset tuple', async () => {
120
+ const reservation = {
121
+ ...owner,
122
+ shareId,
123
+ contentVersion: 1,
124
+ attachmentId,
125
+ attachmentChunkCount: 2,
126
+ } as ShareLinkReservationRecord;
127
+ const client = {
128
+ stat: vi.fn(async (tuple: ShareContentTuple) => ({
129
+ ok: true as const,
130
+ value: summary(tuple),
131
+ })),
132
+ };
133
+ await expect(verifyShareAttachmentStats(client, reservation)).resolves.toBe(true);
134
+ expect(client.stat).toHaveBeenCalledTimes(2);
135
+ client.stat.mockResolvedValueOnce({ ok: false, error: 'NOT_FOUND' } as never);
136
+ await expect(verifyShareAttachmentStats(client, reservation)).resolves.toBe(false);
137
+ client.stat.mockImplementationOnce(async tuple => ({
138
+ ok: true as const,
139
+ value: summary({ ...tuple, ownerProfileId: 'foreign' }),
140
+ }));
141
+ await expect(verifyShareAttachmentStats(client, reservation)).resolves.toBe(false);
142
+ await expect(
143
+ verifyShareAttachmentStats(client, reservation, {
144
+ exhausted: () => true,
145
+ remainingMs: () => 0,
146
+ })
147
+ ).resolves.toBe(false);
148
+ });
149
+
150
+ it('binds attachment identity and count into client-request idempotency intent', () => {
151
+ const base = { id: shareId, attachment: { id: attachmentId, chunkCount: 2 } };
152
+ const hash = computeShareLinkRequestHash('create', base);
153
+ expect(
154
+ computeShareLinkRequestHash('create', {
155
+ ...base,
156
+ attachment: { id: randomUUID(), chunkCount: 2 },
157
+ })
158
+ ).not.toBe(hash);
159
+ expect(
160
+ computeShareLinkRequestHash('create', {
161
+ ...base,
162
+ attachment: { id: attachmentId, chunkCount: 1 },
163
+ })
164
+ ).not.toBe(hash);
165
+ });
166
+
167
+ it('reserves the bounded remote call and finalization time before each chunk stat', async () => {
168
+ const reservation = {
169
+ ...owner,
170
+ shareId,
171
+ contentVersion: 1,
172
+ attachmentId,
173
+ attachmentChunkCount: 2,
174
+ } as ShareLinkReservationRecord;
175
+ let remaining = 1600;
176
+ const client = {
177
+ stat: vi.fn(async (tuple: ShareContentTuple) => {
178
+ remaining = 1499;
179
+ return { ok: true as const, value: summary(tuple) };
180
+ }),
181
+ };
182
+ const budget = {
183
+ remainingMs: () => remaining,
184
+ reserveMs: () => 500,
185
+ exhausted: () => false,
186
+ };
187
+ await expect(verifyShareAttachmentStats(client, reservation, budget, 1000)).resolves.toBe(
188
+ false
189
+ );
190
+ expect(client.stat).toHaveBeenCalledOnce();
191
+ });
192
+ });
193
+
194
+ describe('short-lived attachment password grant', () => {
195
+ const record = {
196
+ id: shareId,
197
+ ownerProfileId: 'owner',
198
+ contentVersion: 1,
199
+ attachmentId,
200
+ attachmentChunkCount: 16,
201
+ passcodeHash: '$argon2id$password-verifier',
202
+ } as ShareLinkRecord;
203
+ const now = new Date('2026-10-03T00:00:00Z');
204
+ it('exposes neither password verifier nor identity scope and rejects changed bindings', () => {
205
+ const token = mintShareAttachmentAccessToken(record, 'deployment', '203.0.113.1', now);
206
+ const body = Buffer.from(token.split('.')[0]!, 'base64url').toString('utf8');
207
+ expect(body).not.toContain('password-verifier');
208
+ expect(body).not.toContain('owner');
209
+ expect(body).not.toContain('deployment');
210
+ expect(body).not.toContain('203.0.113.1');
211
+ expect(
212
+ verifyShareAttachmentAccessToken(token, record, 'deployment', '203.0.113.1', now)
213
+ ).toBe(true);
214
+ for (const changed of [
215
+ { ...record, contentVersion: 2 },
216
+ { ...record, attachmentId: randomUUID() },
217
+ { ...record, attachmentChunkCount: 15 },
218
+ { ...record, ownerProfileId: 'someone-else' },
219
+ { ...record, passcodeHash: 'changed' },
220
+ ]) {
221
+ expect(
222
+ verifyShareAttachmentAccessToken(token, changed, 'deployment', '203.0.113.1', now)
223
+ ).toBe(false);
224
+ }
225
+ expect(verifyShareAttachmentAccessToken(token, record, 'foreign', '203.0.113.1', now)).toBe(
226
+ false
227
+ );
228
+ expect(
229
+ verifyShareAttachmentAccessToken(token, record, 'deployment', '203.0.113.2', now)
230
+ ).toBe(false);
231
+ expect(
232
+ verifyShareAttachmentAccessToken(
233
+ token,
234
+ record,
235
+ 'deployment',
236
+ '203.0.113.1',
237
+ new Date(now.getTime() + 60_000)
238
+ )
239
+ ).toBe(false);
240
+ expect(
241
+ verifyShareAttachmentAccessToken(`${token}x`, record, 'deployment', '203.0.113.1', now)
242
+ ).toBe(false);
243
+ });
244
+ });