@capxul/sdk 4.2.0-rc.7 → 4.2.0-rc.9

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.
@@ -1,4 +1,4 @@
1
- import { $ as PayrollGroupId, A as AllowanceKey, B as ChainId, C as AuthCachePort, D as Account$1, Et as FailureMode, H as CurrencyCode, I as AuthSession, K as Email, L as AuthUserId, M as AnonymousDistinctId, N as AppId, Q as PaymentCommandId, R as BlockNumber, U as DocumentHash, V as CountryCode, W as DurationMs, X as OrgId, Y as Money, Z as PartyId, _ as Session$1, _t as CapxulErrorCode, at as RoleKey, b as AuthClientPort, bt as Failure, ct as TesterKind, d as AccountRequirement, et as PayrollRunId, g as Profile$1, gt as CapxulError, h as CapxulResult, i as CapxulSigner, it as PublishableKey, j as AllowedOrigin, k as Address$1, lt as TxHash, nt as PermissionId, ot as SessionToken, q as EpochMs, rt as Profile, s as SignerStatusStore, st as SmartAccount, tt as PermissionAssignmentId, u as AccountProviderSource, ut as WeiAmount, v as SmartAccount$1, vt as CapxulErrorDetails, x as CanSendOtpStatus, y as ClockPort, z as BudgetId } from "./OAuthBearerAuthClient-C-ip-z8M.mjs";
1
+ import { $ as PayrollGroupId, A as AllowanceKey, B as ChainId, C as AuthCachePort, D as Account$1, Et as FailureMode, F as AssetId, H as CurrencyCode, I as AuthSession, K as Email, L as AuthUserId, M as AnonymousDistinctId, N as AppId, Q as PaymentCommandId, R as BlockNumber, U as DocumentHash, V as CountryCode, W as DurationMs, X as OrgId, Y as Money, Z as PartyId, _ as Session$1, _t as CapxulErrorCode, at as RoleKey, b as AuthClientPort, bt as Failure, ct as TesterKind, d as AccountRequirement, et as PayrollRunId, g as Profile$1, gt as CapxulError, h as CapxulResult, i as CapxulSigner, it as PublishableKey, j as AllowedOrigin, k as Address$1, lt as TxHash, nt as PermissionId, ot as SessionToken, q as EpochMs, rt as Profile, s as SignerStatusStore, st as SmartAccount, tt as PermissionAssignmentId, u as AccountProviderSource, ut as WeiAmount, v as SmartAccount$1, vt as CapxulErrorDetails, x as CanSendOtpStatus, y as ClockPort, z as BudgetId } from "./OAuthBearerAuthClient-C-ip-z8M.mjs";
2
2
  import { Hex } from "viem";
3
3
  import { Context, Effect, Layer, Schema, Scope, Tracer } from "effect";
4
4
  import { FunctionReference } from "convex/server";
@@ -284,7 +284,7 @@ interface TelemetryPort {
284
284
  }
285
285
  //#endregion
286
286
  //#region ../observability/src/index.d.ts
287
- declare const TELEMETRY_EVENT_NAMES: readonly ["auth_otp_requested", "auth_otp_delivered", "auth_otp_expired", "auth_verified", "auth_failed", "auth_signed_out", "onboarding_intent_selected", "onboarding_profile_submitted", "onboarding_organization_submitted", "onboarding_dashboard_reached", "provisioning_safe_created", "provisioning_safe_confirmed", "bootstrap_resolved", "bootstrap_failed", "member_activation_started", "member_activation_ready", "member_activation_failed", "organization_creation_started", "organization_creation_ready", "organization_creation_failed", "chain_upstream_fallback", "bundler_error", "receipt_pending", "operation_retried", "account_balance_read", "account_balance_failed", "faucet_requested", "faucet_confirmed", "faucet_failed", "org_create_started", "org_safe_created", "org_safe_confirmed", "org_roles_seeded", "org_created", "org_create_failed", "org_invite_sent", "org_invite_accepted", "org_invite_expired", "payment_initiated", "payment_settled", "payment_failed", "payment_verification_retry", "payment_lifecycle_transition", "quick_pay_external_address_acknowledged", "deposit_initiated", "deposit_settled", "permission_mirror_verification", "movement_scan_window", "movement_scan_checkpoint", "movement_scan_incident", "activity_annotation_saved", "ui_activity_action_completed", "contact_relationship_changed", "ui_contact_action_completed", "access_recovery_completed", "ui_access_step_completed", "org_invite_progressed", "invoice_issued", "invoice_request_transitioned", "ui_invoice_step_completed", "ui_treasury_step_completed", "ui_receive_step_completed", "payroll_group_changed", "payroll_run_transitioned", "ui_payroll_action_completed"];
287
+ declare const TELEMETRY_EVENT_NAMES: readonly ["auth_otp_requested", "auth_otp_delivered", "auth_otp_expired", "auth_verified", "auth_failed", "identity_refused", "auth_signed_out", "onboarding_intent_selected", "onboarding_profile_submitted", "onboarding_organization_submitted", "onboarding_dashboard_reached", "provisioning_safe_created", "provisioning_safe_confirmed", "bootstrap_resolved", "bootstrap_failed", "member_activation_started", "member_activation_ready", "member_activation_failed", "organization_creation_started", "organization_creation_ready", "organization_creation_failed", "chain_upstream_fallback", "bundler_error", "receipt_pending", "operation_retried", "account_balance_read", "account_balance_failed", "faucet_requested", "faucet_confirmed", "faucet_failed", "org_create_started", "org_safe_created", "org_safe_confirmed", "org_roles_seeded", "org_created", "org_create_failed", "org_invite_sent", "org_invite_accepted", "org_invite_expired", "payment_initiated", "payment_settled", "payment_failed", "payment_verification_retry", "payment_lifecycle_transition", "quick_pay_external_address_acknowledged", "deposit_initiated", "deposit_settled", "permission_mirror_verification", "movement_scan_window", "movement_scan_checkpoint", "movement_scan_incident", "activity_annotation_saved", "ui_activity_action_completed", "contact_relationship_changed", "ui_contact_action_completed", "access_recovery_completed", "ui_access_step_completed", "org_invite_progressed", "invoice_issued", "invoice_request_transitioned", "ui_invoice_step_completed", "ui_treasury_step_completed", "ui_receive_step_completed", "payroll_group_changed", "payroll_run_transitioned", "ui_payroll_action_completed"];
288
288
  type TelemetryEventName = (typeof TELEMETRY_EVENT_NAMES)[number];
289
289
  type TelemetryProps = Record<string, unknown>;
290
290
  type TelemetryEvent = {
@@ -611,6 +611,10 @@ type BootstrapResolution = {
611
611
  readonly siteBaseUrl: string;
612
612
  readonly openfortPublishableKey: string;
613
613
  readonly shieldPublishableKey: string;
614
+ /** Openfort third-party auth provider the signer presents; absent means better-auth. */
615
+ readonly openfortAuthProvider?: "better-auth" | "oidc";
616
+ /** Origin of the Ponder indexer the SDK reads directly; absent means no indexer reads. */
617
+ readonly indexerUrl?: string;
614
618
  readonly engineeringTelemetry?: EngineeringTelemetryBootstrapPolicyV1;
615
619
  };
616
620
  type BootstrapErrorKind = "notAuthenticated" | "network" | "provider" | "malformedBody" | "invalidInput" | "rateLimited";
@@ -904,6 +908,118 @@ interface AccountReadPort {
904
908
  fundFromFaucet(input: FundFromFaucetInput): Effect.Effect<FundFromFaucetResult, AccountReadError, never>;
905
909
  }
906
910
  //#endregion
911
+ //#region src/ports/indexer-read.d.ts
912
+ /**
913
+ * IndexerReadPort is the SDK's read seam onto the Ponder read model
914
+ * (`apps/indexer`). Ponder holds what the chain said; Convex holds what the
915
+ * user meant. Nothing here is copied into Convex, nothing waits for a
916
+ * finality tag, and no write path may read through this port.
917
+ *
918
+ * Every read is scoped to one Safe and one page. The indexer accepts only the
919
+ * exact compiled statements in `apps/indexer/src/queries.ts`, so the adapter
920
+ * that implements this port reproduces those statements byte for byte and the
921
+ * service refuses anything else with 403.
922
+ */
923
+ /** One page of rows for one Safe. `offset` defaults to 0 (the first page). */
924
+ interface IndexerReadInput {
925
+ /** The Safe the caller owns. The indexer refuses a Safe outside the session claim. */
926
+ readonly safe: Address$1;
927
+ /** 1 to 1000 inclusive — the indexer's own bound. */
928
+ readonly limit: number;
929
+ readonly offset?: number;
930
+ }
931
+ /**
932
+ * The chain evidence every indexed row carries. `id` is the logical event id
933
+ * `chainId:txHash:transactionLogOrdinal`; it survives a reorg, so it is the
934
+ * key a Convex intent row stitches against.
935
+ */
936
+ interface IndexerEventIdentity {
937
+ readonly id: string;
938
+ readonly chainId: ChainId;
939
+ readonly txHash: TxHash;
940
+ readonly transactionLogOrdinal: number;
941
+ readonly blockNumber: bigint;
942
+ readonly blockHash: string;
943
+ /** Block timestamp in epoch seconds. */
944
+ readonly timestamp: bigint;
945
+ }
946
+ interface IndexerTransferRow extends IndexerEventIdentity {
947
+ readonly assetId: AssetId;
948
+ readonly token: Address$1;
949
+ readonly from: Address$1;
950
+ readonly to: Address$1;
951
+ /**
952
+ * Raw token units, exact. This is an evidence boundary, so the row never
953
+ * carries Money or AssetAmount: the reader owns the asset's decimals.
954
+ */
955
+ readonly amount: bigint;
956
+ }
957
+ interface IndexerPaymentRow extends IndexerEventIdentity {
958
+ readonly contract: Address$1;
959
+ /** The protocol settlement id a Convex Payment command stitches against. */
960
+ readonly settlementId: string;
961
+ readonly sender: Address$1;
962
+ readonly recipient: Address$1;
963
+ readonly assetId: AssetId;
964
+ readonly token: Address$1;
965
+ /** Raw token units, exact. */
966
+ readonly amount: bigint;
967
+ readonly kind: number;
968
+ readonly documentHash: string;
969
+ }
970
+ /** A row of the indexer's signed-transfer `balance` view. */
971
+ interface IndexerBalanceRow {
972
+ readonly chainId: ChainId;
973
+ readonly assetId: AssetId;
974
+ readonly address: Address$1;
975
+ /** Raw token units, exact. */
976
+ readonly amount: bigint;
977
+ }
978
+ /** One chain's sync position, from the indexer's native `/status` route. */
979
+ interface IndexerChainStatus {
980
+ readonly chain: string;
981
+ readonly chainId: ChainId;
982
+ readonly blockNumber: bigint;
983
+ /** Block timestamp in epoch seconds. Its age is the freshness label. */
984
+ readonly blockTimestamp: bigint;
985
+ }
986
+ /**
987
+ * The tables this seam reads today. P04 adds the authority rows (`safe`,
988
+ * `safe_event`, `roles_module`) and P05 adds `commitment`: each is one more
989
+ * entry here plus its statement and decoder.
990
+ */
991
+ interface IndexerRowsByTable {
992
+ readonly transfer: readonly IndexerTransferRow[];
993
+ readonly payment: readonly IndexerPaymentRow[];
994
+ readonly balance: readonly IndexerBalanceRow[];
995
+ }
996
+ type IndexerTable = keyof IndexerRowsByTable;
997
+ declare const IndexerReadError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
998
+ readonly _tag: "IndexerReadError";
999
+ } & Readonly<A>;
1000
+ declare class IndexerReadError extends IndexerReadError_base<{
1001
+ readonly operation: string;
1002
+ readonly publicCode: CapxulErrorCode;
1003
+ readonly publicError: CapxulError;
1004
+ readonly cause: unknown;
1005
+ readonly details?: CapxulErrorDetails;
1006
+ }> {}
1007
+ interface IndexerReadPort {
1008
+ /** One page of rows, newest block first. An empty page is a value, not an error. */
1009
+ read<T extends IndexerTable>(table: T, input: IndexerReadInput): Effect.Effect<IndexerRowsByTable[T], IndexerReadError>;
1010
+ /**
1011
+ * The same page over the indexer's live stream. The first snapshot is
1012
+ * `loading`; each later snapshot carries the whole page again. The indexer
1013
+ * closes the stream on token expiry or revoked authority, which arrives as
1014
+ * one `error` snapshot — the caller re-reads with a refreshed token.
1015
+ *
1016
+ * @internal-use — substrate reactive transport, like `ConvexCallPort.subscribe`.
1017
+ */
1018
+ subscribe<T extends IndexerTable>(table: T, input: IndexerReadInput, callback: (snapshot: Snapshot<IndexerRowsByTable[T]>) => void): Effect.Effect<Unsubscribe, IndexerReadError>;
1019
+ /** Sync position per chain. Unauthenticated: a stale index is not a refusal. */
1020
+ readonly status: Effect.Effect<readonly IndexerChainStatus[], IndexerReadError>;
1021
+ }
1022
+ //#endregion
907
1023
  //#region src/flows/types.d.ts
908
1024
  /** The runtime port bundle that `assembleCapxulClient` uses. */
909
1025
  interface FlowPorts {
@@ -916,6 +1032,11 @@ interface FlowPorts {
916
1032
  readonly clock: ClockPort;
917
1033
  readonly telemetry: TelemetryPort;
918
1034
  readonly convexCall: ConvexCallPort;
1035
+ /**
1036
+ * The Ponder read seam (P02). Optional: it is present only when the client
1037
+ * was given an indexer origin, and every existing flow works without it.
1038
+ */
1039
+ readonly indexerRead?: IndexerReadPort;
919
1040
  }
920
1041
  /** Legacy method input retained by the imperative auth surface. */
921
1042
  interface ExternalSigner {
@@ -2669,6 +2790,13 @@ interface CapxulClient {
2669
2790
  * NOT on the public `client.accounts` surface.
2670
2791
  */
2671
2792
  readonly accounts: AccountsFaucetMethods;
2793
+ /**
2794
+ * Ponder read seam (P02). Present only when the client was built with an
2795
+ * indexer origin, so every reader treats it as optional. Ponder holds what
2796
+ * the chain said; Convex holds what the user meant. Reads here never run
2797
+ * inside a write path and never wait for a finality tag.
2798
+ */
2799
+ readonly indexer?: IndexerReadPort;
2672
2800
  /** Quarantined Reference-harness evidence; never a product Organization method. */
2673
2801
  readonly organizationSetup: OrganizationSetupProofMethods;
2674
2802
  /** Product telemetry projection for internal composition and test harnesses. */
@@ -1,10 +1,85 @@
1
1
  import { h as CapxulResult } from "../OAuthBearerAuthClient-C-ip-z8M.mjs";
2
- import { er as TelemetryEvent, f as CapxulClient, i as ObservationAdapter, n as ProductionSignerControls, nr as TelemetryIdentifyInput, pr as IdentityTransition, t as CapxulClientInput, tr as TelemetryGroupInput } from "../production-B8BQj9l0.mjs";
2
+ import { er as TelemetryEvent, f as CapxulClient, i as ObservationAdapter, n as ProductionSignerControls, nr as TelemetryIdentifyInput, pr as IdentityTransition, t as CapxulClientInput, tr as TelemetryGroupInput } from "../production-Du4Vc7YD.mjs";
3
3
  import { Effect, Layer } from "effect";
4
4
  //#region src/testing/production-client.d.ts
5
5
  /** Create the one production client with per-client controls at its signing boundaries. */
6
6
  declare function createCapxulProductionTestClient(input: CapxulClientInput, controls: ProductionSignerControls): Promise<CapxulResult<CapxulClient>>;
7
7
  //#endregion
8
+ //#region src/testing/signer-faults.d.ts
9
+ /**
10
+ * Which provider refusal to inject. The codes are the ones the SDK's own
11
+ * classifiers decode (`src/signer.ts`), so a fault reaches the public error
12
+ * contract through the real decode path rather than a shortcut.
13
+ *
14
+ * - `missing-signer` — Openfort's typed not-ready code. `openfortSignerNotReadyCode`
15
+ * decodes it, so the production recovery wrapper runs its single retry (A02).
16
+ * - `user-rejected` — the EIP-1193 4001 result. `signerFailure` decodes it to
17
+ * `SIGNER_REJECTED`, an expected refusal (A08).
18
+ * - `unexpected` — no provider code at all, so it stays an unexpected
19
+ * `PROVIDER_ERROR` (A09).
20
+ */
21
+ type SignerFaultKind = "missing-signer" | "user-rejected" | "unexpected";
22
+ /** The value a controlled fault throws. Named so a test can assert its origin. */
23
+ declare class ControlledSignerFault extends Error {
24
+ readonly kind: SignerFaultKind;
25
+ /** Provider code the SDK classifiers read; absent for `unexpected`. */
26
+ readonly code: string | number | undefined;
27
+ constructor(kind: SignerFaultKind);
28
+ }
29
+ /** Build one fault value without the harness, for a caller that writes its own control. */
30
+ declare function signerFaultCause(kind: SignerFaultKind): ControlledSignerFault;
31
+ interface SignerFaultPlan {
32
+ /**
33
+ * Fault the next embedded signature ONCE, beneath the production recovery
34
+ * wrapper, then delegate every later call to the real signer. The address
35
+ * read and the readiness cycle still run first, so the fault lands at the
36
+ * actual final signing boundary after the prepared-command equality checks.
37
+ */
38
+ readonly signOnce?: SignerFaultKind;
39
+ /**
40
+ * Call the production signer's own `resetSession()` ONCE, immediately before
41
+ * the next signature and after the command is prepared. The delegate then
42
+ * re-runs the real readiness cycle, so the configure request under test is
43
+ * the first one the browser observes after the `reset-session` record (A03).
44
+ *
45
+ * Throws when the composed signer owns no session — a silently skipped reset
46
+ * would leave the run looking green while proving nothing.
47
+ */
48
+ readonly resetSessionBeforeNextSign?: boolean;
49
+ }
50
+ /** What the seam did. JSON-serializable, so a browser test can read it back. */
51
+ interface SignerSeamRecord {
52
+ readonly boundary: "reset-session" | "embedded-sign";
53
+ /** 1-based count of calls at that boundary. */
54
+ readonly attempt: number;
55
+ readonly hash?: string;
56
+ /** Present only on the call that actually threw. */
57
+ readonly fault?: SignerFaultKind;
58
+ readonly atMs: number;
59
+ }
60
+ interface ArmedSignerSeam {
61
+ /** Hand these to `createCapxulProductionTestClient`. */
62
+ readonly controls: ProductionSignerControls;
63
+ /** Oldest first. Empty until the client reaches a signing boundary. */
64
+ readonly records: () => readonly SignerSeamRecord[];
65
+ /** True once every armed one-shot has fired. */
66
+ readonly spent: () => boolean;
67
+ }
68
+ /**
69
+ * Arm one-shot faults on the production embedded signer.
70
+ *
71
+ * ```ts
72
+ * const seam = armSignerFaults({ signOnce: "user-rejected" });
73
+ * const client = await createCapxulProductionTestClient(input, seam.controls);
74
+ * const result = await client.org(orgId).permissions.create(command);
75
+ * // result.error.code === "SIGNER_REJECTED"; nothing was submitted.
76
+ * // seam.records() names the boundary that refused.
77
+ * ```
78
+ *
79
+ * An empty plan installs no controls, which composes the ordinary client.
80
+ */
81
+ declare function armSignerFaults(plan?: SignerFaultPlan): ArmedSignerSeam;
82
+ //#endregion
8
83
  //#region src/testing/telemetry/RecordingTelemetryAdapter.d.ts
9
84
  type RecordingTelemetryOperation = {
10
85
  readonly type: "emit";
@@ -56,4 +131,4 @@ interface CapxulTestObservation {
56
131
  */
57
132
  declare function createCapxulTestClient(options?: CreateCapxulTestClientOptions): CapxulTestClient;
58
133
  //#endregion
59
- export { CapxulTestClient, CapxulTestClock, CapxulTestObservation, CreateCapxulTestClientOptions, type ProductionSignerControls, SeedTestIdentityInput, createCapxulProductionTestClient, createCapxulTestClient };
134
+ export { type ArmedSignerSeam, CapxulTestClient, CapxulTestClock, CapxulTestObservation, ControlledSignerFault, CreateCapxulTestClientOptions, type ProductionSignerControls, SeedTestIdentityInput, type SignerFaultKind, type SignerFaultPlan, type SignerSeamRecord, armSignerFaults, createCapxulProductionTestClient, createCapxulTestClient, signerFaultCause };
@@ -1,5 +1,5 @@
1
- import { D as fromWei, P as redactTelemetryEvent, a as smartAccountErrorFromCapxul, c as identityErrorFromCapxul, i as assembleCapxulClient, l as convexCallErrorFromCapxul, n as createCapxulClientWithSignerControls, o as accountReadErrorFromCapxul, s as wireChainId, u as bootstrapErrorFromCapxul, v as toWei } from "../production-D-wB2nFa.mjs";
2
- import { $ as toSessionToken, B as toDurationMs, F as toAuthUserId, G as toJwtToken, H as toEpochMs, K as toKycTier, L as toChainId, M as toAddress, N as toAllowedOrigin, P as toAppId, R as toCountryCode, U as toEpochSeconds, V as toEmail, W as toHandle, Z as toPublishableKey, at as CapxulError, d as authClientPortFromPromiseAdapter, j as toAccountId, n as readClockNow, r as InMemoryAuthCacheAdapter, st as Errors, tt as validateHandle, y as deriveCapxulSafeAddress } from "../OAuthBearerAuthClient-hPwtSpEf.mjs";
1
+ import { D as fromWei, P as redactTelemetryEvent, a as smartAccountErrorFromCapxul, c as identityErrorFromCapxul, i as assembleCapxulClient, l as convexCallErrorFromCapxul, n as createCapxulClientWithSignerControls, o as accountReadErrorFromCapxul, s as wireChainId, u as bootstrapErrorFromCapxul, v as toWei } from "../production-216hsAKc.mjs";
2
+ import { G as toHandle, H as toEmail, I as toAuthUserId, K as toJwtToken, M as toAddress, N as toAllowedOrigin, P as toAppId, Q as toPublishableKey, R as toChainId, U as toEpochMs, V as toDurationMs, W as toEpochSeconds, d as authClientPortFromPromiseAdapter, et as toSessionToken, j as toAccountId, lt as Errors, n as readClockNow, q as toKycTier, r as InMemoryAuthCacheAdapter, rt as validateHandle, st as CapxulError, y as deriveCapxulSafeAddress, z as toCountryCode } from "../OAuthBearerAuthClient-IB2W1Fzj.mjs";
3
3
  import { keccak256 } from "viem";
4
4
  import { Effect, Result, Semaphore } from "effect";
5
5
  import { getFunctionName } from "convex/server";
@@ -9,6 +9,97 @@ function createCapxulProductionTestClient(input, controls) {
9
9
  return createCapxulClientWithSignerControls(input, controls);
10
10
  }
11
11
  //#endregion
12
+ //#region src/testing/signer-faults.ts
13
+ const FAULT_CODES = {
14
+ "missing-signer": "MISSING_SIGNER",
15
+ "user-rejected": 4001,
16
+ unexpected: void 0
17
+ };
18
+ /** The value a controlled fault throws. Named so a test can assert its origin. */
19
+ var ControlledSignerFault = class extends Error {
20
+ kind;
21
+ /** Provider code the SDK classifiers read; absent for `unexpected`. */
22
+ code;
23
+ constructor(kind) {
24
+ super(`controlled signer fault (${kind})`);
25
+ this.name = "ControlledSignerFault";
26
+ this.kind = kind;
27
+ this.code = FAULT_CODES[kind];
28
+ }
29
+ };
30
+ /** Build one fault value without the harness, for a caller that writes its own control. */
31
+ function signerFaultCause(kind) {
32
+ return new ControlledSignerFault(kind);
33
+ }
34
+ /**
35
+ * Arm one-shot faults on the production embedded signer.
36
+ *
37
+ * ```ts
38
+ * const seam = armSignerFaults({ signOnce: "user-rejected" });
39
+ * const client = await createCapxulProductionTestClient(input, seam.controls);
40
+ * const result = await client.org(orgId).permissions.create(command);
41
+ * // result.error.code === "SIGNER_REJECTED"; nothing was submitted.
42
+ * // seam.records() names the boundary that refused.
43
+ * ```
44
+ *
45
+ * An empty plan installs no controls, which composes the ordinary client.
46
+ */
47
+ function armSignerFaults(plan = {}) {
48
+ const records = [];
49
+ let pendingFault = plan.signOnce;
50
+ let pendingReset = plan.resetSessionBeforeNextSign === true;
51
+ let resetCalls = 0;
52
+ let embeddedCalls = 0;
53
+ function record(entry) {
54
+ records.push({
55
+ ...entry,
56
+ atMs: Date.now()
57
+ });
58
+ }
59
+ const resetThenSign = async (hash, signer) => {
60
+ if (pendingReset) {
61
+ pendingReset = false;
62
+ resetCalls += 1;
63
+ if (signer.resetSession === void 0) throw new Error("armSignerFaults: resetSessionBeforeNextSign needs the embedded browser signer; this client composed a signer with no session");
64
+ signer.resetSession();
65
+ record({
66
+ boundary: "reset-session",
67
+ attempt: resetCalls,
68
+ hash
69
+ });
70
+ }
71
+ return signer.signUserOpHash(hash);
72
+ };
73
+ const faultThenDelegate = async (hash, delegate) => {
74
+ embeddedCalls += 1;
75
+ const fault = pendingFault;
76
+ if (fault === void 0) {
77
+ record({
78
+ boundary: "embedded-sign",
79
+ attempt: embeddedCalls,
80
+ hash
81
+ });
82
+ return delegate(hash);
83
+ }
84
+ pendingFault = void 0;
85
+ record({
86
+ boundary: "embedded-sign",
87
+ attempt: embeddedCalls,
88
+ hash,
89
+ fault
90
+ });
91
+ throw signerFaultCause(fault);
92
+ };
93
+ return {
94
+ controls: {
95
+ ...plan.resetSessionBeforeNextSign === true ? { signUserOpHash: resetThenSign } : {},
96
+ ...plan.signOnce === void 0 ? {} : { signEmbeddedUserOpHash: faultThenDelegate }
97
+ },
98
+ records: () => records.slice(),
99
+ spent: () => pendingFault === void 0 && !pendingReset
100
+ };
101
+ }
102
+ //#endregion
12
103
  //#region src/testing/account/InMemoryAccountReadAdapter.ts
13
104
  var InMemoryAccountReadAdapter = class {
14
105
  #deps;
@@ -915,4 +1006,4 @@ function createCapxulTestClient(options = {}) {
915
1006
  };
916
1007
  }
917
1008
  //#endregion
918
- export { createCapxulProductionTestClient, createCapxulTestClient };
1009
+ export { ControlledSignerFault, armSignerFaults, createCapxulProductionTestClient, createCapxulTestClient, signerFaultCause };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/sdk",
3
- "version": "4.2.0-rc.7",
3
+ "version": "4.2.0-rc.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Xelmar-tech/infrastructure.git",
@@ -48,11 +48,11 @@
48
48
  "vite-plus": "0.3.0",
49
49
  "vitest": "4.1.11",
50
50
  "@capxul/config": "0.3.0",
51
- "@capxul/observability": "4.2.0-rc.7",
52
- "@capxul/types": "0.3.0",
51
+ "@capxul/observability": "4.2.0-rc.9",
53
52
  "@capxul/errors": "0.3.0",
54
- "@capxul/typescript-config": "0.0.0",
55
- "@capxul/wire": "0.7.0"
53
+ "@capxul/wire": "0.7.0",
54
+ "@capxul/types": "0.3.0",
55
+ "@capxul/typescript-config": "0.0.0"
56
56
  },
57
57
  "_permissionlessPinReason": "permissionless.toSafeSmartAccount is pinned to 0.3.4 for live Safe deployment E2E. Counterfactual address fixtures captured 2026-05-17 in packages/backend/convex/_shared/__tests__/counterfactual.test.ts and packages/config/tests/safe.test.ts must be re-verified before upgrading.",
58
58
  "scripts": {