@capxul/sdk 4.20.0-beta.1 → 4.20.0-beta.3

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,9 +1,9 @@
1
- import { $ as toRoleKey, B as toCurrencyCode, C as ASSET_ID_RE, Ct as isCredentialField, D as WEI_RE, E as SUPPORTED_CURRENCY_CODES, F as toAssetId, G as toHandle, H as toEmail, I as toAuthUserId, J as toOrgId, K as toJwtToken, L as toBudgetId, M as toAddress, N as toAllowedOrigin, O as ZERO_BYTES32, Q as toPublishableKey, R as toChainId, S as APP_ID_RE, St as containsSensitiveMaterial, T as EVM_ADDRESS_RE$1, Tt as redactUrlSecrets, U as toEpochMs, V as toDurationMs, W as toEpochSeconds, X as toPayrollGroupId, Y as toPartyId, Z as toPayrollRunId, _ as normalizeBindingEmail, _t as decodeChainCause, b as configuredMoneyAssetById, bt as isFailureMode, c as AuthCachePortTag, ct as CapxulError, d as authClientPortFromPromiseAdapter, dt as isCapxulError, et as toSessionToken, f as AuthClientError, ft as CHAIN_UPSTREAMS, gt as chainEvidenceLabel, h as orgRoleKeyForLabel, ht as chainCauseProperties, i as BrowserAuthCacheAdapter, it as validateHandle, j as toAccountId, l as SystemClockLayer, lt as EXPECTED_OPERATION_OUTCOMES, m as ADMIN_ROLE_LABEL, mt as boundedResponseHeaders, nt as toTxHash, ot as decodeConvexError, p as AuthClientPortTag, pt as FAILURE_MODES$1, q as toKycTier, r as InMemoryAuthCacheAdapter, rt as toWeiAmount, st as CAPXUL_ERROR_CODES, tt as toTesterKind, u as ClockPortTag, ut as Errors, v as BASE_SEPOLIA_CHAIN_ID, vt as failureFingerprint, w as BYTES32_RE, wt as redactSecrets, x as ACCOUNT_ID_RE, xt as revertSummaryText, y as deriveCapxulSafeAddress, yt as isChainUpstream, z as toCountryCode } from "./OAuthBearerAuthClient-DDD0JlaI.mjs";
1
+ import { $ as toPartyId, A as SUPPORTED_CURRENCY_CODES, B as toAuthUserId, C as ASSET_ID_RE, Ct as isChainUpstream, D as COUNTRY_CODE_RE, Dt as isCredentialField, E as CLIENT_REQUEST_ID_RE, Et as containsSensitiveMaterial, F as toAccountId, G as toDurationMs, H as toChainId, I as toAddress, J as toEpochSeconds, K as toEmail, L as toAllowedOrigin, M as ZERO_BYTES32, O as EMAIL_RE$1, Ot as redactSecrets, Q as toOrgId, S as APP_ID_RE, St as failureFingerprint, T as CLIENT_GRANT_ID_RE, Tt as revertSummaryText, U as toCountryCode, V as toBudgetId, W as toCurrencyCode, X as toJwtToken, Y as toHandle, Z as toKycTier, _ as normalizeBindingEmail, _t as FAILURE_MODES$1, at as toTesterKind, b as configuredMoneyAssetById, bt as chainEvidenceLabel, c as AuthCachePortTag, ct as validateHandle, d as authClientPortFromPromiseAdapter, dt as CAPXUL_ERROR_CODES, et as toPayrollGroupId, f as AuthClientError, ft as CapxulError, gt as CHAIN_UPSTREAMS, h as orgRoleKeyForLabel, ht as isCapxulError, i as BrowserAuthCacheAdapter, it as toSessionToken, j as WEI_RE, k as EVM_ADDRESS_RE$1, kt as redactUrlSecrets, l as SystemClockLayer, lt as HANDLE_RE$1, m as ADMIN_ROLE_LABEL, mt as Errors, nt as toPublishableKey, ot as toTxHash, p as AuthClientPortTag, pt as EXPECTED_OPERATION_OUTCOMES, q as toEpochMs, r as InMemoryAuthCacheAdapter, rt as toRoleKey, st as toWeiAmount, tt as toPayrollRunId, u as ClockPortTag, ut as decodeConvexError, v as BASE_SEPOLIA_CHAIN_ID, vt as boundedResponseHeaders, w as BYTES32_RE, wt as isFailureMode, x as ACCOUNT_ID_RE, xt as decodeChainCause, y as deriveCapxulSafeAddress, yt as chainCauseProperties, z as toAssetId } from "./OAuthBearerAuthClient-DWkofxdZ.mjs";
2
2
  import { formatUnits, keccak256, parseUnits, recoverAddress, stringToHex, toBytes } from "viem";
3
- import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Logger, Option, Queue, Ref, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
3
+ import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Logger, Metric, Option, Queue, Ref, References, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
4
4
  import { getFunctionName, makeFunctionReference } from "convex/server";
5
5
  import { privateKeyToAccount } from "viem/accounts";
6
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientResponse, HttpTraceContext } from "effect/unstable/http";
6
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse, HttpTraceContext } from "effect/unstable/http";
7
7
  import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
8
8
  import { ConvexClient } from "convex/browser";
9
9
  import { AccountTypeEnum, ChainTypeEnum, EmbeddedState, Openfort, RecoveryMethod, ThirdPartyOAuthProvider } from "@openfort/openfort-js";
@@ -1735,6 +1735,92 @@ const ContactActionProps = {
1735
1735
  party_id: OptionalString,
1736
1736
  reason_code: OptionalReasonCode
1737
1737
  };
1738
+ /** One completed online CLI diagnostic. No command input or provider text. */
1739
+ const CLI_COMMAND_NAMES = [
1740
+ "capxul",
1741
+ "doctor",
1742
+ "telemetry",
1743
+ "telemetry.status",
1744
+ "telemetry.enable",
1745
+ "telemetry.disable",
1746
+ "unknown"
1747
+ ];
1748
+ const CLI_CAPABILITIES = [
1749
+ "cli",
1750
+ "diagnostics",
1751
+ "collection"
1752
+ ];
1753
+ const CLI_INVOCATION_KINDS = [
1754
+ "command",
1755
+ "help",
1756
+ "version",
1757
+ "completion",
1758
+ "parse_refusal",
1759
+ "collection_control"
1760
+ ];
1761
+ const CliRelease = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u));
1762
+ const CliInvocationProps = {
1763
+ ...TelemetryEnvelopeProps,
1764
+ schema_version: Schema.Literal(1),
1765
+ command: Schema.Literals(CLI_COMMAND_NAMES),
1766
+ capability: Schema.Literals(CLI_CAPABILITIES),
1767
+ invocation_kind: Schema.Literals(CLI_INVOCATION_KINDS),
1768
+ invocation_id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u)),
1769
+ cli_identity_kind: Schema.optional(Schema.Literal("anonymous")),
1770
+ anonymous_id: Schema.optional(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u))),
1771
+ occurred_at: Schema.Number,
1772
+ cli_version: CliRelease,
1773
+ sdk_version: CliRelease,
1774
+ release: CliRelease,
1775
+ platform: Schema.Literals([
1776
+ "darwin",
1777
+ "linux",
1778
+ "unknown"
1779
+ ]),
1780
+ architecture: Schema.Literals([
1781
+ "arm64",
1782
+ "x64",
1783
+ "unknown"
1784
+ ]),
1785
+ ci: Schema.Boolean,
1786
+ tty: Schema.Boolean,
1787
+ output_mode: Schema.Literals(["human", "json"])
1788
+ };
1789
+ Schema.Struct({
1790
+ name: Schema.Literal("cli_first_used"),
1791
+ props: Schema.Struct(CliInvocationProps)
1792
+ });
1793
+ Schema.Struct({
1794
+ name: Schema.Literal("cli_command_started"),
1795
+ props: Schema.Struct({
1796
+ ...CliInvocationProps,
1797
+ invocation_kind: Schema.Literals([
1798
+ "command",
1799
+ "help",
1800
+ "version"
1801
+ ])
1802
+ })
1803
+ });
1804
+ Schema.Struct({
1805
+ name: Schema.Literal("cli_command_completed"),
1806
+ props: Schema.Struct({
1807
+ ...CliInvocationProps,
1808
+ outcome: Schema.Literals([
1809
+ "succeeded",
1810
+ "refused",
1811
+ "failed",
1812
+ "cancelled"
1813
+ ]),
1814
+ duration_ms: Schema.Number,
1815
+ error_code: Schema.optional(Schema.Literals([
1816
+ ...CAPXUL_ERROR_CODES,
1817
+ "CLI_USAGE",
1818
+ "CLI_CONFIG",
1819
+ "CLI_STORAGE",
1820
+ "CLI_DEFECT"
1821
+ ]))
1822
+ })
1823
+ });
1738
1824
  Schema.Struct({
1739
1825
  name: Schema.Literal("cli_diagnostic_completed"),
1740
1826
  props: Schema.Struct({
@@ -2607,6 +2693,30 @@ const PAYMENT_DOCUMENT_KINDS = [
2607
2693
  "receipt",
2608
2694
  "withdrawal"
2609
2695
  ];
2696
+ /** Native browser projection, distinct from the issuer's persisted request state. */
2697
+ const CLIENT_AUTHORIZATION_PHASE = {
2698
+ OTP_REQUIRED: "otp_required",
2699
+ REVIEW: "review",
2700
+ PROCESSING: "processing",
2701
+ CONNECTED: "connected",
2702
+ REFUSED: "refused"
2703
+ };
2704
+ CLIENT_AUTHORIZATION_PHASE.OTP_REQUIRED, CLIENT_AUTHORIZATION_PHASE.REVIEW, CLIENT_AUTHORIZATION_PHASE.PROCESSING, CLIENT_AUTHORIZATION_PHASE.CONNECTED, CLIENT_AUTHORIZATION_PHASE.REFUSED;
2705
+ /** Closed native refusals; existing Capxul error codes retain their own meaning. */
2706
+ const CLIENT_AUTHORIZATION_REFUSALS = [
2707
+ "signup_required",
2708
+ "setup_required",
2709
+ "already_signed_up",
2710
+ "identity_mismatch",
2711
+ "invalid_request",
2712
+ "request_expired",
2713
+ "request_denied",
2714
+ "cancelled",
2715
+ "grant_revoked",
2716
+ "grant_expired",
2717
+ "refresh_reused",
2718
+ "revoke_unconfirmed"
2719
+ ];
2610
2720
  //#endregion
2611
2721
  //#region ../wire/src/financial-ops.ts
2612
2722
  const MINOR_UNIT_STRING_RE = /^[0-9]+$/;
@@ -4308,6 +4418,251 @@ function isSafeField(field, value) {
4308
4418
  return value.length > 0 && value.length <= rule.maxLength && value === value.trim() && !value.includes("://") && !containsSensitiveMaterial(value) && rule.pattern.test(value);
4309
4419
  }
4310
4420
  //#endregion
4421
+ //#region ../wire/src/client-authorization.ts
4422
+ const NATIVE_CLIENT_ID = "capxul-cli";
4423
+ const NATIVE_CLIENT_SCOPE = "account:read";
4424
+ const NATIVE_CLIENT_LIMITS = {
4425
+ accessMs: 36e5,
4426
+ renewalMs: 2592e6,
4427
+ absoluteMs: 7776e6,
4428
+ requestMs: 6e5,
4429
+ pollSeconds: 5,
4430
+ imageBytes: 5242880
4431
+ };
4432
+ const strict = { parseOptions: { onExcessProperty: "error" } };
4433
+ const text$2 = (max) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(max));
4434
+ const nonNegativeInt = Schema.Number.check(Schema.makeFilter((n) => Number.isSafeInteger(n) && n >= 0));
4435
+ const normalize = (f) => Schema.String.pipe(Schema.decodeTo(Schema.String, {
4436
+ decode: SchemaGetter.transform(f),
4437
+ encode: SchemaGetter.transform((s) => s)
4438
+ }));
4439
+ const ClientRequestIdSchema = Schema.String.pipe(Schema.refine((s) => CLIENT_REQUEST_ID_RE.test(s)));
4440
+ const ClientGrantIdSchema = Schema.String.pipe(Schema.refine((s) => CLIENT_GRANT_ID_RE.test(s)));
4441
+ const authUserId = Schema.String.pipe(Schema.refine((s) => s.length > 0));
4442
+ const keyId = Schema.String.pipe(Schema.refine((s) => s.length > 0));
4443
+ const email = normalize((s) => s.toLowerCase()).pipe(Schema.refine((s) => EMAIL_RE$1.test(s))).check(Schema.isMaxLength(254));
4444
+ const handle = Schema.String.pipe(Schema.refine((s) => HANDLE_RE$1.test(s)));
4445
+ const country = normalize((s) => s.toUpperCase()).pipe(Schema.refine((s) => COUNTRY_CODE_RE.test(s)));
4446
+ const credential = Schema.RedactedFromValue(Schema.String.check(Schema.isMinLength(32), Schema.isMaxLength(8192), Schema.isPattern(/^[A-Za-z0-9._~-]+$/u)), { label: "native authorization credential" });
4447
+ const otp = Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[0-9]{6}$/u)), { label: "OTP" });
4448
+ const scopes = Schema.Tuple([Schema.Literal(NATIVE_CLIENT_SCOPE)]);
4449
+ /** Exact HTTPS origin, no credentials/path/query and no retired cloud deployment. */
4450
+ const NativeHttpsOriginSchema = Schema.String.check(Schema.makeFilter((value) => {
4451
+ try {
4452
+ const url = new URL(value);
4453
+ const host = url.hostname.toLowerCase().replace(/\.$/u, "");
4454
+ return url.protocol === "https:" && value === url.origin && !url.username && !url.password && !url.search && !url.hash && host !== "convex.cloud" && !host.endsWith(".convex.cloud") && host !== "convex.site" && !host.endsWith(".convex.site");
4455
+ } catch {
4456
+ return false;
4457
+ }
4458
+ }, { message: "must be a canonical credential-free HTTPS origin outside retired Convex cloud hosts" }));
4459
+ const NativeClientConfigurationSchema = Schema.Struct({
4460
+ clientId: Schema.Literal(NATIVE_CLIENT_ID),
4461
+ applicationId: AppIdSchema,
4462
+ keyId,
4463
+ environment: Schema.Literals([
4464
+ "development",
4465
+ "staging",
4466
+ "production"
4467
+ ]),
4468
+ keyEnvironment: Schema.Literals(["test", "live"]),
4469
+ issuer: NativeHttpsOriginSchema,
4470
+ verificationOrigin: NativeHttpsOriginSchema
4471
+ }).annotate(strict).check(Schema.makeFilter((c) => c.keyEnvironment === (c.environment === "production" ? "live" : "test"), { message: "key environment must match the configured deployment" })).annotate(strict);
4472
+ const NativeSignupProfileSchema = Schema.Struct({
4473
+ displayName: normalize((s) => s.trim()).check(Schema.isMinLength(1), Schema.isMaxLength(200)),
4474
+ country,
4475
+ handle,
4476
+ payoutAddresses: Schema.optional(Schema.Array(Schema.Struct({
4477
+ chain: Schema.Literals([
4478
+ "evm",
4479
+ "solana",
4480
+ "starknet"
4481
+ ]),
4482
+ address: text$2(256)
4483
+ }).annotate(strict)).check(Schema.isMaxLength(10))),
4484
+ image: Schema.optional(Schema.Struct({
4485
+ mediaType: Schema.Literals([
4486
+ "image/jpeg",
4487
+ "image/png",
4488
+ "image/webp"
4489
+ ]),
4490
+ byteLength: Schema.Number.check(Schema.isInt(), Schema.isBetween({
4491
+ minimum: 1,
4492
+ maximum: NATIVE_CLIENT_LIMITS.imageBytes
4493
+ })),
4494
+ sha256: Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/u))
4495
+ }).annotate(strict))
4496
+ }).annotate(strict);
4497
+ const ClientAuthorizationIntentSchema = Schema.Union([Schema.Struct({ mode: Schema.Literal("login") }).annotate(strict), Schema.Struct({
4498
+ mode: Schema.Literal("signup"),
4499
+ profile: NativeSignupProfileSchema
4500
+ }).annotate(strict)]).annotate(strict);
4501
+ Schema.Struct({
4502
+ clientId: Schema.Literal(NATIVE_CLIENT_ID),
4503
+ email,
4504
+ intent: ClientAuthorizationIntentSchema,
4505
+ waitMs: Schema.Number.check(Schema.isInt(), Schema.isBetween({
4506
+ minimum: 1,
4507
+ maximum: NATIVE_CLIENT_LIMITS.requestMs
4508
+ })),
4509
+ continuityToken: Schema.optional(credential)
4510
+ }).annotate(strict);
4511
+ const request = {
4512
+ requestId: ClientRequestIdSchema,
4513
+ revision: nonNegativeInt,
4514
+ expiresAt: EpochMsSchema
4515
+ };
4516
+ const principal = {
4517
+ authUserId,
4518
+ email
4519
+ };
4520
+ const profileSummary = {
4521
+ displayName: text$2(200),
4522
+ handle,
4523
+ country
4524
+ };
4525
+ Schema.Union([
4526
+ Schema.Struct({
4527
+ ...request,
4528
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.OTP_REQUIRED),
4529
+ email,
4530
+ resendAt: EpochMsSchema
4531
+ }).annotate(strict),
4532
+ Schema.Struct({
4533
+ ...request,
4534
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.REVIEW),
4535
+ ...principal,
4536
+ clientLabel: Schema.Literal("Capxul CLI"),
4537
+ scopes,
4538
+ intent: ClientAuthorizationIntentSchema,
4539
+ setupState: Schema.Literals([
4540
+ "not_required",
4541
+ "new",
4542
+ "resumable"
4543
+ ])
4544
+ }).annotate(strict).check(Schema.makeFilter((v) => v.intent.mode === "login" === (v.setupState === "not_required"), { message: "login review cannot permit account setup" })),
4545
+ Schema.Struct({
4546
+ ...request,
4547
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.PROCESSING),
4548
+ ...principal,
4549
+ purpose: Schema.Literals(["login", "signup"]),
4550
+ step: Schema.Literals([
4551
+ "profile",
4552
+ "wallet",
4553
+ "account",
4554
+ "delivery"
4555
+ ])
4556
+ }).annotate(strict).check(Schema.makeFilter((v) => v.purpose === "signup" || v.step === "delivery", { message: "login may wait for delivery but cannot enter signup processing" })),
4557
+ Schema.Struct({
4558
+ ...request,
4559
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.CONNECTED),
4560
+ ...principal,
4561
+ ...profileSummary,
4562
+ accountId: AccountIdSchema,
4563
+ grantId: ClientGrantIdSchema
4564
+ }).annotate(strict),
4565
+ Schema.Struct({
4566
+ ...request,
4567
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.REFUSED),
4568
+ reason: Schema.Literals(CLIENT_AUTHORIZATION_REFUSALS),
4569
+ code: Schema.Literals(CAPXUL_ERROR_CODES),
4570
+ field: Schema.optional(Schema.Literals([
4571
+ "email",
4572
+ "displayName",
4573
+ "country",
4574
+ "handle",
4575
+ "image",
4576
+ "payoutAddresses"
4577
+ ])),
4578
+ retryable: Schema.Boolean
4579
+ }).annotate(strict)
4580
+ ]).annotate(strict);
4581
+ Schema.Struct({
4582
+ requestId: ClientRequestIdSchema,
4583
+ revision: nonNegativeInt
4584
+ }).annotate(strict);
4585
+ Schema.Struct({
4586
+ requestId: ClientRequestIdSchema,
4587
+ code: otp
4588
+ }).annotate(strict);
4589
+ Schema.Struct({
4590
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
4591
+ grant_type: Schema.Literal("refresh_token"),
4592
+ refresh_token: credential,
4593
+ refreshAttemptId: Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{32,128}$/u)), { label: "refresh attempt" })
4594
+ }).annotate(strict);
4595
+ Schema.Struct({
4596
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
4597
+ applicationId: AppIdSchema,
4598
+ resource: text$2(2048),
4599
+ token_type: Schema.Literal("Bearer"),
4600
+ access_token: credential,
4601
+ refresh_token: credential,
4602
+ scope: Schema.Literal(NATIVE_CLIENT_SCOPE),
4603
+ expires_in: Schema.Number.check(Schema.isInt(), Schema.isBetween({
4604
+ minimum: 1,
4605
+ maximum: NATIVE_CLIENT_LIMITS.accessMs / 1e3
4606
+ })),
4607
+ grantId: ClientGrantIdSchema,
4608
+ ...principal,
4609
+ accountId: AccountIdSchema,
4610
+ issuedAt: EpochMsSchema,
4611
+ grantCreatedAt: EpochMsSchema,
4612
+ accessExpiresAt: EpochMsSchema,
4613
+ renewalExpiresAt: EpochMsSchema,
4614
+ absoluteExpiresAt: EpochMsSchema,
4615
+ revision: nonNegativeInt
4616
+ }).annotate(strict).check(Schema.makeFilter((t) => t.grantCreatedAt <= t.issuedAt && t.absoluteExpiresAt - t.grantCreatedAt === NATIVE_CLIENT_LIMITS.absoluteMs && t.renewalExpiresAt <= Math.min(t.issuedAt + NATIVE_CLIENT_LIMITS.renewalMs, t.absoluteExpiresAt) && t.accessExpiresAt <= Math.min(t.issuedAt + NATIVE_CLIENT_LIMITS.accessMs, t.renewalExpiresAt) && t.expires_in === Math.floor((t.accessExpiresAt - t.issuedAt) / 1e3), { message: "native credentials must obey the approved access, renewal and absolute deadlines" })).annotate(strict);
4617
+ /** One endpoint mapping; consumers cannot substitute callback or resource URLs. */
4618
+ function nativeClientEndpoints(config) {
4619
+ const resource = `${config.issuer}/api/auth/native`;
4620
+ return {
4621
+ resource,
4622
+ device_authorization_endpoint: `${resource}/device/code`,
4623
+ token_endpoint: `${resource}/token`,
4624
+ revocation_endpoint: `${resource}/revoke`,
4625
+ session_endpoint: `${resource}/session`,
4626
+ verification_uri: `${config.verificationOrigin}/connect`
4627
+ };
4628
+ }
4629
+ Schema.Struct({
4630
+ protocol: Schema.Literal("capxul.client-authorization"),
4631
+ version: Schema.Literal(1),
4632
+ configuration: NativeClientConfigurationSchema,
4633
+ scopes_supported: scopes,
4634
+ grant_types_supported: Schema.Tuple([Schema.Literal("urn:ietf:params:oauth:grant-type:device_code"), Schema.Literal("refresh_token")]),
4635
+ resource: text$2(2048),
4636
+ device_authorization_endpoint: text$2(2048),
4637
+ token_endpoint: text$2(2048),
4638
+ revocation_endpoint: text$2(2048),
4639
+ session_endpoint: text$2(2048),
4640
+ verification_uri: text$2(2048)
4641
+ }).annotate(strict).check(Schema.makeFilter((m) => {
4642
+ const expected = nativeClientEndpoints(m.configuration);
4643
+ return m.resource === expected.resource && m.device_authorization_endpoint === expected.device_authorization_endpoint && m.token_endpoint === expected.token_endpoint && m.revocation_endpoint === expected.revocation_endpoint && m.session_endpoint === expected.session_endpoint && m.verification_uri === expected.verification_uri;
4644
+ }, { message: "native endpoints must match their configured issuer and verification origin" })).annotate(strict);
4645
+ Schema.Struct({
4646
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
4647
+ grant_type: Schema.Literal("urn:ietf:params:oauth:grant-type:device_code"),
4648
+ device_code: credential
4649
+ }).annotate(strict);
4650
+ Schema.Struct({
4651
+ requestId: ClientRequestIdSchema,
4652
+ device_code: credential,
4653
+ user_code: Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[A-Z0-9]{4}-[A-Z0-9]{4}$/u)), { label: "device user code" }),
4654
+ continuityToken: credential,
4655
+ verification_uri: text$2(2048),
4656
+ expires_in: Schema.Number.check(Schema.isInt(), Schema.isBetween({
4657
+ minimum: 1,
4658
+ maximum: NATIVE_CLIENT_LIMITS.requestMs / 1e3
4659
+ })),
4660
+ interval: Schema.Number.check(Schema.isInt(), Schema.isBetween({
4661
+ minimum: NATIVE_CLIENT_LIMITS.pollSeconds,
4662
+ maximum: NATIVE_CLIENT_LIMITS.requestMs / 1e3
4663
+ }))
4664
+ }).annotate(strict);
4665
+ //#endregion
4311
4666
  //#region src/internal/invocation-observation.ts
4312
4667
  const INVOCATION_OBSERVATION = Symbol("capxul.invocation-observation");
4313
4668
  const FAILURE_INVOCATION_SNAPSHOT = Symbol("capxul.failure-invocation-snapshot");
@@ -5894,6 +6249,9 @@ const moneyExecutionContract = {
5894
6249
  //#region src/ports/indexer-read.ts
5895
6250
  /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
5896
6251
  const INDEXER_MAX_LIMIT = 1e3;
6252
+ function validTime(value) {
6253
+ return Number.isSafeInteger(value) && value >= 0 && value <= Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
6254
+ }
5897
6255
  const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
5898
6256
  /**
5899
6257
  * Refuse a page the indexer would refuse, before any implementation spends a
@@ -5901,12 +6259,21 @@ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
5901
6259
  * against the hermetic adapter cannot pass on input production rejects. Throws
5902
6260
  * a `CapxulError`; callers convert it to an `IndexerReadError`.
5903
6261
  */
6262
+ function assertEventLookup(input, id) {
6263
+ if (input.limit !== 1 || (input.offset ?? 0) !== 0 || input.timeline !== void 0 || input.settlements !== void 0 || !/^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(id)) throw Errors.invalidInput("eventId", "must identify one logical event without another cursor");
6264
+ }
5904
6265
  function assertIndexerPage(input) {
5905
6266
  const safe = String(input.safe).toLowerCase();
5906
6267
  if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
5907
6268
  if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 1e3) throw Errors.invalidInput("limit", `must be an integer from 1 to ${INDEXER_MAX_LIMIT}`);
5908
6269
  const offset = input.offset ?? 0;
5909
6270
  if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
6271
+ const timeline = input.timeline;
6272
+ if (input.eventId !== void 0) assertEventLookup(input, input.eventId);
6273
+ if (input.settlements !== void 0 && (offset !== 0 || timeline !== void 0 || input.settlements.length < 1 || input.settlements.length > 50 || !input.settlements.every((id) => /^0x[0-9a-f]{64}$/.test(id)))) throw Errors.invalidInput("settlements", "must hold 1 to 50 settlement IDs without another cursor");
6274
+ if (timeline !== void 0) {
6275
+ if (offset !== 0 || !validTime(timeline.from) || !validTime(timeline.to) || timeline.from > timeline.to || timeline.before !== void 0 && (!validTime(timeline.before.timestamp) || !/^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(timeline.before.id))) throw Errors.invalidInput("timeline", "must be a bounded time window with a logical event cursor and no offset");
6276
+ }
5910
6277
  return safe;
5911
6278
  }
5912
6279
  var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
@@ -5936,20 +6303,6 @@ function directionFor(safe, transfer) {
5936
6303
  if (from === safe && to === safe) return "self";
5937
6304
  return to === safe ? "in" : "out";
5938
6305
  }
5939
- /**
5940
- * The Transfer a protocol Payment moved is the same money as its `payment`
5941
- * row. One actor sees one item for it, so the Transfer is claimed and does not
5942
- * become a second row. Another transfer in the same transaction that this
5943
- * payment did not move stays unclaimed and keeps its own row.
5944
- */
5945
- function claimedTransferIds(transfers, payments) {
5946
- const claimed = /* @__PURE__ */ new Set();
5947
- for (const payment of payments) {
5948
- const match = transfers.find((transfer) => !claimed.has(transfer.id) && transfer.txHash === payment.txHash && transfer.token.toLowerCase() === payment.token.toLowerCase() && transfer.amount === payment.amount && transfer.to.toLowerCase() === payment.recipient.toLowerCase());
5949
- if (match !== void 0) claimed.add(match.id);
5950
- }
5951
- return claimed;
5952
- }
5953
6306
  function withAnnotation(item, annotation) {
5954
6307
  return annotation?.counterpartyLabel === void 0 ? item : {
5955
6308
  ...item,
@@ -5960,10 +6313,14 @@ function withAnnotation(item, annotation) {
5960
6313
  * An indexed settlement confirms the intent. A missing row can be outside
5961
6314
  * this page or still backfilling; only the correction owner can prove rollback.
5962
6315
  */
5963
- function confirmPayment(intent, chain) {
5964
- if (chain !== void 0) return {
6316
+ function confirmPayment(intent, chain, expected) {
6317
+ if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
5965
6318
  ...intent,
5966
6319
  status: "settled",
6320
+ ...intent.execution === void 0 ? {} : { execution: {
6321
+ status: "settled",
6322
+ retryRequired: false
6323
+ } },
5967
6324
  receipt: {
5968
6325
  chainId: chain.chainId,
5969
6326
  transactionHash: chain.txHash
@@ -5972,16 +6329,22 @@ function confirmPayment(intent, chain) {
5972
6329
  };
5973
6330
  return intent;
5974
6331
  }
6332
+ function matchesPaymentFact(chain, expected) {
6333
+ return chain !== void 0 && expected !== void 0 && chain.chainId === expected.chainId && chain.contract.toLowerCase() === expected.contract.toLowerCase() && chain.sender.toLowerCase() === expected.sender.toLowerCase() && chain.recipient.toLowerCase() === expected.recipient.toLowerCase() && chain.token.toLowerCase() === expected.token.toLowerCase() && chain.amount.toString() === expected.rawAmount && chain.documentHash.toLowerCase() === expected.documentHash.toLowerCase() && chain.kind === expected.kind;
6334
+ }
5975
6335
  /**
5976
6336
  * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
5977
6337
  * denominate its asset. The indexer serves only configured assets, so a row
5978
6338
  * the registry does not know means the SDK and that deployment disagree; it is
5979
6339
  * dropped rather than shown with a guessed currency.
5980
6340
  */
6341
+ function isActivityVisible(item) {
6342
+ return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
6343
+ }
5981
6344
  function transferItem(safe, transfer, annotation) {
5982
6345
  const asset = configuredMoneyAssetById(transfer.assetId);
5983
6346
  if (asset === null) return null;
5984
- const direction = directionFor(safe, transfer);
6347
+ const direction = directionFor(safe.toLowerCase(), transfer);
5985
6348
  const counterparty = direction === "in" ? transfer.from : transfer.to;
5986
6349
  const at = blockMillis(transfer);
5987
6350
  return withAnnotation({
@@ -6003,47 +6366,6 @@ function transferItem(safe, transfer, annotation) {
6003
6366
  updatedAt: at
6004
6367
  }, annotation);
6005
6368
  }
6006
- /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
6007
- function compareItems(left, right) {
6008
- if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
6009
- if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
6010
- if (left.id === right.id) return 0;
6011
- return left.id < right.id ? 1 : -1;
6012
- }
6013
- function stitchActivity(input) {
6014
- const safe = input.safe.toLowerCase();
6015
- const chainBySettlement = /* @__PURE__ */ new Map();
6016
- for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
6017
- const chainByPaymentId = /* @__PURE__ */ new Map();
6018
- for (const link of input.links) {
6019
- const row = chainBySettlement.get(link.settlementId.toLowerCase());
6020
- if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
6021
- }
6022
- const annotations = /* @__PURE__ */ new Map();
6023
- for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
6024
- const items = [];
6025
- const claiming = [];
6026
- for (const intent of input.intents) {
6027
- if (intent.kind !== "payment") continue;
6028
- const chain = chainByPaymentId.get(intent.id);
6029
- if (chain !== void 0) claiming.push(chain);
6030
- items.push(withAnnotation(confirmPayment(intent, chain), annotations.get(`payment:${intent.id}`)));
6031
- }
6032
- const claimed = claimedTransferIds(input.transfers, claiming);
6033
- for (const transfer of input.transfers) {
6034
- if (claimed.has(transfer.id)) continue;
6035
- const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
6036
- if (item !== null) items.push(item);
6037
- }
6038
- return items.sort(compareItems);
6039
- }
6040
- /** The reference ids one stitched page needs its Convex overlay for. */
6041
- function overlayReferences(items) {
6042
- return items.map((item) => ({
6043
- kind: item.kind,
6044
- id: item.id
6045
- }));
6046
- }
6047
6369
  /**
6048
6370
  * Money in, money out and the pending share, one bucket per currency and
6049
6371
  * precision. Two assets that peg to the same currency at different precision
@@ -6062,7 +6384,7 @@ function summarizeItems(items) {
6062
6384
  };
6063
6385
  const raw = rawUnits(item.amount.value, item.amount.decimals);
6064
6386
  if (item.kind === "payment" && item.status !== "settled") {
6065
- if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed") bucket.pendingIn += raw;
6387
+ if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
6066
6388
  } else if (item.direction === "in") bucket.in += raw;
6067
6389
  else if (item.direction === "out") bucket.out += raw;
6068
6390
  buckets.set(key, bucket);
@@ -6085,179 +6407,412 @@ function rawUnits(value, decimals) {
6085
6407
  }
6086
6408
  //#endregion
6087
6409
  //#region src/surface/activity-read.ts
6088
- /** How many Convex intent rows one page reads for its labels and pending rows. */
6089
- const INTENT_WINDOW = 100;
6090
- /** movement/annotations:overlay bounds each identifier array to 200. */
6410
+ const BATCH = 100;
6091
6411
  const MAX_OVERLAY_IDS = 200;
6092
- /** The window a summary aggregates over. The indexer's own page bound. */
6093
- const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
6094
- function parseCursor(value) {
6095
- if (value === void 0) return 0;
6096
- try {
6097
- const parsed = JSON.parse(value);
6098
- return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
6099
- } catch {
6100
- return 0;
6101
- }
6102
- }
6103
- function inRange(item, range) {
6104
- if (range?.from !== void 0 && item.createdAt < range.from) return false;
6105
- return range?.to === void 0 || item.createdAt <= range.to;
6412
+ function actorKey(actor) {
6413
+ return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
6414
+ }
6415
+ function scopeKey(scope) {
6416
+ return `${scope.principalAccountId ?? "legacy"}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
6417
+ }
6418
+ function selectionKey(params) {
6419
+ const filter = params?.filter;
6420
+ return JSON.stringify([
6421
+ params?.range?.from ?? null,
6422
+ params?.range?.to ?? null,
6423
+ filter?.kind ?? null,
6424
+ filter?.direction ?? null,
6425
+ filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
6426
+ ]);
6427
+ }
6428
+ function validString(value, max) {
6429
+ return typeof value === "string" && value.length <= max;
6430
+ }
6431
+ function validPosition(position) {
6432
+ return position === null || position !== void 0 && typeof position === "object" && Number.isSafeInteger(position.timestamp) && position.timestamp >= 0 && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(position.id);
6433
+ }
6434
+ function parseCursor(value, scope, selection) {
6435
+ if (value === void 0) return null;
6436
+ if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
6437
+ const cursor = JSON.parse(value);
6438
+ if (cursor === null || typeof cursor !== "object" || cursor.version !== 1 || cursor.scope !== scope || cursor.selection !== selection || !Number.isSafeInteger(cursor.checkpoint) || cursor.checkpoint < 0 || cursor.source !== "indexed" && cursor.source !== "fallback" || cursor.paymentCursor !== null && !validString(cursor.paymentCursor, 2048) || typeof cursor.paymentDone !== "boolean" || typeof cursor.transferDone !== "boolean" || typeof cursor.degraded !== "boolean" || !Array.isArray(cursor.paymentPending) || cursor.paymentPending.length > BATCH || new Set(cursor.paymentPending).size !== cursor.paymentPending.length || !cursor.paymentPending.every((id) => validString(id, 256)) || !validPosition(cursor.transferBefore)) throw Errors.invalidInput("cursor", "Activity scope or filters changed; restart from the first page");
6439
+ return cursor;
6440
+ }
6441
+ function requireParams(params) {
6442
+ const limit = params?.limit ?? 25;
6443
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > BATCH) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
6444
+ for (const value of [params?.range?.from, params?.range?.to]) if (value !== void 0 && !Number.isSafeInteger(value)) throw Errors.invalidInput("range", "must contain integer epoch milliseconds");
6445
+ const f = params?.filter;
6446
+ if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
6447
+ "in",
6448
+ "out",
6449
+ "self"
6450
+ ].includes(f.direction) || f?.status !== void 0 && (!Array.isArray(f.status) || !f.status.every((status) => PAYMENT_STATUSES.includes(status)))) throw Errors.invalidInput("filter", "invalid Activity filter");
6106
6451
  }
6107
- /** The caller's filter applied to the merged page. The join owns the order. */
6108
6452
  function matchesFilter(item, filter) {
6109
- if (filter === void 0) return true;
6110
- if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
6111
- if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
6112
- if (filter.status === void 0) return true;
6113
- return item.kind === "payment" && filter.status.includes(item.status);
6453
+ return (filter?.kind === void 0 || item.kind === filter.kind) && (filter?.direction === void 0 || item.direction === filter.direction) && (filter?.status === void 0 || item.kind === "payment" && filter.status.includes(item.status));
6114
6454
  }
6115
6455
  function overlay(deps, actor, controls, input) {
6116
6456
  const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
6117
- const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, index) => index * MAX_OVERLAY_IDS);
6457
+ const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
6118
6458
  return Effect.forEach(offsets, (offset) => deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
6119
6459
  ...actor === void 0 ? {} : { actor },
6120
6460
  ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
6121
6461
  ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
6122
- } })), { concurrency: 2 }).pipe(Effect.map((pages) => ({
6123
- ...pages[0],
6124
- links: pages.flatMap((page) => page.links),
6125
- annotations: pages.flatMap((page) => page.annotations)
6126
- })));
6462
+ } })), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
6463
+ const first = pages[0];
6464
+ if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
6465
+ return Effect.succeed({
6466
+ ...first,
6467
+ links: pages.flatMap((page) => page.links),
6468
+ annotations: pages.flatMap((page) => page.annotations),
6469
+ ...pages.every((page) => page.payments !== void 0) ? { payments: pages.flatMap((page) => page.payments ?? []) } : {}
6470
+ });
6471
+ }));
6127
6472
  }
6128
6473
  function convexPage(deps, actor, controls, input) {
6129
6474
  return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
6130
- ...actor === void 0 ? {} : { actor },
6131
- ...input.cursor === void 0 ? {} : { cursor: input.cursor },
6132
- ...input.limit === void 0 ? {} : { limit: input.limit },
6133
- ...input.range === void 0 ? {} : { range: input.range }
6475
+ ...input,
6476
+ actor
6134
6477
  } }));
6135
6478
  }
6136
- /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
6137
- function chainRows(deps, safe, limit) {
6138
- const page = {
6139
- safe,
6140
- limit: Math.min(limit, INDEXER_MAX_LIMIT)
6141
- };
6142
- return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
6143
- transfers,
6144
- payments
6145
- })), Effect.catch(() => Effect.succeed(null)));
6479
+ function chainRead(deps, table, input) {
6480
+ return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
6481
+ if (error.publicError.details?.reason === "scope-refused" || ![
6482
+ "NETWORK_ERROR",
6483
+ "RATE_LIMITED",
6484
+ "PROVIDER_ERROR",
6485
+ "CAPABILITY_UNAVAILABLE",
6486
+ "TIMEOUT"
6487
+ ].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
6488
+ return Effect.succeed(null);
6489
+ }));
6146
6490
  }
6147
- /** The stitched, filtered rows of one window, newest first. */
6148
- function stitchedWindow(deps, actor, controls, window, params) {
6491
+ function fallbackPage(deps, scope, controls, params, state, degraded) {
6149
6492
  return Effect.gen(function* () {
6150
- const scope = yield* overlay(deps, actor, controls, {});
6151
- const convex = yield* convexPage(deps, actor, controls, {
6152
- limit: INTENT_WINDOW,
6153
- ...params.range === void 0 ? {} : { range: params.range }
6493
+ const page = yield* convexPage(deps, scope.actor, controls, {
6494
+ limit: params?.limit ?? 25,
6495
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
6496
+ range: {
6497
+ ...params?.range?.from === void 0 ? {} : { from: params.range.from },
6498
+ to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
6499
+ },
6500
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
6154
6501
  });
6155
- const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
6156
- if (rows === null || scope.safe === null) {
6157
- if (scope.safe !== null) yield* Effect.annotateCurrentSpan({
6158
- read_source: "convex",
6159
- read_warning: "indexer-unavailable"
6160
- });
6161
- return {
6162
- items: convex.items.filter((item) => matchesFilter(item, params.filter)),
6163
- stitched: false,
6164
- ...scope.safe === null ? {} : { readWarning: "indexer-unavailable" }
6165
- };
6502
+ const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
6503
+ kind: item.kind,
6504
+ id: item.id
6505
+ })) });
6506
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
6507
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
6508
+ yield* Effect.annotateCurrentSpan({
6509
+ read_source: "convex",
6510
+ ...degraded ? { read_warning: "indexer-unavailable" } : {}
6511
+ });
6512
+ return {
6513
+ ...page,
6514
+ items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
6515
+ checkpoint: state.checkpoint,
6516
+ cursor: page.cursor === null ? null : JSON.stringify({
6517
+ ...state,
6518
+ source: "fallback",
6519
+ paymentCursor: page.cursor,
6520
+ degraded
6521
+ }),
6522
+ ...degraded ? { readWarning: "indexer-unavailable" } : {}
6523
+ };
6524
+ });
6525
+ }
6526
+ function boundedRange(range, checkpoint) {
6527
+ return {
6528
+ ...range?.from === void 0 ? {} : { from: range.from },
6529
+ to: Math.min(checkpoint, range?.to ?? checkpoint)
6530
+ };
6531
+ }
6532
+ function unavailablePage(deps, scope, controls, params, state, first) {
6533
+ if (first) return fallbackPage(deps, scope, controls, params, state, true);
6534
+ return Effect.annotateCurrentSpan({
6535
+ read_source: "indexer",
6536
+ read_warning: "indexer-unavailable"
6537
+ }).pipe(Effect.as({
6538
+ items: [],
6539
+ cursor: null,
6540
+ checkpoint: state.checkpoint,
6541
+ observedAt: Date.now(),
6542
+ readWarning: "indexer-unavailable"
6543
+ }));
6544
+ }
6545
+ function readTransfers(deps, safe, range, state, wanted, first) {
6546
+ const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
6547
+ const to = Math.floor(range.to / 1e3);
6548
+ if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
6549
+ return chainRead(deps, "transfer", {
6550
+ safe: toAddress(safe),
6551
+ limit: wanted ? BATCH : 1,
6552
+ timeline: {
6553
+ from,
6554
+ to,
6555
+ ...state.transferBefore === null ? {} : { before: state.transferBefore }
6166
6556
  }
6167
- yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
6168
- const decoration = yield* overlay(deps, actor, controls, {
6169
- references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
6557
+ });
6558
+ }
6559
+ function readPayments(deps, scope, controls, range, state, wanted) {
6560
+ const done = state.paymentDone || !wanted;
6561
+ if (state.paymentPending.length > 0 || done) return Effect.succeed({
6562
+ items: [],
6563
+ pending: state.paymentPending,
6564
+ cursor: state.paymentCursor,
6565
+ done
6566
+ });
6567
+ return convexPage(deps, scope.actor, controls, {
6568
+ limit: BATCH,
6569
+ range,
6570
+ filter: { kind: "payment" },
6571
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
6572
+ }).pipe(Effect.map((page) => ({
6573
+ items: page.items,
6574
+ pending: page.items.map((item) => item.id),
6575
+ cursor: page.cursor,
6576
+ done: page.cursor === null
6577
+ })));
6578
+ }
6579
+ function readPaymentFacts(deps, safe, settlements) {
6580
+ const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
6581
+ return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
6582
+ safe: toAddress(safe),
6583
+ limit: 50,
6584
+ settlements: settlements.slice(offset, offset + 50)
6585
+ }), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
6586
+ }
6587
+ function decoratePage(deps, scope, controls, transfers, page, range) {
6588
+ return Effect.gen(function* () {
6589
+ const decoration = yield* overlay(deps, scope.actor, controls, {
6590
+ references: [...page.pending.map((id) => ({
6591
+ kind: "payment",
6592
+ id
6593
+ })), ...transfers.map((row) => ({
6170
6594
  kind: "movement",
6171
6595
  id: row.id
6172
6596
  }))],
6173
- settlementIds: rows.payments.map((row) => row.settlementId)
6597
+ settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
6174
6598
  });
6175
- const intents = [...convex.items];
6176
- const missing = new Set(decoration.links.map((link) => link.paymentId));
6177
- for (const item of intents) if (item.kind === "payment") missing.delete(item.id);
6178
- let cursor = convex.cursor;
6179
- while (missing.size > 0 && cursor !== null) {
6180
- const page = yield* convexPage(deps, actor, controls, {
6181
- limit: INTENT_WINDOW,
6182
- cursor,
6183
- ...params.range === void 0 ? {} : { range: params.range }
6184
- });
6185
- for (const item of page.items) if (item.kind === "payment" && missing.delete(item.id)) intents.push(item);
6186
- cursor = page.cursor;
6599
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
6600
+ const byId = new Map([...page.items, ...decoration.payments ?? []].map((item) => [item.id, item]));
6601
+ const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
6602
+ const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
6603
+ if (facts === null) return null;
6604
+ const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
6605
+ const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
6606
+ fact: bySettlement.get(link.settlementId),
6607
+ expected: link.expected
6608
+ }]));
6609
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
6610
+ return {
6611
+ payments: page.pending.flatMap((id) => {
6612
+ const item = byId.get(id);
6613
+ if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
6614
+ const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
6615
+ return isActivityVisible(confirmed) ? [confirmed] : [];
6616
+ }),
6617
+ annotations,
6618
+ linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
6619
+ };
6620
+ });
6621
+ }
6622
+ function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
6623
+ const { payments, annotations, linked } = decoration;
6624
+ let pi = 0;
6625
+ let ti = 0;
6626
+ const items = [];
6627
+ const transferEnd = !wantsTransfers || transfers.length < BATCH;
6628
+ let transferBefore = state.transferBefore;
6629
+ while (items.length < limit) {
6630
+ if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
6631
+ const payment = payments[pi];
6632
+ const row = transfers[ti];
6633
+ if (payment === void 0 && row === void 0) break;
6634
+ if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
6635
+ pi += 1;
6636
+ if (matchesFilter(payment, filter)) items.push(payment);
6637
+ } else if (row !== void 0) {
6638
+ ti += 1;
6639
+ transferBefore = {
6640
+ timestamp: Number(row.timestamp),
6641
+ id: row.id
6642
+ };
6643
+ if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
6644
+ const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
6645
+ if (item !== null && matchesFilter(item, filter)) items.push(item);
6187
6646
  }
6188
- const older = intents.slice(convex.items.length);
6189
- const olderDecoration = older.length === 0 ? [] : (yield* overlay(deps, actor, controls, { references: overlayReferences(older) })).annotations;
6647
+ }
6648
+ return {
6649
+ items,
6650
+ next: {
6651
+ ...state,
6652
+ paymentCursor: page.cursor,
6653
+ paymentDone: page.done,
6654
+ paymentPending: payments.slice(pi).map((item) => item.id),
6655
+ transferBefore,
6656
+ transferDone: ti === transfers.length && transferEnd
6657
+ }
6658
+ };
6659
+ }
6660
+ function indexedPage(deps, scope, controls, params, state, first) {
6661
+ return Effect.gen(function* () {
6662
+ const range = boundedRange(params?.range, state.checkpoint);
6663
+ const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
6664
+ const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
6665
+ if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
6666
+ const transfers = wantsTransfers ? found : [];
6667
+ const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
6668
+ const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
6669
+ if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
6670
+ const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
6671
+ const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
6672
+ if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
6673
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
6190
6674
  return {
6191
- items: stitchActivity({
6192
- safe: scope.safe,
6193
- transfers: rows.transfers,
6194
- payments: rows.payments,
6195
- intents,
6196
- links: decoration.links,
6197
- annotations: [...decoration.annotations, ...olderDecoration]
6198
- }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
6199
- stitched: true
6675
+ items,
6676
+ cursor,
6677
+ checkpoint: state.checkpoint,
6678
+ observedAt: Date.now()
6200
6679
  };
6201
6680
  });
6202
6681
  }
6203
6682
  function readActivityPage(deps, actor, controls, params) {
6204
- const limit = params?.limit ?? 25;
6205
- const offset = parseCursor(params?.cursor);
6206
- return stitchedWindow(deps, actor, controls, offset + limit + 1, {
6207
- ...params?.range === void 0 ? {} : { range: params.range },
6208
- ...params?.filter === void 0 ? {} : { filter: params.filter }
6209
- }).pipe(Effect.map(({ items, stitched, readWarning }) => {
6210
- const observedAt = Date.now();
6211
- if (!stitched) return {
6212
- items: items.slice(0, limit),
6683
+ return Effect.gen(function* () {
6684
+ yield* Effect.try({
6685
+ try: () => requireParams(params),
6686
+ catch: (cause) => ({ publicError: cause })
6687
+ });
6688
+ const scope = yield* overlay(deps, actor, controls, {});
6689
+ const selection = selectionKey(params);
6690
+ const cursor = yield* Effect.try({
6691
+ try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
6692
+ catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
6693
+ });
6694
+ const state = cursor ?? {
6695
+ version: 1,
6696
+ scope: scopeKey(scope),
6697
+ selection,
6698
+ checkpoint: Date.now(),
6699
+ source: "indexed",
6700
+ paymentCursor: null,
6701
+ paymentDone: false,
6702
+ paymentPending: [],
6703
+ transferBefore: null,
6704
+ transferDone: false,
6705
+ degraded: false
6706
+ };
6707
+ const range = boundedRange(params?.range, state.checkpoint);
6708
+ if (range.from !== void 0 && range.from > range.to) return {
6709
+ items: [],
6213
6710
  cursor: null,
6214
- checkpoint: observedAt,
6215
- observedAt,
6216
- ...readWarning === void 0 ? {} : { readWarning }
6711
+ checkpoint: state.checkpoint,
6712
+ observedAt: Date.now()
6217
6713
  };
6714
+ if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
6715
+ if (scope.payments === void 0) return yield* unavailablePage(deps, scope, controls, params, state, cursor === null);
6716
+ return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
6717
+ });
6718
+ }
6719
+ function readActivitySummary(deps, actor, controls, params) {
6720
+ return Effect.gen(function* () {
6721
+ const totals = /* @__PURE__ */ new Map();
6722
+ let cursor;
6723
+ let count = 0;
6724
+ let readWarning;
6725
+ do {
6726
+ const page = yield* readActivityPage(deps, actor, controls, {
6727
+ limit: BATCH,
6728
+ ...cursor === void 0 ? {} : { cursor },
6729
+ ...params?.window === void 0 ? {} : { range: params.window }
6730
+ });
6731
+ count += page.items.length;
6732
+ readWarning ??= page.readWarning;
6733
+ for (const total of summarizeItems(page.items)) {
6734
+ const key = `${total.currency}:${total.decimals}`;
6735
+ const old = totals.get(key);
6736
+ totals.set(key, old === void 0 ? total : {
6737
+ ...total,
6738
+ in: String(BigInt(old.in) + BigInt(total.in)),
6739
+ out: String(BigInt(old.out) + BigInt(total.out)),
6740
+ net: String(BigInt(old.net) + BigInt(total.net)),
6741
+ pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
6742
+ });
6743
+ }
6744
+ cursor = page.cursor ?? void 0;
6745
+ } while (cursor !== void 0);
6218
6746
  return {
6219
- items: items.slice(offset, offset + limit),
6220
- cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
6221
- checkpoint: observedAt,
6222
- observedAt
6747
+ window: {
6748
+ from: params?.window?.from ?? null,
6749
+ to: params?.window?.to ?? null
6750
+ },
6751
+ totals: [...totals.values()],
6752
+ count,
6753
+ observedAt: Date.now(),
6754
+ ...readWarning === void 0 ? {} : { readWarning }
6223
6755
  };
6224
- }));
6756
+ });
6225
6757
  }
6226
- function readActivitySummary(deps, actor, controls, params) {
6227
- return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items, readWarning }) => ({
6228
- window: {
6229
- from: params?.window?.from ?? null,
6230
- to: params?.window?.to ?? null
6231
- },
6232
- totals: summarizeItems(items),
6233
- count: items.length,
6234
- observedAt: Date.now(),
6235
- ...readWarning === void 0 ? {} : { readWarning }
6236
- })));
6758
+ /** An indexed Movement has no Convex materialization. Read its exact native row. */
6759
+ function readIndexedMovement(deps, actor, controls, eventId) {
6760
+ return Effect.gen(function* () {
6761
+ const scope = yield* overlay(deps, actor, controls, { references: [{
6762
+ kind: "movement",
6763
+ id: eventId
6764
+ }] });
6765
+ if (scope.safe === null) return null;
6766
+ const safe = scope.safe.toLowerCase();
6767
+ const row = (yield* deps.indexer.read("transfer", {
6768
+ safe: toAddress(safe),
6769
+ limit: 1,
6770
+ eventId
6771
+ }).pipe(Effect.catch((error) => Effect.fail({ publicError: error.publicError }))))[0];
6772
+ if (row === void 0) return null;
6773
+ const blockNumber = Number(row.blockNumber);
6774
+ if (row.id !== eventId || row.from.toLowerCase() !== safe && row.to.toLowerCase() !== safe || !Number.isSafeInteger(blockNumber) || blockNumber < 0 || !Number.isSafeInteger(row.logIndex) || row.logIndex < 0) return yield* Effect.fail({ publicError: Errors.invalidInput("activity", "indexed Movement does not match the requested scope or evidence") });
6775
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
6776
+ return {
6777
+ kind: "movement",
6778
+ movementId: row.id,
6779
+ viewer: scope.actor,
6780
+ annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
6781
+ evidence: {
6782
+ chainId: row.chainId,
6783
+ contractAddress: row.token,
6784
+ tokenAddress: row.token,
6785
+ transactionHash: row.txHash,
6786
+ blockNumber,
6787
+ blockHash: row.blockHash,
6788
+ inclusion: "included",
6789
+ verification: "verified",
6790
+ canonicalState: "finalized",
6791
+ transferLogIndex: row.logIndex,
6792
+ source: row.from,
6793
+ destination: row.to,
6794
+ rawAmount: row.amount.toString(),
6795
+ link: {
6796
+ movementId: row.id,
6797
+ depositPaymentId: null
6798
+ }
6799
+ }
6800
+ };
6801
+ });
6237
6802
  }
6238
- /**
6239
- * Tell the caller when the indexed page for this Safe changed.
6240
- *
6241
- * The transport is the indexer's one SSE connection, wired in the Ponder
6242
- * adapter under `activity.subscribe`. A reactive snapshot is not a product
6243
- * event, so nothing here emits telemetry: the adapter reports a teardown, and
6244
- * the caller re-reads.
6245
- */
6246
6803
  function subscribeActivity(deps, actor, controls, onChange) {
6247
6804
  return Effect.gen(function* () {
6248
6805
  const scope = yield* overlay(deps, actor, controls, {});
6249
6806
  if (scope.safe === null) return () => {};
6250
- const page = {
6251
- safe: scope.safe,
6252
- limit: INTENT_WINDOW
6253
- };
6254
6807
  let delivered = false;
6255
- const notify = () => {
6256
- if (delivered) onChange();
6257
- delivered = true;
6258
- };
6259
- return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
6260
- if (snapshot.status === "ok") notify();
6808
+ return yield* deps.indexer.subscribe("transfer", {
6809
+ safe: toAddress(scope.safe),
6810
+ limit: BATCH
6811
+ }, (snapshot) => {
6812
+ if (snapshot.status === "ok") {
6813
+ if (delivered) onChange();
6814
+ delivered = true;
6815
+ }
6261
6816
  if (snapshot.status === "error") {
6262
6817
  delivered = true;
6263
6818
  onChange();
@@ -6267,7 +6822,7 @@ function subscribeActivity(deps, actor, controls, onChange) {
6267
6822
  }
6268
6823
  //#endregion
6269
6824
  //#region package.json
6270
- var version = "4.20.0-beta.1";
6825
+ var version = "4.20.0-beta.3";
6271
6826
  //#endregion
6272
6827
  //#region src/telemetry/exception-projection.ts
6273
6828
  /** Fixed fallback for failures that have no safe message. */
@@ -7833,7 +8388,10 @@ function makeFinancialOpsMethods(deps) {
7833
8388
  ...params?.limit === void 0 ? {} : { limit: params.limit },
7834
8389
  ...params?.range === void 0 ? {} : { range: params.range },
7835
8390
  ...params?.filter === void 0 ? {} : { filter: params.filter }
7836
- } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
8391
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
8392
+ ...page,
8393
+ items: page.items.filter(isActivityVisible)
8394
+ })), Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
7837
8395
  },
7838
8396
  summary: (params, options) => {
7839
8397
  const controls = deps.invocationControls?.(options) ?? options;
@@ -7854,7 +8412,10 @@ function makeFinancialOpsMethods(deps) {
7854
8412
  actor: scope,
7855
8413
  referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
7856
8414
  });
7857
- }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
8415
+ }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => indexerDeps !== void 0 && reference.kind === "movement" && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(reference.id) ? readIndexedMovement(indexerDeps, actor?.kind === "org" ? {
8416
+ kind: "organization",
8417
+ orgId: actor.orgId
8418
+ } : void 0, controls, reference.id) : deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
7858
8419
  input: {
7859
8420
  kind: reference.kind,
7860
8421
  id: reference.id,
@@ -8375,6 +8936,277 @@ async function recoverRawDigestSigner(input) {
8375
8936
  }
8376
8937
  }
8377
8938
  //#endregion
8939
+ //#region ../observability/src/engineering-delivery.ts
8940
+ /** Fixed admission limits for one CLI invocation and its SDK client. */
8941
+ const CLI_ENGINEERING_LIMITS = Object.freeze({
8942
+ records: 80,
8943
+ ordinaryRecords: 64,
8944
+ recordBytes: 16384,
8945
+ requests: 5,
8946
+ ordinaryRequests: 2,
8947
+ requestBytes: 1572864,
8948
+ metricSeries: 16,
8949
+ requestTimeoutMs: 1e3
8950
+ });
8951
+ const text$1 = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
8952
+ const attributes = (input, limit = 32) => {
8953
+ const out = {};
8954
+ let count = 0;
8955
+ let bytes = 0;
8956
+ try {
8957
+ for (const key in input) {
8958
+ if (count++ >= limit) break;
8959
+ if (key.length > 64) continue;
8960
+ const value = Object.getOwnPropertyDescriptor(input, key)?.value;
8961
+ const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
8962
+ if (scalar === void 0) continue;
8963
+ bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
8964
+ if (bytes > (limit >= 24 ? 2e3 : 300)) break;
8965
+ out[key] = scalar;
8966
+ }
8967
+ } catch {}
8968
+ return out;
8969
+ };
8970
+ const terminalName = (name) => name === "cli.command.completed" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
8971
+ /** Native exporters retain their serialization and scheduling; this owns admission only. */
8972
+ function cliEngineeringDelivery(budget, ownsMetrics) {
8973
+ const closed = Deferred.makeUnsafe();
8974
+ let isClosed = false;
8975
+ let metricsRunning = false;
8976
+ const logs = [];
8977
+ const spans = /* @__PURE__ */ new Map();
8978
+ const terminalBodies = /* @__PURE__ */ new WeakSet();
8979
+ const bodyLeases = /* @__PURE__ */ new WeakMap();
8980
+ const pendingBodies = /* @__PURE__ */ new Set();
8981
+ const holdBody = (body, releases) => {
8982
+ if (releases.length === 0) return;
8983
+ const lease = {
8984
+ started: false,
8985
+ release: () => {
8986
+ if (!pendingBodies.delete(lease)) return;
8987
+ bodyLeases.delete(body);
8988
+ for (const release of releases) release();
8989
+ }
8990
+ };
8991
+ bodyLeases.set(body, lease);
8992
+ pendingBodies.add(lease);
8993
+ };
8994
+ const discardBody = (body) => bodyLeases.get(body)?.release();
8995
+ const drainLogs = () => {
8996
+ for (const release of logs.splice(0)) release();
8997
+ };
8998
+ const close = () => {
8999
+ isClosed = true;
9000
+ Deferred.doneUnsafe(closed, Effect.void);
9001
+ for (const lease of pendingBodies) if (!lease.started) lease.release();
9002
+ drainLogs();
9003
+ for (const release of spans.values()) release();
9004
+ spans.clear();
9005
+ if (ownsMetrics) budget.metricRegistry.clear();
9006
+ };
9007
+ return {
9008
+ close,
9009
+ discardBody,
9010
+ metrics(run) {
9011
+ return Effect.suspend(() => {
9012
+ if (isClosed || metricsRunning) return Effect.void;
9013
+ metricsRunning = true;
9014
+ return run.pipe(Effect.ensuring(Effect.sync(() => {
9015
+ metricsRunning = false;
9016
+ })));
9017
+ });
9018
+ },
9019
+ logger(delegate) {
9020
+ return Logger.make((options) => {
9021
+ if (isClosed) return;
9022
+ let release;
9023
+ try {
9024
+ const message = text$1(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
9025
+ release = budget.admitRecord(terminalName(message));
9026
+ if (release === void 0) return;
9027
+ const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
9028
+ const fiber = Object.create(options.fiber);
9029
+ Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
9030
+ logs.push(release);
9031
+ delegate.log({
9032
+ ...options,
9033
+ message,
9034
+ cause: Cause.empty,
9035
+ fiber
9036
+ });
9037
+ } catch {
9038
+ if (release !== void 0) {
9039
+ const index = logs.indexOf(release);
9040
+ if (index !== -1) logs.splice(index, 1);
9041
+ release();
9042
+ }
9043
+ budget.drop();
9044
+ }
9045
+ });
9046
+ },
9047
+ tracer(delegate) {
9048
+ return Tracer.make({
9049
+ span(options) {
9050
+ const name = text$1(options.name);
9051
+ const release = isClosed ? void 0 : budget.admitRecord(terminalName(name));
9052
+ const spanOptions = {
9053
+ ...options,
9054
+ name,
9055
+ annotations: Context.empty(),
9056
+ links: []
9057
+ };
9058
+ const span = release === void 0 ? new Tracer.NativeSpan({
9059
+ ...spanOptions,
9060
+ sampled: false
9061
+ }) : delegate.span(spanOptions);
9062
+ if (release !== void 0) spans.set(span.spanId, release);
9063
+ const wrapped = Object.create(span);
9064
+ let fields = 0, events = 0, links = 0, attributeBytes = 0;
9065
+ let ended = false;
9066
+ Object.defineProperties(wrapped, {
9067
+ attribute: { value: (key, value) => {
9068
+ if (fields >= 32 && !span.attributes.has(key)) {
9069
+ budget.drop();
9070
+ return;
9071
+ }
9072
+ const safe = attributes({ [key]: value }, 1);
9073
+ if (!Object.hasOwn(safe, key)) return;
9074
+ const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
9075
+ const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
9076
+ if (attributeBytes - previous + next > 2e3) {
9077
+ budget.drop();
9078
+ return;
9079
+ }
9080
+ attributeBytes += next - previous;
9081
+ if (!span.attributes.has(key)) fields += 1;
9082
+ span.attribute(key, safe[key]);
9083
+ } },
9084
+ event: { value: (eventName, time, attrs) => {
9085
+ if (events++ >= 2) {
9086
+ budget.drop();
9087
+ return;
9088
+ }
9089
+ span.event(text$1(eventName), time, attributes(attrs ?? {}, 4));
9090
+ } },
9091
+ addLinks: { value: (items) => {
9092
+ for (const item of items.slice(0, Math.max(0, 2 - links))) {
9093
+ if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
9094
+ budget.drop();
9095
+ continue;
9096
+ }
9097
+ span.addLinks([{
9098
+ span: Tracer.externalSpan({
9099
+ traceId: item.span.traceId,
9100
+ spanId: item.span.spanId,
9101
+ sampled: item.span.sampled
9102
+ }),
9103
+ attributes: attributes(item.attributes, 4)
9104
+ }]);
9105
+ links += 1;
9106
+ }
9107
+ } },
9108
+ end: { value: (time, exit) => {
9109
+ if (ended) return;
9110
+ ended = true;
9111
+ span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
9112
+ } }
9113
+ });
9114
+ wrapped.addLinks(options.links ?? []);
9115
+ return wrapped;
9116
+ },
9117
+ ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
9118
+ });
9119
+ },
9120
+ serialization(delegate) {
9121
+ return {
9122
+ logs(data) {
9123
+ const releases = logs.splice(0);
9124
+ try {
9125
+ const body = delegate.logs(data);
9126
+ if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => l.body?.stringValue === "cli.command.completed")))) terminalBodies.add(body);
9127
+ holdBody(body, releases);
9128
+ return body;
9129
+ } catch (error) {
9130
+ for (const release of releases) release();
9131
+ throw error;
9132
+ }
9133
+ },
9134
+ traces(data) {
9135
+ const releases = [];
9136
+ for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
9137
+ const release = spans.get(span.spanId);
9138
+ if (release !== void 0) releases.push(release);
9139
+ spans.delete(span.spanId);
9140
+ }
9141
+ try {
9142
+ const body = delegate.traces(data);
9143
+ if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) terminalBodies.add(body);
9144
+ holdBody(body, releases);
9145
+ return body;
9146
+ } catch (error) {
9147
+ for (const release of releases) release();
9148
+ throw error;
9149
+ }
9150
+ },
9151
+ metrics(data) {
9152
+ const body = delegate.metrics(data);
9153
+ terminalBodies.add(body);
9154
+ return body;
9155
+ }
9156
+ };
9157
+ },
9158
+ transport(client, enabled = () => true) {
9159
+ const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
9160
+ if (enabled()) return response;
9161
+ budget.drop();
9162
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
9163
+ })).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
9164
+ let received = 0;
9165
+ return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
9166
+ received += chunk.byteLength;
9167
+ if (received > 65536) throw new Error("Engineering response limit exceeded");
9168
+ })).pipe(Effect.catchTag("HttpClientError", (error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error)));
9169
+ }))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
9170
+ times: 1,
9171
+ schedule: Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
9172
+ if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
9173
+ const header = input.reason.response.headers["retry-after"];
9174
+ const numeric = Number(header);
9175
+ const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
9176
+ return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
9177
+ })))
9178
+ }));
9179
+ return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
9180
+ if (isClosed) {
9181
+ discardBody(request.body);
9182
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
9183
+ }
9184
+ const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
9185
+ if (request.body._tag === "Empty") {
9186
+ discardBody(request.body);
9187
+ return Effect.succeed(droppedResponse());
9188
+ }
9189
+ const bytes = request.body.contentLength;
9190
+ const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, terminalBodies.has(request.body));
9191
+ if (release === void 0) {
9192
+ discardBody(request.body);
9193
+ return Effect.succeed(droppedResponse());
9194
+ }
9195
+ const lease = bodyLeases.get(request.body);
9196
+ if (lease !== void 0) lease.started = true;
9197
+ return response.pipe(Effect.asVoid, Effect.timeoutOption(CLI_ENGINEERING_LIMITS.requestTimeoutMs), Effect.raceFirst(Deferred.await(closed).pipe(Effect.andThen(Effect.interrupt))), Effect.tap((result) => Effect.sync(() => {
9198
+ if (Option.isNone(result)) budget.drop();
9199
+ })), Effect.catchCause(() => Effect.sync(() => {
9200
+ budget.drop();
9201
+ })), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
9202
+ release();
9203
+ discardBody(request.body);
9204
+ })));
9205
+ }));
9206
+ }
9207
+ };
9208
+ }
9209
+ //#endregion
8378
9210
  //#region ../observability/src/engineering.ts
8379
9211
  const ENGINEERING_CAPXUL_ENVS = [
8380
9212
  "development",
@@ -8549,10 +9381,13 @@ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
8549
9381
  const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
8550
9382
  "correlation_id",
8551
9383
  "journey_id",
8552
- "invocation_id"
9384
+ "invocation_id",
9385
+ "posthogDistinctId"
8553
9386
  ]);
8554
9387
  const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
8555
9388
  "command",
9389
+ "capability",
9390
+ "invocation_kind",
8556
9391
  "outcome",
8557
9392
  "capxul_env",
8558
9393
  "unit"
@@ -8560,7 +9395,7 @@ const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
8560
9395
  const MINIMAL_METRIC_NAMES = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
8561
9396
  const traceId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{32}$/u.test(value);
8562
9397
  const spanId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{16}$/u.test(value);
8563
- const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value)) ? value : "capxul.engineering";
9398
+ const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value) || CLI_COMMAND_NAMES.some((command) => value === `cli.command.${command}`)) ? value : "capxul.engineering";
8564
9399
  const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
8565
9400
  ["outcome", [
8566
9401
  "succeeded",
@@ -8573,7 +9408,10 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
8573
9408
  ["failure_mode", FAILURE_MODES$1],
8574
9409
  ["capxul_env", ENGINEERING_CAPXUL_ENVS],
8575
9410
  ["producer", ["browser", "server"]],
8576
- ["command", ["doctor"]],
9411
+ ["command", CLI_COMMAND_NAMES],
9412
+ ["capability", CLI_CAPABILITIES],
9413
+ ["invocation_kind", CLI_INVOCATION_KINDS],
9414
+ ["cli_identity_kind", ["anonymous"]],
8577
9415
  ["output_mode", ["human", "json"]],
8578
9416
  ["unit", [
8579
9417
  "1",
@@ -8790,10 +9628,12 @@ function minimalSerialization(config) {
8790
9628
  };
8791
9629
  }
8792
9630
  Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
9631
+ var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
8793
9632
  /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
8794
9633
  const makeEngineeringTelemetryLayer = (config) => {
8795
9634
  const validated = validateEngineeringTelemetryConfig(config);
8796
9635
  return Layer.suspend(() => {
9636
+ const bounded = validated.deliveryBudget === void 0 ? void 0 : cliEngineeringDelivery(validated.deliveryBudget, validated.serviceName === "capxul-cli");
8797
9637
  const endpoints = postHogOtlpEndpoints(validated.host);
8798
9638
  let disabled = false;
8799
9639
  const enabled = () => {
@@ -8807,7 +9647,12 @@ const makeEngineeringTelemetryLayer = (config) => {
8807
9647
  };
8808
9648
  const delivery = {
8809
9649
  ...validated.shutdownTimeout === void 0 ? {} : { shutdownTimeout: validated.shutdownTimeout },
8810
- ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval }
9650
+ ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval },
9651
+ ...bounded === void 0 ? {} : {
9652
+ maxBatchSize: Number.MAX_SAFE_INTEGER,
9653
+ exportInterval: 1e3,
9654
+ shutdownTimeout: validated.serviceName === "capxul-cli" ? 0 : 500
9655
+ }
8811
9656
  };
8812
9657
  const resource = {
8813
9658
  serviceName: validated.serviceName ?? "capxul-sdk",
@@ -8823,22 +9668,38 @@ const makeEngineeringTelemetryLayer = (config) => {
8823
9668
  headers: validated.headers,
8824
9669
  resource,
8825
9670
  ...delivery
8826
- }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
8827
- const logging = Logger.layer([OtlpLogger.make({
9671
+ }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(bounded?.tracer(tracer) ?? tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
9672
+ const ownedLogging = Layer.effect(EngineeringLog, OtlpLogger.make({
8828
9673
  url: endpoints.logs,
8829
9674
  headers: validated.headers,
8830
9675
  resource,
8831
- ...delivery
8832
- }).pipe(Effect.map((logger) => Logger.make((options) => {
8833
- if (enabled()) logger.log(options);
8834
- })))], { mergeWithExisting: true }).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
8835
- const metrics = OtlpMetrics.layer({
9676
+ ...delivery,
9677
+ ...bounded === void 0 ? {} : { excludeLogSpans: true }
9678
+ }).pipe(Effect.map((nativeLogger) => {
9679
+ const logger = bounded?.logger(nativeLogger) ?? nativeLogger;
9680
+ return Logger.make((options) => {
9681
+ if (enabled()) logger.log(options);
9682
+ });
9683
+ }))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
9684
+ const logging = Logger.layer([EngineeringLog], { mergeWithExisting: true }).pipe(Layer.provideMerge(ownedLogging));
9685
+ const metricConfig = {
8836
9686
  url: endpoints.metrics,
8837
9687
  headers: validated.headers,
8838
9688
  resource,
8839
9689
  temporality: "delta",
8840
9690
  ...delivery
8841
- });
9691
+ };
9692
+ const metrics = bounded === void 0 ? OtlpMetrics.layer(metricConfig) : validated.serviceName !== "capxul-cli" ? Layer.empty : Layer.effectDiscard(Effect.gen(function* () {
9693
+ const flusher = yield* OtlpExporter.Flusher;
9694
+ yield* OtlpMetrics.make({
9695
+ ...metricConfig,
9696
+ exportInterval: Number.POSITIVE_INFINITY,
9697
+ shutdownTimeout: 0
9698
+ }).pipe(Effect.provideService(OtlpExporter.Flusher, {
9699
+ ...flusher,
9700
+ register: (run) => flusher.register(bounded.metrics(run))
9701
+ }));
9702
+ })).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
8842
9703
  const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
8843
9704
  const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
8844
9705
  if (validated.producer !== "browser" || !hasBrowserDocument()) return;
@@ -8861,17 +9722,25 @@ const makeEngineeringTelemetryLayer = (config) => {
8861
9722
  }));
8862
9723
  }));
8863
9724
  const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => {
8864
- const selectedTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
8865
- return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => enabled() && request.body._tag !== "Empty" ? response : Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })))));
9725
+ const originalTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
9726
+ const selectedTransport = bounded?.transport(originalTransport, enabled) ?? originalTransport;
9727
+ return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => {
9728
+ if (enabled() && request.body._tag !== "Empty") return response;
9729
+ bounded?.discardBody(request.body);
9730
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })));
9731
+ }));
8866
9732
  })).pipe(Layer.provide(FetchHttpClient.layer));
8867
9733
  const serialization = Layer.effect(OtlpSerialization.OtlpSerialization, Effect.map(OtlpSerialization.OtlpSerialization, (json) => {
8868
9734
  const projected = minimalSerialization(validated);
8869
- return validated.privacy === "minimal" ? projected : {
9735
+ const selected = validated.privacy === "minimal" ? projected : {
8870
9736
  ...json,
8871
9737
  metrics: projected.metrics
8872
9738
  };
9739
+ return bounded?.serialization(selected) ?? selected;
8873
9740
  })).pipe(Layer.provide(OtlpSerialization.layerJson));
8874
- return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
9741
+ const combined = browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
9742
+ const scoped = bounded === void 0 ? combined : combined.pipe(Layer.provideMerge(Layer.effectDiscard(Effect.addFinalizer(() => Effect.sync(bounded.close)))));
9743
+ return validated.deliveryBudget === void 0 ? scoped : scoped.pipe(Layer.provideMerge(Layer.succeed(Metric.MetricRegistry, validated.serviceName === "capxul-cli" ? validated.deliveryBudget.metricRegistry : validated.deliveryBudget.emptyMetricRegistry())));
8875
9744
  });
8876
9745
  };
8877
9746
  //#endregion
@@ -15155,6 +16024,7 @@ function postHogObservability(client, options = {}) {
15155
16024
  product: postHogProductTelemetry(policy)
15156
16025
  };
15157
16026
  Object.defineProperty(module, HOST_ENGINEERING_POLICY, { value: Object.freeze({
16027
+ ...options.engineeringDeliveryBudget === void 0 ? {} : { engineeringDeliveryBudget: options.engineeringDeliveryBudget },
15158
16028
  ...options.engineeringEnabled === void 0 ? {} : { engineeringEnabled: options.engineeringEnabled },
15159
16029
  ...options.engineeringPrivacy === void 0 ? {} : { engineeringPrivacy: options.engineeringPrivacy }
15160
16030
  }) });
@@ -15192,6 +16062,7 @@ function isPromiseLike(value) {
15192
16062
  function hostEngineeringPolicy(observability) {
15193
16063
  const policy = observability?.[HOST_ENGINEERING_POLICY];
15194
16064
  return {
16065
+ ...policy?.engineeringDeliveryBudget === void 0 ? {} : { deliveryBudget: policy.engineeringDeliveryBudget },
15195
16066
  ...policy?.engineeringEnabled === void 0 ? {} : { enabled: policy.engineeringEnabled },
15196
16067
  ...policy?.engineeringPrivacy === void 0 ? {} : { privacy: policy.engineeringPrivacy }
15197
16068
  };
@@ -15260,7 +16131,8 @@ function decodeTransfer(value) {
15260
16131
  token: toAddress(hex(row, "token")),
15261
16132
  from: toAddress(hex(row, "from")),
15262
16133
  to: toAddress(hex(row, "to")),
15263
- amount: big(row, "amount")
16134
+ amount: big(row, "amount"),
16135
+ ..."payment_settlement_id" in row ? { paymentSettlementId: row.payment_settlement_id === null ? null : hex(row, "payment_settlement_id") } : {}
15264
16136
  };
15265
16137
  }
15266
16138
  function decodePayment(value) {
@@ -15346,6 +16218,60 @@ const ACTOR_PARAMS = {
15346
16218
  */
15347
16219
  function ponderQuery(table, input) {
15348
16220
  const safe = assertIndexerPage(input);
16221
+ if (input.eventId !== void 0) {
16222
+ if (table !== "transfer") throw Errors.invalidInput("eventId", "only transfer reads accept a logical event ID");
16223
+ return {
16224
+ sql: `${HEADS.transfer.slice(0, HEADS.transfer.indexOf(" from \"transfer\""))} from "transfer" where (("transfer"."from" = $1 or "transfer"."to" = $2) and "transfer"."id" = $3) limit $4`,
16225
+ params: [
16226
+ safe,
16227
+ safe,
16228
+ input.eventId,
16229
+ 1
16230
+ ],
16231
+ typings: [
16232
+ "none",
16233
+ "none",
16234
+ "none",
16235
+ "none"
16236
+ ]
16237
+ };
16238
+ }
16239
+ if (input.settlements !== void 0) {
16240
+ if (table !== "payment") throw Errors.invalidInput("settlements", "only Payment reads accept settlement IDs");
16241
+ const params = [
16242
+ safe,
16243
+ safe,
16244
+ ...input.settlements,
16245
+ input.limit
16246
+ ];
16247
+ return {
16248
+ sql: `${HEADS.payment.slice(0, HEADS.payment.indexOf("where "))}where (("payment"."sender" = $1 or "payment"."recipient" = $2) and "payment"."settlement_id" in (${input.settlements.map((_, i) => `$${i + 3}`).join(", ")})) limit $${params.length}`,
16249
+ params,
16250
+ typings: params.map(() => "none")
16251
+ };
16252
+ }
16253
+ if (input.timeline !== void 0) {
16254
+ if (table !== "transfer") throw Errors.invalidInput("timeline", "only transfer reads have a timeline");
16255
+ const { from, to, before } = input.timeline;
16256
+ const params = [
16257
+ safe,
16258
+ safe,
16259
+ from,
16260
+ to
16261
+ ];
16262
+ const select = HEADS.transfer.slice(0, HEADS.transfer.indexOf(" from \"transfer\"")) + ", \"payment_settlement_id\" from \"activity_transfer\" ";
16263
+ let where = "(\"activity_transfer\".\"from\" = $1 or \"activity_transfer\".\"to\" = $2) and \"activity_transfer\".\"timestamp\" >= $3 and \"activity_transfer\".\"timestamp\" <= $4";
16264
+ if (before !== void 0) {
16265
+ params.push(before.timestamp, before.timestamp, before.id);
16266
+ where += " and (\"activity_transfer\".\"timestamp\" < $5 or (\"activity_transfer\".\"timestamp\" = $6 and \"activity_transfer\".\"id\" < $7))";
16267
+ }
16268
+ params.push(input.limit);
16269
+ return {
16270
+ sql: `${select}where (${where}) order by "activity_transfer"."timestamp" desc, "activity_transfer"."id" desc limit $${params.length}`,
16271
+ params,
16272
+ typings: params.map(() => "none")
16273
+ };
16274
+ }
15349
16275
  const offset = input.offset ?? 0;
15350
16276
  const actors = ACTOR_PARAMS[table];
15351
16277
  const params = [];