@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,365 @@
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ import { describe, expect, it, vi } from 'vitest';
4
+
5
+ // TEST-ONLY cross-service imports of the reviewed C1 verifier. These are never
6
+ // imported by brain-service production code; they exist purely to prove the
7
+ // token/claim/hash contract interoperates with the real C1 implementation.
8
+ import { computeShareContentRequestHash } from '../../learn-cloud-service/src/helpers/share-content-auth/canonical';
9
+ import { resolveShareContentTrustConfig } from '../../learn-cloud-service/src/helpers/share-content-auth/config';
10
+ import { createShareContentAuthorizationVerifier } from '../../learn-cloud-service/src/helpers/share-content-auth/verifier';
11
+ import type { PresentationVerifier } from '../../learn-cloud-service/src/helpers/share-content-auth/types';
12
+
13
+ import {
14
+ computeShareContentRequestBodyHash,
15
+ createShareContentClient,
16
+ resolveShareContentClientConfig,
17
+ type ShareContentPutRequest,
18
+ type ShareContentTuple,
19
+ } from '@helpers/share-content-client';
20
+
21
+ /**
22
+ * LC-2187 Task C5 → C1 interop contract.
23
+ *
24
+ * The Brain client's signer is a REAL DIDKit holder-signed VP JWT (checked-in
25
+ * WASM, did:key fixture), and the token it produces over HTTP is accepted by the
26
+ * actual reviewed C1 authorization verifier with a real DIDKit signature check.
27
+ * This is the strongest available evidence that the fixed cross-task contract
28
+ * interoperates; it is not a mock echo.
29
+ */
30
+
31
+ type DidkitWasm = {
32
+ initSync: (input: { module: Buffer | Uint8Array }) => void;
33
+ generateEd25519KeyFromBytes: (seed: Uint8Array) => string;
34
+ keyToDID: (method: string, jwk: string) => string;
35
+ issuePresentation: (
36
+ presentation: string,
37
+ proofOptions: string,
38
+ key: string,
39
+ context: string
40
+ ) => Promise<string>;
41
+ verifyPresentation: (
42
+ presentation: string,
43
+ proofOptions: string,
44
+ context: string
45
+ ) => Promise<string>;
46
+ };
47
+
48
+ let didkitPromise: Promise<DidkitWasm> | null = null;
49
+
50
+ const loadDidkit = (): Promise<DidkitWasm> => {
51
+ didkitPromise ??= (async () => {
52
+ const wasmModule =
53
+ (await import('../../../../packages/plugins/didkit/src/didkit/pkg/didkit_wasm.js')) as unknown as DidkitWasm;
54
+
55
+ wasmModule.initSync({
56
+ module: readFileSync(
57
+ new URL(
58
+ '../../../../packages/plugins/didkit/src/didkit/pkg/didkit_wasm_bg.wasm',
59
+ import.meta.url
60
+ )
61
+ ),
62
+ });
63
+
64
+ return wasmModule;
65
+ })();
66
+
67
+ return didkitPromise;
68
+ };
69
+
70
+ const makeDidkitIdentity = async (fill: number): Promise<{ jwk: string; did: string }> => {
71
+ const didkit = await loadDidkit();
72
+ const seed = new Uint8Array(32).fill(fill);
73
+ const jwk = didkit.generateEd25519KeyFromBytes(seed);
74
+
75
+ return { jwk, did: didkit.keyToDID('key', jwk) };
76
+ };
77
+
78
+ const issueRealPresentation = async (
79
+ identity: { jwk: string; did: string },
80
+ claims: Record<string, unknown>
81
+ ): Promise<string> => {
82
+ const didkit = await loadDidkit();
83
+
84
+ return didkit.issuePresentation(
85
+ JSON.stringify({
86
+ '@context': ['https://www.w3.org/2018/credentials/v1'],
87
+ type: ['VerifiablePresentation'],
88
+ holder: identity.did,
89
+ verifiableCredential: [],
90
+ }),
91
+ JSON.stringify({
92
+ proofPurpose: 'authentication',
93
+ proofFormat: 'jwt',
94
+ challenge: JSON.stringify(claims),
95
+ }),
96
+ identity.jwk,
97
+ '{}'
98
+ );
99
+ };
100
+
101
+ const realDidkitVerifier: PresentationVerifier = async token => {
102
+ const didkit = await loadDidkit();
103
+ const result = await didkit.verifyPresentation(
104
+ token,
105
+ JSON.stringify({ proofFormat: 'jwt' }),
106
+ '{}'
107
+ );
108
+
109
+ return JSON.parse(result) as Awaited<ReturnType<PresentationVerifier>>;
110
+ };
111
+
112
+ const kidOf = (token: string): string => {
113
+ const [headerPart] = token.split('.');
114
+ const header = JSON.parse(Buffer.from(headerPart ?? '', 'base64url').toString('utf8'));
115
+
116
+ return header.kid as string;
117
+ };
118
+
119
+ const AUDIENCE = 'did:web:learncloud.test';
120
+ const NAMESPACE = 'test-namespace';
121
+
122
+ const baseTuple = (): ShareContentTuple => ({
123
+ namespace: NAMESPACE,
124
+ ownerProfileId: 'owner-1',
125
+ shareId: Buffer.alloc(16, 1).toString('base64url'),
126
+ contentVersion: 1,
127
+ objectId: 'o'.repeat(43),
128
+ operationId: '11111111-1111-4111-8111-111111111111',
129
+ });
130
+
131
+ const basePutRequest = (): ShareContentPutRequest => ({
132
+ ...baseTuple(),
133
+ envelope: {
134
+ v: 1,
135
+ alg: 'A256GCM',
136
+ iv: Buffer.alloc(12, 2).toString('base64url'),
137
+ ct: Buffer.alloc(32, 3).toString('base64url'),
138
+ },
139
+ ownerEncryptedRecovery: { protected: 'a', iv: 'b', ciphertext: 'c', tag: 'd' },
140
+ });
141
+
142
+ describe('share-content canonical request hash matches C1', () => {
143
+ it.each([
144
+ [
145
+ { 'a': 1, 'b': 'two', 'c': [true, null] },
146
+ 'd6a41c0217ee07fd7db67d3b28147282891df0ab90f17d5eb0181317dd1527bf',
147
+ ],
148
+ [
149
+ { 'z': 'é', 'a': { 'b': 2, 'a': 1 } },
150
+ '71a3755223791b0f221a3235c7dd6349ec5744be3b9ad0b68cc1215befcbc8a5',
151
+ ],
152
+ ])('matches the fixed C1 digest for %#', (body, digest) => {
153
+ expect(computeShareContentRequestBodyHash(body)).toBe(digest);
154
+ expect(computeShareContentRequestHash(body)).toBe(digest);
155
+ });
156
+
157
+ it.each([
158
+ [{ a: 1, b: 'two', c: [true, null] }],
159
+ [{ namespace: NAMESPACE, ownerProfileId: 'owner-1', contentVersion: 2 }],
160
+ [
161
+ {
162
+ envelope: { v: 1, alg: 'A256GCM', iv: 'aa', ct: 'bb' },
163
+ ownerEncryptedRecovery: { protected: 'p', iv: 'i', ciphertext: 'c', tag: 't' },
164
+ },
165
+ ],
166
+ ])('agrees on %#', body => {
167
+ expect(computeShareContentRequestBodyHash(body)).toBe(computeShareContentRequestHash(body));
168
+ });
169
+
170
+ it('rejects the same unsupported values as C1', () => {
171
+ expect(() => computeShareContentRequestBodyHash({ bad: undefined })).toThrow();
172
+ expect(() => computeShareContentRequestHash({ bad: undefined })).toThrow();
173
+ });
174
+ });
175
+
176
+ describe('real DIDKit signer token is accepted by the reviewed C1 verifier', () => {
177
+ it('signs over the fixed put body and authorizes end-to-end', async () => {
178
+ const brain = await makeDidkitIdentity(3);
179
+ const request = basePutRequest();
180
+ let capturedToken: string | null = null;
181
+ let capturedBody: Record<string, unknown> | null = null;
182
+
183
+ const fetchImpl = vi.fn(async (_url: unknown, init?: RequestInit) => {
184
+ const headers = (init?.headers ?? {}) as Record<string, string>;
185
+ capturedToken = (headers.authorization ?? '').slice('Bearer '.length);
186
+ capturedBody = JSON.parse(String(init?.body));
187
+
188
+ return new Response(
189
+ JSON.stringify({
190
+ ok: true,
191
+ value: {
192
+ status: 'created',
193
+ record: {
194
+ kind: 'active',
195
+ namespace: request.namespace,
196
+ ownerProfileId: request.ownerProfileId,
197
+ shareId: request.shareId,
198
+ contentVersion: request.contentVersion,
199
+ objectId: request.objectId,
200
+ operationId: request.operationId,
201
+ contentHash: 'a'.repeat(64),
202
+ payloadHash: 'b'.repeat(64),
203
+ ciphertextBytes: 32,
204
+ recoveryBytes: 53,
205
+ createdAt: '2026-09-20T00:00:00.000Z',
206
+ },
207
+ },
208
+ }),
209
+ { status: 200, headers: { 'content-type': 'application/json' } }
210
+ );
211
+ });
212
+
213
+ const client = createShareContentClient(
214
+ resolveShareContentClientConfig({
215
+ enabled: true,
216
+ origin: 'https://learncloud.test',
217
+ namespace: NAMESPACE,
218
+ audience: AUDIENCE,
219
+ signerDid: brain.did,
220
+ signer: claims => issueRealPresentation(brain, claims),
221
+ fetchImpl,
222
+ sleep: async () => undefined,
223
+ })
224
+ );
225
+
226
+ const result = await client.put(request);
227
+
228
+ expect(result.ok).toBe(true);
229
+ expect(capturedToken).not.toBeNull();
230
+ expect(capturedBody).not.toBeNull();
231
+
232
+ const token = capturedToken as unknown as string;
233
+ const body = capturedBody as unknown as Record<string, unknown>;
234
+ const kid = kidOf(token);
235
+
236
+ expect(kid.startsWith(`${brain.did}#`)).toBe(true);
237
+
238
+ const verifier = createShareContentAuthorizationVerifier({
239
+ config: resolveShareContentTrustConfig({
240
+ enabled: true,
241
+ audience: AUDIENCE,
242
+ serviceDids: [brain.did],
243
+ verificationMethods: [kid],
244
+ clockSkewSeconds: 5,
245
+ maxTokenTtlSeconds: 60,
246
+ }),
247
+ verifyPresentation: realDidkitVerifier,
248
+ replayStore: {
249
+ consumeOnce: vi.fn(async () => true),
250
+ },
251
+ });
252
+
253
+ const authorized = await verifier.authorize(
254
+ {
255
+ namespace: NAMESPACE,
256
+ op: 'put',
257
+ shareId: request.shareId,
258
+ contentVersion: request.contentVersion,
259
+ ownerProfileId: request.ownerProfileId,
260
+ objectId: request.objectId,
261
+ operationId: request.operationId,
262
+ requestHash: computeShareContentRequestHash(body),
263
+ },
264
+ token
265
+ );
266
+
267
+ expect(authorized.ok).toBe(true);
268
+ if (authorized.ok) {
269
+ expect(authorized.context.signerDid).toBe(brain.did);
270
+ expect(authorized.context.claims.aud).toBe(AUDIENCE);
271
+ expect(authorized.context.claims.purpose).toBe('lc-share-content/v1');
272
+ expect(authorized.context.claims.requestHash).toBe(
273
+ computeShareContentRequestHash(body)
274
+ );
275
+ }
276
+
277
+ // A different tuple must be rejected even with a valid signature.
278
+ const mismatchVerifier = createShareContentAuthorizationVerifier({
279
+ config: resolveShareContentTrustConfig({
280
+ enabled: true,
281
+ audience: AUDIENCE,
282
+ serviceDids: [brain.did],
283
+ verificationMethods: [kid],
284
+ }),
285
+ verifyPresentation: realDidkitVerifier,
286
+ replayStore: { consumeOnce: vi.fn(async () => true) },
287
+ });
288
+
289
+ const mismatch = await mismatchVerifier.authorize(
290
+ {
291
+ namespace: NAMESPACE,
292
+ op: 'put',
293
+ shareId: request.shareId,
294
+ contentVersion: request.contentVersion,
295
+ ownerProfileId: request.ownerProfileId,
296
+ objectId: 'x'.repeat(43),
297
+ operationId: request.operationId,
298
+ requestHash: computeShareContentRequestHash(body),
299
+ },
300
+ token
301
+ );
302
+
303
+ expect(mismatch).toEqual({ ok: false, reason: 'REQUEST_BINDING_MISMATCH' });
304
+ });
305
+
306
+ it('is rejected by C1 when the request hash does not match the body', async () => {
307
+ const brain = await makeDidkitIdentity(7);
308
+ const request = basePutRequest();
309
+ let capturedToken = '';
310
+
311
+ const fetchImpl = vi.fn(async (_url: unknown, init?: RequestInit) => {
312
+ const headers = (init?.headers ?? {}) as Record<string, string>;
313
+ capturedToken = (headers.authorization ?? '').slice('Bearer '.length);
314
+
315
+ return new Response(JSON.stringify({ ok: false, error: 'CONFLICT' }), {
316
+ status: 409,
317
+ headers: { 'content-type': 'application/json' },
318
+ });
319
+ });
320
+
321
+ const client = createShareContentClient(
322
+ resolveShareContentClientConfig({
323
+ enabled: true,
324
+ origin: 'https://learncloud.test',
325
+ namespace: NAMESPACE,
326
+ audience: AUDIENCE,
327
+ signerDid: brain.did,
328
+ signer: claims => issueRealPresentation(brain, claims),
329
+ fetchImpl,
330
+ sleep: async () => undefined,
331
+ maxAttempts: 1,
332
+ })
333
+ );
334
+
335
+ await client.put(request);
336
+
337
+ const kid = kidOf(capturedToken);
338
+ const verifier = createShareContentAuthorizationVerifier({
339
+ config: resolveShareContentTrustConfig({
340
+ enabled: true,
341
+ audience: AUDIENCE,
342
+ serviceDids: [brain.did],
343
+ verificationMethods: [kid],
344
+ }),
345
+ verifyPresentation: realDidkitVerifier,
346
+ replayStore: { consumeOnce: vi.fn(async () => true) },
347
+ });
348
+
349
+ const result = await verifier.authorize(
350
+ {
351
+ namespace: NAMESPACE,
352
+ op: 'put',
353
+ shareId: request.shareId,
354
+ contentVersion: request.contentVersion,
355
+ ownerProfileId: request.ownerProfileId,
356
+ objectId: request.objectId,
357
+ operationId: request.operationId,
358
+ requestHash: '0'.repeat(64),
359
+ },
360
+ capturedToken
361
+ );
362
+
363
+ expect(result).toEqual({ ok: false, reason: 'REQUEST_HASH_MISMATCH' });
364
+ });
365
+ });