@archipelagolab/lobi 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/ENDOFFILE +0 -0
  3. package/EOF +0 -0
  4. package/LICENSE +21 -0
  5. package/SPEC-SUPPORT.md +116 -0
  6. package/YAMLEND +0 -0
  7. package/api.ts +18 -0
  8. package/archipelagolab-lobi-1.0.0.tgz +0 -0
  9. package/auth-presence.ts +56 -0
  10. package/channel-plugin-api.ts +3 -0
  11. package/cli-metadata.ts +11 -0
  12. package/contract-api.ts +17 -0
  13. package/docs/CHECKLIST.md +83 -0
  14. package/docs/FORK_SDK_GUIDE.md +279 -0
  15. package/helper-api.ts +3 -0
  16. package/index.test.ts +61 -0
  17. package/index.ts +65 -0
  18. package/openclaw.plugin.json +23 -0
  19. package/package.json +52 -0
  20. package/plugin-entry.handlers.runtime.ts +1 -0
  21. package/runtime-api.ts +54 -0
  22. package/runtime-heavy-api.ts +1 -0
  23. package/scripts/migrate-to-lobi.sh +72 -0
  24. package/secret-contract-api.ts +5 -0
  25. package/setup-entry.ts +13 -0
  26. package/src/account-selection.test.ts +124 -0
  27. package/src/account-selection.ts +226 -0
  28. package/src/actions.account-propagation.test.ts +251 -0
  29. package/src/actions.test.ts +251 -0
  30. package/src/actions.ts +336 -0
  31. package/src/approval-auth.test.ts +23 -0
  32. package/src/approval-auth.ts +25 -0
  33. package/src/approval-handler.runtime.test.ts +46 -0
  34. package/src/approval-handler.runtime.ts +400 -0
  35. package/src/approval-ids.ts +6 -0
  36. package/src/approval-native.test.ts +329 -0
  37. package/src/approval-native.ts +336 -0
  38. package/src/approval-reactions.test.ts +107 -0
  39. package/src/approval-reactions.ts +158 -0
  40. package/src/auth-precedence.ts +61 -0
  41. package/src/channel-account-paths.ts +92 -0
  42. package/src/channel.account-paths.test.ts +102 -0
  43. package/src/channel.directory.test.ts +601 -0
  44. package/src/channel.resolve.test.ts +38 -0
  45. package/src/channel.runtime.ts +16 -0
  46. package/src/channel.setup.test.ts +269 -0
  47. package/src/channel.ts +570 -0
  48. package/src/cli-metadata.ts +19 -0
  49. package/src/cli.test.ts +1015 -0
  50. package/src/cli.ts +1198 -0
  51. package/src/config-adapter.ts +41 -0
  52. package/src/config-schema.test.ts +90 -0
  53. package/src/config-schema.ts +114 -0
  54. package/src/directory-live.test.ts +200 -0
  55. package/src/directory-live.ts +238 -0
  56. package/src/doctor-contract.ts +287 -0
  57. package/src/doctor.test.ts +440 -0
  58. package/src/doctor.ts +262 -0
  59. package/src/env-vars.ts +92 -0
  60. package/src/exec-approval-resolver.test.ts +68 -0
  61. package/src/exec-approval-resolver.ts +23 -0
  62. package/src/exec-approvals.test.ts +483 -0
  63. package/src/exec-approvals.ts +290 -0
  64. package/src/group-mentions.ts +41 -0
  65. package/src/legacy-crypto-inspector-availability.test.ts +81 -0
  66. package/src/legacy-crypto-inspector-availability.ts +60 -0
  67. package/src/legacy-crypto.test.ts +234 -0
  68. package/src/legacy-crypto.ts +549 -0
  69. package/src/legacy-state.test.ts +86 -0
  70. package/src/legacy-state.ts +156 -0
  71. package/src/matrix/account-config.ts +150 -0
  72. package/src/matrix/accounts.readiness.test.ts +27 -0
  73. package/src/matrix/accounts.test.ts +757 -0
  74. package/src/matrix/accounts.ts +194 -0
  75. package/src/matrix/actions/client.test.ts +215 -0
  76. package/src/matrix/actions/client.ts +31 -0
  77. package/src/matrix/actions/devices.test.ts +114 -0
  78. package/src/matrix/actions/devices.ts +34 -0
  79. package/src/matrix/actions/limits.test.ts +15 -0
  80. package/src/matrix/actions/limits.ts +6 -0
  81. package/src/matrix/actions/messages.test.ts +289 -0
  82. package/src/matrix/actions/messages.ts +123 -0
  83. package/src/matrix/actions/pins.test.ts +74 -0
  84. package/src/matrix/actions/pins.ts +64 -0
  85. package/src/matrix/actions/polls.test.ts +71 -0
  86. package/src/matrix/actions/polls.ts +109 -0
  87. package/src/matrix/actions/profile.test.ts +109 -0
  88. package/src/matrix/actions/profile.ts +37 -0
  89. package/src/matrix/actions/reactions.test.ts +135 -0
  90. package/src/matrix/actions/reactions.ts +59 -0
  91. package/src/matrix/actions/room.test.ts +79 -0
  92. package/src/matrix/actions/room.ts +71 -0
  93. package/src/matrix/actions/summary.test.ts +87 -0
  94. package/src/matrix/actions/summary.ts +88 -0
  95. package/src/matrix/actions/types.ts +82 -0
  96. package/src/matrix/actions/verification.test.ts +105 -0
  97. package/src/matrix/actions/verification.ts +237 -0
  98. package/src/matrix/actions.ts +37 -0
  99. package/src/matrix/active-client.ts +26 -0
  100. package/src/matrix/async-lock.ts +18 -0
  101. package/src/matrix/backup-health.ts +115 -0
  102. package/src/matrix/client/config-runtime-api.ts +14 -0
  103. package/src/matrix/client/config-secret-input.runtime.ts +1 -0
  104. package/src/matrix/client/config.ts +982 -0
  105. package/src/matrix/client/create-client.test.ts +115 -0
  106. package/src/matrix/client/create-client.ts +101 -0
  107. package/src/matrix/client/env-auth.ts +6 -0
  108. package/src/matrix/client/file-sync-store.test.ts +265 -0
  109. package/src/matrix/client/file-sync-store.ts +289 -0
  110. package/src/matrix/client/logging.ts +123 -0
  111. package/src/matrix/client/migration-snapshot.runtime.ts +1 -0
  112. package/src/matrix/client/private-network-host.ts +56 -0
  113. package/src/matrix/client/runtime.ts +4 -0
  114. package/src/matrix/client/shared.test.ts +344 -0
  115. package/src/matrix/client/shared.ts +306 -0
  116. package/src/matrix/client/storage.test.ts +634 -0
  117. package/src/matrix/client/storage.ts +544 -0
  118. package/src/matrix/client/types.ts +50 -0
  119. package/src/matrix/client-bootstrap.test.ts +84 -0
  120. package/src/matrix/client-bootstrap.ts +164 -0
  121. package/src/matrix/client-resolver.test-helpers.ts +147 -0
  122. package/src/matrix/client.test.ts +1521 -0
  123. package/src/matrix/client.ts +23 -0
  124. package/src/matrix/config-paths.ts +31 -0
  125. package/src/matrix/config-update.test.ts +237 -0
  126. package/src/matrix/config-update.ts +291 -0
  127. package/src/matrix/credentials-read.ts +206 -0
  128. package/src/matrix/credentials-write.runtime.ts +26 -0
  129. package/src/matrix/credentials.test.ts +501 -0
  130. package/src/matrix/credentials.ts +95 -0
  131. package/src/matrix/deps.test.ts +74 -0
  132. package/src/matrix/deps.ts +225 -0
  133. package/src/matrix/device-health.test.ts +45 -0
  134. package/src/matrix/device-health.ts +31 -0
  135. package/src/matrix/direct-management.test.ts +350 -0
  136. package/src/matrix/direct-management.ts +347 -0
  137. package/src/matrix/direct-room.test.ts +61 -0
  138. package/src/matrix/direct-room.ts +128 -0
  139. package/src/matrix/draft-stream.test.ts +406 -0
  140. package/src/matrix/draft-stream.ts +216 -0
  141. package/src/matrix/encryption-guidance.ts +27 -0
  142. package/src/matrix/errors.ts +21 -0
  143. package/src/matrix/format.test.ts +340 -0
  144. package/src/matrix/format.ts +428 -0
  145. package/src/matrix/legacy-crypto-inspector.ts +95 -0
  146. package/src/matrix/media-errors.ts +20 -0
  147. package/src/matrix/media-text.ts +169 -0
  148. package/src/matrix/monitor/access-state.test.ts +45 -0
  149. package/src/matrix/monitor/access-state.ts +77 -0
  150. package/src/matrix/monitor/ack-config.test.ts +57 -0
  151. package/src/matrix/monitor/ack-config.ts +26 -0
  152. package/src/matrix/monitor/allowlist.test.ts +45 -0
  153. package/src/matrix/monitor/allowlist.ts +94 -0
  154. package/src/matrix/monitor/auto-join.test.ts +203 -0
  155. package/src/matrix/monitor/auto-join.ts +86 -0
  156. package/src/matrix/monitor/config.test.ts +197 -0
  157. package/src/matrix/monitor/config.ts +303 -0
  158. package/src/matrix/monitor/context-summary.ts +43 -0
  159. package/src/matrix/monitor/direct.test.ts +529 -0
  160. package/src/matrix/monitor/direct.ts +270 -0
  161. package/src/matrix/monitor/events.test.ts +1524 -0
  162. package/src/matrix/monitor/events.ts +213 -0
  163. package/src/matrix/monitor/handler.body-for-agent.test.ts +396 -0
  164. package/src/matrix/monitor/handler.group-history.test.ts +648 -0
  165. package/src/matrix/monitor/handler.media-failure.test.ts +267 -0
  166. package/src/matrix/monitor/handler.test-helpers.ts +308 -0
  167. package/src/matrix/monitor/handler.test.ts +2952 -0
  168. package/src/matrix/monitor/handler.thread-root-media.test.ts +82 -0
  169. package/src/matrix/monitor/handler.ts +1679 -0
  170. package/src/matrix/monitor/inbound-dedupe.test.ts +146 -0
  171. package/src/matrix/monitor/inbound-dedupe.ts +267 -0
  172. package/src/matrix/monitor/index.test.ts +920 -0
  173. package/src/matrix/monitor/index.ts +434 -0
  174. package/src/matrix/monitor/legacy-crypto-restore.test.ts +206 -0
  175. package/src/matrix/monitor/legacy-crypto-restore.ts +139 -0
  176. package/src/matrix/monitor/location.ts +100 -0
  177. package/src/matrix/monitor/media.test.ts +159 -0
  178. package/src/matrix/monitor/media.ts +119 -0
  179. package/src/matrix/monitor/mentions.test.ts +289 -0
  180. package/src/matrix/monitor/mentions.ts +177 -0
  181. package/src/matrix/monitor/reaction-events.test.ts +326 -0
  182. package/src/matrix/monitor/reaction-events.ts +187 -0
  183. package/src/matrix/monitor/recent-invite.test.ts +92 -0
  184. package/src/matrix/monitor/recent-invite.ts +30 -0
  185. package/src/matrix/monitor/replies.test.ts +265 -0
  186. package/src/matrix/monitor/replies.ts +136 -0
  187. package/src/matrix/monitor/reply-context.test.ts +276 -0
  188. package/src/matrix/monitor/reply-context.ts +92 -0
  189. package/src/matrix/monitor/room-history.test.ts +258 -0
  190. package/src/matrix/monitor/room-history.ts +301 -0
  191. package/src/matrix/monitor/room-info.test.ts +201 -0
  192. package/src/matrix/monitor/room-info.ts +126 -0
  193. package/src/matrix/monitor/rooms.test.ts +121 -0
  194. package/src/matrix/monitor/rooms.ts +52 -0
  195. package/src/matrix/monitor/route.test.ts +255 -0
  196. package/src/matrix/monitor/route.ts +178 -0
  197. package/src/matrix/monitor/runtime-api.ts +31 -0
  198. package/src/matrix/monitor/startup-verification.test.ts +294 -0
  199. package/src/matrix/monitor/startup-verification.ts +237 -0
  200. package/src/matrix/monitor/startup.test.ts +257 -0
  201. package/src/matrix/monitor/startup.ts +218 -0
  202. package/src/matrix/monitor/status.ts +111 -0
  203. package/src/matrix/monitor/sync-lifecycle.test.ts +224 -0
  204. package/src/matrix/monitor/sync-lifecycle.ts +91 -0
  205. package/src/matrix/monitor/task-runner.ts +38 -0
  206. package/src/matrix/monitor/thread-context.test.ts +149 -0
  207. package/src/matrix/monitor/thread-context.ts +108 -0
  208. package/src/matrix/monitor/threads.test.ts +68 -0
  209. package/src/matrix/monitor/threads.ts +85 -0
  210. package/src/matrix/monitor/types.ts +30 -0
  211. package/src/matrix/monitor/verification-events.ts +627 -0
  212. package/src/matrix/monitor/verification-utils.test.ts +47 -0
  213. package/src/matrix/monitor/verification-utils.ts +46 -0
  214. package/src/matrix/outbound-media-runtime.ts +1 -0
  215. package/src/matrix/poll-summary.ts +110 -0
  216. package/src/matrix/poll-types.test.ts +205 -0
  217. package/src/matrix/poll-types.ts +433 -0
  218. package/src/matrix/probe.runtime.ts +4 -0
  219. package/src/matrix/probe.test.ts +154 -0
  220. package/src/matrix/probe.ts +96 -0
  221. package/src/matrix/profile.test.ts +154 -0
  222. package/src/matrix/profile.ts +184 -0
  223. package/src/matrix/reaction-common.test.ts +96 -0
  224. package/src/matrix/reaction-common.ts +147 -0
  225. package/src/matrix/sdk/crypto-bootstrap.test.ts +505 -0
  226. package/src/matrix/sdk/crypto-bootstrap.ts +341 -0
  227. package/src/matrix/sdk/crypto-facade.test.ts +197 -0
  228. package/src/matrix/sdk/crypto-facade.ts +207 -0
  229. package/src/matrix/sdk/crypto-node.runtime.test.ts +27 -0
  230. package/src/matrix/sdk/crypto-node.runtime.ts +9 -0
  231. package/src/matrix/sdk/crypto-runtime.ts +11 -0
  232. package/src/matrix/sdk/decrypt-bridge.ts +356 -0
  233. package/src/matrix/sdk/event-helpers.test.ts +60 -0
  234. package/src/matrix/sdk/event-helpers.ts +71 -0
  235. package/src/matrix/sdk/http-client.test.ts +134 -0
  236. package/src/matrix/sdk/http-client.ts +87 -0
  237. package/src/matrix/sdk/idb-persistence-lock.ts +51 -0
  238. package/src/matrix/sdk/idb-persistence.lock-order.test.ts +108 -0
  239. package/src/matrix/sdk/idb-persistence.test-helpers.ts +88 -0
  240. package/src/matrix/sdk/idb-persistence.test.ts +149 -0
  241. package/src/matrix/sdk/idb-persistence.ts +283 -0
  242. package/src/matrix/sdk/logger.test.ts +25 -0
  243. package/src/matrix/sdk/logger.ts +108 -0
  244. package/src/matrix/sdk/read-response-with-limit.ts +19 -0
  245. package/src/matrix/sdk/recovery-key-store.test.ts +385 -0
  246. package/src/matrix/sdk/recovery-key-store.ts +430 -0
  247. package/src/matrix/sdk/transport.test.ts +161 -0
  248. package/src/matrix/sdk/transport.ts +344 -0
  249. package/src/matrix/sdk/types.ts +236 -0
  250. package/src/matrix/sdk/verification-manager.test.ts +509 -0
  251. package/src/matrix/sdk/verification-manager.ts +694 -0
  252. package/src/matrix/sdk/verification-status.ts +23 -0
  253. package/src/matrix/sdk.test.ts +2568 -0
  254. package/src/matrix/sdk.ts +1789 -0
  255. package/src/matrix/send/client.test.ts +174 -0
  256. package/src/matrix/send/client.ts +90 -0
  257. package/src/matrix/send/formatting.ts +189 -0
  258. package/src/matrix/send/media.ts +244 -0
  259. package/src/matrix/send/targets.test.ts +254 -0
  260. package/src/matrix/send/targets.ts +104 -0
  261. package/src/matrix/send/types.ts +134 -0
  262. package/src/matrix/send.test.ts +958 -0
  263. package/src/matrix/send.ts +609 -0
  264. package/src/matrix/session-store-metadata.ts +108 -0
  265. package/src/matrix/startup-abort.ts +44 -0
  266. package/src/matrix/sync-state.ts +27 -0
  267. package/src/matrix/target-ids.ts +102 -0
  268. package/src/matrix/thread-bindings-shared.ts +201 -0
  269. package/src/matrix/thread-bindings.test.ts +673 -0
  270. package/src/matrix/thread-bindings.ts +577 -0
  271. package/src/matrix-migration.runtime.ts +9 -0
  272. package/src/migration-config.test.ts +228 -0
  273. package/src/migration-config.ts +243 -0
  274. package/src/migration-snapshot-backup.ts +117 -0
  275. package/src/migration-snapshot.test.ts +184 -0
  276. package/src/migration-snapshot.ts +55 -0
  277. package/src/onboarding.resolve.test.ts +55 -0
  278. package/src/onboarding.test-harness.ts +158 -0
  279. package/src/onboarding.test.ts +665 -0
  280. package/src/onboarding.ts +773 -0
  281. package/src/outbound.test.ts +173 -0
  282. package/src/outbound.ts +78 -0
  283. package/src/plugin-entry.runtime.js +159 -0
  284. package/src/plugin-entry.runtime.test.ts +108 -0
  285. package/src/plugin-entry.runtime.ts +68 -0
  286. package/src/profile-update.ts +68 -0
  287. package/src/record-shared.ts +3 -0
  288. package/src/resolve-targets.test.ts +178 -0
  289. package/src/resolve-targets.ts +175 -0
  290. package/src/resolver.ts +21 -0
  291. package/src/runtime-api.ts +144 -0
  292. package/src/runtime.ts +7 -0
  293. package/src/secret-contract.ts +174 -0
  294. package/src/session-route.test.ts +315 -0
  295. package/src/session-route.ts +113 -0
  296. package/src/setup-bootstrap.ts +94 -0
  297. package/src/setup-config.ts +222 -0
  298. package/src/setup-contract.ts +89 -0
  299. package/src/setup-core.test.ts +326 -0
  300. package/src/setup-core.ts +50 -0
  301. package/src/setup-surface.ts +4 -0
  302. package/src/startup-maintenance.test.ts +227 -0
  303. package/src/startup-maintenance.ts +114 -0
  304. package/src/storage-paths.ts +92 -0
  305. package/src/test-helpers.ts +42 -0
  306. package/src/test-mocks.ts +55 -0
  307. package/src/test-runtime.ts +72 -0
  308. package/src/test-support/monitor-route-test-support.ts +8 -0
  309. package/src/tool-actions.runtime.ts +1 -0
  310. package/src/tool-actions.test.ts +422 -0
  311. package/src/tool-actions.ts +498 -0
  312. package/src/types.ts +230 -0
  313. package/test-api.ts +2 -0
  314. package/thread-bindings-runtime.ts +4 -0
  315. package/tsconfig.json +16 -0
@@ -0,0 +1,505 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { MatrixCryptoBootstrapper, type MatrixCryptoBootstrapperDeps } from "./crypto-bootstrap.js";
3
+ import type { MatrixCryptoBootstrapApi, MatrixRawEvent } from "./types.js";
4
+
5
+ function createBootstrapperDeps() {
6
+ return {
7
+ getUserId: vi.fn(async () => "@bot:example.org"),
8
+ getPassword: vi.fn(() => "super-secret-password"),
9
+ getDeviceId: vi.fn(() => "DEVICE123"),
10
+ verificationManager: {
11
+ trackVerificationRequest: vi.fn(),
12
+ },
13
+ recoveryKeyStore: {
14
+ bootstrapSecretStorageWithRecoveryKey: vi.fn(async () => {}),
15
+ },
16
+ decryptBridge: {
17
+ bindCryptoRetrySignals: vi.fn(),
18
+ },
19
+ };
20
+ }
21
+
22
+ function createCryptoApi(overrides?: Partial<MatrixCryptoBootstrapApi>): MatrixCryptoBootstrapApi {
23
+ return {
24
+ on: vi.fn(),
25
+ bootstrapCrossSigning: vi.fn(async () => {}),
26
+ bootstrapSecretStorage: vi.fn(async () => {}),
27
+ requestOwnUserVerification: vi.fn(async () => null),
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ function createVerifiedDeviceStatus(overrides?: {
33
+ localVerified?: boolean;
34
+ crossSigningVerified?: boolean;
35
+ signedByOwner?: boolean;
36
+ }) {
37
+ return {
38
+ isVerified: () => true,
39
+ localVerified: overrides?.localVerified ?? true,
40
+ crossSigningVerified: overrides?.crossSigningVerified ?? true,
41
+ signedByOwner: overrides?.signedByOwner ?? true,
42
+ };
43
+ }
44
+
45
+ function createBootstrapperHarness(
46
+ cryptoOverrides?: Partial<MatrixCryptoBootstrapApi>,
47
+ depsOverrides?: Partial<ReturnType<typeof createBootstrapperDeps>>,
48
+ ) {
49
+ const deps = {
50
+ ...createBootstrapperDeps(),
51
+ ...depsOverrides,
52
+ };
53
+ const crypto = createCryptoApi(cryptoOverrides);
54
+ const bootstrapper = new MatrixCryptoBootstrapper(
55
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
56
+ );
57
+ return { deps, crypto, bootstrapper };
58
+ }
59
+
60
+ async function runExplicitSecretStorageRepairScenario(firstError: string) {
61
+ const bootstrapCrossSigning = vi
62
+ .fn<() => Promise<void>>()
63
+ .mockRejectedValueOnce(new Error(firstError))
64
+ .mockResolvedValueOnce(undefined);
65
+ const { deps, crypto, bootstrapper } = createBootstrapperHarness({
66
+ bootstrapCrossSigning,
67
+ isCrossSigningReady: vi.fn(async () => true),
68
+ userHasCrossSigningKeys: vi.fn(async () => true),
69
+ getDeviceVerificationStatus: vi.fn(async () => createVerifiedDeviceStatus()),
70
+ });
71
+
72
+ await bootstrapper.bootstrap(crypto, {
73
+ strict: true,
74
+ allowSecretStorageRecreateWithoutRecoveryKey: true,
75
+ allowAutomaticCrossSigningReset: false,
76
+ });
77
+
78
+ return { deps, crypto, bootstrapCrossSigning };
79
+ }
80
+
81
+ function expectSecretStorageRepairRetry(
82
+ deps: ReturnType<typeof createBootstrapperDeps>,
83
+ crypto: MatrixCryptoBootstrapApi,
84
+ bootstrapCrossSigning: ReturnType<typeof vi.fn>,
85
+ ) {
86
+ expect(deps.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey).toHaveBeenCalledWith(crypto, {
87
+ allowSecretStorageRecreateWithoutRecoveryKey: true,
88
+ forceNewSecretStorage: true,
89
+ });
90
+ expect(bootstrapCrossSigning).toHaveBeenCalledTimes(2);
91
+ }
92
+
93
+ async function bootstrapWithVerificationRequestListener(overrides?: {
94
+ deps?: Partial<ReturnType<typeof createBootstrapperDeps>>;
95
+ crypto?: Partial<MatrixCryptoBootstrapApi>;
96
+ }) {
97
+ const listeners = new Map<string, (...args: unknown[]) => void>();
98
+ const { deps, bootstrapper, crypto } = createBootstrapperHarness(
99
+ {
100
+ getDeviceVerificationStatus: vi.fn(async () => ({
101
+ isVerified: () => true,
102
+ })),
103
+ on: vi.fn((eventName: string, listener: (...args: unknown[]) => void) => {
104
+ listeners.set(eventName, listener);
105
+ }),
106
+ ...overrides?.crypto,
107
+ },
108
+ overrides?.deps,
109
+ );
110
+
111
+ await bootstrapper.bootstrap(crypto);
112
+ const listener = Array.from(listeners.entries()).find(([eventName]) =>
113
+ eventName.toLowerCase().includes("verificationrequest"),
114
+ )?.[1];
115
+ expect(listener).toBeTypeOf("function");
116
+
117
+ return {
118
+ deps,
119
+ listener,
120
+ };
121
+ }
122
+
123
+ describe("MatrixCryptoBootstrapper", () => {
124
+ beforeEach(() => {
125
+ vi.restoreAllMocks();
126
+ });
127
+
128
+ it("bootstraps cross-signing/secret-storage and binds decrypt retry signals", async () => {
129
+ const deps = createBootstrapperDeps();
130
+ const crypto = createCryptoApi({
131
+ getDeviceVerificationStatus: vi.fn(async () => ({
132
+ isVerified: () => true,
133
+ })),
134
+ });
135
+ const bootstrapper = new MatrixCryptoBootstrapper(
136
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
137
+ );
138
+
139
+ await bootstrapper.bootstrap(crypto);
140
+
141
+ expect(crypto.bootstrapCrossSigning).toHaveBeenCalledWith(
142
+ expect.objectContaining({
143
+ authUploadDeviceSigningKeys: expect.any(Function),
144
+ }),
145
+ );
146
+ expect(deps.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey).toHaveBeenCalledWith(
147
+ crypto,
148
+ {
149
+ allowSecretStorageRecreateWithoutRecoveryKey: false,
150
+ },
151
+ );
152
+ expect(deps.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey).toHaveBeenCalledTimes(2);
153
+ expect(deps.decryptBridge.bindCryptoRetrySignals).toHaveBeenCalledWith(crypto);
154
+ });
155
+
156
+ it("forces new cross-signing keys only when readiness check still fails", async () => {
157
+ const deps = createBootstrapperDeps();
158
+ const bootstrapCrossSigning = vi.fn(async () => {});
159
+ const crypto = createCryptoApi({
160
+ bootstrapCrossSigning,
161
+ isCrossSigningReady: vi
162
+ .fn<() => Promise<boolean>>()
163
+ .mockResolvedValueOnce(false)
164
+ .mockResolvedValueOnce(true),
165
+ userHasCrossSigningKeys: vi
166
+ .fn<() => Promise<boolean>>()
167
+ .mockResolvedValueOnce(false)
168
+ .mockResolvedValueOnce(true),
169
+ getDeviceVerificationStatus: vi.fn(async () => ({
170
+ isVerified: () => true,
171
+ })),
172
+ });
173
+ const bootstrapper = new MatrixCryptoBootstrapper(
174
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
175
+ );
176
+
177
+ await bootstrapper.bootstrap(crypto);
178
+
179
+ expect(bootstrapCrossSigning).toHaveBeenCalledTimes(2);
180
+ expect(bootstrapCrossSigning).toHaveBeenNthCalledWith(
181
+ 1,
182
+ expect.objectContaining({
183
+ authUploadDeviceSigningKeys: expect.any(Function),
184
+ }),
185
+ );
186
+ expect(bootstrapCrossSigning).toHaveBeenNthCalledWith(
187
+ 2,
188
+ expect.objectContaining({
189
+ setupNewCrossSigning: true,
190
+ authUploadDeviceSigningKeys: expect.any(Function),
191
+ }),
192
+ );
193
+ });
194
+
195
+ it("does not auto-reset cross-signing when automatic reset is disabled", async () => {
196
+ const deps = createBootstrapperDeps();
197
+ const bootstrapCrossSigning = vi.fn(async () => {});
198
+ const crypto = createCryptoApi({
199
+ bootstrapCrossSigning,
200
+ isCrossSigningReady: vi.fn(async () => false),
201
+ userHasCrossSigningKeys: vi.fn(async () => false),
202
+ getDeviceVerificationStatus: vi.fn(async () => ({
203
+ isVerified: () => true,
204
+ localVerified: true,
205
+ crossSigningVerified: false,
206
+ signedByOwner: true,
207
+ })),
208
+ });
209
+ const bootstrapper = new MatrixCryptoBootstrapper(
210
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
211
+ );
212
+
213
+ await bootstrapper.bootstrap(crypto, {
214
+ allowAutomaticCrossSigningReset: false,
215
+ });
216
+
217
+ expect(bootstrapCrossSigning).toHaveBeenCalledTimes(1);
218
+ expect(bootstrapCrossSigning).toHaveBeenCalledWith(
219
+ expect.objectContaining({
220
+ authUploadDeviceSigningKeys: expect.any(Function),
221
+ }),
222
+ );
223
+ });
224
+
225
+ it("passes explicit secret-storage repair allowance only when requested", async () => {
226
+ const deps = createBootstrapperDeps();
227
+ const crypto = createCryptoApi({
228
+ getDeviceVerificationStatus: vi.fn(async () => ({
229
+ isVerified: () => true,
230
+ localVerified: true,
231
+ crossSigningVerified: true,
232
+ signedByOwner: true,
233
+ })),
234
+ });
235
+ const bootstrapper = new MatrixCryptoBootstrapper(
236
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
237
+ );
238
+
239
+ await bootstrapper.bootstrap(crypto, {
240
+ strict: true,
241
+ allowSecretStorageRecreateWithoutRecoveryKey: true,
242
+ });
243
+
244
+ expect(deps.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey).toHaveBeenCalledWith(
245
+ crypto,
246
+ {
247
+ allowSecretStorageRecreateWithoutRecoveryKey: true,
248
+ },
249
+ );
250
+ });
251
+
252
+ it("recreates secret storage and retries cross-signing when explicit bootstrap hits a stale server key", async () => {
253
+ const { deps, crypto, bootstrapCrossSigning } = await runExplicitSecretStorageRepairScenario(
254
+ "getSecretStorageKey callback returned falsey",
255
+ );
256
+
257
+ expectSecretStorageRepairRetry(deps, crypto, bootstrapCrossSigning);
258
+ expect(bootstrapCrossSigning).toHaveBeenNthCalledWith(
259
+ 1,
260
+ expect.objectContaining({
261
+ authUploadDeviceSigningKeys: expect.any(Function),
262
+ }),
263
+ );
264
+ expect(bootstrapCrossSigning).toHaveBeenNthCalledWith(
265
+ 2,
266
+ expect.objectContaining({
267
+ authUploadDeviceSigningKeys: expect.any(Function),
268
+ }),
269
+ );
270
+ });
271
+
272
+ it("recreates secret storage and retries cross-signing when explicit bootstrap hits bad MAC", async () => {
273
+ const { deps, crypto, bootstrapCrossSigning } = await runExplicitSecretStorageRepairScenario(
274
+ "Error decrypting secret m.cross_signing.master: bad MAC",
275
+ );
276
+
277
+ expectSecretStorageRepairRetry(deps, crypto, bootstrapCrossSigning);
278
+ });
279
+
280
+ it("fails in strict mode when cross-signing keys are still unpublished", async () => {
281
+ const deps = createBootstrapperDeps();
282
+ const crypto = createCryptoApi({
283
+ bootstrapCrossSigning: vi.fn(async () => {}),
284
+ isCrossSigningReady: vi.fn(async () => false),
285
+ userHasCrossSigningKeys: vi.fn(async () => false),
286
+ getDeviceVerificationStatus: vi.fn(async () => ({
287
+ isVerified: () => true,
288
+ })),
289
+ });
290
+ const bootstrapper = new MatrixCryptoBootstrapper(
291
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
292
+ );
293
+
294
+ await expect(bootstrapper.bootstrap(crypto, { strict: true })).rejects.toThrow(
295
+ "Cross-signing bootstrap finished but server keys are still not published",
296
+ );
297
+ });
298
+
299
+ it("uses password UIA fallback when null and dummy auth fail", async () => {
300
+ const bootstrapCrossSigning = vi.fn(async () => {});
301
+ const { bootstrapper, crypto } = createBootstrapperHarness({
302
+ bootstrapCrossSigning,
303
+ isCrossSigningReady: vi.fn(async () => true),
304
+ userHasCrossSigningKeys: vi.fn(async () => true),
305
+ getDeviceVerificationStatus: vi.fn(async () => ({
306
+ isVerified: () => true,
307
+ })),
308
+ });
309
+
310
+ await bootstrapper.bootstrap(crypto);
311
+
312
+ const bootstrapCrossSigningCalls = bootstrapCrossSigning.mock.calls as Array<
313
+ [
314
+ {
315
+ authUploadDeviceSigningKeys?: <T>(
316
+ makeRequest: (authData: Record<string, unknown> | null) => Promise<T>,
317
+ ) => Promise<T>;
318
+ }?,
319
+ ]
320
+ >;
321
+ const authUploadDeviceSigningKeys =
322
+ bootstrapCrossSigningCalls[0]?.[0]?.authUploadDeviceSigningKeys;
323
+ expect(authUploadDeviceSigningKeys).toBeTypeOf("function");
324
+
325
+ const seenAuthStages: Array<Record<string, unknown> | null> = [];
326
+ const result = await authUploadDeviceSigningKeys?.(async (authData) => {
327
+ seenAuthStages.push(authData);
328
+ if (authData === null) {
329
+ throw new Error("need auth");
330
+ }
331
+ if (authData.type === "m.login.dummy") {
332
+ throw new Error("dummy rejected");
333
+ }
334
+ if (authData.type === "m.login.password") {
335
+ return "ok";
336
+ }
337
+ throw new Error("unexpected auth stage");
338
+ });
339
+
340
+ expect(result).toBe("ok");
341
+ expect(seenAuthStages).toEqual([
342
+ null,
343
+ { type: "m.login.dummy" },
344
+ {
345
+ type: "m.login.password",
346
+ identifier: { type: "m.id.user", user: "@bot:example.org" },
347
+ password: "super-secret-password", // pragma: allowlist secret
348
+ },
349
+ ]);
350
+ });
351
+
352
+ it("resets cross-signing when first bootstrap attempt throws", async () => {
353
+ const bootstrapCrossSigning = vi
354
+ .fn<() => Promise<void>>()
355
+ .mockRejectedValueOnce(new Error("first attempt failed"))
356
+ .mockResolvedValueOnce(undefined);
357
+ const { bootstrapper, crypto } = createBootstrapperHarness({
358
+ bootstrapCrossSigning,
359
+ isCrossSigningReady: vi.fn(async () => true),
360
+ userHasCrossSigningKeys: vi.fn(async () => true),
361
+ getDeviceVerificationStatus: vi.fn(async () => ({
362
+ isVerified: () => true,
363
+ })),
364
+ });
365
+
366
+ await bootstrapper.bootstrap(crypto);
367
+
368
+ expect(bootstrapCrossSigning).toHaveBeenCalledTimes(2);
369
+ expect(bootstrapCrossSigning).toHaveBeenNthCalledWith(
370
+ 2,
371
+ expect.objectContaining({
372
+ setupNewCrossSigning: true,
373
+ authUploadDeviceSigningKeys: expect.any(Function),
374
+ }),
375
+ );
376
+ });
377
+
378
+ it("marks own device verified and cross-signs it when needed", async () => {
379
+ const deps = createBootstrapperDeps();
380
+ const setDeviceVerified = vi.fn(async () => {});
381
+ const crossSignDevice = vi.fn(async () => {});
382
+ const crypto = createCryptoApi({
383
+ getDeviceVerificationStatus: vi.fn(async () => ({
384
+ isVerified: () => false,
385
+ localVerified: false,
386
+ crossSigningVerified: false,
387
+ signedByOwner: false,
388
+ })),
389
+ setDeviceVerified,
390
+ crossSignDevice,
391
+ isCrossSigningReady: vi.fn(async () => true),
392
+ });
393
+ const bootstrapper = new MatrixCryptoBootstrapper(
394
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
395
+ );
396
+
397
+ await bootstrapper.bootstrap(crypto);
398
+
399
+ expect(setDeviceVerified).toHaveBeenCalledWith("@bot:example.org", "DEVICE123", true);
400
+ expect(crossSignDevice).toHaveBeenCalledWith("DEVICE123");
401
+ });
402
+
403
+ it("does not treat local-only trust as sufficient for own-device bootstrap", async () => {
404
+ const deps = createBootstrapperDeps();
405
+ const setDeviceVerified = vi.fn(async () => {});
406
+ const crossSignDevice = vi.fn(async () => {});
407
+ const getDeviceVerificationStatus = vi
408
+ .fn<
409
+ () => Promise<{
410
+ isVerified: () => boolean;
411
+ localVerified: boolean;
412
+ crossSigningVerified: boolean;
413
+ signedByOwner: boolean;
414
+ }>
415
+ >()
416
+ .mockResolvedValueOnce({
417
+ isVerified: () => true,
418
+ localVerified: true,
419
+ crossSigningVerified: false,
420
+ signedByOwner: false,
421
+ })
422
+ .mockResolvedValueOnce({
423
+ isVerified: () => true,
424
+ localVerified: true,
425
+ crossSigningVerified: true,
426
+ signedByOwner: true,
427
+ });
428
+ const crypto = createCryptoApi({
429
+ getDeviceVerificationStatus,
430
+ setDeviceVerified,
431
+ crossSignDevice,
432
+ isCrossSigningReady: vi.fn(async () => true),
433
+ });
434
+ const bootstrapper = new MatrixCryptoBootstrapper(
435
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
436
+ );
437
+
438
+ await bootstrapper.bootstrap(crypto);
439
+
440
+ expect(setDeviceVerified).toHaveBeenCalledWith("@bot:example.org", "DEVICE123", true);
441
+ expect(crossSignDevice).toHaveBeenCalledWith("DEVICE123");
442
+ expect(getDeviceVerificationStatus).toHaveBeenCalledTimes(2);
443
+ });
444
+
445
+ it("tracks incoming verification requests from other users", async () => {
446
+ const { deps, listener } = await bootstrapWithVerificationRequestListener();
447
+ const verificationRequest = {
448
+ otherUserId: "@alice:example.org",
449
+ isSelfVerification: false,
450
+ initiatedByMe: false,
451
+ accept: vi.fn(async () => {}),
452
+ };
453
+ listener?.(verificationRequest);
454
+
455
+ expect(deps.verificationManager.trackVerificationRequest).toHaveBeenCalledWith(
456
+ verificationRequest,
457
+ );
458
+ expect(verificationRequest.accept).not.toHaveBeenCalled();
459
+ });
460
+
461
+ it("does not touch request state when tracking summary throws", async () => {
462
+ const { listener } = await bootstrapWithVerificationRequestListener({
463
+ deps: {
464
+ verificationManager: {
465
+ trackVerificationRequest: vi.fn(() => {
466
+ throw new Error("summary failure");
467
+ }),
468
+ },
469
+ },
470
+ crypto: {
471
+ getDeviceVerificationStatus: vi.fn(async () => ({
472
+ isVerified: () => true,
473
+ })),
474
+ },
475
+ });
476
+
477
+ const verificationRequest = {
478
+ otherUserId: "@alice:example.org",
479
+ isSelfVerification: false,
480
+ initiatedByMe: false,
481
+ accept: vi.fn(async () => {}),
482
+ };
483
+ listener?.(verificationRequest);
484
+
485
+ expect(verificationRequest.accept).not.toHaveBeenCalled();
486
+ });
487
+
488
+ it("registers verification listeners only once across repeated bootstrap calls", async () => {
489
+ const deps = createBootstrapperDeps();
490
+ const crypto = createCryptoApi({
491
+ getDeviceVerificationStatus: vi.fn(async () => ({
492
+ isVerified: () => true,
493
+ })),
494
+ });
495
+ const bootstrapper = new MatrixCryptoBootstrapper(
496
+ deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
497
+ );
498
+
499
+ await bootstrapper.bootstrap(crypto);
500
+ await bootstrapper.bootstrap(crypto);
501
+
502
+ expect(crypto.on).toHaveBeenCalledTimes(1);
503
+ expect(deps.decryptBridge.bindCryptoRetrySignals).toHaveBeenCalledTimes(1);
504
+ });
505
+ });