@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,601 @@
1
+ import type {
2
+ ShareContentCleanupReason,
3
+ ShareContentCleanupStatus,
4
+ ShareLinkOperationKind,
5
+ } from '@helpers/share-link-lifecycle';
6
+ import type { ShareLinkPolicySnapshot } from '@helpers/share-link-policy';
7
+
8
+ import type { ShareContentState, ShareLinkRecord, ShareLinkStatus } from '../../models/ShareLink';
9
+ import type { ShareLinkTransactionOptions } from './transaction';
10
+
11
+ /** Namespace + owner + share id binding used by every lifecycle operation. */
12
+ export type ShareLinkKey = {
13
+ namespace: string;
14
+ ownerProfileId: string;
15
+ shareId: string;
16
+ };
17
+
18
+ export type ShareLinkOperationStatus = 'in_progress' | 'committed' | 'abandoned';
19
+
20
+ /**
21
+ * Idempotency record for one owner request.
22
+ *
23
+ * The key is `(namespace, ownerProfileId, opKind, clientRequestId)` and is enforced
24
+ * by a composite unique constraint. `requestHash` is the canonical hash of the full
25
+ * validated request (including encrypted recovery, excluding the request id); a
26
+ * retry with the same key and hash returns the recorded result, any other payload
27
+ * under the same key is a conflict.
28
+ */
29
+ export type ShareLinkOperationRecord = {
30
+ namespace: string;
31
+ ownerProfileId: string;
32
+ opKind: ShareLinkOperationKind;
33
+ clientRequestId: string;
34
+ operationId: string;
35
+ shareId: string;
36
+ requestHash: string;
37
+ status: ShareLinkOperationStatus;
38
+ resultJson: string | null;
39
+ resultVersion: number | null;
40
+ createdAt: string;
41
+ updatedAt: string;
42
+ /**
43
+ * Recommended prune instant (createdAt + 30 days). Pruning is explicit; no
44
+ * worker auto-deletes records or replays them.
45
+ */
46
+ pruneAfter: string;
47
+ };
48
+
49
+ /**
50
+ * One in-flight content/metadata operation for a share.
51
+ *
52
+ * There is at most one per share (unique `shareId`). The reservation carries the
53
+ * intended metadata, the opaque immutable object reference, content/recovery
54
+ * hashes and byte counts, the expected base versions and a fenced generation with
55
+ * a bounded lease so a restart can either resume or supersede it.
56
+ */
57
+ export type ShareLinkReservationRecord = {
58
+ shareId: string;
59
+ namespace: string;
60
+ ownerProfileId: string;
61
+ opKind: ShareLinkOperationKind;
62
+ clientRequestId: string;
63
+ requestHash: string;
64
+ operationId: string;
65
+ /** Opaque immutable LearnCloud object id; `null` for a metadata-only revision. */
66
+ objectRef: string | null;
67
+ /** Intended visible content version; `null` for a metadata-only revision. */
68
+ contentVersion: number | null;
69
+ baseVersion: number;
70
+ baseContentVersion: number;
71
+ contentHash: string | null;
72
+ contentBytes: number | null;
73
+ recoveryHash: string | null;
74
+ recoveryBytes: number | null;
75
+ title: string;
76
+ note: string | null;
77
+ expiresAt: string | null;
78
+ selectedCount: number;
79
+ passcodeHash: string | null;
80
+ notifyOnView: boolean;
81
+ /**
82
+ * Coherent policy snapshot derived server-side at reservation time and
83
+ * applied to the share at finalize under the same lock. Re-derived on every
84
+ * mutation so a stale adult snapshot cannot keep counting views.
85
+ */
86
+ policy: ShareLinkPolicySnapshot;
87
+ /** Fence that must still equal `ShareLink.generation` at finalize. */
88
+ generation: number;
89
+ leaseOwner: string;
90
+ leaseExpiresAt: string;
91
+ createdAt: string;
92
+ updatedAt: string;
93
+ };
94
+
95
+ export type ShareContentCleanupJobRecord = {
96
+ objectRef: string;
97
+ operationId: string;
98
+ namespace: string;
99
+ ownerProfileId: string;
100
+ shareId: string;
101
+ contentVersion: number;
102
+ reason: ShareContentCleanupReason;
103
+ status: ShareContentCleanupStatus;
104
+ attempts: number;
105
+ nextAttemptAt: string;
106
+ claimToken: string | null;
107
+ claimedBy: string | null;
108
+ claimExpiresAt: string | null;
109
+ lastError: string | null;
110
+ createdAt: string;
111
+ updatedAt: string;
112
+ completedAt: string | null;
113
+ };
114
+
115
+ /** Opaque content/recovery binding for a staged revision. Never ciphertext. */
116
+ export type ShareContentBinding = {
117
+ /** LearnCloud payloadHash over envelope + ownerEncryptedRecovery, excluding allocated IDs. */
118
+ contentHash: string;
119
+ contentBytes: number;
120
+ recoveryHash: string;
121
+ recoveryBytes: number;
122
+ };
123
+
124
+ export type ReserveCreateInput = {
125
+ namespace: string;
126
+ ownerProfileId: string;
127
+ clientRequestId: string;
128
+ shareId: string;
129
+ title: string;
130
+ note?: string | null;
131
+ expiresAt?: string | null;
132
+ selectedCount: number;
133
+ passcodeHash?: string | null;
134
+ notifyOnView?: boolean;
135
+ content: ShareContentBinding;
136
+ /** Canonical hash of the full validated create request. */
137
+ requestHash: string;
138
+ /** Server-derived policy snapshot; conservative default when omitted. */
139
+ policy?: ShareLinkPolicySnapshot;
140
+ leaseOwner: string;
141
+ leaseMs?: number;
142
+ now?: Date;
143
+ };
144
+
145
+ export type ReserveReplacementContent = ShareContentBinding & {
146
+ /** Must equal the current visible content version + 1. */
147
+ contentVersion: number;
148
+ selectedCount: number;
149
+ };
150
+
151
+ export type ReserveReplacementInput = {
152
+ namespace: string;
153
+ ownerProfileId: string;
154
+ clientRequestId: string;
155
+ shareId: string;
156
+ expectedVersion: number;
157
+ requestHash: string;
158
+ leaseOwner: string;
159
+ leaseMs?: number;
160
+ now?: Date;
161
+ /**
162
+ * When present, stages a content replacement; when absent, the reservation is a
163
+ * metadata-only revision (for example an expiry extension) that leaves the
164
+ * visible object reference untouched.
165
+ */
166
+ content?: ReserveReplacementContent;
167
+ title?: string;
168
+ note?: string | null;
169
+ expiresAt?: string | null;
170
+ /** Omitted preserves the current hash; null removes passcode protection. */
171
+ passcodeHash?: string | null;
172
+ notifyOnView?: boolean;
173
+ /**
174
+ * Re-derived policy on every mutation so a profile that becomes managed (or
175
+ * whose age source changes) stops accumulating views going forward.
176
+ */
177
+ policy?: ShareLinkPolicySnapshot;
178
+ };
179
+
180
+ export type ReserveShareLinkResult =
181
+ | {
182
+ outcome: 'reserved';
183
+ /** `created` for a new reservation, `resumed` for an in-flight retry. */
184
+ state: 'created' | 'resumed';
185
+ share: ShareLinkRecord;
186
+ reservation: ShareLinkReservationRecord;
187
+ }
188
+ | {
189
+ outcome: 'already_committed';
190
+ /** The recorded result for this request id; may be older than `current`. */
191
+ recorded: ShareLinkRecord;
192
+ /** The current committed share, which a caller must not roll back. */
193
+ current: ShareLinkRecord;
194
+ };
195
+
196
+ export type FinalizeReservationInput = {
197
+ namespace: string;
198
+ ownerProfileId: string;
199
+ shareId: string;
200
+ operationId: string;
201
+ objectRef: string | null;
202
+ generation: number;
203
+ leaseOwner: string;
204
+ /**
205
+ * PRECONDITION: the external coordinator must already have verified that the
206
+ * immutable object for `objectRef` exists in LearnCloud and that its stored
207
+ * content hash matches. When supplied here it is re-checked against the
208
+ * reservation as defense in depth; this repository never performs network I/O.
209
+ */
210
+ verifiedContentHash?: string;
211
+ /** Production-only graph-local policy recheck under the share write lock. */
212
+ resolveCurrentPolicy?: (
213
+ tx: import('./transaction').ShareLinkTransaction,
214
+ ownerProfileId: string,
215
+ now: Date
216
+ ) => Promise<import('@helpers/share-link-policy/types').ShareLinkPolicySnapshot>;
217
+ now?: Date;
218
+ /** Maintenance-only: bounded transaction timeout for this unit. */
219
+ transactionTimeoutMs?: number;
220
+ /** Maintenance-only: single explicit attempt with no inline retry. */
221
+ noInlineRetry?: boolean;
222
+ };
223
+
224
+ export type FinalizeReservationResult =
225
+ | { outcome: 'finalized'; share: ShareLinkRecord; cleanupQueuedFor: string | null }
226
+ | { outcome: 'already_finalized'; share: ShareLinkRecord };
227
+
228
+ export type AbandonReservationInput = {
229
+ namespace: string;
230
+ ownerProfileId: string;
231
+ shareId: string;
232
+ operationId: string;
233
+ /** When supplied, only the matching reservation generation is abandoned. */
234
+ generation?: number;
235
+ /** When supplied, only the matching lease holder may abandon. */
236
+ leaseOwner?: string;
237
+ reason?: string;
238
+ now?: Date;
239
+ };
240
+
241
+ export type AbandonReservationResult =
242
+ | { outcome: 'abandoned'; cleanupQueuedFor: string | null }
243
+ | { outcome: 'already_absent' }
244
+ | { outcome: 'already_finalized'; share: ShareLinkRecord };
245
+
246
+ export type RevokeShareLinkInput = {
247
+ namespace: string;
248
+ ownerProfileId: string;
249
+ shareId: string;
250
+ /** Optional optimistic check; ignored when the share is already stopped. */
251
+ expectedVersion?: number;
252
+ clientRequestId?: string;
253
+ requestHash?: string;
254
+ now?: Date;
255
+ };
256
+
257
+ export type RevokeShareLinkResult = {
258
+ outcome: 'revoked' | 'already_stopped';
259
+ share: ShareLinkRecord;
260
+ /** Immutable object refs queued for tracked cleanup by this call. */
261
+ cleanupQueuedFor: string[];
262
+ };
263
+
264
+ export type GetShareLinkInput = {
265
+ shareId: string;
266
+ namespace?: string;
267
+ ownerProfileId?: string;
268
+ };
269
+
270
+ /**
271
+ * Decoded keyset cursor for the owner list. Exactly the two immutable ordering
272
+ * fields (`createdAt`, `id`); never a raw database node or a query clause.
273
+ */
274
+ export type ShareLinkListCursor = {
275
+ createdAt: string;
276
+ id: string;
277
+ };
278
+
279
+ /**
280
+ * Trusted, caller-independent owner-list query. Both `namespace` and
281
+ * `ownerProfileId` are required and always originate from server config and the
282
+ * authenticated profile. `cursor` is already decoded/validated.
283
+ */
284
+ export type ListShareLinksInput = {
285
+ namespace: string;
286
+ ownerProfileId: string;
287
+ limit: number;
288
+ cursor?: ShareLinkListCursor | null;
289
+ };
290
+
291
+ export type ListShareLinksResult = {
292
+ records: ShareLinkRecord[];
293
+ /** Opaque next cursor; `null` on the last page. */
294
+ nextCursor: string | null;
295
+ hasMore: boolean;
296
+ };
297
+
298
+ export type GetCurrentShareContentInput = {
299
+ shareId: string;
300
+ namespace?: string;
301
+ ownerProfileId?: string;
302
+ now?: Date;
303
+ /**
304
+ * Owner-recovery only. When true, retained expired content is reported
305
+ * `active` so the owner can extend/edit it. The public read path must never
306
+ * set this; it defaults to false and the public active-content guard is
307
+ * unchanged.
308
+ */
309
+ allowExpired?: boolean;
310
+ };
311
+
312
+ export type CurrentShareContentResult =
313
+ | {
314
+ state: 'active';
315
+ shareId: string;
316
+ namespace: string;
317
+ ownerProfileId: string;
318
+ version: number;
319
+ contentVersion: number;
320
+ objectRef: string;
321
+ operationId: string;
322
+ contentHash: string | null;
323
+ contentBytes: number | null;
324
+ recoveryHash: string | null;
325
+ selectedCount: number;
326
+ expiresAt: string | null;
327
+ updatedAt: string;
328
+ }
329
+ | {
330
+ state: 'not_active';
331
+ shareId: string;
332
+ namespace: string;
333
+ ownerProfileId: string;
334
+ status: ShareLinkStatus;
335
+ contentState: ShareContentState;
336
+ reason: 'stopped' | 'expired' | 'staging' | 'content_missing';
337
+ }
338
+ | { state: 'missing' };
339
+
340
+ export type ClaimCleanupJobsInput = {
341
+ /**
342
+ * Required explicit namespace. Scheduled maintenance signs for exactly one
343
+ * namespace, so it must never discover and claim another namespace's jobs and
344
+ * rely on the remote client to reject them.
345
+ */
346
+ namespace: string;
347
+ claimant: string;
348
+ limit?: number;
349
+ claimMs?: number;
350
+ /** When supplied, the managed transaction timeout in milliseconds. */
351
+ transactionTimeoutMs?: number;
352
+ /** Maintenance-only: single explicit attempt with no inline retry. */
353
+ noInlineRetry?: boolean;
354
+ now?: Date;
355
+ };
356
+
357
+ export type ClaimCleanupJobsResult = {
358
+ claimToken: string;
359
+ jobs: ShareContentCleanupJobRecord[];
360
+ };
361
+
362
+ export type CompleteCleanupJobInput = {
363
+ objectRef: string;
364
+ claimToken: string;
365
+ outcome: 'completed' | 'retry';
366
+ errorMessage?: string;
367
+ /** When supplied, the managed transaction timeout in milliseconds. */
368
+ transactionTimeoutMs?: number;
369
+ /** Maintenance-only: single explicit attempt with no inline retry. */
370
+ noInlineRetry?: boolean;
371
+ now?: Date;
372
+ };
373
+
374
+ export type CompleteCleanupJobResult =
375
+ | { outcome: 'completed'; job: ShareContentCleanupJobRecord }
376
+ | { outcome: 'retry'; job: ShareContentCleanupJobRecord }
377
+ | { outcome: 'claim_lost' };
378
+
379
+ /**
380
+ * Exact scoped recovery key. Every field is persisted Brain state; a caller
381
+ * never supplies an object ref, hash, generation or lease as authority.
382
+ */
383
+ export type ShareLinkRecoveryKey = {
384
+ namespace: string;
385
+ ownerProfileId: string;
386
+ shareId: string;
387
+ operationId: string;
388
+ };
389
+
390
+ export type DiscoverRecoverableReservationsInput = {
391
+ /** Required. Discovery is never global. */
392
+ namespace: string;
393
+ /** Bounded to the 1..100 recovery batch window. */
394
+ limit?: number;
395
+ now?: Date;
396
+ /** Maintenance-only: bounded read-transaction timeout. */
397
+ transactionTimeoutMs?: number;
398
+ /** Maintenance-only: single explicit attempt with no inline retry. */
399
+ noInlineRetry?: boolean;
400
+ };
401
+
402
+ export type DiscoverRecoverableReservationsResult = {
403
+ keys: ShareLinkRecoveryKey[];
404
+ };
405
+
406
+ /**
407
+ * Read-only recovery target derived from durable Brain state.
408
+ *
409
+ * `committed` lets a scoped retry observe a finalize whose response was lost
410
+ * without re-uploading or reconstructing the deleted reservation.
411
+ */
412
+ export type ShareLinkRecoveryTarget =
413
+ | {
414
+ state: 'reservation';
415
+ share: ShareLinkRecord;
416
+ reservation: ShareLinkReservationRecord;
417
+ }
418
+ | { state: 'committed'; share: ShareLinkRecord }
419
+ | { state: 'absent' };
420
+
421
+ /**
422
+ * Why a persisted reservation was not reclaimed. Every value is a bounded,
423
+ * non-sensitive category safe to aggregate in logs or a runner summary.
424
+ */
425
+ export type RecoveryClaimRejection =
426
+ | 'absent'
427
+ | 'lease_active'
428
+ | 'lease_lapsed'
429
+ | 'stale_generation'
430
+ | 'binding_mismatch'
431
+ | 'malformed_binding'
432
+ | 'operation_not_in_progress'
433
+ | 'share_not_recoverable'
434
+ | 'version_changed';
435
+
436
+ export type ClaimRecoverableReservationInput = ShareLinkRecoveryKey & {
437
+ leaseOwner: string;
438
+ leaseMs?: number;
439
+ now?: Date;
440
+ /** Maintenance-only: bounded transaction timeout for this claim. */
441
+ transactionTimeoutMs?: number;
442
+ /** Maintenance-only: single explicit attempt with no inline retry. */
443
+ noInlineRetry?: boolean;
444
+ };
445
+
446
+ /**
447
+ * A claim atomically bumps the share and reservation generation and renews only
448
+ * the lease owner/expiry. The immutable object tuple, hashes, metadata and
449
+ * original operation id are preserved.
450
+ */
451
+ export type ClaimRecoverableReservationResult =
452
+ | {
453
+ outcome: 'claimed';
454
+ share: ShareLinkRecord;
455
+ reservation: ShareLinkReservationRecord;
456
+ }
457
+ | { outcome: 'not_claimable'; reason: RecoveryClaimRejection };
458
+
459
+ export type AbandonRecoveredReservationInput = ShareLinkRecoveryKey & {
460
+ /** Required. A stale generation can never abandon reclaimed work. */
461
+ generation: number;
462
+ /** Required. A reused claimant string is still fenced by generation + lease. */
463
+ leaseOwner: string;
464
+ reason?: string;
465
+ now?: Date;
466
+ /** Maintenance-only: bounded transaction timeout for this abandon. */
467
+ transactionTimeoutMs?: number;
468
+ /** Maintenance-only: single explicit attempt with no inline retry. */
469
+ noInlineRetry?: boolean;
470
+ };
471
+
472
+ /**
473
+ * Fenced recovery abandon. Unlike the internal abandon path, this requires an
474
+ * active lease so a worker that already lost its fence cannot destroy work.
475
+ */
476
+ export type AbandonRecoveredReservationResult =
477
+ | { outcome: 'abandoned'; cleanupQueuedFor: string | null }
478
+ | { outcome: 'already_absent' }
479
+ | { outcome: 'already_finalized'; share: ShareLinkRecord }
480
+ | { outcome: 'not_claimable'; reason: RecoveryClaimRejection };
481
+
482
+ /**
483
+ * Input handed to a {@link ShareViewEligibilitySource} inside the receipt
484
+ * transaction, after the share lock is held and the committed tuple re-read. It
485
+ * carries only the already-validated identity of one committed revision.
486
+ */
487
+ export type ShareViewEligibilityCheck = {
488
+ namespace: string;
489
+ ownerProfileId: string;
490
+ shareId: string;
491
+ shareVersion: number;
492
+ contentVersion: number;
493
+ objectRef: string;
494
+ operationId: string;
495
+ /** Clock sampled after the blocking share lock, never before. */
496
+ now: Date;
497
+ };
498
+
499
+ /**
500
+ * Narrowly typed, transaction-compatible trusted policy source.
501
+ *
502
+ * This is the ONLY authority for "the owner is currently eligible to count a
503
+ * view". Implementations MUST read persisted server state through the provided
504
+ * transaction and MUST NOT perform network I/O or trust caller/request values.
505
+ *
506
+ * Fail-closed contract: an absent source, an unavailable/unverifiable source or
507
+ * any thrown error is ineligible. A pre-lock boolean is never authority.
508
+ */
509
+ export type ShareViewEligibilitySource = {
510
+ isEligible: (
511
+ tx: import('./transaction').ShareLinkTransaction,
512
+ input: ShareViewEligibilityCheck
513
+ ) => Promise<boolean>;
514
+ };
515
+
516
+ /**
517
+ * Persist one short-lived, single-use view receipt for an eligible owner.
518
+ *
519
+ * Only the caller-supplied opaque token is hashed; the raw token never reaches
520
+ * the graph. The persisted tuple is the exact committed revision that produced
521
+ * it, so a superseded receipt can never count toward current content. The
522
+ * current share state and `eligibilitySource` are re-checked under the share
523
+ * lock before the node is created; an absent source writes nothing.
524
+ */
525
+ export type PersistShareViewReceiptInput = {
526
+ /** Raw opaque CSPRNG token (43-char base64url); hashed before storage. */
527
+ receipt: string;
528
+ namespace: string;
529
+ ownerProfileId: string;
530
+ shareId: string;
531
+ /** Immutable share record version (optimistic-concurrency revision). */
532
+ shareVersion: number;
533
+ contentVersion: number;
534
+ objectRef: string;
535
+ operationId: string;
536
+ ttlSeconds: number;
537
+ /**
538
+ * Transaction-compatible trusted policy source. When omitted the receipt is
539
+ * never written (production has no authoritative age source today).
540
+ */
541
+ eligibilitySource?: ShareViewEligibilitySource;
542
+ /**
543
+ * Clock seam, sampled AFTER the blocking share lock is held so a delayed
544
+ * lock cannot mint a receipt with a stale expiry.
545
+ */
546
+ now?: () => Date;
547
+ };
548
+
549
+ /**
550
+ * Outcome of one acknowledgement attempt. Every non-`consumed` value is a
551
+ * fixed, non-sensitive category; the public route collapses all of them to the
552
+ * same `{ ok: true }` response.
553
+ */
554
+ export type ConsumeShareViewReceiptOutcome =
555
+ | 'consumed'
556
+ | 'not_found'
557
+ | 'expired'
558
+ | 'already_consumed'
559
+ | 'ineligible'
560
+ | 'not_active'
561
+ | 'version_mismatch'
562
+ | 'binding_mismatch';
563
+
564
+ export type ConsumeShareViewReceiptInput = {
565
+ /** Raw opaque token exactly as returned to the viewer. */
566
+ receipt: string;
567
+ /**
568
+ * Trusted deployment namespace from server configuration. The receipt, the
569
+ * share and this value must all agree; a token minted in another namespace is
570
+ * never consumable by this deployment.
571
+ */
572
+ namespace: string;
573
+ /**
574
+ * Transaction-compatible trusted policy source, consulted under the share
575
+ * and receipt locks. When omitted, counting is denied. The committed share
576
+ * policy is additionally required, so a stale permissive snapshot can never
577
+ * re-enable counting on its own.
578
+ */
579
+ eligibilitySource?: ShareViewEligibilitySource;
580
+ /**
581
+ * Clock seam. Sampled AFTER the share and receipt locks are held, and
582
+ * re-sampled on each managed-transaction retry.
583
+ */
584
+ now?: () => Date;
585
+ };
586
+
587
+ export type PruneShareViewReceiptsInput = {
588
+ /**
589
+ * Required trusted namespace. Pruning is never global: a pass may only
590
+ * delete receipts belonging to the namespace it signs for.
591
+ */
592
+ namespace: string;
593
+ /** Bounded 1..1000 batch; defaults to 200. Non-finite values are rejected. */
594
+ limit?: number;
595
+ now?: Date;
596
+ /**
597
+ * Maintenance bounds. When supplied the prune runs as a single explicit
598
+ * bounded read transaction with no driver-managed inline retry.
599
+ */
600
+ transaction?: ShareLinkTransactionOptions;
601
+ };
package/src/app.ts CHANGED
@@ -21,6 +21,8 @@ import { integrationsRouter, IntegrationsRouter } from '@routes/integrations';
21
21
  import { appStoreRouter, AppStoreRouter } from '@routes/app-store';
22
22
  import { activityRouter, ActivityRouter } from '@routes/activity';
23
23
  import { federationRouter, FederationRouter } from '@routes/federation';
24
+ import { shareLinksRouter, ShareLinksRouter } from '@routes/share-links';
25
+ import { publicShareLinksRouter, PublicShareLinksRouter } from '@routes/public-share-links';
24
26
 
25
27
  /** For end-to-end testing, only available in test environment */
26
28
  import { testRouter, TestRouter } from '@routes/test';
@@ -52,6 +54,8 @@ export const appRouter = t.router<{
52
54
  appStore: AppStoreRouter;
53
55
  activity: ActivityRouter;
54
56
  federation: FederationRouter;
57
+ shareLinks: ShareLinksRouter;
58
+ publicShareLinks: PublicShareLinksRouter;
55
59
  test?: TestRouter;
56
60
  bench?: BenchRouter;
57
61
  }>({
@@ -76,6 +80,8 @@ export const appRouter = t.router<{
76
80
  appStore: appStoreRouter,
77
81
  activity: activityRouter,
78
82
  federation: federationRouter,
83
+ shareLinks: shareLinksRouter,
84
+ publicShareLinks: publicShareLinksRouter,
79
85
  test: environment.IS_E2E_TEST ? testRouter : undefined,
80
86
  bench: environment.ENABLE_BENCH_ROUTES ? benchRouter : undefined,
81
87
  });
@@ -77,6 +77,7 @@ export const brainServiceEnvironmentShape = {
77
77
  SKILL_SEMANTIC_SEARCH_RATE_LIMIT_PER_MIN: optionalEnvironmentPort,
78
78
  BITSTRING_STATUS_LIST_SIZE: optionalEnvironmentPort,
79
79
  CREDENTIAL_REFRESH_DIGEST_SECRET: optionalEnvironmentString,
80
+ SHARE_LINK_REQUEST_HASH_SECRET: optionalEnvironmentString,
80
81
  CREDENTIAL_REFRESH_ENABLED: optionalEnvironmentBoolean.default(false),
81
82
  CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS: credentialRefreshNotificationWindowHours,
82
83
  IS_OFFLINE: optionalEnvironmentBoolean.default(false),
@@ -211,6 +212,19 @@ export const getCredentialRefreshRuntimeEnvironment = (): CredentialRefreshRunti
211
212
  examplePath: 'services/learn-card-network/brain-service/.env.example',
212
213
  });
213
214
 
215
+ const shareLinkRequestHashRuntimeEnvironmentSchema = z.object({
216
+ NODE_ENV: brainServiceEnvironmentShape.NODE_ENV,
217
+ SHARE_LINK_REQUEST_HASH_SECRET: brainServiceEnvironmentShape.SHARE_LINK_REQUEST_HASH_SECRET,
218
+ });
219
+
220
+ /** Reads the dedicated fingerprint secret at call time for enabled share-link APIs. */
221
+ export const getShareLinkRequestHashRuntimeEnvironment = () =>
222
+ parseEnvironment(shareLinkRequestHashRuntimeEnvironmentSchema, process.env, {
223
+ project: 'brain-service share links',
224
+ source: 'process environment',
225
+ examplePath: 'services/learn-card-network/brain-service/.env.example',
226
+ });
227
+
214
228
  const inboxBatchRuntimeEnvironmentSchema = z.object({
215
229
  INBOX_QUEUE_URL: brainServiceEnvironmentShape.INBOX_QUEUE_URL,
216
230
  INBOX_DEAD_LETTER_QUEUE_URL: brainServiceEnvironmentShape.INBOX_DEAD_LETTER_QUEUE_URL,
@@ -45,3 +45,8 @@ export const AUTH_GRANT_CREDENTIAL_MANAGEMENT_SCOPE = 'credential:* presentation
45
45
  export const AUTH_GRANT_CONTRACTS_SCOPE = 'contracts:*';
46
46
  export const AUTH_GRANT_DID_METADATA_SCOPE = 'didMetadata:*';
47
47
  export const AUTH_GRANT_AUTH_GRANTS_SCOPE = 'authGrants:*';
48
+
49
+ // LC-2187 owner share-link APIs. Least privilege: owner metadata/recovery reads
50
+ // use `shareLinks:read`; create/update/revoke/retry use `shareLinks:write`.
51
+ export const AUTH_GRANT_SHARE_LINKS_READ_SCOPE = 'shareLinks:read';
52
+ export const AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE = 'shareLinks:write';