@capxul/sdk 4.2.0-rc.10 → 4.2.0-rc.12

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.
@@ -985,6 +985,10 @@ function toChainId(raw) {
985
985
  if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw <= 0) throw Errors.invalidInput("chainId", invalidValueReason("must be a positive safe integer", raw));
986
986
  return raw;
987
987
  }
988
+ function toWeiAmount(raw) {
989
+ if (typeof raw !== "string" || !WEI_RE.test(raw)) throw Errors.invalidInput("weiAmount", invalidValueReason("must be a non-negative integer string", raw));
990
+ return raw;
991
+ }
988
992
  function toCountryCode(raw) {
989
993
  if (typeof raw !== "string") throw Errors.invalidInput("countryCode", "must be a string");
990
994
  const upper = raw.toUpperCase();
@@ -2468,4 +2472,4 @@ function readExchange(body) {
2468
2472
  };
2469
2473
  }
2470
2474
  //#endregion
2471
- export { toRoleKey as $, currencySymbolFor as A, toCurrencyCode as B, ASSET_ID_RE as C, redactSecrets as Ct, WEI_RE as D, SUPPORTED_CURRENCY_CODES as E, toAssetId as F, toHandle as G, toEmail as H, toAuthUserId as I, toOrgId as J, toJwtToken as K, toBudgetId as L, toAddress as M, toAllowedOrigin as N, ZERO_BYTES32 as O, toAppId as P, toPublishableKey as Q, toChainId as R, APP_ID_RE as S, isCredentialField as St, EVM_ADDRESS_RE as T, toEpochMs as U, toDurationMs as V, toEpochSeconds as W, toPayrollGroupId as X, toPartyId as Y, toPayrollRunId as Z, normalizeBindingEmail as _, failureFingerprint as _t, parseAuthSession as a, decodeConvexError as at, configuredMoneyAssetById as b, revertSummaryText as bt, AuthCachePortTag as c, EXPECTED_OPERATION_OUTCOMES as ct, authClientPortFromPromiseAdapter as d, CHAIN_UPSTREAMS as dt, toSessionToken as et, AuthClientError as f, FAILURE_MODES as ft, CAPXUL_PAYMENTS_V2_ADDRESS as g, decodeChainCause as gt, orgRoleKeyForLabel as h, chainEvidenceLabel as ht, BrowserAuthCacheAdapter as i, HANDLE_RE as it, toAccountId as j, assetIdFor as k, SystemClockLayer as l, Errors as lt, ADMIN_ROLE_LABEL as m, chainCauseProperties as mt, readClockNow as n, toTxHash as nt, parseCachedJwt as o, CAPXUL_ERROR_CODES as ot, AuthClientPortTag as p, boundedResponseHeaders as pt, toKycTier as q, InMemoryAuthCacheAdapter as r, validateHandle as rt, AuthCacheError as s, CapxulError as st, oauthBearerAuthClient as t, toTesterKind as tt, ClockPortTag as u, isCapxulError as ut, BASE_SEPOLIA_CHAIN_ID as v, isChainUpstream as vt, BYTES32_RE as w, redactUrlSecrets as wt, ACCOUNT_ID_RE as x, containsSensitiveMaterial as xt, deriveCapxulSafeAddress as y, isFailureMode as yt, toCountryCode as z };
2475
+ export { toRoleKey as $, currencySymbolFor as A, toCurrencyCode as B, ASSET_ID_RE as C, isCredentialField as Ct, WEI_RE as D, SUPPORTED_CURRENCY_CODES as E, toAssetId as F, toHandle as G, toEmail as H, toAuthUserId as I, toOrgId as J, toJwtToken as K, toBudgetId as L, toAddress as M, toAllowedOrigin as N, ZERO_BYTES32 as O, toAppId as P, toPublishableKey as Q, toChainId as R, APP_ID_RE as S, containsSensitiveMaterial as St, EVM_ADDRESS_RE as T, redactUrlSecrets as Tt, toEpochMs as U, toDurationMs as V, toEpochSeconds as W, toPayrollGroupId as X, toPartyId as Y, toPayrollRunId as Z, normalizeBindingEmail as _, decodeChainCause as _t, parseAuthSession as a, HANDLE_RE as at, configuredMoneyAssetById as b, isFailureMode as bt, AuthCachePortTag as c, CapxulError as ct, authClientPortFromPromiseAdapter as d, isCapxulError as dt, toSessionToken as et, AuthClientError as f, CHAIN_UPSTREAMS as ft, CAPXUL_PAYMENTS_V2_ADDRESS as g, chainEvidenceLabel as gt, orgRoleKeyForLabel as h, chainCauseProperties as ht, BrowserAuthCacheAdapter as i, validateHandle as it, toAccountId as j, assetIdFor as k, SystemClockLayer as l, EXPECTED_OPERATION_OUTCOMES as lt, ADMIN_ROLE_LABEL as m, boundedResponseHeaders as mt, readClockNow as n, toTxHash as nt, parseCachedJwt as o, decodeConvexError as ot, AuthClientPortTag as p, FAILURE_MODES as pt, toKycTier as q, InMemoryAuthCacheAdapter as r, toWeiAmount as rt, AuthCacheError as s, CAPXUL_ERROR_CODES as st, oauthBearerAuthClient as t, toTesterKind as tt, ClockPortTag as u, Errors as ut, BASE_SEPOLIA_CHAIN_ID as v, failureFingerprint as vt, BYTES32_RE as w, redactSecrets as wt, ACCOUNT_ID_RE as x, revertSummaryText as xt, deriveCapxulSafeAddress as y, isChainUpstream as yt, toCountryCode as z };
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "source": {
3
- "commit": "a7e55e5f1c2af5567a8b285a4ef70b8b5a3289f6",
4
- "tree": "91bf1b080c0006fe30131711c13c843afd627158",
3
+ "commit": "fd5b4d0f124c75f609e469b8673c61f84253c0dc",
4
+ "tree": "d61f0f6b0c83dfc2d405ffc6b55eecf2e9d80f2d",
5
5
  "branch": "codex/programme-rc",
6
6
  "repository": "https://github.com/Xelmar-tech/infrastructure",
7
7
  "lockfileSha256": "5870f53b72706d103220c1bcf733cb4fb71196aea32a9cfbdc277d73cc959264"
8
8
  },
9
9
  "name": "@capxul/sdk",
10
- "version": "4.2.0-rc.10"
10
+ "version": "4.2.0-rc.12"
11
11
  }
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { $ as PayrollGroupId, B as ChainId, Ct as ChainCause, D as Account, Dt as RevertSummary, E as CAPXUL_PAYMENTS_V2_ADDRESS, Et as FailureMode, F as AssetId, G as EVM_ADDRESS_RE, H as CurrencyCode, I as AuthSession, J as Handle, L as AuthUserId, O as AccountId, P as AssetAmount, St as CHAIN_UPSTREAMS, Tt as FAILURE_MODES, V as CountryCode, X as OrgId, Y as Money, Z as PartyId, _ as Session, _t as CapxulErrorCode, a as Eip1193RequestProvider, at as RoleKey, c as injectedWalletSigner, ct as TesterKind, d as AccountRequirement, dt as toAddress, et as PayrollRunId, f as Eip1193Provider, ft as toCountryCode, g as Profile, gt as CapxulError, h as CapxulResult, ht as CAPXUL_ERROR_CODES, i as CapxulSigner, k as Address, l as AccountProvider, m as localPrivateKeyAccountProvider, mt as toPartyId, o as SignerStatus, p as eip1193AccountProvider, pt as toHandle, r as CapxulDigestSigner, s as SignerStatusStore, u as AccountProviderSource, v as SmartAccount, vt as CapxulErrorDetails, wt as ChainUpstream, xt as isCapxulError, yt as Errors, z as BudgetId } from "./OAuthBearerAuthClient-C-ip-z8M.mjs";
2
- import { $ as OrganizationPaymentBatchInput, $n as RequestStatus, $t as Destination, A as OrgScopedMethods, An as Ref, At as AddressBookAddInput, B as AuthorizeRunOptions, Bn as SmartAccountMethods, Bt as ActivityFilter, C as CurrentUserMethods, Cn as PaymentMoney, Ct as AccountMethods, D as MemberStatus, Dn as PaymentsMethods, Dt as ActorRequest, E as InviteMemberInput, En as PaymentType, Et as ActorRelationshipMethods, F as ResendInviteTokenInput, Fn as AccountLifecycle, Ft as InboxItem, G as PayrollGroupsMethods, Gn as ActorRef, Gt as ActivityPage, H as PayrollGroup, Hn as OrgLifecycle, Ht as ActivityKind, I as RoleDefinition, In as AccountSetupStep, It as InboxMethods, J as PayrollRun, Jn as SubmittedPermissionExecution, Jt as ActivitySummary, K as PayrollMethods, Kn as CurrentHoldings, Kt as ActivityRange, L as RoleSpendCap, Ln as isSettingUpLifecycle, Lt as ActivityAnnotation, M as OrgView, Mn as TargetReference, Mt as AddressBookLabelInput, N as OrganizationAccount, Nn as TargetsMethods, Nt as AddressBookMethods, O as MemberView, On as PaymentsPayInput, Ot as ActorRequestIssueInput, P as OrganizationAuditLogItem, Pn as fingerprintPaymentIntent, Pt as InboxApproveInput, Q as PayrollTermsUnit, Qn as PaymentStatus$1, Qt as DepositInstructions, R as RoleView, Rn as CompleteProfileInput, Rt as ActivityAnnotationInput, S as CurrentUserContext, Sn as PaymentDocumentsMethods, St as ReadyAccountLifecycle, T as DetectPendingOrgInvitationsResult, Tn as PaymentTiming, Tt as ActorProfileMethods, U as PayrollGroupInput, Un as OrgSetupStep, Ut as ActivityListParams, V as PayrollEngagementTerms, Vn as AuthMethods, Vt as ActivityItem, W as PayrollGroupMember, Wn as PayoutAddress, Wt as ActivityMethods, X as PayrollRunStatus, Xn as PAYMENT_DIRECTIONS, Xt as ActivitySummaryTotal, Y as PayrollRunItemInput, Yn as InboxStatus, Yt as ActivitySummaryParams, Z as PayrollRuns, Zn as PAYMENT_STATUSES, Zt as ActorReference, _ as HoldingsMethods, _n as PaymentDirection, _t as OnboardingMethods, a as ObservationContext, an as DestinationRemoveInput, ar as IdentityEvent, at as PermissionCreateInput, b as SystemHealth, bn as PaymentDocumentRender, bt as PersonOnboarding, c as HostObservability, cn as MeMethods, cr as Readiness, ct as PermissionReplaceInput, d as postHogObservability, dn as OfframpMethods, dr as isClaimed, dt as Budget, en as DestinationAddInput, et as OrganizationPaymentInput, f as CapxulClient, fn as OfframpQuote, fr as isRestoring, ft as OrgMe, g as IdentityRuntimeSendResult, gn as PaymentActivityEvidence, gt as CompletedPersonProfile, h as IdentityRuntime, hn as Payment, ht as AccountsMethods, i as ObservationAdapter, in as DestinationRail, ir as Destination$1, it as PermissionChangeInput, j as OrgTemplate, jn as ResolvedTarget, jt as AddressBookEntry, k as OrgMethods, kn as RecipientResolution, kt as ActorRequestsMethods, l as PostHogObservabilityClient, ln as MeProfile, lr as StateLabel, lt as PermissionRevokeInput, m as IdentityProfileDetails, mn as OfframpStatus, mr as InvocationControls, mt as OrgMeOptions, nn as DestinationListInput, nt as OrganizationPaymentsMethods, o as ObservationDelivery, on as DestinationsMethods, or as IdentityState, ot as PermissionMethods, p as CreateCapxulClientInput, pn as OfframpQuoteInput, pr as IdentityTransition, pt as OrgMeMethod, q as PayrollOptions, qn as Permission, qt as ActivityReference, r as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, rn as DestinationPayload, rr as TelemetryPort, rt as PermissionAssignInput, s as SdkFailureObservation, sn as FinancialOpsMethods, sr as OrgLane, st as PermissionOptions, t as CapxulClientInput, tn as DestinationKind, tt as OrganizationPaymentItemInput, u as PostHogObservabilityOptions, un as MovementActivityEvidence, ur as destination, ut as PermissionReadResult, v as Holding, vn as PaymentDocumentKind, vt as OrganizationOnboarding, w as CreateOrgInput, wn as PaymentStatus, wt as ActorProfile, x as MediaMethods, xn as PaymentDocumentVerification, xt as PersonOnboardingInput, y as SystemMethods, yn as PaymentDocumentRef, yt as OrganizationOnboardingInput, z as AuthorizeRunInput, zn as IdentityMethods, zt as ActivityDetail } from "./production-Du4Vc7YD.mjs";
2
+ import { $ as OrganizationPaymentBatchInput, $n as RequestStatus, $t as Destination, A as OrgScopedMethods, An as Ref, At as AddressBookAddInput, B as AuthorizeRunOptions, Bn as SmartAccountMethods, Bt as ActivityFilter, C as CurrentUserMethods, Cn as PaymentMoney, Ct as AccountMethods, D as MemberStatus, Dn as PaymentsMethods, Dt as ActorRequest, E as InviteMemberInput, En as PaymentType, Et as ActorRelationshipMethods, F as ResendInviteTokenInput, Fn as AccountLifecycle, Ft as InboxItem, G as PayrollGroupsMethods, Gn as ActorRef, Gt as ActivityPage, H as PayrollGroup, Hn as OrgLifecycle, Ht as ActivityKind, I as RoleDefinition, In as AccountSetupStep, It as InboxMethods, J as PayrollRun, Jn as SubmittedPermissionExecution, Jt as ActivitySummary, K as PayrollMethods, Kn as CurrentHoldings, Kt as ActivityRange, L as RoleSpendCap, Ln as isSettingUpLifecycle, Lt as ActivityAnnotation, M as OrgView, Mn as TargetReference, Mt as AddressBookLabelInput, N as OrganizationAccount, Nn as TargetsMethods, Nt as AddressBookMethods, O as MemberView, On as PaymentsPayInput, Ot as ActorRequestIssueInput, P as OrganizationAuditLogItem, Pn as fingerprintPaymentIntent, Pt as InboxApproveInput, Q as PayrollTermsUnit, Qn as PaymentStatus$1, Qt as DepositInstructions, R as RoleView, Rn as CompleteProfileInput, Rt as ActivityAnnotationInput, S as CurrentUserContext, Sn as PaymentDocumentsMethods, St as ReadyAccountLifecycle, T as DetectPendingOrgInvitationsResult, Tn as PaymentTiming, Tt as ActorProfileMethods, U as PayrollGroupInput, Un as OrgSetupStep, Ut as ActivityListParams, V as PayrollEngagementTerms, Vn as AuthMethods, Vt as ActivityItem, W as PayrollGroupMember, Wn as PayoutAddress, Wt as ActivityMethods, X as PayrollRunStatus, Xn as PAYMENT_DIRECTIONS, Xt as ActivitySummaryTotal, Y as PayrollRunItemInput, Yn as InboxStatus, Yt as ActivitySummaryParams, Z as PayrollRuns, Zn as PAYMENT_STATUSES, Zt as ActorReference, _ as HoldingsMethods, _n as PaymentDirection, _t as OnboardingMethods, a as ObservationContext, an as DestinationRemoveInput, ar as IdentityEvent, at as PermissionCreateInput, b as SystemHealth, bn as PaymentDocumentRender, bt as PersonOnboarding, c as HostObservability, cn as MeMethods, cr as Readiness, ct as PermissionReplaceInput, d as postHogObservability, dn as OfframpMethods, dr as isClaimed, dt as Budget, en as DestinationAddInput, et as OrganizationPaymentInput, f as CapxulClient, fn as OfframpQuote, fr as isRestoring, ft as OrgMe, g as IdentityRuntimeSendResult, gn as PaymentActivityEvidence, gt as CompletedPersonProfile, h as IdentityRuntime, hn as Payment, ht as AccountsMethods, i as ObservationAdapter, in as DestinationRail, ir as Destination$1, it as PermissionChangeInput, j as OrgTemplate, jn as ResolvedTarget, jt as AddressBookEntry, k as OrgMethods, kn as RecipientResolution, kt as ActorRequestsMethods, l as PostHogObservabilityClient, ln as MeProfile, lr as StateLabel, lt as PermissionRevokeInput, m as IdentityProfileDetails, mn as OfframpStatus, mr as InvocationControls, mt as OrgMeOptions, nn as DestinationListInput, nt as OrganizationPaymentsMethods, o as ObservationDelivery, on as DestinationsMethods, or as IdentityState, ot as PermissionMethods, p as CreateCapxulClientInput, pn as OfframpQuoteInput, pr as IdentityTransition, pt as OrgMeMethod, q as PayrollOptions, qn as Permission, qt as ActivityReference, r as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, rn as DestinationPayload, rr as TelemetryPort, rt as PermissionAssignInput, s as SdkFailureObservation, sn as FinancialOpsMethods, sr as OrgLane, st as PermissionOptions, t as CapxulClientInput, tn as DestinationKind, tt as OrganizationPaymentItemInput, u as PostHogObservabilityOptions, un as MovementActivityEvidence, ur as destination, ut as PermissionReadResult, v as Holding, vn as PaymentDocumentKind, vt as OrganizationOnboarding, w as CreateOrgInput, wn as PaymentStatus, wt as ActorProfile, x as MediaMethods, xn as PaymentDocumentVerification, xt as PersonOnboardingInput, y as SystemMethods, yn as PaymentDocumentRef, yt as OrganizationOnboardingInput, z as AuthorizeRunInput, zn as IdentityMethods, zt as ActivityDetail } from "./production-Cpz4hm8p.mjs";
3
3
  import { Hex } from "viem";
4
4
  import { Effect, Layer } from "effect";
5
5
  //#region ../errors/src/index.d.ts
@@ -339,6 +339,8 @@ declare const CAPXUL_OPERATIONS: {
339
339
  };
340
340
  readonly payroll: {
341
341
  readonly authorizeRun: "payroll.authorizeRun";
342
+ readonly emitGroupChanged: "payroll.emitGroupChanged";
343
+ readonly emitRunTransitioned: "payroll.emitRunTransitioned";
342
344
  readonly groups: {
343
345
  readonly access: "payroll.groups";
344
346
  readonly list: "payroll.groups.list";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { A as signerFailure, B as isRestoring, C as normalizeExceptionErrorKind, E as safeExceptionLabel, F as CAPXUL_OPERATIONS, I as isCapxulOperation, L as normalizeCapxulOperation, M as PAYMENT_DIRECTIONS, N as PAYMENT_STATUSES, O as isSettingUpLifecycle, R as destination, S as SDK_VERSION, T as projectSdkException, _ as fingerprintPaymentIntent, b as failureDetail, d as embeddedSigner, f as openfortEmbeddedSigner, g as devPrivateKeySigner, h as deriveDevPrivateKey, j as resolveFailureMode, k as injectedWalletSigner, m as openfortEmbeddedWalletPort, p as openfortEmbeddedSignerFromWallet, r as postHogObservability, t as createCapxulClient$1, w as normalizeExceptionOperation, x as EXCEPTION_MESSAGE, y as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, z as isClaimed } from "./production-BndbwuVx.mjs";
2
- import { A as currencySymbolFor, B as toCurrencyCode, G as toHandle, M as toAddress, T as EVM_ADDRESS_RE, Y as toPartyId, b as configuredMoneyAssetById, dt as CHAIN_UPSTREAMS, ft as FAILURE_MODES, g as CAPXUL_PAYMENTS_V2_ADDRESS, it as HANDLE_RE, k as assetIdFor, lt as Errors, ot as CAPXUL_ERROR_CODES, st as CapxulError, ut as isCapxulError, z as toCountryCode } from "./OAuthBearerAuthClient-IB2W1Fzj.mjs";
1
+ import { A as signerFailure, B as isRestoring, C as normalizeExceptionErrorKind, E as safeExceptionLabel, F as CAPXUL_OPERATIONS, I as isCapxulOperation, L as normalizeCapxulOperation, M as PAYMENT_DIRECTIONS, N as PAYMENT_STATUSES, O as isSettingUpLifecycle, R as destination, S as SDK_VERSION, T as projectSdkException, _ as fingerprintPaymentIntent, b as failureDetail, d as embeddedSigner, f as openfortEmbeddedSigner, g as devPrivateKeySigner, h as deriveDevPrivateKey, j as resolveFailureMode, k as injectedWalletSigner, m as openfortEmbeddedWalletPort, p as openfortEmbeddedSignerFromWallet, r as postHogObservability, t as createCapxulClient$1, w as normalizeExceptionOperation, x as EXCEPTION_MESSAGE, y as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, z as isClaimed } from "./production-C8FuAYm1.mjs";
2
+ import { A as currencySymbolFor, B as toCurrencyCode, G as toHandle, M as toAddress, T as EVM_ADDRESS_RE, Y as toPartyId, at as HANDLE_RE, b as configuredMoneyAssetById, ct as CapxulError, dt as isCapxulError, ft as CHAIN_UPSTREAMS, g as CAPXUL_PAYMENTS_V2_ADDRESS, k as assetIdFor, pt as FAILURE_MODES, st as CAPXUL_ERROR_CODES, ut as Errors, z as toCountryCode } from "./OAuthBearerAuthClient-CbU_W9Sp.mjs";
3
3
  import { formatUnits } from "viem";
4
4
  import { Effect } from "effect";
5
5
  import { privateKeyToAccount } from "viem/accounts";
@@ -1,4 +1,4 @@
1
- import { M as toAddress, a as parseAuthSession, c as AuthCachePortTag, i as BrowserAuthCacheAdapter, o as parseCachedJwt, r as InMemoryAuthCacheAdapter, s as AuthCacheError, t as oauthBearerAuthClient } from "../OAuthBearerAuthClient-IB2W1Fzj.mjs";
1
+ import { M as toAddress, a as parseAuthSession, c as AuthCachePortTag, i as BrowserAuthCacheAdapter, o as parseCachedJwt, r as InMemoryAuthCacheAdapter, s as AuthCacheError, t as oauthBearerAuthClient } from "../OAuthBearerAuthClient-CbU_W9Sp.mjs";
2
2
  import { Effect, FileSystem, Layer, Path } from "effect";
3
3
  import { privateKeyToAccount } from "viem/accounts";
4
4
  import * as os from "node:os";
@@ -1,4 +1,4 @@
1
- import { $ as toRoleKey, B as toCurrencyCode, C as ASSET_ID_RE, Ct as redactSecrets, D as WEI_RE, E as SUPPORTED_CURRENCY_CODES, F as toAssetId, G as toHandle, H as toEmail, I as toAuthUserId, J as toOrgId, K as toJwtToken, L as toBudgetId, M as toAddress, N as toAllowedOrigin, O as ZERO_BYTES32, Q as toPublishableKey, R as toChainId, S as APP_ID_RE, St as isCredentialField, T as EVM_ADDRESS_RE$1, U as toEpochMs, V as toDurationMs, W as toEpochSeconds, X as toPayrollGroupId, Y as toPartyId, Z as toPayrollRunId, _ as normalizeBindingEmail, _t as failureFingerprint, at as decodeConvexError, bt as revertSummaryText, c as AuthCachePortTag, ct as EXPECTED_OPERATION_OUTCOMES, d as authClientPortFromPromiseAdapter, dt as CHAIN_UPSTREAMS, et as toSessionToken, f as AuthClientError, ft as FAILURE_MODES$1, gt as decodeChainCause, h as orgRoleKeyForLabel, ht as chainEvidenceLabel, i as BrowserAuthCacheAdapter, j as toAccountId, l as SystemClockLayer, lt as Errors, m as ADMIN_ROLE_LABEL, mt as chainCauseProperties, nt as toTxHash, ot as CAPXUL_ERROR_CODES, p as AuthClientPortTag, pt as boundedResponseHeaders, q as toKycTier, r as InMemoryAuthCacheAdapter, rt as validateHandle, st as CapxulError, tt as toTesterKind, u as ClockPortTag, ut as isCapxulError, v as BASE_SEPOLIA_CHAIN_ID, vt as isChainUpstream, w as BYTES32_RE, wt as redactUrlSecrets, x as ACCOUNT_ID_RE, xt as containsSensitiveMaterial, y as deriveCapxulSafeAddress, yt as isFailureMode, z as toCountryCode } from "./OAuthBearerAuthClient-IB2W1Fzj.mjs";
1
+ import { $ as toRoleKey, B as toCurrencyCode, C as ASSET_ID_RE, Ct as isCredentialField, D as WEI_RE, E as SUPPORTED_CURRENCY_CODES, F as toAssetId, G as toHandle, H as toEmail, I as toAuthUserId, J as toOrgId, K as toJwtToken, L as toBudgetId, M as toAddress, N as toAllowedOrigin, O as ZERO_BYTES32, Q as toPublishableKey, R as toChainId, S as APP_ID_RE, St as containsSensitiveMaterial, T as EVM_ADDRESS_RE$1, Tt as redactUrlSecrets, U as toEpochMs, V as toDurationMs, W as toEpochSeconds, X as toPayrollGroupId, Y as toPartyId, Z as toPayrollRunId, _ as normalizeBindingEmail, _t as decodeChainCause, b as configuredMoneyAssetById, bt as isFailureMode, c as AuthCachePortTag, ct as CapxulError, d as authClientPortFromPromiseAdapter, dt as isCapxulError, et as toSessionToken, f as AuthClientError, ft as CHAIN_UPSTREAMS, gt as chainEvidenceLabel, h as orgRoleKeyForLabel, ht as chainCauseProperties, i as BrowserAuthCacheAdapter, it as validateHandle, j as toAccountId, l as SystemClockLayer, lt as EXPECTED_OPERATION_OUTCOMES, m as ADMIN_ROLE_LABEL, mt as boundedResponseHeaders, nt as toTxHash, ot as decodeConvexError, p as AuthClientPortTag, pt as FAILURE_MODES$1, q as toKycTier, r as InMemoryAuthCacheAdapter, rt as toWeiAmount, st as CAPXUL_ERROR_CODES, tt as toTesterKind, u as ClockPortTag, ut as Errors, v as BASE_SEPOLIA_CHAIN_ID, vt as failureFingerprint, w as BYTES32_RE, wt as redactSecrets, x as ACCOUNT_ID_RE, xt as revertSummaryText, y as deriveCapxulSafeAddress, yt as isChainUpstream, z as toCountryCode } from "./OAuthBearerAuthClient-CbU_W9Sp.mjs";
2
2
  import { formatUnits, keccak256, parseUnits, recoverAddress, stringToHex, toBytes } from "viem";
3
3
  import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Option, Queue, Ref, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
4
4
  import { getFunctionName, makeFunctionReference } from "convex/server";
@@ -695,6 +695,8 @@ const CAPXUL_OPERATIONS = {
695
695
  recipient: { resolve: "recipient.resolve" },
696
696
  payroll: {
697
697
  authorizeRun: "payroll.authorizeRun",
698
+ emitGroupChanged: "payroll.emitGroupChanged",
699
+ emitRunTransitioned: "payroll.emitRunTransitioned",
698
700
  groups: {
699
701
  access: "payroll.groups",
700
702
  list: "payroll.groups.list",
@@ -1363,7 +1365,27 @@ const PermissionMirrorVerificationProps = Schema.Struct({
1363
1365
  "conflicting_replay",
1364
1366
  "revision_mismatch"
1365
1367
  ])),
1366
- command_id: OptionalString
1368
+ command_id: OptionalString,
1369
+ /**
1370
+ * #1961 R03. The authorized stored row names the Permission it changed; the
1371
+ * verified receipt names the chain facts that prove it. Every field is
1372
+ * optional because a pending or provider-failed verification has neither an
1373
+ * application result nor a receipt to report.
1374
+ */
1375
+ permission_id: OptionalString,
1376
+ permission_assignment_id: OptionalString,
1377
+ permission_operation: Schema.optional(Schema.Literals([
1378
+ "create",
1379
+ "change",
1380
+ "assign",
1381
+ "revoke",
1382
+ "replace"
1383
+ ])),
1384
+ revision: Schema.optional(Schema.Number),
1385
+ user_op_hash: OptionalString,
1386
+ transaction_hash: OptionalString,
1387
+ block_number: Schema.optional(Schema.Number),
1388
+ log_index: Schema.optional(Schema.Number)
1367
1389
  });
1368
1390
  const MovementScanWindowProps = Schema.Struct({
1369
1391
  ...TelemetryEnvelopeProps,
@@ -1909,6 +1931,12 @@ Schema.Struct({
1909
1931
  "settled"
1910
1932
  ]),
1911
1933
  run_id: Schema.String,
1934
+ /**
1935
+ * The `paymentCommands` row at `payrollRuns.batchRef` — the durable owner of
1936
+ * this run's Payment membership. `payment_count` counts THAT command's
1937
+ * `paymentIds`, so an unrelated Payment quoting the run id changes neither.
1938
+ */
1939
+ command_id: OptionalString,
1912
1940
  payment_count: Schema.optional(Schema.Number),
1913
1941
  occurred_at: Schema.Number
1914
1942
  })
@@ -2295,6 +2323,7 @@ const CAPXUL_FUNCTIONS = {
2295
2323
  list: "movement/activity:list",
2296
2324
  summary: "movement/activity:summary"
2297
2325
  },
2326
+ "movement/annotations": { overlay: "movement/annotations:overlay" },
2298
2327
  "moneyExecution/actions": {
2299
2328
  abandonPaymentExecution: "moneyExecution/actions:abandonPaymentExecution",
2300
2329
  preparePermissionExecution: "moneyExecution/actions:preparePermissionExecution",
@@ -2893,7 +2922,19 @@ const StoredPermissionCommandSchema = Schema.Struct({
2893
2922
  roleKey: RoleKeySchema,
2894
2923
  allowanceKey: Schema.NullOr(AllowanceKeySchema),
2895
2924
  memberSafe: Schema.NullOr(SafeAddressSchema),
2896
- expectedAuthorityEvents: Schema.Array(Bytes32Schema)
2925
+ expectedAuthorityEvents: Schema.Array(Bytes32Schema),
2926
+ /**
2927
+ * #1961 R03. The authenticated user who created the command, and the browser
2928
+ * linkage captured with it. Delayed verification and bounded retries read
2929
+ * this snapshot instead of the current viewer, so one command keeps one
2930
+ * origin. Both are absent on commands created before this contract.
2931
+ *
2932
+ * The context stays `unknown` on purpose: `sanitizeObservationContext` owns
2933
+ * the field allowlist, and every reader passes the stored value through it
2934
+ * rather than repeating those rules here.
2935
+ */
2936
+ originatingAuthUserId: Schema.optional(Schema.String),
2937
+ originatingObservationContext: Schema.optional(Schema.Unknown)
2897
2938
  });
2898
2939
  Schema.Struct({
2899
2940
  command: StoredPermissionCommandSchema,
@@ -5666,6 +5707,7 @@ const financialOpsContract = {
5666
5707
  activitySummary: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].summary),
5667
5708
  activityGet: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].get),
5668
5709
  activityAnnotate: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].annotate),
5710
+ activityOverlay: makeFunctionReference(CAPXUL_FUNCTIONS["movement/annotations"].overlay),
5669
5711
  verifyPaymentDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].verifyPaymentDocument),
5670
5712
  renderStoredDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].renderStoredDocument)
5671
5713
  };
@@ -5682,8 +5724,357 @@ const moneyExecutionContract = {
5682
5724
  submitPaymentCommandExecution: makeFunctionReference(CAPXUL_FUNCTIONS["moneyExecution/paymentCommandActions"].submitPaymentCommandExecution)
5683
5725
  };
5684
5726
  //#endregion
5727
+ //#region src/ports/indexer-read.ts
5728
+ /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
5729
+ const INDEXER_MAX_LIMIT = 1e3;
5730
+ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
5731
+ /**
5732
+ * Refuse a page the indexer would refuse, before any implementation spends a
5733
+ * round trip on it. Every implementation calls this, so a consumer test written
5734
+ * against the hermetic adapter cannot pass on input production rejects. Throws
5735
+ * a `CapxulError`; callers convert it to an `IndexerReadError`.
5736
+ */
5737
+ function assertIndexerPage(input) {
5738
+ const safe = String(input.safe).toLowerCase();
5739
+ if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
5740
+ if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 1e3) throw Errors.invalidInput("limit", `must be an integer from 1 to ${INDEXER_MAX_LIMIT}`);
5741
+ const offset = input.offset ?? 0;
5742
+ if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
5743
+ return safe;
5744
+ }
5745
+ var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
5746
+ function indexerReadErrorFromCapxul(operation, error, cause = error) {
5747
+ return new IndexerReadError({
5748
+ operation,
5749
+ publicCode: error.code,
5750
+ publicError: error,
5751
+ cause,
5752
+ ...error.details === void 0 ? {} : { details: error.details }
5753
+ });
5754
+ }
5755
+ Context.Service()("@capxul/sdk/ports/IndexerReadPort");
5756
+ //#endregion
5757
+ //#region src/surface/activity-stitch.ts
5758
+ /** Epoch milliseconds of the block that carried the row. */
5759
+ function blockMillis(row) {
5760
+ return Number(row.timestamp) * 1e3;
5761
+ }
5762
+ /** `0x1234…abcd` — the counterparty of an unclaimed transfer is an address. */
5763
+ function shortAddressLabel(address) {
5764
+ return address.length <= 12 ? address : `${address.slice(0, 6)}…${address.slice(-4)}`;
5765
+ }
5766
+ function directionFor(safe, transfer) {
5767
+ const from = transfer.from.toLowerCase();
5768
+ const to = transfer.to.toLowerCase();
5769
+ if (from === safe && to === safe) return "self";
5770
+ return to === safe ? "in" : "out";
5771
+ }
5772
+ /**
5773
+ * The Transfer a protocol Payment moved is the same money as its `payment`
5774
+ * row. One actor sees one item for it, so the Transfer is claimed and does not
5775
+ * become a second row. Another transfer in the same transaction that this
5776
+ * payment did not move stays unclaimed and keeps its own row.
5777
+ */
5778
+ function claimedTransferIds(transfers, payments) {
5779
+ const claimed = /* @__PURE__ */ new Set();
5780
+ for (const payment of payments) {
5781
+ const match = transfers.find((transfer) => !claimed.has(transfer.id) && transfer.txHash === payment.txHash && transfer.token.toLowerCase() === payment.token.toLowerCase() && transfer.amount === payment.amount && transfer.to.toLowerCase() === payment.recipient.toLowerCase());
5782
+ if (match !== void 0) claimed.add(match.id);
5783
+ }
5784
+ return claimed;
5785
+ }
5786
+ function withAnnotation(item, annotation) {
5787
+ return annotation?.counterpartyLabel === void 0 ? item : {
5788
+ ...item,
5789
+ counterpartyLabel: annotation.counterpartyLabel
5790
+ };
5791
+ }
5792
+ /**
5793
+ * Confirmation comes from the chain, never from Convex.
5794
+ *
5795
+ * A Ponder row for this Payment's settlement means settled, and its receipt is
5796
+ * the indexed transaction. No Ponder row and a Convex row that still says
5797
+ * settled means the block that carried it was dropped: the row goes back to
5798
+ * `settling` and its annotation, which keys on the Payment id, is untouched.
5799
+ */
5800
+ function confirmPayment(intent, chain, hasSettlement) {
5801
+ if (chain !== void 0) return {
5802
+ ...intent,
5803
+ status: "settled",
5804
+ receipt: {
5805
+ chainId: chain.chainId,
5806
+ transactionHash: chain.txHash
5807
+ },
5808
+ updatedAt: blockMillis(chain)
5809
+ };
5810
+ if (hasSettlement && intent.status === "settled") return {
5811
+ ...intent,
5812
+ status: "settling",
5813
+ receipt: null
5814
+ };
5815
+ return intent;
5816
+ }
5817
+ /**
5818
+ * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
5819
+ * denominate its asset. The indexer serves only configured assets, so a row
5820
+ * the registry does not know means the SDK and that deployment disagree; it is
5821
+ * dropped rather than shown with a guessed currency.
5822
+ */
5823
+ function transferItem(safe, transfer, annotation) {
5824
+ const asset = configuredMoneyAssetById(transfer.assetId);
5825
+ if (asset === null) return null;
5826
+ const direction = directionFor(safe, transfer);
5827
+ const counterparty = direction === "in" ? transfer.from : transfer.to;
5828
+ const at = blockMillis(transfer);
5829
+ return withAnnotation({
5830
+ kind: "movement",
5831
+ id: transfer.id,
5832
+ classification: "movement_only_outflow",
5833
+ amount: {
5834
+ currency: asset.peg ?? asset.symbol,
5835
+ value: formatUnits(transfer.amount, asset.decimals),
5836
+ decimals: asset.decimals
5837
+ },
5838
+ direction,
5839
+ counterpartyLabel: shortAddressLabel(counterparty),
5840
+ receipt: {
5841
+ chainId: transfer.chainId,
5842
+ transactionHash: transfer.txHash
5843
+ },
5844
+ createdAt: at,
5845
+ updatedAt: at
5846
+ }, annotation);
5847
+ }
5848
+ /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
5849
+ function compareItems(left, right) {
5850
+ if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
5851
+ if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
5852
+ if (left.id === right.id) return 0;
5853
+ return left.id < right.id ? 1 : -1;
5854
+ }
5855
+ function stitchActivity(input) {
5856
+ const safe = input.safe.toLowerCase();
5857
+ const chainBySettlement = /* @__PURE__ */ new Map();
5858
+ for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
5859
+ const chainByPaymentId = /* @__PURE__ */ new Map();
5860
+ const settledPaymentIds = /* @__PURE__ */ new Set();
5861
+ for (const link of input.links) {
5862
+ settledPaymentIds.add(link.paymentId);
5863
+ const row = chainBySettlement.get(link.settlementId.toLowerCase());
5864
+ if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
5865
+ }
5866
+ const annotations = /* @__PURE__ */ new Map();
5867
+ for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
5868
+ const items = [];
5869
+ const claiming = [];
5870
+ for (const intent of input.intents) {
5871
+ if (intent.kind !== "payment") continue;
5872
+ const chain = chainByPaymentId.get(intent.id);
5873
+ if (chain !== void 0) claiming.push(chain);
5874
+ items.push(withAnnotation(confirmPayment(intent, chain, settledPaymentIds.has(intent.id)), annotations.get(`payment:${intent.id}`)));
5875
+ }
5876
+ const claimed = claimedTransferIds(input.transfers, claiming);
5877
+ for (const transfer of input.transfers) {
5878
+ if (claimed.has(transfer.id)) continue;
5879
+ const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
5880
+ if (item !== null) items.push(item);
5881
+ }
5882
+ return items.sort(compareItems);
5883
+ }
5884
+ /** The reference ids one stitched page needs its Convex overlay for. */
5885
+ function overlayReferences(items) {
5886
+ return items.map((item) => ({
5887
+ kind: item.kind,
5888
+ id: item.id
5889
+ }));
5890
+ }
5891
+ /**
5892
+ * Money in, money out and the pending share, one bucket per currency and
5893
+ * precision. Two assets that peg to the same currency at different precision
5894
+ * stay distinct buckets: their raw units are not interchangeable.
5895
+ */
5896
+ function summarizeItems(items) {
5897
+ const buckets = /* @__PURE__ */ new Map();
5898
+ for (const item of items) {
5899
+ const key = `${item.amount.currency}:${String(item.amount.decimals)}`;
5900
+ const bucket = buckets.get(key) ?? {
5901
+ currency: item.amount.currency,
5902
+ decimals: item.amount.decimals,
5903
+ in: 0n,
5904
+ out: 0n,
5905
+ pendingIn: 0n
5906
+ };
5907
+ const raw = rawUnits(item.amount.value, item.amount.decimals);
5908
+ if (item.direction === "in") {
5909
+ bucket.in += raw;
5910
+ if (item.kind === "payment" && item.status !== "settled") bucket.pendingIn += raw;
5911
+ } else if (item.direction === "out") bucket.out += raw;
5912
+ buckets.set(key, bucket);
5913
+ }
5914
+ return [...buckets.values()].map((bucket) => ({
5915
+ currency: bucket.currency,
5916
+ decimals: bucket.decimals,
5917
+ in: bucket.in.toString(),
5918
+ out: bucket.out.toString(),
5919
+ net: (bucket.in - bucket.out).toString(),
5920
+ pendingIn: bucket.pendingIn.toString()
5921
+ }));
5922
+ }
5923
+ /** The inverse of `formatUnits`, kept exact: `"1.25"` at 6 decimals is `1250000n`. */
5924
+ function rawUnits(value, decimals) {
5925
+ const negative = value.startsWith("-");
5926
+ const [whole = "0", fraction = ""] = (negative ? value.slice(1) : value).split(".");
5927
+ const raw = BigInt(`${whole}${fraction.padEnd(decimals, "0").slice(0, decimals)}`);
5928
+ return negative ? -raw : raw;
5929
+ }
5930
+ //#endregion
5931
+ //#region src/surface/activity-read.ts
5932
+ /** How many Convex intent rows one page reads for its labels and pending rows. */
5933
+ const INTENT_WINDOW = 100;
5934
+ /** The window a summary aggregates over. The indexer's own page bound. */
5935
+ const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
5936
+ function parseCursor(value) {
5937
+ if (value === void 0) return 0;
5938
+ try {
5939
+ const parsed = JSON.parse(value);
5940
+ return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
5941
+ } catch {
5942
+ return 0;
5943
+ }
5944
+ }
5945
+ function inRange(item, range) {
5946
+ if (range?.from !== void 0 && item.createdAt < range.from) return false;
5947
+ return range?.to === void 0 || item.createdAt <= range.to;
5948
+ }
5949
+ /** The caller's filter applied to the merged page. The join owns the order. */
5950
+ function matchesFilter(item, filter) {
5951
+ if (filter === void 0) return true;
5952
+ if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
5953
+ if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
5954
+ if (filter.status === void 0) return true;
5955
+ return item.kind === "payment" && filter.status.includes(item.status);
5956
+ }
5957
+ function overlay(deps, actor, controls, input) {
5958
+ return deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
5959
+ ...actor === void 0 ? {} : { actor },
5960
+ ...input.references === void 0 ? {} : { references: input.references },
5961
+ ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds }
5962
+ } }));
5963
+ }
5964
+ function convexPage(deps, actor, controls, input) {
5965
+ return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
5966
+ ...actor === void 0 ? {} : { actor },
5967
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor },
5968
+ ...input.limit === void 0 ? {} : { limit: input.limit },
5969
+ ...input.range === void 0 ? {} : { range: input.range }
5970
+ } }));
5971
+ }
5972
+ /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
5973
+ function chainRows(deps, safe, limit) {
5974
+ const page = {
5975
+ safe,
5976
+ limit: Math.min(limit, INDEXER_MAX_LIMIT)
5977
+ };
5978
+ return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
5979
+ transfers,
5980
+ payments
5981
+ })), Effect.catch(() => Effect.succeed(null)));
5982
+ }
5983
+ /** The stitched, filtered rows of one window, newest first. */
5984
+ function stitchedWindow(deps, actor, controls, window, params) {
5985
+ return Effect.gen(function* () {
5986
+ const scope = yield* overlay(deps, actor, controls, {});
5987
+ const convex = yield* convexPage(deps, actor, controls, {
5988
+ limit: INTENT_WINDOW,
5989
+ ...params.range === void 0 ? {} : { range: params.range }
5990
+ });
5991
+ const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
5992
+ if (rows === null || scope.safe === null) return {
5993
+ items: convex.items.filter((item) => matchesFilter(item, params.filter)),
5994
+ stitched: false
5995
+ };
5996
+ const decoration = yield* overlay(deps, actor, controls, {
5997
+ references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
5998
+ kind: "movement",
5999
+ id: row.id
6000
+ }))],
6001
+ settlementIds: rows.payments.map((row) => row.settlementId)
6002
+ });
6003
+ return {
6004
+ items: stitchActivity({
6005
+ safe: scope.safe,
6006
+ transfers: rows.transfers,
6007
+ payments: rows.payments,
6008
+ intents: convex.items,
6009
+ links: decoration.links,
6010
+ annotations: decoration.annotations
6011
+ }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
6012
+ stitched: true
6013
+ };
6014
+ });
6015
+ }
6016
+ function readActivityPage(deps, actor, controls, params) {
6017
+ const limit = params?.limit ?? 25;
6018
+ const offset = parseCursor(params?.cursor);
6019
+ return stitchedWindow(deps, actor, controls, offset + limit + 1, {
6020
+ ...params?.range === void 0 ? {} : { range: params.range },
6021
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
6022
+ }).pipe(Effect.map(({ items, stitched }) => {
6023
+ const observedAt = Date.now();
6024
+ if (!stitched) return {
6025
+ items: items.slice(0, limit),
6026
+ cursor: null,
6027
+ checkpoint: observedAt,
6028
+ observedAt
6029
+ };
6030
+ return {
6031
+ items: items.slice(offset, offset + limit),
6032
+ cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
6033
+ checkpoint: observedAt,
6034
+ observedAt
6035
+ };
6036
+ }));
6037
+ }
6038
+ function readActivitySummary(deps, actor, controls, params) {
6039
+ return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items }) => ({
6040
+ window: {
6041
+ from: params?.window?.from ?? null,
6042
+ to: params?.window?.to ?? null
6043
+ },
6044
+ totals: summarizeItems(items),
6045
+ count: items.length,
6046
+ observedAt: Date.now()
6047
+ })));
6048
+ }
6049
+ /**
6050
+ * Tell the caller when the indexed page for this Safe changed.
6051
+ *
6052
+ * The transport is the indexer's one SSE connection, wired in the Ponder
6053
+ * adapter under `activity.subscribe`. A reactive snapshot is not a product
6054
+ * event, so nothing here emits telemetry: the adapter reports a teardown, and
6055
+ * the caller re-reads.
6056
+ */
6057
+ function subscribeActivity(deps, actor, controls, onChange) {
6058
+ return Effect.gen(function* () {
6059
+ const scope = yield* overlay(deps, actor, controls, {});
6060
+ if (scope.safe === null) return () => {};
6061
+ const page = {
6062
+ safe: scope.safe,
6063
+ limit: INTENT_WINDOW
6064
+ };
6065
+ let delivered = false;
6066
+ const notify = () => {
6067
+ if (delivered) onChange();
6068
+ delivered = true;
6069
+ };
6070
+ return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
6071
+ if (snapshot.status === "ok") notify();
6072
+ });
6073
+ });
6074
+ }
6075
+ //#endregion
5685
6076
  //#region package.json
5686
- var version = "4.2.0-rc.10";
6077
+ var version = "4.2.0-rc.12";
5687
6078
  //#endregion
5688
6079
  //#region src/telemetry/exception-projection.ts
5689
6080
  /** Fixed fallback for failures that have no safe message. */
@@ -5881,10 +6272,15 @@ function observeFailedResult(result, adapter, operation, origin) {
5881
6272
  * A reactive stream is torn down after its call already returned, so nothing
5882
6273
  * downstream observes it. `captureException` is the right kind: the caller did
5883
6274
  * not ask for the end, so it is not an expected product outcome.
6275
+ *
6276
+ * A caller that DID ask for the end — the session it authenticated with
6277
+ * expired, and the service closed the stream on purpose — passes
6278
+ * `kind: "operation"` instead, so the refusal lands in the expected-outcome
6279
+ * stream its code already declares.
5884
6280
  */
5885
- function observeStreamFailure(adapter, operation, cause) {
6281
+ function observeStreamFailure(adapter, operation, cause, kind = "exception") {
5886
6282
  if (adapter === void 0) return;
5887
- report(adapter, "exception", operation, cause, resolveAdapterSnapshot(adapter));
6283
+ report(adapter, kind, operation, cause, resolveAdapterSnapshot(adapter));
5888
6284
  }
5889
6285
  /**
5890
6286
  * One failure, one event. The identity machine reports a failed transition
@@ -6989,6 +7385,11 @@ function paymentExecutionIntent(input) {
6989
7385
  }
6990
7386
  function makeFinancialOpsMethods(deps) {
6991
7387
  const fns = deps.functions ?? financialOpsContract;
7388
+ const indexerDeps = deps.indexerRead === void 0 ? void 0 : {
7389
+ indexer: deps.indexerRead,
7390
+ convexCall: deps.convexCall,
7391
+ functions: fns
7392
+ };
6992
7393
  const executionFns = deps.moneyExecutionFunctions ?? moneyExecutionContract;
6993
7394
  const requestKeyScope = deps.requestKeyScope ?? randomPaymentRequestKey();
6994
7395
  const inFlightPersonalPayments = /* @__PURE__ */ new Map();
@@ -7219,22 +7620,26 @@ function makeFinancialOpsMethods(deps) {
7219
7620
  }
7220
7621
  },
7221
7622
  activity: {
7623
+ subscribe: (onChange, params, options) => {
7624
+ const controls = deps.invocationControls?.(options) ?? options;
7625
+ return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => indexerDeps === void 0 ? Effect.succeed(() => {}) : Effect.suspend(() => subscribeActivity(indexerDeps, actor, controls, onChange)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.subscribe, deps.runPromise);
7626
+ },
7222
7627
  list: (params, options) => {
7223
7628
  const controls = deps.invocationControls?.(options) ?? options;
7224
- return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => deps.convexCall.query(fns.activityList, copyInvocationObservation(controls, { input: {
7629
+ return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => indexerDeps === void 0 ? deps.convexCall.query(fns.activityList, copyInvocationObservation(controls, { input: {
7225
7630
  ...actor === void 0 ? {} : { actor },
7226
7631
  ...params?.cursor === void 0 ? {} : { cursor: params.cursor },
7227
7632
  ...params?.limit === void 0 ? {} : { limit: params.limit },
7228
7633
  ...params?.range === void 0 ? {} : { range: params.range },
7229
7634
  ...params?.filter === void 0 ? {} : { filter: params.filter }
7230
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
7635
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
7231
7636
  },
7232
7637
  summary: (params, options) => {
7233
7638
  const controls = deps.invocationControls?.(options) ?? options;
7234
- return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => deps.convexCall.query(fns.activitySummary, copyInvocationObservation(controls, { input: {
7639
+ return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => indexerDeps === void 0 ? deps.convexCall.query(fns.activitySummary, copyInvocationObservation(controls, { input: {
7235
7640
  ...actor === void 0 ? {} : { actor },
7236
7641
  ...params?.window === void 0 ? {} : { window: params.window }
7237
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
7642
+ } })) : readActivitySummary(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
7238
7643
  },
7239
7644
  get: (reference, options) => {
7240
7645
  const controls = deps.invocationControls?.(options) ?? options;
@@ -9041,6 +9446,9 @@ const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
9041
9446
  "org/actions:resumeBootstrapSubmission",
9042
9447
  "org/actions:confirmBootstrap",
9043
9448
  "org/actions:readTreasury",
9449
+ "moneyExecution/actions:preparePermissionExecution",
9450
+ "moneyExecution/actions:submitPermissionExecution",
9451
+ "permission/actions:verify",
9044
9452
  "moneyExecution/actions:preparePaymentExecution",
9045
9453
  "moneyExecution/actions:abandonPaymentExecution",
9046
9454
  "moneyExecution/actions:submitPaymentExecution",
@@ -9065,6 +9473,9 @@ const OBSERVED_CONVEX_QUERIES = /* @__PURE__ */ new Set([
9065
9473
  "payroll/queries:groups",
9066
9474
  "payroll/queries:terms",
9067
9475
  "financialOps/queries:resolveRecipient",
9476
+ "org/queries:me",
9477
+ "permission/queries:read",
9478
+ "permission/queries:authorize",
9068
9479
  "identity/queries:loadByAuthUserId",
9069
9480
  "smartAccount/queries:loadByAuthUserId",
9070
9481
  "org/lifecycle:load"
@@ -9083,6 +9494,7 @@ const OBSERVED_CONVEX_MUTATIONS = /* @__PURE__ */ new Set([
9083
9494
  "financialOps/requestsInbox:decline",
9084
9495
  "payroll/mutations:saveGroup",
9085
9496
  "payroll/mutations:removeGroup",
9497
+ "permission/mutations:command",
9086
9498
  "identity/mutations:create",
9087
9499
  "identity/mutations:update",
9088
9500
  "identity/mutations:completeOnboarding",
@@ -12139,8 +12551,33 @@ function toOrgMe(wire) {
12139
12551
  observedAt: wire.observedAt
12140
12552
  };
12141
12553
  }
12554
+ /**
12555
+ * The read runs through the canonical observed runner, so `org.me` owns one
12556
+ * `capxul_sdk_operation` record and one span like every other public method.
12557
+ *
12558
+ * The call-start invocation snapshot travels on the query arguments: the
12559
+ * Convex adapter reads it there and sends the browser linkage with the call,
12560
+ * so an account switch mid-flight cannot re-resolve the actor. A successful
12561
+ * read proves participation in this Organization, so the owning span carries
12562
+ * the verified Organization and the two authority booleans the backend
12563
+ * actually enforces. Labels and Budget caps stay off the record.
12564
+ */
12142
12565
  function makeOrgMeMethod(deps, orgId) {
12143
- return (options) => runIfActive(options?.signal, CAPXUL_OPERATIONS.org.me, () => Effect.map(deps.convexCall.query(orgReadContract.me, { orgId }), toOrgMe));
12566
+ return (options) => {
12567
+ const controls = deps.invocationControls?.(options) ?? options;
12568
+ return runCapxulEffect(Effect.gen(function* () {
12569
+ const wire = yield* deps.convexCall.query(orgReadContract.me, copyInvocationObservation(controls, { orgId })).pipe(Effect.mapError((failure) => failure.publicError));
12570
+ yield* Effect.annotateCurrentSpan({
12571
+ verified_organization_id: orgId,
12572
+ scope_kind: "organization",
12573
+ can_manage_people: wire.capabilities.canManagePeople,
12574
+ can_spend: wire.capabilities.canSpend,
12575
+ budget_count: wire.budgets.length,
12576
+ observed_at: wire.observedAt
12577
+ }).pipe(Effect.catchCause(() => Effect.void));
12578
+ return toOrgMe(wire);
12579
+ }), controls, CAPXUL_OPERATIONS.org.me, deps.runPromise);
12580
+ };
12144
12581
  }
12145
12582
  //#endregion
12146
12583
  //#region src/contract/permission.ts
@@ -12160,16 +12597,24 @@ function isAborted(signal) {
12160
12597
  */
12161
12598
  function observed(deps, operation, options, run) {
12162
12599
  const controls = deps.invocationControls?.(options) ?? options;
12600
+ let verifiedOrganizationId;
12163
12601
  return runCapxulEffect(Effect.gen(function* () {
12164
12602
  if (controls?.signal?.aborted === true) return yield* Effect.fail(Errors.cancelled({ operation }));
12603
+ const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
12604
+ const captured = carryInvocationParentSpan(controls, parent);
12165
12605
  const result = yield* Effect.tryPromise({
12166
- try: () => run(controls?.signal),
12606
+ try: () => run(controls?.signal, captured, (orgId) => {
12607
+ verifiedOrganizationId = orgId;
12608
+ }),
12167
12609
  catch: (cause) => cause instanceof CapxulError ? cause : Errors.unknown(cause)
12168
12610
  });
12169
12611
  return yield* result.ok ? Effect.succeed(result.value) : Effect.fail(result.error);
12170
- }), observationOnlyControls(controls), operation, deps.runPromise);
12612
+ }).pipe(Effect.ensuring(Effect.suspend(() => verifiedOrganizationId === void 0 ? Effect.void : Effect.annotateCurrentSpan({
12613
+ verified_organization_id: verifiedOrganizationId,
12614
+ scope_kind: "organization"
12615
+ })).pipe(Effect.catchCause(() => Effect.void)))), observationOnlyControls(controls), operation, deps.runPromise);
12171
12616
  }
12172
- async function execute(deps, orgId, command, signal) {
12617
+ async function execute(deps, orgId, command, signal, controls, verifiedOrganization) {
12173
12618
  if (isAborted(signal)) return {
12174
12619
  ok: false,
12175
12620
  error: Errors.cancelled({ operation: "permissions" })
@@ -12193,17 +12638,18 @@ async function execute(deps, orgId, command, signal) {
12193
12638
  };
12194
12639
  }
12195
12640
  const executionFns = deps.executionFunctions ?? moneyExecutionContract;
12196
- const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => deps.convexCall.action(executionFns.preparePermissionExecution, { input: {
12641
+ const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.preparePermissionExecution, copyInvocationObservation(controls, { input: {
12197
12642
  orgId,
12198
12643
  signerAddress,
12199
12644
  command
12200
- } }));
12645
+ } }))), controls), deps.runPromise);
12201
12646
  if (!prepared.ok) return prepared;
12202
12647
  const expectedSafe = deriveCapxulSafeAddress({ email: session.email });
12203
12648
  if (prepared.value.chainId !== deps.chainId || prepared.value.signerAddress.toLowerCase() !== signerAddress.toLowerCase() || prepared.value.userOpSenderSafe.toLowerCase() !== expectedSafe.toLowerCase() || prepared.value.operation !== command.operation || prepared.value.orgId !== orgId || canonicalJson(prepared.value.command) !== canonicalJson(command)) return {
12204
12649
  ok: false,
12205
12650
  error: Errors.invalidInput("permission", "prepared authority mismatch")
12206
12651
  };
12652
+ verifiedOrganization(prepared.value.orgId);
12207
12653
  if (isAborted(signal)) return {
12208
12654
  ok: false,
12209
12655
  error: Errors.cancelled({ operation: "permissions" })
@@ -12221,38 +12667,40 @@ async function execute(deps, orgId, command, signal) {
12221
12667
  ok: false,
12222
12668
  error: Errors.cancelled({ operation: "permissions" })
12223
12669
  };
12224
- return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => deps.convexCall.action(executionFns.submitPermissionExecution, { input: {
12670
+ return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.submitPermissionExecution, copyInvocationObservation(controls, { input: {
12225
12671
  executionId: prepared.value.executionId,
12226
12672
  signature
12227
- } }));
12673
+ } }))), controls), deps.runPromise);
12228
12674
  }
12229
12675
  function makePermissionMethods(deps, orgId) {
12230
12676
  const reads = deps.permissionFunctions ?? permissionContract;
12231
12677
  const ops = CAPXUL_OPERATIONS.org.permissions;
12678
+ const read = (signal, operation, controls) => runIfActive(signal, operation, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.query(reads.read, copyInvocationObservation(controls, { orgId }))), controls), deps.runPromise);
12679
+ const command = (operation, build, options) => observed(deps, operation, options, (signal, captured, verifiedOrganization) => execute(deps, orgId, build(), signal, captured, verifiedOrganization));
12232
12680
  return {
12233
- create: (input, options) => observed(deps, ops.create, options, (signal) => execute(deps, orgId, {
12681
+ create: (input, options) => command(ops.create, () => ({
12234
12682
  operation: "create",
12235
12683
  ...input
12236
- }, signal)),
12237
- change: (input, options) => observed(deps, ops.change, options, (signal) => execute(deps, orgId, {
12684
+ }), options),
12685
+ change: (input, options) => command(ops.change, () => ({
12238
12686
  operation: "change",
12239
12687
  ...input
12240
- }, signal)),
12241
- assign: (input, options) => observed(deps, ops.assign, options, (signal) => execute(deps, orgId, {
12688
+ }), options),
12689
+ assign: (input, options) => command(ops.assign, () => ({
12242
12690
  operation: "assign",
12243
12691
  ...input
12244
- }, signal)),
12245
- revoke: (input, options) => observed(deps, ops.revoke, options, (signal) => execute(deps, orgId, {
12692
+ }), options),
12693
+ revoke: (input, options) => command(ops.revoke, () => ({
12246
12694
  operation: "revoke",
12247
12695
  ...input
12248
- }, signal)),
12249
- replace: (input, options) => observed(deps, ops.replace, options, (signal) => execute(deps, orgId, {
12696
+ }), options),
12697
+ replace: (input, options) => command(ops.replace, () => ({
12250
12698
  operation: "replace",
12251
12699
  ...input
12252
- }, signal)),
12253
- list: (options) => observed(deps, ops.list, options, (signal) => runIfActive(signal, CAPXUL_OPERATIONS.permissions.list, () => deps.convexCall.query(reads.read, { orgId }))),
12254
- get: (permissionId, options) => observed(deps, ops.get, options, async (signal) => {
12255
- const listed = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.get, () => deps.convexCall.query(reads.read, { orgId }));
12700
+ }), options),
12701
+ list: (options) => observed(deps, ops.list, options, (signal, captured) => read(signal, CAPXUL_OPERATIONS.permissions.list, captured)),
12702
+ get: (permissionId, options) => observed(deps, ops.get, options, async (signal, captured) => {
12703
+ const listed = await read(signal, CAPXUL_OPERATIONS.permissions.get, captured);
12256
12704
  if (!listed.ok) return listed;
12257
12705
  return {
12258
12706
  ok: true,
@@ -13123,7 +13571,10 @@ function makeOrgMethods(deps) {
13123
13571
  },
13124
13572
  ...convexCall === void 0 ? {} : { convexCall }
13125
13573
  }),
13126
- me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({ convexCall }, String(orgId)),
13574
+ me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({
13575
+ convexCall,
13576
+ ...observedRunnerDeps(deps)
13577
+ }, String(orgId)),
13127
13578
  async getLifecycle(options) {
13128
13579
  if (options?.signal?.aborted) return {
13129
13580
  ok: false,
@@ -13360,14 +13811,65 @@ function compareDecimal(first, second) {
13360
13811
  }
13361
13812
  //#endregion
13362
13813
  //#region src/surface/holdings.ts
13814
+ /** SPEC Freshness: past this block age the read is labelled stale. Nothing refuses. */
13815
+ const FRESHNESS_LIMIT_SECONDS = 60;
13816
+ /**
13817
+ * The Ponder balance view for one Safe as `CurrentHoldings`.
13818
+ *
13819
+ * The view is the signed sum of every indexed transfer, so the balance IS what
13820
+ * the chain said — there is no snapshot to go stale behind it. `observedAt` is
13821
+ * the indexed block's own timestamp, which is what the last-good label reads,
13822
+ * and `stale` is that block's age. A stopped indexer therefore shows an old
13823
+ * "as of" and the stale flag; it never turns the read into a refusal.
13824
+ */
13825
+ function indexerHoldings(indexer, convexCall, overlay, actor, controls) {
13826
+ return Effect.gen(function* () {
13827
+ const scope = yield* convexCall.query(overlay, copyInvocationObservation(controls, actor?.kind === "organization" ? { input: { actor: {
13828
+ kind: "organization",
13829
+ orgId: actor.organizationId
13830
+ } } } : { input: {} }));
13831
+ if (scope.safe === null) return null;
13832
+ const balances = yield* indexer.read("balance", {
13833
+ safe: toAddress(scope.safe),
13834
+ limit: INDEXER_MAX_LIMIT
13835
+ });
13836
+ const status = yield* indexer.status.pipe(Effect.catch(() => Effect.succeed([])));
13837
+ const rows = [];
13838
+ for (const balance of balances) {
13839
+ const asset = configuredMoneyAssetById(balance.assetId);
13840
+ if (asset === null || balance.amount < 0n) continue;
13841
+ rows.push({
13842
+ assetKind: "erc20",
13843
+ tokenAddress: toAddress(asset.tokenAddress),
13844
+ symbol: asset.symbol,
13845
+ decimals: asset.decimals,
13846
+ rawBalance: toWeiAmount(balance.amount.toString())
13847
+ });
13848
+ }
13849
+ const chainId = balances[0]?.chainId ?? status[0]?.chainId;
13850
+ if (chainId === void 0) return null;
13851
+ const head = status.find((entry) => entry.chainId === chainId);
13852
+ const nowSeconds = Math.floor(Date.now() / 1e3);
13853
+ const headSeconds = head === void 0 ? void 0 : Number(head.blockTimestamp);
13854
+ return {
13855
+ actor: scope.actor,
13856
+ chainId: toChainId(chainId),
13857
+ observedAt: headSeconds === void 0 ? Date.now() : headSeconds * 1e3,
13858
+ stale: headSeconds === void 0 || nowSeconds - headSeconds > FRESHNESS_LIMIT_SECONDS,
13859
+ rows
13860
+ };
13861
+ }).pipe(Effect.catch(() => Effect.succeed(null)));
13862
+ }
13363
13863
  function makeHoldingsMethods(deps) {
13364
13864
  const functions = deps.functions ?? holdingsContract;
13865
+ const overlay = (deps.financialOpsFunctions ?? financialOpsContract).activityOverlay;
13365
13866
  const read = (operation, actor, options, pick) => {
13366
13867
  const controls = deps.invocationControls?.(options) ?? options;
13367
- return runCapxulEffect(retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
13868
+ const convexSnapshot = retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
13368
13869
  kind: "organization",
13369
13870
  orgId: actor.organizationId
13370
- } } : {})), operation, deps.telemetry, controls).pipe(Effect.map(pick), Effect.mapError((error) => error.publicError)), controls, operation, deps.runPromise);
13871
+ } } : {})), operation, deps.telemetry, controls).pipe(Effect.mapError((error) => error.publicError));
13872
+ return runCapxulEffect((deps.indexerRead === void 0 ? convexSnapshot : indexerHoldings(deps.indexerRead, deps.convexCall, overlay, actor, controls).pipe(Effect.flatMap((current) => current === null ? convexSnapshot : Effect.succeed(current)))).pipe(Effect.map(pick)), controls, operation, deps.runPromise);
13371
13873
  };
13372
13874
  return {
13373
13875
  current: (input, options) => read(CAPXUL_OPERATIONS.holdings.current, input?.actor, options, (snapshot) => snapshot),
@@ -13393,8 +13895,8 @@ const APPLIED_ACTION_BY_EVENT = {
13393
13895
  RestoreSession: "none",
13394
13896
  ResumeOtpEntry: "none",
13395
13897
  Reset: "none",
13396
- SessionRestored: "none",
13397
- SessionAbsent: "none",
13898
+ SessionRestored: "restoration",
13899
+ SessionAbsent: "restoration",
13398
13900
  EnsureAccount: "none",
13399
13901
  ClaimAccount: "none",
13400
13902
  RetryAccount: "none",
@@ -13406,8 +13908,8 @@ const APPLIED_ACTION_BY_EVENT = {
13406
13908
  OtpFailed: "failed",
13407
13909
  Verified: "verified",
13408
13910
  VerifyFailed: "failed",
13409
- SessionRead: "none",
13410
- SessionReadFailed: "none",
13911
+ SessionRead: "restoration",
13912
+ SessionReadFailed: "restoration",
13411
13913
  SignedOut: "signed-out",
13412
13914
  SignOutFailed: "failed",
13413
13915
  AccountAt: "none",
@@ -13433,6 +13935,49 @@ const authFailure = (record, reason, mode) => ({
13433
13935
  ...linkage(record)
13434
13936
  }
13435
13937
  });
13938
+ /**
13939
+ * The label of the restore window every client boots into. A settled session
13940
+ * fact that left this label is the restoration result (#1962 R04); the same
13941
+ * facts from a signed-out or authenticated state are the routine session probe
13942
+ * that `getSession()` runs, and a probe is not a restoration attempt.
13943
+ */
13944
+ const RESTORE_WINDOW = "restoring";
13945
+ /**
13946
+ * Which result the window settled on. `SessionRestored` and `SessionAbsent` are
13947
+ * the cache door and say it themselves. `ReadSession` is also valid inside the
13948
+ * window — `getSession()` drives it on a cache miss and its `SessionRead`
13949
+ * completion settles the window — so that one event reports both halves and the
13950
+ * state it produced is what separates them.
13951
+ */
13952
+ const restorationOutcome = (event, state) => {
13953
+ switch (event) {
13954
+ case "SessionRestored": return "restored";
13955
+ case "SessionAbsent": return "absent";
13956
+ case "SessionReadFailed": return "failed";
13957
+ case "SessionRead":
13958
+ if (state.phase === "authenticated") return "restored";
13959
+ return state.phase === "signed_out" ? "absent" : void 0;
13960
+ default: return;
13961
+ }
13962
+ };
13963
+ /**
13964
+ * The restore window's one result, or nothing when this settled session fact
13965
+ * belongs to a probe rather than to the window.
13966
+ */
13967
+ const restorationObservation = (record, state) => {
13968
+ if (record.slot !== "identity:session" || record.from !== RESTORE_WINDOW) return NONE;
13969
+ const outcome = restorationOutcome(record.event, state);
13970
+ if (outcome === void 0) return NONE;
13971
+ return {
13972
+ name: "access_recovery_completed",
13973
+ props: {
13974
+ recovery: "session_restoration",
13975
+ outcome,
13976
+ ...outcome === "failed" && state.phase === "faulted" ? { reason_code: state.failure.code } : {},
13977
+ ...linkage(record)
13978
+ }
13979
+ };
13980
+ };
13436
13981
  const REFUSAL_EVENTS = /* @__PURE__ */ new Set([
13437
13982
  "ClaimAccount",
13438
13983
  "RetryAccount",
@@ -13477,6 +14022,7 @@ function mapIdentityProductObservation(record, state) {
13477
14022
  name: "auth_signed_out",
13478
14023
  props: linkage(record)
13479
14024
  };
14025
+ case "restoration": return restorationObservation(record, state);
13480
14026
  }
13481
14027
  }
13482
14028
  const ignoreTransportFailure = (operation, operationName, eventName) => Effect.suspend(operation).pipe(Effect.catchCause(() => Effect.logWarning("identity.product.telemetry.dropped").pipe(Effect.annotateLogs({
@@ -13488,7 +14034,7 @@ function executeIdentityProductObservation(telemetry, record, state) {
13488
14034
  if (mapped === null) return Effect.void;
13489
14035
  copyInvocationObservation(record, mapped);
13490
14036
  const invocationTelemetry = bindProductTelemetryInvocation(telemetry, mapped);
13491
- const after = () => mapped.name === "auth_verified" && state.phase === "authenticated" ? invocationTelemetry.identify({
14037
+ const after = () => state.phase === "authenticated" && (mapped.name === "auth_verified" || mapped.name === "access_recovery_completed") ? invocationTelemetry.identify({
13492
14038
  distinctId: state.session.authUserId,
13493
14039
  traits: { email_domain: emailDomain(state.session.email) }
13494
14040
  }) : mapped.name === "auth_signed_out" ? invocationTelemetry.reset() : Effect.void;
@@ -13533,6 +14079,51 @@ function startSignerReadinessOnClaim(actor, signer) {
13533
14079
  browserSigner.ensureWalletReady?.().catch(() => {});
13534
14080
  });
13535
14081
  }
14082
+ /**
14083
+ * Report the result of each signer readiness cycle (#1962 R04).
14084
+ *
14085
+ * The readiness store already carries the cycle boundary this needs, so no
14086
+ * public signer hook, second identity subscriber, or render counter is added.
14087
+ * `ensureOpenfortWalletReady` writes `recovering` synchronously when it starts
14088
+ * a cycle, and only the cycle that is still the current one writes `ready` or
14089
+ * `unavailable` (`create-openfort-browser-signer.ts:501-521`). Callers that
14090
+ * join a live cycle share that single write, and a `resetSession` writes
14091
+ * `unknown` first, so a cycle that a reset or an account replacement outran
14092
+ * reports nothing.
14093
+ *
14094
+ * The host context is bound when the cycle starts, so a person who signs out
14095
+ * and back in while a cycle runs cannot relabel the result that began under
14096
+ * the previous person. A signer with no readiness cycle — a node or dev key,
14097
+ * and a headless client whose requirement never opens the account lane —
14098
+ * writes no status and reports nothing.
14099
+ */
14100
+ function observeSignerReadinessCycles(signer, telemetry, hostObservationSnapshot, runPromise) {
14101
+ const statusStore = signer?.statusStore;
14102
+ if (statusStore === void 0 || telemetry === void 0) return () => {};
14103
+ let running = false;
14104
+ let startContext;
14105
+ return statusStore.subscribe((status) => {
14106
+ if (status === "recovering") {
14107
+ running = true;
14108
+ startContext = hostObservationSnapshot?.();
14109
+ return;
14110
+ }
14111
+ const cycleContext = startContext;
14112
+ const completesCycle = running;
14113
+ running = false;
14114
+ startContext = void 0;
14115
+ if (!completesCycle || status !== "ready" && status !== "unavailable") return;
14116
+ const event = {
14117
+ name: "access_recovery_completed",
14118
+ props: {
14119
+ recovery: "signer_readiness",
14120
+ outcome: status
14121
+ }
14122
+ };
14123
+ if (cycleContext !== void 0) attachInvocationObservation(event, cycleContext);
14124
+ runPromise(Effect.suspend(() => bindProductTelemetryInvocation(telemetry, event).emit(event)).pipe(Effect.catchCause(() => Effect.void))).catch(() => {});
14125
+ });
14126
+ }
13536
14127
  function assembleCapxulClient(input) {
13537
14128
  const observation = input;
13538
14129
  const authCache = input.authCache ?? detectAuthCacheAdapter();
@@ -13562,12 +14153,14 @@ function assembleCapxulClient(input) {
13562
14153
  }, failureObservation, normalizeCapxulOperation(`identity.${record.event}`), "machine");
13563
14154
  });
13564
14155
  const unsubscribeSignerReadiness = startSignerReadinessOnClaim(actor, input.signer);
14156
+ const unsubscribeReadinessObservation = observeSignerReadinessCycles(input.signer, input.ports.telemetry, observation.hostObservationSnapshot, effectRunner.runPromise);
13565
14157
  let stopPromise = null;
13566
14158
  const stopActor = () => {
13567
14159
  if (stopPromise === null) {
13568
14160
  unsubscribeProductTelemetry();
13569
14161
  unsubscribeFailureObservation();
13570
14162
  unsubscribeSignerReadiness();
14163
+ unsubscribeReadinessObservation();
13571
14164
  stopPromise = effectRunner.runPromise(Scope.close(scope, Exit.void));
13572
14165
  }
13573
14166
  return stopPromise;
@@ -13584,7 +14177,8 @@ function assembleCapxulClient(input) {
13584
14177
  convexCall: input.ports.convexCall,
13585
14178
  ...input.signer === void 0 ? {} : { signer: input.signer },
13586
14179
  telemetry: input.ports.telemetry,
13587
- requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`
14180
+ requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`,
14181
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
13588
14182
  });
13589
14183
  const accountBundle = makeAccountMethods({
13590
14184
  actor,
@@ -13655,7 +14249,8 @@ function assembleCapxulClient(input) {
13655
14249
  convexCall: input.ports.convexCall,
13656
14250
  telemetry: input.ports.telemetry,
13657
14251
  runPromise: effectRunner.runPromise,
13658
- invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId)
14252
+ invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId),
14253
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
13659
14254
  });
13660
14255
  const accounts = makeAccountsMethods({
13661
14256
  accountReadPort: input.ports.accountRead,
@@ -13983,36 +14578,6 @@ function isPromiseLike(value) {
13983
14578
  return (typeof value === "object" && value !== null || typeof value === "function") && "then" in value;
13984
14579
  }
13985
14580
  //#endregion
13986
- //#region src/ports/indexer-read.ts
13987
- /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
13988
- const INDEXER_MAX_LIMIT = 1e3;
13989
- const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
13990
- /**
13991
- * Refuse a page the indexer would refuse, before any implementation spends a
13992
- * round trip on it. Every implementation calls this, so a consumer test written
13993
- * against the hermetic adapter cannot pass on input production rejects. Throws
13994
- * a `CapxulError`; callers convert it to an `IndexerReadError`.
13995
- */
13996
- function assertIndexerPage(input) {
13997
- const safe = String(input.safe).toLowerCase();
13998
- if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
13999
- if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 1e3) throw Errors.invalidInput("limit", `must be an integer from 1 to ${INDEXER_MAX_LIMIT}`);
14000
- const offset = input.offset ?? 0;
14001
- if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
14002
- return safe;
14003
- }
14004
- var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
14005
- function indexerReadErrorFromCapxul(operation, error, cause = error) {
14006
- return new IndexerReadError({
14007
- operation,
14008
- publicCode: error.code,
14009
- publicError: error,
14010
- cause,
14011
- ...error.details === void 0 ? {} : { details: error.details }
14012
- });
14013
- }
14014
- Context.Service()("@capxul/sdk/ports/IndexerReadPort");
14015
- //#endregion
14016
14581
  //#region src/adapters/ponder/decode.ts
14017
14582
  /**
14018
14583
  * Decode the indexer's rows at the trust boundary.
@@ -14318,7 +14883,7 @@ var PonderIndexerAdapter = class {
14318
14883
  }
14319
14884
  if (stopped()) return;
14320
14885
  const error = failure ?? Errors.capabilityUnavailable("indexer", operation);
14321
- observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error);
14886
+ observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error, error.code === "NOT_AUTHENTICATED" ? "operation" : "exception");
14322
14887
  callback({
14323
14888
  status: "error",
14324
14889
  error
@@ -14955,6 +15520,7 @@ function idempotentClose(close) {
14955
15520
  * and Convex see one session with one scope.
14956
15521
  */
14957
15522
  function productionIndexerReadPort(input, ports, observation) {
15523
+ if (input.activityReadSource === "convex") return void 0;
14958
15524
  if (input.indexerUrl === void 0 || input.indexerUrl === "") return void 0;
14959
15525
  return new PonderIndexerAdapter({
14960
15526
  baseUrl: normalizeHttpUrl("indexerUrl", input.indexerUrl),
@@ -1679,6 +1679,17 @@ interface ActivityAnnotationInput {
1679
1679
  * are unaffected.
1680
1680
  */
1681
1681
  interface ActivityMethods {
1682
+ /**
1683
+ * Tell the caller when the indexed page for this actor's Safe changed, over
1684
+ * the indexer's one live connection. The callback carries no rows: it is the
1685
+ * signal to re-read `list` or `summary`. Without an indexer this resolves a
1686
+ * no-op teardown, so a caller needs no branch.
1687
+ *
1688
+ * @internal-use — substrate reactive transport, like `ConvexCallPort.subscribe`.
1689
+ */
1690
+ subscribe(onChange: () => void, params?: {
1691
+ readonly actor?: ActorReference;
1692
+ }, options?: InvocationControls): Promise<CapxulResult<() => void>>;
1682
1693
  list(params?: ActivityListParams, options?: InvocationControls): Promise<CapxulResult<ActivityPage>>;
1683
1694
  summary(params?: ActivitySummaryParams, options?: InvocationControls): Promise<CapxulResult<ActivitySummary>>;
1684
1695
  get(reference: ActivityReference, options?: InvocationControls & {
@@ -2332,9 +2343,15 @@ interface AuthorizeRunInput {
2332
2343
  */
2333
2344
  readonly requestKey?: string;
2334
2345
  }
2335
- type PayrollOptions = {
2336
- readonly signal?: AbortSignal;
2337
- };
2346
+ /**
2347
+ * R13 (#1968): the declared option type is the `InvocationControls` these
2348
+ * methods have always forwarded to `deps.invocationControls` — the same naming
2349
+ * R02 gave the address book. It lets a caller that captured one browser attempt
2350
+ * hand that attempt's `correlation_id`/`journey_id` to the payroll operation it
2351
+ * starts, so `ui_payroll_action_completed` and the durable group and run events
2352
+ * share a correlation. `{ signal }` callers are unaffected.
2353
+ */
2354
+ type PayrollOptions = InvocationControls;
2338
2355
  type AuthorizeRunOptions = PayrollOptions & {
2339
2356
  /** Persist this SDK-allocated key before the first backend write. Reject to prevent execution. */
2340
2357
  readonly onRequestKey?: (key: string) => void | Promise<void>;
@@ -1,5 +1,5 @@
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-Du4Vc7YD.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-Cpz4hm8p.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. */
@@ -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-BndbwuVx.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";
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-C8FuAYm1.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, ct as CapxulError, d as authClientPortFromPromiseAdapter, et as toSessionToken, it as validateHandle, j as toAccountId, n as readClockNow, q as toKycTier, r as InMemoryAuthCacheAdapter, ut as Errors, y as deriveCapxulSafeAddress, z as toCountryCode } from "../OAuthBearerAuthClient-CbU_W9Sp.mjs";
3
3
  import { keccak256 } from "viem";
4
4
  import { Effect, Result, Semaphore } from "effect";
5
5
  import { getFunctionName } from "convex/server";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/sdk",
3
- "version": "4.2.0-rc.10",
3
+ "version": "4.2.0-rc.12",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Xelmar-tech/infrastructure.git",
@@ -47,12 +47,12 @@
47
47
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
48
48
  "vite-plus": "0.3.0",
49
49
  "vitest": "4.1.11",
50
- "@capxul/errors": "0.3.0",
50
+ "@capxul/config": "0.3.0",
51
+ "@capxul/observability": "4.2.0-rc.12",
51
52
  "@capxul/typescript-config": "0.0.0",
52
- "@capxul/observability": "4.2.0-rc.10",
53
- "@capxul/wire": "0.7.0",
54
53
  "@capxul/types": "0.3.0",
55
- "@capxul/config": "0.3.0"
54
+ "@capxul/errors": "0.3.0",
55
+ "@capxul/wire": "0.7.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": {