@capxul/sdk 4.2.0-rc.9 → 4.20.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
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-DDD0JlaI.mjs";
2
2
  import { formatUnits, keccak256, parseUnits, recoverAddress, stringToHex, toBytes } from "viem";
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";
3
+ import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Logger, Option, Queue, Ref, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
4
4
  import { getFunctionName, makeFunctionReference } from "convex/server";
5
5
  import { privateKeyToAccount } from "viem/accounts";
6
- import { FetchHttpClient, Headers, HttpClient } from "effect/unstable/http";
7
- import { OtlpExporter, OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
6
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientResponse, HttpTraceContext } from "effect/unstable/http";
7
+ import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
8
8
  import { ConvexClient } from "convex/browser";
9
9
  import { AccountTypeEnum, ChainTypeEnum, EmbeddedState, Openfort, RecoveryMethod, ThirdPartyOAuthProvider } from "@openfort/openfort-js";
10
10
  //#region src/domain/identity/model.ts
@@ -493,8 +493,12 @@ const CAPXUL_OPERATIONS = {
493
493
  cancelRequest: "financialOps.cancelRequest",
494
494
  createPayee: "financialOps.createPayee",
495
495
  declineRequest: "financialOps.declineRequest",
496
+ emitInvoiceIssued: "financialOps.emitInvoiceIssued",
497
+ emitInvoiceRequestTransitioned: "financialOps.emitInvoiceRequestTransitioned",
496
498
  emitPaymentWriterTelemetry: "financialOps.emitPaymentWriterTelemetry",
497
499
  issueRequest: "financialOps.issueRequest",
500
+ migrateEvidenceIdentity: "financialOps.migrateEvidenceIdentity",
501
+ planEvidenceIdentity: "financialOps.planEvidenceIdentity",
498
502
  recoverSettlementVerification: "financialOps.recoverSettlementVerification",
499
503
  removeDestination: "financialOps.removeDestination",
500
504
  verifySettlement: "financialOps.verifySettlement"
@@ -503,6 +507,7 @@ const CAPXUL_OPERATIONS = {
503
507
  current: "holdings.current",
504
508
  primary: "holdings.primary"
505
509
  },
510
+ indexer: { reconcile: "indexer.reconcile" },
506
511
  identity: {
507
512
  accountAt: "identity.AccountAt",
508
513
  accountClaimed: "identity.AccountClaimed",
@@ -592,6 +597,7 @@ const CAPXUL_OPERATIONS = {
592
597
  createOrg: "org.createOrg",
593
598
  create: "org.create",
594
599
  detectAndAcceptPendingInvitations: "org.detectAndAcceptPendingInvitations",
600
+ emitInviteProgressed: "org.emitInviteProgressed",
595
601
  expireInvitations: "org.expireInvitations",
596
602
  getLifecycle: "org.getLifecycle",
597
603
  getProofReceipt: "org.getProofReceipt",
@@ -695,6 +701,8 @@ const CAPXUL_OPERATIONS = {
695
701
  recipient: { resolve: "recipient.resolve" },
696
702
  payroll: {
697
703
  authorizeRun: "payroll.authorizeRun",
704
+ emitGroupChanged: "payroll.emitGroupChanged",
705
+ emitRunTransitioned: "payroll.emitRunTransitioned",
698
706
  groups: {
699
707
  access: "payroll.groups",
700
708
  list: "payroll.groups.list",
@@ -826,6 +834,10 @@ const OptionalAccountId = Schema.optional(AccountIdSchema$1);
826
834
  const OptionalWeiAmount = Schema.optional(WeiAmountSchema$1);
827
835
  const OptionalCurrencyCode = Schema.optional(CurrencyCodeSchema$1);
828
836
  const OptionalBalanceBucket = Schema.optional(BalanceBucketSchema);
837
+ /** Which Safe a money record is about. Declared here so the money-domain
838
+ * props below can name it as well as the UI-step props further down. */
839
+ const AccountKindSchema = Schema.Literals(["personalSafe", "orgTreasury"]);
840
+ const OptionalAccountKind = Schema.optional(AccountKindSchema);
829
841
  const OptionalAddress = Schema.optional(AddressSchema$1);
830
842
  const OptionalAppId = Schema.optional(AppIdSchema$1);
831
843
  const OptionalBlockNumber = Schema.optional(BlockNumberSchema$1);
@@ -993,9 +1005,22 @@ const BootstrapFailedProps = Schema.Struct({
993
1005
  reason: OptionalString,
994
1006
  target_host: OptionalString
995
1007
  });
1008
+ /**
1009
+ * R08 (#1966): which Safe was read, on which chain, of which token. `currency`
1010
+ * is the DISPLAY code the surface shows ("USD"); it never says which ERC-20 the
1011
+ * `balanceOf` actually hit, so a reader could not tell a treasury read from a
1012
+ * personal one, nor USDX from any other dollar-labelled token. The producer
1013
+ * takes each of these from the authority that already decided it — the verified
1014
+ * Organization row's chain, the configured token address — never from a label.
1015
+ * The verified `organization_id` and its group arrive from the backend
1016
+ * attribution, so they are not repeated here.
1017
+ */
996
1018
  const AccountBalanceReadProps = Schema.Struct({
997
1019
  ...TelemetryEnvelopeProps,
998
1020
  account_id: OptionalAccountId,
1021
+ account_kind: OptionalAccountKind,
1022
+ chain_id: OptionalChainId,
1023
+ token_address: OptionalAddress,
999
1024
  currency: OptionalCurrencyCode,
1000
1025
  balance_bucket: OptionalBalanceBucket,
1001
1026
  durationMs: OptionalDurationMs
@@ -1363,7 +1388,27 @@ const PermissionMirrorVerificationProps = Schema.Struct({
1363
1388
  "conflicting_replay",
1364
1389
  "revision_mismatch"
1365
1390
  ])),
1366
- command_id: OptionalString
1391
+ command_id: OptionalString,
1392
+ /**
1393
+ * #1961 R03. The authorized stored row names the Permission it changed; the
1394
+ * verified receipt names the chain facts that prove it. Every field is
1395
+ * optional because a pending or provider-failed verification has neither an
1396
+ * application result nor a receipt to report.
1397
+ */
1398
+ permission_id: OptionalString,
1399
+ permission_assignment_id: OptionalString,
1400
+ permission_operation: Schema.optional(Schema.Literals([
1401
+ "create",
1402
+ "change",
1403
+ "assign",
1404
+ "revoke",
1405
+ "replace"
1406
+ ])),
1407
+ revision: Schema.optional(Schema.Number),
1408
+ user_op_hash: OptionalString,
1409
+ transaction_hash: OptionalString,
1410
+ block_number: Schema.optional(Schema.Number),
1411
+ log_index: Schema.optional(Schema.Number)
1367
1412
  });
1368
1413
  const MovementScanWindowProps = Schema.Struct({
1369
1414
  ...TelemetryEnvelopeProps,
@@ -1389,6 +1434,33 @@ const MovementScanIncidentProps = Schema.Struct({
1389
1434
  incident_kind: Schema.String,
1390
1435
  anchor_block_number: Schema.Number
1391
1436
  });
1437
+ /**
1438
+ * One durable row the reconciliation job corrected against Ponder (SPEC D9).
1439
+ *
1440
+ * Both block fields are optional and for the same reason: a correction INTO
1441
+ * drift (a Payment settled in Convex whose Ponder fact a reorg removed) has no
1442
+ * current block, and a correction that confirms a later fact has one.
1443
+ *
1444
+ * `projection_kind` is closed. A later ticket that reconciles a deploy, a
1445
+ * bootstrap or a grant outcome adds its kind here in the same change; an
1446
+ * unknown kind must not decode.
1447
+ */
1448
+ const IndexerDriftCorrectedProps = Schema.Struct({
1449
+ ...TelemetryEnvelopeProps,
1450
+ projection_kind: Schema.Literals([
1451
+ "member_authority",
1452
+ "payment_settlement",
1453
+ "permission_assignment"
1454
+ ]),
1455
+ previous_state: Schema.String,
1456
+ current_state: Schema.String,
1457
+ previous_block: Schema.optional(Schema.Number),
1458
+ current_block: Schema.optional(Schema.Number),
1459
+ logical_event_id: Schema.optional(Schema.String),
1460
+ scope_kind: Schema.optional(Schema.Literals(["account", "organization"])),
1461
+ account_id: OptionalAccountId,
1462
+ reference_id: OptionalString
1463
+ });
1392
1464
  Schema.Struct({
1393
1465
  name: Schema.Literal("auth_otp_requested"),
1394
1466
  props: AuthOtpRequestedProps
@@ -1593,9 +1665,12 @@ Schema.Struct({
1593
1665
  name: Schema.Literal("movement_scan_incident"),
1594
1666
  props: MovementScanIncidentProps
1595
1667
  });
1668
+ Schema.Struct({
1669
+ name: Schema.Literal("indexer_drift_corrected"),
1670
+ props: IndexerDriftCorrectedProps
1671
+ });
1596
1672
  /** The one spelling of "whose scope did this happen in" across every surface. */
1597
1673
  const ScopeKindSchema = Schema.Literals(["account", "organization"]);
1598
- const AccountKindSchema = Schema.Literals(["personalSafe", "orgTreasury"]);
1599
1674
  const OptionalReasonCode = Schema.optional(Schema.Literals(CAPXUL_ERROR_CODES));
1600
1675
  const ActivityReferenceProps = {
1601
1676
  reference_kind: Schema.Literals(["payment", "movement"]),
@@ -1660,6 +1735,20 @@ const ContactActionProps = {
1660
1735
  party_id: OptionalString,
1661
1736
  reason_code: OptionalReasonCode
1662
1737
  };
1738
+ Schema.Struct({
1739
+ name: Schema.Literal("cli_diagnostic_completed"),
1740
+ props: Schema.Struct({
1741
+ ...TelemetryEnvelopeProps,
1742
+ outcome: Schema.Literals([
1743
+ "succeeded",
1744
+ "refused",
1745
+ "failed",
1746
+ "cancelled"
1747
+ ]),
1748
+ invocation_id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u)),
1749
+ cli_version: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u))
1750
+ })
1751
+ });
1663
1752
  Schema.Struct({
1664
1753
  name: Schema.Literal("ui_contact_action_completed"),
1665
1754
  props: Schema.Union([
@@ -1693,6 +1782,16 @@ Schema.Struct({
1693
1782
  Schema.Struct({
1694
1783
  name: Schema.Literal("ui_activity_action_completed"),
1695
1784
  props: Schema.Union([
1785
+ Schema.Struct({
1786
+ ...ActivityActionProps,
1787
+ action: Schema.Literal("read"),
1788
+ outcome: Schema.Literals(["partial_displayed", "recovered"]),
1789
+ surface: Schema.Literals([
1790
+ "transactions",
1791
+ "recent",
1792
+ "summary"
1793
+ ])
1794
+ }),
1696
1795
  Schema.Struct({
1697
1796
  ...ActivityActionProps,
1698
1797
  ...ActivityFilterProps,
@@ -1909,6 +2008,12 @@ Schema.Struct({
1909
2008
  "settled"
1910
2009
  ]),
1911
2010
  run_id: Schema.String,
2011
+ /**
2012
+ * The `paymentCommands` row at `payrollRuns.batchRef` — the durable owner of
2013
+ * this run's Payment membership. `payment_count` counts THAT command's
2014
+ * `paymentIds`, so an unrelated Payment quoting the run id changes neither.
2015
+ */
2016
+ command_id: OptionalString,
1912
2017
  payment_count: Schema.optional(Schema.Number),
1913
2018
  occurred_at: Schema.Number
1914
2019
  })
@@ -1941,6 +2046,71 @@ function redactTelemetryProps(name, props, options = {}) {
1941
2046
  for (const key of PII_PROP_KEYS_BY_EVENT[name] ?? []) redactProperty(clone, key);
1942
2047
  return clone;
1943
2048
  }
2049
+ /** In unstructured bodies, preserve text around explicitly named credentials. */
2050
+ const NETWORK_FIELD = /((?:"?)([A-Za-z_][A-Za-z0-9_.-]*)(?:"?)\s*[:=]\s*)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s,;&}]+)/gu;
2051
+ function maskNetworkText(text, context) {
2052
+ return redactSecrets(text).replace(NETWORK_FIELD, (whole, prefix, name, value) => {
2053
+ if (!isCredentialField(name, context)) return whole;
2054
+ const quote = value.startsWith("\"") ? "\"" : value.startsWith("'") ? "'" : "";
2055
+ return `${prefix}${quote}[REDACTED]${quote}`;
2056
+ });
2057
+ }
2058
+ function maskNetworkHeaders(headers) {
2059
+ if (headers === void 0) return void 0;
2060
+ const masked = {};
2061
+ for (const [name, value] of Object.entries(headers)) masked[name] = isCredentialField(name, "header") ? "[REDACTED]" : /^(?:location|content-location|referer)$/iu.test(name) ? redactUrlSecrets(value) : maskNetworkText(value);
2062
+ return masked;
2063
+ }
2064
+ function maskJsonValue(value, depth = 0, context) {
2065
+ if (typeof value === "function" || typeof value === "symbol") return "[UNREADABLE]";
2066
+ if (typeof value === "string") return maskNetworkText(value, context);
2067
+ if (typeof value !== "object" || value === null) return value;
2068
+ if (depth >= 16) return "[REDACTED]";
2069
+ if (Array.isArray(value)) return value.map((item) => maskJsonValue(item, depth + 1, context));
2070
+ const masked = Object.create(null);
2071
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
2072
+ if (!descriptor.enumerable) continue;
2073
+ masked[key] = isCredentialField(key, context) ? "[REDACTED]" : !("value" in descriptor) ? "[UNREADABLE]" : typeof descriptor.value === "string" && isPostHogUrlProperty(key) ? redactUrlSecrets(descriptor.value) : maskJsonValue(descriptor.value, depth + 1, context);
2074
+ }
2075
+ return masked;
2076
+ }
2077
+ function maskNetworkBody(body, context) {
2078
+ try {
2079
+ if (typeof body !== "string") return maskJsonValue(body, 0, context);
2080
+ let parsed;
2081
+ try {
2082
+ parsed = JSON.parse(body);
2083
+ } catch {
2084
+ if (/^[^=&\s]+=[\s\S]*$/u.test(body)) return new URLSearchParams(Array.from(new URLSearchParams(body), ([name, value]) => [name, isCredentialField(name, context) ? "[REDACTED]" : maskNetworkText(value)])).toString();
2085
+ return maskNetworkText(body, context);
2086
+ }
2087
+ return JSON.stringify(maskJsonValue(parsed, 0, context));
2088
+ } catch {
2089
+ return "[UNREADABLE]";
2090
+ }
2091
+ }
2092
+ /** Keep request evidence while masking credential values at the browser boundary. */
2093
+ function maskCapturedNetworkRequest(request) {
2094
+ const requestUrl = request.name ?? request.url ?? "";
2095
+ let context;
2096
+ try {
2097
+ const path = new URL(requestUrl, "https://capxul.invalid").pathname;
2098
+ if (/\/(?:auth|oauth|sign-in|signin|login|verify-otp|verify-email)(?:\/|$)/iu.test(path)) context = "authentication";
2099
+ } catch {}
2100
+ return {
2101
+ ...request,
2102
+ ...request.name === void 0 ? {} : { name: redactUrlSecrets(request.name) },
2103
+ ...request.url === void 0 ? {} : { url: redactUrlSecrets(request.url) },
2104
+ requestHeaders: maskNetworkHeaders(request.requestHeaders),
2105
+ responseHeaders: maskNetworkHeaders(request.responseHeaders),
2106
+ requestBody: maskNetworkBody(request.requestBody, context),
2107
+ responseBody: maskNetworkBody(request.responseBody)
2108
+ };
2109
+ }
2110
+ function isPostHogUrlProperty(key) {
2111
+ const normalized = key.toLowerCase();
2112
+ return normalized.includes("url") || normalized.includes("referrer") || normalized.includes("path") || normalized.includes("filename");
2113
+ }
1944
2114
  const PII_PROP_KEYS_BY_EVENT = {
1945
2115
  auth_otp_requested: ["email"],
1946
2116
  auth_otp_delivered: ["email"],
@@ -2295,6 +2465,7 @@ const CAPXUL_FUNCTIONS = {
2295
2465
  list: "movement/activity:list",
2296
2466
  summary: "movement/activity:summary"
2297
2467
  },
2468
+ "movement/annotations": { overlay: "movement/annotations:overlay" },
2298
2469
  "moneyExecution/actions": {
2299
2470
  abandonPaymentExecution: "moneyExecution/actions:abandonPaymentExecution",
2300
2471
  preparePermissionExecution: "moneyExecution/actions:preparePermissionExecution",
@@ -2372,7 +2543,10 @@ const CAPXUL_FUNCTIONS = {
2372
2543
  isUserOperationReceiptHeld: "e2eControls:isUserOperationReceiptHeld",
2373
2544
  releaseUserOperationReceipt: "e2eControls:releaseUserOperationReceipt"
2374
2545
  },
2375
- system: { health: "system:health" }
2546
+ system: {
2547
+ health: "system:health",
2548
+ healthObserved: "system:healthObserved"
2549
+ }
2376
2550
  };
2377
2551
  //#endregion
2378
2552
  //#region ../wire/src/status.ts
@@ -2893,7 +3067,19 @@ const StoredPermissionCommandSchema = Schema.Struct({
2893
3067
  roleKey: RoleKeySchema,
2894
3068
  allowanceKey: Schema.NullOr(AllowanceKeySchema),
2895
3069
  memberSafe: Schema.NullOr(SafeAddressSchema),
2896
- expectedAuthorityEvents: Schema.Array(Bytes32Schema)
3070
+ expectedAuthorityEvents: Schema.Array(Bytes32Schema),
3071
+ /**
3072
+ * #1961 R03. The authenticated user who created the command, and the browser
3073
+ * linkage captured with it. Delayed verification and bounded retries read
3074
+ * this snapshot instead of the current viewer, so one command keeps one
3075
+ * origin. Both are absent on commands created before this contract.
3076
+ *
3077
+ * The context stays `unknown` on purpose: `sanitizeObservationContext` owns
3078
+ * the field allowlist, and every reader passes the stored value through it
3079
+ * rather than repeating those rules here.
3080
+ */
3081
+ originatingAuthUserId: Schema.optional(Schema.String),
3082
+ originatingObservationContext: Schema.optional(Schema.Unknown)
2897
3083
  });
2898
3084
  Schema.Struct({
2899
3085
  command: StoredPermissionCommandSchema,
@@ -3053,7 +3239,9 @@ const ExpectedPaymentCommonSchema = Schema.Struct({
3053
3239
  chainId: BaseSepoliaChainIdSchema,
3054
3240
  contractAddress: AddressSchema,
3055
3241
  userOpHash: Bytes32Schema,
3056
- userOpSenderSafe: SafeAddressSchema
3242
+ userOpSenderSafe: SafeAddressSchema,
3243
+ /** Retained receipt location only; it cannot replace UserOperation or financial proof. */
3244
+ recordedTransactionHash: Schema.optional(Bytes32Schema)
3057
3245
  });
3058
3246
  Schema.Union([
3059
3247
  Schema.Struct({
@@ -3171,6 +3359,13 @@ Schema.Struct({
3171
3359
  transfers: Schema.Array(TransferEvidenceSchema),
3172
3360
  requestCount: nonNegativeInteger("requestCount")
3173
3361
  });
3362
+ /**
3363
+ * `chainId:txHash:transactionLogOrdinal` — the indexer's own row id. The
3364
+ * ordinal is the position in the transaction's receipt logs, never the
3365
+ * block-global index that `eventLogIndex` carries.
3366
+ */
3367
+ const LOGICAL_EVENT_ID_RE = /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:(0|[1-9][0-9]{0,5})$/u;
3368
+ const LogicalEventIdSchema = Schema.String.pipe(Schema.refine((value) => LOGICAL_EVENT_ID_RE.test(value), { message: "must be chainId:txHash:transactionLogOrdinal" }));
3174
3369
  const VerifiedMoneyEvidenceCommonSchema = Schema.Struct({
3175
3370
  chainId: BaseSepoliaChainIdSchema,
3176
3371
  contractAddress: AddressSchema,
@@ -3179,7 +3374,18 @@ const VerifiedMoneyEvidenceCommonSchema = Schema.Struct({
3179
3374
  blockNumber: BlockNumberSchema,
3180
3375
  blockHash: Bytes32Schema,
3181
3376
  blockTimestamp: Uint48Schema,
3182
- eventLogIndex: LogIndexSchema
3377
+ /**
3378
+ * The block-global log index of the settled event, as
3379
+ * `eth_getTransactionReceipt` reports it. Both readers key on this value, and
3380
+ * the ledger de-duplicates a settlement on `(txHash, eventLogIndex)`.
3381
+ */
3382
+ eventLogIndex: LogIndexSchema,
3383
+ /**
3384
+ * Stable chain event identity. Ponder supplies its row id; the receipt-backed
3385
+ * direct-send reader derives the same ordinal from the complete transaction
3386
+ * receipt. Legacy and remaining Commitment readers can omit this field.
3387
+ */
3388
+ logicalEventId: Schema.optional(LogicalEventIdSchema)
3183
3389
  });
3184
3390
  const VerifiedPaymentEvidenceSchema = Schema.Struct({
3185
3391
  ...VerifiedMoneyEvidenceCommonSchema.fields,
@@ -4795,7 +5001,9 @@ function verifiedOperationFields(attributes) {
4795
5001
  for (const [key, allowed] of [
4796
5002
  ["scope_kind", ["account", "organization"]],
4797
5003
  ["account_kind", ["personalSafe", "orgTreasury"]],
4798
- ["network", ["base-sepolia"]]
5004
+ ["network", ["base-sepolia"]],
5005
+ ["read_source", ["indexer", "convex"]],
5006
+ ["read_warning", ["indexer-unavailable"]]
4799
5007
  ]) {
4800
5008
  const value = attributes.get(key);
4801
5009
  if (typeof value === "string" && allowed.some((candidate) => candidate === value)) domain[key] = value;
@@ -5134,31 +5342,31 @@ function makeActorRelationshipMethods(deps) {
5134
5342
  reference: input.reference,
5135
5343
  ...input.memo === void 0 ? {} : { memo: input.memo },
5136
5344
  ...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt }
5137
- }))), (request) => mapActorRequest(request, input.payer)), controls, CAPXUL_OPERATIONS.requests.issue, deps.runPromise);
5345
+ }))), (request) => mapActorRequest(request, input.payer)).pipe(Effect.tap(() => observeContactScope)), controls, CAPXUL_OPERATIONS.requests.issue, deps.runPromise);
5138
5346
  },
5139
5347
  list: (options) => {
5140
5348
  const controls = deps.invocationControls?.(options) ?? options;
5141
- return runPortEffect(mapReply(CAPXUL_OPERATIONS.requests.list, Effect.suspend(() => convexCall.query(fns.requestsList, copyInvocationObservation(controls, { actor }))), (requests) => requests.map((request) => mapActorRequest(request))), controls, CAPXUL_OPERATIONS.requests.list, deps.runPromise);
5349
+ return runPortEffect(mapReply(CAPXUL_OPERATIONS.requests.list, Effect.suspend(() => convexCall.query(fns.requestsList, copyInvocationObservation(controls, { actor }))), (requests) => requests.map((request) => mapActorRequest(request))).pipe(Effect.tap(() => observeContactScope)), controls, CAPXUL_OPERATIONS.requests.list, deps.runPromise);
5142
5350
  },
5143
5351
  get: (requestId, options) => {
5144
5352
  const controls = deps.invocationControls?.(options) ?? options;
5145
5353
  return runPortEffect(mapReply(CAPXUL_OPERATIONS.requests.get, Effect.suspend(() => convexCall.query(fns.requestsGet, copyInvocationObservation(controls, {
5146
5354
  actor,
5147
5355
  paymentRequestId: requestId
5148
- }))), (request) => request === null ? null : mapActorRequest(request)), controls, CAPXUL_OPERATIONS.requests.get, deps.runPromise);
5356
+ }))), (request) => request === null ? null : mapActorRequest(request)).pipe(Effect.tap(() => observeContactScope)), controls, CAPXUL_OPERATIONS.requests.get, deps.runPromise);
5149
5357
  },
5150
5358
  cancel: (requestId, options) => {
5151
5359
  const controls = deps.invocationControls?.(options) ?? options;
5152
5360
  return runPortEffect(mapReply(CAPXUL_OPERATIONS.requests.cancel, Effect.suspend(() => convexCall.mutation(fns.requestsCancel, copyInvocationObservation(controls, {
5153
5361
  actor,
5154
5362
  paymentRequestId: requestId
5155
- }))), (request) => mapActorRequest(request)), controls, CAPXUL_OPERATIONS.requests.cancel, deps.runPromise);
5363
+ }))), (request) => mapActorRequest(request)).pipe(Effect.tap(() => observeContactScope)), controls, CAPXUL_OPERATIONS.requests.cancel, deps.runPromise);
5156
5364
  }
5157
5365
  },
5158
5366
  inbox: {
5159
5367
  list: (options) => {
5160
5368
  const controls = deps.invocationControls?.(options) ?? options;
5161
- return runPortEffect(mapReply(CAPXUL_OPERATIONS.inbox.list, Effect.suspend(() => convexCall.query(fns.inboxList, copyInvocationObservation(controls, { actor }))), (items) => items.map(mapInboxItem)), controls, CAPXUL_OPERATIONS.inbox.list, deps.runPromise);
5369
+ return runPortEffect(mapReply(CAPXUL_OPERATIONS.inbox.list, Effect.suspend(() => convexCall.query(fns.inboxList, copyInvocationObservation(controls, { actor }))), (items) => items.map(mapInboxItem)).pipe(Effect.tap(() => observeContactScope)), controls, CAPXUL_OPERATIONS.inbox.list, deps.runPromise);
5162
5370
  },
5163
5371
  approve: (input, options) => {
5164
5372
  const controls = deps.invocationControls?.(options) ?? options;
@@ -5170,14 +5378,14 @@ function makeActorRelationshipMethods(deps) {
5170
5378
  }))).pipe(Effect.flatMap((prepared) => {
5171
5379
  const result = mapApprovedInboxPayment(prepared, input);
5172
5380
  return result.ok ? Effect.succeed(result.value) : Effect.fail({ publicError: result.error });
5173
- })), controls, CAPXUL_OPERATIONS.inbox.approve, deps.runPromise);
5381
+ }), Effect.tap(() => observeContactScope)), controls, CAPXUL_OPERATIONS.inbox.approve, deps.runPromise);
5174
5382
  },
5175
5383
  decline: (requestId, options) => {
5176
5384
  const controls = deps.invocationControls?.(options) ?? options;
5177
5385
  return runPortEffect(mapReply(CAPXUL_OPERATIONS.inbox.decline, Effect.suspend(() => convexCall.mutation(fns.inboxDecline, copyInvocationObservation(controls, {
5178
5386
  actor,
5179
5387
  paymentRequestId: requestId
5180
- }))), mapInboxItem), controls, CAPXUL_OPERATIONS.inbox.decline, deps.runPromise);
5388
+ }))), mapInboxItem).pipe(Effect.tap(() => observeContactScope)), controls, CAPXUL_OPERATIONS.inbox.decline, deps.runPromise);
5181
5389
  }
5182
5390
  },
5183
5391
  profile: {
@@ -5666,6 +5874,7 @@ const financialOpsContract = {
5666
5874
  activitySummary: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].summary),
5667
5875
  activityGet: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].get),
5668
5876
  activityAnnotate: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].annotate),
5877
+ activityOverlay: makeFunctionReference(CAPXUL_FUNCTIONS["movement/annotations"].overlay),
5669
5878
  verifyPaymentDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].verifyPaymentDocument),
5670
5879
  renderStoredDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].renderStoredDocument)
5671
5880
  };
@@ -5682,8 +5891,383 @@ const moneyExecutionContract = {
5682
5891
  submitPaymentCommandExecution: makeFunctionReference(CAPXUL_FUNCTIONS["moneyExecution/paymentCommandActions"].submitPaymentCommandExecution)
5683
5892
  };
5684
5893
  //#endregion
5894
+ //#region src/ports/indexer-read.ts
5895
+ /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
5896
+ const INDEXER_MAX_LIMIT = 1e3;
5897
+ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
5898
+ /**
5899
+ * Refuse a page the indexer would refuse, before any implementation spends a
5900
+ * round trip on it. Every implementation calls this, so a consumer test written
5901
+ * against the hermetic adapter cannot pass on input production rejects. Throws
5902
+ * a `CapxulError`; callers convert it to an `IndexerReadError`.
5903
+ */
5904
+ function assertIndexerPage(input) {
5905
+ const safe = String(input.safe).toLowerCase();
5906
+ if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
5907
+ 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}`);
5908
+ const offset = input.offset ?? 0;
5909
+ if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
5910
+ return safe;
5911
+ }
5912
+ var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
5913
+ function indexerReadErrorFromCapxul(operation, error, cause = error) {
5914
+ return new IndexerReadError({
5915
+ operation,
5916
+ publicCode: error.code,
5917
+ publicError: error,
5918
+ cause,
5919
+ ...error.details === void 0 ? {} : { details: error.details }
5920
+ });
5921
+ }
5922
+ Context.Service()("@capxul/sdk/ports/IndexerReadPort");
5923
+ //#endregion
5924
+ //#region src/surface/activity-stitch.ts
5925
+ /** Epoch milliseconds of the block that carried the row. */
5926
+ function blockMillis(row) {
5927
+ return Number(row.timestamp) * 1e3;
5928
+ }
5929
+ /** `0x1234…abcd` — the counterparty of an unclaimed transfer is an address. */
5930
+ function shortAddressLabel(address) {
5931
+ return address.length <= 12 ? address : `${address.slice(0, 6)}…${address.slice(-4)}`;
5932
+ }
5933
+ function directionFor(safe, transfer) {
5934
+ const from = transfer.from.toLowerCase();
5935
+ const to = transfer.to.toLowerCase();
5936
+ if (from === safe && to === safe) return "self";
5937
+ return to === safe ? "in" : "out";
5938
+ }
5939
+ /**
5940
+ * The Transfer a protocol Payment moved is the same money as its `payment`
5941
+ * row. One actor sees one item for it, so the Transfer is claimed and does not
5942
+ * become a second row. Another transfer in the same transaction that this
5943
+ * payment did not move stays unclaimed and keeps its own row.
5944
+ */
5945
+ function claimedTransferIds(transfers, payments) {
5946
+ const claimed = /* @__PURE__ */ new Set();
5947
+ for (const payment of payments) {
5948
+ 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());
5949
+ if (match !== void 0) claimed.add(match.id);
5950
+ }
5951
+ return claimed;
5952
+ }
5953
+ function withAnnotation(item, annotation) {
5954
+ return annotation?.counterpartyLabel === void 0 ? item : {
5955
+ ...item,
5956
+ counterpartyLabel: annotation.counterpartyLabel
5957
+ };
5958
+ }
5959
+ /**
5960
+ * An indexed settlement confirms the intent. A missing row can be outside
5961
+ * this page or still backfilling; only the correction owner can prove rollback.
5962
+ */
5963
+ function confirmPayment(intent, chain) {
5964
+ if (chain !== void 0) return {
5965
+ ...intent,
5966
+ status: "settled",
5967
+ receipt: {
5968
+ chainId: chain.chainId,
5969
+ transactionHash: chain.txHash
5970
+ },
5971
+ updatedAt: blockMillis(chain)
5972
+ };
5973
+ return intent;
5974
+ }
5975
+ /**
5976
+ * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
5977
+ * denominate its asset. The indexer serves only configured assets, so a row
5978
+ * the registry does not know means the SDK and that deployment disagree; it is
5979
+ * dropped rather than shown with a guessed currency.
5980
+ */
5981
+ function transferItem(safe, transfer, annotation) {
5982
+ const asset = configuredMoneyAssetById(transfer.assetId);
5983
+ if (asset === null) return null;
5984
+ const direction = directionFor(safe, transfer);
5985
+ const counterparty = direction === "in" ? transfer.from : transfer.to;
5986
+ const at = blockMillis(transfer);
5987
+ return withAnnotation({
5988
+ kind: "movement",
5989
+ id: transfer.id,
5990
+ classification: "movement_only_outflow",
5991
+ amount: {
5992
+ currency: asset.peg ?? asset.symbol,
5993
+ value: formatUnits(transfer.amount, asset.decimals),
5994
+ decimals: asset.decimals
5995
+ },
5996
+ direction,
5997
+ counterpartyLabel: shortAddressLabel(counterparty),
5998
+ receipt: {
5999
+ chainId: transfer.chainId,
6000
+ transactionHash: transfer.txHash
6001
+ },
6002
+ createdAt: at,
6003
+ updatedAt: at
6004
+ }, annotation);
6005
+ }
6006
+ /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
6007
+ function compareItems(left, right) {
6008
+ if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
6009
+ if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
6010
+ if (left.id === right.id) return 0;
6011
+ return left.id < right.id ? 1 : -1;
6012
+ }
6013
+ function stitchActivity(input) {
6014
+ const safe = input.safe.toLowerCase();
6015
+ const chainBySettlement = /* @__PURE__ */ new Map();
6016
+ for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
6017
+ const chainByPaymentId = /* @__PURE__ */ new Map();
6018
+ for (const link of input.links) {
6019
+ const row = chainBySettlement.get(link.settlementId.toLowerCase());
6020
+ if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
6021
+ }
6022
+ const annotations = /* @__PURE__ */ new Map();
6023
+ for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
6024
+ const items = [];
6025
+ const claiming = [];
6026
+ for (const intent of input.intents) {
6027
+ if (intent.kind !== "payment") continue;
6028
+ const chain = chainByPaymentId.get(intent.id);
6029
+ if (chain !== void 0) claiming.push(chain);
6030
+ items.push(withAnnotation(confirmPayment(intent, chain), annotations.get(`payment:${intent.id}`)));
6031
+ }
6032
+ const claimed = claimedTransferIds(input.transfers, claiming);
6033
+ for (const transfer of input.transfers) {
6034
+ if (claimed.has(transfer.id)) continue;
6035
+ const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
6036
+ if (item !== null) items.push(item);
6037
+ }
6038
+ return items.sort(compareItems);
6039
+ }
6040
+ /** The reference ids one stitched page needs its Convex overlay for. */
6041
+ function overlayReferences(items) {
6042
+ return items.map((item) => ({
6043
+ kind: item.kind,
6044
+ id: item.id
6045
+ }));
6046
+ }
6047
+ /**
6048
+ * Money in, money out and the pending share, one bucket per currency and
6049
+ * precision. Two assets that peg to the same currency at different precision
6050
+ * stay distinct buckets: their raw units are not interchangeable.
6051
+ */
6052
+ function summarizeItems(items) {
6053
+ const buckets = /* @__PURE__ */ new Map();
6054
+ for (const item of items) {
6055
+ const key = `${item.amount.currency}:${String(item.amount.decimals)}`;
6056
+ const bucket = buckets.get(key) ?? {
6057
+ currency: item.amount.currency,
6058
+ decimals: item.amount.decimals,
6059
+ in: 0n,
6060
+ out: 0n,
6061
+ pendingIn: 0n
6062
+ };
6063
+ const raw = rawUnits(item.amount.value, item.amount.decimals);
6064
+ if (item.kind === "payment" && item.status !== "settled") {
6065
+ if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed") bucket.pendingIn += raw;
6066
+ } else if (item.direction === "in") bucket.in += raw;
6067
+ else if (item.direction === "out") bucket.out += raw;
6068
+ buckets.set(key, bucket);
6069
+ }
6070
+ return [...buckets.values()].map((bucket) => ({
6071
+ currency: bucket.currency,
6072
+ decimals: bucket.decimals,
6073
+ in: bucket.in.toString(),
6074
+ out: bucket.out.toString(),
6075
+ net: (bucket.in - bucket.out).toString(),
6076
+ pendingIn: bucket.pendingIn.toString()
6077
+ }));
6078
+ }
6079
+ /** The inverse of `formatUnits`, kept exact: `"1.25"` at 6 decimals is `1250000n`. */
6080
+ function rawUnits(value, decimals) {
6081
+ const negative = value.startsWith("-");
6082
+ const [whole = "0", fraction = ""] = (negative ? value.slice(1) : value).split(".");
6083
+ const raw = BigInt(`${whole}${fraction.padEnd(decimals, "0").slice(0, decimals)}`);
6084
+ return negative ? -raw : raw;
6085
+ }
6086
+ //#endregion
6087
+ //#region src/surface/activity-read.ts
6088
+ /** How many Convex intent rows one page reads for its labels and pending rows. */
6089
+ const INTENT_WINDOW = 100;
6090
+ /** movement/annotations:overlay bounds each identifier array to 200. */
6091
+ const MAX_OVERLAY_IDS = 200;
6092
+ /** The window a summary aggregates over. The indexer's own page bound. */
6093
+ const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
6094
+ function parseCursor(value) {
6095
+ if (value === void 0) return 0;
6096
+ try {
6097
+ const parsed = JSON.parse(value);
6098
+ return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
6099
+ } catch {
6100
+ return 0;
6101
+ }
6102
+ }
6103
+ function inRange(item, range) {
6104
+ if (range?.from !== void 0 && item.createdAt < range.from) return false;
6105
+ return range?.to === void 0 || item.createdAt <= range.to;
6106
+ }
6107
+ /** The caller's filter applied to the merged page. The join owns the order. */
6108
+ function matchesFilter(item, filter) {
6109
+ if (filter === void 0) return true;
6110
+ if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
6111
+ if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
6112
+ if (filter.status === void 0) return true;
6113
+ return item.kind === "payment" && filter.status.includes(item.status);
6114
+ }
6115
+ function overlay(deps, actor, controls, input) {
6116
+ const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
6117
+ const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, index) => index * MAX_OVERLAY_IDS);
6118
+ return Effect.forEach(offsets, (offset) => deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
6119
+ ...actor === void 0 ? {} : { actor },
6120
+ ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
6121
+ ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
6122
+ } })), { concurrency: 2 }).pipe(Effect.map((pages) => ({
6123
+ ...pages[0],
6124
+ links: pages.flatMap((page) => page.links),
6125
+ annotations: pages.flatMap((page) => page.annotations)
6126
+ })));
6127
+ }
6128
+ function convexPage(deps, actor, controls, input) {
6129
+ return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
6130
+ ...actor === void 0 ? {} : { actor },
6131
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor },
6132
+ ...input.limit === void 0 ? {} : { limit: input.limit },
6133
+ ...input.range === void 0 ? {} : { range: input.range }
6134
+ } }));
6135
+ }
6136
+ /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
6137
+ function chainRows(deps, safe, limit) {
6138
+ const page = {
6139
+ safe,
6140
+ limit: Math.min(limit, INDEXER_MAX_LIMIT)
6141
+ };
6142
+ return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
6143
+ transfers,
6144
+ payments
6145
+ })), Effect.catch(() => Effect.succeed(null)));
6146
+ }
6147
+ /** The stitched, filtered rows of one window, newest first. */
6148
+ function stitchedWindow(deps, actor, controls, window, params) {
6149
+ return Effect.gen(function* () {
6150
+ const scope = yield* overlay(deps, actor, controls, {});
6151
+ const convex = yield* convexPage(deps, actor, controls, {
6152
+ limit: INTENT_WINDOW,
6153
+ ...params.range === void 0 ? {} : { range: params.range }
6154
+ });
6155
+ const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
6156
+ if (rows === null || scope.safe === null) {
6157
+ if (scope.safe !== null) yield* Effect.annotateCurrentSpan({
6158
+ read_source: "convex",
6159
+ read_warning: "indexer-unavailable"
6160
+ });
6161
+ return {
6162
+ items: convex.items.filter((item) => matchesFilter(item, params.filter)),
6163
+ stitched: false,
6164
+ ...scope.safe === null ? {} : { readWarning: "indexer-unavailable" }
6165
+ };
6166
+ }
6167
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
6168
+ const decoration = yield* overlay(deps, actor, controls, {
6169
+ references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
6170
+ kind: "movement",
6171
+ id: row.id
6172
+ }))],
6173
+ settlementIds: rows.payments.map((row) => row.settlementId)
6174
+ });
6175
+ const intents = [...convex.items];
6176
+ const missing = new Set(decoration.links.map((link) => link.paymentId));
6177
+ for (const item of intents) if (item.kind === "payment") missing.delete(item.id);
6178
+ let cursor = convex.cursor;
6179
+ while (missing.size > 0 && cursor !== null) {
6180
+ const page = yield* convexPage(deps, actor, controls, {
6181
+ limit: INTENT_WINDOW,
6182
+ cursor,
6183
+ ...params.range === void 0 ? {} : { range: params.range }
6184
+ });
6185
+ for (const item of page.items) if (item.kind === "payment" && missing.delete(item.id)) intents.push(item);
6186
+ cursor = page.cursor;
6187
+ }
6188
+ const older = intents.slice(convex.items.length);
6189
+ const olderDecoration = older.length === 0 ? [] : (yield* overlay(deps, actor, controls, { references: overlayReferences(older) })).annotations;
6190
+ return {
6191
+ items: stitchActivity({
6192
+ safe: scope.safe,
6193
+ transfers: rows.transfers,
6194
+ payments: rows.payments,
6195
+ intents,
6196
+ links: decoration.links,
6197
+ annotations: [...decoration.annotations, ...olderDecoration]
6198
+ }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
6199
+ stitched: true
6200
+ };
6201
+ });
6202
+ }
6203
+ function readActivityPage(deps, actor, controls, params) {
6204
+ const limit = params?.limit ?? 25;
6205
+ const offset = parseCursor(params?.cursor);
6206
+ return stitchedWindow(deps, actor, controls, offset + limit + 1, {
6207
+ ...params?.range === void 0 ? {} : { range: params.range },
6208
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
6209
+ }).pipe(Effect.map(({ items, stitched, readWarning }) => {
6210
+ const observedAt = Date.now();
6211
+ if (!stitched) return {
6212
+ items: items.slice(0, limit),
6213
+ cursor: null,
6214
+ checkpoint: observedAt,
6215
+ observedAt,
6216
+ ...readWarning === void 0 ? {} : { readWarning }
6217
+ };
6218
+ return {
6219
+ items: items.slice(offset, offset + limit),
6220
+ cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
6221
+ checkpoint: observedAt,
6222
+ observedAt
6223
+ };
6224
+ }));
6225
+ }
6226
+ function readActivitySummary(deps, actor, controls, params) {
6227
+ return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items, readWarning }) => ({
6228
+ window: {
6229
+ from: params?.window?.from ?? null,
6230
+ to: params?.window?.to ?? null
6231
+ },
6232
+ totals: summarizeItems(items),
6233
+ count: items.length,
6234
+ observedAt: Date.now(),
6235
+ ...readWarning === void 0 ? {} : { readWarning }
6236
+ })));
6237
+ }
6238
+ /**
6239
+ * Tell the caller when the indexed page for this Safe changed.
6240
+ *
6241
+ * The transport is the indexer's one SSE connection, wired in the Ponder
6242
+ * adapter under `activity.subscribe`. A reactive snapshot is not a product
6243
+ * event, so nothing here emits telemetry: the adapter reports a teardown, and
6244
+ * the caller re-reads.
6245
+ */
6246
+ function subscribeActivity(deps, actor, controls, onChange) {
6247
+ return Effect.gen(function* () {
6248
+ const scope = yield* overlay(deps, actor, controls, {});
6249
+ if (scope.safe === null) return () => {};
6250
+ const page = {
6251
+ safe: scope.safe,
6252
+ limit: INTENT_WINDOW
6253
+ };
6254
+ let delivered = false;
6255
+ const notify = () => {
6256
+ if (delivered) onChange();
6257
+ delivered = true;
6258
+ };
6259
+ return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
6260
+ if (snapshot.status === "ok") notify();
6261
+ if (snapshot.status === "error") {
6262
+ delivered = true;
6263
+ onChange();
6264
+ }
6265
+ });
6266
+ });
6267
+ }
6268
+ //#endregion
5685
6269
  //#region package.json
5686
- var version = "4.2.0-rc.9";
6270
+ var version = "4.20.0-beta.1";
5687
6271
  //#endregion
5688
6272
  //#region src/telemetry/exception-projection.ts
5689
6273
  /** Fixed fallback for failures that have no safe message. */
@@ -5881,10 +6465,15 @@ function observeFailedResult(result, adapter, operation, origin) {
5881
6465
  * A reactive stream is torn down after its call already returned, so nothing
5882
6466
  * downstream observes it. `captureException` is the right kind: the caller did
5883
6467
  * not ask for the end, so it is not an expected product outcome.
6468
+ *
6469
+ * A caller that DID ask for the end — the session it authenticated with
6470
+ * expired, and the service closed the stream on purpose — passes
6471
+ * `kind: "operation"` instead, so the refusal lands in the expected-outcome
6472
+ * stream its code already declares.
5884
6473
  */
5885
- function observeStreamFailure(adapter, operation, cause) {
6474
+ function observeStreamFailure(adapter, operation, cause, kind = "exception") {
5886
6475
  if (adapter === void 0) return;
5887
- report(adapter, "exception", operation, cause, resolveAdapterSnapshot(adapter));
6476
+ report(adapter, kind, operation, cause, resolveAdapterSnapshot(adapter));
5888
6477
  }
5889
6478
  /**
5890
6479
  * One failure, one event. The identity machine reports a failed transition
@@ -6989,6 +7578,11 @@ function paymentExecutionIntent(input) {
6989
7578
  }
6990
7579
  function makeFinancialOpsMethods(deps) {
6991
7580
  const fns = deps.functions ?? financialOpsContract;
7581
+ const indexerDeps = deps.indexerRead === void 0 ? void 0 : {
7582
+ indexer: deps.indexerRead,
7583
+ convexCall: deps.convexCall,
7584
+ functions: fns
7585
+ };
6992
7586
  const executionFns = deps.moneyExecutionFunctions ?? moneyExecutionContract;
6993
7587
  const requestKeyScope = deps.requestKeyScope ?? randomPaymentRequestKey();
6994
7588
  const inFlightPersonalPayments = /* @__PURE__ */ new Map();
@@ -7135,10 +7729,12 @@ function makeFinancialOpsMethods(deps) {
7135
7729
  return runPortEffect(Effect.suspend(() => deps.convexCall.query(fns.depositInstructions, copyInvocationObservation(controls, {}))).pipe(Effect.tap((instructions) => emitDepositInitiated(deps.telemetry, instructions, controls))), controls, CAPXUL_OPERATIONS.me.depositInstructions, deps.runPromise);
7136
7730
  }
7137
7731
  },
7138
- targets: { resolve: async (reference, options) => {
7139
- const recipient = normalizeTargetForBackend(reference, "reference");
7140
- if (!recipient.ok) return recipient;
7141
- return mapOk(await runIfActive(options?.signal, CAPXUL_OPERATIONS.recipient.resolve, () => deps.convexCall.query(fns.resolveRecipient, { recipient: recipient.value })), resolvedTargetFromResolution);
7732
+ targets: { resolve: (reference, options) => {
7733
+ const controls = deps.invocationControls?.(options) ?? options;
7734
+ return runPortEffect(projectReply(Effect.suspend(() => {
7735
+ const recipient = normalizeTargetForBackend(reference, "reference");
7736
+ return recipient.ok ? deps.convexCall.query(fns.resolveRecipient, copyInvocationObservation(controls, { recipient: recipient.value })) : Effect.fail({ publicError: recipient.error });
7737
+ }), resolvedTargetFromResolution), controls, CAPXUL_OPERATIONS.recipient.resolve, deps.runPromise);
7142
7738
  } },
7143
7739
  destinations: {
7144
7740
  add: (input, options) => {
@@ -7201,8 +7797,14 @@ function makeFinancialOpsMethods(deps) {
7201
7797
  kind: "claim",
7202
7798
  paymentId
7203
7799
  }, deps.invocationControls?.(options) ?? options, CAPXUL_OPERATIONS.payments.claim),
7204
- list: async (options) => mapOk(await runIfActive(options?.signal, CAPXUL_OPERATIONS.payments.list, () => deps.convexCall.query(fns.listPayments, {})), (payments) => payments.map(normalizePaymentTiming)),
7205
- get: async (paymentId, options) => mapOk(await runIfActive(options?.signal, CAPXUL_OPERATIONS.payments.get, () => deps.convexCall.query(fns.getPayment, { paymentId })), (payment) => payment === null ? null : normalizePaymentTiming(payment)),
7800
+ list: (options) => {
7801
+ const controls = deps.invocationControls?.(options) ?? options;
7802
+ return runPortEffect(projectReply(Effect.suspend(() => deps.convexCall.query(fns.listPayments, copyInvocationObservation(controls, {}))), (payments) => payments.map(normalizePaymentTiming)), controls, CAPXUL_OPERATIONS.payments.list, deps.runPromise);
7803
+ },
7804
+ get: (paymentId, options) => {
7805
+ const controls = deps.invocationControls?.(options) ?? options;
7806
+ return runPortEffect(projectReply(Effect.suspend(() => deps.convexCall.query(fns.getPayment, copyInvocationObservation(controls, { paymentId }))), (payment) => payment === null ? null : normalizePaymentTiming(payment)), controls, CAPXUL_OPERATIONS.payments.get, deps.runPromise);
7807
+ },
7206
7808
  cancel: (paymentId, options) => runLifecycle({
7207
7809
  kind: "cancel",
7208
7810
  paymentId
@@ -7219,22 +7821,26 @@ function makeFinancialOpsMethods(deps) {
7219
7821
  }
7220
7822
  },
7221
7823
  activity: {
7824
+ subscribe: (onChange, params, options) => {
7825
+ const controls = deps.invocationControls?.(options) ?? options;
7826
+ 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);
7827
+ },
7222
7828
  list: (params, options) => {
7223
7829
  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: {
7830
+ 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
7831
  ...actor === void 0 ? {} : { actor },
7226
7832
  ...params?.cursor === void 0 ? {} : { cursor: params.cursor },
7227
7833
  ...params?.limit === void 0 ? {} : { limit: params.limit },
7228
7834
  ...params?.range === void 0 ? {} : { range: params.range },
7229
7835
  ...params?.filter === void 0 ? {} : { filter: params.filter }
7230
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
7836
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
7231
7837
  },
7232
7838
  summary: (params, options) => {
7233
7839
  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: {
7840
+ 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
7841
  ...actor === void 0 ? {} : { actor },
7236
7842
  ...params?.window === void 0 ? {} : { window: params.window }
7237
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
7843
+ } })) : readActivitySummary(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
7238
7844
  },
7239
7845
  get: (reference, options) => {
7240
7846
  const controls = deps.invocationControls?.(options) ?? options;
@@ -7566,6 +8172,22 @@ function normalizePaymentTiming(payment) {
7566
8172
  timing: payment.timing ?? payment.release ?? { kind: "instant" }
7567
8173
  };
7568
8174
  }
8175
+ /**
8176
+ * `mapOk`, moved inside the observed Effect (R09 #1967).
8177
+ *
8178
+ * The projection can refuse — `toAddress` and `toPartyId` throw on a malformed
8179
+ * reply — and every verb on this surface returns a `CapxulResult`, so that
8180
+ * throw stays a value here (ADR-0023). The error identity is `mapOk`'s: a
8181
+ * `CapxulError` passes through, anything else becomes `Errors.unknown(cause)`.
8182
+ * Running it inside the runner is what puts the decode failure on the same
8183
+ * operation record as the read that produced the reply.
8184
+ */
8185
+ function projectReply(program, project) {
8186
+ return program.pipe(Effect.flatMap((value) => Effect.try({
8187
+ try: () => project(value),
8188
+ catch: (cause) => ({ publicError: cause instanceof CapxulError ? cause : Errors.unknown(cause) })
8189
+ })));
8190
+ }
7569
8191
  function mapOk(result, f) {
7570
8192
  if (!result.ok) return result;
7571
8193
  try {
@@ -7766,13 +8388,18 @@ const postHogOtlpEndpoints = (host) => {
7766
8388
  const base = host.replace(/\/+$/, "");
7767
8389
  return {
7768
8390
  logs: `${base}/i/v1/logs`,
7769
- traces: `${base}/i/v1/traces`
8391
+ traces: `${base}/i/v1/traces`,
8392
+ metrics: `${base}/i/v1/metrics`
7770
8393
  };
7771
8394
  };
7772
8395
  const ENGINEERING_CAPXUL_ENV_SET = new Set(ENGINEERING_CAPXUL_ENVS);
7773
8396
  const ENGINEERING_PRODUCERS = /* @__PURE__ */ new Set(["browser", "server"]);
7774
8397
  const PUBLIC_POSTHOG_AUTHORIZATION = /^Bearer phc_[A-Za-z0-9_-]{1,191}$/u;
7775
8398
  const SAFE_RESOURCE_VALUE = /^[A-Za-z0-9][A-Za-z0-9._+@/-]{0,127}$/u;
8399
+ function validateEngineeringPrivacy(config) {
8400
+ if (config.privacy !== void 0 && config.privacy !== "minimal") throw new TypeError("engineering telemetry privacy must be minimal when specified");
8401
+ if (config.privacy === "minimal" && (!/^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(config.sdkVersion) || config.serviceName !== void 0 && !/^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(config.serviceName))) throw new TypeError("minimal engineering resource values must not contain paths");
8402
+ }
7776
8403
  const validateEngineeringTelemetryConfig = (config) => {
7777
8404
  let url;
7778
8405
  try {
@@ -7783,6 +8410,8 @@ const validateEngineeringTelemetryConfig = (config) => {
7783
8410
  if (url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0 || url.pathname !== "/" || url.search.length > 0 || url.hash.length > 0 || !(url.hostname === "posthog.com" || url.hostname.endsWith(".posthog.com"))) throw new TypeError("engineering telemetry host must be a credential-free HTTPS origin");
7784
8411
  if (!ENGINEERING_CAPXUL_ENV_SET.has(config.capxulEnv)) throw new TypeError("engineering telemetry capxulEnv is not canonical");
7785
8412
  if (!ENGINEERING_PRODUCERS.has(config.producer)) throw new TypeError("engineering telemetry producer is not canonical");
8413
+ if (config.enabled !== void 0 && typeof config.enabled !== "boolean" && typeof config.enabled !== "function") throw new TypeError("engineering telemetry enabled must be a boolean or callback");
8414
+ validateEngineeringPrivacy(config);
7786
8415
  if (!SAFE_RESOURCE_VALUE.test(config.sdkVersion)) throw new TypeError("engineering telemetry sdkVersion must be a bounded safe value");
7787
8416
  if (config.serviceName !== void 0 && !SAFE_RESOURCE_VALUE.test(config.serviceName)) throw new TypeError("engineering telemetry serviceName must be a bounded safe value");
7788
8417
  const headerEntries = Object.entries(config.headers);
@@ -7833,25 +8462,34 @@ const spanUpstream = (span) => {
7833
8462
  let current = span;
7834
8463
  while (current?._tag === "Span") {
7835
8464
  const upstream = current.attributes.get("chain.upstream");
7836
- if (upstream === "alchemy" || upstream === "infura") return upstream;
8465
+ if (upstream === "alchemy" || upstream === "infura" || upstream === "gateway") return upstream;
7837
8466
  current = current.parent._tag === "Some" ? current.parent.value : void 0;
7838
8467
  }
7839
8468
  };
8469
+ function redactHttpPath(path, fullUrl, source) {
8470
+ let origin = "https://redaction.invalid";
8471
+ try {
8472
+ const url = new URL(String(fullUrl));
8473
+ if (url.protocol === "https:" || url.protocol === "http:") origin = url.origin;
8474
+ } catch {}
8475
+ const prefix = path.startsWith("/") ? origin : "";
8476
+ return redactUrlSecrets(`${prefix}${path}`, source).slice(prefix.length, prefix.length + 2048);
8477
+ }
7840
8478
  /** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
7841
- const makeLeakSafeEngineeringTracer = (delegate) => Tracer.make({
8479
+ const makeLeakSafeEngineeringTracer = (delegate, enabled = () => true) => Tracer.make({
7842
8480
  span(options) {
7843
- const span = delegate.span(options);
8481
+ const span = enabled() ? delegate.span(options) : new Tracer.NativeSpan(options);
7844
8482
  const wrapped = Object.create(span);
7845
8483
  Object.defineProperty(wrapped, "attribute", { value: (name, value) => {
7846
8484
  const source = spanUpstream(span);
7847
8485
  const header = /^http\.(?:request|response)\.header\.(.+)$/u.exec(name)?.[1];
7848
8486
  if (header !== void 0 && isCredentialField(header, "header")) span.attribute(name, "[REDACTED]");
7849
8487
  else if (typeof value === "string" && name === "url.query") span.attribute(name, redactUrlSecrets(`?${value}`).slice(1, 2049));
7850
- else if (typeof value === "string" && name === "url.path") {
7851
- const fullUrl = span.attributes.get("url.full");
7852
- const path = typeof fullUrl === "string" ? /^https?:\/\/[^/]+([^?#]*)/u.exec(fullUrl)?.[1] ?? value : value;
7853
- span.attribute(name, redactUrlSecrets(path, source).slice(0, 2048));
7854
- } else if (typeof value === "string" && (name === "url.full" || header === "location" || header === "referer" || header === "referrer")) span.attribute(name, redactUrlSecrets(value, source).slice(0, 2048));
8488
+ else if (typeof value === "string" && (name === "url.path" || name === "http.target")) {
8489
+ const fullUrl = span.attributes.get("url.full") ?? span.attributes.get("http.url");
8490
+ const path = name === "url.path" && typeof fullUrl === "string" ? /^https?:\/\/[^/]+([^?#]*)/u.exec(fullUrl)?.[1] ?? value : value;
8491
+ span.attribute(name, redactHttpPath(path, fullUrl, source));
8492
+ } else if (typeof value === "string" && (name === "url.full" || name === "http.url" || header === "location" || header === "referer" || header === "referrer")) span.attribute(name, redactUrlSecrets(value, source).slice(0, 2048));
7855
8493
  else if (header !== void 0 && typeof value === "string") span.attribute(name, redactSecrets(value, source).slice(0, 2048));
7856
8494
  else span.attribute(name, value);
7857
8495
  } });
@@ -7859,6 +8497,15 @@ const makeLeakSafeEngineeringTracer = (delegate) => Tracer.make({
7859
8497
  configurable: false,
7860
8498
  enumerable: false,
7861
8499
  value: (endTime, exit) => {
8500
+ if (!enabled()) {
8501
+ Object.defineProperty(wrapped, "status", { value: {
8502
+ _tag: "Ended",
8503
+ startTime: span.status.startTime,
8504
+ endTime,
8505
+ exit
8506
+ } });
8507
+ return;
8508
+ }
7862
8509
  if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {
7863
8510
  const first = exit.cause.reasons.find((reason) => !Cause.isInterruptReason(reason));
7864
8511
  const code = failureText(first === void 0 ? void 0 : Cause.isFailReason(first) ? first.error : first.defect, "code", 128);
@@ -7872,53 +8519,360 @@ const makeLeakSafeEngineeringTracer = (delegate) => Tracer.make({
7872
8519
  },
7873
8520
  ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
7874
8521
  });
8522
+ const MINIMAL_LOG_NAMES = /* @__PURE__ */ new Set([
8523
+ "capxul_sdk_operation",
8524
+ "identity.transition",
8525
+ "convex.connection.changed",
8526
+ "convex.action.connection_lost",
8527
+ "cli.command.completed",
8528
+ "cli.sdk.operation.failed"
8529
+ ]);
8530
+ const MINIMAL_ERROR_CODES = /* @__PURE__ */ new Set([
8531
+ ...CAPXUL_ERROR_CODES,
8532
+ "CLI_USAGE",
8533
+ "CLI_CONFIG",
8534
+ "CLI_STORAGE",
8535
+ "CLI_DEFECT"
8536
+ ]);
8537
+ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
8538
+ "schema_version",
8539
+ "duration_ms",
8540
+ "wait_duration_ms",
8541
+ "retry_count",
8542
+ "http.status_code",
8543
+ "http.response.status_code",
8544
+ "http_status",
8545
+ "rpc.code",
8546
+ "connection_count",
8547
+ "connection_retries"
8548
+ ]);
8549
+ const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
8550
+ "correlation_id",
8551
+ "journey_id",
8552
+ "invocation_id"
8553
+ ]);
8554
+ const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
8555
+ "command",
8556
+ "outcome",
8557
+ "capxul_env",
8558
+ "unit"
8559
+ ]);
8560
+ const MINIMAL_METRIC_NAMES = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
8561
+ const traceId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{32}$/u.test(value);
8562
+ const spanId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{16}$/u.test(value);
8563
+ const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value)) ? value : "capxul.engineering";
8564
+ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
8565
+ ["outcome", [
8566
+ "succeeded",
8567
+ "refused",
8568
+ "failed",
8569
+ "cancelled",
8570
+ "applied"
8571
+ ]],
8572
+ ["mode", FAILURE_MODES$1],
8573
+ ["failure_mode", FAILURE_MODES$1],
8574
+ ["capxul_env", ENGINEERING_CAPXUL_ENVS],
8575
+ ["producer", ["browser", "server"]],
8576
+ ["command", ["doctor"]],
8577
+ ["output_mode", ["human", "json"]],
8578
+ ["unit", [
8579
+ "1",
8580
+ "s",
8581
+ "ms"
8582
+ ]]
8583
+ ]);
8584
+ const MINIMAL_CODE_KEYS = /* @__PURE__ */ new Set([
8585
+ "error.code",
8586
+ "error_code",
8587
+ "failure_code",
8588
+ "refusal_code",
8589
+ "capxul_error_code"
8590
+ ]);
8591
+ const MINIMAL_VERSION_KEYS = /* @__PURE__ */ new Set([
8592
+ "sdk_version",
8593
+ "cli_version",
8594
+ "release"
8595
+ ]);
8596
+ function minimalStringValue(key, text) {
8597
+ if (key === "operation") return isCapxulOperation(text);
8598
+ if (MINIMAL_CODE_KEYS.has(key)) return MINIMAL_ERROR_CODES.has(text);
8599
+ if (MINIMAL_VERSION_KEYS.has(key)) return /^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(text);
8600
+ if (key === "trace_id") return traceId(text);
8601
+ if (key === "span_id" || key === "parent_span_id") return spanId(text);
8602
+ const values = MINIMAL_ENUM_VALUES.get(key);
8603
+ if (values !== void 0) return values.includes(text);
8604
+ return MINIMAL_CORRELATIONS.has(key) && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(text) && !/^0x[0-9a-f]+$/iu.test(text) && redactSecrets(text) === text;
8605
+ }
8606
+ /** A named privacy projection, separate from the default credential-only redaction policy. */
8607
+ function minimalAttributes(attributes, metric = false) {
8608
+ return attributes.flatMap(({ key, value }) => {
8609
+ if (metric && !MINIMAL_METRIC_LABELS.has(key)) return [];
8610
+ const text = value.stringValue;
8611
+ const number = value.doubleValue ?? value.intValue;
8612
+ if (MINIMAL_NUMBERS.has(key) && typeof number === "number" && Number.isFinite(number)) return [{
8613
+ key,
8614
+ value: { doubleValue: number }
8615
+ }];
8616
+ if (typeof text !== "string") return [];
8617
+ return minimalStringValue(key, text) ? [{
8618
+ key,
8619
+ value: { stringValue: text }
8620
+ }] : [];
8621
+ });
8622
+ }
8623
+ const finiteProtocolNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
8624
+ const protocolCount = (value, maximum = Number.MAX_SAFE_INTEGER) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= maximum ? value : void 0;
8625
+ const protocolFlags = (value) => protocolCount(Object.getOwnPropertyDescriptor(value, "flags")?.value, 4294967295);
8626
+ function protocolTime(value) {
8627
+ if (typeof value === "string" && /^\d{1,20}$/u.test(value) && BigInt(value) <= 18446744073709551615n) return value;
8628
+ const number = protocolCount(value);
8629
+ if (number !== void 0) return String(number);
8630
+ if (value === null || typeof value !== "object") return void 0;
8631
+ const low = protocolCount(Object.getOwnPropertyDescriptor(value, "low")?.value, 4294967295);
8632
+ const high = protocolCount(Object.getOwnPropertyDescriptor(value, "high")?.value, 4294967295);
8633
+ return low === void 0 || high === void 0 ? void 0 : (BigInt(high) << 32n | BigInt(low)).toString();
8634
+ }
8635
+ function minimalNumberPoints(points) {
8636
+ return points.flatMap((point) => {
8637
+ const attributes = minimalAttributes(point.attributes, true);
8638
+ const asDouble = finiteProtocolNumber(point.asDouble);
8639
+ const asInt = typeof point.asInt === "number" && Number.isSafeInteger(point.asInt) ? point.asInt : void 0;
8640
+ if (attributes.length !== point.attributes.length || asDouble === void 0 && asInt === void 0) return [];
8641
+ return [{
8642
+ attributes,
8643
+ timeUnixNano: protocolTime(point.timeUnixNano),
8644
+ startTimeUnixNano: protocolTime(point.startTimeUnixNano),
8645
+ asDouble,
8646
+ asInt: asDouble === void 0 ? asInt : void 0,
8647
+ flags: protocolFlags(point)
8648
+ }];
8649
+ });
8650
+ }
8651
+ function minimalHistogramPoints(points) {
8652
+ return points.flatMap((point) => {
8653
+ const attributes = minimalAttributes(point.attributes ?? [], true);
8654
+ const count = protocolCount(point.count);
8655
+ const bucketCounts = point.bucketCounts;
8656
+ const explicitBounds = point.explicitBounds;
8657
+ if (attributes.length !== (point.attributes?.length ?? 0) || count === void 0 || !Array.isArray(bucketCounts) || !bucketCounts.every((value) => protocolCount(value) !== void 0) || !Array.isArray(explicitBounds) || !explicitBounds.every((value) => finiteProtocolNumber(value) !== void 0) || bucketCounts.length !== explicitBounds.length + 1) return [];
8658
+ return [{
8659
+ attributes,
8660
+ timeUnixNano: protocolTime(point.timeUnixNano),
8661
+ startTimeUnixNano: protocolTime(point.startTimeUnixNano),
8662
+ count,
8663
+ sum: finiteProtocolNumber(point.sum),
8664
+ min: finiteProtocolNumber(point.min),
8665
+ max: finiteProtocolNumber(point.max),
8666
+ bucketCounts: bucketCounts.map((value) => value),
8667
+ explicitBounds: explicitBounds.map((value) => value),
8668
+ flags: protocolFlags(point)
8669
+ }];
8670
+ });
8671
+ }
8672
+ function minimalSerialization(config) {
8673
+ const service = config.serviceName ?? "capxul-sdk";
8674
+ const resource = { attributes: Object.entries({
8675
+ "service.name": service,
8676
+ "service.version": config.sdkVersion,
8677
+ sdk_version: config.sdkVersion,
8678
+ capxul_env: config.capxulEnv,
8679
+ producer: config.producer
8680
+ }).map(([key, value]) => ({
8681
+ key,
8682
+ value: { stringValue: value }
8683
+ })) };
8684
+ return {
8685
+ logs: (data) => HttpBody.jsonUnsafe({ resourceLogs: data.resourceLogs.map((entry) => ({
8686
+ resource,
8687
+ scopeLogs: entry.scopeLogs.map((scope) => ({
8688
+ scope: { name: service },
8689
+ logRecords: (scope.logRecords ?? []).map((record) => ({
8690
+ timeUnixNano: protocolTime(record.timeUnixNano),
8691
+ observedTimeUnixNano: protocolTime(record.observedTimeUnixNano),
8692
+ severityNumber: protocolCount(record.severityNumber, 24),
8693
+ severityText: [
8694
+ "All",
8695
+ "Trace",
8696
+ "Debug",
8697
+ "Info",
8698
+ "Warn",
8699
+ "Error",
8700
+ "Fatal",
8701
+ "None"
8702
+ ].includes(record.severityText ?? "") ? record.severityText : void 0,
8703
+ body: { stringValue: minimalName(record.body?.stringValue) },
8704
+ attributes: minimalAttributes(record.attributes),
8705
+ droppedAttributesCount: protocolCount(record.droppedAttributesCount) ?? 0,
8706
+ traceId: traceId(record.traceId) ? record.traceId : void 0,
8707
+ spanId: spanId(record.spanId) ? record.spanId : void 0,
8708
+ flags: protocolFlags(record)
8709
+ }))
8710
+ }))
8711
+ })) }),
8712
+ traces: (data) => HttpBody.jsonUnsafe({ resourceSpans: data.resourceSpans.map((entry) => ({
8713
+ resource,
8714
+ scopeSpans: entry.scopeSpans.map((scope) => ({
8715
+ scope: { name: service },
8716
+ spans: scope.spans.filter((span) => traceId(span.traceId) && spanId(span.spanId)).map((span) => {
8717
+ const attributes = minimalAttributes(span.attributes);
8718
+ const code = attributes.find((attribute) => attribute.key === "error.code");
8719
+ return {
8720
+ traceId: span.traceId,
8721
+ spanId: span.spanId,
8722
+ name: minimalName(span.name),
8723
+ kind: protocolCount(span.kind, 5) ?? 0,
8724
+ startTimeUnixNano: protocolTime(span.startTimeUnixNano),
8725
+ endTimeUnixNano: protocolTime(span.endTimeUnixNano),
8726
+ attributes,
8727
+ droppedAttributesCount: protocolCount(span.droppedAttributesCount) ?? 0,
8728
+ parentSpanId: spanId(span.parentSpanId) ? span.parentSpanId : void 0,
8729
+ flags: protocolFlags(span),
8730
+ status: { code: protocolCount(span.status.code, 2) ?? 0 },
8731
+ events: span.events.slice(0, 8).map((event) => ({
8732
+ timeUnixNano: protocolTime(event.timeUnixNano),
8733
+ droppedAttributesCount: protocolCount(event.droppedAttributesCount) ?? 0,
8734
+ name: event.name === "exception" ? "exception" : minimalName(event.name),
8735
+ attributes: event.name === "exception" ? [{
8736
+ key: "exception.type",
8737
+ value: code?.value ?? { stringValue: "Error" }
8738
+ }] : minimalAttributes(event.attributes)
8739
+ })),
8740
+ droppedEventsCount: protocolCount(span.droppedEventsCount) ?? 0,
8741
+ links: span.links.filter((link) => traceId(link.traceId) && spanId(link.spanId)).slice(0, 8).map((link) => ({
8742
+ traceId: link.traceId,
8743
+ spanId: link.spanId,
8744
+ flags: protocolFlags(link),
8745
+ attributes: minimalAttributes(link.attributes),
8746
+ droppedAttributesCount: protocolCount(link.droppedAttributesCount) ?? 0
8747
+ })),
8748
+ droppedLinksCount: protocolCount(span.droppedLinksCount) ?? 0
8749
+ };
8750
+ })
8751
+ }))
8752
+ })) }),
8753
+ metrics: (data) => {
8754
+ const resourceMetrics = data.resourceMetrics.map((entry) => ({
8755
+ resource,
8756
+ scopeMetrics: entry.scopeMetrics.map((scope) => ({
8757
+ scope: { name: service },
8758
+ metrics: scope.metrics.filter((metric) => MINIMAL_METRIC_NAMES.has(metric.name)).flatMap((metric) => {
8759
+ const gauge = metric.gauge === void 0 ? void 0 : { dataPoints: minimalNumberPoints(metric.gauge.dataPoints) };
8760
+ const sum = metric.sum === void 0 ? void 0 : {
8761
+ aggregationTemporality: protocolCount(metric.sum.aggregationTemporality, 2) ?? 0,
8762
+ isMonotonic: metric.sum.isMonotonic === true,
8763
+ dataPoints: minimalNumberPoints(metric.sum.dataPoints)
8764
+ };
8765
+ const histogram = metric.histogram === void 0 ? void 0 : {
8766
+ aggregationTemporality: protocolCount(metric.histogram.aggregationTemporality, 2) ?? 0,
8767
+ dataPoints: minimalHistogramPoints(metric.histogram.dataPoints)
8768
+ };
8769
+ if (![
8770
+ gauge,
8771
+ sum,
8772
+ histogram
8773
+ ].some((series) => (series?.dataPoints.length ?? 0) > 0)) return [];
8774
+ return [{
8775
+ name: metric.name,
8776
+ unit: [
8777
+ "1",
8778
+ "s",
8779
+ "ms"
8780
+ ].includes(metric.unit ?? "") ? metric.unit : "1",
8781
+ ...gauge === void 0 ? {} : { gauge },
8782
+ ...sum === void 0 ? {} : { sum },
8783
+ ...histogram === void 0 ? {} : { histogram }
8784
+ }];
8785
+ })
8786
+ }))
8787
+ }));
8788
+ return resourceMetrics.some((entry) => entry.scopeMetrics.some((scope) => scope.metrics.length > 0)) ? HttpBody.jsonUnsafe({ resourceMetrics }) : HttpBody.empty;
8789
+ }
8790
+ };
8791
+ }
8792
+ Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
7875
8793
  /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
7876
8794
  const makeEngineeringTelemetryLayer = (config) => {
7877
8795
  const validated = validateEngineeringTelemetryConfig(config);
7878
- const endpoints = postHogOtlpEndpoints(validated.host);
7879
- const resource = {
7880
- serviceName: validated.serviceName ?? "capxul-sdk",
7881
- serviceVersion: validated.sdkVersion,
7882
- attributes: {
7883
- capxul_env: validated.capxulEnv,
7884
- producer: validated.producer,
7885
- sdk_version: validated.sdkVersion
7886
- }
7887
- };
7888
- const tracing = Layer.effect(Tracer.Tracer, OtlpTracer.make({
7889
- url: endpoints.traces,
7890
- headers: validated.headers,
7891
- resource
7892
- }).pipe(Effect.map(makeLeakSafeEngineeringTracer))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
7893
- const logging = OtlpLogger.layer({
7894
- url: endpoints.logs,
7895
- headers: validated.headers,
7896
- resource,
7897
- mergeWithExisting: true
7898
- });
7899
- const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
7900
- const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
7901
- if (validated.producer !== "browser" || !hasBrowserDocument()) return;
7902
- const browserWindow = window;
7903
- const browserDocument = document;
7904
- const flusher = yield* OtlpExporter.Flusher;
7905
- const run = yield* FiberSet.makeRuntime();
7906
- const flush = () => {
7907
- run(flusher.flush);
7908
- };
7909
- const visibilityChanged = () => {
7910
- if (browserDocument.visibilityState === "hidden") flush();
7911
- };
7912
- yield* Effect.acquireRelease(Effect.sync(() => {
7913
- browserDocument.addEventListener("visibilitychange", visibilityChanged);
7914
- browserWindow.addEventListener("pagehide", flush);
7915
- }), () => Effect.sync(() => {
7916
- browserDocument.removeEventListener("visibilitychange", visibilityChanged);
7917
- browserWindow.removeEventListener("pagehide", flush);
8796
+ return Layer.suspend(() => {
8797
+ const endpoints = postHogOtlpEndpoints(validated.host);
8798
+ let disabled = false;
8799
+ const enabled = () => {
8800
+ if (disabled) return false;
8801
+ try {
8802
+ disabled = (typeof validated.enabled === "function" ? validated.enabled() : validated.enabled ?? true) !== true;
8803
+ } catch {
8804
+ disabled = true;
8805
+ }
8806
+ return !disabled;
8807
+ };
8808
+ const delivery = {
8809
+ ...validated.shutdownTimeout === void 0 ? {} : { shutdownTimeout: validated.shutdownTimeout },
8810
+ ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval }
8811
+ };
8812
+ const resource = {
8813
+ serviceName: validated.serviceName ?? "capxul-sdk",
8814
+ serviceVersion: validated.sdkVersion,
8815
+ attributes: {
8816
+ capxul_env: validated.capxulEnv,
8817
+ producer: validated.producer,
8818
+ sdk_version: validated.sdkVersion
8819
+ }
8820
+ };
8821
+ const tracing = Layer.effect(Tracer.Tracer, OtlpTracer.make({
8822
+ url: endpoints.traces,
8823
+ headers: validated.headers,
8824
+ resource,
8825
+ ...delivery
8826
+ }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
8827
+ const logging = Logger.layer([OtlpLogger.make({
8828
+ url: endpoints.logs,
8829
+ headers: validated.headers,
8830
+ resource,
8831
+ ...delivery
8832
+ }).pipe(Effect.map((logger) => Logger.make((options) => {
8833
+ if (enabled()) logger.log(options);
8834
+ })))], { mergeWithExisting: true }).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
8835
+ const metrics = OtlpMetrics.layer({
8836
+ url: endpoints.metrics,
8837
+ headers: validated.headers,
8838
+ resource,
8839
+ temporality: "delta",
8840
+ ...delivery
8841
+ });
8842
+ const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
8843
+ const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
8844
+ if (validated.producer !== "browser" || !hasBrowserDocument()) return;
8845
+ const browserWindow = window;
8846
+ const browserDocument = document;
8847
+ const flusher = yield* OtlpExporter.Flusher;
8848
+ const run = yield* FiberSet.makeRuntime();
8849
+ const flush = () => {
8850
+ run(flusher.flush);
8851
+ };
8852
+ const visibilityChanged = () => {
8853
+ if (browserDocument.visibilityState === "hidden") flush();
8854
+ };
8855
+ yield* Effect.acquireRelease(Effect.sync(() => {
8856
+ browserDocument.addEventListener("visibilitychange", visibilityChanged);
8857
+ browserWindow.addEventListener("pagehide", flush);
8858
+ }), () => Effect.sync(() => {
8859
+ browserDocument.removeEventListener("visibilitychange", visibilityChanged);
8860
+ browserWindow.removeEventListener("pagehide", flush);
8861
+ }));
7918
8862
  }));
7919
- }));
7920
- const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client)).pipe(Layer.provide(FetchHttpClient.layer));
7921
- return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, headerPolicy)), Layer.provide(OtlpSerialization.layerJson), Layer.provide(transport));
8863
+ const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => {
8864
+ const selectedTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
8865
+ return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => enabled() && request.body._tag !== "Empty" ? response : Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })))));
8866
+ })).pipe(Layer.provide(FetchHttpClient.layer));
8867
+ const serialization = Layer.effect(OtlpSerialization.OtlpSerialization, Effect.map(OtlpSerialization.OtlpSerialization, (json) => {
8868
+ const projected = minimalSerialization(validated);
8869
+ return validated.privacy === "minimal" ? projected : {
8870
+ ...json,
8871
+ metrics: projected.metrics
8872
+ };
8873
+ })).pipe(Layer.provide(OtlpSerialization.layerJson));
8874
+ return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
8875
+ });
7922
8876
  };
7923
8877
  //#endregion
7924
8878
  //#region src/adapters/auth-client/resolve-auth-url.ts
@@ -8207,9 +9161,16 @@ var BetterAuthBrowserAdapter = class {
8207
9161
  ok: false,
8208
9162
  error: Errors.notAuthenticated()
8209
9163
  };
9164
+ if (res.status === 429) return {
9165
+ ok: false,
9166
+ error: Errors.rateLimited({ resource: "auth.token" })
9167
+ };
8210
9168
  if (!res.ok) return {
8211
9169
  ok: false,
8212
- error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
9170
+ error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error(`HTTP ${res.status}`), {
9171
+ httpStatus: res.status,
9172
+ ...res.status >= 500 ? { failure_mode: "upstream-down" } : {}
9173
+ })
8213
9174
  };
8214
9175
  const body = await safeJson$1(res);
8215
9176
  if (typeof body === "object" && body !== null) {
@@ -8638,9 +9599,16 @@ var BetterAuthNodeAdapter = class {
8638
9599
  ok: false,
8639
9600
  error: Errors.notAuthenticated()
8640
9601
  };
9602
+ if (res.status === 429) return {
9603
+ ok: false,
9604
+ error: Errors.rateLimited({ resource: "auth.token" })
9605
+ };
8641
9606
  if (!res.ok) return {
8642
9607
  ok: false,
8643
- error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
9608
+ error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error(`HTTP ${res.status}`), {
9609
+ httpStatus: res.status,
9610
+ ...res.status >= 500 ? { failure_mode: "upstream-down" } : {}
9611
+ })
8644
9612
  };
8645
9613
  const body = await safeJson(res);
8646
9614
  if (typeof body === "object" && body !== null) {
@@ -9029,8 +9997,20 @@ function makeConnectionId() {
9029
9997
  * ever needs a different one.
9030
9998
  */
9031
9999
  const DEFAULT_CLOSE_TIMEOUT_MS = 5e3;
10000
+ const writeConvexDiagnostic = (...args) => {
10001
+ try {
10002
+ console.error(...maskCapturedNetworkRequest({ requestBody: args }).requestBody);
10003
+ } catch {}
10004
+ };
10005
+ const stderrConvexLogger = {
10006
+ log: writeConvexDiagnostic,
10007
+ warn: writeConvexDiagnostic,
10008
+ error: writeConvexDiagnostic,
10009
+ logVerbose: () => void 0
10010
+ };
9032
10011
  /** Exact floor-first allowlist; every additional handler must migrate its validator first. */
9033
10012
  const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
10013
+ "system:healthObserved",
9034
10014
  "payroll/actions:authorizeRun",
9035
10015
  "account/actions:readBalance",
9036
10016
  "holdings/actions:current",
@@ -9041,6 +10021,9 @@ const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
9041
10021
  "org/actions:resumeBootstrapSubmission",
9042
10022
  "org/actions:confirmBootstrap",
9043
10023
  "org/actions:readTreasury",
10024
+ "moneyExecution/actions:preparePermissionExecution",
10025
+ "moneyExecution/actions:submitPermissionExecution",
10026
+ "permission/actions:verify",
9044
10027
  "moneyExecution/actions:preparePaymentExecution",
9045
10028
  "moneyExecution/actions:abandonPaymentExecution",
9046
10029
  "moneyExecution/actions:submitPaymentExecution",
@@ -9053,6 +10036,8 @@ const OBSERVED_CONVEX_QUERIES = /* @__PURE__ */ new Set([
9053
10036
  "movement/activity:summary",
9054
10037
  "movement/activity:get",
9055
10038
  "financialOps/queries:depositInstructions",
10039
+ "financialOps/queries:listPayments",
10040
+ "financialOps/queries:getPayment",
9056
10041
  "financialOps/destinations:list",
9057
10042
  "financialOps/queries:verifyPaymentDocument",
9058
10043
  "financialOps/queries:renderStoredDocument",
@@ -9065,6 +10050,9 @@ const OBSERVED_CONVEX_QUERIES = /* @__PURE__ */ new Set([
9065
10050
  "payroll/queries:groups",
9066
10051
  "payroll/queries:terms",
9067
10052
  "financialOps/queries:resolveRecipient",
10053
+ "org/queries:me",
10054
+ "permission/queries:read",
10055
+ "permission/queries:authorize",
9068
10056
  "identity/queries:loadByAuthUserId",
9069
10057
  "smartAccount/queries:loadByAuthUserId",
9070
10058
  "org/lifecycle:load"
@@ -9083,6 +10071,7 @@ const OBSERVED_CONVEX_MUTATIONS = /* @__PURE__ */ new Set([
9083
10071
  "financialOps/requestsInbox:decline",
9084
10072
  "payroll/mutations:saveGroup",
9085
10073
  "payroll/mutations:removeGroup",
10074
+ "permission/mutations:command",
9086
10075
  "identity/mutations:create",
9087
10076
  "identity/mutations:update",
9088
10077
  "identity/mutations:completeOnboarding",
@@ -9102,7 +10091,10 @@ var ConvexCallAdapter = class {
9102
10091
  this.#closeTimeoutMs = deps.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;
9103
10092
  this.#connectionMonitor = new ConvexConnectionMonitor();
9104
10093
  const webSocketConstructor = this.#connectionMonitor.observedWebSocketConstructor();
9105
- this.#client = deps.client ?? new ConvexClient(deps.convexUrl, webSocketConstructor === void 0 ? {} : { webSocketConstructor });
10094
+ this.#client = deps.client ?? new ConvexClient(deps.convexUrl, {
10095
+ ...webSocketConstructor === void 0 ? {} : { webSocketConstructor },
10096
+ ...deps.logToStderr === true ? { logger: stderrConvexLogger } : {}
10097
+ });
9106
10098
  this.#tokenProvider = deps.tokenProvider;
9107
10099
  this.#applicationId = deps.applicationId;
9108
10100
  this.#observation = deps.observation;
@@ -9150,9 +10142,13 @@ var ConvexCallAdapter = class {
9150
10142
  }
9151
10143
  #observedArgs(allowlist, path, args, traceparent) {
9152
10144
  if (!allowlist.has(path)) return args;
10145
+ const invocationSnapshot = readInvocationObservation(args);
10146
+ if (path === "system:healthObserved" && invocationSnapshot?.active === false) {
10147
+ const { observationContext: _context, ...unobservedArgs } = args;
10148
+ return unobservedArgs;
10149
+ }
9153
10150
  const carriedContext = sanitizeObservationContext(args.observationContext);
9154
10151
  let hostContext;
9155
- const invocationSnapshot = readInvocationObservation(args);
9156
10152
  if (invocationSnapshot !== void 0) hostContext = invocationSnapshot.active ? invocationSnapshot.context : void 0;
9157
10153
  else {
9158
10154
  const resolveContext = this.#observation?.resolveContext;
@@ -9773,11 +10769,11 @@ var ConvexOrganizationAdapter = class {
9773
10769
  return this.listMembers(input).pipe(Effect.map((members) => members.filter((member) => member.status === "pending" || member.status === "pending_safe" || member.status === "pending_grant")));
9774
10770
  }
9775
10771
  inviteMember(input) {
9776
- return this.#convex.action(this.#fns.inviteMember, {
10772
+ return this.#convex.action(this.#fns.inviteMember, copyInvocationObservation(input, {
9777
10773
  orgId: input.orgId,
9778
10774
  email: input.input.email,
9779
10775
  role: input.input.role
9780
- }).pipe(Effect.mapError((error) => orgErrorFromCapxul("inviteMember", error.publicError, error)), Effect.map(brandOrgMember), Effect.catchDefect((cause) => Effect.fail(toOrgError("inviteMember", cause))));
10776
+ })).pipe(Effect.mapError((error) => orgErrorFromCapxul("inviteMember", error.publicError, error)), Effect.map(brandOrgMember), Effect.catchDefect((cause) => Effect.fail(toOrgError("inviteMember", cause))));
9781
10777
  }
9782
10778
  resendInviteToken(input) {
9783
10779
  return this.#convex.mutation(this.#fns.resendInvite, {
@@ -12110,11 +13106,18 @@ function makeMediaMethods(deps) {
12110
13106
  }
12111
13107
  //#endregion
12112
13108
  //#region src/contract/system.ts
12113
- const healthQuery = makeFunctionReference(CAPXUL_FUNCTIONS.system.health);
13109
+ const healthAction = makeFunctionReference(CAPXUL_FUNCTIONS.system.healthObserved);
12114
13110
  //#endregion
12115
13111
  //#region src/surface/system.ts
12116
13112
  function makeSystemMethods(deps) {
12117
- return { health: (nonce, options) => runIfActive(options?.signal, CAPXUL_OPERATIONS.system.health, () => deps.convexCall.query(healthQuery, { nonce })) };
13113
+ return { health: (nonce, options) => {
13114
+ let controls = deps.invocationControls?.(options) ?? options;
13115
+ const invocation = readInvocationObservation(controls);
13116
+ const traceparent = invocation?.active === false ? void 0 : sanitizeObservationContext(invocation?.context)?.traceparent;
13117
+ const parent = traceparent === void 0 ? void 0 : Option.getOrUndefined(HttpTraceContext.fromHeaders(Headers.fromRecordUnsafe({ traceparent })));
13118
+ if (parent !== void 0) controls = carryInvocationParentSpan(controls, parent);
13119
+ return runCapxulEffect(Effect.suspend(() => deps.convexCall.action(healthAction, copyInvocationObservation(controls, { nonce }))).pipe(Effect.mapError((error) => error.publicError)), controls, CAPXUL_OPERATIONS.system.health, deps.runPromise);
13120
+ } };
12118
13121
  }
12119
13122
  //#endregion
12120
13123
  //#region src/contract/org.ts
@@ -12139,8 +13142,33 @@ function toOrgMe(wire) {
12139
13142
  observedAt: wire.observedAt
12140
13143
  };
12141
13144
  }
13145
+ /**
13146
+ * The read runs through the canonical observed runner, so `org.me` owns one
13147
+ * `capxul_sdk_operation` record and one span like every other public method.
13148
+ *
13149
+ * The call-start invocation snapshot travels on the query arguments: the
13150
+ * Convex adapter reads it there and sends the browser linkage with the call,
13151
+ * so an account switch mid-flight cannot re-resolve the actor. A successful
13152
+ * read proves participation in this Organization, so the owning span carries
13153
+ * the verified Organization and the two authority booleans the backend
13154
+ * actually enforces. Labels and Budget caps stay off the record.
13155
+ */
12142
13156
  function makeOrgMeMethod(deps, orgId) {
12143
- return (options) => runIfActive(options?.signal, CAPXUL_OPERATIONS.org.me, () => Effect.map(deps.convexCall.query(orgReadContract.me, { orgId }), toOrgMe));
13157
+ return (options) => {
13158
+ const controls = deps.invocationControls?.(options) ?? options;
13159
+ return runCapxulEffect(Effect.gen(function* () {
13160
+ const wire = yield* deps.convexCall.query(orgReadContract.me, copyInvocationObservation(controls, { orgId })).pipe(Effect.mapError((failure) => failure.publicError));
13161
+ yield* Effect.annotateCurrentSpan({
13162
+ verified_organization_id: orgId,
13163
+ scope_kind: "organization",
13164
+ can_manage_people: wire.capabilities.canManagePeople,
13165
+ can_spend: wire.capabilities.canSpend,
13166
+ budget_count: wire.budgets.length,
13167
+ observed_at: wire.observedAt
13168
+ }).pipe(Effect.catchCause(() => Effect.void));
13169
+ return toOrgMe(wire);
13170
+ }), controls, CAPXUL_OPERATIONS.org.me, deps.runPromise);
13171
+ };
12144
13172
  }
12145
13173
  //#endregion
12146
13174
  //#region src/contract/permission.ts
@@ -12150,7 +13178,34 @@ const permissionContract = { read: makeFunctionReference(CAPXUL_FUNCTIONS["permi
12150
13178
  function isAborted(signal) {
12151
13179
  return signal?.aborted === true;
12152
13180
  }
12153
- async function execute(deps, orgId, command, signal) {
13181
+ /**
13182
+ * One owning operation per public Permission method. The compound command
13183
+ * (prepare, equality checks, sign, submit) keeps owning its own cancellation;
13184
+ * the observed runner around it is what gives the attempt its
13185
+ * `capxul_sdk_operation` record, its span and its failure capture, so a signer
13186
+ * refusal or an unexpected provider error joins to the operation like every
13187
+ * other public surface.
13188
+ */
13189
+ function observed(deps, operation, options, run) {
13190
+ const controls = deps.invocationControls?.(options) ?? options;
13191
+ let verifiedOrganizationId;
13192
+ return runCapxulEffect(Effect.gen(function* () {
13193
+ if (controls?.signal?.aborted === true) return yield* Effect.fail(Errors.cancelled({ operation }));
13194
+ const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
13195
+ const captured = carryInvocationParentSpan(controls, parent);
13196
+ const result = yield* Effect.tryPromise({
13197
+ try: () => run(controls?.signal, captured, (orgId) => {
13198
+ verifiedOrganizationId = orgId;
13199
+ }),
13200
+ catch: (cause) => cause instanceof CapxulError ? cause : Errors.unknown(cause)
13201
+ });
13202
+ return yield* result.ok ? Effect.succeed(result.value) : Effect.fail(result.error);
13203
+ }).pipe(Effect.ensuring(Effect.suspend(() => verifiedOrganizationId === void 0 ? Effect.void : Effect.annotateCurrentSpan({
13204
+ verified_organization_id: verifiedOrganizationId,
13205
+ scope_kind: "organization"
13206
+ })).pipe(Effect.catchCause(() => Effect.void)))), observationOnlyControls(controls), operation, deps.runPromise);
13207
+ }
13208
+ async function execute(deps, orgId, command, signal, controls, verifiedOrganization) {
12154
13209
  if (isAborted(signal)) return {
12155
13210
  ok: false,
12156
13211
  error: Errors.cancelled({ operation: "permissions" })
@@ -12174,17 +13229,18 @@ async function execute(deps, orgId, command, signal) {
12174
13229
  };
12175
13230
  }
12176
13231
  const executionFns = deps.executionFunctions ?? moneyExecutionContract;
12177
- const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => deps.convexCall.action(executionFns.preparePermissionExecution, { input: {
13232
+ const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.preparePermissionExecution, copyInvocationObservation(controls, { input: {
12178
13233
  orgId,
12179
13234
  signerAddress,
12180
13235
  command
12181
- } }));
13236
+ } }))), controls), deps.runPromise);
12182
13237
  if (!prepared.ok) return prepared;
12183
13238
  const expectedSafe = deriveCapxulSafeAddress({ email: session.email });
12184
13239
  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 {
12185
13240
  ok: false,
12186
13241
  error: Errors.invalidInput("permission", "prepared authority mismatch")
12187
13242
  };
13243
+ verifiedOrganization(prepared.value.orgId);
12188
13244
  if (isAborted(signal)) return {
12189
13245
  ok: false,
12190
13246
  error: Errors.cancelled({ operation: "permissions" })
@@ -12202,43 +13258,46 @@ async function execute(deps, orgId, command, signal) {
12202
13258
  ok: false,
12203
13259
  error: Errors.cancelled({ operation: "permissions" })
12204
13260
  };
12205
- return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => deps.convexCall.action(executionFns.submitPermissionExecution, { input: {
13261
+ return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.submitPermissionExecution, copyInvocationObservation(controls, { input: {
12206
13262
  executionId: prepared.value.executionId,
12207
13263
  signature
12208
- } }));
13264
+ } }))), controls), deps.runPromise);
12209
13265
  }
12210
13266
  function makePermissionMethods(deps, orgId) {
12211
13267
  const reads = deps.permissionFunctions ?? permissionContract;
13268
+ const ops = CAPXUL_OPERATIONS.org.permissions;
13269
+ const read = (signal, operation, controls) => runIfActive(signal, operation, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.query(reads.read, copyInvocationObservation(controls, { orgId }))), controls), deps.runPromise);
13270
+ const command = (operation, build, options) => observed(deps, operation, options, (signal, captured, verifiedOrganization) => execute(deps, orgId, build(), signal, captured, verifiedOrganization));
12212
13271
  return {
12213
- create: (input, options) => execute(deps, orgId, {
13272
+ create: (input, options) => command(ops.create, () => ({
12214
13273
  operation: "create",
12215
13274
  ...input
12216
- }, options?.signal),
12217
- change: (input, options) => execute(deps, orgId, {
13275
+ }), options),
13276
+ change: (input, options) => command(ops.change, () => ({
12218
13277
  operation: "change",
12219
13278
  ...input
12220
- }, options?.signal),
12221
- assign: (input, options) => execute(deps, orgId, {
13279
+ }), options),
13280
+ assign: (input, options) => command(ops.assign, () => ({
12222
13281
  operation: "assign",
12223
13282
  ...input
12224
- }, options?.signal),
12225
- revoke: (input, options) => execute(deps, orgId, {
13283
+ }), options),
13284
+ revoke: (input, options) => command(ops.revoke, () => ({
12226
13285
  operation: "revoke",
12227
13286
  ...input
12228
- }, options?.signal),
12229
- replace: (input, options) => execute(deps, orgId, {
13287
+ }), options),
13288
+ replace: (input, options) => command(ops.replace, () => ({
12230
13289
  operation: "replace",
12231
13290
  ...input
12232
- }, options?.signal),
12233
- list: (options) => runIfActive(options?.signal, CAPXUL_OPERATIONS.permissions.list, () => deps.convexCall.query(reads.read, { orgId })),
12234
- get: async (permissionId, options) => {
12235
- const listed = await runIfActive(options?.signal, CAPXUL_OPERATIONS.permissions.get, () => deps.convexCall.query(reads.read, { orgId }));
13291
+ }), options),
13292
+ list: (options) => observed(deps, ops.list, options, (signal, captured) => read(signal, CAPXUL_OPERATIONS.permissions.list, captured)),
13293
+ get: (permissionId, options) => observed(deps, ops.get, options, async (signal, captured) => {
13294
+ const listed = await read(signal, CAPXUL_OPERATIONS.permissions.get, captured);
12236
13295
  if (!listed.ok) return listed;
12237
13296
  return {
12238
13297
  ok: true,
12239
13298
  value: listed.value.permissions.find((row) => row.permissionId === permissionId) ?? null
12240
13299
  };
12241
- }
13300
+ })
12242
13301
  };
12243
13302
  }
12244
13303
  //#endregion
@@ -12733,18 +13792,6 @@ function emitOrgCreateFailed(telemetry, error) {
12733
13792
  }
12734
13793
  });
12735
13794
  }
12736
- function emitOrgInviteSent(telemetry, input) {
12737
- const domain = emailDomain$1(input.email);
12738
- return emitOrgTelemetry(telemetry, {
12739
- name: "org_invite_sent",
12740
- props: {
12741
- org_id: input.orgId,
12742
- ...domain === void 0 ? {} : { email_domain: domain },
12743
- role: input.role,
12744
- status: input.status
12745
- }
12746
- });
12747
- }
12748
13795
  function reportOrgTelemetryFailure(kind, name, cause) {
12749
13796
  if (!isOrgTelemetryDebugEnabled()) return;
12750
13797
  globalThis.console?.warn?.("[capxul] org telemetry dropped", {
@@ -12920,25 +13967,26 @@ function listMembersProgram(orgId) {
12920
13967
  return [];
12921
13968
  });
12922
13969
  }
13970
+ /**
13971
+ * R06 (#1964): the SDK no longer emits `org_invite_sent`. The backend action
13972
+ * that writes the invitation is its ONE producer, so a hermetic call — which
13973
+ * writes nothing — can no longer report an invitation that never happened, and
13974
+ * a live call is counted once instead of twice. The captured attempt rides the
13975
+ * port input, so the durable event carries the correlation this call ran under.
13976
+ */
12923
13977
  function inviteMemberProgram(orgId, input) {
12924
13978
  return Effect.gen(function* () {
12925
13979
  const deps = yield* OrgDepsTag;
12926
- const member = deps.orgPort !== void 0 ? yield* deps.orgPort.inviteMember({
12927
- orgId,
12928
- input
12929
- }).pipe(Effect.mapError((error) => error.publicError)) : hermeticMember({
13980
+ if (deps.orgPort === void 0) return hermeticMember({
12930
13981
  orgId,
12931
13982
  email: input.email,
12932
13983
  role: input.role,
12933
13984
  status: "pending"
12934
13985
  });
12935
- yield* Effect.promise(() => emitOrgInviteSent(deps.telemetry, {
13986
+ return yield* deps.orgPort.inviteMember(copyInvocationObservation(deps.invocationSource, {
12936
13987
  orgId,
12937
- email: input.email,
12938
- role: member.role,
12939
- status: member.status
12940
- }));
12941
- return member;
13988
+ input
13989
+ })).pipe(Effect.mapError((error) => error.publicError));
12942
13990
  });
12943
13991
  }
12944
13992
  function detectAndAcceptPendingInvitationsProgram() {
@@ -13046,6 +14094,13 @@ function isRetryableOrganizationSetupFailure(error) {
13046
14094
  * constructed for each `org(orgId)` call. It has no shared mutable active-Org
13047
14095
  * state. The closed-over `orgId` is its only entity reference.
13048
14096
  */
14097
+ /** The observed-runner pair a method bundle needs to own its operation records. */
14098
+ function observedRunnerDeps(deps) {
14099
+ return {
14100
+ ...deps.runPromise === void 0 ? {} : { runPromise: deps.runPromise },
14101
+ ...deps.invocationControls === void 0 ? {} : { invocationControls: deps.invocationControls }
14102
+ };
14103
+ }
13049
14104
  function makeOrgMethods(deps) {
13050
14105
  const orgDeps = {
13051
14106
  ...deps.orgPort === void 0 ? {} : { orgPort: deps.orgPort },
@@ -13096,7 +14151,10 @@ function makeOrgMethods(deps) {
13096
14151
  },
13097
14152
  ...convexCall === void 0 ? {} : { convexCall }
13098
14153
  }),
13099
- me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({ convexCall }, String(orgId)),
14154
+ me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({
14155
+ convexCall,
14156
+ ...observedRunnerDeps(deps)
14157
+ }, String(orgId)),
13100
14158
  async getLifecycle(options) {
13101
14159
  if (options?.signal?.aborted) return {
13102
14160
  ok: false,
@@ -13153,32 +14211,27 @@ function makeOrgMethods(deps) {
13153
14211
  });
13154
14212
  return treasury(orgId, deps.invocationControls?.(options) ?? options);
13155
14213
  },
13156
- account: { async get(options) {
13157
- if (options?.signal?.aborted) return {
13158
- ok: false,
13159
- error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.org.account.get })
13160
- };
14214
+ account: { get(options) {
13161
14215
  const controls = deps.invocationControls?.(options) ?? options;
13162
- const treasuryResult = await treasury(orgId, controls);
13163
- if (!treasuryResult.ok) return {
14216
+ if (controls?.signal?.aborted) return Promise.resolve({
13164
14217
  ok: false,
13165
- error: treasuryResult.error
13166
- };
13167
- const orgList = await listOrganizations(controls);
13168
- if (!orgList.ok) return {
13169
- ok: false,
13170
- error: orgList.error
13171
- };
13172
- const org = orgList.value.find((candidate) => candidate.id === orgId);
13173
- return {
13174
- ok: true,
13175
- value: {
14218
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.org.account.get })
14219
+ });
14220
+ return runCapxulEffect(Effect.gen(function* () {
14221
+ const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
14222
+ const captured = carryInvocationParentSpan(controls, parent);
14223
+ const treasuryResult = yield* Effect.promise(() => treasury(orgId, captured));
14224
+ if (!treasuryResult.ok) return yield* Effect.fail(treasuryResult.error);
14225
+ const orgList = yield* Effect.promise(() => listOrganizations(captured));
14226
+ if (!orgList.ok) return yield* Effect.fail(orgList.error);
14227
+ const org = orgList.value.find((candidate) => candidate.id === orgId);
14228
+ return {
13176
14229
  id: treasuryResult.value.id,
13177
14230
  address: org?.safeAddress ?? null,
13178
14231
  balance: treasuryResult.value.balance,
13179
14232
  available: treasuryResult.value.available
13180
- }
13181
- };
14233
+ };
14234
+ }), observationOnlyControls(controls), CAPXUL_OPERATIONS.org.account.get, deps.runPromise);
13182
14235
  } },
13183
14236
  members(_options) {
13184
14237
  if (_options?.signal?.aborted) return Promise.resolve({
@@ -13195,11 +14248,15 @@ function makeOrgMethods(deps) {
13195
14248
  return toCapxulResult(listRolesProgram(orgId), layer);
13196
14249
  },
13197
14250
  invite(_input, _options) {
13198
- if (_options?.signal?.aborted) return Promise.resolve({
14251
+ const controls = deps.invocationControls?.(_options) ?? _options;
14252
+ if (controls?.signal?.aborted) return Promise.resolve({
13199
14253
  ok: false,
13200
14254
  error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.org.invite })
13201
14255
  });
13202
- return toCapxulResult(inviteMemberProgram(orgId, _input), layer);
14256
+ return runCapxulEffect(inviteMemberProgram(orgId, _input).pipe(Effect.provide(orgDepsLayer({
14257
+ ...orgDeps,
14258
+ ...controls === void 0 ? {} : { invocationSource: controls }
14259
+ }))), controls, CAPXUL_OPERATIONS.org.invite, deps.runPromise);
13203
14260
  },
13204
14261
  auditLog(_options) {
13205
14262
  if (_options?.signal?.aborted) return Promise.resolve({
@@ -13213,6 +14270,7 @@ function makeOrgMethods(deps) {
13213
14270
  },
13214
14271
  permissions: convexCall === void 0 ? makeNotImplementedPermissionMethods() : makePermissionMethods({
13215
14272
  convexCall,
14273
+ ...observedRunnerDeps(deps),
13216
14274
  ...deps.actor === void 0 ? {} : { actor: deps.actor },
13217
14275
  ...deps.chainId === void 0 ? {} : { chainId: Number(deps.chainId) },
13218
14276
  ...deps.signer === void 0 ? {} : { signer: deps.signer }
@@ -13332,14 +14390,65 @@ function compareDecimal(first, second) {
13332
14390
  }
13333
14391
  //#endregion
13334
14392
  //#region src/surface/holdings.ts
14393
+ /** SPEC Freshness: past this block age the read is labelled stale. Nothing refuses. */
14394
+ const FRESHNESS_LIMIT_SECONDS = 60;
14395
+ /**
14396
+ * The Ponder balance view for one Safe as `CurrentHoldings`.
14397
+ *
14398
+ * The view is the signed sum of every indexed transfer, so the balance IS what
14399
+ * the chain said — there is no snapshot to go stale behind it. `observedAt` is
14400
+ * the indexed block's own timestamp, which is what the last-good label reads,
14401
+ * and `stale` is that block's age. A stopped indexer therefore shows an old
14402
+ * "as of" and the stale flag; it never turns the read into a refusal.
14403
+ */
14404
+ function indexerHoldings(indexer, convexCall, overlay, actor, controls) {
14405
+ return Effect.gen(function* () {
14406
+ const scope = yield* convexCall.query(overlay, copyInvocationObservation(controls, actor?.kind === "organization" ? { input: { actor: {
14407
+ kind: "organization",
14408
+ orgId: actor.organizationId
14409
+ } } } : { input: {} }));
14410
+ if (scope.safe === null) return null;
14411
+ const balances = yield* indexer.read("balance", {
14412
+ safe: toAddress(scope.safe),
14413
+ limit: INDEXER_MAX_LIMIT
14414
+ });
14415
+ const status = yield* indexer.status.pipe(Effect.catch(() => Effect.succeed([])));
14416
+ const rows = [];
14417
+ for (const balance of balances) {
14418
+ const asset = configuredMoneyAssetById(balance.assetId);
14419
+ if (asset === null || balance.amount < 0n) continue;
14420
+ rows.push({
14421
+ assetKind: "erc20",
14422
+ tokenAddress: toAddress(asset.tokenAddress),
14423
+ symbol: asset.symbol,
14424
+ decimals: asset.decimals,
14425
+ rawBalance: toWeiAmount(balance.amount.toString())
14426
+ });
14427
+ }
14428
+ const chainId = balances[0]?.chainId ?? status[0]?.chainId;
14429
+ if (chainId === void 0) return null;
14430
+ const head = status.find((entry) => entry.chainId === chainId);
14431
+ const nowSeconds = Math.floor(Date.now() / 1e3);
14432
+ const headSeconds = head === void 0 ? void 0 : Number(head.blockTimestamp);
14433
+ return {
14434
+ actor: scope.actor,
14435
+ chainId: toChainId(chainId),
14436
+ observedAt: headSeconds === void 0 ? Date.now() : headSeconds * 1e3,
14437
+ stale: headSeconds === void 0 || nowSeconds - headSeconds > FRESHNESS_LIMIT_SECONDS,
14438
+ rows
14439
+ };
14440
+ }).pipe(Effect.catch(() => Effect.succeed(null)));
14441
+ }
13335
14442
  function makeHoldingsMethods(deps) {
13336
14443
  const functions = deps.functions ?? holdingsContract;
14444
+ const overlay = (deps.financialOpsFunctions ?? financialOpsContract).activityOverlay;
13337
14445
  const read = (operation, actor, options, pick) => {
13338
14446
  const controls = deps.invocationControls?.(options) ?? options;
13339
- return runCapxulEffect(retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
14447
+ const convexSnapshot = retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
13340
14448
  kind: "organization",
13341
14449
  orgId: actor.organizationId
13342
- } } : {})), operation, deps.telemetry, controls).pipe(Effect.map(pick), Effect.mapError((error) => error.publicError)), controls, operation, deps.runPromise);
14450
+ } } : {})), operation, deps.telemetry, controls).pipe(Effect.mapError((error) => error.publicError));
14451
+ 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);
13343
14452
  };
13344
14453
  return {
13345
14454
  current: (input, options) => read(CAPXUL_OPERATIONS.holdings.current, input?.actor, options, (snapshot) => snapshot),
@@ -13365,8 +14474,8 @@ const APPLIED_ACTION_BY_EVENT = {
13365
14474
  RestoreSession: "none",
13366
14475
  ResumeOtpEntry: "none",
13367
14476
  Reset: "none",
13368
- SessionRestored: "none",
13369
- SessionAbsent: "none",
14477
+ SessionRestored: "restoration",
14478
+ SessionAbsent: "restoration",
13370
14479
  EnsureAccount: "none",
13371
14480
  ClaimAccount: "none",
13372
14481
  RetryAccount: "none",
@@ -13378,8 +14487,8 @@ const APPLIED_ACTION_BY_EVENT = {
13378
14487
  OtpFailed: "failed",
13379
14488
  Verified: "verified",
13380
14489
  VerifyFailed: "failed",
13381
- SessionRead: "none",
13382
- SessionReadFailed: "none",
14490
+ SessionRead: "restoration",
14491
+ SessionReadFailed: "restoration",
13383
14492
  SignedOut: "signed-out",
13384
14493
  SignOutFailed: "failed",
13385
14494
  AccountAt: "none",
@@ -13405,6 +14514,49 @@ const authFailure = (record, reason, mode) => ({
13405
14514
  ...linkage(record)
13406
14515
  }
13407
14516
  });
14517
+ /**
14518
+ * The label of the restore window every client boots into. A settled session
14519
+ * fact that left this label is the restoration result (#1962 R04); the same
14520
+ * facts from a signed-out or authenticated state are the routine session probe
14521
+ * that `getSession()` runs, and a probe is not a restoration attempt.
14522
+ */
14523
+ const RESTORE_WINDOW = "restoring";
14524
+ /**
14525
+ * Which result the window settled on. `SessionRestored` and `SessionAbsent` are
14526
+ * the cache door and say it themselves. `ReadSession` is also valid inside the
14527
+ * window — `getSession()` drives it on a cache miss and its `SessionRead`
14528
+ * completion settles the window — so that one event reports both halves and the
14529
+ * state it produced is what separates them.
14530
+ */
14531
+ const restorationOutcome = (event, state) => {
14532
+ switch (event) {
14533
+ case "SessionRestored": return "restored";
14534
+ case "SessionAbsent": return "absent";
14535
+ case "SessionReadFailed": return "failed";
14536
+ case "SessionRead":
14537
+ if (state.phase === "authenticated") return "restored";
14538
+ return state.phase === "signed_out" ? "absent" : void 0;
14539
+ default: return;
14540
+ }
14541
+ };
14542
+ /**
14543
+ * The restore window's one result, or nothing when this settled session fact
14544
+ * belongs to a probe rather than to the window.
14545
+ */
14546
+ const restorationObservation = (record, state) => {
14547
+ if (record.slot !== "identity:session" || record.from !== RESTORE_WINDOW) return NONE;
14548
+ const outcome = restorationOutcome(record.event, state);
14549
+ if (outcome === void 0) return NONE;
14550
+ return {
14551
+ name: "access_recovery_completed",
14552
+ props: {
14553
+ recovery: "session_restoration",
14554
+ outcome,
14555
+ ...outcome === "failed" && state.phase === "faulted" ? { reason_code: state.failure.code } : {},
14556
+ ...linkage(record)
14557
+ }
14558
+ };
14559
+ };
13408
14560
  const REFUSAL_EVENTS = /* @__PURE__ */ new Set([
13409
14561
  "ClaimAccount",
13410
14562
  "RetryAccount",
@@ -13449,6 +14601,7 @@ function mapIdentityProductObservation(record, state) {
13449
14601
  name: "auth_signed_out",
13450
14602
  props: linkage(record)
13451
14603
  };
14604
+ case "restoration": return restorationObservation(record, state);
13452
14605
  }
13453
14606
  }
13454
14607
  const ignoreTransportFailure = (operation, operationName, eventName) => Effect.suspend(operation).pipe(Effect.catchCause(() => Effect.logWarning("identity.product.telemetry.dropped").pipe(Effect.annotateLogs({
@@ -13460,7 +14613,7 @@ function executeIdentityProductObservation(telemetry, record, state) {
13460
14613
  if (mapped === null) return Effect.void;
13461
14614
  copyInvocationObservation(record, mapped);
13462
14615
  const invocationTelemetry = bindProductTelemetryInvocation(telemetry, mapped);
13463
- const after = () => mapped.name === "auth_verified" && state.phase === "authenticated" ? invocationTelemetry.identify({
14616
+ const after = () => state.phase === "authenticated" && (mapped.name === "auth_verified" || mapped.name === "access_recovery_completed") ? invocationTelemetry.identify({
13464
14617
  distinctId: state.session.authUserId,
13465
14618
  traits: { email_domain: emailDomain(state.session.email) }
13466
14619
  }) : mapped.name === "auth_signed_out" ? invocationTelemetry.reset() : Effect.void;
@@ -13505,6 +14658,73 @@ function startSignerReadinessOnClaim(actor, signer) {
13505
14658
  browserSigner.ensureWalletReady?.().catch(() => {});
13506
14659
  });
13507
14660
  }
14661
+ /**
14662
+ * Report the result of each signer readiness cycle (#1962 R04).
14663
+ *
14664
+ * The readiness store already carries the cycle boundary this needs, so no
14665
+ * public signer hook, second identity subscriber, or render counter is added.
14666
+ * `ensureOpenfortWalletReady` writes `recovering` synchronously when it starts
14667
+ * a cycle, and only the cycle that is still the current one writes `ready` or
14668
+ * `unavailable` (`create-openfort-browser-signer.ts:501-521`). Callers that
14669
+ * join a live cycle share that single write, and a `resetSession` writes
14670
+ * `unknown` first, so a cycle that a reset or an account replacement outran
14671
+ * reports nothing.
14672
+ *
14673
+ * The host context is bound when the cycle starts, so a person who signs out
14674
+ * and back in while a cycle runs cannot relabel the result that began under
14675
+ * the previous person. A signer with no readiness cycle — a node or dev key,
14676
+ * and a headless client whose requirement never opens the account lane —
14677
+ * writes no status and reports nothing.
14678
+ */
14679
+ function observeSignerReadinessCycles(signer, telemetry, hostObservationSnapshot, runPromise) {
14680
+ const statusStore = signer?.statusStore;
14681
+ if (statusStore === void 0 || telemetry === void 0) return () => {};
14682
+ let running = false;
14683
+ let startContext;
14684
+ return statusStore.subscribe((status) => {
14685
+ if (status === "recovering") {
14686
+ running = true;
14687
+ startContext = hostObservationSnapshot?.();
14688
+ return;
14689
+ }
14690
+ const cycleContext = startContext;
14691
+ const completesCycle = running;
14692
+ running = false;
14693
+ startContext = void 0;
14694
+ if (!completesCycle || status !== "ready" && status !== "unavailable") return;
14695
+ const event = {
14696
+ name: "access_recovery_completed",
14697
+ props: {
14698
+ recovery: "signer_readiness",
14699
+ outcome: status
14700
+ }
14701
+ };
14702
+ if (cycleContext !== void 0) attachInvocationObservation(event, cycleContext);
14703
+ runPromise(Effect.suspend(() => bindProductTelemetryInvocation(telemetry, event).emit(event)).pipe(Effect.catchCause(() => Effect.void))).catch(() => {});
14704
+ });
14705
+ }
14706
+ /** Join the assembled actor lifetime to the adapter lifetime for internal client factories. */
14707
+ function withAdapterClose(client, closeAdapter) {
14708
+ const closeActor = client._internal.close;
14709
+ let closePromise;
14710
+ const close = () => {
14711
+ closePromise ??= Promise.allSettled([Promise.resolve().then(() => closeActor?.()), Promise.resolve().then(closeAdapter)]).then((results) => {
14712
+ const failure = results.find((result) => result.status === "rejected");
14713
+ if (failure?.status === "rejected") throw failure.reason;
14714
+ });
14715
+ return closePromise;
14716
+ };
14717
+ return {
14718
+ ...client,
14719
+ close: () => closeClientResources(close),
14720
+ _internal: {
14721
+ ...client._internal,
14722
+ close
14723
+ }
14724
+ };
14725
+ }
14726
+ /** Keep lifecycle failures on the same public result boundary as domain work. */
14727
+ const closeClientResources = (close) => toCapxulResult(Effect.promise(close), Layer.empty);
13508
14728
  function assembleCapxulClient(input) {
13509
14729
  const observation = input;
13510
14730
  const authCache = input.authCache ?? detectAuthCacheAdapter();
@@ -13534,12 +14754,14 @@ function assembleCapxulClient(input) {
13534
14754
  }, failureObservation, normalizeCapxulOperation(`identity.${record.event}`), "machine");
13535
14755
  });
13536
14756
  const unsubscribeSignerReadiness = startSignerReadinessOnClaim(actor, input.signer);
14757
+ const unsubscribeReadinessObservation = observeSignerReadinessCycles(input.signer, input.ports.telemetry, observation.hostObservationSnapshot, effectRunner.runPromise);
13537
14758
  let stopPromise = null;
13538
14759
  const stopActor = () => {
13539
14760
  if (stopPromise === null) {
13540
14761
  unsubscribeProductTelemetry();
13541
14762
  unsubscribeFailureObservation();
13542
14763
  unsubscribeSignerReadiness();
14764
+ unsubscribeReadinessObservation();
13543
14765
  stopPromise = effectRunner.runPromise(Scope.close(scope, Exit.void));
13544
14766
  }
13545
14767
  return stopPromise;
@@ -13556,7 +14778,8 @@ function assembleCapxulClient(input) {
13556
14778
  convexCall: input.ports.convexCall,
13557
14779
  ...input.signer === void 0 ? {} : { signer: input.signer },
13558
14780
  telemetry: input.ports.telemetry,
13559
- requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`
14781
+ requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`,
14782
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
13560
14783
  });
13561
14784
  const accountBundle = makeAccountMethods({
13562
14785
  actor,
@@ -13621,13 +14844,18 @@ function assembleCapxulClient(input) {
13621
14844
  subscribeIdentity: actor.subscribe,
13622
14845
  sendIdentity: (event, options) => runCapxulEffect(askIdentity(actor, event, options), options, CAPXUL_OPERATIONS._internal.identity.send, effectRunner.runPromise)
13623
14846
  });
13624
- const system = makeSystemMethods({ convexCall: input.ports.convexCall });
14847
+ const system = makeSystemMethods({
14848
+ convexCall: input.ports.convexCall,
14849
+ runPromise: effectRunner.runPromise,
14850
+ invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls)
14851
+ });
13625
14852
  const media = makeMediaMethods({ convexCall: input.ports.convexCall });
13626
14853
  const holdings = makeHoldingsMethods({
13627
14854
  convexCall: input.ports.convexCall,
13628
14855
  telemetry: input.ports.telemetry,
13629
14856
  runPromise: effectRunner.runPromise,
13630
- invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId)
14857
+ invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId),
14858
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
13631
14859
  });
13632
14860
  const accounts = makeAccountsMethods({
13633
14861
  accountReadPort: input.ports.accountRead,
@@ -13770,7 +14998,8 @@ function assembleCapxulClient(input) {
13770
14998
  })).pipe(Effect.catchCause(() => Effect.void)));
13771
14999
  },
13772
15000
  close: stopActor
13773
- }
15001
+ },
15002
+ close: () => closeClientResources(stopActor)
13774
15003
  }, observation.failureObservation, observation.hostObservationSnapshot);
13775
15004
  }
13776
15005
  function transitionError(record) {
@@ -13887,6 +15116,7 @@ function postHogProductTelemetry(policy, fixedSnapshot) {
13887
15116
  }
13888
15117
  //#endregion
13889
15118
  //#region src/host-observability.ts
15119
+ const HOST_ENGINEERING_POLICY = Symbol("capxul.host-engineering-policy");
13890
15120
  const HOST_INVOCATION = Symbol("capxul.host-observability-invocation");
13891
15121
  /** Build the SDK's one host module around an already-initialized PostHog client. */
13892
15122
  function postHogObservability(client, options = {}) {
@@ -13924,6 +15154,10 @@ function postHogObservability(client, options = {}) {
13924
15154
  failures: postHogFailureObservation(policy),
13925
15155
  product: postHogProductTelemetry(policy)
13926
15156
  };
15157
+ Object.defineProperty(module, HOST_ENGINEERING_POLICY, { value: Object.freeze({
15158
+ ...options.engineeringEnabled === void 0 ? {} : { engineeringEnabled: options.engineeringEnabled },
15159
+ ...options.engineeringPrivacy === void 0 ? {} : { engineeringPrivacy: options.engineeringPrivacy }
15160
+ }) });
13927
15161
  Object.defineProperty(module, HOST_INVOCATION, {
13928
15162
  enumerable: false,
13929
15163
  value: {
@@ -13954,36 +15188,14 @@ function snapshotHostObservability(observability) {
13954
15188
  function isPromiseLike(value) {
13955
15189
  return (typeof value === "object" && value !== null || typeof value === "function") && "then" in value;
13956
15190
  }
13957
- //#endregion
13958
- //#region src/ports/indexer-read.ts
13959
- /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
13960
- const INDEXER_MAX_LIMIT = 1e3;
13961
- const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
13962
- /**
13963
- * Refuse a page the indexer would refuse, before any implementation spends a
13964
- * round trip on it. Every implementation calls this, so a consumer test written
13965
- * against the hermetic adapter cannot pass on input production rejects. Throws
13966
- * a `CapxulError`; callers convert it to an `IndexerReadError`.
13967
- */
13968
- function assertIndexerPage(input) {
13969
- const safe = String(input.safe).toLowerCase();
13970
- if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
13971
- 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}`);
13972
- const offset = input.offset ?? 0;
13973
- if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
13974
- return safe;
13975
- }
13976
- var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
13977
- function indexerReadErrorFromCapxul(operation, error, cause = error) {
13978
- return new IndexerReadError({
13979
- operation,
13980
- publicCode: error.code,
13981
- publicError: error,
13982
- cause,
13983
- ...error.details === void 0 ? {} : { details: error.details }
13984
- });
15191
+ /** @internal Carry the explicit host engineering policy into production composition. */
15192
+ function hostEngineeringPolicy(observability) {
15193
+ const policy = observability?.[HOST_ENGINEERING_POLICY];
15194
+ return {
15195
+ ...policy?.engineeringEnabled === void 0 ? {} : { enabled: policy.engineeringEnabled },
15196
+ ...policy?.engineeringPrivacy === void 0 ? {} : { privacy: policy.engineeringPrivacy }
15197
+ };
13985
15198
  }
13986
- Context.Service()("@capxul/sdk/ports/IndexerReadPort");
13987
15199
  //#endregion
13988
15200
  //#region src/adapters/ponder/decode.ts
13989
15201
  /**
@@ -14034,6 +15246,7 @@ function identity(row) {
14034
15246
  chainId: toChainId(integer(row, "chain_id")),
14035
15247
  txHash: toTxHash(hex(row, "tx_hash")),
14036
15248
  transactionLogOrdinal: integer(row, "transaction_log_ordinal"),
15249
+ logIndex: integer(row, "log_index"),
14037
15250
  blockNumber: big(row, "block_number"),
14038
15251
  blockHash: hex(row, "block_hash"),
14039
15252
  timestamp: big(row, "timestamp")
@@ -14116,8 +15329,8 @@ function decodeStatus(body) {
14116
15329
  * that allowlist changes, this file changes with it or every read fails 403.
14117
15330
  */
14118
15331
  const HEADS = {
14119
- transfer: "select \"id\", \"chain_id\", \"tx_hash\", \"transaction_log_ordinal\", \"block_number\", \"block_hash\", \"timestamp\", \"asset_id\", \"token\", \"from\", \"to\", \"amount\" from \"transfer\" where (\"transfer\".\"from\" = $1 or \"transfer\".\"to\" = $2) order by \"transfer\".\"block_number\" desc, \"transfer\".\"id\" desc",
14120
- payment: "select \"id\", \"chain_id\", \"tx_hash\", \"transaction_log_ordinal\", \"block_number\", \"block_hash\", \"timestamp\", \"contract\", \"settlement_id\", \"sender\", \"recipient\", \"asset_id\", \"token\", \"amount\", \"kind\", \"document_hash\" from \"payment\" where (\"payment\".\"sender\" = $1 or \"payment\".\"recipient\" = $2) order by \"payment\".\"block_number\" desc, \"payment\".\"id\" desc",
15332
+ transfer: "select \"id\", \"chain_id\", \"tx_hash\", \"transaction_log_ordinal\", \"log_index\", \"block_number\", \"block_hash\", \"timestamp\", \"asset_id\", \"token\", \"from\", \"to\", \"amount\" from \"transfer\" where (\"transfer\".\"from\" = $1 or \"transfer\".\"to\" = $2) order by \"transfer\".\"block_number\" desc, \"transfer\".\"id\" desc",
15333
+ payment: "select \"id\", \"chain_id\", \"tx_hash\", \"transaction_log_ordinal\", \"log_index\", \"block_number\", \"block_hash\", \"timestamp\", \"contract\", \"settlement_id\", \"sender\", \"recipient\", \"asset_id\", \"token\", \"amount\", \"kind\", \"document_hash\" from \"payment\" where (\"payment\".\"sender\" = $1 or \"payment\".\"recipient\" = $2) order by \"payment\".\"block_number\" desc, \"payment\".\"id\" desc",
14121
15334
  balance: "select \"chain_id\", \"asset_id\", \"to\", \"amount\" from \"balance\" where \"balance\".\"to\" = $1"
14122
15335
  };
14123
15336
  /** How many times the statement binds the Safe address. */
@@ -14167,11 +15380,16 @@ function ponderQueryParam(query) {
14167
15380
  * policy allows exactly one request header (`Authorization`), so this reader
14168
15381
  * sends nothing else. Ponder writes one `data:` line per frame.
14169
15382
  */
14170
- async function readEventStream(body, onData, stopped) {
15383
+ async function readEventStream(body, onData, stopped, signal) {
14171
15384
  const reader = body.getReader();
14172
15385
  const decoder = new TextDecoder();
14173
15386
  let buffer = "";
15387
+ const cancel = () => {
15388
+ reader.cancel().catch(() => void 0);
15389
+ };
15390
+ signal?.addEventListener("abort", cancel, { once: true });
14174
15391
  try {
15392
+ if (signal?.aborted) return;
14175
15393
  for (;;) {
14176
15394
  const { done, value } = await reader.read();
14177
15395
  if (done || stopped()) return;
@@ -14187,6 +15405,7 @@ async function readEventStream(body, onData, stopped) {
14187
15405
  }
14188
15406
  }
14189
15407
  } finally {
15408
+ signal?.removeEventListener("abort", cancel);
14190
15409
  await reader.cancel().catch(() => void 0);
14191
15410
  }
14192
15411
  }
@@ -14223,12 +15442,10 @@ var PonderIndexerAdapter = class {
14223
15442
  subscribe(table, input, callback) {
14224
15443
  const operation = CAPXUL_OPERATIONS.activity.subscribe;
14225
15444
  return Effect.sync(() => {
14226
- let stopped = false;
14227
- const unsubscribe = () => {
14228
- stopped = true;
14229
- };
15445
+ const controller = new AbortController();
15446
+ const unsubscribe = () => controller.abort();
14230
15447
  callback({ status: "loading" });
14231
- this.#stream(table, input, operation, callback, () => stopped).catch(() => void 0);
15448
+ this.#stream(table, input, operation, callback, controller.signal).catch(() => void 0);
14232
15449
  return unsubscribe;
14233
15450
  });
14234
15451
  }
@@ -14241,9 +15458,13 @@ var PonderIndexerAdapter = class {
14241
15458
  catch: (cause) => toIndexerReadError("indexer.status", cause)
14242
15459
  });
14243
15460
  /** One authenticated `/sql/:method` call. Throws a `CapxulError` on refusal. */
14244
- async #request(method, table, input, operation) {
15461
+ async #request(method, table, input, operation, signal, forceRefreshToken = false) {
14245
15462
  const query = ponderQuery(table, input);
14246
- const token = await this.#token({ forceRefreshToken: false });
15463
+ const token = await this.#token({
15464
+ forceRefreshToken,
15465
+ ...signal === void 0 ? {} : { signal }
15466
+ });
15467
+ if (signal?.aborted) throw Errors.cancelled({ operation });
14247
15468
  if (token === null || token === "") throw Errors.notAuthenticated();
14248
15469
  const url = `${this.#baseUrl}/sql/${method}?sql=${encodeURIComponent(ponderQueryParam(query))}`;
14249
15470
  const headers = { authorization: `Bearer ${token}` };
@@ -14252,7 +15473,8 @@ var PonderIndexerAdapter = class {
14252
15473
  try {
14253
15474
  response = await this.#fetch(url, {
14254
15475
  method: "GET",
14255
- headers
15476
+ headers,
15477
+ ...signal === void 0 ? {} : { signal }
14256
15478
  });
14257
15479
  } catch (cause) {
14258
15480
  throw Errors.networkError(operation, cause, {
@@ -14264,39 +15486,72 @@ var PonderIndexerAdapter = class {
14264
15486
  return response;
14265
15487
  }
14266
15488
  /**
14267
- * Follow the live stream until the caller unsubscribes or the indexer closes
14268
- * it. The indexer closes the stream on token expiry or revoked authority
14269
- * with no frame, so any end the caller did not ask for is a teardown: it
14270
- * reaches `captureException` under `activity.subscribe` and the caller sees
14271
- * one `error` snapshot.
15489
+ * Keep the subscription alive across transport loss. Every new connection
15490
+ * rechecks authority. A refusal or invalid payload is terminal. Unsubscribe
15491
+ * aborts both the current request and any reconnect delay.
14272
15492
  */
14273
- async #stream(table, input, operation, callback, stopped) {
14274
- let failure;
14275
- try {
14276
- const response = await this.#request("live", table, input, operation);
14277
- if (response.body === null) throw Errors.providerError("indexer", operation, "empty stream");
14278
- await readEventStream(response.body, (data) => {
14279
- try {
14280
- callback({
14281
- status: "ok",
14282
- value: decodeRows(table, JSON.parse(data))
14283
- });
14284
- } catch (cause) {
14285
- failure = toPublicError$1(operation, cause);
14286
- }
14287
- }, () => stopped() || failure !== void 0);
14288
- } catch (cause) {
14289
- failure = toPublicError$1(operation, cause);
15493
+ async #stream(table, input, operation, callback, signal) {
15494
+ let refreshToken = false;
15495
+ let delay = 1e3;
15496
+ let reported = false;
15497
+ while (!signal.aborted) {
15498
+ let failure;
15499
+ let invalidFrame = false;
15500
+ let reading = false;
15501
+ const startedAt = Date.now();
15502
+ try {
15503
+ const response = await this.#request("live", table, input, operation, signal, refreshToken);
15504
+ if (response.body === null) throw Errors.providerError("indexer", operation, "empty stream");
15505
+ reading = true;
15506
+ await readEventStream(response.body, (data) => {
15507
+ try {
15508
+ callback({
15509
+ status: "ok",
15510
+ value: decodeRows(table, JSON.parse(data))
15511
+ });
15512
+ reported = false;
15513
+ } catch (cause) {
15514
+ invalidFrame = true;
15515
+ failure = toPublicError$1(operation, cause);
15516
+ }
15517
+ }, () => signal.aborted || failure !== void 0, signal);
15518
+ } catch (cause) {
15519
+ failure = reading && !(cause instanceof CapxulError) ? Errors.networkError(operation, cause, {
15520
+ provider: "indexer",
15521
+ failure_mode: "upstream-down"
15522
+ }) : toPublicError$1(operation, cause);
15523
+ }
15524
+ if (signal.aborted || failure?.code === "CANCELLED") return;
15525
+ const error = failure ?? Errors.capabilityUnavailable("indexer", operation);
15526
+ const retryable = !invalidFrame && (error.code === "NETWORK_ERROR" || error.code === "CAPABILITY_UNAVAILABLE" || error.code === "RATE_LIMITED" || error.mode === "upstream-down");
15527
+ if (!reported || !retryable) {
15528
+ observeStreamFailure(this.#observation, operation, error, error.code === "NOT_AUTHENTICATED" ? "operation" : "exception");
15529
+ callback({
15530
+ status: "error",
15531
+ error
15532
+ });
15533
+ reported = true;
15534
+ }
15535
+ if (!retryable) return;
15536
+ if (Date.now() - startedAt >= 3e4) delay = 1e3;
15537
+ await reconnectDelay(delay, signal);
15538
+ delay = Math.min(delay * 2, 3e4);
15539
+ refreshToken = true;
14290
15540
  }
14291
- if (stopped()) return;
14292
- const error = failure ?? Errors.capabilityUnavailable("indexer", operation);
14293
- observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error);
14294
- callback({
14295
- status: "error",
14296
- error
14297
- });
14298
15541
  }
14299
15542
  };
15543
+ function reconnectDelay(milliseconds, signal) {
15544
+ return new Promise((resolve) => {
15545
+ const finish = () => {
15546
+ clearTimeout(timer);
15547
+ signal.removeEventListener("abort", finish);
15548
+ resolve();
15549
+ };
15550
+ const timer = setTimeout(finish, milliseconds);
15551
+ signal.addEventListener("abort", finish, { once: true });
15552
+ if (signal.aborted) finish();
15553
+ });
15554
+ }
14300
15555
  /**
14301
15556
  * Map an indexer refusal into the shared error vocabulary.
14302
15557
  *
@@ -14456,6 +15711,7 @@ function productionAuthCacheLayer(input) {
14456
15711
  function productionConvexCallLayer(input, refreshConvexAuthRef) {
14457
15712
  return Layer.unwrap(Effect.map(AuthClientPortTag, (authClient) => ConvexCallLayer({
14458
15713
  convexUrl: input.runtimeUrls.convexUrl,
15714
+ logToStderr: input.runtime === "node",
14459
15715
  ...input.applicationId === void 0 ? {} : { applicationId: input.applicationId },
14460
15716
  ...input.observation === void 0 ? {} : { observation: input.observation },
14461
15717
  ...input.convexClient === void 0 ? {} : { client: input.convexClient },
@@ -14577,7 +15833,7 @@ async function createProductionAdapters(input) {
14577
15833
  ...input.signal === void 0 ? {} : { signal: input.signal },
14578
15834
  ...input.resetSignerSession === void 0 ? {} : { resetSignerSession: input.resetSignerSession }
14579
15835
  }));
14580
- const applicationLayer = Layer.merge(portsLayer, optionalEngineeringTelemetryLayer(bootstrapResult.value.engineeringTelemetry, resolvedInput.value.runtime));
15836
+ const applicationLayer = Layer.merge(portsLayer, optionalEngineeringTelemetryLayer(bootstrapResult.value.engineeringTelemetry, resolvedInput.value.runtime, hostEngineeringPolicy(input.observability))).pipe(Layer.provideMerge(resolvedInput.value.runtime === "node" ? Layer.succeed(Logger.LogToStderr, true) : Layer.empty));
14581
15837
  const context = await Effect.runPromise(Layer.buildWithScope(applicationLayer, scope));
14582
15838
  const collected = await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(context)));
14583
15839
  const indexerUrl = input.indexerUrl ?? bootstrapResult.value.indexerUrl;
@@ -14626,7 +15882,7 @@ function observeProductionConvexConnection(convexCall, context) {
14626
15882
  return () => {};
14627
15883
  }
14628
15884
  }
14629
- function optionalEngineeringTelemetryLayer(policy, runtime) {
15885
+ function optionalEngineeringTelemetryLayer(policy, runtime, hostPolicy) {
14630
15886
  if (policy === void 0) return Layer.empty;
14631
15887
  try {
14632
15888
  return makeEngineeringTelemetryLayer({
@@ -14634,7 +15890,8 @@ function optionalEngineeringTelemetryLayer(policy, runtime) {
14634
15890
  headers: { authorization: `Bearer ${policy.projectToken}` },
14635
15891
  capxulEnv: policy.capxulEnv,
14636
15892
  producer: runtime === "browser" ? "browser" : "server",
14637
- sdkVersion: SDK_VERSION
15893
+ sdkVersion: SDK_VERSION,
15894
+ ...hostPolicy
14638
15895
  });
14639
15896
  } catch {
14640
15897
  return Layer.empty;
@@ -14729,7 +15986,7 @@ async function createCapxulClientWithSignerControls(input, controls) {
14729
15986
  }
14730
15987
  };
14731
15988
  }
14732
- const client = assembleCapxulClient({
15989
+ const ownedClient = withAdapterClose(assembleCapxulClient({
14733
15990
  ports: adapters.value.ports,
14734
15991
  bootstrap: adapters.value.bootstrap,
14735
15992
  authCache: adapters.value.ports.authCache,
@@ -14750,8 +16007,7 @@ async function createCapxulClientWithSignerControls(input, controls) {
14750
16007
  runSync: Effect.runSyncWith(adapters.value.context),
14751
16008
  runPromise: Effect.runPromiseWith(adapters.value.context)
14752
16009
  }
14753
- });
14754
- const upstreamClose = adapters.value.close;
16010
+ }), adapters.value.close);
14755
16011
  const close = idempotentClose(async () => {
14756
16012
  try {
14757
16013
  if (signer !== void 0 && "resetSession" in signer) try {
@@ -14760,15 +16016,16 @@ async function createCapxulClientWithSignerControls(input, controls) {
14760
16016
  throw signerFailure(signer.source, "resetSession", cause);
14761
16017
  }
14762
16018
  } finally {
14763
- await Promise.all([client._internal.close?.(), upstreamClose()]);
16019
+ await ownedClient._internal.close?.();
14764
16020
  }
14765
16021
  });
14766
16022
  return {
14767
16023
  ok: true,
14768
16024
  value: {
14769
- ...client,
16025
+ ...ownedClient,
16026
+ close: () => closeClientResources(close),
14770
16027
  _internal: {
14771
- ...client._internal,
16028
+ ...ownedClient._internal,
14772
16029
  close
14773
16030
  }
14774
16031
  }
@@ -14927,15 +16184,17 @@ function idempotentClose(close) {
14927
16184
  * and Convex see one session with one scope.
14928
16185
  */
14929
16186
  function productionIndexerReadPort(input, ports, observation) {
16187
+ if (input.activityReadSource === "convex") return void 0;
14930
16188
  if (input.indexerUrl === void 0 || input.indexerUrl === "") return void 0;
14931
16189
  return new PonderIndexerAdapter({
14932
16190
  baseUrl: normalizeHttpUrl("indexerUrl", input.indexerUrl),
14933
- token: async ({ forceRefreshToken }) => {
16191
+ token: async ({ forceRefreshToken, signal }) => {
16192
+ const tokenSignal = input.signal === void 0 ? signal : signal === void 0 ? input.signal : AbortSignal.any([input.signal, signal]);
14934
16193
  const token = await Effect.runPromise(Effect.result(ports.authClient.getConvexJwt({
14935
16194
  forceRefresh: forceRefreshToken,
14936
- ...input.signal === void 0 ? {} : { signal: input.signal }
16195
+ ...tokenSignal === void 0 ? {} : { signal: tokenSignal }
14937
16196
  })));
14938
- if (Result.isFailure(token)) return null;
16197
+ if (Result.isFailure(token)) throw toPublicError(token.failure, "getConvexJwt");
14939
16198
  return String(token.success.token);
14940
16199
  },
14941
16200
  ...input.fetch === void 0 ? {} : { fetch: input.fetch },