@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
@@ -1,9 +1,22 @@
1
+ import { SendContractRequestValidator, ContractRequestFieldsValidator } from '@learncard/types';
2
+ import {
3
+ sendGenericContractRequest,
4
+ decideGenericContractRequest,
5
+ markGenericContractRequestSeen,
6
+ assertGenericRequestWriteScope,
7
+ canManageContractRequests,
8
+ resolveContractRequest,
9
+ } from '@helpers/contract-requests.helpers';
1
10
  import { environment } from '@environment';
11
+ import {
12
+ smartResumeFingerprint,
13
+ publishSmartResume,
14
+ } from '@accesslayer/consentflowcontract/relationships/smartResume';
15
+ import { uploadSmartResume } from '@helpers/smartResume.helpers';
2
16
  import { TRPCError } from '@trpc/server';
3
17
  import { z } from 'zod';
4
18
 
5
19
  import { t, profileRoute, openRoute, guardianGatedRoute } from '@routes';
6
- import { ConsentFlowContract } from '@models';
7
20
 
8
21
  import {
9
22
  ConsentFlowContractValidator,
@@ -47,10 +60,11 @@ import {
47
60
  isProfileConsentFlowContractAdmin,
48
61
  getWritersForContract,
49
62
  } from '@accesslayer/consentflowcontract/relationships/read';
50
- import { constructUri, getIdFromUri, resolveUri } from '@helpers/uri.helpers';
63
+ import { inflateObject } from '@helpers/objects.helpers';
64
+ import { constructUri, getIdFromUri } from '@helpers/uri.helpers';
51
65
  import {
52
66
  getContractByUri,
53
- getConsentFlowContractById,
67
+ getStoredContractRequest,
54
68
  getRequestedForList,
55
69
  getRequestedForForUser,
56
70
  getContractById,
@@ -64,19 +78,15 @@ import { areTermsValid } from '@helpers/contract.helpers';
64
78
  import { updateDidForProfile, getProfileIdFromString } from '@helpers/did.helpers';
65
79
  import {
66
80
  syncCredentialsToContract,
67
- updateRequestedForStatusIfExists,
68
81
  updateTerms,
69
82
  pruneDeletedUrisFromConsentTerms,
70
83
  upsertRequestedForRelationship,
71
84
  withdrawTerms,
72
85
  } from '@accesslayer/consentflowcontract/relationships/update';
73
- import {
74
- consentToContract,
75
- setAutoBoostForContract,
76
- setCreatorForContract,
77
- } from '@accesslayer/consentflowcontract/relationships/create';
86
+ import { consentToContract } from '@accesslayer/consentflowcontract/relationships/create';
78
87
  import { getProfileByDid, getProfileByProfileId } from '@accesslayer/profile/read';
79
88
  import { sendBoost, isDraftBoost } from '@helpers/boost.helpers';
89
+ import { setCredentialSubjectIds } from '@helpers/credentialSubject.helpers';
80
90
  import { isRelationshipBlocked } from '@helpers/connection.helpers';
81
91
  import { getBoostByUri } from '@accesslayer/boost/read';
82
92
  import { canProfileIssueBoost } from '@accesslayer/boost/relationships/read';
@@ -104,6 +114,24 @@ import { addNotificationToQueue } from '@helpers/notifications.helpers';
104
114
  import { getNotificationMessage } from '@helpers/notificationMessages';
105
115
  import { resolveRecipientLocale } from '@helpers/getRecipientLocale.helpers';
106
116
  import { ProfileType } from 'types/profile';
117
+ import {
118
+ getRecipientsForContract,
119
+ addRecipientToContract,
120
+ removeRecipientFromContract,
121
+ canReadContractData,
122
+ } from '@accesslayer/consentflowcontract/relationships/recipients';
123
+
124
+ const publicContractRecipient = (domain: string, recipient: ProfileType) => {
125
+ const profile = updateDidForProfile(domain, recipient);
126
+ return { ...sanitizeProfileForTier(profile, 'unauthenticated'), did: profile.did };
127
+ };
128
+
129
+ const resolveRecipientProfileId = async (identifier: string, domain: string): Promise<string> => {
130
+ const profileId = await getProfileIdFromString(identifier, domain);
131
+ if (!profileId)
132
+ throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid recipient identifier' });
133
+ return profileId;
134
+ };
107
135
 
108
136
  export const contractsRouter = t.router({
109
137
  createConsentFlowContract: profileRoute
@@ -132,6 +160,7 @@ export const contractsRouter = t.router({
132
160
  expiresAt: z.string().optional(),
133
161
  autoboosts: z.array(AutoBoostConfigValidator).optional(),
134
162
  writers: z.array(z.string()).optional(),
163
+ recipients: z.array(z.string()).max(50).optional(),
135
164
  })
136
165
  )
137
166
  .output(z.string())
@@ -149,26 +178,31 @@ export const contractsRouter = t.router({
149
178
  expiresAt,
150
179
  autoboosts,
151
180
  writers,
181
+ recipients,
152
182
  } = input;
153
183
 
154
- // Create ConsentFlow instance
155
- const createdContract = await createConsentFlowContract({
156
- contract,
157
- name,
158
- subtitle,
159
- description,
160
- reasonForAccessing,
161
- needsGuardianConsent,
162
- redirectUrl,
163
- frontDoorBoostUri,
164
- image,
165
- expiresAt,
166
- });
184
+ const recipientIds = [
185
+ ...new Set(
186
+ await Promise.all(
187
+ (recipients ?? []).map(id => resolveRecipientProfileId(id, ctx.domain))
188
+ )
189
+ ),
190
+ ].filter(id => id !== ctx.user.profile.profileId);
191
+ const recipientProfiles = await getProfilesByProfileIds(recipientIds);
192
+ if (recipientProfiles.length !== recipientIds.length) {
193
+ throw new TRPCError({
194
+ code: 'BAD_REQUEST',
195
+ message: 'Could not find all recipient profiles',
196
+ });
197
+ }
167
198
 
168
- // Get profile by profileId
169
- await setCreatorForContract(createdContract, ctx.user.profile);
199
+ const preparedAutoBoosts: {
200
+ id: string;
201
+ signingAuthorityEndpoint: string;
202
+ signingAuthorityName: string;
203
+ }[] = [];
170
204
 
171
- // Add specified writers
205
+ // Validate specified writers before creating the contract.
172
206
  if (writers && writers.length > 0) {
173
207
  const writerProfiles = await getProfilesByProfileIds(writers);
174
208
 
@@ -183,17 +217,6 @@ export const contractsRouter = t.router({
183
217
  )}`,
184
218
  });
185
219
  }
186
-
187
- // Create CAN_WRITE relationship for each writer using static relateTo
188
- for (const writerProfile of writerProfiles) {
189
- await ConsentFlowContract.relateTo({
190
- alias: 'canWrite',
191
- where: {
192
- source: { id: createdContract.id }, // Specify source contract by id
193
- target: { profileId: writerProfile.profileId }, // Specify target profile by profileId
194
- },
195
- });
196
- }
197
220
  }
198
221
 
199
222
  if (autoboosts && autoboosts.length > 0) {
@@ -242,18 +265,94 @@ export const contractsRouter = t.router({
242
265
  });
243
266
  }
244
267
 
245
- await setAutoBoostForContract(
246
- createdContract,
247
- boost,
248
- normalizedSigningAuthority,
249
- ctx.user.profile.profileId
250
- );
268
+ preparedAutoBoosts.push({
269
+ id: boost.id,
270
+ signingAuthorityEndpoint: normalizedSigningAuthority.endpoint,
271
+ signingAuthorityName: normalizedSigningAuthority.name,
272
+ });
251
273
  }
252
274
  }
253
275
 
276
+ // All validation precedes the single atomic creation statement.
277
+ const createdContract = await createConsentFlowContract({
278
+ contract,
279
+ ownerProfileId: ctx.user.profile.profileId,
280
+ recipientIds,
281
+ writerIds: writers,
282
+ autoboosts: preparedAutoBoosts,
283
+ name,
284
+ subtitle,
285
+ description,
286
+ reasonForAccessing,
287
+ needsGuardianConsent,
288
+ redirectUrl,
289
+ frontDoorBoostUri,
290
+ image,
291
+ expiresAt,
292
+ });
293
+
254
294
  return constructUri('contract', createdContract.id, ctx.domain);
255
295
  }),
256
296
 
297
+ addContractRecipient: profileRoute
298
+ .meta({
299
+ requiredScope: 'contracts:write',
300
+ openapi: {
301
+ protect: true,
302
+ method: 'POST',
303
+ path: '/consent-flow-contract/recipient/add',
304
+ tags: ['Contracts'],
305
+ summary: 'Add a data recipient before first consent',
306
+ },
307
+ })
308
+ .input(z.object({ contractUri: z.string(), recipient: z.string() }))
309
+ .output(z.boolean())
310
+ .mutation(async ({ ctx, input }) => {
311
+ const contract = await getContractByUri(input.contractUri);
312
+ if (!contract)
313
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
314
+ if (!(await isProfileConsentFlowContractAdmin(ctx.user.profile, contract)))
315
+ throw new TRPCError({
316
+ code: 'UNAUTHORIZED',
317
+ message: 'Profile does not own contract',
318
+ });
319
+ const profileId = await resolveRecipientProfileId(input.recipient, ctx.domain);
320
+ if (!(await getProfileByProfileId(profileId)))
321
+ throw new TRPCError({
322
+ code: 'BAD_REQUEST',
323
+ message: 'Could not find recipient profile',
324
+ });
325
+ return addRecipientToContract(contract.id, profileId);
326
+ }),
327
+
328
+ removeContractRecipient: profileRoute
329
+ .meta({
330
+ requiredScope: 'contracts:write',
331
+ openapi: {
332
+ protect: true,
333
+ method: 'POST',
334
+ path: '/consent-flow-contract/recipient/remove',
335
+ tags: ['Contracts'],
336
+ summary: 'Remove a data recipient',
337
+ },
338
+ })
339
+ .input(z.object({ contractUri: z.string(), recipient: z.string() }))
340
+ .output(z.boolean())
341
+ .mutation(async ({ ctx, input }) => {
342
+ const contract = await getContractByUri(input.contractUri);
343
+ if (!contract)
344
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
345
+ if (!(await isProfileConsentFlowContractAdmin(ctx.user.profile, contract)))
346
+ throw new TRPCError({
347
+ code: 'UNAUTHORIZED',
348
+ message: 'Profile does not own contract',
349
+ });
350
+ return removeRecipientFromContract(
351
+ contract.id,
352
+ await resolveRecipientProfileId(input.recipient, ctx.domain)
353
+ );
354
+ }),
355
+
257
356
  getConsentFlowContract: openRoute
258
357
  .meta({
259
358
  openapi: {
@@ -279,6 +378,7 @@ export const contractsRouter = t.router({
279
378
  }
280
379
 
281
380
  const autoboosts = await getAutoBoostsForContract(result.contract.id, ctx.domain);
381
+ const recipients = await getRecipientsForContract(result.contract.id);
282
382
 
283
383
  return {
284
384
  owner: updateDidForProfile(ctx.domain, result.contractOwner),
@@ -296,6 +396,10 @@ export const contractsRouter = t.router({
296
396
  uri: constructUri('contract', result.contract.id, ctx.domain),
297
397
  ...(result.contract.expiresAt ? { expiresAt: result.contract.expiresAt } : {}),
298
398
  autoBoosts: autoboosts,
399
+ recipients: recipients.map(recipient =>
400
+ publicContractRecipient(ctx.domain, recipient)
401
+ ),
402
+ audienceVersion: Number(result.contract.audienceVersion ?? 0),
299
403
  };
300
404
  }),
301
405
 
@@ -425,16 +529,16 @@ export const contractsRouter = t.router({
425
529
  throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
426
530
  }
427
531
 
428
- if (!(await isProfileConsentFlowContractAdmin(profile, contract))) {
532
+ if (!(await canReadContractData(contract.id, profile.profileId))) {
429
533
  throw new TRPCError({
430
534
  code: 'UNAUTHORIZED',
431
- message: 'Profile does not own contract',
535
+ message: 'Profile is not in the contract data audience',
432
536
  });
433
537
  }
434
538
 
435
539
  const contractId = getIdFromUri(uri);
436
540
 
437
- const results = await getConsentedDataForContract(contractId, {
541
+ const results = await getConsentedDataForContract(contractId, profile.profileId, {
438
542
  query,
439
543
  limit: limit + 1,
440
544
  cursor,
@@ -818,17 +922,7 @@ export const contractsRouter = t.router({
818
922
  unsignedVc.issuanceDate = new Date().toISOString();
819
923
  }
820
924
  unsignedVc.issuer = { id: getDidWeb(ctx.domain, profile.profileId) };
821
- if (Array.isArray(unsignedVc.credentialSubject)) {
822
- unsignedVc.credentialSubject = unsignedVc.credentialSubject.map(subject => ({
823
- ...subject,
824
- id: getDidWeb(ctx.domain, otherProfile.profileId),
825
- }));
826
- } else {
827
- unsignedVc.credentialSubject = {
828
- ...unsignedVc.credentialSubject,
829
- id: getDidWeb(ctx.domain, otherProfile.profileId),
830
- };
831
- }
925
+ setCredentialSubjectIds(unsignedVc, getDidWeb(ctx.domain, otherProfile.profileId));
832
926
  if (unsignedVc?.type?.includes('BoostCredential')) unsignedVc.boostId = boostUri;
833
927
  // Inject OBv3 skill alignments based on boost's framework/skills
834
928
  await injectObv3AlignmentsIntoCredentialForBoost(unsignedVc, boost, ctx.domain);
@@ -901,6 +995,8 @@ export const contractsRouter = t.router({
901
995
  contractUri: z.string(),
902
996
  expiresAt: z.string().optional(),
903
997
  oneTime: z.boolean().optional(),
998
+ audienceVersion: z.number().int().nonnegative().optional(),
999
+ expectedRequestId: z.string().min(1).optional(),
904
1000
  recipientToken: z.string().optional(), // SmartResume recipientToken needed for API call
905
1001
  })
906
1002
  )
@@ -916,7 +1012,15 @@ export const contractsRouter = t.router({
916
1012
  });
917
1013
  }
918
1014
 
919
- const { terms, contractUri, expiresAt, oneTime, recipientToken } = input;
1015
+ const {
1016
+ terms,
1017
+ contractUri,
1018
+ expiresAt,
1019
+ oneTime,
1020
+ recipientToken,
1021
+ audienceVersion,
1022
+ expectedRequestId,
1023
+ } = input;
920
1024
 
921
1025
  const contractDetails = await getContractDetailsByUri(contractUri);
922
1026
 
@@ -924,7 +1028,37 @@ export const contractsRouter = t.router({
924
1028
  throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
925
1029
  }
926
1030
 
927
- if (await hasProfileConsentedToContract(profile, contractDetails.contract)) {
1031
+ if (
1032
+ contractDetails.contract.expiresAt?.trim() &&
1033
+ (!Number.isFinite(Date.parse(contractDetails.contract.expiresAt)) ||
1034
+ Date.parse(contractDetails.contract.expiresAt) <= Date.now())
1035
+ ) {
1036
+ throw new TRPCError({ code: 'CONFLICT', message: 'This contract has expired.' });
1037
+ }
1038
+ const isSmartResume =
1039
+ contractUri === environment.SMART_RESUME_CONTRACT_URI ||
1040
+ contractUri ===
1041
+ 'lc:network:network.learncard.com/trpc:contract:55b738f0-49f4-4b33-b6c1-afa99b605cd6';
1042
+ if (isSmartResume && !recipientToken) {
1043
+ throw new TRPCError({
1044
+ code: 'BAD_REQUEST',
1045
+ message: 'Missing recipientToken for SmartResume',
1046
+ });
1047
+ }
1048
+ const previousTerms = await getContractTermsForProfile(
1049
+ profile,
1050
+ contractDetails.contract
1051
+ );
1052
+ const retrySmartResume =
1053
+ isSmartResume &&
1054
+ Boolean(previousTerms?.smartResumeFingerprint) &&
1055
+ (previousTerms?.status === 'live' ||
1056
+ (previousTerms?.status === 'stale' && previousTerms.oneTime));
1057
+
1058
+ if (
1059
+ !retrySmartResume &&
1060
+ (await hasProfileConsentedToContract(profile, contractDetails.contract))
1061
+ ) {
928
1062
  try {
929
1063
  await upsertRequestedForRelationship(
930
1064
  contractDetails.contract.id,
@@ -939,10 +1073,6 @@ export const contractsRouter = t.router({
939
1073
  }
940
1074
 
941
1075
  if (!guardianIdentity) {
942
- const previousTerms = await getContractTermsForProfile(
943
- profile,
944
- contractDetails.contract
945
- );
946
1076
  if (previousTerms && (await hasGuardianApprovalHistory(previousTerms))) {
947
1077
  throw new TRPCError({
948
1078
  code: 'FORBIDDEN',
@@ -963,156 +1093,46 @@ export const contractsRouter = t.router({
963
1093
  );
964
1094
  }
965
1095
 
966
- let redirectUrl: string | undefined;
967
- // SmartResume handling
968
- const isSmartResume =
969
- contractUri === environment.SMART_RESUME_CONTRACT_URI ||
970
- contractUri ===
971
- 'lc:network:network.learncard.com/trpc:contract:55b738f0-49f4-4b33-b6c1-afa99b605cd6'; // hardcode for quick fix purposes
972
- if (isSmartResume) {
973
- if (!recipientToken) {
974
- throw new Error('Missing recipientToken for SmartResume');
975
- }
976
-
977
- const isProduction = !environment.IS_OFFLINE;
978
-
979
- const srUrl = isProduction
980
- ? 'https://my.smartresume.com/'
981
- : 'https://mystage.smartresume.com/';
982
- const clientId = environment.SMART_RESUME_CLIENT_ID;
983
- const accessKey = environment.SMART_RESUME_ACCESS_KEY;
984
-
985
- const accessTokenResponse = (await fetch(`${srUrl}api/v1/token`, {
986
- method: 'POST',
987
- headers: {
988
- 'Content-Type': 'application/x-www-form-urlencoded',
989
- Authorization: `Basic ${btoa(`${clientId}:${accessKey}`)}`,
990
- },
991
- body: new URLSearchParams({
992
- grant_type: 'client_credentials',
993
- scope: 'delete readonly replace',
994
- }),
995
- }).then(res => res.json())) as { access_token?: string };
996
-
997
- const accessToken = accessTokenResponse.access_token;
998
- if (!accessToken) throw new Error('Missing access_token for SmartResume');
999
-
1000
- const parsedTerms = ConsentFlowTermsValidator.parse(terms);
1001
-
1002
- const categories = parsedTerms.read.credentials.categories;
1003
- const categoryValues = Object.values(categories) as Array<{ shared?: string[] }>;
1004
-
1005
- const allSharedCredentialUris = [
1006
- // filter out duplicates
1007
- ...new Set(categoryValues.flatMap(({ shared }) => shared ?? [])),
1008
- ];
1009
-
1010
- const resolvedCredentials = await Promise.all(
1011
- allSharedCredentialUris.map(async uri => {
1012
- try {
1013
- return await resolveUri(uri);
1014
- } catch (error) {
1015
- console.error(`Error resolving URI ${uri}:`, error);
1016
- return undefined;
1017
- }
1018
- })
1019
- );
1020
-
1021
- type ResolvedCredential = {
1022
- issuer?: string | { id: string };
1023
- id?: string;
1024
- boostCredential?: Record<string, unknown>;
1025
- } & Record<string, unknown>;
1026
-
1027
- const credentials = resolvedCredentials
1028
- .filter(
1029
- (cred): cred is ResolvedCredential =>
1030
- typeof cred === 'object' && cred !== null
1031
- )
1032
- .map(cred =>
1033
- cred.boostCredential && typeof cred.boostCredential === 'object'
1034
- ? ({ ...cred.boostCredential, id: cred.id } as ResolvedCredential) // unwrap credential, preserve id
1035
- : cred
1036
- );
1037
-
1038
- const transformedCredentials = credentials.map(cred => {
1039
- const issuer =
1040
- typeof cred.issuer === 'string'
1041
- ? { id: cred.issuer }
1042
- : cred.issuer || { id: '' };
1043
-
1044
- return {
1045
- ...cred,
1046
- issuer,
1047
- };
1048
- });
1049
-
1050
- const { name, email } = parsedTerms.read.personal;
1051
-
1052
- const body = JSON.stringify({
1053
- '@context': [
1054
- 'https://www.w3.org/ns/credentials/v2',
1055
- 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json',
1056
- 'https://w3id.org/security/suites/ed25519-2020/v1',
1057
- ],
1058
- recipienttoken: recipientToken,
1059
- recipient: {
1060
- id: ctx.user.did,
1061
- givenName: name && name !== 'Anonymous' ? name : '',
1062
- familyName: '', // this is neecessary in order for givenName to be respected
1063
- email: email && email !== 'anonymous@hidden.com' ? email : '',
1096
+ const fingerprint = isSmartResume
1097
+ ? smartResumeFingerprint({
1098
+ terms,
1099
+ expiresAt,
1100
+ oneTime,
1101
+ audienceVersion,
1102
+ recipientToken,
1103
+ expectedRequestId,
1104
+ })
1105
+ : undefined;
1106
+ // A completed one-time snapshot can be followed by a new reviewed decision.
1107
+ // Identical submissions still replay the completed upload instead.
1108
+ const newDecisionAfterOneTime =
1109
+ retrySmartResume &&
1110
+ previousTerms?.status === 'stale' &&
1111
+ previousTerms.oneTime &&
1112
+ previousTerms.smartResumePublicationStatus === 'succeeded' &&
1113
+ fingerprint !== previousTerms.smartResumeFingerprint;
1114
+ if (!retrySmartResume || newDecisionAfterOneTime)
1115
+ await consentToContract(
1116
+ profile,
1117
+ contractDetails,
1118
+ {
1119
+ terms,
1120
+ expiresAt,
1121
+ oneTime,
1122
+ audienceVersion,
1123
+ expectedRequestId,
1124
+ smartResumeFingerprint: fingerprint,
1125
+ guardianApproval: guardianIdentity
1126
+ ? {
1127
+ guardianProfileId: guardianIdentity.profileId,
1128
+ guardianDid: guardianIdentity.did,
1129
+ approvedAt: new Date().toISOString(),
1130
+ contractUpdatedAt: contractDetails.contract.updatedAt,
1131
+ }
1132
+ : undefined,
1064
1133
  },
1065
- credentials: transformedCredentials,
1066
- });
1067
-
1068
- try {
1069
- const response = await fetch(`${srUrl}api/v1/credentials`, {
1070
- method: 'POST',
1071
- headers: {
1072
- Authorization: `Bearer ${accessToken}`,
1073
- 'Content-Type': 'application/json',
1074
- },
1075
- body,
1076
- });
1077
-
1078
- if (!response.ok) {
1079
- throw new Error(`Error (${response.status}): ${await response.text()}`);
1080
- }
1081
-
1082
- const result = (await response.json()) as { redirect_url?: string };
1083
- redirectUrl = result.redirect_url;
1084
- } catch (error) {
1085
- console.error('Error uploading credentials to SmartResume:', error);
1086
- throw error;
1087
- }
1088
- }
1089
-
1090
- await consentToContract(
1091
- profile,
1092
- contractDetails,
1093
- {
1094
- terms,
1095
- expiresAt,
1096
- oneTime,
1097
- guardianApproval: guardianIdentity
1098
- ? {
1099
- guardianProfileId: guardianIdentity.profileId,
1100
- guardianDid: guardianIdentity.did,
1101
- approvedAt: new Date().toISOString(),
1102
- contractUpdatedAt: contractDetails.contract.updatedAt,
1103
- }
1104
- : undefined,
1105
- },
1106
- ctx.domain
1107
- );
1108
-
1109
- try {
1110
- await updateRequestedForStatusIfExists(
1111
- contractDetails.contract.id,
1112
- profile.profileId,
1113
- 'accepted'
1134
+ ctx.domain
1114
1135
  );
1115
- } catch {}
1116
1136
 
1117
1137
  const relationship = await getContractTermsForProfile(
1118
1138
  profile,
@@ -1126,6 +1146,18 @@ export const contractsRouter = t.router({
1126
1146
  });
1127
1147
  }
1128
1148
 
1149
+ const redirectUrl = isSmartResume
1150
+ ? await publishSmartResume({
1151
+ contractId: contractDetails.contract.id,
1152
+ profileId: profile.profileId,
1153
+ termsId: relationship.id,
1154
+ fingerprint: fingerprint!,
1155
+ audienceVersion,
1156
+ expectedRequestId,
1157
+ upload: acceptedTerms =>
1158
+ uploadSmartResume(acceptedTerms, ctx.user.did, recipientToken!),
1159
+ })
1160
+ : undefined;
1129
1161
  return { termsUri: constructUri('terms', relationship.id, ctx.domain), redirectUrl };
1130
1162
  }),
1131
1163
 
@@ -1187,9 +1219,14 @@ export const contractsRouter = t.router({
1187
1219
  ? { expiresAt: record.contract.expiresAt }
1188
1220
  : {}),
1189
1221
  autoBoosts: record.autoBoosts,
1222
+ audienceVersion: Number(record.contract.audienceVersion ?? 0),
1223
+ recipients: record.recipients.map(recipient =>
1224
+ publicContractRecipient(ctx.domain, recipient)
1225
+ ),
1190
1226
  },
1191
1227
  uri: constructUri('terms', record.terms.id, ctx.domain),
1192
1228
  terms: record.terms.terms,
1229
+ ...(record.terms.referral ? { referral: record.terms.referral } : {}),
1193
1230
  ...(record.terms.expiresAt ? { expiresAt: record.terms.expiresAt } : {}),
1194
1231
  ...(record.terms.oneTime ? { oneTime: record.terms.oneTime } : {}),
1195
1232
  consenter: updateDidForProfile(ctx.domain, profile),
@@ -1216,6 +1253,7 @@ export const contractsRouter = t.router({
1216
1253
  terms: ConsentFlowTermsValidator,
1217
1254
  expiresAt: z.string().optional(),
1218
1255
  oneTime: z.boolean().optional(),
1256
+ audienceVersion: z.number().int().nonnegative().optional(),
1219
1257
  })
1220
1258
  )
1221
1259
  .output(z.boolean())
@@ -1231,7 +1269,7 @@ export const contractsRouter = t.router({
1231
1269
  });
1232
1270
  }
1233
1271
 
1234
- const { uri, terms, expiresAt, oneTime } = input;
1272
+ const { uri, terms, expiresAt, oneTime, audienceVersion } = input;
1235
1273
 
1236
1274
  const decodedUri = decodeURIComponent(uri);
1237
1275
  const relationship = await getContractTermsByUri(decodedUri);
@@ -1280,6 +1318,7 @@ export const contractsRouter = t.router({
1280
1318
  terms,
1281
1319
  expiresAt,
1282
1320
  oneTime,
1321
+ audienceVersion,
1283
1322
  guardianApproval: guardianIdentity
1284
1323
  ? {
1285
1324
  guardianProfileId: guardianIdentity.profileId,
@@ -1405,7 +1444,7 @@ export const contractsRouter = t.router({
1405
1444
  });
1406
1445
  }
1407
1446
 
1408
- await Promise.all([withdrawTerms(relationship), deleteStorageForUri(uri)]);
1447
+ await Promise.all([withdrawTerms(relationship, ctx.domain), deleteStorageForUri(uri)]);
1409
1448
 
1410
1449
  return true;
1411
1450
  }),
@@ -1537,13 +1576,14 @@ export const contractsRouter = t.router({
1537
1576
  .input(
1538
1577
  z.object({
1539
1578
  termsUri: z.string(),
1579
+ audienceVersion: z.number().int().nonnegative().optional(),
1540
1580
  categories: z.record(z.string(), z.string().array()),
1541
1581
  })
1542
1582
  )
1543
1583
  .output(z.boolean())
1544
1584
  .mutation(async ({ ctx, input }) => {
1545
1585
  const { profile } = ctx.user;
1546
- const { termsUri, categories } = input;
1586
+ const { termsUri, categories, audienceVersion } = input;
1547
1587
 
1548
1588
  // Verify the terms exist and belong to this profile
1549
1589
  const relationship = await getContractTermsByUri(termsUri);
@@ -1613,7 +1653,12 @@ export const contractsRouter = t.router({
1613
1653
  }
1614
1654
 
1615
1655
  // Sync the credentials
1616
- const result = await syncCredentialsToContract(relationship, categories);
1656
+ const result = await syncCredentialsToContract(
1657
+ relationship,
1658
+ categories,
1659
+ audienceVersion,
1660
+ ctx.domain
1661
+ );
1617
1662
 
1618
1663
  return result;
1619
1664
  }),
@@ -1833,6 +1878,49 @@ export const contractsRouter = t.router({
1833
1878
  return true;
1834
1879
  }),
1835
1880
 
1881
+ sendContractRequest: profileRoute
1882
+ .meta({
1883
+ requiredScope: 'contracts:write',
1884
+ openapi: {
1885
+ protect: true,
1886
+ method: 'POST',
1887
+ path: '/consent-flow-contracts/request',
1888
+ tags: ['Contracts'],
1889
+ summary: 'Send an attributed contract request',
1890
+ },
1891
+ })
1892
+ .input(SendContractRequestValidator)
1893
+ .output(z.boolean())
1894
+ .mutation(async ({ ctx, input }) =>
1895
+ sendGenericContractRequest(input, ctx.user.profile, ctx.domain)
1896
+ ),
1897
+
1898
+ denyContractRequest: profileRoute
1899
+ .meta({
1900
+ requiredScope: 'contracts:write',
1901
+ openapi: {
1902
+ protect: true,
1903
+ method: 'POST',
1904
+ path: '/consent-flow-contracts/deny-request',
1905
+ tags: ['Contracts'],
1906
+ summary: 'Deny a pending contract request as its target',
1907
+ },
1908
+ })
1909
+ .input(z.object({ contractUri: z.string() }))
1910
+ .output(z.boolean())
1911
+ .mutation(async ({ ctx, input }) => {
1912
+ const contract = await getContractByUri(input.contractUri);
1913
+ if (!contract)
1914
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
1915
+ return decideGenericContractRequest({
1916
+ contract,
1917
+ targetProfileId: ctx.user.profile.profileId,
1918
+ actorProfileId: ctx.user.profile.profileId,
1919
+ status: 'denied',
1920
+ domain: ctx.domain,
1921
+ });
1922
+ }),
1923
+
1836
1924
  sendAiInsightsContractRequest: profileRoute
1837
1925
  .meta({
1838
1926
  openapi: {
@@ -1857,43 +1945,14 @@ export const contractsRouter = t.router({
1857
1945
  const { profile } = ctx.user;
1858
1946
  const { contractUri, targetProfileId, shareLink } = input;
1859
1947
 
1860
- const resolvedTargetProfileId = await getProfileIdFromString(
1948
+ const { contract: contractByUri, target: targetProfile } = await resolveContractRequest(
1949
+ contractUri,
1861
1950
  targetProfileId,
1862
- ctx.domain
1951
+ profile,
1952
+ ctx.domain,
1953
+ false
1863
1954
  );
1864
- if (!resolvedTargetProfileId) {
1865
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Profile not found' });
1866
- }
1867
-
1868
- const contractByUri = await getContractByUri(contractUri);
1869
-
1870
- if (!contractByUri)
1871
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
1872
-
1873
- const contract = await getConsentFlowContractById(contractByUri.id); // model instance
1874
-
1875
- if (!contract) {
1876
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
1877
- }
1878
-
1879
- const writers = await getWritersForContract(contractByUri);
1880
- const isAuthorized = writers.some(writer => writer.profileId === profile.profileId);
1881
-
1882
- if (!isAuthorized) {
1883
- throw new TRPCError({
1884
- code: 'UNAUTHORIZED',
1885
- message: 'You do not have permission to send requests for this contract.',
1886
- });
1887
- }
1888
-
1889
- const targetProfile = await getProfileByProfileId(resolvedTargetProfileId);
1890
-
1891
- if (!targetProfile) {
1892
- throw new TRPCError({
1893
- code: 'NOT_FOUND',
1894
- message: 'Target profile not found.',
1895
- });
1896
- }
1955
+ const resolvedTargetProfileId = targetProfile.profileId;
1897
1956
 
1898
1957
  try {
1899
1958
  await upsertRequestedForRelationship(
@@ -1903,6 +1962,7 @@ export const contractsRouter = t.router({
1903
1962
  null
1904
1963
  );
1905
1964
  } catch (error) {
1965
+ if (error instanceof TRPCError && error.code === 'CONFLICT') throw error;
1906
1966
  throw new TRPCError({
1907
1967
  code: 'BAD_REQUEST',
1908
1968
  message: 'Unable to send request',
@@ -2035,9 +2095,9 @@ export const contractsRouter = t.router({
2035
2095
  )
2036
2096
  .output(
2037
2097
  z.array(
2038
- z.object({
2098
+ ContractRequestFieldsValidator.extend({
2039
2099
  profile: LCNProfileValidator,
2040
- status: z.enum(['pending', 'accepted', 'denied']).nullable(),
2100
+ status: z.enum(['pending', 'accepted', 'denied', 'cancelled']).nullable(),
2041
2101
  readStatus: z.enum(['unseen', 'seen']).nullable().optional(),
2042
2102
  })
2043
2103
  )
@@ -2050,17 +2110,19 @@ export const contractsRouter = t.router({
2050
2110
 
2051
2111
  if (!contractByUri)
2052
2112
  throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
2053
- const writers = await getWritersForContract(contractByUri);
2054
- const isAuthorized = writers.some(w => w.profileId === profile.profileId);
2113
+ const isAuthorized = await canManageContractRequests(contractByUri, profile.profileId);
2055
2114
 
2056
- if (!isAuthorized) {
2115
+ if (
2116
+ !isAuthorized &&
2117
+ !(await canReadContractData(contractByUri.id, profile.profileId))
2118
+ ) {
2057
2119
  throw new TRPCError({
2058
2120
  code: 'UNAUTHORIZED',
2059
2121
  message: 'You do not have permissions to view requests made for this contract.',
2060
2122
  });
2061
2123
  }
2062
2124
 
2063
- const requests = await getRequestedForList(contractByUri.id);
2125
+ const requests = await getRequestedForList(contractByUri.id, profile.profileId);
2064
2126
 
2065
2127
  return Promise.all(
2066
2128
  requests.map(async request => {
@@ -2095,13 +2157,11 @@ export const contractsRouter = t.router({
2095
2157
  })
2096
2158
  )
2097
2159
  .output(
2098
- z
2099
- .object({
2100
- profile: LCNProfileValidator,
2101
- status: z.enum(['pending', 'accepted', 'denied']).nullable(),
2102
- readStatus: z.enum(['unseen', 'seen']).nullable().optional(),
2103
- })
2104
- .nullable()
2160
+ ContractRequestFieldsValidator.extend({
2161
+ profile: LCNProfileValidator,
2162
+ status: z.enum(['pending', 'accepted', 'denied', 'cancelled']).nullable(),
2163
+ readStatus: z.enum(['unseen', 'seen']).nullable().optional(),
2164
+ }).nullable()
2105
2165
  )
2106
2166
  .query(async ({ ctx, input }) => {
2107
2167
  const { profile } = ctx.user;
@@ -2128,10 +2188,9 @@ export const contractsRouter = t.router({
2128
2188
  const isCheckingOwnStatus = profile.profileId === resolvedTargetProfileId;
2129
2189
 
2130
2190
  if (!isCheckingOwnStatus) {
2131
- const writers = await getWritersForContract(contract);
2132
- const isAuthorized = writers.some(w => w.profileId === profile.profileId);
2191
+ const isAuthorized = await canManageContractRequests(contract, profile.profileId);
2133
2192
 
2134
- if (!isAuthorized) {
2193
+ if (!isAuthorized && !(await canReadContractData(contract.id, profile.profileId))) {
2135
2194
  throw new TRPCError({
2136
2195
  code: 'UNAUTHORIZED',
2137
2196
  message:
@@ -2140,7 +2199,11 @@ export const contractsRouter = t.router({
2140
2199
  }
2141
2200
  }
2142
2201
 
2143
- const requests = await getRequestedForForUser(contract.id, resolvedTargetProfileId);
2202
+ const requests = await getRequestedForForUser(
2203
+ contract.id,
2204
+ resolvedTargetProfileId,
2205
+ profile.profileId
2206
+ );
2144
2207
 
2145
2208
  if (!requests?.[0]) return null;
2146
2209
 
@@ -2163,7 +2226,7 @@ export const contractsRouter = t.router({
2163
2226
  tags: ['Contracts'],
2164
2227
  summary: 'Marks a contract request as seen',
2165
2228
  description:
2166
- 'Updates the read status of a contract request to "seen" for the specified target profile. Only contract writers are authorized to perform this action.',
2229
+ 'Updates the read status of a contract request to "seen" for the specified target profile. Targets can mark their existing requests seen. Legacy writer behavior is retained for legacy requests.',
2167
2230
  },
2168
2231
  })
2169
2232
  .input(
@@ -2190,6 +2253,31 @@ export const contractsRouter = t.router({
2190
2253
  if (!contractByUri)
2191
2254
  throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
2192
2255
 
2256
+ const request = await getStoredContractRequest(
2257
+ contractByUri.id,
2258
+ resolvedTargetProfileId
2259
+ );
2260
+ if (request?.requestId) {
2261
+ assertGenericRequestWriteScope(ctx.user.scope);
2262
+ if (profile.profileId !== resolvedTargetProfileId)
2263
+ throw new TRPCError({
2264
+ code: 'UNAUTHORIZED',
2265
+ message: 'Only the target can mark this request seen',
2266
+ });
2267
+ return markGenericContractRequestSeen(contractByUri.id, resolvedTargetProfileId);
2268
+ }
2269
+ if (profile.profileId === resolvedTargetProfileId) {
2270
+ if (!request)
2271
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Request not found' });
2272
+ await upsertRequestedForRelationship(
2273
+ contractByUri.id,
2274
+ resolvedTargetProfileId,
2275
+ undefined,
2276
+ 'seen'
2277
+ );
2278
+ return true;
2279
+ }
2280
+
2193
2281
  const writers = await getWritersForContract(contractByUri);
2194
2282
  const isAuthorized = writers.some(w => w.profileId === profile.profileId);
2195
2283
 
@@ -2226,7 +2314,7 @@ export const contractsRouter = t.router({
2226
2314
  tags: ['Contracts'],
2227
2315
  summary: 'Cancels/removes a contract request',
2228
2316
  description:
2229
- 'Removes a REQUESTED_FOR relationship, cancelling the request sent to the specified target profile. Only contract writers are authorized to perform this action.',
2317
+ 'Cancels a pending generic request while retaining its history. Owner/writers, the requesting recipient, or the target may cancel. Legacy requests retain deletion behavior.',
2230
2318
  },
2231
2319
  })
2232
2320
  .input(
@@ -2253,6 +2341,21 @@ export const contractsRouter = t.router({
2253
2341
  if (!contractByUri)
2254
2342
  throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
2255
2343
 
2344
+ const request = await getStoredContractRequest(
2345
+ contractByUri.id,
2346
+ resolvedTargetProfileId
2347
+ );
2348
+ if (request?.requestId) {
2349
+ assertGenericRequestWriteScope(ctx.user.scope);
2350
+ return decideGenericContractRequest({
2351
+ contract: contractByUri,
2352
+ targetProfileId: resolvedTargetProfileId,
2353
+ actorProfileId: profile.profileId,
2354
+ status: 'cancelled',
2355
+ domain: ctx.domain,
2356
+ });
2357
+ }
2358
+
2256
2359
  const isDenyingRequest = profile.profileId === resolvedTargetProfileId;
2257
2360
 
2258
2361
  if (!isDenyingRequest) {
@@ -2311,10 +2414,15 @@ export const contractsRouter = t.router({
2311
2414
  )
2312
2415
  .output(
2313
2416
  z.array(
2314
- z.object({
2315
- contract: ConsentFlowContractValidator.extend({ uri: z.string() }),
2417
+ ContractRequestFieldsValidator.extend({
2418
+ contract: ConsentFlowContractValidator.extend({
2419
+ uri: z.string(),
2420
+ name: z.string().optional(),
2421
+ image: z.string().optional(),
2422
+ description: z.string().optional(),
2423
+ }),
2316
2424
  profile: LCNProfileValidator,
2317
- status: z.enum(['pending', 'accepted', 'denied']).nullable(),
2425
+ status: z.enum(['pending', 'accepted', 'denied', 'cancelled']).nullable(),
2318
2426
  readStatus: z.enum(['unseen', 'seen']).nullable().optional(),
2319
2427
  })
2320
2428
  )
@@ -2354,7 +2462,10 @@ export const contractsRouter = t.router({
2354
2462
  sanitizeProfileForTier(updatedProfile, 'authenticated')
2355
2463
  ),
2356
2464
  contract: {
2357
- ...request.contract,
2465
+ ...inflateObject(request.contract).contract,
2466
+ name: request.contract.name,
2467
+ image: request.contract.image,
2468
+ description: request.contract.description,
2358
2469
  uri: constructUri('contract', request.contract.id, ctx.domain),
2359
2470
  },
2360
2471
  };
@@ -2381,7 +2492,7 @@ export const contractsRouter = t.router({
2381
2492
  )
2382
2493
  .output(
2383
2494
  z.array(
2384
- z.object({
2495
+ ContractRequestFieldsValidator.extend({
2385
2496
  profile: LCNProfileValidator,
2386
2497
  status: z.enum(['pending', 'accepted', 'denied']).nullable(),
2387
2498
  readStatus: z.enum(['unseen', 'seen']).nullable().optional(),