@capxul/mcp 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.
Files changed (2) hide show
  1. package/dist/main.mjs +1147 -215
  2. package/package.json +3 -3
package/dist/main.mjs CHANGED
@@ -8,8 +8,8 @@ import { createHash } from "node:crypto";
8
8
  import { createServer } from "node:http";
9
9
  import { localhostHostValidation, localhostOriginValidation, toNodeHandler } from "@modelcontextprotocol/node";
10
10
  import { z } from "zod";
11
- 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";
12
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientResponse, HttpTraceContext } from "effect/unstable/http";
11
+ 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";
12
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse, HttpTraceContext } from "effect/unstable/http";
13
13
  import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
14
14
  import { resolve } from "path";
15
15
  import util from "util";
@@ -99,7 +99,12 @@ const CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
99
99
  "authcode",
100
100
  "authorizationcode",
101
101
  "oauthcode",
102
- "codeverifier"
102
+ "codeverifier",
103
+ "devicecode",
104
+ "usercode",
105
+ "continuitytoken",
106
+ "refreshattemptid",
107
+ "revocationtoken"
103
108
  ]);
104
109
  /** Match credential fields by meaning, without hiding token addresses or replay IDs. */
105
110
  function isCredentialField(name, context) {
@@ -858,6 +863,8 @@ const COUNTRY_CODE_RE = /^[A-Z]{2}$/;
858
863
  const ACCOUNT_ID_RE = /^account_[0-9A-Za-z]+$/;
859
864
  const PARTY_ID_RE$1 = /^party_[0-9A-Za-z]+$/;
860
865
  const APP_ID_RE = /^app_[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
866
+ const CLIENT_REQUEST_ID_RE = /^creq_[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
867
+ const CLIENT_GRANT_ID_RE = /^cgrant_[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
861
868
  const TX_HASH_RE = /^0x[0-9a-f]{64}$/i;
862
869
  const WEI_RE = /^[0-9]+$/;
863
870
  Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
@@ -8180,6 +8187,92 @@ const ContactActionProps = {
8180
8187
  party_id: OptionalString,
8181
8188
  reason_code: OptionalReasonCode
8182
8189
  };
8190
+ /** One completed online CLI diagnostic. No command input or provider text. */
8191
+ const CLI_COMMAND_NAMES = [
8192
+ "capxul",
8193
+ "doctor",
8194
+ "telemetry",
8195
+ "telemetry.status",
8196
+ "telemetry.enable",
8197
+ "telemetry.disable",
8198
+ "unknown"
8199
+ ];
8200
+ const CLI_CAPABILITIES = [
8201
+ "cli",
8202
+ "diagnostics",
8203
+ "collection"
8204
+ ];
8205
+ const CLI_INVOCATION_KINDS = [
8206
+ "command",
8207
+ "help",
8208
+ "version",
8209
+ "completion",
8210
+ "parse_refusal",
8211
+ "collection_control"
8212
+ ];
8213
+ const CliRelease = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u));
8214
+ const CliInvocationProps = {
8215
+ ...TelemetryEnvelopeProps,
8216
+ schema_version: Schema.Literal(1),
8217
+ command: Schema.Literals(CLI_COMMAND_NAMES),
8218
+ capability: Schema.Literals(CLI_CAPABILITIES),
8219
+ invocation_kind: Schema.Literals(CLI_INVOCATION_KINDS),
8220
+ invocation_id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u)),
8221
+ cli_identity_kind: Schema.optional(Schema.Literal("anonymous")),
8222
+ anonymous_id: Schema.optional(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u))),
8223
+ occurred_at: Schema.Number,
8224
+ cli_version: CliRelease,
8225
+ sdk_version: CliRelease,
8226
+ release: CliRelease,
8227
+ platform: Schema.Literals([
8228
+ "darwin",
8229
+ "linux",
8230
+ "unknown"
8231
+ ]),
8232
+ architecture: Schema.Literals([
8233
+ "arm64",
8234
+ "x64",
8235
+ "unknown"
8236
+ ]),
8237
+ ci: Schema.Boolean,
8238
+ tty: Schema.Boolean,
8239
+ output_mode: Schema.Literals(["human", "json"])
8240
+ };
8241
+ Schema.Struct({
8242
+ name: Schema.Literal("cli_first_used"),
8243
+ props: Schema.Struct(CliInvocationProps)
8244
+ });
8245
+ Schema.Struct({
8246
+ name: Schema.Literal("cli_command_started"),
8247
+ props: Schema.Struct({
8248
+ ...CliInvocationProps,
8249
+ invocation_kind: Schema.Literals([
8250
+ "command",
8251
+ "help",
8252
+ "version"
8253
+ ])
8254
+ })
8255
+ });
8256
+ Schema.Struct({
8257
+ name: Schema.Literal("cli_command_completed"),
8258
+ props: Schema.Struct({
8259
+ ...CliInvocationProps,
8260
+ outcome: Schema.Literals([
8261
+ "succeeded",
8262
+ "refused",
8263
+ "failed",
8264
+ "cancelled"
8265
+ ]),
8266
+ duration_ms: Schema.Number,
8267
+ error_code: Schema.optional(Schema.Literals([
8268
+ ...CAPXUL_ERROR_CODES,
8269
+ "CLI_USAGE",
8270
+ "CLI_CONFIG",
8271
+ "CLI_STORAGE",
8272
+ "CLI_DEFECT"
8273
+ ]))
8274
+ })
8275
+ });
8183
8276
  Schema.Struct({
8184
8277
  name: Schema.Literal("cli_diagnostic_completed"),
8185
8278
  props: Schema.Struct({
@@ -9045,6 +9138,30 @@ const PAYMENT_DOCUMENT_KINDS = [
9045
9138
  "receipt",
9046
9139
  "withdrawal"
9047
9140
  ];
9141
+ /** Native browser projection, distinct from the issuer's persisted request state. */
9142
+ const CLIENT_AUTHORIZATION_PHASE = {
9143
+ OTP_REQUIRED: "otp_required",
9144
+ REVIEW: "review",
9145
+ PROCESSING: "processing",
9146
+ CONNECTED: "connected",
9147
+ REFUSED: "refused"
9148
+ };
9149
+ CLIENT_AUTHORIZATION_PHASE.OTP_REQUIRED, CLIENT_AUTHORIZATION_PHASE.REVIEW, CLIENT_AUTHORIZATION_PHASE.PROCESSING, CLIENT_AUTHORIZATION_PHASE.CONNECTED, CLIENT_AUTHORIZATION_PHASE.REFUSED;
9150
+ /** Closed native refusals; existing Capxul error codes retain their own meaning. */
9151
+ const CLIENT_AUTHORIZATION_REFUSALS = [
9152
+ "signup_required",
9153
+ "setup_required",
9154
+ "already_signed_up",
9155
+ "identity_mismatch",
9156
+ "invalid_request",
9157
+ "request_expired",
9158
+ "request_denied",
9159
+ "cancelled",
9160
+ "grant_revoked",
9161
+ "grant_expired",
9162
+ "refresh_reused",
9163
+ "revoke_unconfirmed"
9164
+ ];
9048
9165
  //#endregion
9049
9166
  //#region ../../packages/wire/src/financial-ops.ts
9050
9167
  const MINOR_UNIT_STRING_RE = /^[0-9]+$/;
@@ -10746,6 +10863,251 @@ function isSafeField(field, value) {
10746
10863
  return value.length > 0 && value.length <= rule.maxLength && value === value.trim() && !value.includes("://") && !containsSensitiveMaterial(value) && rule.pattern.test(value);
10747
10864
  }
10748
10865
  //#endregion
10866
+ //#region ../../packages/wire/src/client-authorization.ts
10867
+ const NATIVE_CLIENT_ID = "capxul-cli";
10868
+ const NATIVE_CLIENT_SCOPE = "account:read";
10869
+ const NATIVE_CLIENT_LIMITS = {
10870
+ accessMs: 36e5,
10871
+ renewalMs: 2592e6,
10872
+ absoluteMs: 7776e6,
10873
+ requestMs: 6e5,
10874
+ pollSeconds: 5,
10875
+ imageBytes: 5242880
10876
+ };
10877
+ const strict = { parseOptions: { onExcessProperty: "error" } };
10878
+ const text$2 = (max) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(max));
10879
+ const nonNegativeInt = Schema.Number.check(Schema.makeFilter((n) => Number.isSafeInteger(n) && n >= 0));
10880
+ const normalize = (f) => Schema.String.pipe(Schema.decodeTo(Schema.String, {
10881
+ decode: SchemaGetter.transform(f),
10882
+ encode: SchemaGetter.transform((s) => s)
10883
+ }));
10884
+ const ClientRequestIdSchema = Schema.String.pipe(Schema.refine((s) => CLIENT_REQUEST_ID_RE.test(s)));
10885
+ const ClientGrantIdSchema = Schema.String.pipe(Schema.refine((s) => CLIENT_GRANT_ID_RE.test(s)));
10886
+ const authUserId = Schema.String.pipe(Schema.refine((s) => s.length > 0));
10887
+ const keyId = Schema.String.pipe(Schema.refine((s) => s.length > 0));
10888
+ const email$1 = normalize((s) => s.toLowerCase()).pipe(Schema.refine((s) => EMAIL_RE$1.test(s))).check(Schema.isMaxLength(254));
10889
+ const handle$1 = Schema.String.pipe(Schema.refine((s) => HANDLE_RE$1.test(s)));
10890
+ const country = normalize((s) => s.toUpperCase()).pipe(Schema.refine((s) => COUNTRY_CODE_RE.test(s)));
10891
+ const credential = Schema.RedactedFromValue(Schema.String.check(Schema.isMinLength(32), Schema.isMaxLength(8192), Schema.isPattern(/^[A-Za-z0-9._~-]+$/u)), { label: "native authorization credential" });
10892
+ const otp = Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[0-9]{6}$/u)), { label: "OTP" });
10893
+ const scopes = Schema.Tuple([Schema.Literal(NATIVE_CLIENT_SCOPE)]);
10894
+ /** Exact HTTPS origin, no credentials/path/query and no retired cloud deployment. */
10895
+ const NativeHttpsOriginSchema = Schema.String.check(Schema.makeFilter((value) => {
10896
+ try {
10897
+ const url = new URL(value);
10898
+ const host = url.hostname.toLowerCase().replace(/\.$/u, "");
10899
+ 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");
10900
+ } catch {
10901
+ return false;
10902
+ }
10903
+ }, { message: "must be a canonical credential-free HTTPS origin outside retired Convex cloud hosts" }));
10904
+ const NativeClientConfigurationSchema = Schema.Struct({
10905
+ clientId: Schema.Literal(NATIVE_CLIENT_ID),
10906
+ applicationId: AppIdSchema,
10907
+ keyId,
10908
+ environment: Schema.Literals([
10909
+ "development",
10910
+ "staging",
10911
+ "production"
10912
+ ]),
10913
+ keyEnvironment: Schema.Literals(["test", "live"]),
10914
+ issuer: NativeHttpsOriginSchema,
10915
+ verificationOrigin: NativeHttpsOriginSchema
10916
+ }).annotate(strict).check(Schema.makeFilter((c) => c.keyEnvironment === (c.environment === "production" ? "live" : "test"), { message: "key environment must match the configured deployment" })).annotate(strict);
10917
+ const NativeSignupProfileSchema = Schema.Struct({
10918
+ displayName: normalize((s) => s.trim()).check(Schema.isMinLength(1), Schema.isMaxLength(200)),
10919
+ country,
10920
+ handle: handle$1,
10921
+ payoutAddresses: Schema.optional(Schema.Array(Schema.Struct({
10922
+ chain: Schema.Literals([
10923
+ "evm",
10924
+ "solana",
10925
+ "starknet"
10926
+ ]),
10927
+ address: text$2(256)
10928
+ }).annotate(strict)).check(Schema.isMaxLength(10))),
10929
+ image: Schema.optional(Schema.Struct({
10930
+ mediaType: Schema.Literals([
10931
+ "image/jpeg",
10932
+ "image/png",
10933
+ "image/webp"
10934
+ ]),
10935
+ byteLength: Schema.Number.check(Schema.isInt(), Schema.isBetween({
10936
+ minimum: 1,
10937
+ maximum: NATIVE_CLIENT_LIMITS.imageBytes
10938
+ })),
10939
+ sha256: Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/u))
10940
+ }).annotate(strict))
10941
+ }).annotate(strict);
10942
+ const ClientAuthorizationIntentSchema = Schema.Union([Schema.Struct({ mode: Schema.Literal("login") }).annotate(strict), Schema.Struct({
10943
+ mode: Schema.Literal("signup"),
10944
+ profile: NativeSignupProfileSchema
10945
+ }).annotate(strict)]).annotate(strict);
10946
+ Schema.Struct({
10947
+ clientId: Schema.Literal(NATIVE_CLIENT_ID),
10948
+ email: email$1,
10949
+ intent: ClientAuthorizationIntentSchema,
10950
+ waitMs: Schema.Number.check(Schema.isInt(), Schema.isBetween({
10951
+ minimum: 1,
10952
+ maximum: NATIVE_CLIENT_LIMITS.requestMs
10953
+ })),
10954
+ continuityToken: Schema.optional(credential)
10955
+ }).annotate(strict);
10956
+ const request = {
10957
+ requestId: ClientRequestIdSchema,
10958
+ revision: nonNegativeInt,
10959
+ expiresAt: EpochMsSchema
10960
+ };
10961
+ const principal = {
10962
+ authUserId,
10963
+ email: email$1
10964
+ };
10965
+ const profileSummary = {
10966
+ displayName: text$2(200),
10967
+ handle: handle$1,
10968
+ country
10969
+ };
10970
+ Schema.Union([
10971
+ Schema.Struct({
10972
+ ...request,
10973
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.OTP_REQUIRED),
10974
+ email: email$1,
10975
+ resendAt: EpochMsSchema
10976
+ }).annotate(strict),
10977
+ Schema.Struct({
10978
+ ...request,
10979
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.REVIEW),
10980
+ ...principal,
10981
+ clientLabel: Schema.Literal("Capxul CLI"),
10982
+ scopes,
10983
+ intent: ClientAuthorizationIntentSchema,
10984
+ setupState: Schema.Literals([
10985
+ "not_required",
10986
+ "new",
10987
+ "resumable"
10988
+ ])
10989
+ }).annotate(strict).check(Schema.makeFilter((v) => v.intent.mode === "login" === (v.setupState === "not_required"), { message: "login review cannot permit account setup" })),
10990
+ Schema.Struct({
10991
+ ...request,
10992
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.PROCESSING),
10993
+ ...principal,
10994
+ purpose: Schema.Literals(["login", "signup"]),
10995
+ step: Schema.Literals([
10996
+ "profile",
10997
+ "wallet",
10998
+ "account",
10999
+ "delivery"
11000
+ ])
11001
+ }).annotate(strict).check(Schema.makeFilter((v) => v.purpose === "signup" || v.step === "delivery", { message: "login may wait for delivery but cannot enter signup processing" })),
11002
+ Schema.Struct({
11003
+ ...request,
11004
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.CONNECTED),
11005
+ ...principal,
11006
+ ...profileSummary,
11007
+ accountId: AccountIdSchema,
11008
+ grantId: ClientGrantIdSchema
11009
+ }).annotate(strict),
11010
+ Schema.Struct({
11011
+ ...request,
11012
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.REFUSED),
11013
+ reason: Schema.Literals(CLIENT_AUTHORIZATION_REFUSALS),
11014
+ code: Schema.Literals(CAPXUL_ERROR_CODES),
11015
+ field: Schema.optional(Schema.Literals([
11016
+ "email",
11017
+ "displayName",
11018
+ "country",
11019
+ "handle",
11020
+ "image",
11021
+ "payoutAddresses"
11022
+ ])),
11023
+ retryable: Schema.Boolean
11024
+ }).annotate(strict)
11025
+ ]).annotate(strict);
11026
+ Schema.Struct({
11027
+ requestId: ClientRequestIdSchema,
11028
+ revision: nonNegativeInt
11029
+ }).annotate(strict);
11030
+ Schema.Struct({
11031
+ requestId: ClientRequestIdSchema,
11032
+ code: otp
11033
+ }).annotate(strict);
11034
+ Schema.Struct({
11035
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
11036
+ grant_type: Schema.Literal("refresh_token"),
11037
+ refresh_token: credential,
11038
+ refreshAttemptId: Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{32,128}$/u)), { label: "refresh attempt" })
11039
+ }).annotate(strict);
11040
+ Schema.Struct({
11041
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
11042
+ applicationId: AppIdSchema,
11043
+ resource: text$2(2048),
11044
+ token_type: Schema.Literal("Bearer"),
11045
+ access_token: credential,
11046
+ refresh_token: credential,
11047
+ scope: Schema.Literal(NATIVE_CLIENT_SCOPE),
11048
+ expires_in: Schema.Number.check(Schema.isInt(), Schema.isBetween({
11049
+ minimum: 1,
11050
+ maximum: NATIVE_CLIENT_LIMITS.accessMs / 1e3
11051
+ })),
11052
+ grantId: ClientGrantIdSchema,
11053
+ ...principal,
11054
+ accountId: AccountIdSchema,
11055
+ issuedAt: EpochMsSchema,
11056
+ grantCreatedAt: EpochMsSchema,
11057
+ accessExpiresAt: EpochMsSchema,
11058
+ renewalExpiresAt: EpochMsSchema,
11059
+ absoluteExpiresAt: EpochMsSchema,
11060
+ revision: nonNegativeInt
11061
+ }).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);
11062
+ /** One endpoint mapping; consumers cannot substitute callback or resource URLs. */
11063
+ function nativeClientEndpoints(config) {
11064
+ const resource = `${config.issuer}/api/auth/native`;
11065
+ return {
11066
+ resource,
11067
+ device_authorization_endpoint: `${resource}/device/code`,
11068
+ token_endpoint: `${resource}/token`,
11069
+ revocation_endpoint: `${resource}/revoke`,
11070
+ session_endpoint: `${resource}/session`,
11071
+ verification_uri: `${config.verificationOrigin}/connect`
11072
+ };
11073
+ }
11074
+ Schema.Struct({
11075
+ protocol: Schema.Literal("capxul.client-authorization"),
11076
+ version: Schema.Literal(1),
11077
+ configuration: NativeClientConfigurationSchema,
11078
+ scopes_supported: scopes,
11079
+ grant_types_supported: Schema.Tuple([Schema.Literal("urn:ietf:params:oauth:grant-type:device_code"), Schema.Literal("refresh_token")]),
11080
+ resource: text$2(2048),
11081
+ device_authorization_endpoint: text$2(2048),
11082
+ token_endpoint: text$2(2048),
11083
+ revocation_endpoint: text$2(2048),
11084
+ session_endpoint: text$2(2048),
11085
+ verification_uri: text$2(2048)
11086
+ }).annotate(strict).check(Schema.makeFilter((m) => {
11087
+ const expected = nativeClientEndpoints(m.configuration);
11088
+ 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;
11089
+ }, { message: "native endpoints must match their configured issuer and verification origin" })).annotate(strict);
11090
+ Schema.Struct({
11091
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
11092
+ grant_type: Schema.Literal("urn:ietf:params:oauth:grant-type:device_code"),
11093
+ device_code: credential
11094
+ }).annotate(strict);
11095
+ Schema.Struct({
11096
+ requestId: ClientRequestIdSchema,
11097
+ device_code: credential,
11098
+ user_code: Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[A-Z0-9]{4}-[A-Z0-9]{4}$/u)), { label: "device user code" }),
11099
+ continuityToken: credential,
11100
+ verification_uri: text$2(2048),
11101
+ expires_in: Schema.Number.check(Schema.isInt(), Schema.isBetween({
11102
+ minimum: 1,
11103
+ maximum: NATIVE_CLIENT_LIMITS.requestMs / 1e3
11104
+ })),
11105
+ interval: Schema.Number.check(Schema.isInt(), Schema.isBetween({
11106
+ minimum: NATIVE_CLIENT_LIMITS.pollSeconds,
11107
+ maximum: NATIVE_CLIENT_LIMITS.requestMs / 1e3
11108
+ }))
11109
+ }).annotate(strict);
11110
+ //#endregion
10749
11111
  //#region ../../packages/sdk/src/internal/invocation-observation.ts
10750
11112
  const INVOCATION_OBSERVATION = Symbol("capxul.invocation-observation");
10751
11113
  const FAILURE_INVOCATION_SNAPSHOT = Symbol("capxul.failure-invocation-snapshot");
@@ -12609,6 +12971,9 @@ const moneyExecutionContract = {
12609
12971
  //#region ../../packages/sdk/src/ports/indexer-read.ts
12610
12972
  /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
12611
12973
  const INDEXER_MAX_LIMIT = 1e3;
12974
+ function validTime(value) {
12975
+ return Number.isSafeInteger(value) && value >= 0 && value <= Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
12976
+ }
12612
12977
  const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
12613
12978
  /**
12614
12979
  * Refuse a page the indexer would refuse, before any implementation spends a
@@ -12616,12 +12981,21 @@ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
12616
12981
  * against the hermetic adapter cannot pass on input production rejects. Throws
12617
12982
  * a `CapxulError`; callers convert it to an `IndexerReadError`.
12618
12983
  */
12984
+ function assertEventLookup(input, id) {
12985
+ 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");
12986
+ }
12619
12987
  function assertIndexerPage(input) {
12620
12988
  const safe = String(input.safe).toLowerCase();
12621
12989
  if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
12622
12990
  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}`);
12623
12991
  const offset = input.offset ?? 0;
12624
12992
  if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
12993
+ const timeline = input.timeline;
12994
+ if (input.eventId !== void 0) assertEventLookup(input, input.eventId);
12995
+ 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");
12996
+ if (timeline !== void 0) {
12997
+ 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");
12998
+ }
12625
12999
  return safe;
12626
13000
  }
12627
13001
  var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
@@ -12651,20 +13025,6 @@ function directionFor(safe, transfer) {
12651
13025
  if (from === safe && to === safe) return "self";
12652
13026
  return to === safe ? "in" : "out";
12653
13027
  }
12654
- /**
12655
- * The Transfer a protocol Payment moved is the same money as its `payment`
12656
- * row. One actor sees one item for it, so the Transfer is claimed and does not
12657
- * become a second row. Another transfer in the same transaction that this
12658
- * payment did not move stays unclaimed and keeps its own row.
12659
- */
12660
- function claimedTransferIds(transfers, payments) {
12661
- const claimed = /* @__PURE__ */ new Set();
12662
- for (const payment of payments) {
12663
- 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());
12664
- if (match !== void 0) claimed.add(match.id);
12665
- }
12666
- return claimed;
12667
- }
12668
13028
  function withAnnotation(item, annotation) {
12669
13029
  return annotation?.counterpartyLabel === void 0 ? item : {
12670
13030
  ...item,
@@ -12675,10 +13035,14 @@ function withAnnotation(item, annotation) {
12675
13035
  * An indexed settlement confirms the intent. A missing row can be outside
12676
13036
  * this page or still backfilling; only the correction owner can prove rollback.
12677
13037
  */
12678
- function confirmPayment(intent, chain) {
12679
- if (chain !== void 0) return {
13038
+ function confirmPayment(intent, chain, expected) {
13039
+ if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
12680
13040
  ...intent,
12681
13041
  status: "settled",
13042
+ ...intent.execution === void 0 ? {} : { execution: {
13043
+ status: "settled",
13044
+ retryRequired: false
13045
+ } },
12682
13046
  receipt: {
12683
13047
  chainId: chain.chainId,
12684
13048
  transactionHash: chain.txHash
@@ -12687,16 +13051,22 @@ function confirmPayment(intent, chain) {
12687
13051
  };
12688
13052
  return intent;
12689
13053
  }
13054
+ function matchesPaymentFact(chain, expected) {
13055
+ 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;
13056
+ }
12690
13057
  /**
12691
13058
  * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
12692
13059
  * denominate its asset. The indexer serves only configured assets, so a row
12693
13060
  * the registry does not know means the SDK and that deployment disagree; it is
12694
13061
  * dropped rather than shown with a guessed currency.
12695
13062
  */
13063
+ function isActivityVisible(item) {
13064
+ return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
13065
+ }
12696
13066
  function transferItem(safe, transfer, annotation) {
12697
13067
  const asset = configuredMoneyAssetById(transfer.assetId);
12698
13068
  if (asset === null) return null;
12699
- const direction = directionFor(safe, transfer);
13069
+ const direction = directionFor(safe.toLowerCase(), transfer);
12700
13070
  const counterparty = direction === "in" ? transfer.from : transfer.to;
12701
13071
  const at = blockMillis(transfer);
12702
13072
  return withAnnotation({
@@ -12718,47 +13088,6 @@ function transferItem(safe, transfer, annotation) {
12718
13088
  updatedAt: at
12719
13089
  }, annotation);
12720
13090
  }
12721
- /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
12722
- function compareItems(left, right) {
12723
- if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
12724
- if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
12725
- if (left.id === right.id) return 0;
12726
- return left.id < right.id ? 1 : -1;
12727
- }
12728
- function stitchActivity(input) {
12729
- const safe = input.safe.toLowerCase();
12730
- const chainBySettlement = /* @__PURE__ */ new Map();
12731
- for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
12732
- const chainByPaymentId = /* @__PURE__ */ new Map();
12733
- for (const link of input.links) {
12734
- const row = chainBySettlement.get(link.settlementId.toLowerCase());
12735
- if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
12736
- }
12737
- const annotations = /* @__PURE__ */ new Map();
12738
- for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
12739
- const items = [];
12740
- const claiming = [];
12741
- for (const intent of input.intents) {
12742
- if (intent.kind !== "payment") continue;
12743
- const chain = chainByPaymentId.get(intent.id);
12744
- if (chain !== void 0) claiming.push(chain);
12745
- items.push(withAnnotation(confirmPayment(intent, chain), annotations.get(`payment:${intent.id}`)));
12746
- }
12747
- const claimed = claimedTransferIds(input.transfers, claiming);
12748
- for (const transfer of input.transfers) {
12749
- if (claimed.has(transfer.id)) continue;
12750
- const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
12751
- if (item !== null) items.push(item);
12752
- }
12753
- return items.sort(compareItems);
12754
- }
12755
- /** The reference ids one stitched page needs its Convex overlay for. */
12756
- function overlayReferences(items) {
12757
- return items.map((item) => ({
12758
- kind: item.kind,
12759
- id: item.id
12760
- }));
12761
- }
12762
13091
  /**
12763
13092
  * Money in, money out and the pending share, one bucket per currency and
12764
13093
  * precision. Two assets that peg to the same currency at different precision
@@ -12777,7 +13106,7 @@ function summarizeItems(items) {
12777
13106
  };
12778
13107
  const raw = rawUnits(item.amount.value, item.amount.decimals);
12779
13108
  if (item.kind === "payment" && item.status !== "settled") {
12780
- if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed") bucket.pendingIn += raw;
13109
+ if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
12781
13110
  } else if (item.direction === "in") bucket.in += raw;
12782
13111
  else if (item.direction === "out") bucket.out += raw;
12783
13112
  buckets.set(key, bucket);
@@ -12800,179 +13129,412 @@ function rawUnits(value, decimals) {
12800
13129
  }
12801
13130
  //#endregion
12802
13131
  //#region ../../packages/sdk/src/surface/activity-read.ts
12803
- /** How many Convex intent rows one page reads for its labels and pending rows. */
12804
- const INTENT_WINDOW = 100;
12805
- /** movement/annotations:overlay bounds each identifier array to 200. */
13132
+ const BATCH = 100;
12806
13133
  const MAX_OVERLAY_IDS = 200;
12807
- /** The window a summary aggregates over. The indexer's own page bound. */
12808
- const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
12809
- function parseCursor(value) {
12810
- if (value === void 0) return 0;
12811
- try {
12812
- const parsed = JSON.parse(value);
12813
- return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
12814
- } catch {
12815
- return 0;
12816
- }
13134
+ function actorKey(actor) {
13135
+ return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
13136
+ }
13137
+ function scopeKey(scope) {
13138
+ return `${scope.principalAccountId ?? "legacy"}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
13139
+ }
13140
+ function selectionKey(params) {
13141
+ const filter = params?.filter;
13142
+ return JSON.stringify([
13143
+ params?.range?.from ?? null,
13144
+ params?.range?.to ?? null,
13145
+ filter?.kind ?? null,
13146
+ filter?.direction ?? null,
13147
+ filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
13148
+ ]);
12817
13149
  }
12818
- function inRange(item, range) {
12819
- if (range?.from !== void 0 && item.createdAt < range.from) return false;
12820
- return range?.to === void 0 || item.createdAt <= range.to;
13150
+ function validString(value, max) {
13151
+ return typeof value === "string" && value.length <= max;
13152
+ }
13153
+ function validPosition(position) {
13154
+ 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);
13155
+ }
13156
+ function parseCursor(value, scope, selection) {
13157
+ if (value === void 0) return null;
13158
+ if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
13159
+ const cursor = JSON.parse(value);
13160
+ 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");
13161
+ return cursor;
13162
+ }
13163
+ function requireParams(params) {
13164
+ const limit = params?.limit ?? 25;
13165
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > BATCH) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
13166
+ 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");
13167
+ const f = params?.filter;
13168
+ if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
13169
+ "in",
13170
+ "out",
13171
+ "self"
13172
+ ].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");
12821
13173
  }
12822
- /** The caller's filter applied to the merged page. The join owns the order. */
12823
13174
  function matchesFilter(item, filter) {
12824
- if (filter === void 0) return true;
12825
- if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
12826
- if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
12827
- if (filter.status === void 0) return true;
12828
- return item.kind === "payment" && filter.status.includes(item.status);
13175
+ 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));
12829
13176
  }
12830
13177
  function overlay(deps, actor, controls, input) {
12831
13178
  const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
12832
- const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, index) => index * MAX_OVERLAY_IDS);
13179
+ const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
12833
13180
  return Effect.forEach(offsets, (offset) => deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
12834
13181
  ...actor === void 0 ? {} : { actor },
12835
13182
  ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
12836
13183
  ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
12837
- } })), { concurrency: 2 }).pipe(Effect.map((pages) => ({
12838
- ...pages[0],
12839
- links: pages.flatMap((page) => page.links),
12840
- annotations: pages.flatMap((page) => page.annotations)
12841
- })));
13184
+ } })), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
13185
+ const first = pages[0];
13186
+ if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
13187
+ return Effect.succeed({
13188
+ ...first,
13189
+ links: pages.flatMap((page) => page.links),
13190
+ annotations: pages.flatMap((page) => page.annotations),
13191
+ ...pages.every((page) => page.payments !== void 0) ? { payments: pages.flatMap((page) => page.payments ?? []) } : {}
13192
+ });
13193
+ }));
12842
13194
  }
12843
13195
  function convexPage(deps, actor, controls, input) {
12844
13196
  return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
12845
- ...actor === void 0 ? {} : { actor },
12846
- ...input.cursor === void 0 ? {} : { cursor: input.cursor },
12847
- ...input.limit === void 0 ? {} : { limit: input.limit },
12848
- ...input.range === void 0 ? {} : { range: input.range }
13197
+ ...input,
13198
+ actor
12849
13199
  } }));
12850
13200
  }
12851
- /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
12852
- function chainRows(deps, safe, limit) {
12853
- const page = {
12854
- safe,
12855
- limit: Math.min(limit, INDEXER_MAX_LIMIT)
12856
- };
12857
- return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
12858
- transfers,
12859
- payments
12860
- })), Effect.catch(() => Effect.succeed(null)));
13201
+ function chainRead(deps, table, input) {
13202
+ return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
13203
+ if (error.publicError.details?.reason === "scope-refused" || ![
13204
+ "NETWORK_ERROR",
13205
+ "RATE_LIMITED",
13206
+ "PROVIDER_ERROR",
13207
+ "CAPABILITY_UNAVAILABLE",
13208
+ "TIMEOUT"
13209
+ ].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
13210
+ return Effect.succeed(null);
13211
+ }));
12861
13212
  }
12862
- /** The stitched, filtered rows of one window, newest first. */
12863
- function stitchedWindow(deps, actor, controls, window, params) {
13213
+ function fallbackPage(deps, scope, controls, params, state, degraded) {
12864
13214
  return Effect.gen(function* () {
12865
- const scope = yield* overlay(deps, actor, controls, {});
12866
- const convex = yield* convexPage(deps, actor, controls, {
12867
- limit: INTENT_WINDOW,
12868
- ...params.range === void 0 ? {} : { range: params.range }
13215
+ const page = yield* convexPage(deps, scope.actor, controls, {
13216
+ limit: params?.limit ?? 25,
13217
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
13218
+ range: {
13219
+ ...params?.range?.from === void 0 ? {} : { from: params.range.from },
13220
+ to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
13221
+ },
13222
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
12869
13223
  });
12870
- const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
12871
- if (rows === null || scope.safe === null) {
12872
- if (scope.safe !== null) yield* Effect.annotateCurrentSpan({
12873
- read_source: "convex",
12874
- read_warning: "indexer-unavailable"
12875
- });
12876
- return {
12877
- items: convex.items.filter((item) => matchesFilter(item, params.filter)),
12878
- stitched: false,
12879
- ...scope.safe === null ? {} : { readWarning: "indexer-unavailable" }
12880
- };
13224
+ const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
13225
+ kind: item.kind,
13226
+ id: item.id
13227
+ })) });
13228
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
13229
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
13230
+ yield* Effect.annotateCurrentSpan({
13231
+ read_source: "convex",
13232
+ ...degraded ? { read_warning: "indexer-unavailable" } : {}
13233
+ });
13234
+ return {
13235
+ ...page,
13236
+ items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
13237
+ checkpoint: state.checkpoint,
13238
+ cursor: page.cursor === null ? null : JSON.stringify({
13239
+ ...state,
13240
+ source: "fallback",
13241
+ paymentCursor: page.cursor,
13242
+ degraded
13243
+ }),
13244
+ ...degraded ? { readWarning: "indexer-unavailable" } : {}
13245
+ };
13246
+ });
13247
+ }
13248
+ function boundedRange(range, checkpoint) {
13249
+ return {
13250
+ ...range?.from === void 0 ? {} : { from: range.from },
13251
+ to: Math.min(checkpoint, range?.to ?? checkpoint)
13252
+ };
13253
+ }
13254
+ function unavailablePage(deps, scope, controls, params, state, first) {
13255
+ if (first) return fallbackPage(deps, scope, controls, params, state, true);
13256
+ return Effect.annotateCurrentSpan({
13257
+ read_source: "indexer",
13258
+ read_warning: "indexer-unavailable"
13259
+ }).pipe(Effect.as({
13260
+ items: [],
13261
+ cursor: null,
13262
+ checkpoint: state.checkpoint,
13263
+ observedAt: Date.now(),
13264
+ readWarning: "indexer-unavailable"
13265
+ }));
13266
+ }
13267
+ function readTransfers(deps, safe, range, state, wanted, first) {
13268
+ const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
13269
+ const to = Math.floor(range.to / 1e3);
13270
+ if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
13271
+ return chainRead(deps, "transfer", {
13272
+ safe: toAddress(safe),
13273
+ limit: wanted ? BATCH : 1,
13274
+ timeline: {
13275
+ from,
13276
+ to,
13277
+ ...state.transferBefore === null ? {} : { before: state.transferBefore }
12881
13278
  }
12882
- yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
12883
- const decoration = yield* overlay(deps, actor, controls, {
12884
- references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
13279
+ });
13280
+ }
13281
+ function readPayments(deps, scope, controls, range, state, wanted) {
13282
+ const done = state.paymentDone || !wanted;
13283
+ if (state.paymentPending.length > 0 || done) return Effect.succeed({
13284
+ items: [],
13285
+ pending: state.paymentPending,
13286
+ cursor: state.paymentCursor,
13287
+ done
13288
+ });
13289
+ return convexPage(deps, scope.actor, controls, {
13290
+ limit: BATCH,
13291
+ range,
13292
+ filter: { kind: "payment" },
13293
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
13294
+ }).pipe(Effect.map((page) => ({
13295
+ items: page.items,
13296
+ pending: page.items.map((item) => item.id),
13297
+ cursor: page.cursor,
13298
+ done: page.cursor === null
13299
+ })));
13300
+ }
13301
+ function readPaymentFacts(deps, safe, settlements) {
13302
+ const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
13303
+ return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
13304
+ safe: toAddress(safe),
13305
+ limit: 50,
13306
+ settlements: settlements.slice(offset, offset + 50)
13307
+ }), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
13308
+ }
13309
+ function decoratePage(deps, scope, controls, transfers, page, range) {
13310
+ return Effect.gen(function* () {
13311
+ const decoration = yield* overlay(deps, scope.actor, controls, {
13312
+ references: [...page.pending.map((id) => ({
13313
+ kind: "payment",
13314
+ id
13315
+ })), ...transfers.map((row) => ({
12885
13316
  kind: "movement",
12886
13317
  id: row.id
12887
13318
  }))],
12888
- settlementIds: rows.payments.map((row) => row.settlementId)
13319
+ settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
12889
13320
  });
12890
- const intents = [...convex.items];
12891
- const missing = new Set(decoration.links.map((link) => link.paymentId));
12892
- for (const item of intents) if (item.kind === "payment") missing.delete(item.id);
12893
- let cursor = convex.cursor;
12894
- while (missing.size > 0 && cursor !== null) {
12895
- const page = yield* convexPage(deps, actor, controls, {
12896
- limit: INTENT_WINDOW,
12897
- cursor,
12898
- ...params.range === void 0 ? {} : { range: params.range }
12899
- });
12900
- for (const item of page.items) if (item.kind === "payment" && missing.delete(item.id)) intents.push(item);
12901
- cursor = page.cursor;
13321
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
13322
+ const byId = new Map([...page.items, ...decoration.payments ?? []].map((item) => [item.id, item]));
13323
+ const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
13324
+ const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
13325
+ if (facts === null) return null;
13326
+ const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
13327
+ const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
13328
+ fact: bySettlement.get(link.settlementId),
13329
+ expected: link.expected
13330
+ }]));
13331
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
13332
+ return {
13333
+ payments: page.pending.flatMap((id) => {
13334
+ const item = byId.get(id);
13335
+ if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
13336
+ const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
13337
+ return isActivityVisible(confirmed) ? [confirmed] : [];
13338
+ }),
13339
+ annotations,
13340
+ linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
13341
+ };
13342
+ });
13343
+ }
13344
+ function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
13345
+ const { payments, annotations, linked } = decoration;
13346
+ let pi = 0;
13347
+ let ti = 0;
13348
+ const items = [];
13349
+ const transferEnd = !wantsTransfers || transfers.length < BATCH;
13350
+ let transferBefore = state.transferBefore;
13351
+ while (items.length < limit) {
13352
+ if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
13353
+ const payment = payments[pi];
13354
+ const row = transfers[ti];
13355
+ if (payment === void 0 && row === void 0) break;
13356
+ if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
13357
+ pi += 1;
13358
+ if (matchesFilter(payment, filter)) items.push(payment);
13359
+ } else if (row !== void 0) {
13360
+ ti += 1;
13361
+ transferBefore = {
13362
+ timestamp: Number(row.timestamp),
13363
+ id: row.id
13364
+ };
13365
+ if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
13366
+ const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
13367
+ if (item !== null && matchesFilter(item, filter)) items.push(item);
12902
13368
  }
12903
- const older = intents.slice(convex.items.length);
12904
- const olderDecoration = older.length === 0 ? [] : (yield* overlay(deps, actor, controls, { references: overlayReferences(older) })).annotations;
13369
+ }
13370
+ return {
13371
+ items,
13372
+ next: {
13373
+ ...state,
13374
+ paymentCursor: page.cursor,
13375
+ paymentDone: page.done,
13376
+ paymentPending: payments.slice(pi).map((item) => item.id),
13377
+ transferBefore,
13378
+ transferDone: ti === transfers.length && transferEnd
13379
+ }
13380
+ };
13381
+ }
13382
+ function indexedPage(deps, scope, controls, params, state, first) {
13383
+ return Effect.gen(function* () {
13384
+ const range = boundedRange(params?.range, state.checkpoint);
13385
+ const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
13386
+ const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
13387
+ if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
13388
+ const transfers = wantsTransfers ? found : [];
13389
+ const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
13390
+ const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
13391
+ if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
13392
+ const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
13393
+ const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
13394
+ if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
13395
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
12905
13396
  return {
12906
- items: stitchActivity({
12907
- safe: scope.safe,
12908
- transfers: rows.transfers,
12909
- payments: rows.payments,
12910
- intents,
12911
- links: decoration.links,
12912
- annotations: [...decoration.annotations, ...olderDecoration]
12913
- }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
12914
- stitched: true
13397
+ items,
13398
+ cursor,
13399
+ checkpoint: state.checkpoint,
13400
+ observedAt: Date.now()
12915
13401
  };
12916
13402
  });
12917
13403
  }
12918
13404
  function readActivityPage(deps, actor, controls, params) {
12919
- const limit = params?.limit ?? 25;
12920
- const offset = parseCursor(params?.cursor);
12921
- return stitchedWindow(deps, actor, controls, offset + limit + 1, {
12922
- ...params?.range === void 0 ? {} : { range: params.range },
12923
- ...params?.filter === void 0 ? {} : { filter: params.filter }
12924
- }).pipe(Effect.map(({ items, stitched, readWarning }) => {
12925
- const observedAt = Date.now();
12926
- if (!stitched) return {
12927
- items: items.slice(0, limit),
13405
+ return Effect.gen(function* () {
13406
+ yield* Effect.try({
13407
+ try: () => requireParams(params),
13408
+ catch: (cause) => ({ publicError: cause })
13409
+ });
13410
+ const scope = yield* overlay(deps, actor, controls, {});
13411
+ const selection = selectionKey(params);
13412
+ const cursor = yield* Effect.try({
13413
+ try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
13414
+ catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
13415
+ });
13416
+ const state = cursor ?? {
13417
+ version: 1,
13418
+ scope: scopeKey(scope),
13419
+ selection,
13420
+ checkpoint: Date.now(),
13421
+ source: "indexed",
13422
+ paymentCursor: null,
13423
+ paymentDone: false,
13424
+ paymentPending: [],
13425
+ transferBefore: null,
13426
+ transferDone: false,
13427
+ degraded: false
13428
+ };
13429
+ const range = boundedRange(params?.range, state.checkpoint);
13430
+ if (range.from !== void 0 && range.from > range.to) return {
13431
+ items: [],
12928
13432
  cursor: null,
12929
- checkpoint: observedAt,
12930
- observedAt,
12931
- ...readWarning === void 0 ? {} : { readWarning }
13433
+ checkpoint: state.checkpoint,
13434
+ observedAt: Date.now()
12932
13435
  };
13436
+ if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
13437
+ if (scope.payments === void 0) return yield* unavailablePage(deps, scope, controls, params, state, cursor === null);
13438
+ return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
13439
+ });
13440
+ }
13441
+ function readActivitySummary(deps, actor, controls, params) {
13442
+ return Effect.gen(function* () {
13443
+ const totals = /* @__PURE__ */ new Map();
13444
+ let cursor;
13445
+ let count = 0;
13446
+ let readWarning;
13447
+ do {
13448
+ const page = yield* readActivityPage(deps, actor, controls, {
13449
+ limit: BATCH,
13450
+ ...cursor === void 0 ? {} : { cursor },
13451
+ ...params?.window === void 0 ? {} : { range: params.window }
13452
+ });
13453
+ count += page.items.length;
13454
+ readWarning ??= page.readWarning;
13455
+ for (const total of summarizeItems(page.items)) {
13456
+ const key = `${total.currency}:${total.decimals}`;
13457
+ const old = totals.get(key);
13458
+ totals.set(key, old === void 0 ? total : {
13459
+ ...total,
13460
+ in: String(BigInt(old.in) + BigInt(total.in)),
13461
+ out: String(BigInt(old.out) + BigInt(total.out)),
13462
+ net: String(BigInt(old.net) + BigInt(total.net)),
13463
+ pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
13464
+ });
13465
+ }
13466
+ cursor = page.cursor ?? void 0;
13467
+ } while (cursor !== void 0);
12933
13468
  return {
12934
- items: items.slice(offset, offset + limit),
12935
- cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
12936
- checkpoint: observedAt,
12937
- observedAt
13469
+ window: {
13470
+ from: params?.window?.from ?? null,
13471
+ to: params?.window?.to ?? null
13472
+ },
13473
+ totals: [...totals.values()],
13474
+ count,
13475
+ observedAt: Date.now(),
13476
+ ...readWarning === void 0 ? {} : { readWarning }
12938
13477
  };
12939
- }));
13478
+ });
12940
13479
  }
12941
- function readActivitySummary(deps, actor, controls, params) {
12942
- return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items, readWarning }) => ({
12943
- window: {
12944
- from: params?.window?.from ?? null,
12945
- to: params?.window?.to ?? null
12946
- },
12947
- totals: summarizeItems(items),
12948
- count: items.length,
12949
- observedAt: Date.now(),
12950
- ...readWarning === void 0 ? {} : { readWarning }
12951
- })));
13480
+ /** An indexed Movement has no Convex materialization. Read its exact native row. */
13481
+ function readIndexedMovement(deps, actor, controls, eventId) {
13482
+ return Effect.gen(function* () {
13483
+ const scope = yield* overlay(deps, actor, controls, { references: [{
13484
+ kind: "movement",
13485
+ id: eventId
13486
+ }] });
13487
+ if (scope.safe === null) return null;
13488
+ const safe = scope.safe.toLowerCase();
13489
+ const row = (yield* deps.indexer.read("transfer", {
13490
+ safe: toAddress(safe),
13491
+ limit: 1,
13492
+ eventId
13493
+ }).pipe(Effect.catch((error) => Effect.fail({ publicError: error.publicError }))))[0];
13494
+ if (row === void 0) return null;
13495
+ const blockNumber = Number(row.blockNumber);
13496
+ 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") });
13497
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
13498
+ return {
13499
+ kind: "movement",
13500
+ movementId: row.id,
13501
+ viewer: scope.actor,
13502
+ annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
13503
+ evidence: {
13504
+ chainId: row.chainId,
13505
+ contractAddress: row.token,
13506
+ tokenAddress: row.token,
13507
+ transactionHash: row.txHash,
13508
+ blockNumber,
13509
+ blockHash: row.blockHash,
13510
+ inclusion: "included",
13511
+ verification: "verified",
13512
+ canonicalState: "finalized",
13513
+ transferLogIndex: row.logIndex,
13514
+ source: row.from,
13515
+ destination: row.to,
13516
+ rawAmount: row.amount.toString(),
13517
+ link: {
13518
+ movementId: row.id,
13519
+ depositPaymentId: null
13520
+ }
13521
+ }
13522
+ };
13523
+ });
12952
13524
  }
12953
- /**
12954
- * Tell the caller when the indexed page for this Safe changed.
12955
- *
12956
- * The transport is the indexer's one SSE connection, wired in the Ponder
12957
- * adapter under `activity.subscribe`. A reactive snapshot is not a product
12958
- * event, so nothing here emits telemetry: the adapter reports a teardown, and
12959
- * the caller re-reads.
12960
- */
12961
13525
  function subscribeActivity(deps, actor, controls, onChange) {
12962
13526
  return Effect.gen(function* () {
12963
13527
  const scope = yield* overlay(deps, actor, controls, {});
12964
13528
  if (scope.safe === null) return () => {};
12965
- const page = {
12966
- safe: scope.safe,
12967
- limit: INTENT_WINDOW
12968
- };
12969
13529
  let delivered = false;
12970
- const notify = () => {
12971
- if (delivered) onChange();
12972
- delivered = true;
12973
- };
12974
- return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
12975
- if (snapshot.status === "ok") notify();
13530
+ return yield* deps.indexer.subscribe("transfer", {
13531
+ safe: toAddress(scope.safe),
13532
+ limit: BATCH
13533
+ }, (snapshot) => {
13534
+ if (snapshot.status === "ok") {
13535
+ if (delivered) onChange();
13536
+ delivered = true;
13537
+ }
12976
13538
  if (snapshot.status === "error") {
12977
13539
  delivered = true;
12978
13540
  onChange();
@@ -12982,7 +13544,7 @@ function subscribeActivity(deps, actor, controls, onChange) {
12982
13544
  }
12983
13545
  //#endregion
12984
13546
  //#region ../../packages/sdk/package.json
12985
- var version$1 = "4.20.0-beta.1";
13547
+ var version$1 = "4.20.0-beta.3";
12986
13548
  //#endregion
12987
13549
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
12988
13550
  /** Fixed fallback for failures that have no safe message. */
@@ -14339,7 +14901,10 @@ function makeFinancialOpsMethods(deps) {
14339
14901
  ...params?.limit === void 0 ? {} : { limit: params.limit },
14340
14902
  ...params?.range === void 0 ? {} : { range: params.range },
14341
14903
  ...params?.filter === void 0 ? {} : { filter: params.filter }
14342
- } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
14904
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
14905
+ ...page,
14906
+ items: page.items.filter(isActivityVisible)
14907
+ })), Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
14343
14908
  },
14344
14909
  summary: (params, options) => {
14345
14910
  const controls = deps.invocationControls?.(options) ?? options;
@@ -14360,7 +14925,10 @@ function makeFinancialOpsMethods(deps) {
14360
14925
  actor: scope,
14361
14926
  referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
14362
14927
  });
14363
- }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
14928
+ }).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" ? {
14929
+ kind: "organization",
14930
+ orgId: actor.orgId
14931
+ } : void 0, controls, reference.id) : deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
14364
14932
  input: {
14365
14933
  kind: reference.kind,
14366
14934
  id: reference.id,
@@ -15052,6 +15620,277 @@ async function recoverRawDigestSigner(input) {
15052
15620
  }
15053
15621
  }
15054
15622
  //#endregion
15623
+ //#region ../../packages/observability/src/engineering-delivery.ts
15624
+ /** Fixed admission limits for one CLI invocation and its SDK client. */
15625
+ const CLI_ENGINEERING_LIMITS = Object.freeze({
15626
+ records: 80,
15627
+ ordinaryRecords: 64,
15628
+ recordBytes: 16384,
15629
+ requests: 5,
15630
+ ordinaryRequests: 2,
15631
+ requestBytes: 1572864,
15632
+ metricSeries: 16,
15633
+ requestTimeoutMs: 1e3
15634
+ });
15635
+ const text$1 = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
15636
+ const attributes = (input, limit = 32) => {
15637
+ const out = {};
15638
+ let count = 0;
15639
+ let bytes = 0;
15640
+ try {
15641
+ for (const key in input) {
15642
+ if (count++ >= limit) break;
15643
+ if (key.length > 64) continue;
15644
+ const value = Object.getOwnPropertyDescriptor(input, key)?.value;
15645
+ const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
15646
+ if (scalar === void 0) continue;
15647
+ bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
15648
+ if (bytes > (limit >= 24 ? 2e3 : 300)) break;
15649
+ out[key] = scalar;
15650
+ }
15651
+ } catch {}
15652
+ return out;
15653
+ };
15654
+ const terminalName = (name) => name === "cli.command.completed" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
15655
+ /** Native exporters retain their serialization and scheduling; this owns admission only. */
15656
+ function cliEngineeringDelivery(budget, ownsMetrics) {
15657
+ const closed = Deferred.makeUnsafe();
15658
+ let isClosed = false;
15659
+ let metricsRunning = false;
15660
+ const logs = [];
15661
+ const spans = /* @__PURE__ */ new Map();
15662
+ const terminalBodies = /* @__PURE__ */ new WeakSet();
15663
+ const bodyLeases = /* @__PURE__ */ new WeakMap();
15664
+ const pendingBodies = /* @__PURE__ */ new Set();
15665
+ const holdBody = (body, releases) => {
15666
+ if (releases.length === 0) return;
15667
+ const lease = {
15668
+ started: false,
15669
+ release: () => {
15670
+ if (!pendingBodies.delete(lease)) return;
15671
+ bodyLeases.delete(body);
15672
+ for (const release of releases) release();
15673
+ }
15674
+ };
15675
+ bodyLeases.set(body, lease);
15676
+ pendingBodies.add(lease);
15677
+ };
15678
+ const discardBody = (body) => bodyLeases.get(body)?.release();
15679
+ const drainLogs = () => {
15680
+ for (const release of logs.splice(0)) release();
15681
+ };
15682
+ const close = () => {
15683
+ isClosed = true;
15684
+ Deferred.doneUnsafe(closed, Effect.void);
15685
+ for (const lease of pendingBodies) if (!lease.started) lease.release();
15686
+ drainLogs();
15687
+ for (const release of spans.values()) release();
15688
+ spans.clear();
15689
+ if (ownsMetrics) budget.metricRegistry.clear();
15690
+ };
15691
+ return {
15692
+ close,
15693
+ discardBody,
15694
+ metrics(run) {
15695
+ return Effect.suspend(() => {
15696
+ if (isClosed || metricsRunning) return Effect.void;
15697
+ metricsRunning = true;
15698
+ return run.pipe(Effect.ensuring(Effect.sync(() => {
15699
+ metricsRunning = false;
15700
+ })));
15701
+ });
15702
+ },
15703
+ logger(delegate) {
15704
+ return Logger.make((options) => {
15705
+ if (isClosed) return;
15706
+ let release;
15707
+ try {
15708
+ const message = text$1(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
15709
+ release = budget.admitRecord(terminalName(message));
15710
+ if (release === void 0) return;
15711
+ const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
15712
+ const fiber = Object.create(options.fiber);
15713
+ Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
15714
+ logs.push(release);
15715
+ delegate.log({
15716
+ ...options,
15717
+ message,
15718
+ cause: Cause.empty,
15719
+ fiber
15720
+ });
15721
+ } catch {
15722
+ if (release !== void 0) {
15723
+ const index = logs.indexOf(release);
15724
+ if (index !== -1) logs.splice(index, 1);
15725
+ release();
15726
+ }
15727
+ budget.drop();
15728
+ }
15729
+ });
15730
+ },
15731
+ tracer(delegate) {
15732
+ return Tracer.make({
15733
+ span(options) {
15734
+ const name = text$1(options.name);
15735
+ const release = isClosed ? void 0 : budget.admitRecord(terminalName(name));
15736
+ const spanOptions = {
15737
+ ...options,
15738
+ name,
15739
+ annotations: Context.empty(),
15740
+ links: []
15741
+ };
15742
+ const span = release === void 0 ? new Tracer.NativeSpan({
15743
+ ...spanOptions,
15744
+ sampled: false
15745
+ }) : delegate.span(spanOptions);
15746
+ if (release !== void 0) spans.set(span.spanId, release);
15747
+ const wrapped = Object.create(span);
15748
+ let fields = 0, events = 0, links = 0, attributeBytes = 0;
15749
+ let ended = false;
15750
+ Object.defineProperties(wrapped, {
15751
+ attribute: { value: (key, value) => {
15752
+ if (fields >= 32 && !span.attributes.has(key)) {
15753
+ budget.drop();
15754
+ return;
15755
+ }
15756
+ const safe = attributes({ [key]: value }, 1);
15757
+ if (!Object.hasOwn(safe, key)) return;
15758
+ const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
15759
+ const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
15760
+ if (attributeBytes - previous + next > 2e3) {
15761
+ budget.drop();
15762
+ return;
15763
+ }
15764
+ attributeBytes += next - previous;
15765
+ if (!span.attributes.has(key)) fields += 1;
15766
+ span.attribute(key, safe[key]);
15767
+ } },
15768
+ event: { value: (eventName, time, attrs) => {
15769
+ if (events++ >= 2) {
15770
+ budget.drop();
15771
+ return;
15772
+ }
15773
+ span.event(text$1(eventName), time, attributes(attrs ?? {}, 4));
15774
+ } },
15775
+ addLinks: { value: (items) => {
15776
+ for (const item of items.slice(0, Math.max(0, 2 - links))) {
15777
+ if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
15778
+ budget.drop();
15779
+ continue;
15780
+ }
15781
+ span.addLinks([{
15782
+ span: Tracer.externalSpan({
15783
+ traceId: item.span.traceId,
15784
+ spanId: item.span.spanId,
15785
+ sampled: item.span.sampled
15786
+ }),
15787
+ attributes: attributes(item.attributes, 4)
15788
+ }]);
15789
+ links += 1;
15790
+ }
15791
+ } },
15792
+ end: { value: (time, exit) => {
15793
+ if (ended) return;
15794
+ ended = true;
15795
+ span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
15796
+ } }
15797
+ });
15798
+ wrapped.addLinks(options.links ?? []);
15799
+ return wrapped;
15800
+ },
15801
+ ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
15802
+ });
15803
+ },
15804
+ serialization(delegate) {
15805
+ return {
15806
+ logs(data) {
15807
+ const releases = logs.splice(0);
15808
+ try {
15809
+ const body = delegate.logs(data);
15810
+ if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => l.body?.stringValue === "cli.command.completed")))) terminalBodies.add(body);
15811
+ holdBody(body, releases);
15812
+ return body;
15813
+ } catch (error) {
15814
+ for (const release of releases) release();
15815
+ throw error;
15816
+ }
15817
+ },
15818
+ traces(data) {
15819
+ const releases = [];
15820
+ for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
15821
+ const release = spans.get(span.spanId);
15822
+ if (release !== void 0) releases.push(release);
15823
+ spans.delete(span.spanId);
15824
+ }
15825
+ try {
15826
+ const body = delegate.traces(data);
15827
+ if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) terminalBodies.add(body);
15828
+ holdBody(body, releases);
15829
+ return body;
15830
+ } catch (error) {
15831
+ for (const release of releases) release();
15832
+ throw error;
15833
+ }
15834
+ },
15835
+ metrics(data) {
15836
+ const body = delegate.metrics(data);
15837
+ terminalBodies.add(body);
15838
+ return body;
15839
+ }
15840
+ };
15841
+ },
15842
+ transport(client, enabled = () => true) {
15843
+ const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
15844
+ if (enabled()) return response;
15845
+ budget.drop();
15846
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
15847
+ })).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
15848
+ let received = 0;
15849
+ return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
15850
+ received += chunk.byteLength;
15851
+ if (received > 65536) throw new Error("Engineering response limit exceeded");
15852
+ })).pipe(Effect.catchTag("HttpClientError", (error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error)));
15853
+ }))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
15854
+ times: 1,
15855
+ schedule: Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
15856
+ if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
15857
+ const header = input.reason.response.headers["retry-after"];
15858
+ const numeric = Number(header);
15859
+ const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
15860
+ return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
15861
+ })))
15862
+ }));
15863
+ return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
15864
+ if (isClosed) {
15865
+ discardBody(request.body);
15866
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
15867
+ }
15868
+ const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
15869
+ if (request.body._tag === "Empty") {
15870
+ discardBody(request.body);
15871
+ return Effect.succeed(droppedResponse());
15872
+ }
15873
+ const bytes = request.body.contentLength;
15874
+ const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, terminalBodies.has(request.body));
15875
+ if (release === void 0) {
15876
+ discardBody(request.body);
15877
+ return Effect.succeed(droppedResponse());
15878
+ }
15879
+ const lease = bodyLeases.get(request.body);
15880
+ if (lease !== void 0) lease.started = true;
15881
+ 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(() => {
15882
+ if (Option.isNone(result)) budget.drop();
15883
+ })), Effect.catchCause(() => Effect.sync(() => {
15884
+ budget.drop();
15885
+ })), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
15886
+ release();
15887
+ discardBody(request.body);
15888
+ })));
15889
+ }));
15890
+ }
15891
+ };
15892
+ }
15893
+ //#endregion
15055
15894
  //#region ../../packages/observability/src/engineering.ts
15056
15895
  const ENGINEERING_CAPXUL_ENVS = [
15057
15896
  "development",
@@ -15226,10 +16065,13 @@ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
15226
16065
  const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
15227
16066
  "correlation_id",
15228
16067
  "journey_id",
15229
- "invocation_id"
16068
+ "invocation_id",
16069
+ "posthogDistinctId"
15230
16070
  ]);
15231
16071
  const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
15232
16072
  "command",
16073
+ "capability",
16074
+ "invocation_kind",
15233
16075
  "outcome",
15234
16076
  "capxul_env",
15235
16077
  "unit"
@@ -15237,7 +16079,7 @@ const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
15237
16079
  const MINIMAL_METRIC_NAMES = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
15238
16080
  const traceId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{32}$/u.test(value);
15239
16081
  const spanId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{16}$/u.test(value);
15240
- const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value)) ? value : "capxul.engineering";
16082
+ 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";
15241
16083
  const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
15242
16084
  ["outcome", [
15243
16085
  "succeeded",
@@ -15250,7 +16092,10 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
15250
16092
  ["failure_mode", FAILURE_MODES$1],
15251
16093
  ["capxul_env", ENGINEERING_CAPXUL_ENVS],
15252
16094
  ["producer", ["browser", "server"]],
15253
- ["command", ["doctor"]],
16095
+ ["command", CLI_COMMAND_NAMES],
16096
+ ["capability", CLI_CAPABILITIES],
16097
+ ["invocation_kind", CLI_INVOCATION_KINDS],
16098
+ ["cli_identity_kind", ["anonymous"]],
15254
16099
  ["output_mode", ["human", "json"]],
15255
16100
  ["unit", [
15256
16101
  "1",
@@ -15467,10 +16312,12 @@ function minimalSerialization(config) {
15467
16312
  };
15468
16313
  }
15469
16314
  Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
16315
+ var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
15470
16316
  /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
15471
16317
  const makeEngineeringTelemetryLayer = (config) => {
15472
16318
  const validated = validateEngineeringTelemetryConfig(config);
15473
16319
  return Layer.suspend(() => {
16320
+ const bounded = validated.deliveryBudget === void 0 ? void 0 : cliEngineeringDelivery(validated.deliveryBudget, validated.serviceName === "capxul-cli");
15474
16321
  const endpoints = postHogOtlpEndpoints(validated.host);
15475
16322
  let disabled = false;
15476
16323
  const enabled = () => {
@@ -15484,7 +16331,12 @@ const makeEngineeringTelemetryLayer = (config) => {
15484
16331
  };
15485
16332
  const delivery = {
15486
16333
  ...validated.shutdownTimeout === void 0 ? {} : { shutdownTimeout: validated.shutdownTimeout },
15487
- ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval }
16334
+ ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval },
16335
+ ...bounded === void 0 ? {} : {
16336
+ maxBatchSize: Number.MAX_SAFE_INTEGER,
16337
+ exportInterval: 1e3,
16338
+ shutdownTimeout: validated.serviceName === "capxul-cli" ? 0 : 500
16339
+ }
15488
16340
  };
15489
16341
  const resource = {
15490
16342
  serviceName: validated.serviceName ?? "capxul-sdk",
@@ -15500,22 +16352,38 @@ const makeEngineeringTelemetryLayer = (config) => {
15500
16352
  headers: validated.headers,
15501
16353
  resource,
15502
16354
  ...delivery
15503
- }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
15504
- const logging = Logger.layer([OtlpLogger.make({
16355
+ }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(bounded?.tracer(tracer) ?? tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
16356
+ const ownedLogging = Layer.effect(EngineeringLog, OtlpLogger.make({
15505
16357
  url: endpoints.logs,
15506
16358
  headers: validated.headers,
15507
16359
  resource,
15508
- ...delivery
15509
- }).pipe(Effect.map((logger) => Logger.make((options) => {
15510
- if (enabled()) logger.log(options);
15511
- })))], { mergeWithExisting: true }).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
15512
- const metrics = OtlpMetrics.layer({
16360
+ ...delivery,
16361
+ ...bounded === void 0 ? {} : { excludeLogSpans: true }
16362
+ }).pipe(Effect.map((nativeLogger) => {
16363
+ const logger = bounded?.logger(nativeLogger) ?? nativeLogger;
16364
+ return Logger.make((options) => {
16365
+ if (enabled()) logger.log(options);
16366
+ });
16367
+ }))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
16368
+ const logging = Logger.layer([EngineeringLog], { mergeWithExisting: true }).pipe(Layer.provideMerge(ownedLogging));
16369
+ const metricConfig = {
15513
16370
  url: endpoints.metrics,
15514
16371
  headers: validated.headers,
15515
16372
  resource,
15516
16373
  temporality: "delta",
15517
16374
  ...delivery
15518
- });
16375
+ };
16376
+ const metrics = bounded === void 0 ? OtlpMetrics.layer(metricConfig) : validated.serviceName !== "capxul-cli" ? Layer.empty : Layer.effectDiscard(Effect.gen(function* () {
16377
+ const flusher = yield* OtlpExporter.Flusher;
16378
+ yield* OtlpMetrics.make({
16379
+ ...metricConfig,
16380
+ exportInterval: Number.POSITIVE_INFINITY,
16381
+ shutdownTimeout: 0
16382
+ }).pipe(Effect.provideService(OtlpExporter.Flusher, {
16383
+ ...flusher,
16384
+ register: (run) => flusher.register(bounded.metrics(run))
16385
+ }));
16386
+ })).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
15519
16387
  const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
15520
16388
  const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
15521
16389
  if (validated.producer !== "browser" || !hasBrowserDocument()) return;
@@ -15538,17 +16406,25 @@ const makeEngineeringTelemetryLayer = (config) => {
15538
16406
  }));
15539
16407
  }));
15540
16408
  const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => {
15541
- const selectedTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
15542
- return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => enabled() && request.body._tag !== "Empty" ? response : Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })))));
16409
+ const originalTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
16410
+ const selectedTransport = bounded?.transport(originalTransport, enabled) ?? originalTransport;
16411
+ return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => {
16412
+ if (enabled() && request.body._tag !== "Empty") return response;
16413
+ bounded?.discardBody(request.body);
16414
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })));
16415
+ }));
15543
16416
  })).pipe(Layer.provide(FetchHttpClient.layer));
15544
16417
  const serialization = Layer.effect(OtlpSerialization.OtlpSerialization, Effect.map(OtlpSerialization.OtlpSerialization, (json) => {
15545
16418
  const projected = minimalSerialization(validated);
15546
- return validated.privacy === "minimal" ? projected : {
16419
+ const selected = validated.privacy === "minimal" ? projected : {
15547
16420
  ...json,
15548
16421
  metrics: projected.metrics
15549
16422
  };
16423
+ return bounded?.serialization(selected) ?? selected;
15550
16424
  })).pipe(Layer.provide(OtlpSerialization.layerJson));
15551
- return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
16425
+ const combined = browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
16426
+ const scoped = bounded === void 0 ? combined : combined.pipe(Layer.provideMerge(Layer.effectDiscard(Effect.addFinalizer(() => Effect.sync(bounded.close)))));
16427
+ return validated.deliveryBudget === void 0 ? scoped : scoped.pipe(Layer.provideMerge(Layer.succeed(Metric.MetricRegistry, validated.serviceName === "capxul-cli" ? validated.deliveryBudget.metricRegistry : validated.deliveryBudget.emptyMetricRegistry())));
15552
16428
  });
15553
16429
  };
15554
16430
  //#endregion
@@ -47788,6 +48664,7 @@ function snapshotHostObservability(observability) {
47788
48664
  function hostEngineeringPolicy(observability) {
47789
48665
  const policy = observability?.[HOST_ENGINEERING_POLICY];
47790
48666
  return {
48667
+ ...policy?.engineeringDeliveryBudget === void 0 ? {} : { deliveryBudget: policy.engineeringDeliveryBudget },
47791
48668
  ...policy?.engineeringEnabled === void 0 ? {} : { enabled: policy.engineeringEnabled },
47792
48669
  ...policy?.engineeringPrivacy === void 0 ? {} : { privacy: policy.engineeringPrivacy }
47793
48670
  };
@@ -47856,7 +48733,8 @@ function decodeTransfer(value) {
47856
48733
  token: toAddress(hex(row, "token")),
47857
48734
  from: toAddress(hex(row, "from")),
47858
48735
  to: toAddress(hex(row, "to")),
47859
- amount: big(row, "amount")
48736
+ amount: big(row, "amount"),
48737
+ ..."payment_settlement_id" in row ? { paymentSettlementId: row.payment_settlement_id === null ? null : hex(row, "payment_settlement_id") } : {}
47860
48738
  };
47861
48739
  }
47862
48740
  function decodePayment(value) {
@@ -47942,6 +48820,60 @@ const ACTOR_PARAMS = {
47942
48820
  */
47943
48821
  function ponderQuery(table, input) {
47944
48822
  const safe = assertIndexerPage(input);
48823
+ if (input.eventId !== void 0) {
48824
+ if (table !== "transfer") throw Errors.invalidInput("eventId", "only transfer reads accept a logical event ID");
48825
+ return {
48826
+ 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`,
48827
+ params: [
48828
+ safe,
48829
+ safe,
48830
+ input.eventId,
48831
+ 1
48832
+ ],
48833
+ typings: [
48834
+ "none",
48835
+ "none",
48836
+ "none",
48837
+ "none"
48838
+ ]
48839
+ };
48840
+ }
48841
+ if (input.settlements !== void 0) {
48842
+ if (table !== "payment") throw Errors.invalidInput("settlements", "only Payment reads accept settlement IDs");
48843
+ const params = [
48844
+ safe,
48845
+ safe,
48846
+ ...input.settlements,
48847
+ input.limit
48848
+ ];
48849
+ return {
48850
+ 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}`,
48851
+ params,
48852
+ typings: params.map(() => "none")
48853
+ };
48854
+ }
48855
+ if (input.timeline !== void 0) {
48856
+ if (table !== "transfer") throw Errors.invalidInput("timeline", "only transfer reads have a timeline");
48857
+ const { from, to, before } = input.timeline;
48858
+ const params = [
48859
+ safe,
48860
+ safe,
48861
+ from,
48862
+ to
48863
+ ];
48864
+ const select = HEADS.transfer.slice(0, HEADS.transfer.indexOf(" from \"transfer\"")) + ", \"payment_settlement_id\" from \"activity_transfer\" ";
48865
+ let where = "(\"activity_transfer\".\"from\" = $1 or \"activity_transfer\".\"to\" = $2) and \"activity_transfer\".\"timestamp\" >= $3 and \"activity_transfer\".\"timestamp\" <= $4";
48866
+ if (before !== void 0) {
48867
+ params.push(before.timestamp, before.timestamp, before.id);
48868
+ where += " and (\"activity_transfer\".\"timestamp\" < $5 or (\"activity_transfer\".\"timestamp\" = $6 and \"activity_transfer\".\"id\" < $7))";
48869
+ }
48870
+ params.push(input.limit);
48871
+ return {
48872
+ sql: `${select}where (${where}) order by "activity_transfer"."timestamp" desc, "activity_transfer"."id" desc limit $${params.length}`,
48873
+ params,
48874
+ typings: params.map(() => "none")
48875
+ };
48876
+ }
47945
48877
  const offset = input.offset ?? 0;
47946
48878
  const actors = ACTOR_PARAMS[table];
47947
48879
  const params = [];
@@ -49616,7 +50548,7 @@ function stdioIdentity(env = process.env, options = {}) {
49616
50548
  }
49617
50549
  //#endregion
49618
50550
  //#region package.json
49619
- var version = "4.20.0-beta.1";
50551
+ var version = "4.20.0-beta.3";
49620
50552
  //#endregion
49621
50553
  //#region src/deps.ts
49622
50554
  const NO_IDENTITY = async () => void 0;