@learncard/network-brain-service 3.19.0 → 3.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/.env.example +34 -2
  2. package/CHANGELOG.md +38 -0
  3. package/README.md +42 -0
  4. package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
  5. package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
  6. package/dist/accesslayer/presentation/read.d.ts.map +1 -1
  7. package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
  8. package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
  9. package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
  10. package/dist/accesslayer/share-link/abandon.d.ts +3 -0
  11. package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
  12. package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
  13. package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
  14. package/dist/accesslayer/share-link/errors.d.ts +8 -0
  15. package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
  16. package/dist/accesslayer/share-link/finalize.d.ts +3 -0
  17. package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
  18. package/dist/accesslayer/share-link/helpers.d.ts +56 -0
  19. package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
  20. package/dist/accesslayer/share-link/index.d.ts +12 -0
  21. package/dist/accesslayer/share-link/index.d.ts.map +1 -0
  22. package/dist/accesslayer/share-link/list.d.ts +5 -0
  23. package/dist/accesslayer/share-link/list.d.ts.map +1 -0
  24. package/dist/accesslayer/share-link/read.d.ts +5 -0
  25. package/dist/accesslayer/share-link/read.d.ts.map +1 -0
  26. package/dist/accesslayer/share-link/receipt.d.ts +11 -0
  27. package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
  28. package/dist/accesslayer/share-link/recovery.d.ts +6 -0
  29. package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
  30. package/dist/accesslayer/share-link/reserve.d.ts +4 -0
  31. package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
  32. package/dist/accesslayer/share-link/revoke.d.ts +3 -0
  33. package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
  34. package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
  35. package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
  36. package/dist/accesslayer/share-link/transaction.d.ts +15 -0
  37. package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
  38. package/dist/accesslayer/share-link/types.d.ts +370 -0
  39. package/dist/accesslayer/share-link/types.d.ts.map +1 -0
  40. package/dist/app.d.ts +4 -0
  41. package/dist/app.d.ts.map +1 -1
  42. package/dist/config/environment.d.ts +9 -0
  43. package/dist/config/environment.d.ts.map +1 -1
  44. package/dist/constants/auth-grant.d.ts +2 -0
  45. package/dist/constants/auth-grant.d.ts.map +1 -1
  46. package/dist/helpers/boost.helpers.d.ts.map +1 -1
  47. package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
  48. package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
  49. package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
  50. package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
  51. package/dist/helpers/inbox.helpers.d.ts.map +1 -1
  52. package/dist/helpers/notificationMessages.d.ts +1 -1
  53. package/dist/helpers/notificationMessages.d.ts.map +1 -1
  54. package/dist/helpers/presentation-metadata.d.ts +4 -0
  55. package/dist/helpers/presentation-metadata.d.ts.map +1 -0
  56. package/dist/helpers/presentation.helpers.d.ts +1 -1
  57. package/dist/helpers/presentation.helpers.d.ts.map +1 -1
  58. package/dist/helpers/profile.helpers.d.ts +1 -0
  59. package/dist/helpers/profile.helpers.d.ts.map +1 -1
  60. package/dist/helpers/share-content-client/adapters.d.ts +10 -0
  61. package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
  62. package/dist/helpers/share-content-client/canonical.d.ts +7 -0
  63. package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
  64. package/dist/helpers/share-content-client/client.d.ts +5 -0
  65. package/dist/helpers/share-content-client/client.d.ts.map +1 -0
  66. package/dist/helpers/share-content-client/config.d.ts +11 -0
  67. package/dist/helpers/share-content-client/config.d.ts.map +1 -0
  68. package/dist/helpers/share-content-client/index.d.ts +6 -0
  69. package/dist/helpers/share-content-client/index.d.ts.map +1 -0
  70. package/dist/helpers/share-content-client/responses.d.ts +16 -0
  71. package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
  72. package/dist/helpers/share-content-client/types.d.ts +145 -0
  73. package/dist/helpers/share-content-client/types.d.ts.map +1 -0
  74. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
  75. package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
  76. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
  77. package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
  78. package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
  79. package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
  80. package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
  81. package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
  82. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
  83. package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
  84. package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
  85. package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
  86. package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
  87. package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
  88. package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
  89. package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
  90. package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
  91. package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
  92. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
  93. package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
  94. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
  95. package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
  96. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
  97. package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
  98. package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
  99. package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
  100. package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
  101. package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
  102. package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
  103. package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
  104. package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
  105. package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
  106. package/dist/helpers/share-link-maintenance/runner.d.ts +19 -0
  107. package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
  108. package/dist/helpers/share-link-maintenance/runtime.d.ts +36 -0
  109. package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
  110. package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
  111. package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
  112. package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
  113. package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
  114. package/dist/helpers/share-link-maintenance/types.d.ts +53 -0
  115. package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
  116. package/dist/helpers/share-link-owner/config.d.ts +16 -0
  117. package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
  118. package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
  119. package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
  120. package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
  121. package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
  122. package/dist/helpers/share-link-owner-projection.d.ts +4 -0
  123. package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
  124. package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
  125. package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
  126. package/dist/helpers/share-link-passcode.d.ts +6 -0
  127. package/dist/helpers/share-link-passcode.d.ts.map +1 -0
  128. package/dist/helpers/share-link-policy/index.d.ts +4 -0
  129. package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
  130. package/dist/helpers/share-link-policy/production.d.ts +13 -0
  131. package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
  132. package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
  133. package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
  134. package/dist/helpers/share-link-policy/types.d.ts +16 -0
  135. package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
  136. package/dist/helpers/share-link-public/config.d.ts +13 -0
  137. package/dist/helpers/share-link-public/config.d.ts.map +1 -0
  138. package/dist/helpers/share-link-view-notification.d.ts +2 -0
  139. package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
  140. package/dist/index.js +2 -2
  141. package/dist/models/Presentation.d.ts +2 -0
  142. package/dist/models/Presentation.d.ts.map +1 -1
  143. package/dist/models/Profile.d.ts +2 -0
  144. package/dist/models/Profile.d.ts.map +1 -1
  145. package/dist/models/ShareLink.d.ts +44 -0
  146. package/dist/models/ShareLink.d.ts.map +1 -0
  147. package/dist/models/ShareViewReceipt.d.ts +22 -0
  148. package/dist/models/ShareViewReceipt.d.ts.map +1 -0
  149. package/dist/models/index.d.ts +1 -0
  150. package/dist/models/index.d.ts.map +1 -1
  151. package/dist/models/share-link-constraints.d.ts +3 -0
  152. package/dist/models/share-link-constraints.d.ts.map +1 -0
  153. package/dist/public-share-link-http.d.ts +4 -0
  154. package/dist/public-share-link-http.d.ts.map +1 -0
  155. package/dist/routes/app-store.d.ts.map +1 -1
  156. package/dist/routes/boosts.d.ts.map +1 -1
  157. package/dist/routes/contracts.d.ts +2 -2
  158. package/dist/routes/contracts.d.ts.map +1 -1
  159. package/dist/routes/credentials.d.ts +1 -1
  160. package/dist/routes/index.d.ts +153 -0
  161. package/dist/routes/index.d.ts.map +1 -1
  162. package/dist/routes/presentations.d.ts +1 -0
  163. package/dist/routes/presentations.d.ts.map +1 -1
  164. package/dist/routes/profile-manager.d.ts.map +1 -1
  165. package/dist/routes/profiles.d.ts.map +1 -1
  166. package/dist/routes/public-share-links.d.ts +230 -0
  167. package/dist/routes/public-share-links.d.ts.map +1 -0
  168. package/dist/routes/share-links.d.ts +844 -0
  169. package/dist/routes/share-links.d.ts.map +1 -0
  170. package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
  171. package/dist/types/consentflowcontract.d.ts +1 -1
  172. package/lambda.ts +4 -1
  173. package/package.json +22 -16
  174. package/scripts/seed-demo-persona.ts +425 -0
  175. package/serverless.yml +36 -0
  176. package/shareLinkMaintenanceLambda.ts +49 -0
  177. package/src/accesslayer/consentflowcontract/relationships/create.ts +9 -16
  178. package/src/accesslayer/consentflowcontract/relationships/update.ts +15 -28
  179. package/src/accesslayer/presentation/read.ts +57 -25
  180. package/src/accesslayer/presentation/relationships/create.ts +54 -11
  181. package/src/accesslayer/presentation/relationships/read.ts +21 -6
  182. package/src/accesslayer/share-link/abandon.ts +100 -0
  183. package/src/accesslayer/share-link/cleanup.ts +271 -0
  184. package/src/accesslayer/share-link/errors.ts +32 -0
  185. package/src/accesslayer/share-link/finalize.ts +265 -0
  186. package/src/accesslayer/share-link/helpers.ts +419 -0
  187. package/src/accesslayer/share-link/index.ts +11 -0
  188. package/src/accesslayer/share-link/list.ts +162 -0
  189. package/src/accesslayer/share-link/read.ts +107 -0
  190. package/src/accesslayer/share-link/receipt.ts +548 -0
  191. package/src/accesslayer/share-link/recovery.ts +545 -0
  192. package/src/accesslayer/share-link/reserve.ts +684 -0
  193. package/src/accesslayer/share-link/revoke.ts +197 -0
  194. package/src/accesslayer/share-link/transaction-execution.ts +72 -0
  195. package/src/accesslayer/share-link/transaction.ts +126 -0
  196. package/src/accesslayer/share-link/types.ts +601 -0
  197. package/src/app.ts +6 -0
  198. package/src/config/environment.ts +14 -0
  199. package/src/constants/auth-grant.ts +5 -0
  200. package/src/docker-entry.ts +57 -0
  201. package/src/helpers/boost.helpers.ts +4 -31
  202. package/src/helpers/credentialSubject.helpers.test.ts +114 -0
  203. package/src/helpers/credentialSubject.helpers.ts +40 -0
  204. package/src/helpers/inbox.helpers.ts +7 -4
  205. package/src/helpers/notificationMessages.ts +18 -1
  206. package/src/helpers/presentation-metadata.ts +41 -0
  207. package/src/helpers/presentation.helpers.ts +27 -20
  208. package/src/helpers/profile.helpers.ts +7 -0
  209. package/src/helpers/share-content-client/adapters.ts +55 -0
  210. package/src/helpers/share-content-client/canonical.ts +25 -0
  211. package/src/helpers/share-content-client/client.ts +440 -0
  212. package/src/helpers/share-content-client/config.ts +189 -0
  213. package/src/helpers/share-content-client/index.ts +27 -0
  214. package/src/helpers/share-content-client/responses.ts +273 -0
  215. package/src/helpers/share-content-client/types.ts +253 -0
  216. package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
  217. package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
  218. package/src/helpers/share-link-coordinator/coordinator.ts +714 -0
  219. package/src/helpers/share-link-coordinator/index.ts +8 -0
  220. package/src/helpers/share-link-coordinator/recovery-runner.ts +428 -0
  221. package/src/helpers/share-link-coordinator/runtime.ts +88 -0
  222. package/src/helpers/share-link-coordinator/stat.ts +30 -0
  223. package/src/helpers/share-link-coordinator/types.ts +353 -0
  224. package/src/helpers/share-link-lifecycle/index.ts +4 -0
  225. package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
  226. package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
  227. package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
  228. package/src/helpers/share-link-lifecycle/types.ts +58 -0
  229. package/src/helpers/share-link-maintenance/budget.ts +63 -0
  230. package/src/helpers/share-link-maintenance/config.ts +312 -0
  231. package/src/helpers/share-link-maintenance/index.ts +24 -0
  232. package/src/helpers/share-link-maintenance/runner.ts +223 -0
  233. package/src/helpers/share-link-maintenance/runtime.ts +334 -0
  234. package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
  235. package/src/helpers/share-link-maintenance/telemetry.ts +70 -0
  236. package/src/helpers/share-link-maintenance/types.ts +89 -0
  237. package/src/helpers/share-link-owner/config.ts +48 -0
  238. package/src/helpers/share-link-owner/diagnostics.ts +89 -0
  239. package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
  240. package/src/helpers/share-link-owner-projection.ts +52 -0
  241. package/src/helpers/share-link-passcode-abuse.ts +36 -0
  242. package/src/helpers/share-link-passcode.ts +47 -0
  243. package/src/helpers/share-link-policy/index.ts +7 -0
  244. package/src/helpers/share-link-policy/production.ts +88 -0
  245. package/src/helpers/share-link-policy/resolver.ts +108 -0
  246. package/src/helpers/share-link-policy/types.ts +48 -0
  247. package/src/helpers/share-link-public/config.ts +41 -0
  248. package/src/helpers/share-link-view-notification.ts +18 -0
  249. package/src/models/Presentation.ts +2 -2
  250. package/src/models/Profile.ts +3 -2
  251. package/src/models/ShareLink.ts +130 -0
  252. package/src/models/ShareViewReceipt.ts +69 -0
  253. package/src/models/index.ts +2 -0
  254. package/src/models/share-link-constraints.ts +93 -0
  255. package/src/public-share-link-http.ts +49 -0
  256. package/src/routes/app-store.ts +2 -11
  257. package/src/routes/boosts.ts +2 -11
  258. package/src/routes/contracts.ts +2 -11
  259. package/src/routes/index.ts +139 -44
  260. package/src/routes/presentations.ts +19 -3
  261. package/src/routes/profile-manager.ts +10 -2
  262. package/src/routes/profiles.ts +17 -6
  263. package/src/routes/public-share-links.ts +814 -0
  264. package/src/routes/share-links.ts +640 -0
  265. package/src/services/delivery/adapters/postmark.adapter.ts +4 -1
  266. package/test/dids.spec.ts +10 -1
  267. package/test/guardian-gated-routes.spec.ts +13 -0
  268. package/test/helpers/share-link-fixtures.ts +94 -0
  269. package/test/managed-recipient-inbox.spec.ts +130 -0
  270. package/test/notificationMessages.spec.ts +13 -0
  271. package/test/openapi.spec.ts +20 -0
  272. package/test/presentations.spec.ts +54 -6
  273. package/test/profiles.spec.ts +67 -1
  274. package/test/share-content-client.contract.spec.ts +365 -0
  275. package/test/share-content-client.unit.spec.ts +462 -0
  276. package/test/share-link-abuse.unit.spec.ts +65 -0
  277. package/test/share-link-coordinator.neo4j.spec.ts +684 -0
  278. package/test/share-link-coordinator.unit.spec.ts +990 -0
  279. package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
  280. package/test/share-link-lifecycle.neo4j.spec.ts +1268 -0
  281. package/test/share-link-lifecycle.unit.spec.ts +205 -0
  282. package/test/share-link-list-cursor.unit.spec.ts +86 -0
  283. package/test/share-link-list.neo4j.spec.ts +324 -0
  284. package/test/share-link-maintenance.neo4j.spec.ts +355 -0
  285. package/test/share-link-maintenance.unit.spec.ts +1113 -0
  286. package/test/share-link-owner-policy.unit.spec.ts +210 -0
  287. package/test/share-link-owner-route.unit.spec.ts +752 -0
  288. package/test/share-link-owner-runtime.unit.spec.ts +102 -0
  289. package/test/share-link-passcode.unit.spec.ts +29 -0
  290. package/test/share-link-policy.unit.spec.ts +135 -0
  291. package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
  292. package/test/share-link-production-policy.unit.spec.ts +77 -0
  293. package/test/share-link-public-config.unit.spec.ts +64 -0
  294. package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
  295. package/test/share-link-public-route.unit.spec.ts +800 -0
  296. package/test/share-link-receipt.neo4j.spec.ts +943 -0
  297. package/test/share-link-recovery.neo4j.spec.ts +935 -0
  298. package/test/share-link-recovery.unit.spec.ts +726 -0
  299. package/vitest.config.ts +1 -0
  300. package/vitest.share-links-neo4j.config.ts +19 -0
  301. package/vitest.share-links-unit.config.ts +28 -0
@@ -0,0 +1,640 @@
1
+ import { TRPCError } from '@trpc/server';
2
+
3
+ import {
4
+ CreateShareLinkInputValidator,
5
+ ListShareLinksInputValidator,
6
+ PaginatedShareLinksValidator,
7
+ ShareLinkOwnerCommitOutputValidator,
8
+ ShareLinkOwnerContentOutputValidator,
9
+ ShareLinkOwnerRecoveryOutputValidator,
10
+ ShareLinkOwnerStatusOutputValidator,
11
+ ShareLinkOperationKeyInputValidator,
12
+ ShareOwnerRecoveryValidator,
13
+ UpdateShareLinkInputValidator,
14
+ } from '@learncard/types';
15
+
16
+ import { t, profileRouteWithoutInputCapture } from '@routes';
17
+ import {
18
+ AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
19
+ AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
20
+ } from 'src/constants/auth-grant';
21
+ import { enforceRateLimits } from '@helpers/rateLimit.helpers';
22
+ import { toOwnerShareLink } from '@helpers/share-link-owner-projection';
23
+ import { createRetryableLazyInitializer } from '@helpers/share-link-owner/lazy-initializer';
24
+ import { resolveShareLinkOwnerApiConfig } from '@helpers/share-link-owner/config';
25
+ import type { ShareLinkOwnerApiConfigResolution } from '@helpers/share-link-owner/config';
26
+ import {
27
+ createBoundedShareLinkDiagnosticReporter,
28
+ createShareLinkDependencyResolver,
29
+ SHARE_LINK_COMPOSITION_DIAGNOSTIC,
30
+ } from '@helpers/share-link-owner/diagnostics';
31
+ import { createShareLinkPolicyResolver } from '@helpers/share-link-policy/resolver';
32
+ import { ShareLinkCoordinatorError } from '@helpers/share-link-coordinator';
33
+ import { getShareLinkRequestHashSecret } from '@helpers/share-link-lifecycle';
34
+ import type {
35
+ RecoveryRunnerDependencies,
36
+ ShareLinkCoordinator,
37
+ ShareLinkCoordinatorErrorCode,
38
+ } from '@helpers/share-link-coordinator';
39
+ import { decodeShareLinkListCursor } from '@accesslayer/share-link/list';
40
+ import type {
41
+ ListShareLinksInput,
42
+ ListShareLinksResult,
43
+ ShareLinkListCursor,
44
+ } from '@accesslayer/share-link/types';
45
+
46
+ /**
47
+ * LC-2187 owner share-link APIs (D1).
48
+ *
49
+ * Disabled by default and fail-closed: the namespace is taken exclusively from
50
+ * trusted server configuration (never from the caller, tenant header or Host),
51
+ * the owner exclusively from the authenticated profile, and the default policy
52
+ * exclusively from server-side sources. There are no public resolve/content/ack
53
+ * routes in D1.
54
+ */
55
+
56
+ export type ShareLinkRouterDependencies = {
57
+ readonly namespace: string;
58
+ readonly coordinator: ShareLinkCoordinator;
59
+ readonly recovery: RecoveryRunnerDependencies;
60
+ /**
61
+ * Bounded, namespace+owner-scoped keyset listing over the immutable
62
+ * `(createdAt, id)` key. Receives only trusted scope; the route decodes and
63
+ * validates any caller cursor first.
64
+ */
65
+ readonly listShareLinks: (input: ListShareLinksInput) => Promise<ListShareLinksResult>;
66
+ /** Per-owner create/update/revoke/retry abuse limit. */
67
+ readonly enforceOwnerWriteRateLimit: (ownerProfileId: string) => Promise<void>;
68
+ /**
69
+ * Freshly re-resolve authoritative view-counting eligibility for the owner.
70
+ * Used only to decide whether `viewCount`/`lastViewedAt` may appear in the
71
+ * owner projection; a resolver failure fails closed to `false`.
72
+ */
73
+ readonly resolveViewCountingEligibility: (ownerProfileId: string) => Promise<boolean>;
74
+ };
75
+
76
+ /** Eligibility for projection: an unavailable resolver never leaks a count. */
77
+ const safeEligibility = async (
78
+ dependencies: ShareLinkRouterDependencies,
79
+ ownerProfileId: string
80
+ ): Promise<boolean> => {
81
+ try {
82
+ return (await dependencies.resolveViewCountingEligibility(ownerProfileId)) === true;
83
+ } catch {
84
+ return false;
85
+ }
86
+ };
87
+
88
+ function notFound(): never {
89
+ throw new TRPCError({ code: 'NOT_FOUND' });
90
+ }
91
+
92
+ /**
93
+ * Fixed, sanitized tRPC mapping for coordinator/repository failures.
94
+ *
95
+ * Raw messages, stack traces and `cause` chains from the graph/remote layer
96
+ * must never cross the route boundary or reach telemetry. Every documented
97
+ * coordinator code maps to one stable tRPC code and one safe message.
98
+ */
99
+ const mapCoordinatorError = (code: ShareLinkCoordinatorErrorCode): TRPCError => {
100
+ switch (code) {
101
+ case 'INVALID_INPUT':
102
+ return new TRPCError({ code: 'BAD_REQUEST', message: 'invalid share-link request' });
103
+ case 'NOT_FOUND':
104
+ return new TRPCError({ code: 'NOT_FOUND', message: 'share-link not found' });
105
+ case 'CONFLICT':
106
+ case 'OPERATION_IN_FLIGHT':
107
+ case 'LEASE_EXPIRED':
108
+ case 'STALE_GENERATION':
109
+ return new TRPCError({ code: 'CONFLICT', message: 'share-link state conflict' });
110
+ case 'PRECONDITION_FAILED':
111
+ return new TRPCError({
112
+ code: 'PRECONDITION_FAILED',
113
+ message: 'share-link precondition failed',
114
+ });
115
+ case 'UNAUTHORIZED':
116
+ return new TRPCError({
117
+ code: 'UNAUTHORIZED',
118
+ message: 'share-link operation is not authorized',
119
+ });
120
+ case 'PAYLOAD_TOO_LARGE':
121
+ return new TRPCError({
122
+ code: 'PAYLOAD_TOO_LARGE',
123
+ message: 'share-link payload is too large',
124
+ });
125
+ case 'UNAVAILABLE':
126
+ return new TRPCError({
127
+ code: 'SERVICE_UNAVAILABLE',
128
+ message: 'share-link service is unavailable',
129
+ });
130
+ case 'SIGNING_FAILED':
131
+ case 'UNEXPECTED':
132
+ return new TRPCError({
133
+ code: 'INTERNAL_SERVER_ERROR',
134
+ message: 'share-link operation failed',
135
+ });
136
+ }
137
+ };
138
+
139
+ /** Run a coordinator/recovery call, converting only its typed failures. */
140
+ const runCoordinated = async <T>(operation: () => Promise<T>): Promise<T> => {
141
+ try {
142
+ return await operation();
143
+ } catch (error) {
144
+ if (error instanceof TRPCError) throw error;
145
+ if (error instanceof ShareLinkCoordinatorError) throw mapCoordinatorError(error.code);
146
+
147
+ throw new TRPCError({
148
+ code: 'INTERNAL_SERVER_ERROR',
149
+ message: 'share-link operation failed',
150
+ });
151
+ }
152
+ };
153
+
154
+ /**
155
+ * Complete-request byte bounding lives in the route base
156
+ * (`openRouteWithoutInputCapture`) so it runs before authentication and before
157
+ * Zod parsing; see `@routes`.
158
+ */
159
+
160
+ const openapi = (method: 'GET' | 'POST', path: `/${string}`, summary: string) => ({
161
+ protect: true as const,
162
+ method,
163
+ path,
164
+ tags: ['ShareLinks'],
165
+ summary,
166
+ });
167
+
168
+ const projectCommit = (
169
+ result:
170
+ | { status: 'committed' | 'replayed'; share: Parameters<typeof toOwnerShareLink>[0] }
171
+ | { status: 'pending'; reservation: { operationId: string } },
172
+ id: string,
173
+ viewCountingEnabled: boolean
174
+ ) =>
175
+ result.status === 'pending'
176
+ ? { status: 'pending' as const, id, operationId: result.reservation.operationId }
177
+ : {
178
+ status: 'completed' as const,
179
+ share: toOwnerShareLink(result.share, viewCountingEnabled),
180
+ };
181
+
182
+ /**
183
+ * Build the owner router over injected dependencies. Route-boundary tests pass
184
+ * fakes; production passes the lazily-built runtime.
185
+ */
186
+ export const createShareLinksRouter = (
187
+ getDependencies: () => Promise<ShareLinkRouterDependencies | null>
188
+ ) => {
189
+ const resolve = async (): Promise<ShareLinkRouterDependencies> => {
190
+ let dependencies: ShareLinkRouterDependencies | null;
191
+
192
+ try {
193
+ dependencies = await getDependencies();
194
+ } catch {
195
+ // Initialization failed transiently. Never surface the raw failure;
196
+ // the next request retries initialization.
197
+ throw new TRPCError({
198
+ code: 'INTERNAL_SERVER_ERROR',
199
+ message: 'share-link service is unavailable',
200
+ });
201
+ }
202
+
203
+ if (!dependencies) notFound();
204
+
205
+ return dependencies;
206
+ };
207
+
208
+ return t.router({
209
+ create: profileRouteWithoutInputCapture
210
+ .meta({
211
+ openapi: openapi('POST', '/share-links/create', 'Create an owner share link'),
212
+ requiredScope: AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
213
+ })
214
+ .input(CreateShareLinkInputValidator)
215
+ .output(ShareLinkOwnerCommitOutputValidator)
216
+ .mutation(async ({ ctx, input }) => {
217
+ const dependencies = await resolve();
218
+ await dependencies.enforceOwnerWriteRateLimit(ctx.user.profile.profileId);
219
+
220
+ const result = await runCoordinated(() =>
221
+ dependencies.coordinator.createShareLink(input, {
222
+ namespace: dependencies.namespace,
223
+ ownerProfileId: ctx.user.profile.profileId,
224
+ })
225
+ );
226
+
227
+ return projectCommit(
228
+ result,
229
+ input.id,
230
+ await safeEligibility(dependencies, ctx.user.profile.profileId)
231
+ );
232
+ }),
233
+
234
+ update: profileRouteWithoutInputCapture
235
+ .meta({
236
+ openapi: openapi('POST', '/share-links/update', 'Update an owner share link'),
237
+ requiredScope: AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
238
+ })
239
+ .input(UpdateShareLinkInputValidator)
240
+ .output(ShareLinkOwnerCommitOutputValidator)
241
+ .mutation(async ({ ctx, input }) => {
242
+ const dependencies = await resolve();
243
+ await dependencies.enforceOwnerWriteRateLimit(ctx.user.profile.profileId);
244
+
245
+ const result = await runCoordinated(() =>
246
+ dependencies.coordinator.updateShareLink(input, {
247
+ namespace: dependencies.namespace,
248
+ ownerProfileId: ctx.user.profile.profileId,
249
+ })
250
+ );
251
+
252
+ return projectCommit(
253
+ result,
254
+ input.id,
255
+ await safeEligibility(dependencies, ctx.user.profile.profileId)
256
+ );
257
+ }),
258
+
259
+ revoke: profileRouteWithoutInputCapture
260
+ .meta({
261
+ openapi: openapi('POST', '/share-links/revoke', 'Revoke an owner share link'),
262
+ requiredScope: AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
263
+ })
264
+ .input(
265
+ ShareLinkOperationKeyInputValidator.pick({ id: true }).extend({
266
+ expectedVersion: UpdateShareLinkInputValidator.shape.expectedVersion.optional(),
267
+ clientRequestId: CreateShareLinkInputValidator.shape.clientRequestId.optional(),
268
+ })
269
+ )
270
+ .output(ShareLinkOwnerCommitOutputValidator)
271
+ .mutation(async ({ ctx, input }) => {
272
+ const dependencies = await resolve();
273
+ await dependencies.enforceOwnerWriteRateLimit(ctx.user.profile.profileId);
274
+
275
+ const result = await runCoordinated(() =>
276
+ dependencies.coordinator.revokeShareLink(input, {
277
+ namespace: dependencies.namespace,
278
+ ownerProfileId: ctx.user.profile.profileId,
279
+ })
280
+ );
281
+
282
+ // `completed` is the outer terminal tag; the authoritative state
283
+ // (here `stopped`) lives on `share.status`.
284
+ return {
285
+ status: 'completed' as const,
286
+ share: toOwnerShareLink(
287
+ result.share,
288
+ await safeEligibility(dependencies, ctx.user.profile.profileId)
289
+ ),
290
+ };
291
+ }),
292
+
293
+ list: profileRouteWithoutInputCapture
294
+ .meta({
295
+ // A collection path with no trailing segment, so it can never be
296
+ // shadowed by (or shadow) the `GET /share-links/{id}` owner route
297
+ // or any public `/public/share-links/...` route.
298
+ openapi: openapi('GET', '/share-links', 'List owner share links'),
299
+ requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
300
+ })
301
+ .input(ListShareLinksInputValidator)
302
+ .output(PaginatedShareLinksValidator)
303
+ .query(async ({ ctx, input }) => {
304
+ const dependencies = await resolve();
305
+
306
+ let cursor: ShareLinkListCursor | null = null;
307
+ if (input.cursor !== undefined) {
308
+ cursor = decodeShareLinkListCursor(input.cursor);
309
+ // Malformed/oversized/impossible/foreign-version cursors fail
310
+ // closed with one fixed message and never reach the graph.
311
+ if (cursor === null) {
312
+ throw new TRPCError({
313
+ code: 'BAD_REQUEST',
314
+ message: 'invalid share-link cursor',
315
+ });
316
+ }
317
+ }
318
+
319
+ let page: ListShareLinksResult;
320
+ try {
321
+ page = await dependencies.listShareLinks({
322
+ namespace: dependencies.namespace,
323
+ ownerProfileId: ctx.user.profile.profileId,
324
+ limit: input.limit,
325
+ cursor,
326
+ });
327
+ } catch {
328
+ // Fixed safe mapping: never surface a raw repository message.
329
+ throw new TRPCError({
330
+ code: 'INTERNAL_SERVER_ERROR',
331
+ message: 'share-link list is unavailable',
332
+ });
333
+ }
334
+
335
+ // Eligibility is re-resolved exactly once per request; a failure
336
+ // or non-`true` answer suppresses viewCount/lastViewedAt for
337
+ // every record (fail-closed).
338
+ const viewCountingEnabled = await safeEligibility(
339
+ dependencies,
340
+ ctx.user.profile.profileId
341
+ );
342
+
343
+ return {
344
+ records: page.records.map(record =>
345
+ toOwnerShareLink(record, viewCountingEnabled)
346
+ ),
347
+ hasMore: page.hasMore,
348
+ ...(page.nextCursor === null ? {} : { cursor: page.nextCursor }),
349
+ };
350
+ }),
351
+
352
+ get: profileRouteWithoutInputCapture
353
+ .meta({
354
+ openapi: openapi('GET', '/share-links/{id}', 'Get owner share-link metadata'),
355
+ requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
356
+ })
357
+ .input(ShareLinkOperationKeyInputValidator.pick({ id: true }))
358
+ .output(ShareLinkOwnerStatusOutputValidator)
359
+ .query(async ({ ctx, input }) => {
360
+ const dependencies = await resolve();
361
+ const share = await runCoordinated(() =>
362
+ dependencies.coordinator.getShareLink(input.id, {
363
+ namespace: dependencies.namespace,
364
+ ownerProfileId: ctx.user.profile.profileId,
365
+ })
366
+ );
367
+
368
+ return share === null
369
+ ? { status: 'not_found' as const, id: input.id }
370
+ : {
371
+ status: 'found' as const,
372
+ share: toOwnerShareLink(
373
+ share,
374
+ await safeEligibility(dependencies, ctx.user.profile.profileId)
375
+ ),
376
+ };
377
+ }),
378
+
379
+ getOperationStatus: profileRouteWithoutInputCapture
380
+ .meta({
381
+ openapi: openapi(
382
+ 'GET',
383
+ '/share-links/operations/{operationId}',
384
+ 'Get owner scoped operation status'
385
+ ),
386
+ requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
387
+ })
388
+ .input(ShareLinkOperationKeyInputValidator)
389
+ .output(ShareLinkOwnerStatusOutputValidator)
390
+ .query(async ({ ctx, input }) => {
391
+ const dependencies = await resolve();
392
+ const target = await runCoordinated(() =>
393
+ dependencies.recovery.repository.readShareLinkRecoveryTarget({
394
+ namespace: dependencies.namespace,
395
+ ownerProfileId: ctx.user.profile.profileId,
396
+ shareId: input.id,
397
+ operationId: input.operationId,
398
+ })
399
+ );
400
+
401
+ if (target.state === 'committed') {
402
+ return {
403
+ status: 'found' as const,
404
+ share: toOwnerShareLink(
405
+ target.share,
406
+ await safeEligibility(dependencies, ctx.user.profile.profileId)
407
+ ),
408
+ };
409
+ }
410
+ if (target.state === 'reservation') {
411
+ return {
412
+ status: 'pending' as const,
413
+ id: input.id,
414
+ operationId: input.operationId,
415
+ };
416
+ }
417
+
418
+ return { status: 'not_found' as const, id: input.id };
419
+ }),
420
+
421
+ retry: profileRouteWithoutInputCapture
422
+ .meta({
423
+ openapi: openapi('POST', '/share-links/retry', 'Retry an owner operation'),
424
+ requiredScope: AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE,
425
+ })
426
+ .input(ShareLinkOperationKeyInputValidator)
427
+ .output(ShareLinkOwnerStatusOutputValidator)
428
+ .mutation(async ({ ctx, input }) => {
429
+ const dependencies = await resolve();
430
+ await dependencies.enforceOwnerWriteRateLimit(ctx.user.profile.profileId);
431
+
432
+ // Persisted-key recovery only: the server re-derives the immutable
433
+ // object/lease/generation bindings from durable Brain state. A
434
+ // caller-supplied descriptor is never accepted.
435
+ const result = await runCoordinated(async () => {
436
+ const { recoverShareLinkOperation } =
437
+ await import('@helpers/share-link-coordinator/recovery-runner');
438
+
439
+ return recoverShareLinkOperation(dependencies.recovery, {
440
+ namespace: dependencies.namespace,
441
+ ownerProfileId: ctx.user.profile.profileId,
442
+ shareId: input.id,
443
+ operationId: input.operationId,
444
+ });
445
+ });
446
+
447
+ if (result.status === 'committed') {
448
+ return {
449
+ status: 'found' as const,
450
+ share: toOwnerShareLink(
451
+ result.share,
452
+ await safeEligibility(dependencies, ctx.user.profile.profileId)
453
+ ),
454
+ };
455
+ }
456
+ if (result.status === 'abandoned' || result.status === 'absent') {
457
+ return { status: 'not_found' as const, id: input.id };
458
+ }
459
+
460
+ return {
461
+ status: 'pending' as const,
462
+ id: input.id,
463
+ operationId: input.operationId,
464
+ };
465
+ }),
466
+
467
+ getRecovery: profileRouteWithoutInputCapture
468
+ .meta({
469
+ openapi: openapi(
470
+ 'GET',
471
+ '/share-links/{id}/recovery',
472
+ 'Get owner-encrypted recovery (owner only)'
473
+ ),
474
+ requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
475
+ })
476
+ .input(ShareLinkOperationKeyInputValidator.pick({ id: true }))
477
+ .output(ShareLinkOwnerRecoveryOutputValidator)
478
+ .query(async ({ ctx, input }) => {
479
+ const dependencies = await resolve();
480
+ const result = await runCoordinated(() =>
481
+ dependencies.coordinator.readOwnerRecovery(input.id, {
482
+ namespace: dependencies.namespace,
483
+ ownerProfileId: ctx.user.profile.profileId,
484
+ })
485
+ );
486
+
487
+ if (!result.ok) notFound();
488
+
489
+ const parsed = ShareOwnerRecoveryValidator.safeParse(
490
+ result.value.ownerEncryptedRecovery
491
+ );
492
+ if (!parsed.success) notFound();
493
+
494
+ return { recovery: parsed.data };
495
+ }),
496
+
497
+ getContent: profileRouteWithoutInputCapture
498
+ .meta({
499
+ openapi: openapi(
500
+ 'GET',
501
+ '/share-links/{id}/content',
502
+ 'Get encrypted share content (owner only)'
503
+ ),
504
+ requiredScope: AUTH_GRANT_SHARE_LINKS_READ_SCOPE,
505
+ })
506
+ .input(ShareLinkOperationKeyInputValidator.pick({ id: true }))
507
+ .output(ShareLinkOwnerContentOutputValidator)
508
+ .query(async ({ ctx, input }) => {
509
+ const dependencies = await resolve();
510
+ const result = await runCoordinated(() =>
511
+ dependencies.coordinator.fetchShareContent(
512
+ input.id,
513
+ {
514
+ namespace: dependencies.namespace,
515
+ ownerProfileId: ctx.user.profile.profileId,
516
+ },
517
+ { allowExpired: true }
518
+ )
519
+ );
520
+
521
+ if (!result.ok) notFound();
522
+
523
+ return ShareLinkOwnerContentOutputValidator.parse({
524
+ id: input.id,
525
+ contentVersion: result.value.contentVersion,
526
+ envelope: result.value.envelope,
527
+ });
528
+ }),
529
+ });
530
+ };
531
+
532
+ export type ShareLinksRouter = ReturnType<typeof createShareLinksRouter>;
533
+
534
+ /** Per-hour owner write budget. Fails closed when the cache is unavailable. */
535
+ export const enforceOwnerShareWriteRateLimit = async (
536
+ namespace: string,
537
+ ownerProfileId: string
538
+ ): Promise<void> => {
539
+ await enforceRateLimits([
540
+ {
541
+ key: `share-link-owner-write:${namespace}:${ownerProfileId}`,
542
+ limit: 60,
543
+ windowSeconds: 60 * 60,
544
+ description: 'owner share-link writes',
545
+ },
546
+ ]);
547
+ };
548
+
549
+ /**
550
+ * Lazy production composition. Config is resolved before any signer/graph import.
551
+ * A disabled configuration is inert and silent; malformed wiring emits a bounded
552
+ * sanitized diagnostic and fails closed to NOT_FOUND. A failed build is never
553
+ * cached, so a later request retries with non-null dependencies.
554
+ */
555
+ const initializeProductionDependencies =
556
+ createRetryableLazyInitializer<ShareLinkRouterDependencies>(() =>
557
+ buildProductionDependencies(
558
+ resolveShareLinkOwnerApiConfig(process.env as Record<string, unknown>) as Extract<
559
+ ShareLinkOwnerApiConfigResolution,
560
+ { status: 'enabled' }
561
+ >
562
+ )
563
+ );
564
+
565
+ export const getProductionDependencies =
566
+ createShareLinkDependencyResolver<ShareLinkRouterDependencies>({
567
+ resolveConfig: () => resolveShareLinkOwnerApiConfig(process.env as Record<string, unknown>),
568
+ initializeDependencies: initializeProductionDependencies,
569
+ reportDiagnostic: createBoundedShareLinkDiagnosticReporter(),
570
+ configurationInvalidCategory: SHARE_LINK_COMPOSITION_DIAGNOSTIC.OWNER_CONFIGURATION_INVALID,
571
+ initializationFailedCategory: SHARE_LINK_COMPOSITION_DIAGNOSTIC.OWNER_INITIALIZATION_FAILED,
572
+ });
573
+
574
+ const buildProductionDependencies = async (
575
+ config: Extract<ShareLinkOwnerApiConfigResolution, { status: 'enabled' }>
576
+ ): Promise<ShareLinkRouterDependencies> => {
577
+ // Validate before accepting owner operations, not on the first write.
578
+ getShareLinkRequestHashSecret();
579
+ const [
580
+ { getServerDidWebDID },
581
+ { createDidWebLearnCardTokenSigner },
582
+ clientModule,
583
+ runtimeModule,
584
+ { ensureShareLinkConstraints },
585
+ { createProductionShareLinkPolicySource },
586
+ listRepository,
587
+ ] = await Promise.all([
588
+ import('@helpers/learnCard.helpers'),
589
+ import('@helpers/share-content-client/adapters'),
590
+ import('@helpers/share-content-client'),
591
+ import('@helpers/share-link-coordinator/runtime'),
592
+ import('../models/share-link-constraints'),
593
+ import('@helpers/share-link-policy/production'),
594
+ import('@accesslayer/share-link/list'),
595
+ ]);
596
+
597
+ const clientConfig = clientModule.resolveShareContentClientConfig({
598
+ enabled: true,
599
+ origin: config.origin,
600
+ namespace: config.namespace,
601
+ audience: config.audience,
602
+ signerDid: getServerDidWebDID(),
603
+ allowInsecureLoopback: config.allowInsecureLoopback,
604
+ signer: createDidWebLearnCardTokenSigner(),
605
+ });
606
+
607
+ if (!clientConfig.enabled) {
608
+ // Fail the build so the retryable lazy initializer does not cache it and
609
+ // a later request retries once the wiring is corrected.
610
+ throw new Error('share-content client configuration is invalid');
611
+ }
612
+
613
+ const client = clientModule.createShareContentClient(clientConfig);
614
+ const policyResolver = createShareLinkPolicyResolver(createProductionShareLinkPolicySource());
615
+ const coordinator = runtimeModule.createRuntimeShareLinkCoordinator({
616
+ client,
617
+ leaseOwner: 'brain-share-link-owner-api',
618
+ policyResolver,
619
+ });
620
+ const recovery = runtimeModule.createRuntimeRecoveryRunnerDependencies({
621
+ client,
622
+ claimant: 'brain-share-link-owner-api',
623
+ namespace: config.namespace,
624
+ });
625
+
626
+ await ensureShareLinkConstraints();
627
+
628
+ return {
629
+ namespace: config.namespace,
630
+ coordinator,
631
+ recovery,
632
+ listShareLinks: listRepository.listShareLinks,
633
+ enforceOwnerWriteRateLimit: ownerProfileId =>
634
+ enforceOwnerShareWriteRateLimit(config.namespace, ownerProfileId),
635
+ resolveViewCountingEligibility: async ownerProfileId =>
636
+ (await policyResolver.resolve(ownerProfileId)).viewCountingEnabled,
637
+ };
638
+ };
639
+
640
+ export const shareLinksRouter = createShareLinksRouter(getProductionDependencies);
@@ -178,7 +178,10 @@ export class PostmarkAdapter implements DeliveryService {
178
178
  };
179
179
 
180
180
  case 'recovery-key':
181
- return { recoveryKey: model.recoveryKey };
181
+ return {
182
+ recoveryKey: model.recoveryKey,
183
+ confirmationCode: model.confirmationCode,
184
+ };
182
185
 
183
186
  case 'endorsement-request':
184
187
  return {
package/test/dids.spec.ts CHANGED
@@ -168,7 +168,7 @@ describe('did:web profile document generation with signing authorities', () => {
168
168
  await deleteDidDocForProfile(profileId);
169
169
  });
170
170
 
171
- it('never emits a fragment on a verificationMethod controller', async () => {
171
+ it('renders controller-owned verification and key-agreement methods', async () => {
172
172
  const profile = await getProfileByProfileId(profileId);
173
173
  const sa = await createSigningAuthority(SA_ENDPOINT);
174
174
  await createUseSigningAuthorityRelationship(profile!, sa, 'lca-sa', SA_DID, true);
@@ -195,6 +195,15 @@ describe('did:web profile document generation with signing authorities', () => {
195
195
  );
196
196
  expect(saVm).toBeDefined();
197
197
  expect((saVm as { controller: string }).controller).toBe(did);
198
+
199
+ const ownerKeyAgreement = (doc.keyAgreement ?? []).find(
200
+ method => typeof method === 'object' && method.controller === did
201
+ );
202
+ expect(ownerKeyAgreement).toMatchObject({
203
+ type: 'X25519KeyAgreementKey2019',
204
+ controller: did,
205
+ publicKeyBase58: expect.any(String),
206
+ });
198
207
  });
199
208
 
200
209
  it('de-duplicates repeated signing-authority relationships in the rendered document', async () => {