@capxul/sdk 0.0.0-rc-2cdddbb91

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.
@@ -0,0 +1,1159 @@
1
+ import { D as convexCallErrorFromCapxul, Di as InMemoryAuthCacheAdapter, Et as deriveCapxulSafeAddress, Fi as toEmail, Fr as toChainId, Ft as CONFIGURED_MONEY_ASSETS, Ii as toJwtToken, Ir as toWeiAmount, Li as toSessionToken, Lr as absurd, Mi as toEpochMs, Ni as toEpochSeconds, Pi as toAuthUserId, Pr as toAddress, Qn as toOrgId, Ri as Errors, Si as authClientPortFromPromiseAdapter, X as assetAmountFromRaw, _ as accountFromAccountPositions, bi as toAppId, ci as readClockNow, f as smartAccountErrorFromCapxul, fn as toCountryCode, fr as toAccountId, gn as toHandle, j as wireChainId, ji as toDurationMs, oi as ClockError, pn as toKycTier, si as ClockPortTag, tt as toRaw, vn as validateHandle, w as accountReadErrorFromCapxul, xi as toPublishableKey, yi as toAllowedOrigin, yn as redactTelemetryEvent, zi as CapxulError } from "../access-CNha7G9T.mjs";
2
+ import { a as cloneTelemetryEvent, c as identityErrorFromCapxul, l as bootstrapErrorFromCapxul, n as createCapxulClientWithSignerControls, o as cloneTelemetryProps, r as assembleCapxulClient, s as IdentityPortTag } from "../production-Bcjowmxk.mjs";
3
+ import { Effect, Layer, Result, Semaphore } from "effect";
4
+ import { getFunctionName } from "convex/server";
5
+ //#region src/testing/production-client.ts
6
+ /** Create the one production client with per-client controls at its signing boundaries. */
7
+ function createCapxulProductionTestClient(input, controls) {
8
+ return createCapxulClientWithSignerControls(input, controls);
9
+ }
10
+ //#endregion
11
+ //#region src/testing/signer-faults.ts
12
+ const FAULT_CODES = {
13
+ "missing-signer": "MISSING_SIGNER",
14
+ "user-rejected": 4001,
15
+ unexpected: void 0
16
+ };
17
+ /** The value a controlled fault throws. Named so a test can assert its origin. */
18
+ var ControlledSignerFault = class extends Error {
19
+ kind;
20
+ /** Provider code the SDK classifiers read; absent for `unexpected`. */
21
+ code;
22
+ constructor(kind) {
23
+ super(`controlled signer fault (${kind})`);
24
+ this.name = "ControlledSignerFault";
25
+ this.kind = kind;
26
+ this.code = FAULT_CODES[kind];
27
+ }
28
+ };
29
+ /** Build one fault value without the harness, for a caller that writes its own control. */
30
+ function signerFaultCause(kind) {
31
+ return new ControlledSignerFault(kind);
32
+ }
33
+ /**
34
+ * Arm one-shot faults on the production embedded signer.
35
+ *
36
+ * ```ts
37
+ * const seam = armSignerFaults({ signOnce: "user-rejected" });
38
+ * const client = await createCapxulProductionTestClient(input, seam.controls);
39
+ * const result = await client.org(orgId).permissions.create(command);
40
+ * // result.error.code === "SIGNER_REJECTED"; nothing was submitted.
41
+ * // seam.records() names the boundary that refused.
42
+ * ```
43
+ *
44
+ * An empty plan installs no controls, which composes the ordinary client.
45
+ */
46
+ function armSignerFaults(plan = {}) {
47
+ const records = [];
48
+ let pendingFault = plan.signOnce;
49
+ let pendingReset = plan.resetSessionBeforeNextSign === true;
50
+ let resetCalls = 0;
51
+ let embeddedCalls = 0;
52
+ function record(entry) {
53
+ records.push({
54
+ ...entry,
55
+ atMs: Date.now()
56
+ });
57
+ }
58
+ const resetThenSign = async (hash, signer) => {
59
+ if (pendingReset) {
60
+ pendingReset = false;
61
+ resetCalls += 1;
62
+ if (signer.resetSession === void 0) throw new Error("armSignerFaults: resetSessionBeforeNextSign needs the embedded browser signer; this client composed a signer with no session");
63
+ signer.resetSession();
64
+ record({
65
+ boundary: "reset-session",
66
+ attempt: resetCalls,
67
+ hash
68
+ });
69
+ }
70
+ return signer.signUserOpHash(hash);
71
+ };
72
+ const faultThenDelegate = async (hash, delegate) => {
73
+ embeddedCalls += 1;
74
+ const fault = pendingFault;
75
+ if (fault === void 0) {
76
+ record({
77
+ boundary: "embedded-sign",
78
+ attempt: embeddedCalls,
79
+ hash
80
+ });
81
+ return delegate(hash);
82
+ }
83
+ pendingFault = void 0;
84
+ record({
85
+ boundary: "embedded-sign",
86
+ attempt: embeddedCalls,
87
+ hash,
88
+ fault
89
+ });
90
+ throw signerFaultCause(fault);
91
+ };
92
+ return {
93
+ controls: {
94
+ ...plan.resetSessionBeforeNextSign === true ? { signUserOpHash: resetThenSign } : {},
95
+ ...plan.signOnce === void 0 ? {} : { signEmbeddedUserOpHash: faultThenDelegate }
96
+ },
97
+ records: () => records.slice(),
98
+ spent: () => pendingFault === void 0 && !pendingReset
99
+ };
100
+ }
101
+ //#endregion
102
+ //#region src/testing/organization-setup.ts
103
+ /** Exact backend setup shape for SDK tests. */
104
+ function organizationSetupFixture(orgId, status = "ready", overrides = {}) {
105
+ return {
106
+ orgId: toOrgId(orgId),
107
+ status,
108
+ completedAt: status === "ready" || status === "rechecking" ? 100 : null,
109
+ lastCheckedAt: null,
110
+ nextCheckAt: null,
111
+ issue: null,
112
+ lastSendIssue: null,
113
+ recovery: status === "awaitingAuthorization" ? "authorize" : "none",
114
+ ...overrides
115
+ };
116
+ }
117
+ /** In-memory setup transport for packed consumer tests. */
118
+ function createTestOrganizationSetup() {
119
+ const rows = /* @__PURE__ */ new Map();
120
+ const handles = /* @__PURE__ */ new Map();
121
+ const listeners = /* @__PURE__ */ new Map();
122
+ const set = (setup) => {
123
+ rows.set(String(setup.orgId), setup);
124
+ for (const listener of listeners.get(String(setup.orgId)) ?? []) listener({
125
+ status: "value",
126
+ value: setup
127
+ });
128
+ };
129
+ const get = async (orgId) => {
130
+ const value = rows.get(String(orgId));
131
+ return value === void 0 ? {
132
+ ok: false,
133
+ error: Errors.invalidInput("orgId", "Organization setup was not found")
134
+ } : {
135
+ ok: true,
136
+ value
137
+ };
138
+ };
139
+ return {
140
+ set,
141
+ ops: {
142
+ listMine: async (input = {}) => {
143
+ const all = [...rows.values()].filter((row) => row.completedAt === null);
144
+ const offset = input.cursor === void 0 ? 0 : Number(input.cursor);
145
+ const limit = input.limit ?? 50;
146
+ if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 50) return {
147
+ ok: false,
148
+ error: Errors.invalidInput("organizationSetupPage", "Invalid cursor or limit")
149
+ };
150
+ const page = all.slice(offset, offset + limit);
151
+ const next = offset + page.length;
152
+ return {
153
+ ok: true,
154
+ value: {
155
+ items: page,
156
+ nextCursor: next < all.length ? String(next) : null
157
+ }
158
+ };
159
+ },
160
+ begin: async (draft) => {
161
+ const orgId = handles.get(draft.handle) ?? toOrgId(`org_test_${draft.handle}`);
162
+ handles.set(draft.handle, orgId);
163
+ const setup = rows.get(String(orgId)) ?? organizationSetupFixture(orgId, "awaitingAuthorization");
164
+ set(setup);
165
+ return {
166
+ ok: true,
167
+ value: {
168
+ orgId,
169
+ setup
170
+ }
171
+ };
172
+ },
173
+ get,
174
+ resume: get,
175
+ authorize: async (orgId) => {
176
+ const current = await get(orgId);
177
+ if (!current.ok || current.value.status !== "awaitingAuthorization") return current;
178
+ const setup = organizationSetupFixture(orgId, "processing", { nextCheckAt: 1 });
179
+ set(setup);
180
+ return {
181
+ ok: true,
182
+ value: setup
183
+ };
184
+ },
185
+ subscribe: async (orgId, listener) => {
186
+ const key = String(orgId);
187
+ const group = listeners.get(key) ?? /* @__PURE__ */ new Set();
188
+ listeners.set(key, group);
189
+ group.add(listener);
190
+ listener({ status: "loading" });
191
+ const current = rows.get(key);
192
+ if (current !== void 0) listener({
193
+ status: "value",
194
+ value: current
195
+ });
196
+ let active = true;
197
+ return {
198
+ ok: true,
199
+ value: () => {
200
+ if (!active) return;
201
+ active = false;
202
+ group.delete(listener);
203
+ }
204
+ };
205
+ },
206
+ invalidateSubscriptions: () => {
207
+ for (const group of listeners.values()) group.clear();
208
+ }
209
+ }
210
+ };
211
+ }
212
+ //#endregion
213
+ //#region src/testing/account/InMemoryAccountReadAdapter.ts
214
+ function fixtureAsset(symbol) {
215
+ const asset = CONFIGURED_MONEY_ASSETS.find((row) => row.symbol === symbol);
216
+ if (asset === void 0) throw new Error(`no configured settlement asset ${symbol}`);
217
+ return asset;
218
+ }
219
+ var InMemoryAccountReadAdapter = class {
220
+ #deps;
221
+ #rawBalance;
222
+ constructor(deps = {}) {
223
+ this.#deps = deps;
224
+ this.#rawBalance = deps.rawBalance ?? "0";
225
+ }
226
+ #asset() {
227
+ return fixtureAsset(this.#deps.assetSymbol ?? "USDC");
228
+ }
229
+ readBalance(_input) {
230
+ const scriptedFailure = this.#deps.failures?.readBalance;
231
+ if (scriptedFailure !== void 0) return Effect.fail(accountReadErrorFromCapxul("readBalance", scriptedFailure));
232
+ if (this.#deps.account !== void 0) return Effect.succeed(cloneAccount(this.#deps.account));
233
+ const asset = this.#asset();
234
+ const balance = assetAmountFromRaw(toWeiAmount(this.#rawBalance), asset).value;
235
+ return Effect.succeed(accountFromAccountPositions(toAccountId(this.#deps.accountId ?? "account_01TEST000000000000000000"), [{
236
+ asset: asset.assetId,
237
+ symbol: asset.symbol,
238
+ decimals: asset.decimals,
239
+ balance,
240
+ available: balance
241
+ }]));
242
+ }
243
+ fundFromFaucet(input) {
244
+ const scriptedFailure = this.#deps.failures?.fundFromFaucet;
245
+ if (scriptedFailure !== void 0) return Effect.fail(accountReadErrorFromCapxul("fundFromFaucet", scriptedFailure));
246
+ const asset = this.#asset();
247
+ if (input.amount.asset !== asset.assetId) return Effect.fail(accountReadErrorFromCapxul("fundFromFaucet", Errors.invalidInput("amount.asset", "must match adapter config")));
248
+ const minted = toRaw(input.amount, asset);
249
+ this.#rawBalance = (BigInt(this.#rawBalance) + minted).toString();
250
+ return Effect.succeed({ txHash: "0x" + "f".repeat(64) });
251
+ }
252
+ };
253
+ function cloneAccount(account) {
254
+ return {
255
+ id: account.id,
256
+ balances: account.balances.map((position) => ({ ...position })),
257
+ balance: account.balance === null ? null : { ...account.balance },
258
+ available: account.available === null ? null : { ...account.available }
259
+ };
260
+ }
261
+ //#endregion
262
+ //#region src/testing/clock/sleep-queue.ts
263
+ var SleepQueueClock = class {
264
+ currentTime = 0;
265
+ pending = [];
266
+ seqCounter = 0;
267
+ now = Effect.sync(() => toEpochMs(this.currentTime));
268
+ sleep(duration) {
269
+ return Effect.promise(() => new Promise((resolve) => {
270
+ this.enqueue({
271
+ deadline: this.currentTime + duration,
272
+ resolve,
273
+ seq: this.seqCounter++
274
+ });
275
+ }));
276
+ }
277
+ /** Move time forward by `ms`, releasing every sleep that falls due. */
278
+ advance(ms) {
279
+ return Effect.runPromise(this.advanceEffect(toDurationMs(ms)));
280
+ }
281
+ /** This clock as the cross-adapter conformance harness drives it. */
282
+ underTest() {
283
+ return {
284
+ clock: this,
285
+ advance: (ms) => this.advance(ms)
286
+ };
287
+ }
288
+ enqueue(entry) {
289
+ this.pending.push(entry);
290
+ }
291
+ };
292
+ /** The clock in context when it is a `kind`; otherwise a ClockError naming the layer `helper` needs. */
293
+ function requireClock(kind, helper, layer) {
294
+ return Effect.gen(function* () {
295
+ const clock = yield* ClockPortTag;
296
+ if (clock instanceof kind) return clock;
297
+ return yield* new ClockError({
298
+ operation: "sleep",
299
+ cause: /* @__PURE__ */ new Error(`${helper} requires ${layer}`)
300
+ });
301
+ });
302
+ }
303
+ //#endregion
304
+ //#region src/testing/clock/ManualClockAdapter.ts
305
+ /**
306
+ * Event-discipline test clock. Time moves only when a release primitive is
307
+ * invoked. `tickOne()` releases EXACTLY ONE pending sleep (the earliest
308
+ * deadline, FIFO at ties) and snaps `now()` to that deadline. `advance(ms)`
309
+ * composes repeated `tickOne()` calls over the resulting window — used to
310
+ * keep the cross-adapter conformance harness uniform with the distance model
311
+ * of `VirtualClockAdapter`. See `packages/sdk/docs/architecture.md` and the
312
+ * slice spec `docs/slices/clock-adapters.md`.
313
+ */
314
+ var ManualClockAdapter = class ManualClockAdapter extends SleepQueueClock {
315
+ constructor(deps) {
316
+ super();
317
+ }
318
+ /**
319
+ * Release exactly one pending sleep — the earliest deadline, FIFO at ties.
320
+ * Snaps `now()` forward to that sleep's deadline. No-op if the queue is
321
+ * empty (preserves the empty-tick safety property).
322
+ */
323
+ tickOne() {
324
+ return Effect.runPromise(this.tickOneEffect());
325
+ }
326
+ tickOneEffect() {
327
+ return Effect.gen({ self: this }, function* () {
328
+ if (this.pending.length === 0) return;
329
+ const next = this.pending.splice(this.findEarliestIndex(), 1)[0];
330
+ if (next.deadline > this.currentTime) this.currentTime = next.deadline;
331
+ next.resolve();
332
+ yield* Effect.yieldNow;
333
+ });
334
+ }
335
+ /**
336
+ * Releases every sleep whose deadline falls inside
337
+ * `[currentTime, currentTime + ms]` via repeated `tickOne()`, then snaps
338
+ * `now()` to `currentTime + ms`. The composition is the seam that lets the
339
+ * harness drive Manual and Virtual identically.
340
+ */
341
+ advanceEffect(duration) {
342
+ return Effect.gen({ self: this }, function* () {
343
+ yield* Effect.yieldNow;
344
+ const target = this.currentTime + duration;
345
+ while (this.pending.length > 0 && this.earliestDeadline() <= target) yield* this.tickOneEffect();
346
+ this.currentTime = target;
347
+ });
348
+ }
349
+ findEarliestIndex() {
350
+ let bestIndex = 0;
351
+ let best = this.pending[0];
352
+ for (let i = 1; i < this.pending.length; i++) {
353
+ const candidate = this.pending[i];
354
+ if (candidate.deadline < best.deadline || candidate.deadline === best.deadline && candidate.seq < best.seq) {
355
+ best = candidate;
356
+ bestIndex = i;
357
+ }
358
+ }
359
+ return bestIndex;
360
+ }
361
+ earliestDeadline() {
362
+ return this.pending[this.findEarliestIndex()].deadline;
363
+ }
364
+ static forConformance() {
365
+ return new ManualClockAdapter({}).underTest();
366
+ }
367
+ };
368
+ Layer.sync(ClockPortTag, () => new ManualClockAdapter({}));
369
+ requireClock(ManualClockAdapter, "tickManualClockOnce", "ManualClockLayer").pipe(Effect.flatMap((clock) => clock.tickOneEffect()));
370
+ //#endregion
371
+ //#region src/testing/auth-client/InMemoryAuthClientAdapter.ts
372
+ const DEFAULT_OTP_TTL_MS = toDurationMs(3e5);
373
+ const DEFAULT_SESSION_TTL_MS = toDurationMs(864e5);
374
+ const DEFAULT_JWT_TTL_MS = toDurationMs(9e5);
375
+ /**
376
+ * Hermetic OTP + session state machine implementing `AuthClientPort`. Wires
377
+ * through `AuthCachePort` for persistence (clause A11) and `ClockPort`
378
+ * for TTL + expiry math. OTPs are deterministic per-email counters (`"000000"`,
379
+ * `"000001"`, …) so test assertions remain stable. Session tokens are
380
+ * deterministic per-adapter counters (`"tok_0"`, `"tok_1"`, …). Returning users
381
+ * (same email post-signOut) get the same `AuthUserId`.
382
+ *
383
+ * Never throws — every failure path returns `{ ok: false, error: CapxulError }`.
384
+ * Honors `options.signal` via a pre-check (clause A10): an aborted controller
385
+ * before method entry yields `{ ok: false, error: Errors.cancelled({ operation }) }`
386
+ * without any state change.
387
+ */
388
+ var InMemoryAuthClientAdapter = class {
389
+ #pendingOtps = /* @__PURE__ */ new Map();
390
+ #otpCountersByEmail = /* @__PURE__ */ new Map();
391
+ #authUserIdByEmail = /* @__PURE__ */ new Map();
392
+ #allocatedAuthUserIds = /* @__PURE__ */ new Set();
393
+ #authCache;
394
+ #clock;
395
+ #otpTtlMs;
396
+ #failures;
397
+ #authUserIdCounter = 0;
398
+ #tokenCounter = 0;
399
+ #jwtCounter = 0;
400
+ constructor(deps) {
401
+ this.#authCache = deps.authCache;
402
+ this.#clock = deps.clock;
403
+ this.#otpTtlMs = deps.otpTtlMs ?? DEFAULT_OTP_TTL_MS;
404
+ this.#failures = deps.failures ?? {};
405
+ }
406
+ /** Test affordance: peek the OTP for an email, simulating "user reads email." */
407
+ peekOtpForTesting(email) {
408
+ return this.#pendingOtps.get(email)?.otp;
409
+ }
410
+ /** Test affordance: make a seeded identity own the session minted for its email. */
411
+ bindAuthUserIdForTesting(email, authUserId) {
412
+ this.#authUserIdByEmail.set(email, authUserId);
413
+ this.#allocatedAuthUserIds.add(authUserId);
414
+ }
415
+ async canSendOtp(input, options) {
416
+ if (options?.signal?.aborted) return {
417
+ ok: false,
418
+ error: Errors.cancelled({ operation: "canSendOtp" })
419
+ };
420
+ if (this.#failures.canSendOtp !== void 0) return {
421
+ ok: false,
422
+ error: this.#failures.canSendOtp
423
+ };
424
+ const pending = this.#pendingOtps.get(input.email);
425
+ if (pending === void 0) return {
426
+ ok: true,
427
+ value: {
428
+ allowed: true,
429
+ cooldownMs: toDurationMs(0)
430
+ }
431
+ };
432
+ const nowResult = await readClockNow(this.#clock);
433
+ if (!nowResult.ok) return nowResult;
434
+ const elapsedMs = nowResult.value - pending.issuedAt;
435
+ const cooldownMs = Math.max(0, this.#otpTtlMs - elapsedMs);
436
+ if (cooldownMs === 0) {
437
+ this.#pendingOtps.delete(input.email);
438
+ return {
439
+ ok: true,
440
+ value: {
441
+ allowed: true,
442
+ cooldownMs: toDurationMs(0)
443
+ }
444
+ };
445
+ }
446
+ return {
447
+ ok: true,
448
+ value: {
449
+ allowed: false,
450
+ cooldownMs: toDurationMs(cooldownMs)
451
+ }
452
+ };
453
+ }
454
+ async sendOtp(input, options) {
455
+ if (options?.signal?.aborted) return {
456
+ ok: false,
457
+ error: Errors.cancelled({ operation: "sendOtp" })
458
+ };
459
+ if (this.#failures.sendOtp !== void 0) return {
460
+ ok: false,
461
+ error: this.#failures.sendOtp
462
+ };
463
+ const issuedAt = await readClockNow(this.#clock);
464
+ if (!issuedAt.ok) return issuedAt;
465
+ const counter = this.#otpCountersByEmail.get(input.email) ?? 0;
466
+ this.#otpCountersByEmail.set(input.email, counter + 1);
467
+ const otp = counter.toString().padStart(6, "0");
468
+ this.#pendingOtps.set(input.email, {
469
+ otp,
470
+ issuedAt: issuedAt.value
471
+ });
472
+ return {
473
+ ok: true,
474
+ value: void 0
475
+ };
476
+ }
477
+ async verifyOtp(input, options) {
478
+ if (options?.signal?.aborted) return {
479
+ ok: false,
480
+ error: Errors.cancelled({ operation: "verifyOtp" })
481
+ };
482
+ if (this.#failures.verifyOtp !== void 0) return {
483
+ ok: false,
484
+ error: this.#failures.verifyOtp
485
+ };
486
+ const pending = this.#pendingOtps.get(input.email);
487
+ if (pending === void 0) return {
488
+ ok: false,
489
+ error: Errors.invalidInput("email", "no OTP issued for this email")
490
+ };
491
+ const nowResult = await readClockNow(this.#clock);
492
+ if (!nowResult.ok) return nowResult;
493
+ const now = nowResult.value;
494
+ if (now - pending.issuedAt > this.#otpTtlMs) {
495
+ this.#pendingOtps.delete(input.email);
496
+ return {
497
+ ok: false,
498
+ error: Errors.otpExpired()
499
+ };
500
+ }
501
+ if (input.otp !== pending.otp) return {
502
+ ok: false,
503
+ error: Errors.invalidInput("otp", "incorrect OTP")
504
+ };
505
+ const authUserId = this.#getOrIssueAuthUserId(input.email);
506
+ const token = toSessionToken(`tok_${this.#tokenCounter++}`);
507
+ const expiresAt = toEpochMs(now + DEFAULT_SESSION_TTL_MS);
508
+ const session = {
509
+ authUserId,
510
+ email: input.email,
511
+ token,
512
+ expiresAt
513
+ };
514
+ const cacheWrite = await runAuthCacheEffect(this.#authCache.setSession(session), "setSession");
515
+ if (!cacheWrite.ok) return cacheWrite;
516
+ this.#pendingOtps.delete(input.email);
517
+ return {
518
+ ok: true,
519
+ value: session
520
+ };
521
+ }
522
+ async getSession(options) {
523
+ if (options?.signal?.aborted) return {
524
+ ok: false,
525
+ error: Errors.cancelled({ operation: "getSession" })
526
+ };
527
+ if (this.#failures.getSession !== void 0) return {
528
+ ok: false,
529
+ error: this.#failures.getSession
530
+ };
531
+ const cacheRead = await runAuthCacheEffect(this.#authCache.getSession, "getSession");
532
+ if (!cacheRead.ok) return cacheRead;
533
+ const persisted = cacheRead.value;
534
+ if (persisted === null) return {
535
+ ok: true,
536
+ value: null
537
+ };
538
+ const nowResult = await readClockNow(this.#clock);
539
+ if (!nowResult.ok) return nowResult;
540
+ const now = nowResult.value;
541
+ if (persisted.expiresAt <= now) {
542
+ const cacheClear = await runAuthCacheEffect(this.#authCache.clearSession, "clearSession");
543
+ if (!cacheClear.ok) return cacheClear;
544
+ return {
545
+ ok: true,
546
+ value: null
547
+ };
548
+ }
549
+ return {
550
+ ok: true,
551
+ value: persisted
552
+ };
553
+ }
554
+ async signOut(options) {
555
+ if (options?.signal?.aborted) return {
556
+ ok: false,
557
+ error: Errors.cancelled({ operation: "signOut" })
558
+ };
559
+ if (this.#failures.signOut !== void 0) return {
560
+ ok: false,
561
+ error: this.#failures.signOut
562
+ };
563
+ return runAuthCacheEffect(this.#authCache.clearSession, "clearSession");
564
+ }
565
+ async getConvexJwt(options) {
566
+ if (options?.signal?.aborted) return {
567
+ ok: false,
568
+ error: Errors.cancelled({ operation: "getConvexJwt" })
569
+ };
570
+ if (this.#failures.getConvexJwt !== void 0) return {
571
+ ok: false,
572
+ error: this.#failures.getConvexJwt
573
+ };
574
+ if (options?.forceRefresh !== true) {
575
+ const cacheRead = await runAuthCacheEffect(this.#authCache.getJwt, "getJwt");
576
+ if (!cacheRead.ok) return cacheRead;
577
+ if (cacheRead.value !== null) return {
578
+ ok: true,
579
+ value: cacheRead.value
580
+ };
581
+ }
582
+ const now = await readClockNow(this.#clock);
583
+ if (!now.ok) return now;
584
+ const jwt = {
585
+ token: toJwtToken(`in-memory-jwt-${this.#jwtCounter++}`),
586
+ expEpochSeconds: toEpochSeconds(Math.floor(now.value / 1e3 + DEFAULT_JWT_TTL_MS / 1e3))
587
+ };
588
+ const cacheWrite = await runAuthCacheEffect(this.#authCache.setJwt(jwt), "setJwt");
589
+ if (!cacheWrite.ok) return cacheWrite;
590
+ return {
591
+ ok: true,
592
+ value: jwt
593
+ };
594
+ }
595
+ #getOrIssueAuthUserId(email) {
596
+ const existing = this.#authUserIdByEmail.get(email);
597
+ if (existing !== void 0) return existing;
598
+ let fresh;
599
+ do
600
+ fresh = toAuthUserId(`user_${this.#authUserIdCounter++}`);
601
+ while (this.#allocatedAuthUserIds.has(fresh));
602
+ this.#authUserIdByEmail.set(email, fresh);
603
+ this.#allocatedAuthUserIds.add(fresh);
604
+ return fresh;
605
+ }
606
+ };
607
+ async function runAuthCacheEffect(effect, operation) {
608
+ const result = await Effect.runPromise(Effect.result(effect));
609
+ if (Result.isFailure(result)) return {
610
+ ok: false,
611
+ error: Errors.providerError("auth-cache", operation, result.failure)
612
+ };
613
+ return {
614
+ ok: true,
615
+ value: result.success
616
+ };
617
+ }
618
+ //#endregion
619
+ //#region src/testing/bootstrap/BootstrapStubAdapter.ts
620
+ var BootstrapStubAdapter = class {
621
+ #script;
622
+ constructor(deps) {
623
+ this.#script = deps.script;
624
+ }
625
+ resolve(input) {
626
+ const key = bootstrapStubScriptKey(input);
627
+ const failure = this.#script.failures?.get(key);
628
+ if (failure !== void 0) return fail$2(kindFromCapxulError(failure), failure);
629
+ const resolution = this.#script.resolutions?.get(key);
630
+ if (resolution !== void 0) return Effect.succeed(resolution);
631
+ const defaultFailure = this.#script.defaultFailure ?? Errors.notAuthenticated();
632
+ return fail$2(kindFromCapxulError(defaultFailure), defaultFailure);
633
+ }
634
+ };
635
+ function bootstrapStubScriptKey(input) {
636
+ return `${String(input.publishableKey)}::${input.origin ?? ""}`;
637
+ }
638
+ function kindFromCapxulError(error) {
639
+ if (error.code === "NOT_AUTHENTICATED") return "notAuthenticated";
640
+ if (error.code === "NETWORK_ERROR") return "network";
641
+ if (error.code === "RATE_LIMITED") return "rateLimited";
642
+ if (error.code === "INVALID_INPUT") return "invalidInput";
643
+ return "provider";
644
+ }
645
+ function fail$2(kind, error) {
646
+ return Effect.fail(bootstrapErrorFromCapxul(kind, error));
647
+ }
648
+ //#endregion
649
+ //#region src/testing/convex-call/subscribers.ts
650
+ /**
651
+ * Register `subscriber` under `path` and return its unsubscribe, which removes
652
+ * it once (a second call does nothing) and drops a path left with no one.
653
+ */
654
+ function subscribeAt(subscribers, path, subscriber) {
655
+ let subs = subscribers.get(path);
656
+ if (subs === void 0) {
657
+ subs = /* @__PURE__ */ new Set();
658
+ subscribers.set(path, subs);
659
+ }
660
+ subs.add(subscriber);
661
+ let active = true;
662
+ return () => {
663
+ if (!active) return;
664
+ active = false;
665
+ const live = subscribers.get(path);
666
+ if (live === void 0) return;
667
+ live.delete(subscriber);
668
+ if (live.size === 0) subscribers.delete(path);
669
+ };
670
+ }
671
+ //#endregion
672
+ //#region src/testing/convex-call/wire-reply.ts
673
+ /**
674
+ * The Convex wire, as the in-memory test doubles stand in for it. A scripted or
675
+ * registered value crosses where a real reply would, and the caller's function
676
+ * reference names its type, exactly as `ConvexClient` types a reply it cannot
677
+ * check. These are the only places the doubles make that claim.
678
+ */
679
+ function wireReply(_fn, value) {
680
+ return value;
681
+ }
682
+ /** A scripted snapshot for a subscription on `fn`: only an `ok` value carries the reply type. */
683
+ function wireSnapshot(fn, snapshot) {
684
+ return snapshot.status === "ok" ? {
685
+ status: "ok",
686
+ value: wireReply(fn, snapshot.value)
687
+ } : snapshot;
688
+ }
689
+ //#endregion
690
+ //#region src/testing/convex-call/ConvexCallStubAdapter.ts
691
+ var ConvexCallStubAdapter = class {
692
+ #script;
693
+ #subscribers = /* @__PURE__ */ new Map();
694
+ constructor(deps) {
695
+ this.#script = deps.script;
696
+ }
697
+ query(fn, _args) {
698
+ return this.#resolve(fn);
699
+ }
700
+ mutation(fn, _args) {
701
+ return this.#resolve(fn);
702
+ }
703
+ action(fn, _args) {
704
+ return this.#resolve(fn);
705
+ }
706
+ subscribe(fn, _args, callback) {
707
+ return Effect.sync(() => {
708
+ const path = getFunctionName(fn);
709
+ const cb = (snapshot) => callback(wireSnapshot(fn, snapshot));
710
+ cb({ status: "loading" });
711
+ const sequence = this.#script.subscriptions?.get(path);
712
+ const subscriber = {
713
+ callback: cb,
714
+ cursor: 0
715
+ };
716
+ const unsubscribe = subscribeAt(this.#subscribers, path, subscriber);
717
+ if (sequence !== void 0 && sequence.length > 0) {
718
+ cb(sequence[0]);
719
+ subscriber.cursor = 1;
720
+ }
721
+ return unsubscribe;
722
+ });
723
+ }
724
+ advanceSubscription(path) {
725
+ const sequence = this.#script.subscriptions?.get(path);
726
+ if (sequence === void 0) return;
727
+ const subs = this.#subscribers.get(path);
728
+ if (subs === void 0) return;
729
+ for (const sub of subs) {
730
+ if (sub.cursor >= sequence.length) continue;
731
+ const next = sequence[sub.cursor];
732
+ sub.cursor += 1;
733
+ sub.callback(next);
734
+ }
735
+ }
736
+ #resolve(fn) {
737
+ const path = getFunctionName(fn);
738
+ const failure = this.#script.failures?.get(path);
739
+ if (failure !== void 0) return Effect.fail(mapFailure(path, failure));
740
+ if (this.#script.responses?.has(path)) return Effect.succeed(wireReply(fn, this.#script.responses.get(path)));
741
+ return Effect.fail(convexCallErrorFromCapxul(path, Errors.providerError("convex", path, "unknown function")));
742
+ }
743
+ };
744
+ function mapFailure(operation, failure) {
745
+ if (failure instanceof CapxulError) return convexCallErrorFromCapxul(operation, failure);
746
+ if (failure instanceof Error) return convexCallErrorFromCapxul(operation, Errors.providerError("convex", operation, failure));
747
+ return convexCallErrorFromCapxul(operation, Errors.providerError("convex", operation, new Error(String(failure))));
748
+ }
749
+ //#endregion
750
+ //#region src/testing/identity/InMemoryIdentityAdapter.ts
751
+ var InMemoryIdentityAdapter = class {
752
+ #identities = /* @__PURE__ */ new Map();
753
+ #clock;
754
+ constructor(deps) {
755
+ this.#clock = deps.clock;
756
+ for (const [authUserId, profile] of deps.profiles ?? []) this.#identities.set(authUserId, cloneProfile(profile));
757
+ }
758
+ loadByAuthUserId(authUserId) {
759
+ return Effect.sync(() => cloneProfileOrNull(this.#identities.get(authUserId) ?? null));
760
+ }
761
+ create(input) {
762
+ return Effect.gen({ self: this }, function* () {
763
+ if (this.#identities.has(input.authUserId)) return yield* fail$1("create", Errors.invalidInput("authUserId", "already exists"));
764
+ const now = yield* this.#now("create.clock");
765
+ const profile = {
766
+ authUserId: input.authUserId,
767
+ email: input.email,
768
+ displayName: input.displayName ?? null,
769
+ country: input.country ?? null,
770
+ onboarded: false,
771
+ withdrawalAddress: null,
772
+ handle: null,
773
+ imageUrl: null,
774
+ kycTier: toKycTier(0),
775
+ createdAt: now,
776
+ updatedAt: now
777
+ };
778
+ this.#identities.set(input.authUserId, cloneProfile(profile));
779
+ return cloneProfile(profile);
780
+ });
781
+ }
782
+ update(input) {
783
+ return Effect.gen({ self: this }, function* () {
784
+ const existing = this.#identities.get(input.authUserId);
785
+ if (existing === void 0) return yield* fail$1("update", Errors.profileNotFound(input.authUserId));
786
+ const now = yield* this.#now("update.clock");
787
+ const updated = {
788
+ authUserId: existing.authUserId,
789
+ email: existing.email,
790
+ kycTier: existing.kycTier,
791
+ createdAt: existing.createdAt,
792
+ displayName: input.displayName !== void 0 ? input.displayName : existing.displayName,
793
+ country: input.country !== void 0 ? input.country : existing.country,
794
+ onboarded: existing.onboarded,
795
+ withdrawalAddress: existing.withdrawalAddress,
796
+ handle: existing.handle,
797
+ imageUrl: existing.imageUrl,
798
+ updatedAt: now
799
+ };
800
+ this.#identities.set(input.authUserId, cloneProfile(updated));
801
+ return cloneProfile(updated);
802
+ });
803
+ }
804
+ completeOnboarding(input) {
805
+ return Effect.gen({ self: this }, function* () {
806
+ const now = yield* this.#now("completeOnboarding.clock");
807
+ const existing = this.#identities.get(input.authUserId);
808
+ const profile = {
809
+ authUserId: input.authUserId,
810
+ email: existing?.email ?? input.email,
811
+ displayName: input.displayName,
812
+ country: input.country,
813
+ onboarded: true,
814
+ withdrawalAddress: input.withdrawalAddress ?? existing?.withdrawalAddress ?? null,
815
+ handle: toHandle(validateHandle(input.handle)),
816
+ imageUrl: existing?.imageUrl ?? null,
817
+ kycTier: existing?.kycTier ?? toKycTier(0),
818
+ createdAt: existing?.createdAt ?? now,
819
+ updatedAt: now
820
+ };
821
+ this.#identities.set(input.authUserId, cloneProfile(profile));
822
+ return cloneProfile(profile);
823
+ });
824
+ }
825
+ #now(operation) {
826
+ if (this.#clock === void 0) return Effect.succeed(toEpochMs(0));
827
+ return this.#clock.now.pipe(Effect.mapError((cause) => identityErrorFromCapxul(operation, Errors.providerError("clock", "now", cause), cause)));
828
+ }
829
+ };
830
+ Layer.effect(IdentityPortTag, Effect.map(ClockPortTag, (clock) => new InMemoryIdentityAdapter({ clock })));
831
+ function fail$1(operation, error) {
832
+ return Effect.fail(identityErrorFromCapxul(operation, error));
833
+ }
834
+ function cloneProfile(profile) {
835
+ return {
836
+ authUserId: profile.authUserId,
837
+ email: profile.email,
838
+ displayName: profile.displayName,
839
+ country: profile.country,
840
+ onboarded: profile.onboarded,
841
+ withdrawalAddress: profile.withdrawalAddress,
842
+ handle: profile.handle,
843
+ imageUrl: profile.imageUrl,
844
+ kycTier: profile.kycTier,
845
+ createdAt: profile.createdAt,
846
+ updatedAt: profile.updatedAt
847
+ };
848
+ }
849
+ function cloneProfileOrNull(profile) {
850
+ return profile === null ? null : cloneProfile(profile);
851
+ }
852
+ //#endregion
853
+ //#region src/testing/smart-account/smart-account-derivation.ts
854
+ const SUPPORTED_CHAIN_IDS = /* @__PURE__ */ new Set([84532, 8453]);
855
+ function isSupportedSmartAccountChain(chainId) {
856
+ return SUPPORTED_CHAIN_IDS.has(wireChainId(chainId));
857
+ }
858
+ //#endregion
859
+ //#region src/testing/smart-account/InMemorySmartAccountAdapter.ts
860
+ /**
861
+ * Hermetic backend stand-in (PRD #462 / derivation v2): `provision` derives
862
+ * the Safe from the authenticated email via the REAL canonical derivation
863
+ * (`deriveCapxulSafeAddress({ email })` — owner is the pinned bootstrap
864
+ * constant). No signer exists at provision; `claim` installs the signer and
865
+ * marks the row deployed, mirroring the backend's one-userOp claim lane.
866
+ */
867
+ var InMemorySmartAccountAdapter = class {
868
+ #accounts = /* @__PURE__ */ new Map();
869
+ #clock;
870
+ #failures;
871
+ #provisionMutex = Semaphore.makeUnsafe(1);
872
+ constructor(deps) {
873
+ this.#clock = deps.clock;
874
+ this.#failures = deps.failures;
875
+ for (const [authUserId, account] of deps.accounts ?? []) this.#accounts.set(authUserId, cloneSmartAccount(account));
876
+ }
877
+ loadByAuthUserId(authUserId) {
878
+ const scriptedFailure = this.#failures?.loadByAuthUserId;
879
+ if (scriptedFailure !== void 0) return fail("loadByAuthUserId", scriptedFailure);
880
+ return Effect.sync(() => cloneSmartAccountOrNull(this.#accounts.get(authUserId) ?? null));
881
+ }
882
+ loadBySmartAccountAddress(address) {
883
+ const scriptedFailure = this.#failures?.loadBySmartAccountAddress;
884
+ if (scriptedFailure !== void 0) return fail("loadBySmartAccountAddress", scriptedFailure);
885
+ return Effect.sync(() => {
886
+ const needle = toAddress(address);
887
+ for (const account of this.#accounts.values()) if (account.smartAccountAddress === needle) return cloneSmartAccount(account);
888
+ return null;
889
+ });
890
+ }
891
+ provision(input) {
892
+ const scriptedFailure = this.#failures?.provision;
893
+ if (scriptedFailure !== void 0) return fail("provision", scriptedFailure);
894
+ return Effect.gen({ self: this }, function* () {
895
+ if (!isSupportedSmartAccountChain(input.chainId)) return yield* fail("provision", Errors.invalidInput("chainId", `unsupported chainId ${String(input.chainId)}`));
896
+ const existing = this.#accounts.get(input.authUserId);
897
+ if (existing !== void 0) return cloneSmartAccount(existing);
898
+ const now = yield* this.#now("provision.clock");
899
+ const account = {
900
+ authUserId: input.authUserId,
901
+ signerAddress: null,
902
+ smartAccountAddress: toAddress(deriveCapxulSafeAddress({ email: input.email })),
903
+ chainId: input.chainId,
904
+ deployedAt: null,
905
+ claimedAt: null,
906
+ createdAt: now
907
+ };
908
+ this.#accounts.set(input.authUserId, cloneSmartAccount(account));
909
+ return cloneSmartAccount(account);
910
+ }).pipe(this.#provisionMutex.withPermits(1));
911
+ }
912
+ promoteDeployedAt(authUserId, deployedAt) {
913
+ const existing = this.#accounts.get(authUserId);
914
+ if (existing === void 0) return;
915
+ if (existing.deployedAt !== null) return;
916
+ this.#accounts.set(authUserId, {
917
+ ...existing,
918
+ deployedAt
919
+ });
920
+ }
921
+ confirmDeployment(input) {
922
+ const scriptedFailure = this.#failures?.confirmDeployment;
923
+ if (scriptedFailure !== void 0) return fail("confirmDeployment", scriptedFailure);
924
+ return Effect.gen({ self: this }, function* () {
925
+ const existing = this.#accounts.get(input.authUserId);
926
+ if (existing === void 0) return yield* fail("confirmDeployment", Errors.profileNotFound(String(input.authUserId)));
927
+ if (existing.smartAccountAddress !== toAddress(input.safeAddress)) return yield* fail("confirmDeployment", Errors.invalidInput("safeAddress", "does not match backend smart-account row"));
928
+ if (existing.deployedAt !== null) return cloneSmartAccount(existing);
929
+ const now = yield* this.#now("confirmDeployment.clock");
930
+ const updated = {
931
+ ...existing,
932
+ deployedAt: now
933
+ };
934
+ this.#accounts.set(input.authUserId, cloneSmartAccount(updated));
935
+ return cloneSmartAccount(updated);
936
+ }).pipe(this.#provisionMutex.withPermits(1));
937
+ }
938
+ claim(input) {
939
+ const scriptedFailure = this.#failures?.claim;
940
+ if (scriptedFailure !== void 0) return fail("claim", scriptedFailure);
941
+ return Effect.gen({ self: this }, function* () {
942
+ const existing = this.#accounts.get(input.authUserId);
943
+ if (existing === void 0) return yield* fail("claim", Errors.invalidInput("account", "no provisioned smart account for this user"));
944
+ if (existing.chainId !== input.chainId) return yield* fail("claim", Errors.invalidInput("chainId", "does not match backend smart-account row"));
945
+ const newOwner = toAddress(input.signerAddress);
946
+ if (existing.claimedAt !== null) {
947
+ if (existing.signerAddress !== newOwner) return yield* fail("claim", Errors.invalidInput("signerAddress", "account already claimed by a different signer"));
948
+ return cloneSmartAccount(existing);
949
+ }
950
+ const now = yield* this.#now("claim.clock");
951
+ const updated = {
952
+ ...existing,
953
+ signerAddress: newOwner,
954
+ claimedAt: now,
955
+ deployedAt: existing.deployedAt ?? now
956
+ };
957
+ this.#accounts.set(input.authUserId, cloneSmartAccount(updated));
958
+ return cloneSmartAccount(updated);
959
+ }).pipe(this.#provisionMutex.withPermits(1));
960
+ }
961
+ #now(operation) {
962
+ if (this.#clock === void 0) return Effect.succeed(toEpochMs(0));
963
+ return this.#clock.now.pipe(Effect.mapError((cause) => smartAccountErrorFromCapxul(operation, Errors.providerError("clock", "now", cause), cause)));
964
+ }
965
+ };
966
+ function fail(operation, error) {
967
+ return Effect.fail(smartAccountErrorFromCapxul(operation, error));
968
+ }
969
+ function cloneSmartAccount(account) {
970
+ return {
971
+ authUserId: account.authUserId,
972
+ signerAddress: account.signerAddress,
973
+ smartAccountAddress: account.smartAccountAddress,
974
+ chainId: account.chainId,
975
+ deployedAt: account.deployedAt,
976
+ claimedAt: account.claimedAt,
977
+ createdAt: account.createdAt
978
+ };
979
+ }
980
+ function cloneSmartAccountOrNull(account) {
981
+ return account === null ? null : cloneSmartAccount(account);
982
+ }
983
+ //#endregion
984
+ //#region src/testing/telemetry/RecordingTelemetryAdapter.ts
985
+ var RecordingTelemetryAdapter = class {
986
+ #events = [];
987
+ #operations = [];
988
+ #rawMode;
989
+ constructor(options = {}) {
990
+ this.#rawMode = options.rawMode === true;
991
+ }
992
+ get events() {
993
+ return this.#events.map(cloneTelemetryEvent);
994
+ }
995
+ get operations() {
996
+ return this.#operations.map(cloneOperation);
997
+ }
998
+ emit(event) {
999
+ return Effect.sync(() => {
1000
+ const recorded = redactTelemetryEvent(event, { rawMode: this.#rawMode });
1001
+ this.#events.push(recorded);
1002
+ this.#operations.push({
1003
+ type: "emit",
1004
+ event: cloneTelemetryEvent(recorded)
1005
+ });
1006
+ });
1007
+ }
1008
+ identify(input) {
1009
+ return Effect.sync(() => {
1010
+ this.#operations.push({
1011
+ type: "identify",
1012
+ input: cloneIdentifyInput(input)
1013
+ });
1014
+ });
1015
+ }
1016
+ group(input) {
1017
+ return Effect.sync(() => {
1018
+ this.#operations.push({
1019
+ type: "group",
1020
+ input: cloneGroupInput(input)
1021
+ });
1022
+ });
1023
+ }
1024
+ reset() {
1025
+ return Effect.sync(() => {
1026
+ this.#operations.push({ type: "reset" });
1027
+ });
1028
+ }
1029
+ clear() {
1030
+ this.#events.length = 0;
1031
+ this.#operations.length = 0;
1032
+ }
1033
+ };
1034
+ function cloneOperation(operation) {
1035
+ switch (operation.type) {
1036
+ case "emit": return {
1037
+ type: "emit",
1038
+ event: cloneTelemetryEvent(operation.event)
1039
+ };
1040
+ case "identify": return {
1041
+ type: "identify",
1042
+ input: cloneIdentifyInput(operation.input)
1043
+ };
1044
+ case "group": return {
1045
+ type: "group",
1046
+ input: cloneGroupInput(operation.input)
1047
+ };
1048
+ case "reset": return { type: "reset" };
1049
+ default: return absurd(operation);
1050
+ }
1051
+ }
1052
+ function cloneIdentifyInput(input) {
1053
+ return {
1054
+ distinctId: input.distinctId,
1055
+ ...input.anonDistinctId === void 0 ? {} : { anonDistinctId: input.anonDistinctId },
1056
+ ...input.traits === void 0 ? {} : { traits: cloneTelemetryProps(input.traits) },
1057
+ ...input.properties === void 0 ? {} : { properties: cloneTelemetryProps(input.properties) }
1058
+ };
1059
+ }
1060
+ function cloneGroupInput(input) {
1061
+ return input.properties === void 0 ? {
1062
+ groupType: input.groupType,
1063
+ groupKey: input.groupKey
1064
+ } : {
1065
+ groupType: input.groupType,
1066
+ groupKey: input.groupKey,
1067
+ properties: cloneTelemetryProps(input.properties)
1068
+ };
1069
+ }
1070
+ //#endregion
1071
+ //#region src/testing/index.ts
1072
+ const TEST_BOOTSTRAP = {
1073
+ applicationId: toAppId("app_01HX0000000000000000000000"),
1074
+ chainId: toChainId(84532),
1075
+ sessionToken: toSessionToken("bootstrap-test-token"),
1076
+ issuedAt: toEpochMs(0),
1077
+ expiresIn: toDurationMs(6e4),
1078
+ authBaseUrl: "https://auth.example.test",
1079
+ convexUrl: "https://convex.example.test",
1080
+ siteBaseUrl: "https://example.test",
1081
+ openfortPublishableKey: "pk_test_openfort_fixture",
1082
+ shieldPublishableKey: "shield_pk_test_fixture"
1083
+ };
1084
+ /**
1085
+ * Assemble the real public client over deterministic in-memory ports.
1086
+ * Unconfigured backend calls fail locally; no adapter owns a network exit.
1087
+ */
1088
+ function createCapxulTestClient(options = {}) {
1089
+ const clock = new ManualClockAdapter({});
1090
+ const authCache = new InMemoryAuthCacheAdapter();
1091
+ const authClient = new InMemoryAuthClientAdapter({
1092
+ authCache,
1093
+ clock
1094
+ });
1095
+ const identity = new InMemoryIdentityAdapter({ clock });
1096
+ const telemetry = new RecordingTelemetryAdapter();
1097
+ const organizationSetup = createTestOrganizationSetup();
1098
+ const ports = {
1099
+ authClient: authClientPortFromPromiseAdapter(authClient),
1100
+ authCache,
1101
+ identity,
1102
+ smartAccount: new InMemorySmartAccountAdapter({ clock }),
1103
+ accountRead: new InMemoryAccountReadAdapter({ rawBalance: "0" }),
1104
+ bootstrap: new BootstrapStubAdapter({ script: { resolutions: /* @__PURE__ */ new Map([[`${toPublishableKey("cap_pk_test_0123456789ABCDEFGHJKMNPQRSTVWXYZ")}::${toAllowedOrigin("https://example.test")}`, TEST_BOOTSTRAP]]) } }),
1105
+ clock,
1106
+ telemetry,
1107
+ convexCall: new ConvexCallStubAdapter({ script: {} })
1108
+ };
1109
+ const client = assembleCapxulClient({
1110
+ ports,
1111
+ bootstrap: TEST_BOOTSTRAP,
1112
+ authCache,
1113
+ requirement: options.requirement ?? "none",
1114
+ organizationSetup: organizationSetup.ops,
1115
+ ...options.failureObservation === void 0 ? {} : { failureObservation: options.failureObservation }
1116
+ });
1117
+ const transitions = [];
1118
+ const unsubscribe = client._internal.identity.subscribeTransitions((record) => {
1119
+ transitions.push(record);
1120
+ });
1121
+ let closed = false;
1122
+ return {
1123
+ client,
1124
+ clock: Object.freeze({ advance: (ms) => clock.advance(ms) }),
1125
+ observation: {
1126
+ emit: (event) => Effect.runPromise(telemetry.emit(event)),
1127
+ identify: (input) => Effect.runPromise(telemetry.identify(input)),
1128
+ group: (input) => Effect.runPromise(telemetry.group(input)),
1129
+ reset: () => Effect.runPromise(telemetry.reset())
1130
+ },
1131
+ get transitions() {
1132
+ return transitions.slice();
1133
+ },
1134
+ get observations() {
1135
+ return telemetry.operations;
1136
+ },
1137
+ peekOtp: (email) => authClient.peekOtpForTesting(toEmail(email)),
1138
+ organizationSetup: { set: organizationSetup.set },
1139
+ seedIdentity: async (input) => {
1140
+ const authUserId = toAuthUserId(input.authUserId);
1141
+ const email = toEmail(input.email);
1142
+ await Effect.runPromise(identity.create({
1143
+ authUserId,
1144
+ email,
1145
+ ...input.displayName === void 0 ? {} : { displayName: input.displayName },
1146
+ ...input.country === void 0 ? {} : { country: toCountryCode(input.country) }
1147
+ }));
1148
+ authClient.bindAuthUserIdForTesting(email, authUserId);
1149
+ },
1150
+ close: async () => {
1151
+ if (closed) return;
1152
+ closed = true;
1153
+ unsubscribe();
1154
+ await client._internal.close?.();
1155
+ }
1156
+ };
1157
+ }
1158
+ //#endregion
1159
+ export { ControlledSignerFault, armSignerFaults, createCapxulProductionTestClient, createCapxulTestClient, organizationSetupFixture, signerFaultCause };