@capxul/sdk 4.2.0-rc.1 → 4.2.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { $ as toTesterKind, A as toAccountId, B as toEmail, C as BYTES32_RE, D as ZERO_BYTES32, E as WEI_RE, F as toBudgetId, G as toKycTier, H as toEpochSeconds, I as toChainId, J as toPayrollGroupId, K as toOrgId, L as toCountryCode, M as toAllowedOrigin, P as toAuthUserId, Q as toSessionToken, R as toCurrencyCode, S as ASSET_ID_RE, T as SUPPORTED_CURRENCY_CODES, U as toHandle, V as toEpochMs, W as toJwtToken, X as toPublishableKey, Y as toPayrollRunId, Z as toRoleKey, _ as BASE_SEPOLIA_CHAIN_ID, _t as revertSummaryText, at as EXPECTED_OPERATION_OUTCOMES, b as ACCOUNT_ID_RE, bt as redactSecrets, c as AuthCachePortTag, ct as CHAIN_UPSTREAMS, d as authClientPortFromPromiseAdapter, dt as chainCauseProperties, et as validateHandle, f as AuthClientError, ft as chainEvidenceLabel, g as normalizeBindingEmail, gt as isFailureMode, ht as isChainUpstream, i as BrowserAuthCacheAdapter, it as CapxulError, j as toAddress, l as SystemClockLayer, lt as FAILURE_MODES$1, m as orgRoleKeyForLabel, mt as failureFingerprint, nt as decodeConvexError, ot as Errors, p as AuthClientPortTag, pt as decodeChainCause, q as toPartyId, r as InMemoryAuthCacheAdapter, rt as CAPXUL_ERROR_CODES, st as isCapxulError, u as ClockPortTag, ut as boundedResponseHeaders, v as deriveCapxulSafeAddress, vt as containsSensitiveMaterial, w as EVM_ADDRESS_RE$1, x as APP_ID_RE, xt as redactUrlSecrets, yt as isCredentialField, z as toDurationMs } from "./OAuthBearerAuthClient-BrbXrndM.mjs";
1
+ import { $ as toRoleKey, B as toCurrencyCode, C as ASSET_ID_RE, Ct as isCredentialField, D as WEI_RE, E as SUPPORTED_CURRENCY_CODES, F as toAssetId, G as toHandle, H as toEmail, I as toAuthUserId, J as toOrgId, K as toJwtToken, L as toBudgetId, M as toAddress, N as toAllowedOrigin, O as ZERO_BYTES32, Q as toPublishableKey, R as toChainId, S as APP_ID_RE, St as containsSensitiveMaterial, T as EVM_ADDRESS_RE$1, Tt as redactUrlSecrets, U as toEpochMs, V as toDurationMs, W as toEpochSeconds, X as toPayrollGroupId, Y as toPartyId, Z as toPayrollRunId, _ as normalizeBindingEmail, _t as decodeChainCause, b as configuredMoneyAssetById, bt as isFailureMode, c as AuthCachePortTag, ct as CapxulError, d as authClientPortFromPromiseAdapter, dt as isCapxulError, et as toSessionToken, f as AuthClientError, ft as CHAIN_UPSTREAMS, gt as chainEvidenceLabel, h as orgRoleKeyForLabel, ht as chainCauseProperties, i as BrowserAuthCacheAdapter, it as validateHandle, j as toAccountId, l as SystemClockLayer, lt as EXPECTED_OPERATION_OUTCOMES, m as ADMIN_ROLE_LABEL, mt as boundedResponseHeaders, nt as toTxHash, ot as decodeConvexError, p as AuthClientPortTag, pt as FAILURE_MODES$1, q as toKycTier, r as InMemoryAuthCacheAdapter, rt as toWeiAmount, st as CAPXUL_ERROR_CODES, tt as toTesterKind, u as ClockPortTag, ut as Errors, v as BASE_SEPOLIA_CHAIN_ID, vt as failureFingerprint, w as BYTES32_RE, wt as redactSecrets, x as ACCOUNT_ID_RE, xt as revertSummaryText, y as deriveCapxulSafeAddress, yt as isChainUpstream, z as toCountryCode } from "./OAuthBearerAuthClient-CbU_W9Sp.mjs";
2
2
  import { formatUnits, keccak256, parseUnits, recoverAddress, stringToHex, toBytes } from "viem";
3
3
  import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Option, Queue, Ref, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
4
4
  import { getFunctionName, makeFunctionReference } from "convex/server";
@@ -448,6 +448,8 @@ const CAPXUL_OPERATIONS = {
448
448
  emitAnnotationSaved: "activity.emitAnnotationSaved",
449
449
  get: "activity.get",
450
450
  list: "activity.list",
451
+ /** The SDK's live Activity read over the indexer's SSE stream. */
452
+ subscribe: "activity.subscribe",
451
453
  summary: "activity.summary"
452
454
  },
453
455
  addressBook: {
@@ -946,6 +948,17 @@ const AuthFailedProps = Schema.Struct({
946
948
  auth_type: OptionalString,
947
949
  reason: OptionalString
948
950
  });
951
+ const IdentityRefusedProps = Schema.Struct({
952
+ ...TelemetryEnvelopeProps,
953
+ /** The refused identity event tag, for example CreateOrganization. */
954
+ event: OptionalString,
955
+ /** The machine's refusal code: WRONG_STATE, SUPERSEDED or STALE_EPOCH. */
956
+ refusal_code: OptionalString,
957
+ /** The machine slot the request targeted, for example identity:org. */
958
+ slot: OptionalString,
959
+ /** The machine state at refusal, for example authenticated:claiming. */
960
+ state: OptionalString
961
+ });
949
962
  const AuthSignedOutProps = Schema.Struct(TelemetryEnvelopeProps);
950
963
  const ProvisioningSafeCreatedProps = Schema.Struct({
951
964
  ...TelemetryEnvelopeProps,
@@ -1333,10 +1346,44 @@ const DepositSettledProps = Schema.Struct({
1333
1346
  });
1334
1347
  const PermissionMirrorVerificationProps = Schema.Struct({
1335
1348
  ...TelemetryEnvelopeProps,
1349
+ /** The RECEIPT verification outcome. What the mirror then did is separate. */
1336
1350
  outcome: Schema.String,
1337
1351
  reason: Schema.String,
1338
1352
  chain_id: Schema.Number,
1339
- retry_count: Schema.Number
1353
+ retry_count: Schema.Number,
1354
+ /**
1355
+ * #1960 R02, consumed by R03. A verified receipt is not an applied Permission:
1356
+ * these two optional fields let the producer say what the applying mutation
1357
+ * actually returned, and which command it was, without either fact rewriting
1358
+ * the receipt outcome above. Absent when application has not happened yet.
1359
+ */
1360
+ application_result: Schema.optional(Schema.Literals([
1361
+ "applied",
1362
+ "replayed",
1363
+ "conflicting_replay",
1364
+ "revision_mismatch"
1365
+ ])),
1366
+ command_id: OptionalString,
1367
+ /**
1368
+ * #1961 R03. The authorized stored row names the Permission it changed; the
1369
+ * verified receipt names the chain facts that prove it. Every field is
1370
+ * optional because a pending or provider-failed verification has neither an
1371
+ * application result nor a receipt to report.
1372
+ */
1373
+ permission_id: OptionalString,
1374
+ permission_assignment_id: OptionalString,
1375
+ permission_operation: Schema.optional(Schema.Literals([
1376
+ "create",
1377
+ "change",
1378
+ "assign",
1379
+ "revoke",
1380
+ "replace"
1381
+ ])),
1382
+ revision: Schema.optional(Schema.Number),
1383
+ user_op_hash: OptionalString,
1384
+ transaction_hash: OptionalString,
1385
+ block_number: Schema.optional(Schema.Number),
1386
+ log_index: Schema.optional(Schema.Number)
1340
1387
  });
1341
1388
  const MovementScanWindowProps = Schema.Struct({
1342
1389
  ...TelemetryEnvelopeProps,
@@ -1382,6 +1429,10 @@ Schema.Struct({
1382
1429
  name: Schema.Literal("auth_failed"),
1383
1430
  props: AuthFailedProps
1384
1431
  });
1432
+ Schema.Struct({
1433
+ name: Schema.Literal("identity_refused"),
1434
+ props: IdentityRefusedProps
1435
+ });
1385
1436
  Schema.Struct({
1386
1437
  name: Schema.Literal("auth_signed_out"),
1387
1438
  props: AuthSignedOutProps
@@ -1562,6 +1613,10 @@ Schema.Struct({
1562
1613
  name: Schema.Literal("movement_scan_incident"),
1563
1614
  props: MovementScanIncidentProps
1564
1615
  });
1616
+ /** The one spelling of "whose scope did this happen in" across every surface. */
1617
+ const ScopeKindSchema = Schema.Literals(["account", "organization"]);
1618
+ const AccountKindSchema = Schema.Literals(["personalSafe", "orgTreasury"]);
1619
+ const OptionalReasonCode = Schema.optional(Schema.Literals(CAPXUL_ERROR_CODES));
1565
1620
  const ActivityReferenceProps = {
1566
1621
  reference_kind: Schema.Literals(["payment", "movement"]),
1567
1622
  reference_id: Schema.String
@@ -1592,8 +1647,8 @@ const ActivityFilterProps = {
1592
1647
  };
1593
1648
  const ActivityActionProps = {
1594
1649
  ...TelemetryEnvelopeProps,
1595
- scope_kind: Schema.Literals(["account", "organization"]),
1596
- reason_code: Schema.optional(Schema.Literals(CAPXUL_ERROR_CODES))
1650
+ scope_kind: ScopeKindSchema,
1651
+ reason_code: OptionalReasonCode
1597
1652
  };
1598
1653
  Schema.Struct({
1599
1654
  name: Schema.Literal("contact_relationship_changed"),
@@ -1605,7 +1660,7 @@ Schema.Struct({
1605
1660
  "hidden",
1606
1661
  "restored"
1607
1662
  ]),
1608
- scope_kind: Schema.Literals(["account", "organization"]),
1663
+ scope_kind: ScopeKindSchema,
1609
1664
  scope_id: Schema.String,
1610
1665
  owner_party_id: Schema.String,
1611
1666
  party_id: Schema.String,
@@ -1621,9 +1676,9 @@ Schema.Struct({
1621
1676
  });
1622
1677
  const ContactActionProps = {
1623
1678
  ...TelemetryEnvelopeProps,
1624
- scope_kind: Schema.Literals(["account", "organization"]),
1679
+ scope_kind: ScopeKindSchema,
1625
1680
  party_id: OptionalString,
1626
- reason_code: Schema.optional(Schema.Literals(CAPXUL_ERROR_CODES))
1681
+ reason_code: OptionalReasonCode
1627
1682
  };
1628
1683
  Schema.Struct({
1629
1684
  name: Schema.Literal("ui_contact_action_completed"),
@@ -1650,7 +1705,7 @@ Schema.Struct({
1650
1705
  props: Schema.Struct({
1651
1706
  ...TelemetryEnvelopeProps,
1652
1707
  ...ActivityReferenceProps,
1653
- scope_kind: Schema.Literals(["account", "organization"]),
1708
+ scope_kind: ScopeKindSchema,
1654
1709
  scope_id: Schema.String,
1655
1710
  occurred_at: Schema.Number
1656
1711
  })
@@ -1705,6 +1760,193 @@ Schema.Struct({
1705
1760
  })
1706
1761
  ])
1707
1762
  });
1763
+ /**
1764
+ * Reliability closeout (#1960 R02) — the shape every explicit UI observation
1765
+ * below shares. `scope_kind` says whose surface the person was looking at,
1766
+ * `organization_id` is the verified Organization the producer resolved (absent
1767
+ * on a personal action), and `reason_code` is a catalog error code, never copy.
1768
+ */
1769
+ const UiStepProps = {
1770
+ ...TelemetryEnvelopeProps,
1771
+ scope_kind: ScopeKindSchema,
1772
+ organization_id: OptionalOrgId,
1773
+ reason_code: OptionalReasonCode
1774
+ };
1775
+ const AccessRecoveryCompletedProps = Schema.Union([Schema.Struct({
1776
+ ...TelemetryEnvelopeProps,
1777
+ recovery: Schema.Literal("session_restoration"),
1778
+ outcome: Schema.Literals([
1779
+ "restored",
1780
+ "absent",
1781
+ "failed"
1782
+ ]),
1783
+ reason_code: OptionalReasonCode,
1784
+ organization_id: OptionalOrgId
1785
+ }), Schema.Struct({
1786
+ ...TelemetryEnvelopeProps,
1787
+ recovery: Schema.Literal("signer_readiness"),
1788
+ outcome: Schema.Literals(["ready", "unavailable"]),
1789
+ reason_code: OptionalReasonCode,
1790
+ organization_id: OptionalOrgId
1791
+ })]);
1792
+ Schema.Struct({
1793
+ name: Schema.Literal("access_recovery_completed"),
1794
+ props: AccessRecoveryCompletedProps
1795
+ });
1796
+ Schema.Struct({
1797
+ name: Schema.Literal("ui_access_step_completed"),
1798
+ props: Schema.Struct({
1799
+ ...UiStepProps,
1800
+ step: Schema.Literals([
1801
+ "dashboard_granted",
1802
+ "redirect_completed",
1803
+ "gate_error_displayed",
1804
+ "retry_selected",
1805
+ "capability_refusal_displayed",
1806
+ "signer_refusal_displayed"
1807
+ ]),
1808
+ capability: Schema.optional(Schema.Literals(["spend", "managePeople"])),
1809
+ probe: Schema.optional(Schema.Literals([
1810
+ "session",
1811
+ "membership",
1812
+ "lifecycle"
1813
+ ])),
1814
+ reason: Schema.optional(Schema.Literals([
1815
+ "loading",
1816
+ "no-permission",
1817
+ "org-unavailable"
1818
+ ]))
1819
+ })
1820
+ });
1821
+ Schema.Struct({
1822
+ name: Schema.Literal("org_invite_progressed"),
1823
+ props: Schema.Struct({
1824
+ ...TelemetryEnvelopeProps,
1825
+ organization_id: OrgIdTelemetrySchema,
1826
+ progress: Schema.Literals([
1827
+ "account_ready",
1828
+ "permission_applied",
1829
+ "membership_activated",
1830
+ "revoked"
1831
+ ]),
1832
+ /** The invited Party. The initiating actor stays on the envelope's identity. */
1833
+ member_party_id: Schema.String,
1834
+ occurred_at: Schema.Number
1835
+ })
1836
+ });
1837
+ Schema.Struct({
1838
+ name: Schema.Literal("invoice_issued"),
1839
+ props: Schema.Struct({
1840
+ ...TelemetryEnvelopeProps,
1841
+ request_id: Schema.String,
1842
+ issuer_scope_kind: ScopeKindSchema,
1843
+ issuer_scope_id: Schema.String,
1844
+ payer_party_id: OptionalString,
1845
+ occurred_at: Schema.Number
1846
+ })
1847
+ });
1848
+ Schema.Struct({
1849
+ name: Schema.Literal("invoice_request_transitioned"),
1850
+ props: Schema.Struct({
1851
+ ...TelemetryEnvelopeProps,
1852
+ request_id: Schema.String,
1853
+ transition: Schema.Literals([
1854
+ "approval_prepared",
1855
+ "declined",
1856
+ "cancelled"
1857
+ ]),
1858
+ actor_scope_kind: ScopeKindSchema,
1859
+ actor_scope_id: Schema.String,
1860
+ occurred_at: Schema.Number
1861
+ })
1862
+ });
1863
+ Schema.Struct({
1864
+ name: Schema.Literal("ui_invoice_step_completed"),
1865
+ props: Schema.Struct({
1866
+ ...UiStepProps,
1867
+ step: Schema.Literals([
1868
+ "opened",
1869
+ "previewed",
1870
+ "dismissed",
1871
+ "submitted",
1872
+ "result_displayed",
1873
+ "refusal_displayed",
1874
+ "retry_selected"
1875
+ ]),
1876
+ request_id: OptionalString
1877
+ })
1878
+ });
1879
+ Schema.Struct({
1880
+ name: Schema.Literal("ui_treasury_step_completed"),
1881
+ props: Schema.Struct({
1882
+ ...UiStepProps,
1883
+ step: Schema.Literals([
1884
+ "balance_displayed",
1885
+ "read_refusal_displayed",
1886
+ "retry_selected",
1887
+ "recovery_completed"
1888
+ ]),
1889
+ account_kind: Schema.optional(AccountKindSchema)
1890
+ })
1891
+ });
1892
+ Schema.Struct({
1893
+ name: Schema.Literal("ui_receive_step_completed"),
1894
+ props: Schema.Struct({
1895
+ ...UiStepProps,
1896
+ step: Schema.Literals([
1897
+ "instructions_displayed",
1898
+ "address_copied",
1899
+ "copy_failed",
1900
+ "refusal_displayed",
1901
+ "retry_selected"
1902
+ ]),
1903
+ account_kind: Schema.optional(AccountKindSchema)
1904
+ })
1905
+ });
1906
+ Schema.Struct({
1907
+ name: Schema.Literal("payroll_group_changed"),
1908
+ props: Schema.Struct({
1909
+ ...TelemetryEnvelopeProps,
1910
+ organization_id: OrgIdTelemetrySchema,
1911
+ change: Schema.Literals([
1912
+ "created",
1913
+ "updated",
1914
+ "deleted"
1915
+ ]),
1916
+ group_id: Schema.String,
1917
+ member_count: Schema.Number,
1918
+ occurred_at: Schema.Number
1919
+ })
1920
+ });
1921
+ Schema.Struct({
1922
+ name: Schema.Literal("payroll_run_transitioned"),
1923
+ props: Schema.Struct({
1924
+ ...TelemetryEnvelopeProps,
1925
+ organization_id: OrgIdTelemetrySchema,
1926
+ transition: Schema.Literals([
1927
+ "prepared",
1928
+ "authorized",
1929
+ "settled"
1930
+ ]),
1931
+ run_id: Schema.String,
1932
+ payment_count: Schema.optional(Schema.Number),
1933
+ occurred_at: Schema.Number
1934
+ })
1935
+ });
1936
+ Schema.Struct({
1937
+ name: Schema.Literal("ui_payroll_action_completed"),
1938
+ props: Schema.Struct({
1939
+ ...UiStepProps,
1940
+ action: Schema.Literals([
1941
+ "opened",
1942
+ "refusal_displayed",
1943
+ "recovery_completed",
1944
+ "cancelled",
1945
+ "submitted_displayed"
1946
+ ]),
1947
+ run_id: OptionalString
1948
+ })
1949
+ });
1708
1950
  function redactTelemetryEvent(event, options = {}) {
1709
1951
  const props = redactTelemetryProps(event.name, event.props, options);
1710
1952
  return props === void 0 ? { name: event.name } : {
@@ -2007,6 +2249,10 @@ const BootstrapEnvelope = Schema.Struct({
2007
2249
  siteBaseUrl: Schema.String,
2008
2250
  openfortPublishableKey: Schema.String,
2009
2251
  shieldPublishableKey: Schema.String,
2252
+ /** Which Openfort third-party auth provider the signer presents; absent means better-auth. */
2253
+ openfortAuthProvider: Schema.optional(Schema.Literals(["better-auth", "oidc"])),
2254
+ /** Origin of the Ponder indexer the SDK reads directly; absent means no indexer reads. */
2255
+ indexerUrl: Schema.optional(Schema.String),
2010
2256
  engineeringTelemetry: Schema.optional(EngineeringTelemetryBootstrapPolicy)
2011
2257
  })
2012
2258
  });
@@ -2069,6 +2315,7 @@ const CAPXUL_FUNCTIONS = {
2069
2315
  list: "movement/activity:list",
2070
2316
  summary: "movement/activity:summary"
2071
2317
  },
2318
+ "movement/annotations": { overlay: "movement/annotations:overlay" },
2072
2319
  "moneyExecution/actions": {
2073
2320
  abandonPaymentExecution: "moneyExecution/actions:abandonPaymentExecution",
2074
2321
  preparePermissionExecution: "moneyExecution/actions:preparePermissionExecution",
@@ -2081,6 +2328,7 @@ const CAPXUL_FUNCTIONS = {
2081
2328
  prepareOrganizationPaymentExecution: "moneyExecution/paymentCommandActions:prepareOrganizationPaymentExecution",
2082
2329
  submitPaymentCommandExecution: "moneyExecution/paymentCommandActions:submitPaymentCommandExecution"
2083
2330
  },
2331
+ "moneyExecution/providerSendWitness": { read: "moneyExecution/providerSendWitness:read" },
2084
2332
  media: {
2085
2333
  generateUploadUrl: "media:generateUploadUrl",
2086
2334
  setOrgLogo: "media:setOrgLogo",
@@ -2140,6 +2388,11 @@ const CAPXUL_FUNCTIONS = {
2140
2388
  loadByAuthUserId: "smartAccount/queries:loadByAuthUserId",
2141
2389
  loadBySmartAccountAddress: "smartAccount/queries:loadBySmartAccountAddress"
2142
2390
  },
2391
+ e2eControls: {
2392
+ holdUserOperationReceipt: "e2eControls:holdUserOperationReceipt",
2393
+ isUserOperationReceiptHeld: "e2eControls:isUserOperationReceiptHeld",
2394
+ releaseUserOperationReceipt: "e2eControls:releaseUserOperationReceipt"
2395
+ },
2143
2396
  system: { health: "system:health" }
2144
2397
  };
2145
2398
  //#endregion
@@ -2661,7 +2914,19 @@ const StoredPermissionCommandSchema = Schema.Struct({
2661
2914
  roleKey: RoleKeySchema,
2662
2915
  allowanceKey: Schema.NullOr(AllowanceKeySchema),
2663
2916
  memberSafe: Schema.NullOr(SafeAddressSchema),
2664
- expectedAuthorityEvents: Schema.Array(Bytes32Schema)
2917
+ expectedAuthorityEvents: Schema.Array(Bytes32Schema),
2918
+ /**
2919
+ * #1961 R03. The authenticated user who created the command, and the browser
2920
+ * linkage captured with it. Delayed verification and bounded retries read
2921
+ * this snapshot instead of the current viewer, so one command keeps one
2922
+ * origin. Both are absent on commands created before this contract.
2923
+ *
2924
+ * The context stays `unknown` on purpose: `sanitizeObservationContext` owns
2925
+ * the field allowlist, and every reader passes the stored value through it
2926
+ * rather than repeating those rules here.
2927
+ */
2928
+ originatingAuthUserId: Schema.optional(Schema.String),
2929
+ originatingObservationContext: Schema.optional(Schema.Unknown)
2665
2930
  });
2666
2931
  Schema.Struct({
2667
2932
  command: StoredPermissionCommandSchema,
@@ -5434,6 +5699,7 @@ const financialOpsContract = {
5434
5699
  activitySummary: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].summary),
5435
5700
  activityGet: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].get),
5436
5701
  activityAnnotate: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].annotate),
5702
+ activityOverlay: makeFunctionReference(CAPXUL_FUNCTIONS["movement/annotations"].overlay),
5437
5703
  verifyPaymentDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].verifyPaymentDocument),
5438
5704
  renderStoredDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].renderStoredDocument)
5439
5705
  };
@@ -5450,8 +5716,357 @@ const moneyExecutionContract = {
5450
5716
  submitPaymentCommandExecution: makeFunctionReference(CAPXUL_FUNCTIONS["moneyExecution/paymentCommandActions"].submitPaymentCommandExecution)
5451
5717
  };
5452
5718
  //#endregion
5719
+ //#region src/ports/indexer-read.ts
5720
+ /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
5721
+ const INDEXER_MAX_LIMIT = 1e3;
5722
+ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
5723
+ /**
5724
+ * Refuse a page the indexer would refuse, before any implementation spends a
5725
+ * round trip on it. Every implementation calls this, so a consumer test written
5726
+ * against the hermetic adapter cannot pass on input production rejects. Throws
5727
+ * a `CapxulError`; callers convert it to an `IndexerReadError`.
5728
+ */
5729
+ function assertIndexerPage(input) {
5730
+ const safe = String(input.safe).toLowerCase();
5731
+ if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
5732
+ 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}`);
5733
+ const offset = input.offset ?? 0;
5734
+ if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
5735
+ return safe;
5736
+ }
5737
+ var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
5738
+ function indexerReadErrorFromCapxul(operation, error, cause = error) {
5739
+ return new IndexerReadError({
5740
+ operation,
5741
+ publicCode: error.code,
5742
+ publicError: error,
5743
+ cause,
5744
+ ...error.details === void 0 ? {} : { details: error.details }
5745
+ });
5746
+ }
5747
+ Context.Service()("@capxul/sdk/ports/IndexerReadPort");
5748
+ //#endregion
5749
+ //#region src/surface/activity-stitch.ts
5750
+ /** Epoch milliseconds of the block that carried the row. */
5751
+ function blockMillis(row) {
5752
+ return Number(row.timestamp) * 1e3;
5753
+ }
5754
+ /** `0x1234…abcd` — the counterparty of an unclaimed transfer is an address. */
5755
+ function shortAddressLabel(address) {
5756
+ return address.length <= 12 ? address : `${address.slice(0, 6)}…${address.slice(-4)}`;
5757
+ }
5758
+ function directionFor(safe, transfer) {
5759
+ const from = transfer.from.toLowerCase();
5760
+ const to = transfer.to.toLowerCase();
5761
+ if (from === safe && to === safe) return "self";
5762
+ return to === safe ? "in" : "out";
5763
+ }
5764
+ /**
5765
+ * The Transfer a protocol Payment moved is the same money as its `payment`
5766
+ * row. One actor sees one item for it, so the Transfer is claimed and does not
5767
+ * become a second row. Another transfer in the same transaction that this
5768
+ * payment did not move stays unclaimed and keeps its own row.
5769
+ */
5770
+ function claimedTransferIds(transfers, payments) {
5771
+ const claimed = /* @__PURE__ */ new Set();
5772
+ for (const payment of payments) {
5773
+ 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());
5774
+ if (match !== void 0) claimed.add(match.id);
5775
+ }
5776
+ return claimed;
5777
+ }
5778
+ function withAnnotation(item, annotation) {
5779
+ return annotation?.counterpartyLabel === void 0 ? item : {
5780
+ ...item,
5781
+ counterpartyLabel: annotation.counterpartyLabel
5782
+ };
5783
+ }
5784
+ /**
5785
+ * Confirmation comes from the chain, never from Convex.
5786
+ *
5787
+ * A Ponder row for this Payment's settlement means settled, and its receipt is
5788
+ * the indexed transaction. No Ponder row and a Convex row that still says
5789
+ * settled means the block that carried it was dropped: the row goes back to
5790
+ * `settling` and its annotation, which keys on the Payment id, is untouched.
5791
+ */
5792
+ function confirmPayment(intent, chain, hasSettlement) {
5793
+ if (chain !== void 0) return {
5794
+ ...intent,
5795
+ status: "settled",
5796
+ receipt: {
5797
+ chainId: chain.chainId,
5798
+ transactionHash: chain.txHash
5799
+ },
5800
+ updatedAt: blockMillis(chain)
5801
+ };
5802
+ if (hasSettlement && intent.status === "settled") return {
5803
+ ...intent,
5804
+ status: "settling",
5805
+ receipt: null
5806
+ };
5807
+ return intent;
5808
+ }
5809
+ /**
5810
+ * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
5811
+ * denominate its asset. The indexer serves only configured assets, so a row
5812
+ * the registry does not know means the SDK and that deployment disagree; it is
5813
+ * dropped rather than shown with a guessed currency.
5814
+ */
5815
+ function transferItem(safe, transfer, annotation) {
5816
+ const asset = configuredMoneyAssetById(transfer.assetId);
5817
+ if (asset === null) return null;
5818
+ const direction = directionFor(safe, transfer);
5819
+ const counterparty = direction === "in" ? transfer.from : transfer.to;
5820
+ const at = blockMillis(transfer);
5821
+ return withAnnotation({
5822
+ kind: "movement",
5823
+ id: transfer.id,
5824
+ classification: "movement_only_outflow",
5825
+ amount: {
5826
+ currency: asset.peg ?? asset.symbol,
5827
+ value: formatUnits(transfer.amount, asset.decimals),
5828
+ decimals: asset.decimals
5829
+ },
5830
+ direction,
5831
+ counterpartyLabel: shortAddressLabel(counterparty),
5832
+ receipt: {
5833
+ chainId: transfer.chainId,
5834
+ transactionHash: transfer.txHash
5835
+ },
5836
+ createdAt: at,
5837
+ updatedAt: at
5838
+ }, annotation);
5839
+ }
5840
+ /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
5841
+ function compareItems(left, right) {
5842
+ if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
5843
+ if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
5844
+ if (left.id === right.id) return 0;
5845
+ return left.id < right.id ? 1 : -1;
5846
+ }
5847
+ function stitchActivity(input) {
5848
+ const safe = input.safe.toLowerCase();
5849
+ const chainBySettlement = /* @__PURE__ */ new Map();
5850
+ for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
5851
+ const chainByPaymentId = /* @__PURE__ */ new Map();
5852
+ const settledPaymentIds = /* @__PURE__ */ new Set();
5853
+ for (const link of input.links) {
5854
+ settledPaymentIds.add(link.paymentId);
5855
+ const row = chainBySettlement.get(link.settlementId.toLowerCase());
5856
+ if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
5857
+ }
5858
+ const annotations = /* @__PURE__ */ new Map();
5859
+ for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
5860
+ const items = [];
5861
+ const claiming = [];
5862
+ for (const intent of input.intents) {
5863
+ if (intent.kind !== "payment") continue;
5864
+ const chain = chainByPaymentId.get(intent.id);
5865
+ if (chain !== void 0) claiming.push(chain);
5866
+ items.push(withAnnotation(confirmPayment(intent, chain, settledPaymentIds.has(intent.id)), annotations.get(`payment:${intent.id}`)));
5867
+ }
5868
+ const claimed = claimedTransferIds(input.transfers, claiming);
5869
+ for (const transfer of input.transfers) {
5870
+ if (claimed.has(transfer.id)) continue;
5871
+ const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
5872
+ if (item !== null) items.push(item);
5873
+ }
5874
+ return items.sort(compareItems);
5875
+ }
5876
+ /** The reference ids one stitched page needs its Convex overlay for. */
5877
+ function overlayReferences(items) {
5878
+ return items.map((item) => ({
5879
+ kind: item.kind,
5880
+ id: item.id
5881
+ }));
5882
+ }
5883
+ /**
5884
+ * Money in, money out and the pending share, one bucket per currency and
5885
+ * precision. Two assets that peg to the same currency at different precision
5886
+ * stay distinct buckets: their raw units are not interchangeable.
5887
+ */
5888
+ function summarizeItems(items) {
5889
+ const buckets = /* @__PURE__ */ new Map();
5890
+ for (const item of items) {
5891
+ const key = `${item.amount.currency}:${String(item.amount.decimals)}`;
5892
+ const bucket = buckets.get(key) ?? {
5893
+ currency: item.amount.currency,
5894
+ decimals: item.amount.decimals,
5895
+ in: 0n,
5896
+ out: 0n,
5897
+ pendingIn: 0n
5898
+ };
5899
+ const raw = rawUnits(item.amount.value, item.amount.decimals);
5900
+ if (item.direction === "in") {
5901
+ bucket.in += raw;
5902
+ if (item.kind === "payment" && item.status !== "settled") bucket.pendingIn += raw;
5903
+ } else if (item.direction === "out") bucket.out += raw;
5904
+ buckets.set(key, bucket);
5905
+ }
5906
+ return [...buckets.values()].map((bucket) => ({
5907
+ currency: bucket.currency,
5908
+ decimals: bucket.decimals,
5909
+ in: bucket.in.toString(),
5910
+ out: bucket.out.toString(),
5911
+ net: (bucket.in - bucket.out).toString(),
5912
+ pendingIn: bucket.pendingIn.toString()
5913
+ }));
5914
+ }
5915
+ /** The inverse of `formatUnits`, kept exact: `"1.25"` at 6 decimals is `1250000n`. */
5916
+ function rawUnits(value, decimals) {
5917
+ const negative = value.startsWith("-");
5918
+ const [whole = "0", fraction = ""] = (negative ? value.slice(1) : value).split(".");
5919
+ const raw = BigInt(`${whole}${fraction.padEnd(decimals, "0").slice(0, decimals)}`);
5920
+ return negative ? -raw : raw;
5921
+ }
5922
+ //#endregion
5923
+ //#region src/surface/activity-read.ts
5924
+ /** How many Convex intent rows one page reads for its labels and pending rows. */
5925
+ const INTENT_WINDOW = 100;
5926
+ /** The window a summary aggregates over. The indexer's own page bound. */
5927
+ const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
5928
+ function parseCursor(value) {
5929
+ if (value === void 0) return 0;
5930
+ try {
5931
+ const parsed = JSON.parse(value);
5932
+ return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
5933
+ } catch {
5934
+ return 0;
5935
+ }
5936
+ }
5937
+ function inRange(item, range) {
5938
+ if (range?.from !== void 0 && item.createdAt < range.from) return false;
5939
+ return range?.to === void 0 || item.createdAt <= range.to;
5940
+ }
5941
+ /** The caller's filter applied to the merged page. The join owns the order. */
5942
+ function matchesFilter(item, filter) {
5943
+ if (filter === void 0) return true;
5944
+ if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
5945
+ if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
5946
+ if (filter.status === void 0) return true;
5947
+ return item.kind === "payment" && filter.status.includes(item.status);
5948
+ }
5949
+ function overlay(deps, actor, controls, input) {
5950
+ return deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
5951
+ ...actor === void 0 ? {} : { actor },
5952
+ ...input.references === void 0 ? {} : { references: input.references },
5953
+ ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds }
5954
+ } }));
5955
+ }
5956
+ function convexPage(deps, actor, controls, input) {
5957
+ return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
5958
+ ...actor === void 0 ? {} : { actor },
5959
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor },
5960
+ ...input.limit === void 0 ? {} : { limit: input.limit },
5961
+ ...input.range === void 0 ? {} : { range: input.range }
5962
+ } }));
5963
+ }
5964
+ /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
5965
+ function chainRows(deps, safe, limit) {
5966
+ const page = {
5967
+ safe,
5968
+ limit: Math.min(limit, INDEXER_MAX_LIMIT)
5969
+ };
5970
+ return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
5971
+ transfers,
5972
+ payments
5973
+ })), Effect.catch(() => Effect.succeed(null)));
5974
+ }
5975
+ /** The stitched, filtered rows of one window, newest first. */
5976
+ function stitchedWindow(deps, actor, controls, window, params) {
5977
+ return Effect.gen(function* () {
5978
+ const scope = yield* overlay(deps, actor, controls, {});
5979
+ const convex = yield* convexPage(deps, actor, controls, {
5980
+ limit: INTENT_WINDOW,
5981
+ ...params.range === void 0 ? {} : { range: params.range }
5982
+ });
5983
+ const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
5984
+ if (rows === null || scope.safe === null) return {
5985
+ items: convex.items.filter((item) => matchesFilter(item, params.filter)),
5986
+ stitched: false
5987
+ };
5988
+ const decoration = yield* overlay(deps, actor, controls, {
5989
+ references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
5990
+ kind: "movement",
5991
+ id: row.id
5992
+ }))],
5993
+ settlementIds: rows.payments.map((row) => row.settlementId)
5994
+ });
5995
+ return {
5996
+ items: stitchActivity({
5997
+ safe: scope.safe,
5998
+ transfers: rows.transfers,
5999
+ payments: rows.payments,
6000
+ intents: convex.items,
6001
+ links: decoration.links,
6002
+ annotations: decoration.annotations
6003
+ }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
6004
+ stitched: true
6005
+ };
6006
+ });
6007
+ }
6008
+ function readActivityPage(deps, actor, controls, params) {
6009
+ const limit = params?.limit ?? 25;
6010
+ const offset = parseCursor(params?.cursor);
6011
+ return stitchedWindow(deps, actor, controls, offset + limit + 1, {
6012
+ ...params?.range === void 0 ? {} : { range: params.range },
6013
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
6014
+ }).pipe(Effect.map(({ items, stitched }) => {
6015
+ const observedAt = Date.now();
6016
+ if (!stitched) return {
6017
+ items: items.slice(0, limit),
6018
+ cursor: null,
6019
+ checkpoint: observedAt,
6020
+ observedAt
6021
+ };
6022
+ return {
6023
+ items: items.slice(offset, offset + limit),
6024
+ cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
6025
+ checkpoint: observedAt,
6026
+ observedAt
6027
+ };
6028
+ }));
6029
+ }
6030
+ function readActivitySummary(deps, actor, controls, params) {
6031
+ return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items }) => ({
6032
+ window: {
6033
+ from: params?.window?.from ?? null,
6034
+ to: params?.window?.to ?? null
6035
+ },
6036
+ totals: summarizeItems(items),
6037
+ count: items.length,
6038
+ observedAt: Date.now()
6039
+ })));
6040
+ }
6041
+ /**
6042
+ * Tell the caller when the indexed page for this Safe changed.
6043
+ *
6044
+ * The transport is the indexer's one SSE connection, wired in the Ponder
6045
+ * adapter under `activity.subscribe`. A reactive snapshot is not a product
6046
+ * event, so nothing here emits telemetry: the adapter reports a teardown, and
6047
+ * the caller re-reads.
6048
+ */
6049
+ function subscribeActivity(deps, actor, controls, onChange) {
6050
+ return Effect.gen(function* () {
6051
+ const scope = yield* overlay(deps, actor, controls, {});
6052
+ if (scope.safe === null) return () => {};
6053
+ const page = {
6054
+ safe: scope.safe,
6055
+ limit: INTENT_WINDOW
6056
+ };
6057
+ let delivered = false;
6058
+ const notify = () => {
6059
+ if (delivered) onChange();
6060
+ delivered = true;
6061
+ };
6062
+ return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
6063
+ if (snapshot.status === "ok") notify();
6064
+ });
6065
+ });
6066
+ }
6067
+ //#endregion
5453
6068
  //#region package.json
5454
- var version = "4.2.0-rc.1";
6069
+ var version = "4.2.0-rc.11";
5455
6070
  //#endregion
5456
6071
  //#region src/telemetry/exception-projection.ts
5457
6072
  /** Fixed fallback for failures that have no safe message. */
@@ -5644,6 +6259,22 @@ function observeFailedResult(result, adapter, operation, origin) {
5644
6259
  return result;
5645
6260
  }
5646
6261
  /**
6262
+ * @internal Reports a failure that can never become a method result.
6263
+ *
6264
+ * A reactive stream is torn down after its call already returned, so nothing
6265
+ * downstream observes it. `captureException` is the right kind: the caller did
6266
+ * not ask for the end, so it is not an expected product outcome.
6267
+ *
6268
+ * A caller that DID ask for the end — the session it authenticated with
6269
+ * expired, and the service closed the stream on purpose — passes
6270
+ * `kind: "operation"` instead, so the refusal lands in the expected-outcome
6271
+ * stream its code already declares.
6272
+ */
6273
+ function observeStreamFailure(adapter, operation, cause, kind = "exception") {
6274
+ if (adapter === void 0) return;
6275
+ report(adapter, kind, operation, cause, resolveAdapterSnapshot(adapter));
6276
+ }
6277
+ /**
5647
6278
  * One failure, one event. The identity machine reports a failed transition
5648
6279
  * first; when the same `CapxulError` then surfaces as a public method result
5649
6280
  * (directly or down its `cause` chain), the boundary drops that second report.
@@ -5721,6 +6352,34 @@ function ignoreDeliveryFailure(delivery) {
5721
6352
  } catch {}
5722
6353
  }
5723
6354
  /**
6355
+ * Fold a caller-captured attempt identity into the host's resolved context.
6356
+ *
6357
+ * The host's `resolveContext` runs at the moment an SDK method is entered, so a
6358
+ * host that mints a correlation per resolve gives every operation of one user
6359
+ * attempt a different identity — and a user or Organization switch between the
6360
+ * click and the reply relabels an action that already started. When the caller
6361
+ * captured `correlation_id`/`journey_id` BEFORE the action started, that is the
6362
+ * attempt, and it replaces what the resolver returned for this one call. The
6363
+ * result is what the SDK span/Log, the failure envelope and the backend carrier
6364
+ * all read, so the three agree by construction rather than by convention.
6365
+ *
6366
+ * A caller that captured nothing gets the resolver's context unchanged, so
6367
+ * automatic reads keep their own invocation context.
6368
+ */
6369
+ function withCapturedAttemptContext(context, controls) {
6370
+ const correlationId = controls?.correlation_id;
6371
+ const journeyId = controls?.journey_id;
6372
+ if (correlationId === void 0 && journeyId === void 0) return context;
6373
+ const captured = sanitizeObservationContext({
6374
+ ...correlationId === void 0 ? {} : { correlationId },
6375
+ ...journeyId === void 0 ? {} : { journeyId }
6376
+ });
6377
+ return captured === void 0 ? context : {
6378
+ ...context,
6379
+ ...captured
6380
+ };
6381
+ }
6382
+ /**
5724
6383
  * Map an ALREADY-sanitized observation context to the snake_case PostHog
5725
6384
  * property keys. Shared by the failure boundary here and the host
5726
6385
  * success-telemetry seam (`telemetry/from-posthog.ts`) so both attach identical
@@ -6718,6 +7377,11 @@ function paymentExecutionIntent(input) {
6718
7377
  }
6719
7378
  function makeFinancialOpsMethods(deps) {
6720
7379
  const fns = deps.functions ?? financialOpsContract;
7380
+ const indexerDeps = deps.indexerRead === void 0 ? void 0 : {
7381
+ indexer: deps.indexerRead,
7382
+ convexCall: deps.convexCall,
7383
+ functions: fns
7384
+ };
6721
7385
  const executionFns = deps.moneyExecutionFunctions ?? moneyExecutionContract;
6722
7386
  const requestKeyScope = deps.requestKeyScope ?? randomPaymentRequestKey();
6723
7387
  const inFlightPersonalPayments = /* @__PURE__ */ new Map();
@@ -6948,22 +7612,26 @@ function makeFinancialOpsMethods(deps) {
6948
7612
  }
6949
7613
  },
6950
7614
  activity: {
7615
+ subscribe: (onChange, params, options) => {
7616
+ const controls = deps.invocationControls?.(options) ?? options;
7617
+ 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);
7618
+ },
6951
7619
  list: (params, options) => {
6952
7620
  const controls = deps.invocationControls?.(options) ?? options;
6953
- return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => deps.convexCall.query(fns.activityList, copyInvocationObservation(controls, { input: {
7621
+ 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: {
6954
7622
  ...actor === void 0 ? {} : { actor },
6955
7623
  ...params?.cursor === void 0 ? {} : { cursor: params.cursor },
6956
7624
  ...params?.limit === void 0 ? {} : { limit: params.limit },
6957
7625
  ...params?.range === void 0 ? {} : { range: params.range },
6958
7626
  ...params?.filter === void 0 ? {} : { filter: params.filter }
6959
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
7627
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
6960
7628
  },
6961
7629
  summary: (params, options) => {
6962
7630
  const controls = deps.invocationControls?.(options) ?? options;
6963
- return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => deps.convexCall.query(fns.activitySummary, copyInvocationObservation(controls, { input: {
7631
+ 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: {
6964
7632
  ...actor === void 0 ? {} : { actor },
6965
7633
  ...params?.window === void 0 ? {} : { window: params.window }
6966
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
7634
+ } })) : readActivitySummary(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
6967
7635
  },
6968
7636
  get: (reference, options) => {
6969
7637
  const controls = deps.invocationControls?.(options) ?? options;
@@ -7852,7 +8520,8 @@ var BetterAuthBrowserAdapter = class {
7852
8520
  try {
7853
8521
  const res = await this.fetchImpl(this.url("/api/auth/get-session"), withSignal$1({
7854
8522
  method: "GET",
7855
- credentials: "include"
8523
+ credentials: "include",
8524
+ cache: "no-store"
7856
8525
  }, options?.signal));
7857
8526
  if (options?.signal?.aborted) return {
7858
8527
  ok: false,
@@ -7924,7 +8593,8 @@ var BetterAuthBrowserAdapter = class {
7924
8593
  try {
7925
8594
  const res = await this.fetchImpl(this.url("/api/auth/convex/token"), withSignal$1({
7926
8595
  method: "GET",
7927
- credentials: "include"
8596
+ credentials: "include",
8597
+ cache: "no-store"
7928
8598
  }, options?.signal));
7929
8599
  if (options?.signal?.aborted) return {
7930
8600
  ok: false,
@@ -8437,7 +9107,7 @@ function isTransientBootstrapFailure(error) {
8437
9107
  const status = error.details?.httpStatus;
8438
9108
  return typeof status === "number" && isTransientHttpStatus(status);
8439
9109
  }
8440
- async function safeText(res) {
9110
+ async function safeText$1(res) {
8441
9111
  try {
8442
9112
  return await res.text();
8443
9113
  } catch {
@@ -8510,6 +9180,8 @@ var HttpBootstrapAdapter = class {
8510
9180
  siteBaseUrl: normalizeRuntimeUrl("siteBaseUrl", decodedState.siteBaseUrl),
8511
9181
  openfortPublishableKey: decodedState.openfortPublishableKey,
8512
9182
  shieldPublishableKey: decodedState.shieldPublishableKey,
9183
+ ...decodedState.openfortAuthProvider === void 0 ? {} : { openfortAuthProvider: decodedState.openfortAuthProvider },
9184
+ ...decodedState.indexerUrl === void 0 ? {} : { indexerUrl: decodedState.indexerUrl },
8513
9185
  ...decodedEngineeringTelemetry !== void 0 && Result.isSuccess(decodedEngineeringTelemetry) ? { engineeringTelemetry: decodedEngineeringTelemetry.success } : {}
8514
9186
  };
8515
9187
  },
@@ -8518,7 +9190,7 @@ var HttpBootstrapAdapter = class {
8518
9190
  return bootstrapErrorFromCapxul("malformedBody", Errors.providerError("convex", "bootstrap", cause instanceof Error ? cause : new Error(String(cause))));
8519
9191
  }
8520
9192
  });
8521
- return Effect.promise(() => safeText(res)).pipe(Effect.flatMap((body) => {
9193
+ return Effect.promise(() => safeText$1(res)).pipe(Effect.flatMap((body) => {
8522
9194
  if (res.status === 401 || body.startsWith("NOT_AUTHENTICATED")) return Effect.fail(bootstrapErrorFromCapxul("notAuthenticated", Errors.notAuthenticated()));
8523
9195
  if (res.status === 400 || body.startsWith("INVALID_INPUT")) return Effect.fail(bootstrapErrorFromCapxul("invalidInput", Errors.invalidInput("publishableKey", "rejected by bootstrap")));
8524
9196
  const responseBody = body.slice(0, 300);
@@ -8748,6 +9420,12 @@ function makeConnectionId() {
8748
9420
  }
8749
9421
  //#endregion
8750
9422
  //#region src/adapters/convex-call/ConvexCallAdapter.ts
9423
+ /**
9424
+ * A socket that has not emitted `close` within this window is not going to.
9425
+ * ponytail: one fixed bound; `closeTimeoutMs` is the knob if a slow runtime
9426
+ * ever needs a different one.
9427
+ */
9428
+ const DEFAULT_CLOSE_TIMEOUT_MS = 5e3;
8751
9429
  /** Exact floor-first allowlist; every additional handler must migrate its validator first. */
8752
9430
  const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
8753
9431
  "payroll/actions:authorizeRun",
@@ -8760,6 +9438,9 @@ const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
8760
9438
  "org/actions:resumeBootstrapSubmission",
8761
9439
  "org/actions:confirmBootstrap",
8762
9440
  "org/actions:readTreasury",
9441
+ "moneyExecution/actions:preparePermissionExecution",
9442
+ "moneyExecution/actions:submitPermissionExecution",
9443
+ "permission/actions:verify",
8763
9444
  "moneyExecution/actions:preparePaymentExecution",
8764
9445
  "moneyExecution/actions:abandonPaymentExecution",
8765
9446
  "moneyExecution/actions:submitPaymentExecution",
@@ -8784,6 +9465,9 @@ const OBSERVED_CONVEX_QUERIES = /* @__PURE__ */ new Set([
8784
9465
  "payroll/queries:groups",
8785
9466
  "payroll/queries:terms",
8786
9467
  "financialOps/queries:resolveRecipient",
9468
+ "org/queries:me",
9469
+ "permission/queries:read",
9470
+ "permission/queries:authorize",
8787
9471
  "identity/queries:loadByAuthUserId",
8788
9472
  "smartAccount/queries:loadByAuthUserId",
8789
9473
  "org/lifecycle:load"
@@ -8802,6 +9486,7 @@ const OBSERVED_CONVEX_MUTATIONS = /* @__PURE__ */ new Set([
8802
9486
  "financialOps/requestsInbox:decline",
8803
9487
  "payroll/mutations:saveGroup",
8804
9488
  "payroll/mutations:removeGroup",
9489
+ "permission/mutations:command",
8805
9490
  "identity/mutations:create",
8806
9491
  "identity/mutations:update",
8807
9492
  "identity/mutations:completeOnboarding",
@@ -8816,7 +9501,9 @@ var ConvexCallAdapter = class {
8816
9501
  #applicationId;
8817
9502
  #observation;
8818
9503
  #connectionMonitor;
9504
+ #closeTimeoutMs;
8819
9505
  constructor(deps) {
9506
+ this.#closeTimeoutMs = deps.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;
8820
9507
  this.#connectionMonitor = new ConvexConnectionMonitor();
8821
9508
  const webSocketConstructor = this.#connectionMonitor.observedWebSocketConstructor();
8822
9509
  this.#client = deps.client ?? new ConvexClient(deps.convexUrl, webSocketConstructor === void 0 ? {} : { webSocketConstructor });
@@ -8915,11 +9602,33 @@ var ConvexCallAdapter = class {
8915
9602
  callback({ status: "loading" });
8916
9603
  })));
8917
9604
  }
9605
+ /**
9606
+ * Teardown always completes. `ConvexClient.close()` resolves only when the
9607
+ * socket emits `close`, and `WebSocketManager.close()` in the `connecting`
9608
+ * state arms `ws.onopen = () => ws.close()` without ever closing the socket
9609
+ * itself — so a handshake that stalls right after a server-initiated close
9610
+ * (code 1012, which is what a Convex deploy sends) leaves that promise
9611
+ * pending for as long as the page lives. Convex marks the client closed and
9612
+ * terminates the socket manager synchronously before returning it, so
9613
+ * abandoning it drops nothing. A rejection is dropped for the same reason —
9614
+ * the layer release turns it into a defect that fails the whole scope close,
9615
+ * and teardown has no caller who can act on it.
9616
+ */
8918
9617
  async close() {
8919
9618
  this.#connectionMonitor.close();
8920
- await this.#client.close();
9619
+ await settledWithin(this.#client.close(), this.#closeTimeoutMs);
8921
9620
  }
8922
9621
  };
9622
+ function settledWithin(promise, timeoutMs) {
9623
+ return new Promise((resolve) => {
9624
+ const timer = setTimeout(resolve, timeoutMs);
9625
+ const finish = () => {
9626
+ clearTimeout(timer);
9627
+ resolve();
9628
+ };
9629
+ promise.then(finish, finish);
9630
+ });
9631
+ }
8923
9632
  function ConvexCallLayer(deps) {
8924
9633
  return Layer.effect(ConvexCallPortTag, Effect.acquireRelease(Effect.sync(() => new ConvexCallAdapter(deps)), (adapter) => Effect.promise(() => adapter.close()).pipe(Effect.orDie)));
8925
9634
  }
@@ -9872,6 +10581,32 @@ var ConsoleDiagnosticAdapter = class {
9872
10581
  }
9873
10582
  };
9874
10583
  //#endregion
10584
+ //#region src/adapters/diagnostic/EffectLogDiagnosticAdapter.ts
10585
+ /**
10586
+ * Diagnostic breadcrumbs as SDK log records. The signer's readiness cycle
10587
+ * traces (`openfort.*`) used to reach only the browser console; through this
10588
+ * adapter they run in the client's own Effect context, so the engineering
10589
+ * telemetry layer ships them to PostHog Logs beside `identity.transition`.
10590
+ * Logging never throws back into the cycle that emitted it.
10591
+ */
10592
+ var EffectLogDiagnosticAdapter = class {
10593
+ #runPromise;
10594
+ #console;
10595
+ constructor(context, consoleFallback) {
10596
+ this.#runPromise = Effect.runPromiseWith(context);
10597
+ this.#console = consoleFallback;
10598
+ }
10599
+ trace(scope, detail) {
10600
+ this.#console?.trace(scope, detail);
10601
+ try {
10602
+ this.#runPromise(Effect.logInfo(scope).pipe(Effect.annotateLogs({
10603
+ ...detail,
10604
+ diagnostic_scope: scope
10605
+ }), Effect.catchCause(() => Effect.void))).catch(() => void 0);
10606
+ } catch {}
10607
+ }
10608
+ };
10609
+ //#endregion
9875
10610
  //#region src/openfort/create-openfort-browser-signer.ts
9876
10611
  function httpStatusOf(link) {
9877
10612
  const carrier = link;
@@ -10003,15 +10738,35 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10003
10738
  });
10004
10739
  throw failOpenfort("configure", "no-secure-context", /* @__PURE__ */ new Error("Web Crypto unavailable: browser is not a secure context"));
10005
10740
  }
10006
- function betterAuthSessionUrl() {
10007
- return `${authBaseUrl}/get-session`;
10741
+ const authProvider = bootstrap.openfortAuthProvider ?? "better-auth";
10742
+ function accessTokenUrl() {
10743
+ return authProvider === "oidc" ? `${authBaseUrl}/convex/token` : `${authBaseUrl}/get-session`;
10008
10744
  }
10009
10745
  function encryptionSessionUrl() {
10010
10746
  return `${authBaseUrl}/encryption-session`;
10011
10747
  }
10748
+ let cycleStartedAt = null;
10749
+ let stepStartedAt = 0;
10750
+ let tokenCalls = 0;
10751
+ const sinceCycleStart = () => cycleStartedAt === null ? void 0 : Date.now() - cycleStartedAt;
10752
+ const step = (name, extra = {}) => {
10753
+ const now = Date.now();
10754
+ diagnostic?.trace("openfort.step", {
10755
+ step: name,
10756
+ duration_ms: now - stepStartedAt,
10757
+ elapsed_ms: sinceCycleStart(),
10758
+ ...extra
10759
+ });
10760
+ stepStartedAt = now;
10761
+ };
10012
10762
  async function fetchBetterAuthAccessToken() {
10763
+ tokenCalls += 1;
10764
+ const tokenStartedAt = Date.now();
10013
10765
  try {
10014
- const response = await fetch(betterAuthSessionUrl(), { credentials: "include" });
10766
+ const response = await fetch(accessTokenUrl(), {
10767
+ credentials: "include",
10768
+ cache: "no-store"
10769
+ });
10015
10770
  if (!response.ok) {
10016
10771
  diagnostic?.trace("openfort.token", {
10017
10772
  ok: false,
@@ -10021,7 +10776,8 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10021
10776
  });
10022
10777
  return null;
10023
10778
  }
10024
- const token = (await response.json()).session?.token?.trim();
10779
+ const body = await response.json();
10780
+ const token = (authProvider === "oidc" ? body.token : body.session?.token)?.trim();
10025
10781
  if (token === void 0 || token.length === 0) {
10026
10782
  diagnostic?.trace("openfort.token", {
10027
10783
  ok: false,
@@ -10032,7 +10788,11 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10032
10788
  }
10033
10789
  diagnostic?.trace("openfort.token", {
10034
10790
  ok: true,
10035
- tokenPresent: true
10791
+ tokenPresent: true,
10792
+ provider: authProvider,
10793
+ call: tokenCalls,
10794
+ duration_ms: Date.now() - tokenStartedAt,
10795
+ elapsed_ms: sinceCycleStart()
10036
10796
  });
10037
10797
  return token;
10038
10798
  } catch (cause) {
@@ -10048,7 +10808,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10048
10808
  baseConfiguration: { publishableKey: bootstrap.openfortPublishableKey },
10049
10809
  shieldConfiguration: { shieldPublishableKey: bootstrap.shieldPublishableKey },
10050
10810
  thirdPartyAuth: {
10051
- provider: ThirdPartyOAuthProvider.BETTER_AUTH,
10811
+ provider: authProvider === "oidc" ? ThirdPartyOAuthProvider.OIDC : ThirdPartyOAuthProvider.BETTER_AUTH,
10052
10812
  getAccessToken: fetchBetterAuthAccessToken
10053
10813
  }
10054
10814
  });
@@ -10088,10 +10848,16 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10088
10848
  let walletReadyPromise = null;
10089
10849
  function startWalletReady() {
10090
10850
  return (async () => {
10851
+ const startedAt = Date.now();
10852
+ cycleStartedAt = startedAt;
10853
+ stepStartedAt = startedAt;
10854
+ tokenCalls = 0;
10091
10855
  if (isInsecureBrowserContext()) failNoSecureContext();
10092
10856
  await openfort.waitForInitialization();
10857
+ step("initialize");
10093
10858
  const accessToken = await fetchBetterAuthAccessToken();
10094
10859
  if (accessToken === null) throw openfortProviderError("token", /* @__PURE__ */ new Error("Better Auth access token unavailable for Openfort"));
10860
+ step("token");
10095
10861
  let encryptionResponse;
10096
10862
  try {
10097
10863
  encryptionResponse = await fetch(encryptionSessionUrl(), {
@@ -10134,10 +10900,12 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10134
10900
  ok: true,
10135
10901
  httpStatus: encryptionResponse.status
10136
10902
  });
10903
+ step("encryptionSession", { httpStatus: encryptionResponse.status });
10137
10904
  let embeddedState;
10138
10905
  try {
10139
10906
  embeddedState = await openfort.embeddedWallet.getEmbeddedState();
10140
10907
  diagnostic?.trace("openfort.embeddedState", { state: embeddedState });
10908
+ step("embeddedState", { state: embeddedState });
10141
10909
  } catch (cause) {
10142
10910
  diagnostic?.trace("openfort.embeddedState", {
10143
10911
  ok: false,
@@ -10151,6 +10919,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10151
10919
  try {
10152
10920
  await configureEmbeddedWallet(encryptionBody.sessionId);
10153
10921
  diagnostic?.trace("openfort.configure", { ok: true });
10922
+ step("configure");
10154
10923
  } catch (cause) {
10155
10924
  const error = openfortProviderError("configure", cause);
10156
10925
  diagnostic?.trace("openfort.configure", {
@@ -10163,6 +10932,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10163
10932
  try {
10164
10933
  await openfort.embeddedWallet.get();
10165
10934
  diagnostic?.trace("openfort.get", { ok: true });
10935
+ step("get");
10166
10936
  } catch (cause) {
10167
10937
  if (!isStaleUserSignal(cause)) {
10168
10938
  diagnostic?.trace("openfort.get", {
@@ -10176,7 +10946,13 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10176
10946
  failure_mode: "stale-openfort-cache"
10177
10947
  });
10178
10948
  await healStaleOpenfortCache(encryptionBody.sessionId);
10949
+ step("healStaleCache");
10179
10950
  }
10951
+ step("walletReady", {
10952
+ ok: true,
10953
+ total_ms: Date.now() - startedAt,
10954
+ token_calls: tokenCalls
10955
+ });
10180
10956
  })();
10181
10957
  }
10182
10958
  async function ensureOpenfortWalletReady() {
@@ -10259,9 +11035,13 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10259
11035
  statusStore,
10260
11036
  ensureWalletReady: ensureOpenfortWalletReady,
10261
11037
  getAddress: async () => {
11038
+ const addressStartedAt = Date.now();
10262
11039
  try {
10263
11040
  const address = await signer.getAddress();
10264
- diagnostic?.trace("openfort.address", { ok: true });
11041
+ diagnostic?.trace("openfort.address", {
11042
+ ok: true,
11043
+ duration_ms: Date.now() - addressStartedAt
11044
+ });
10265
11045
  return address;
10266
11046
  } catch (cause) {
10267
11047
  diagnostic?.trace("openfort.address", {
@@ -11363,14 +12143,66 @@ function organizationWrongState(currentState, validStates) {
11363
12143
  function requestCancelled(options) {
11364
12144
  return options?.signal?.aborted === true;
11365
12145
  }
12146
+ function accountInFlight(state) {
12147
+ return state.phase === "authenticated" && (state.account.at === "deriving" || state.account.at === "claiming");
12148
+ }
12149
+ function settledAccount(deps, options) {
12150
+ return new Promise((resolve) => {
12151
+ let done = false;
12152
+ let unsubscribe = null;
12153
+ const onAbort = () => finish({
12154
+ ok: false,
12155
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.onboarding.completeOrganization })
12156
+ });
12157
+ const finish = (result) => {
12158
+ if (done) return;
12159
+ done = true;
12160
+ unsubscribe?.();
12161
+ options?.signal?.removeEventListener("abort", onAbort);
12162
+ resolve(result);
12163
+ };
12164
+ if (options?.signal?.aborted) {
12165
+ onAbort();
12166
+ return;
12167
+ }
12168
+ options?.signal?.addEventListener("abort", onAbort, { once: true });
12169
+ const stop = deps.subscribeIdentity((state) => {
12170
+ if (!accountInFlight(state)) finish({
12171
+ ok: true,
12172
+ value: state
12173
+ });
12174
+ });
12175
+ if (done) {
12176
+ stop();
12177
+ return;
12178
+ }
12179
+ unsubscribe = stop;
12180
+ const current = deps.snapshotIdentity();
12181
+ if (!accountInFlight(current)) finish({
12182
+ ok: true,
12183
+ value: current
12184
+ });
12185
+ });
12186
+ }
11366
12187
  async function reachClaimedAccount(deps, options) {
11367
12188
  let state = deps.snapshotIdentity();
12189
+ if (accountInFlight(state)) {
12190
+ const settled = await settledAccount(deps, options);
12191
+ if (!settled.ok) return settled;
12192
+ state = settled.value;
12193
+ }
11368
12194
  if (state.phase !== "authenticated" || state.account.at === "unknown") {
11369
12195
  const ensured = await deps.sendIdentity({ _tag: "EnsureAccount" }, options);
11370
12196
  if (!ensured.ok) return ensured;
11371
12197
  state = ensured.value;
11372
12198
  }
11373
12199
  if (state.phase !== "authenticated") return organizationWrongState(state.phase, ["authenticated:claimed"]);
12200
+ if (accountInFlight(state)) {
12201
+ const settled = await settledAccount(deps, options);
12202
+ if (!settled.ok) return settled;
12203
+ state = settled.value;
12204
+ if (state.phase !== "authenticated") return organizationWrongState(state.phase, ["authenticated:claimed"]);
12205
+ }
11374
12206
  if (state.account.at !== "claimed") {
11375
12207
  const event = state.account.at === "failed" ? { _tag: "RetryAccount" } : state.account.at === "counterfactual" ? { _tag: "ClaimAccount" } : { _tag: "EnsureAccount" };
11376
12208
  const claimed = await deps.sendIdentity(event, options);
@@ -11711,8 +12543,33 @@ function toOrgMe(wire) {
11711
12543
  observedAt: wire.observedAt
11712
12544
  };
11713
12545
  }
12546
+ /**
12547
+ * The read runs through the canonical observed runner, so `org.me` owns one
12548
+ * `capxul_sdk_operation` record and one span like every other public method.
12549
+ *
12550
+ * The call-start invocation snapshot travels on the query arguments: the
12551
+ * Convex adapter reads it there and sends the browser linkage with the call,
12552
+ * so an account switch mid-flight cannot re-resolve the actor. A successful
12553
+ * read proves participation in this Organization, so the owning span carries
12554
+ * the verified Organization and the two authority booleans the backend
12555
+ * actually enforces. Labels and Budget caps stay off the record.
12556
+ */
11714
12557
  function makeOrgMeMethod(deps, orgId) {
11715
- return (options) => runIfActive(options?.signal, CAPXUL_OPERATIONS.org.me, () => Effect.map(deps.convexCall.query(orgReadContract.me, { orgId }), toOrgMe));
12558
+ return (options) => {
12559
+ const controls = deps.invocationControls?.(options) ?? options;
12560
+ return runCapxulEffect(Effect.gen(function* () {
12561
+ const wire = yield* deps.convexCall.query(orgReadContract.me, copyInvocationObservation(controls, { orgId })).pipe(Effect.mapError((failure) => failure.publicError));
12562
+ yield* Effect.annotateCurrentSpan({
12563
+ verified_organization_id: orgId,
12564
+ scope_kind: "organization",
12565
+ can_manage_people: wire.capabilities.canManagePeople,
12566
+ can_spend: wire.capabilities.canSpend,
12567
+ budget_count: wire.budgets.length,
12568
+ observed_at: wire.observedAt
12569
+ }).pipe(Effect.catchCause(() => Effect.void));
12570
+ return toOrgMe(wire);
12571
+ }), controls, CAPXUL_OPERATIONS.org.me, deps.runPromise);
12572
+ };
11716
12573
  }
11717
12574
  //#endregion
11718
12575
  //#region src/contract/permission.ts
@@ -11722,7 +12579,34 @@ const permissionContract = { read: makeFunctionReference(CAPXUL_FUNCTIONS["permi
11722
12579
  function isAborted(signal) {
11723
12580
  return signal?.aborted === true;
11724
12581
  }
11725
- async function execute(deps, orgId, command, signal) {
12582
+ /**
12583
+ * One owning operation per public Permission method. The compound command
12584
+ * (prepare, equality checks, sign, submit) keeps owning its own cancellation;
12585
+ * the observed runner around it is what gives the attempt its
12586
+ * `capxul_sdk_operation` record, its span and its failure capture, so a signer
12587
+ * refusal or an unexpected provider error joins to the operation like every
12588
+ * other public surface.
12589
+ */
12590
+ function observed(deps, operation, options, run) {
12591
+ const controls = deps.invocationControls?.(options) ?? options;
12592
+ let verifiedOrganizationId;
12593
+ return runCapxulEffect(Effect.gen(function* () {
12594
+ if (controls?.signal?.aborted === true) return yield* Effect.fail(Errors.cancelled({ operation }));
12595
+ const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
12596
+ const captured = carryInvocationParentSpan(controls, parent);
12597
+ const result = yield* Effect.tryPromise({
12598
+ try: () => run(controls?.signal, captured, (orgId) => {
12599
+ verifiedOrganizationId = orgId;
12600
+ }),
12601
+ catch: (cause) => cause instanceof CapxulError ? cause : Errors.unknown(cause)
12602
+ });
12603
+ return yield* result.ok ? Effect.succeed(result.value) : Effect.fail(result.error);
12604
+ }).pipe(Effect.ensuring(Effect.suspend(() => verifiedOrganizationId === void 0 ? Effect.void : Effect.annotateCurrentSpan({
12605
+ verified_organization_id: verifiedOrganizationId,
12606
+ scope_kind: "organization"
12607
+ })).pipe(Effect.catchCause(() => Effect.void)))), observationOnlyControls(controls), operation, deps.runPromise);
12608
+ }
12609
+ async function execute(deps, orgId, command, signal, controls, verifiedOrganization) {
11726
12610
  if (isAborted(signal)) return {
11727
12611
  ok: false,
11728
12612
  error: Errors.cancelled({ operation: "permissions" })
@@ -11746,17 +12630,18 @@ async function execute(deps, orgId, command, signal) {
11746
12630
  };
11747
12631
  }
11748
12632
  const executionFns = deps.executionFunctions ?? moneyExecutionContract;
11749
- const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => deps.convexCall.action(executionFns.preparePermissionExecution, { input: {
12633
+ const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.preparePermissionExecution, copyInvocationObservation(controls, { input: {
11750
12634
  orgId,
11751
12635
  signerAddress,
11752
12636
  command
11753
- } }));
12637
+ } }))), controls), deps.runPromise);
11754
12638
  if (!prepared.ok) return prepared;
11755
12639
  const expectedSafe = deriveCapxulSafeAddress({ email: session.email });
11756
12640
  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 {
11757
12641
  ok: false,
11758
12642
  error: Errors.invalidInput("permission", "prepared authority mismatch")
11759
12643
  };
12644
+ verifiedOrganization(prepared.value.orgId);
11760
12645
  if (isAborted(signal)) return {
11761
12646
  ok: false,
11762
12647
  error: Errors.cancelled({ operation: "permissions" })
@@ -11774,43 +12659,46 @@ async function execute(deps, orgId, command, signal) {
11774
12659
  ok: false,
11775
12660
  error: Errors.cancelled({ operation: "permissions" })
11776
12661
  };
11777
- return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => deps.convexCall.action(executionFns.submitPermissionExecution, { input: {
12662
+ return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.submitPermissionExecution, copyInvocationObservation(controls, { input: {
11778
12663
  executionId: prepared.value.executionId,
11779
12664
  signature
11780
- } }));
12665
+ } }))), controls), deps.runPromise);
11781
12666
  }
11782
12667
  function makePermissionMethods(deps, orgId) {
11783
12668
  const reads = deps.permissionFunctions ?? permissionContract;
12669
+ const ops = CAPXUL_OPERATIONS.org.permissions;
12670
+ const read = (signal, operation, controls) => runIfActive(signal, operation, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.query(reads.read, copyInvocationObservation(controls, { orgId }))), controls), deps.runPromise);
12671
+ const command = (operation, build, options) => observed(deps, operation, options, (signal, captured, verifiedOrganization) => execute(deps, orgId, build(), signal, captured, verifiedOrganization));
11784
12672
  return {
11785
- create: (input, options) => execute(deps, orgId, {
12673
+ create: (input, options) => command(ops.create, () => ({
11786
12674
  operation: "create",
11787
12675
  ...input
11788
- }, options?.signal),
11789
- change: (input, options) => execute(deps, orgId, {
12676
+ }), options),
12677
+ change: (input, options) => command(ops.change, () => ({
11790
12678
  operation: "change",
11791
12679
  ...input
11792
- }, options?.signal),
11793
- assign: (input, options) => execute(deps, orgId, {
12680
+ }), options),
12681
+ assign: (input, options) => command(ops.assign, () => ({
11794
12682
  operation: "assign",
11795
12683
  ...input
11796
- }, options?.signal),
11797
- revoke: (input, options) => execute(deps, orgId, {
12684
+ }), options),
12685
+ revoke: (input, options) => command(ops.revoke, () => ({
11798
12686
  operation: "revoke",
11799
12687
  ...input
11800
- }, options?.signal),
11801
- replace: (input, options) => execute(deps, orgId, {
12688
+ }), options),
12689
+ replace: (input, options) => command(ops.replace, () => ({
11802
12690
  operation: "replace",
11803
12691
  ...input
11804
- }, options?.signal),
11805
- list: (options) => runIfActive(options?.signal, CAPXUL_OPERATIONS.permissions.list, () => deps.convexCall.query(reads.read, { orgId })),
11806
- get: async (permissionId, options) => {
11807
- const listed = await runIfActive(options?.signal, CAPXUL_OPERATIONS.permissions.get, () => deps.convexCall.query(reads.read, { orgId }));
12692
+ }), options),
12693
+ list: (options) => observed(deps, ops.list, options, (signal, captured) => read(signal, CAPXUL_OPERATIONS.permissions.list, captured)),
12694
+ get: (permissionId, options) => observed(deps, ops.get, options, async (signal, captured) => {
12695
+ const listed = await read(signal, CAPXUL_OPERATIONS.permissions.get, captured);
11808
12696
  if (!listed.ok) return listed;
11809
12697
  return {
11810
12698
  ok: true,
11811
12699
  value: listed.value.permissions.find((row) => row.permissionId === permissionId) ?? null
11812
12700
  };
11813
- }
12701
+ })
11814
12702
  };
11815
12703
  }
11816
12704
  //#endregion
@@ -12372,7 +13260,7 @@ function hermeticOrgView(input) {
12372
13260
  name: input.name,
12373
13261
  handle: input.handle,
12374
13262
  safeAddress: toAddress(placeholder),
12375
- role: "Owner",
13263
+ role: ADMIN_ROLE_LABEL,
12376
13264
  treasury: zeroTreasury(orgId),
12377
13265
  bio: null,
12378
13266
  size: null,
@@ -12618,6 +13506,13 @@ function isRetryableOrganizationSetupFailure(error) {
12618
13506
  * constructed for each `org(orgId)` call. It has no shared mutable active-Org
12619
13507
  * state. The closed-over `orgId` is its only entity reference.
12620
13508
  */
13509
+ /** The observed-runner pair a method bundle needs to own its operation records. */
13510
+ function observedRunnerDeps(deps) {
13511
+ return {
13512
+ ...deps.runPromise === void 0 ? {} : { runPromise: deps.runPromise },
13513
+ ...deps.invocationControls === void 0 ? {} : { invocationControls: deps.invocationControls }
13514
+ };
13515
+ }
12621
13516
  function makeOrgMethods(deps) {
12622
13517
  const orgDeps = {
12623
13518
  ...deps.orgPort === void 0 ? {} : { orgPort: deps.orgPort },
@@ -12668,7 +13563,10 @@ function makeOrgMethods(deps) {
12668
13563
  },
12669
13564
  ...convexCall === void 0 ? {} : { convexCall }
12670
13565
  }),
12671
- me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({ convexCall }, String(orgId)),
13566
+ me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({
13567
+ convexCall,
13568
+ ...observedRunnerDeps(deps)
13569
+ }, String(orgId)),
12672
13570
  async getLifecycle(options) {
12673
13571
  if (options?.signal?.aborted) return {
12674
13572
  ok: false,
@@ -12785,6 +13683,7 @@ function makeOrgMethods(deps) {
12785
13683
  },
12786
13684
  permissions: convexCall === void 0 ? makeNotImplementedPermissionMethods() : makePermissionMethods({
12787
13685
  convexCall,
13686
+ ...observedRunnerDeps(deps),
12788
13687
  ...deps.actor === void 0 ? {} : { actor: deps.actor },
12789
13688
  ...deps.chainId === void 0 ? {} : { chainId: Number(deps.chainId) },
12790
13689
  ...deps.signer === void 0 ? {} : { signer: deps.signer }
@@ -12904,14 +13803,65 @@ function compareDecimal(first, second) {
12904
13803
  }
12905
13804
  //#endregion
12906
13805
  //#region src/surface/holdings.ts
13806
+ /** SPEC Freshness: past this block age the read is labelled stale. Nothing refuses. */
13807
+ const FRESHNESS_LIMIT_SECONDS = 60;
13808
+ /**
13809
+ * The Ponder balance view for one Safe as `CurrentHoldings`.
13810
+ *
13811
+ * The view is the signed sum of every indexed transfer, so the balance IS what
13812
+ * the chain said — there is no snapshot to go stale behind it. `observedAt` is
13813
+ * the indexed block's own timestamp, which is what the last-good label reads,
13814
+ * and `stale` is that block's age. A stopped indexer therefore shows an old
13815
+ * "as of" and the stale flag; it never turns the read into a refusal.
13816
+ */
13817
+ function indexerHoldings(indexer, convexCall, overlay, actor, controls) {
13818
+ return Effect.gen(function* () {
13819
+ const scope = yield* convexCall.query(overlay, copyInvocationObservation(controls, actor?.kind === "organization" ? { input: { actor: {
13820
+ kind: "organization",
13821
+ orgId: actor.organizationId
13822
+ } } } : { input: {} }));
13823
+ if (scope.safe === null) return null;
13824
+ const balances = yield* indexer.read("balance", {
13825
+ safe: toAddress(scope.safe),
13826
+ limit: INDEXER_MAX_LIMIT
13827
+ });
13828
+ const status = yield* indexer.status.pipe(Effect.catch(() => Effect.succeed([])));
13829
+ const rows = [];
13830
+ for (const balance of balances) {
13831
+ const asset = configuredMoneyAssetById(balance.assetId);
13832
+ if (asset === null || balance.amount < 0n) continue;
13833
+ rows.push({
13834
+ assetKind: "erc20",
13835
+ tokenAddress: toAddress(asset.tokenAddress),
13836
+ symbol: asset.symbol,
13837
+ decimals: asset.decimals,
13838
+ rawBalance: toWeiAmount(balance.amount.toString())
13839
+ });
13840
+ }
13841
+ const chainId = balances[0]?.chainId ?? status[0]?.chainId;
13842
+ if (chainId === void 0) return null;
13843
+ const head = status.find((entry) => entry.chainId === chainId);
13844
+ const nowSeconds = Math.floor(Date.now() / 1e3);
13845
+ const headSeconds = head === void 0 ? void 0 : Number(head.blockTimestamp);
13846
+ return {
13847
+ actor: scope.actor,
13848
+ chainId: toChainId(chainId),
13849
+ observedAt: headSeconds === void 0 ? Date.now() : headSeconds * 1e3,
13850
+ stale: headSeconds === void 0 || nowSeconds - headSeconds > FRESHNESS_LIMIT_SECONDS,
13851
+ rows
13852
+ };
13853
+ }).pipe(Effect.catch(() => Effect.succeed(null)));
13854
+ }
12907
13855
  function makeHoldingsMethods(deps) {
12908
13856
  const functions = deps.functions ?? holdingsContract;
13857
+ const overlay = (deps.financialOpsFunctions ?? financialOpsContract).activityOverlay;
12909
13858
  const read = (operation, actor, options, pick) => {
12910
13859
  const controls = deps.invocationControls?.(options) ?? options;
12911
- return runCapxulEffect(retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
13860
+ const convexSnapshot = retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
12912
13861
  kind: "organization",
12913
13862
  orgId: actor.organizationId
12914
- } } : {})), operation, deps.telemetry, controls).pipe(Effect.map(pick), Effect.mapError((error) => error.publicError)), controls, operation, deps.runPromise);
13863
+ } } : {})), operation, deps.telemetry, controls).pipe(Effect.mapError((error) => error.publicError));
13864
+ 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);
12915
13865
  };
12916
13866
  return {
12917
13867
  current: (input, options) => read(CAPXUL_OPERATIONS.holdings.current, input?.actor, options, (snapshot) => snapshot),
@@ -12937,8 +13887,8 @@ const APPLIED_ACTION_BY_EVENT = {
12937
13887
  RestoreSession: "none",
12938
13888
  ResumeOtpEntry: "none",
12939
13889
  Reset: "none",
12940
- SessionRestored: "none",
12941
- SessionAbsent: "none",
13890
+ SessionRestored: "restoration",
13891
+ SessionAbsent: "restoration",
12942
13892
  EnsureAccount: "none",
12943
13893
  ClaimAccount: "none",
12944
13894
  RetryAccount: "none",
@@ -12950,8 +13900,8 @@ const APPLIED_ACTION_BY_EVENT = {
12950
13900
  OtpFailed: "failed",
12951
13901
  Verified: "verified",
12952
13902
  VerifyFailed: "failed",
12953
- SessionRead: "none",
12954
- SessionReadFailed: "none",
13903
+ SessionRead: "restoration",
13904
+ SessionReadFailed: "restoration",
12955
13905
  SignedOut: "signed-out",
12956
13906
  SignOutFailed: "failed",
12957
13907
  AccountAt: "none",
@@ -12977,11 +13927,71 @@ const authFailure = (record, reason, mode) => ({
12977
13927
  ...linkage(record)
12978
13928
  }
12979
13929
  });
13930
+ /**
13931
+ * The label of the restore window every client boots into. A settled session
13932
+ * fact that left this label is the restoration result (#1962 R04); the same
13933
+ * facts from a signed-out or authenticated state are the routine session probe
13934
+ * that `getSession()` runs, and a probe is not a restoration attempt.
13935
+ */
13936
+ const RESTORE_WINDOW = "restoring";
13937
+ /**
13938
+ * Which result the window settled on. `SessionRestored` and `SessionAbsent` are
13939
+ * the cache door and say it themselves. `ReadSession` is also valid inside the
13940
+ * window — `getSession()` drives it on a cache miss and its `SessionRead`
13941
+ * completion settles the window — so that one event reports both halves and the
13942
+ * state it produced is what separates them.
13943
+ */
13944
+ const restorationOutcome = (event, state) => {
13945
+ switch (event) {
13946
+ case "SessionRestored": return "restored";
13947
+ case "SessionAbsent": return "absent";
13948
+ case "SessionReadFailed": return "failed";
13949
+ case "SessionRead":
13950
+ if (state.phase === "authenticated") return "restored";
13951
+ return state.phase === "signed_out" ? "absent" : void 0;
13952
+ default: return;
13953
+ }
13954
+ };
13955
+ /**
13956
+ * The restore window's one result, or nothing when this settled session fact
13957
+ * belongs to a probe rather than to the window.
13958
+ */
13959
+ const restorationObservation = (record, state) => {
13960
+ if (record.slot !== "identity:session" || record.from !== RESTORE_WINDOW) return NONE;
13961
+ const outcome = restorationOutcome(record.event, state);
13962
+ if (outcome === void 0) return NONE;
13963
+ return {
13964
+ name: "access_recovery_completed",
13965
+ props: {
13966
+ recovery: "session_restoration",
13967
+ outcome,
13968
+ ...outcome === "failed" && state.phase === "faulted" ? { reason_code: state.failure.code } : {},
13969
+ ...linkage(record)
13970
+ }
13971
+ };
13972
+ };
13973
+ const REFUSAL_EVENTS = /* @__PURE__ */ new Set([
13974
+ "ClaimAccount",
13975
+ "RetryAccount",
13976
+ "CreateOrganization",
13977
+ "AttachOrganization",
13978
+ "RetryOrganization"
13979
+ ]);
12980
13980
  function mapIdentityProductObservation(record, state) {
12981
13981
  if (record.machine !== "identity" || !IDENTITY_EVENT_TAG_SET.has(record.event)) return NONE;
12982
13982
  if (record.outcome === "cancelled") return NONE;
12983
13983
  if (record.slot === "identity:auth" && record.outcome === "refused") return authFailure(record, record.refusal_code, void 0);
12984
13984
  if (record.slot === "identity:auth" && record.outcome === "failed") return authFailure(record, record.error_code, record.failure?.mode);
13985
+ if (record.outcome === "refused" && REFUSAL_EVENTS.has(record.event)) return {
13986
+ name: "identity_refused",
13987
+ props: {
13988
+ event: record.event,
13989
+ refusal_code: record.refusal_code,
13990
+ slot: record.slot,
13991
+ state: record.state,
13992
+ ...linkage(record)
13993
+ }
13994
+ };
12985
13995
  if (record.outcome !== "applied") return NONE;
12986
13996
  switch (APPLIED_ACTION_BY_EVENT[record.event]) {
12987
13997
  case "none": return NONE;
@@ -13004,6 +14014,7 @@ function mapIdentityProductObservation(record, state) {
13004
14014
  name: "auth_signed_out",
13005
14015
  props: linkage(record)
13006
14016
  };
14017
+ case "restoration": return restorationObservation(record, state);
13007
14018
  }
13008
14019
  }
13009
14020
  const ignoreTransportFailure = (operation, operationName, eventName) => Effect.suspend(operation).pipe(Effect.catchCause(() => Effect.logWarning("identity.product.telemetry.dropped").pipe(Effect.annotateLogs({
@@ -13015,7 +14026,7 @@ function executeIdentityProductObservation(telemetry, record, state) {
13015
14026
  if (mapped === null) return Effect.void;
13016
14027
  copyInvocationObservation(record, mapped);
13017
14028
  const invocationTelemetry = bindProductTelemetryInvocation(telemetry, mapped);
13018
- const after = () => mapped.name === "auth_verified" && state.phase === "authenticated" ? invocationTelemetry.identify({
14029
+ const after = () => state.phase === "authenticated" && (mapped.name === "auth_verified" || mapped.name === "access_recovery_completed") ? invocationTelemetry.identify({
13019
14030
  distinctId: state.session.authUserId,
13020
14031
  traits: { email_domain: emailDomain(state.session.email) }
13021
14032
  }) : mapped.name === "auth_signed_out" ? invocationTelemetry.reset() : Effect.void;
@@ -13060,6 +14071,51 @@ function startSignerReadinessOnClaim(actor, signer) {
13060
14071
  browserSigner.ensureWalletReady?.().catch(() => {});
13061
14072
  });
13062
14073
  }
14074
+ /**
14075
+ * Report the result of each signer readiness cycle (#1962 R04).
14076
+ *
14077
+ * The readiness store already carries the cycle boundary this needs, so no
14078
+ * public signer hook, second identity subscriber, or render counter is added.
14079
+ * `ensureOpenfortWalletReady` writes `recovering` synchronously when it starts
14080
+ * a cycle, and only the cycle that is still the current one writes `ready` or
14081
+ * `unavailable` (`create-openfort-browser-signer.ts:501-521`). Callers that
14082
+ * join a live cycle share that single write, and a `resetSession` writes
14083
+ * `unknown` first, so a cycle that a reset or an account replacement outran
14084
+ * reports nothing.
14085
+ *
14086
+ * The host context is bound when the cycle starts, so a person who signs out
14087
+ * and back in while a cycle runs cannot relabel the result that began under
14088
+ * the previous person. A signer with no readiness cycle — a node or dev key,
14089
+ * and a headless client whose requirement never opens the account lane —
14090
+ * writes no status and reports nothing.
14091
+ */
14092
+ function observeSignerReadinessCycles(signer, telemetry, hostObservationSnapshot, runPromise) {
14093
+ const statusStore = signer?.statusStore;
14094
+ if (statusStore === void 0 || telemetry === void 0) return () => {};
14095
+ let running = false;
14096
+ let startContext;
14097
+ return statusStore.subscribe((status) => {
14098
+ if (status === "recovering") {
14099
+ running = true;
14100
+ startContext = hostObservationSnapshot?.();
14101
+ return;
14102
+ }
14103
+ const cycleContext = startContext;
14104
+ const completesCycle = running;
14105
+ running = false;
14106
+ startContext = void 0;
14107
+ if (!completesCycle || status !== "ready" && status !== "unavailable") return;
14108
+ const event = {
14109
+ name: "access_recovery_completed",
14110
+ props: {
14111
+ recovery: "signer_readiness",
14112
+ outcome: status
14113
+ }
14114
+ };
14115
+ if (cycleContext !== void 0) attachInvocationObservation(event, cycleContext);
14116
+ runPromise(Effect.suspend(() => bindProductTelemetryInvocation(telemetry, event).emit(event)).pipe(Effect.catchCause(() => Effect.void))).catch(() => {});
14117
+ });
14118
+ }
13063
14119
  function assembleCapxulClient(input) {
13064
14120
  const observation = input;
13065
14121
  const authCache = input.authCache ?? detectAuthCacheAdapter();
@@ -13089,12 +14145,14 @@ function assembleCapxulClient(input) {
13089
14145
  }, failureObservation, normalizeCapxulOperation(`identity.${record.event}`), "machine");
13090
14146
  });
13091
14147
  const unsubscribeSignerReadiness = startSignerReadinessOnClaim(actor, input.signer);
14148
+ const unsubscribeReadinessObservation = observeSignerReadinessCycles(input.signer, input.ports.telemetry, observation.hostObservationSnapshot, effectRunner.runPromise);
13092
14149
  let stopPromise = null;
13093
14150
  const stopActor = () => {
13094
14151
  if (stopPromise === null) {
13095
14152
  unsubscribeProductTelemetry();
13096
14153
  unsubscribeFailureObservation();
13097
14154
  unsubscribeSignerReadiness();
14155
+ unsubscribeReadinessObservation();
13098
14156
  stopPromise = effectRunner.runPromise(Scope.close(scope, Exit.void));
13099
14157
  }
13100
14158
  return stopPromise;
@@ -13111,7 +14169,8 @@ function assembleCapxulClient(input) {
13111
14169
  convexCall: input.ports.convexCall,
13112
14170
  ...input.signer === void 0 ? {} : { signer: input.signer },
13113
14171
  telemetry: input.ports.telemetry,
13114
- requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`
14172
+ requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`,
14173
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
13115
14174
  });
13116
14175
  const accountBundle = makeAccountMethods({
13117
14176
  actor,
@@ -13140,10 +14199,13 @@ function assembleCapxulClient(input) {
13140
14199
  ...input.invokeTimeoutMs === void 0 ? {} : { invokeTimeoutMs: input.invokeTimeoutMs },
13141
14200
  afterVerifyOtp: async () => {
13142
14201
  const browserSigner = input.signer;
13143
- if (browserSigner !== void 0 && "resetSession" in browserSigner && typeof browserSigner.resetSession === "function") try {
13144
- browserSigner.resetSession();
13145
- } catch (cause) {
13146
- throw signerFailure(browserSigner.source, "resetSession", cause);
14202
+ if (browserSigner !== void 0 && "resetSession" in browserSigner && typeof browserSigner.resetSession === "function") {
14203
+ try {
14204
+ browserSigner.resetSession();
14205
+ } catch (cause) {
14206
+ throw signerFailure(browserSigner.source, "resetSession", cause);
14207
+ }
14208
+ if (browserSigner.statusStore?.status() === "unknown" && typeof browserSigner.ensureWalletReady === "function") browserSigner.ensureWalletReady().catch(() => {});
13147
14209
  }
13148
14210
  await detectPendingOrgInvitations?.();
13149
14211
  kickProvisioning?.();
@@ -13170,6 +14232,7 @@ function assembleCapxulClient(input) {
13170
14232
  ensureAccount: account._internal.ensureReady,
13171
14233
  getLifecycle: account.getLifecycle,
13172
14234
  snapshotIdentity: actor.snapshot,
14235
+ subscribeIdentity: actor.subscribe,
13173
14236
  sendIdentity: (event, options) => runCapxulEffect(askIdentity(actor, event, options), options, CAPXUL_OPERATIONS._internal.identity.send, effectRunner.runPromise)
13174
14237
  });
13175
14238
  const system = makeSystemMethods({ convexCall: input.ports.convexCall });
@@ -13178,7 +14241,8 @@ function assembleCapxulClient(input) {
13178
14241
  convexCall: input.ports.convexCall,
13179
14242
  telemetry: input.ports.telemetry,
13180
14243
  runPromise: effectRunner.runPromise,
13181
- invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId)
14244
+ invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId),
14245
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
13182
14246
  });
13183
14247
  const accounts = makeAccountsMethods({
13184
14248
  accountReadPort: input.ports.accountRead,
@@ -13303,6 +14367,7 @@ function assembleCapxulClient(input) {
13303
14367
  identity: identityRuntime,
13304
14368
  bootstrap: input.bootstrap,
13305
14369
  accounts: { fund: accounts.fund },
14370
+ ...input.ports.indexerRead === void 0 ? {} : { indexer: input.ports.indexerRead },
13306
14371
  organizationSetup: { getProofReceipt: (orgId) => runPortEffect(input.ports.convexCall.query(organizationSetupProofReceiptQuery, { orgId })) },
13307
14372
  telemetry: input.ports.telemetry,
13308
14373
  captureExternalAddressAcknowledged: (address, organizationId) => {
@@ -13361,8 +14426,10 @@ function withHostObservationControls(snapshot, input, actorId) {
13361
14426
  const previous = readInvocationObservation(input);
13362
14427
  if (previous !== void 0) return attachInvocationObservation({ ...input }, previous);
13363
14428
  const captured = snapshot?.() ?? { active: true };
14429
+ const context = captured.active ? withCapturedAttemptContext(captured.context, input) : captured.context;
13364
14430
  return attachInvocationObservation({ ...input }, {
13365
14431
  ...captured,
14432
+ ...context === void 0 ? {} : { context },
13366
14433
  ...actorId === void 0 ? {} : { actorId }
13367
14434
  });
13368
14435
  }
@@ -13502,6 +14569,352 @@ function snapshotHostObservability(observability) {
13502
14569
  function isPromiseLike(value) {
13503
14570
  return (typeof value === "object" && value !== null || typeof value === "function") && "then" in value;
13504
14571
  }
14572
+ //#endregion
14573
+ //#region src/adapters/ponder/decode.ts
14574
+ /**
14575
+ * Decode the indexer's rows at the trust boundary.
14576
+ *
14577
+ * Ponder returns the raw PostgreSQL result: `integer` arrives as a number,
14578
+ * `numeric(78)` and `text` arrive as strings. Amounts, block numbers and
14579
+ * timestamps are `numeric(78)`, so they become `bigint` here and never pass
14580
+ * through `Number`. Every decoder throws a `CapxulError`; the adapter turns
14581
+ * that into a typed `IndexerReadError`.
14582
+ */
14583
+ const HEX = /^0x[0-9a-fA-F]+$/;
14584
+ function field(row, key) {
14585
+ if (!(key in row)) throw Errors.invalidInput(key, "missing from the indexer row");
14586
+ return row[key];
14587
+ }
14588
+ function text(row, key) {
14589
+ const value = field(row, key);
14590
+ if (typeof value !== "string") throw Errors.invalidInput(key, "must be a string");
14591
+ return value;
14592
+ }
14593
+ function hex(row, key) {
14594
+ const value = text(row, key);
14595
+ if (!HEX.test(value)) throw Errors.invalidInput(key, "must be a hex string");
14596
+ return value.toLowerCase();
14597
+ }
14598
+ function integer(row, key) {
14599
+ const value = field(row, key);
14600
+ const parsed = typeof value === "string" ? Number(value) : value;
14601
+ if (typeof parsed !== "number" || !Number.isSafeInteger(parsed)) throw Errors.invalidInput(key, "must be an integer");
14602
+ return parsed;
14603
+ }
14604
+ /** `numeric(78)` crosses the wire as a decimal string; keep every digit. */
14605
+ function big(row, key) {
14606
+ const value = field(row, key);
14607
+ if (typeof value === "bigint") return value;
14608
+ const raw = typeof value === "number" ? String(value) : value;
14609
+ if (typeof raw !== "string" || !/^-?[0-9]+$/.test(raw)) throw Errors.invalidInput(key, "must be an exact integer amount");
14610
+ return BigInt(raw);
14611
+ }
14612
+ function asRow(value) {
14613
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw Errors.invalidInput("row", "must be an object");
14614
+ return value;
14615
+ }
14616
+ function identity(row) {
14617
+ return {
14618
+ id: text(row, "id"),
14619
+ chainId: toChainId(integer(row, "chain_id")),
14620
+ txHash: toTxHash(hex(row, "tx_hash")),
14621
+ transactionLogOrdinal: integer(row, "transaction_log_ordinal"),
14622
+ blockNumber: big(row, "block_number"),
14623
+ blockHash: hex(row, "block_hash"),
14624
+ timestamp: big(row, "timestamp")
14625
+ };
14626
+ }
14627
+ function decodeTransfer(value) {
14628
+ const row = asRow(value);
14629
+ return {
14630
+ ...identity(row),
14631
+ assetId: toAssetId(text(row, "asset_id")),
14632
+ token: toAddress(hex(row, "token")),
14633
+ from: toAddress(hex(row, "from")),
14634
+ to: toAddress(hex(row, "to")),
14635
+ amount: big(row, "amount")
14636
+ };
14637
+ }
14638
+ function decodePayment(value) {
14639
+ const row = asRow(value);
14640
+ return {
14641
+ ...identity(row),
14642
+ contract: toAddress(hex(row, "contract")),
14643
+ settlementId: hex(row, "settlement_id"),
14644
+ sender: toAddress(hex(row, "sender")),
14645
+ recipient: toAddress(hex(row, "recipient")),
14646
+ assetId: toAssetId(text(row, "asset_id")),
14647
+ token: toAddress(hex(row, "token")),
14648
+ amount: big(row, "amount"),
14649
+ kind: integer(row, "kind"),
14650
+ documentHash: hex(row, "document_hash")
14651
+ };
14652
+ }
14653
+ /** The `balance` view exposes the signed sum under the underlying column name `to`. */
14654
+ function decodeBalance(value) {
14655
+ const row = asRow(value);
14656
+ return {
14657
+ chainId: toChainId(integer(row, "chain_id")),
14658
+ assetId: toAssetId(text(row, "asset_id")),
14659
+ address: toAddress(hex(row, "to")),
14660
+ amount: big(row, "amount")
14661
+ };
14662
+ }
14663
+ const DECODERS = {
14664
+ transfer: decodeTransfer,
14665
+ payment: decodePayment,
14666
+ balance: decodeBalance
14667
+ };
14668
+ /** Decode one `/sql/db` or `/sql/live` payload. Ponder returns `{ rows: [...] }`. */
14669
+ function decodeRows(table, body) {
14670
+ const rows = asRow(body).rows;
14671
+ if (!Array.isArray(rows)) throw Errors.invalidInput("rows", "must be an array");
14672
+ const decode = DECODERS[table];
14673
+ return rows.map(decode);
14674
+ }
14675
+ /** Decode Ponder's native `/status`: `{ [chainName]: { id, block: { number, timestamp } } }`. */
14676
+ function decodeStatus(body) {
14677
+ const chains = asRow(body);
14678
+ return Object.entries(chains).map(([chain, value]) => {
14679
+ const entry = asRow(value);
14680
+ const block = asRow(field(entry, "block"));
14681
+ return {
14682
+ chain,
14683
+ chainId: toChainId(integer(entry, "id")),
14684
+ blockNumber: big(block, "number"),
14685
+ blockTimestamp: big(block, "timestamp")
14686
+ };
14687
+ });
14688
+ }
14689
+ //#endregion
14690
+ //#region src/adapters/ponder/statements.ts
14691
+ /**
14692
+ * The exact statements the indexer accepts.
14693
+ *
14694
+ * `apps/indexer/src/queries.ts` compiles its allowlist with `@ponder/client`
14695
+ * and compares `sql`, `params` and `typings` byte for byte against the caller's
14696
+ * statement. Anything else gets 403. The SDK therefore reproduces the compiled
14697
+ * text here rather than pulling Ponder and Drizzle into a published browser
14698
+ * bundle to regenerate three fixed statements.
14699
+ *
14700
+ * `statements.test.ts` pins the text. When the indexer schema or a query in
14701
+ * that allowlist changes, this file changes with it or every read fails 403.
14702
+ */
14703
+ const HEADS = {
14704
+ 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",
14705
+ 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",
14706
+ balance: "select \"chain_id\", \"asset_id\", \"to\", \"amount\" from \"balance\" where \"balance\".\"to\" = $1"
14707
+ };
14708
+ /** How many times the statement binds the Safe address. */
14709
+ const ACTOR_PARAMS = {
14710
+ transfer: 2,
14711
+ payment: 2,
14712
+ balance: 1
14713
+ };
14714
+ /**
14715
+ * Build the compiled query for one table and page. Throws a `CapxulError` for
14716
+ * a page or address the indexer would refuse, so the caller never spends a
14717
+ * round trip to learn it built the request wrong.
14718
+ */
14719
+ function ponderQuery(table, input) {
14720
+ const safe = assertIndexerPage(input);
14721
+ const offset = input.offset ?? 0;
14722
+ const actors = ACTOR_PARAMS[table];
14723
+ const params = [];
14724
+ for (let i = 0; i < actors; i++) params.push(safe);
14725
+ params.push(input.limit);
14726
+ let sql = `${HEADS[table]} limit $${actors + 1}`;
14727
+ if (offset > 0) {
14728
+ params.push(offset);
14729
+ sql = `${sql} offset $${actors + 2}`;
14730
+ }
14731
+ return {
14732
+ sql,
14733
+ params,
14734
+ typings: params.map(() => "none")
14735
+ };
14736
+ }
14737
+ /**
14738
+ * The `?sql=` value. `@ponder/client` sends `superjson.stringify(query)`; for a
14739
+ * payload of strings and numbers superjson emits no `meta`, so the wire form is
14740
+ * exactly this envelope.
14741
+ */
14742
+ function ponderQueryParam(query) {
14743
+ return JSON.stringify({ json: query });
14744
+ }
14745
+ //#endregion
14746
+ //#region src/adapters/ponder/sse.ts
14747
+ /**
14748
+ * The smallest SSE reader that can carry an `Authorization` header.
14749
+ *
14750
+ * `@ponder/client` opens its live query with `EventSource`, which accepts no
14751
+ * headers, so it cannot reach the guarded indexer at all. The indexer's CORS
14752
+ * policy allows exactly one request header (`Authorization`), so this reader
14753
+ * sends nothing else. Ponder writes one `data:` line per frame.
14754
+ */
14755
+ async function readEventStream(body, onData, stopped) {
14756
+ const reader = body.getReader();
14757
+ const decoder = new TextDecoder();
14758
+ let buffer = "";
14759
+ try {
14760
+ for (;;) {
14761
+ const { done, value } = await reader.read();
14762
+ if (done || stopped()) return;
14763
+ buffer += decoder.decode(value, { stream: true });
14764
+ let split = buffer.indexOf("\n\n");
14765
+ while (split !== -1) {
14766
+ const frame = buffer.slice(0, split);
14767
+ buffer = buffer.slice(split + 2);
14768
+ const data = frameData(frame);
14769
+ if (data !== void 0) onData(data);
14770
+ if (stopped()) return;
14771
+ split = buffer.indexOf("\n\n");
14772
+ }
14773
+ }
14774
+ } finally {
14775
+ await reader.cancel().catch(() => void 0);
14776
+ }
14777
+ }
14778
+ function frameData(frame) {
14779
+ const lines = [];
14780
+ for (const line of frame.split("\n")) {
14781
+ if (!line.startsWith("data:")) continue;
14782
+ lines.push(line.slice(line.startsWith("data: ") ? 6 : 5));
14783
+ }
14784
+ return lines.length === 0 ? void 0 : lines.join("\n");
14785
+ }
14786
+ //#endregion
14787
+ //#region src/adapters/ponder/PonderIndexerAdapter.ts
14788
+ var PonderIndexerAdapter = class {
14789
+ #baseUrl;
14790
+ #token;
14791
+ #fetch;
14792
+ #observation;
14793
+ constructor(deps) {
14794
+ this.#baseUrl = deps.baseUrl.replace(/\/$/, "");
14795
+ this.#token = deps.token;
14796
+ this.#observation = deps.observation;
14797
+ this.#fetch = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
14798
+ }
14799
+ read(table, input) {
14800
+ const operation = `indexer.read.${table}`;
14801
+ return Effect.tryPromise({
14802
+ try: async () => {
14803
+ return decodeRows(table, await (await this.#request("db", table, input, operation)).json());
14804
+ },
14805
+ catch: (cause) => toIndexerReadError(operation, cause)
14806
+ });
14807
+ }
14808
+ subscribe(table, input, callback) {
14809
+ const operation = CAPXUL_OPERATIONS.activity.subscribe;
14810
+ return Effect.sync(() => {
14811
+ let stopped = false;
14812
+ const unsubscribe = () => {
14813
+ stopped = true;
14814
+ };
14815
+ callback({ status: "loading" });
14816
+ this.#stream(table, input, operation, callback, () => stopped).catch(() => void 0);
14817
+ return unsubscribe;
14818
+ });
14819
+ }
14820
+ status = Effect.tryPromise({
14821
+ try: async () => {
14822
+ const response = await this.#fetch(`${this.#baseUrl}/status`, { method: "GET" });
14823
+ if (!response.ok) throw httpError("indexer.status", response.status, await safeText(response));
14824
+ return decodeStatus(await response.json());
14825
+ },
14826
+ catch: (cause) => toIndexerReadError("indexer.status", cause)
14827
+ });
14828
+ /** One authenticated `/sql/:method` call. Throws a `CapxulError` on refusal. */
14829
+ async #request(method, table, input, operation) {
14830
+ const query = ponderQuery(table, input);
14831
+ const token = await this.#token({ forceRefreshToken: false });
14832
+ if (token === null || token === "") throw Errors.notAuthenticated();
14833
+ const url = `${this.#baseUrl}/sql/${method}?sql=${encodeURIComponent(ponderQueryParam(query))}`;
14834
+ const headers = { authorization: `Bearer ${token}` };
14835
+ if (method === "live") headers.accept = "text/event-stream";
14836
+ let response;
14837
+ try {
14838
+ response = await this.#fetch(url, {
14839
+ method: "GET",
14840
+ headers
14841
+ });
14842
+ } catch (cause) {
14843
+ throw Errors.networkError(operation, cause, {
14844
+ provider: "indexer",
14845
+ failure_mode: "upstream-down"
14846
+ });
14847
+ }
14848
+ if (!response.ok) throw httpError(operation, response.status, await safeText(response));
14849
+ return response;
14850
+ }
14851
+ /**
14852
+ * Follow the live stream until the caller unsubscribes or the indexer closes
14853
+ * it. The indexer closes the stream on token expiry or revoked authority
14854
+ * with no frame, so any end the caller did not ask for is a teardown: it
14855
+ * reaches `captureException` under `activity.subscribe` and the caller sees
14856
+ * one `error` snapshot.
14857
+ */
14858
+ async #stream(table, input, operation, callback, stopped) {
14859
+ let failure;
14860
+ try {
14861
+ const response = await this.#request("live", table, input, operation);
14862
+ if (response.body === null) throw Errors.providerError("indexer", operation, "empty stream");
14863
+ await readEventStream(response.body, (data) => {
14864
+ try {
14865
+ callback({
14866
+ status: "ok",
14867
+ value: decodeRows(table, JSON.parse(data))
14868
+ });
14869
+ } catch (cause) {
14870
+ failure = toPublicError$1(operation, cause);
14871
+ }
14872
+ }, () => stopped() || failure !== void 0);
14873
+ } catch (cause) {
14874
+ failure = toPublicError$1(operation, cause);
14875
+ }
14876
+ if (stopped()) return;
14877
+ const error = failure ?? Errors.capabilityUnavailable("indexer", operation);
14878
+ observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error, error.code === "NOT_AUTHENTICATED" ? "operation" : "exception");
14879
+ callback({
14880
+ status: "error",
14881
+ error
14882
+ });
14883
+ }
14884
+ };
14885
+ /**
14886
+ * Map an indexer refusal into the shared error vocabulary.
14887
+ *
14888
+ * 401 is the expected outcome of an expired or revoked session. 403 is not:
14889
+ * the indexer accepts only its own compiled statements for a Safe in the
14890
+ * claim, so a refusal means this SDK and that deployment disagree, or the
14891
+ * session gained a Safe and still holds the older token. Both must be loud.
14892
+ */
14893
+ function httpError(operation, status, body) {
14894
+ if (status === 401) return Errors.notAuthenticated();
14895
+ if (status === 429) return Errors.rateLimited({ resource: "indexer" });
14896
+ if (status === 503) return Errors.capabilityUnavailable("indexer", operation);
14897
+ return Errors.providerError("indexer", operation, body, {
14898
+ httpStatus: status,
14899
+ ...status >= 500 ? { failure_mode: "upstream-down" } : {},
14900
+ details: { reason: status === 403 ? "scope-refused" : "http-status" }
14901
+ });
14902
+ }
14903
+ function toPublicError$1(operation, cause) {
14904
+ if (cause instanceof CapxulError) return cause;
14905
+ return Errors.providerError("indexer", operation, cause);
14906
+ }
14907
+ function toIndexerReadError(operation, cause) {
14908
+ if (cause instanceof IndexerReadError) return cause;
14909
+ return indexerReadErrorFromCapxul(operation, toPublicError$1(operation, cause), cause);
14910
+ }
14911
+ async function safeText(response) {
14912
+ try {
14913
+ return await response.text();
14914
+ } catch {
14915
+ return "";
14916
+ }
14917
+ }
13505
14918
  const SDK_VERSION = version;
13506
14919
  /**
13507
14920
  * Project the built graph into the flat `FlowPorts` record the method bundles
@@ -13751,7 +15164,16 @@ async function createProductionAdapters(input) {
13751
15164
  }));
13752
15165
  const applicationLayer = Layer.merge(portsLayer, optionalEngineeringTelemetryLayer(bootstrapResult.value.engineeringTelemetry, resolvedInput.value.runtime));
13753
15166
  const context = await Effect.runPromise(Layer.buildWithScope(applicationLayer, scope));
13754
- const ports = await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(context)));
15167
+ const collected = await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(context)));
15168
+ const indexerUrl = input.indexerUrl ?? bootstrapResult.value.indexerUrl;
15169
+ const indexerRead = productionIndexerReadPort(indexerUrl === void 0 ? input : {
15170
+ ...input,
15171
+ indexerUrl
15172
+ }, collected, observation);
15173
+ const ports = indexerRead === void 0 ? collected : {
15174
+ ...collected,
15175
+ indexerRead
15176
+ };
13755
15177
  const stopConnectionObservation = observeProductionConvexConnection(ports.convexCall, context);
13756
15178
  const close = idempotentClose(async () => {
13757
15179
  try {
@@ -13878,7 +15300,7 @@ async function createCapxulClientWithSignerControls(input, controls) {
13878
15300
  ...adapters.value.bootstrap,
13879
15301
  authBaseUrl: resolvedAuthBaseUrl
13880
15302
  }, {
13881
- diagnostic: new ConsoleDiagnosticAdapter(),
15303
+ diagnostic: new EffectLogDiagnosticAdapter(adapters.value.context, new ConsoleDiagnosticAdapter()),
13882
15304
  signUserOpHash: controls.signEmbeddedUserOpHash
13883
15305
  });
13884
15306
  signer = withSignerControl(signer, controls.signUserOpHash);
@@ -14083,5 +15505,28 @@ function idempotentClose(close) {
14083
15505
  await close();
14084
15506
  };
14085
15507
  }
15508
+ /**
15509
+ * Build the Ponder read port for this client, or `undefined` when no indexer
15510
+ * origin was supplied. The token provider is the same
15511
+ * `AuthClientPort.getConvexJwt` closure `ConvexCallPort` uses, so the indexer
15512
+ * and Convex see one session with one scope.
15513
+ */
15514
+ function productionIndexerReadPort(input, ports, observation) {
15515
+ if (input.activityReadSource === "convex") return void 0;
15516
+ if (input.indexerUrl === void 0 || input.indexerUrl === "") return void 0;
15517
+ return new PonderIndexerAdapter({
15518
+ baseUrl: normalizeHttpUrl("indexerUrl", input.indexerUrl),
15519
+ token: async ({ forceRefreshToken }) => {
15520
+ const token = await Effect.runPromise(Effect.result(ports.authClient.getConvexJwt({
15521
+ forceRefresh: forceRefreshToken,
15522
+ ...input.signal === void 0 ? {} : { signal: input.signal }
15523
+ })));
15524
+ if (Result.isFailure(token)) return null;
15525
+ return String(token.success.token);
15526
+ },
15527
+ ...input.fetch === void 0 ? {} : { fetch: input.fetch },
15528
+ ...observation === void 0 ? {} : { observation }
15529
+ });
15530
+ }
14086
15531
  //#endregion
14087
15532
  export { signerFailure as A, isRestoring as B, normalizeExceptionErrorKind as C, fromWei as D, safeExceptionLabel as E, CAPXUL_OPERATIONS as F, isCapxulOperation as I, normalizeCapxulOperation as L, PAYMENT_DIRECTIONS as M, PAYMENT_STATUSES as N, isSettingUpLifecycle as O, redactTelemetryEvent as P, destination as R, SDK_VERSION$1 as S, projectSdkException as T, fingerprintPaymentIntent as _, smartAccountErrorFromCapxul as a, failureDetail as b, identityErrorFromCapxul as c, embeddedSigner as d, openfortEmbeddedSigner as f, devPrivateKeySigner as g, deriveDevPrivateKey as h, assembleCapxulClient as i, resolveFailureMode as j, injectedWalletSigner as k, convexCallErrorFromCapxul as l, openfortEmbeddedWalletPort as m, createCapxulClientWithSignerControls as n, accountReadErrorFromCapxul as o, openfortEmbeddedSignerFromWallet as p, postHogObservability as r, wireChainId as s, createCapxulClient as t, bootstrapErrorFromCapxul as u, toWei as v, normalizeExceptionOperation as w, EXCEPTION_MESSAGE as x, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT as y, isClaimed as z };