@capxul/cli 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.
package/dist/main.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
- import process$1, { exit, platform, versions } from "node:process";
4
- import { Cause, Clock, Config, Console, Context, Crypto, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, FileSystem, Layer, Logger, Metric, Option, Path, Queue, Redacted, Ref, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stdio, Stream, Tracer } from "effect";
3
+ import process$1, { arch, exit, platform, versions } from "node:process";
4
+ import { Cause, Clock, Config, Console, Context, Crypto, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, FileSystem, Layer, LogLevel, Logger, Metric, Option, Path, Queue, Redacted, Ref, References, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stdio, Stream, Tracer } from "effect";
5
5
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
6
6
  import { CliConfig, CliError, Command, Flag, GlobalFlag, Primitive } from "effect/unstable/cli";
7
7
  import * as nc from "node:crypto";
8
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientRequest, HttpClientResponse, HttpTraceContext } from "effect/unstable/http";
8
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, HttpTraceContext } from "effect/unstable/http";
9
9
  import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
10
10
  import { resolve } from "path";
11
11
  import util from "util";
@@ -473,7 +473,12 @@ const CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
473
473
  "authcode",
474
474
  "authorizationcode",
475
475
  "oauthcode",
476
- "codeverifier"
476
+ "codeverifier",
477
+ "devicecode",
478
+ "usercode",
479
+ "continuitytoken",
480
+ "refreshattemptid",
481
+ "revocationtoken"
477
482
  ]);
478
483
  /** Match credential fields by meaning, without hiding token addresses or replay IDs. */
479
484
  function isCredentialField(name, context) {
@@ -2398,6 +2403,8 @@ const COUNTRY_CODE_RE = /^[A-Z]{2}$/;
2398
2403
  const ACCOUNT_ID_RE = /^account_[0-9A-Za-z]+$/;
2399
2404
  const PARTY_ID_RE$1 = /^party_[0-9A-Za-z]+$/;
2400
2405
  const APP_ID_RE = /^app_[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
2406
+ const CLIENT_REQUEST_ID_RE = /^creq_[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
2407
+ const CLIENT_GRANT_ID_RE = /^cgrant_[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
2401
2408
  const TX_HASH_RE = /^0x[0-9a-f]{64}$/i;
2402
2409
  const WEI_RE = /^[0-9]+$/;
2403
2410
  Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
@@ -6531,6 +6538,91 @@ const ContactActionProps = {
6531
6538
  reason_code: OptionalReasonCode
6532
6539
  };
6533
6540
  /** One completed online CLI diagnostic. No command input or provider text. */
6541
+ const CLI_COMMAND_NAMES = [
6542
+ "capxul",
6543
+ "doctor",
6544
+ "telemetry",
6545
+ "telemetry.status",
6546
+ "telemetry.enable",
6547
+ "telemetry.disable",
6548
+ "unknown"
6549
+ ];
6550
+ const CLI_CAPABILITIES = [
6551
+ "cli",
6552
+ "diagnostics",
6553
+ "collection"
6554
+ ];
6555
+ const CLI_INVOCATION_KINDS = [
6556
+ "command",
6557
+ "help",
6558
+ "version",
6559
+ "completion",
6560
+ "parse_refusal",
6561
+ "collection_control"
6562
+ ];
6563
+ const CliRelease = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u));
6564
+ const CliInvocationProps = {
6565
+ ...TelemetryEnvelopeProps,
6566
+ schema_version: Schema.Literal(1),
6567
+ command: Schema.Literals(CLI_COMMAND_NAMES),
6568
+ capability: Schema.Literals(CLI_CAPABILITIES),
6569
+ invocation_kind: Schema.Literals(CLI_INVOCATION_KINDS),
6570
+ invocation_id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u)),
6571
+ cli_identity_kind: Schema.optional(Schema.Literal("anonymous")),
6572
+ anonymous_id: Schema.optional(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u))),
6573
+ occurred_at: Schema.Number,
6574
+ cli_version: CliRelease,
6575
+ sdk_version: CliRelease,
6576
+ release: CliRelease,
6577
+ platform: Schema.Literals([
6578
+ "darwin",
6579
+ "linux",
6580
+ "unknown"
6581
+ ]),
6582
+ architecture: Schema.Literals([
6583
+ "arm64",
6584
+ "x64",
6585
+ "unknown"
6586
+ ]),
6587
+ ci: Schema.Boolean,
6588
+ tty: Schema.Boolean,
6589
+ output_mode: Schema.Literals(["human", "json"])
6590
+ };
6591
+ const CliFirstUsedTelemetryEventSchema = Schema.Struct({
6592
+ name: Schema.Literal("cli_first_used"),
6593
+ props: Schema.Struct(CliInvocationProps)
6594
+ });
6595
+ const CliCommandStartedTelemetryEventSchema = Schema.Struct({
6596
+ name: Schema.Literal("cli_command_started"),
6597
+ props: Schema.Struct({
6598
+ ...CliInvocationProps,
6599
+ invocation_kind: Schema.Literals([
6600
+ "command",
6601
+ "help",
6602
+ "version"
6603
+ ])
6604
+ })
6605
+ });
6606
+ const CliCommandCompletedTelemetryEventSchema = Schema.Struct({
6607
+ name: Schema.Literal("cli_command_completed"),
6608
+ props: Schema.Struct({
6609
+ ...CliInvocationProps,
6610
+ outcome: Schema.Literals([
6611
+ "succeeded",
6612
+ "refused",
6613
+ "failed",
6614
+ "cancelled"
6615
+ ]),
6616
+ duration_ms: Schema.Number,
6617
+ error_code: Schema.optional(Schema.Literals([
6618
+ ...CAPXUL_ERROR_CODES,
6619
+ "CLI_USAGE",
6620
+ "CLI_CONFIG",
6621
+ "CLI_STORAGE",
6622
+ "CLI_DEFECT"
6623
+ ]))
6624
+ })
6625
+ });
6534
6626
  const CliDiagnosticCompletedTelemetryEventSchema = Schema.Struct({
6535
6627
  name: Schema.Literal("cli_diagnostic_completed"),
6536
6628
  props: Schema.Struct({
@@ -6832,6 +6924,9 @@ const TELEMETRY_EVENT_SCHEMAS = {
6832
6924
  }),
6833
6925
  ui_contact_action_completed: UiContactActionCompletedTelemetryEventSchema,
6834
6926
  cli_diagnostic_completed: CliDiagnosticCompletedTelemetryEventSchema,
6927
+ cli_first_used: CliFirstUsedTelemetryEventSchema,
6928
+ cli_command_started: CliCommandStartedTelemetryEventSchema,
6929
+ cli_command_completed: CliCommandCompletedTelemetryEventSchema,
6835
6930
  activity_annotation_saved: ActivityAnnotationSavedTelemetryEventSchema,
6836
6931
  ui_activity_action_completed: UiActivityActionCompletedTelemetryEventSchema,
6837
6932
  auth_otp_requested: AuthOtpRequestedTelemetryEventSchema,
@@ -7540,6 +7635,30 @@ const PAYMENT_DOCUMENT_KINDS = [
7540
7635
  "receipt",
7541
7636
  "withdrawal"
7542
7637
  ];
7638
+ /** Native browser projection, distinct from the issuer's persisted request state. */
7639
+ const CLIENT_AUTHORIZATION_PHASE = {
7640
+ OTP_REQUIRED: "otp_required",
7641
+ REVIEW: "review",
7642
+ PROCESSING: "processing",
7643
+ CONNECTED: "connected",
7644
+ REFUSED: "refused"
7645
+ };
7646
+ CLIENT_AUTHORIZATION_PHASE.OTP_REQUIRED, CLIENT_AUTHORIZATION_PHASE.REVIEW, CLIENT_AUTHORIZATION_PHASE.PROCESSING, CLIENT_AUTHORIZATION_PHASE.CONNECTED, CLIENT_AUTHORIZATION_PHASE.REFUSED;
7647
+ /** Closed native refusals; existing Capxul error codes retain their own meaning. */
7648
+ const CLIENT_AUTHORIZATION_REFUSALS = [
7649
+ "signup_required",
7650
+ "setup_required",
7651
+ "already_signed_up",
7652
+ "identity_mismatch",
7653
+ "invalid_request",
7654
+ "request_expired",
7655
+ "request_denied",
7656
+ "cancelled",
7657
+ "grant_revoked",
7658
+ "grant_expired",
7659
+ "refresh_reused",
7660
+ "revoke_unconfirmed"
7661
+ ];
7543
7662
  //#endregion
7544
7663
  //#region ../../packages/wire/src/financial-ops.ts
7545
7664
  const MINOR_UNIT_STRING_RE = /^[0-9]+$/;
@@ -9241,6 +9360,251 @@ function isSafeField(field, value) {
9241
9360
  return value.length > 0 && value.length <= rule.maxLength && value === value.trim() && !value.includes("://") && !containsSensitiveMaterial(value) && rule.pattern.test(value);
9242
9361
  }
9243
9362
  //#endregion
9363
+ //#region ../../packages/wire/src/client-authorization.ts
9364
+ const NATIVE_CLIENT_ID = "capxul-cli";
9365
+ const NATIVE_CLIENT_SCOPE = "account:read";
9366
+ const NATIVE_CLIENT_LIMITS = {
9367
+ accessMs: 36e5,
9368
+ renewalMs: 2592e6,
9369
+ absoluteMs: 7776e6,
9370
+ requestMs: 6e5,
9371
+ pollSeconds: 5,
9372
+ imageBytes: 5242880
9373
+ };
9374
+ const strict = { parseOptions: { onExcessProperty: "error" } };
9375
+ const text$2 = (max) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(max));
9376
+ const nonNegativeInt = Schema.Number.check(Schema.makeFilter((n) => Number.isSafeInteger(n) && n >= 0));
9377
+ const normalize = (f) => Schema.String.pipe(Schema.decodeTo(Schema.String, {
9378
+ decode: SchemaGetter.transform(f),
9379
+ encode: SchemaGetter.transform((s) => s)
9380
+ }));
9381
+ const ClientRequestIdSchema = Schema.String.pipe(Schema.refine((s) => CLIENT_REQUEST_ID_RE.test(s)));
9382
+ const ClientGrantIdSchema = Schema.String.pipe(Schema.refine((s) => CLIENT_GRANT_ID_RE.test(s)));
9383
+ const authUserId = Schema.String.pipe(Schema.refine((s) => s.length > 0));
9384
+ const keyId = Schema.String.pipe(Schema.refine((s) => s.length > 0));
9385
+ const email = normalize((s) => s.toLowerCase()).pipe(Schema.refine((s) => EMAIL_RE$1.test(s))).check(Schema.isMaxLength(254));
9386
+ const handle = Schema.String.pipe(Schema.refine((s) => HANDLE_RE$1.test(s)));
9387
+ const country = normalize((s) => s.toUpperCase()).pipe(Schema.refine((s) => COUNTRY_CODE_RE.test(s)));
9388
+ const credential = Schema.RedactedFromValue(Schema.String.check(Schema.isMinLength(32), Schema.isMaxLength(8192), Schema.isPattern(/^[A-Za-z0-9._~-]+$/u)), { label: "native authorization credential" });
9389
+ const otp = Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[0-9]{6}$/u)), { label: "OTP" });
9390
+ const scopes = Schema.Tuple([Schema.Literal(NATIVE_CLIENT_SCOPE)]);
9391
+ /** Exact HTTPS origin, no credentials/path/query and no retired cloud deployment. */
9392
+ const NativeHttpsOriginSchema = Schema.String.check(Schema.makeFilter((value) => {
9393
+ try {
9394
+ const url = new URL(value);
9395
+ const host = url.hostname.toLowerCase().replace(/\.$/u, "");
9396
+ 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");
9397
+ } catch {
9398
+ return false;
9399
+ }
9400
+ }, { message: "must be a canonical credential-free HTTPS origin outside retired Convex cloud hosts" }));
9401
+ const NativeClientConfigurationSchema = Schema.Struct({
9402
+ clientId: Schema.Literal(NATIVE_CLIENT_ID),
9403
+ applicationId: AppIdSchema,
9404
+ keyId,
9405
+ environment: Schema.Literals([
9406
+ "development",
9407
+ "staging",
9408
+ "production"
9409
+ ]),
9410
+ keyEnvironment: Schema.Literals(["test", "live"]),
9411
+ issuer: NativeHttpsOriginSchema,
9412
+ verificationOrigin: NativeHttpsOriginSchema
9413
+ }).annotate(strict).check(Schema.makeFilter((c) => c.keyEnvironment === (c.environment === "production" ? "live" : "test"), { message: "key environment must match the configured deployment" })).annotate(strict);
9414
+ const NativeSignupProfileSchema = Schema.Struct({
9415
+ displayName: normalize((s) => s.trim()).check(Schema.isMinLength(1), Schema.isMaxLength(200)),
9416
+ country,
9417
+ handle,
9418
+ payoutAddresses: Schema.optional(Schema.Array(Schema.Struct({
9419
+ chain: Schema.Literals([
9420
+ "evm",
9421
+ "solana",
9422
+ "starknet"
9423
+ ]),
9424
+ address: text$2(256)
9425
+ }).annotate(strict)).check(Schema.isMaxLength(10))),
9426
+ image: Schema.optional(Schema.Struct({
9427
+ mediaType: Schema.Literals([
9428
+ "image/jpeg",
9429
+ "image/png",
9430
+ "image/webp"
9431
+ ]),
9432
+ byteLength: Schema.Number.check(Schema.isInt(), Schema.isBetween({
9433
+ minimum: 1,
9434
+ maximum: NATIVE_CLIENT_LIMITS.imageBytes
9435
+ })),
9436
+ sha256: Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/u))
9437
+ }).annotate(strict))
9438
+ }).annotate(strict);
9439
+ const ClientAuthorizationIntentSchema = Schema.Union([Schema.Struct({ mode: Schema.Literal("login") }).annotate(strict), Schema.Struct({
9440
+ mode: Schema.Literal("signup"),
9441
+ profile: NativeSignupProfileSchema
9442
+ }).annotate(strict)]).annotate(strict);
9443
+ Schema.Struct({
9444
+ clientId: Schema.Literal(NATIVE_CLIENT_ID),
9445
+ email,
9446
+ intent: ClientAuthorizationIntentSchema,
9447
+ waitMs: Schema.Number.check(Schema.isInt(), Schema.isBetween({
9448
+ minimum: 1,
9449
+ maximum: NATIVE_CLIENT_LIMITS.requestMs
9450
+ })),
9451
+ continuityToken: Schema.optional(credential)
9452
+ }).annotate(strict);
9453
+ const request = {
9454
+ requestId: ClientRequestIdSchema,
9455
+ revision: nonNegativeInt,
9456
+ expiresAt: EpochMsSchema
9457
+ };
9458
+ const principal = {
9459
+ authUserId,
9460
+ email
9461
+ };
9462
+ const profileSummary = {
9463
+ displayName: text$2(200),
9464
+ handle,
9465
+ country
9466
+ };
9467
+ Schema.Union([
9468
+ Schema.Struct({
9469
+ ...request,
9470
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.OTP_REQUIRED),
9471
+ email,
9472
+ resendAt: EpochMsSchema
9473
+ }).annotate(strict),
9474
+ Schema.Struct({
9475
+ ...request,
9476
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.REVIEW),
9477
+ ...principal,
9478
+ clientLabel: Schema.Literal("Capxul CLI"),
9479
+ scopes,
9480
+ intent: ClientAuthorizationIntentSchema,
9481
+ setupState: Schema.Literals([
9482
+ "not_required",
9483
+ "new",
9484
+ "resumable"
9485
+ ])
9486
+ }).annotate(strict).check(Schema.makeFilter((v) => v.intent.mode === "login" === (v.setupState === "not_required"), { message: "login review cannot permit account setup" })),
9487
+ Schema.Struct({
9488
+ ...request,
9489
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.PROCESSING),
9490
+ ...principal,
9491
+ purpose: Schema.Literals(["login", "signup"]),
9492
+ step: Schema.Literals([
9493
+ "profile",
9494
+ "wallet",
9495
+ "account",
9496
+ "delivery"
9497
+ ])
9498
+ }).annotate(strict).check(Schema.makeFilter((v) => v.purpose === "signup" || v.step === "delivery", { message: "login may wait for delivery but cannot enter signup processing" })),
9499
+ Schema.Struct({
9500
+ ...request,
9501
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.CONNECTED),
9502
+ ...principal,
9503
+ ...profileSummary,
9504
+ accountId: AccountIdSchema,
9505
+ grantId: ClientGrantIdSchema
9506
+ }).annotate(strict),
9507
+ Schema.Struct({
9508
+ ...request,
9509
+ phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.REFUSED),
9510
+ reason: Schema.Literals(CLIENT_AUTHORIZATION_REFUSALS),
9511
+ code: Schema.Literals(CAPXUL_ERROR_CODES),
9512
+ field: Schema.optional(Schema.Literals([
9513
+ "email",
9514
+ "displayName",
9515
+ "country",
9516
+ "handle",
9517
+ "image",
9518
+ "payoutAddresses"
9519
+ ])),
9520
+ retryable: Schema.Boolean
9521
+ }).annotate(strict)
9522
+ ]).annotate(strict);
9523
+ Schema.Struct({
9524
+ requestId: ClientRequestIdSchema,
9525
+ revision: nonNegativeInt
9526
+ }).annotate(strict);
9527
+ Schema.Struct({
9528
+ requestId: ClientRequestIdSchema,
9529
+ code: otp
9530
+ }).annotate(strict);
9531
+ Schema.Struct({
9532
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
9533
+ grant_type: Schema.Literal("refresh_token"),
9534
+ refresh_token: credential,
9535
+ refreshAttemptId: Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{32,128}$/u)), { label: "refresh attempt" })
9536
+ }).annotate(strict);
9537
+ Schema.Struct({
9538
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
9539
+ applicationId: AppIdSchema,
9540
+ resource: text$2(2048),
9541
+ token_type: Schema.Literal("Bearer"),
9542
+ access_token: credential,
9543
+ refresh_token: credential,
9544
+ scope: Schema.Literal(NATIVE_CLIENT_SCOPE),
9545
+ expires_in: Schema.Number.check(Schema.isInt(), Schema.isBetween({
9546
+ minimum: 1,
9547
+ maximum: NATIVE_CLIENT_LIMITS.accessMs / 1e3
9548
+ })),
9549
+ grantId: ClientGrantIdSchema,
9550
+ ...principal,
9551
+ accountId: AccountIdSchema,
9552
+ issuedAt: EpochMsSchema,
9553
+ grantCreatedAt: EpochMsSchema,
9554
+ accessExpiresAt: EpochMsSchema,
9555
+ renewalExpiresAt: EpochMsSchema,
9556
+ absoluteExpiresAt: EpochMsSchema,
9557
+ revision: nonNegativeInt
9558
+ }).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);
9559
+ /** One endpoint mapping; consumers cannot substitute callback or resource URLs. */
9560
+ function nativeClientEndpoints(config) {
9561
+ const resource = `${config.issuer}/api/auth/native`;
9562
+ return {
9563
+ resource,
9564
+ device_authorization_endpoint: `${resource}/device/code`,
9565
+ token_endpoint: `${resource}/token`,
9566
+ revocation_endpoint: `${resource}/revoke`,
9567
+ session_endpoint: `${resource}/session`,
9568
+ verification_uri: `${config.verificationOrigin}/connect`
9569
+ };
9570
+ }
9571
+ Schema.Struct({
9572
+ protocol: Schema.Literal("capxul.client-authorization"),
9573
+ version: Schema.Literal(1),
9574
+ configuration: NativeClientConfigurationSchema,
9575
+ scopes_supported: scopes,
9576
+ grant_types_supported: Schema.Tuple([Schema.Literal("urn:ietf:params:oauth:grant-type:device_code"), Schema.Literal("refresh_token")]),
9577
+ resource: text$2(2048),
9578
+ device_authorization_endpoint: text$2(2048),
9579
+ token_endpoint: text$2(2048),
9580
+ revocation_endpoint: text$2(2048),
9581
+ session_endpoint: text$2(2048),
9582
+ verification_uri: text$2(2048)
9583
+ }).annotate(strict).check(Schema.makeFilter((m) => {
9584
+ const expected = nativeClientEndpoints(m.configuration);
9585
+ 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;
9586
+ }, { message: "native endpoints must match their configured issuer and verification origin" })).annotate(strict);
9587
+ Schema.Struct({
9588
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
9589
+ grant_type: Schema.Literal("urn:ietf:params:oauth:grant-type:device_code"),
9590
+ device_code: credential
9591
+ }).annotate(strict);
9592
+ Schema.Struct({
9593
+ requestId: ClientRequestIdSchema,
9594
+ device_code: credential,
9595
+ user_code: Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[A-Z0-9]{4}-[A-Z0-9]{4}$/u)), { label: "device user code" }),
9596
+ continuityToken: credential,
9597
+ verification_uri: text$2(2048),
9598
+ expires_in: Schema.Number.check(Schema.isInt(), Schema.isBetween({
9599
+ minimum: 1,
9600
+ maximum: NATIVE_CLIENT_LIMITS.requestMs / 1e3
9601
+ })),
9602
+ interval: Schema.Number.check(Schema.isInt(), Schema.isBetween({
9603
+ minimum: NATIVE_CLIENT_LIMITS.pollSeconds,
9604
+ maximum: NATIVE_CLIENT_LIMITS.requestMs / 1e3
9605
+ }))
9606
+ }).annotate(strict);
9607
+ //#endregion
9244
9608
  //#region ../../packages/sdk/src/internal/invocation-observation.ts
9245
9609
  const INVOCATION_OBSERVATION = Symbol("capxul.invocation-observation");
9246
9610
  const FAILURE_INVOCATION_SNAPSHOT = Symbol("capxul.failure-invocation-snapshot");
@@ -11120,6 +11484,9 @@ const moneyExecutionContract = {
11120
11484
  //#region ../../packages/sdk/src/ports/indexer-read.ts
11121
11485
  /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
11122
11486
  const INDEXER_MAX_LIMIT = 1e3;
11487
+ function validTime(value) {
11488
+ return Number.isSafeInteger(value) && value >= 0 && value <= Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
11489
+ }
11123
11490
  const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
11124
11491
  /**
11125
11492
  * Refuse a page the indexer would refuse, before any implementation spends a
@@ -11127,12 +11494,21 @@ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
11127
11494
  * against the hermetic adapter cannot pass on input production rejects. Throws
11128
11495
  * a `CapxulError`; callers convert it to an `IndexerReadError`.
11129
11496
  */
11497
+ function assertEventLookup(input, id) {
11498
+ 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");
11499
+ }
11130
11500
  function assertIndexerPage(input) {
11131
11501
  const safe = String(input.safe).toLowerCase();
11132
11502
  if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
11133
11503
  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}`);
11134
11504
  const offset = input.offset ?? 0;
11135
11505
  if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
11506
+ const timeline = input.timeline;
11507
+ if (input.eventId !== void 0) assertEventLookup(input, input.eventId);
11508
+ 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");
11509
+ if (timeline !== void 0) {
11510
+ 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");
11511
+ }
11136
11512
  return safe;
11137
11513
  }
11138
11514
  var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
@@ -11162,20 +11538,6 @@ function directionFor(safe, transfer) {
11162
11538
  if (from === safe && to === safe) return "self";
11163
11539
  return to === safe ? "in" : "out";
11164
11540
  }
11165
- /**
11166
- * The Transfer a protocol Payment moved is the same money as its `payment`
11167
- * row. One actor sees one item for it, so the Transfer is claimed and does not
11168
- * become a second row. Another transfer in the same transaction that this
11169
- * payment did not move stays unclaimed and keeps its own row.
11170
- */
11171
- function claimedTransferIds(transfers, payments) {
11172
- const claimed = /* @__PURE__ */ new Set();
11173
- for (const payment of payments) {
11174
- 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());
11175
- if (match !== void 0) claimed.add(match.id);
11176
- }
11177
- return claimed;
11178
- }
11179
11541
  function withAnnotation(item, annotation) {
11180
11542
  return annotation?.counterpartyLabel === void 0 ? item : {
11181
11543
  ...item,
@@ -11186,10 +11548,14 @@ function withAnnotation(item, annotation) {
11186
11548
  * An indexed settlement confirms the intent. A missing row can be outside
11187
11549
  * this page or still backfilling; only the correction owner can prove rollback.
11188
11550
  */
11189
- function confirmPayment(intent, chain) {
11190
- if (chain !== void 0) return {
11551
+ function confirmPayment(intent, chain, expected) {
11552
+ if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
11191
11553
  ...intent,
11192
11554
  status: "settled",
11555
+ ...intent.execution === void 0 ? {} : { execution: {
11556
+ status: "settled",
11557
+ retryRequired: false
11558
+ } },
11193
11559
  receipt: {
11194
11560
  chainId: chain.chainId,
11195
11561
  transactionHash: chain.txHash
@@ -11198,16 +11564,22 @@ function confirmPayment(intent, chain) {
11198
11564
  };
11199
11565
  return intent;
11200
11566
  }
11567
+ function matchesPaymentFact(chain, expected) {
11568
+ 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;
11569
+ }
11201
11570
  /**
11202
11571
  * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
11203
11572
  * denominate its asset. The indexer serves only configured assets, so a row
11204
11573
  * the registry does not know means the SDK and that deployment disagree; it is
11205
11574
  * dropped rather than shown with a guessed currency.
11206
11575
  */
11576
+ function isActivityVisible(item) {
11577
+ return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
11578
+ }
11207
11579
  function transferItem(safe, transfer, annotation) {
11208
11580
  const asset = configuredMoneyAssetById(transfer.assetId);
11209
11581
  if (asset === null) return null;
11210
- const direction = directionFor(safe, transfer);
11582
+ const direction = directionFor(safe.toLowerCase(), transfer);
11211
11583
  const counterparty = direction === "in" ? transfer.from : transfer.to;
11212
11584
  const at = blockMillis(transfer);
11213
11585
  return withAnnotation({
@@ -11229,47 +11601,6 @@ function transferItem(safe, transfer, annotation) {
11229
11601
  updatedAt: at
11230
11602
  }, annotation);
11231
11603
  }
11232
- /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
11233
- function compareItems(left, right) {
11234
- if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
11235
- if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
11236
- if (left.id === right.id) return 0;
11237
- return left.id < right.id ? 1 : -1;
11238
- }
11239
- function stitchActivity(input) {
11240
- const safe = input.safe.toLowerCase();
11241
- const chainBySettlement = /* @__PURE__ */ new Map();
11242
- for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
11243
- const chainByPaymentId = /* @__PURE__ */ new Map();
11244
- for (const link of input.links) {
11245
- const row = chainBySettlement.get(link.settlementId.toLowerCase());
11246
- if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
11247
- }
11248
- const annotations = /* @__PURE__ */ new Map();
11249
- for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
11250
- const items = [];
11251
- const claiming = [];
11252
- for (const intent of input.intents) {
11253
- if (intent.kind !== "payment") continue;
11254
- const chain = chainByPaymentId.get(intent.id);
11255
- if (chain !== void 0) claiming.push(chain);
11256
- items.push(withAnnotation(confirmPayment(intent, chain), annotations.get(`payment:${intent.id}`)));
11257
- }
11258
- const claimed = claimedTransferIds(input.transfers, claiming);
11259
- for (const transfer of input.transfers) {
11260
- if (claimed.has(transfer.id)) continue;
11261
- const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
11262
- if (item !== null) items.push(item);
11263
- }
11264
- return items.sort(compareItems);
11265
- }
11266
- /** The reference ids one stitched page needs its Convex overlay for. */
11267
- function overlayReferences(items) {
11268
- return items.map((item) => ({
11269
- kind: item.kind,
11270
- id: item.id
11271
- }));
11272
- }
11273
11604
  /**
11274
11605
  * Money in, money out and the pending share, one bucket per currency and
11275
11606
  * precision. Two assets that peg to the same currency at different precision
@@ -11288,7 +11619,7 @@ function summarizeItems(items) {
11288
11619
  };
11289
11620
  const raw = rawUnits(item.amount.value, item.amount.decimals);
11290
11621
  if (item.kind === "payment" && item.status !== "settled") {
11291
- if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed") bucket.pendingIn += raw;
11622
+ if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
11292
11623
  } else if (item.direction === "in") bucket.in += raw;
11293
11624
  else if (item.direction === "out") bucket.out += raw;
11294
11625
  buckets.set(key, bucket);
@@ -11311,179 +11642,412 @@ function rawUnits(value, decimals) {
11311
11642
  }
11312
11643
  //#endregion
11313
11644
  //#region ../../packages/sdk/src/surface/activity-read.ts
11314
- /** How many Convex intent rows one page reads for its labels and pending rows. */
11315
- const INTENT_WINDOW = 100;
11316
- /** movement/annotations:overlay bounds each identifier array to 200. */
11645
+ const BATCH = 100;
11317
11646
  const MAX_OVERLAY_IDS = 200;
11318
- /** The window a summary aggregates over. The indexer's own page bound. */
11319
- const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
11320
- function parseCursor(value) {
11321
- if (value === void 0) return 0;
11322
- try {
11323
- const parsed = JSON.parse(value);
11324
- return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
11325
- } catch {
11326
- return 0;
11327
- }
11647
+ function actorKey(actor) {
11648
+ return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
11649
+ }
11650
+ function scopeKey(scope) {
11651
+ return `${scope.principalAccountId ?? "legacy"}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
11652
+ }
11653
+ function selectionKey(params) {
11654
+ const filter = params?.filter;
11655
+ return JSON.stringify([
11656
+ params?.range?.from ?? null,
11657
+ params?.range?.to ?? null,
11658
+ filter?.kind ?? null,
11659
+ filter?.direction ?? null,
11660
+ filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
11661
+ ]);
11662
+ }
11663
+ function validString(value, max) {
11664
+ return typeof value === "string" && value.length <= max;
11328
11665
  }
11329
- function inRange(item, range) {
11330
- if (range?.from !== void 0 && item.createdAt < range.from) return false;
11331
- return range?.to === void 0 || item.createdAt <= range.to;
11666
+ function validPosition(position) {
11667
+ 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);
11668
+ }
11669
+ function parseCursor(value, scope, selection) {
11670
+ if (value === void 0) return null;
11671
+ if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
11672
+ const cursor = JSON.parse(value);
11673
+ 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");
11674
+ return cursor;
11675
+ }
11676
+ function requireParams(params) {
11677
+ const limit = params?.limit ?? 25;
11678
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > BATCH) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
11679
+ 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");
11680
+ const f = params?.filter;
11681
+ if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
11682
+ "in",
11683
+ "out",
11684
+ "self"
11685
+ ].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");
11332
11686
  }
11333
- /** The caller's filter applied to the merged page. The join owns the order. */
11334
11687
  function matchesFilter(item, filter) {
11335
- if (filter === void 0) return true;
11336
- if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
11337
- if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
11338
- if (filter.status === void 0) return true;
11339
- return item.kind === "payment" && filter.status.includes(item.status);
11688
+ 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));
11340
11689
  }
11341
11690
  function overlay(deps, actor, controls, input) {
11342
11691
  const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
11343
- const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, index) => index * MAX_OVERLAY_IDS);
11692
+ const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
11344
11693
  return Effect.forEach(offsets, (offset) => deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
11345
11694
  ...actor === void 0 ? {} : { actor },
11346
11695
  ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
11347
11696
  ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
11348
- } })), { concurrency: 2 }).pipe(Effect.map((pages) => ({
11349
- ...pages[0],
11350
- links: pages.flatMap((page) => page.links),
11351
- annotations: pages.flatMap((page) => page.annotations)
11352
- })));
11697
+ } })), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
11698
+ const first = pages[0];
11699
+ if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
11700
+ return Effect.succeed({
11701
+ ...first,
11702
+ links: pages.flatMap((page) => page.links),
11703
+ annotations: pages.flatMap((page) => page.annotations),
11704
+ ...pages.every((page) => page.payments !== void 0) ? { payments: pages.flatMap((page) => page.payments ?? []) } : {}
11705
+ });
11706
+ }));
11353
11707
  }
11354
11708
  function convexPage(deps, actor, controls, input) {
11355
11709
  return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
11356
- ...actor === void 0 ? {} : { actor },
11357
- ...input.cursor === void 0 ? {} : { cursor: input.cursor },
11358
- ...input.limit === void 0 ? {} : { limit: input.limit },
11359
- ...input.range === void 0 ? {} : { range: input.range }
11710
+ ...input,
11711
+ actor
11360
11712
  } }));
11361
11713
  }
11362
- /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
11363
- function chainRows(deps, safe, limit) {
11364
- const page = {
11365
- safe,
11366
- limit: Math.min(limit, INDEXER_MAX_LIMIT)
11367
- };
11368
- return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
11369
- transfers,
11370
- payments
11371
- })), Effect.catch(() => Effect.succeed(null)));
11714
+ function chainRead(deps, table, input) {
11715
+ return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
11716
+ if (error.publicError.details?.reason === "scope-refused" || ![
11717
+ "NETWORK_ERROR",
11718
+ "RATE_LIMITED",
11719
+ "PROVIDER_ERROR",
11720
+ "CAPABILITY_UNAVAILABLE",
11721
+ "TIMEOUT"
11722
+ ].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
11723
+ return Effect.succeed(null);
11724
+ }));
11372
11725
  }
11373
- /** The stitched, filtered rows of one window, newest first. */
11374
- function stitchedWindow(deps, actor, controls, window, params) {
11726
+ function fallbackPage(deps, scope, controls, params, state, degraded) {
11375
11727
  return Effect.gen(function* () {
11376
- const scope = yield* overlay(deps, actor, controls, {});
11377
- const convex = yield* convexPage(deps, actor, controls, {
11378
- limit: INTENT_WINDOW,
11379
- ...params.range === void 0 ? {} : { range: params.range }
11728
+ const page = yield* convexPage(deps, scope.actor, controls, {
11729
+ limit: params?.limit ?? 25,
11730
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
11731
+ range: {
11732
+ ...params?.range?.from === void 0 ? {} : { from: params.range.from },
11733
+ to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
11734
+ },
11735
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
11380
11736
  });
11381
- const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
11382
- if (rows === null || scope.safe === null) {
11383
- if (scope.safe !== null) yield* Effect.annotateCurrentSpan({
11384
- read_source: "convex",
11385
- read_warning: "indexer-unavailable"
11386
- });
11387
- return {
11388
- items: convex.items.filter((item) => matchesFilter(item, params.filter)),
11389
- stitched: false,
11390
- ...scope.safe === null ? {} : { readWarning: "indexer-unavailable" }
11391
- };
11737
+ const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
11738
+ kind: item.kind,
11739
+ id: item.id
11740
+ })) });
11741
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
11742
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
11743
+ yield* Effect.annotateCurrentSpan({
11744
+ read_source: "convex",
11745
+ ...degraded ? { read_warning: "indexer-unavailable" } : {}
11746
+ });
11747
+ return {
11748
+ ...page,
11749
+ items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
11750
+ checkpoint: state.checkpoint,
11751
+ cursor: page.cursor === null ? null : JSON.stringify({
11752
+ ...state,
11753
+ source: "fallback",
11754
+ paymentCursor: page.cursor,
11755
+ degraded
11756
+ }),
11757
+ ...degraded ? { readWarning: "indexer-unavailable" } : {}
11758
+ };
11759
+ });
11760
+ }
11761
+ function boundedRange(range, checkpoint) {
11762
+ return {
11763
+ ...range?.from === void 0 ? {} : { from: range.from },
11764
+ to: Math.min(checkpoint, range?.to ?? checkpoint)
11765
+ };
11766
+ }
11767
+ function unavailablePage(deps, scope, controls, params, state, first) {
11768
+ if (first) return fallbackPage(deps, scope, controls, params, state, true);
11769
+ return Effect.annotateCurrentSpan({
11770
+ read_source: "indexer",
11771
+ read_warning: "indexer-unavailable"
11772
+ }).pipe(Effect.as({
11773
+ items: [],
11774
+ cursor: null,
11775
+ checkpoint: state.checkpoint,
11776
+ observedAt: Date.now(),
11777
+ readWarning: "indexer-unavailable"
11778
+ }));
11779
+ }
11780
+ function readTransfers(deps, safe, range, state, wanted, first) {
11781
+ const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
11782
+ const to = Math.floor(range.to / 1e3);
11783
+ if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
11784
+ return chainRead(deps, "transfer", {
11785
+ safe: toAddress(safe),
11786
+ limit: wanted ? BATCH : 1,
11787
+ timeline: {
11788
+ from,
11789
+ to,
11790
+ ...state.transferBefore === null ? {} : { before: state.transferBefore }
11392
11791
  }
11393
- yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
11394
- const decoration = yield* overlay(deps, actor, controls, {
11395
- references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
11792
+ });
11793
+ }
11794
+ function readPayments(deps, scope, controls, range, state, wanted) {
11795
+ const done = state.paymentDone || !wanted;
11796
+ if (state.paymentPending.length > 0 || done) return Effect.succeed({
11797
+ items: [],
11798
+ pending: state.paymentPending,
11799
+ cursor: state.paymentCursor,
11800
+ done
11801
+ });
11802
+ return convexPage(deps, scope.actor, controls, {
11803
+ limit: BATCH,
11804
+ range,
11805
+ filter: { kind: "payment" },
11806
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
11807
+ }).pipe(Effect.map((page) => ({
11808
+ items: page.items,
11809
+ pending: page.items.map((item) => item.id),
11810
+ cursor: page.cursor,
11811
+ done: page.cursor === null
11812
+ })));
11813
+ }
11814
+ function readPaymentFacts(deps, safe, settlements) {
11815
+ const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
11816
+ return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
11817
+ safe: toAddress(safe),
11818
+ limit: 50,
11819
+ settlements: settlements.slice(offset, offset + 50)
11820
+ }), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
11821
+ }
11822
+ function decoratePage(deps, scope, controls, transfers, page, range) {
11823
+ return Effect.gen(function* () {
11824
+ const decoration = yield* overlay(deps, scope.actor, controls, {
11825
+ references: [...page.pending.map((id) => ({
11826
+ kind: "payment",
11827
+ id
11828
+ })), ...transfers.map((row) => ({
11396
11829
  kind: "movement",
11397
11830
  id: row.id
11398
11831
  }))],
11399
- settlementIds: rows.payments.map((row) => row.settlementId)
11832
+ settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
11400
11833
  });
11401
- const intents = [...convex.items];
11402
- const missing = new Set(decoration.links.map((link) => link.paymentId));
11403
- for (const item of intents) if (item.kind === "payment") missing.delete(item.id);
11404
- let cursor = convex.cursor;
11405
- while (missing.size > 0 && cursor !== null) {
11406
- const page = yield* convexPage(deps, actor, controls, {
11407
- limit: INTENT_WINDOW,
11408
- cursor,
11409
- ...params.range === void 0 ? {} : { range: params.range }
11410
- });
11411
- for (const item of page.items) if (item.kind === "payment" && missing.delete(item.id)) intents.push(item);
11412
- cursor = page.cursor;
11834
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
11835
+ const byId = new Map([...page.items, ...decoration.payments ?? []].map((item) => [item.id, item]));
11836
+ const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
11837
+ const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
11838
+ if (facts === null) return null;
11839
+ const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
11840
+ const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
11841
+ fact: bySettlement.get(link.settlementId),
11842
+ expected: link.expected
11843
+ }]));
11844
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
11845
+ return {
11846
+ payments: page.pending.flatMap((id) => {
11847
+ const item = byId.get(id);
11848
+ if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
11849
+ const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
11850
+ return isActivityVisible(confirmed) ? [confirmed] : [];
11851
+ }),
11852
+ annotations,
11853
+ linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
11854
+ };
11855
+ });
11856
+ }
11857
+ function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
11858
+ const { payments, annotations, linked } = decoration;
11859
+ let pi = 0;
11860
+ let ti = 0;
11861
+ const items = [];
11862
+ const transferEnd = !wantsTransfers || transfers.length < BATCH;
11863
+ let transferBefore = state.transferBefore;
11864
+ while (items.length < limit) {
11865
+ if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
11866
+ const payment = payments[pi];
11867
+ const row = transfers[ti];
11868
+ if (payment === void 0 && row === void 0) break;
11869
+ if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
11870
+ pi += 1;
11871
+ if (matchesFilter(payment, filter)) items.push(payment);
11872
+ } else if (row !== void 0) {
11873
+ ti += 1;
11874
+ transferBefore = {
11875
+ timestamp: Number(row.timestamp),
11876
+ id: row.id
11877
+ };
11878
+ if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
11879
+ const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
11880
+ if (item !== null && matchesFilter(item, filter)) items.push(item);
11413
11881
  }
11414
- const older = intents.slice(convex.items.length);
11415
- const olderDecoration = older.length === 0 ? [] : (yield* overlay(deps, actor, controls, { references: overlayReferences(older) })).annotations;
11882
+ }
11883
+ return {
11884
+ items,
11885
+ next: {
11886
+ ...state,
11887
+ paymentCursor: page.cursor,
11888
+ paymentDone: page.done,
11889
+ paymentPending: payments.slice(pi).map((item) => item.id),
11890
+ transferBefore,
11891
+ transferDone: ti === transfers.length && transferEnd
11892
+ }
11893
+ };
11894
+ }
11895
+ function indexedPage(deps, scope, controls, params, state, first) {
11896
+ return Effect.gen(function* () {
11897
+ const range = boundedRange(params?.range, state.checkpoint);
11898
+ const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
11899
+ const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
11900
+ if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
11901
+ const transfers = wantsTransfers ? found : [];
11902
+ const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
11903
+ const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
11904
+ if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
11905
+ const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
11906
+ const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
11907
+ if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
11908
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
11416
11909
  return {
11417
- items: stitchActivity({
11418
- safe: scope.safe,
11419
- transfers: rows.transfers,
11420
- payments: rows.payments,
11421
- intents,
11422
- links: decoration.links,
11423
- annotations: [...decoration.annotations, ...olderDecoration]
11424
- }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
11425
- stitched: true
11910
+ items,
11911
+ cursor,
11912
+ checkpoint: state.checkpoint,
11913
+ observedAt: Date.now()
11426
11914
  };
11427
11915
  });
11428
11916
  }
11429
11917
  function readActivityPage(deps, actor, controls, params) {
11430
- const limit = params?.limit ?? 25;
11431
- const offset = parseCursor(params?.cursor);
11432
- return stitchedWindow(deps, actor, controls, offset + limit + 1, {
11433
- ...params?.range === void 0 ? {} : { range: params.range },
11434
- ...params?.filter === void 0 ? {} : { filter: params.filter }
11435
- }).pipe(Effect.map(({ items, stitched, readWarning }) => {
11436
- const observedAt = Date.now();
11437
- if (!stitched) return {
11438
- items: items.slice(0, limit),
11918
+ return Effect.gen(function* () {
11919
+ yield* Effect.try({
11920
+ try: () => requireParams(params),
11921
+ catch: (cause) => ({ publicError: cause })
11922
+ });
11923
+ const scope = yield* overlay(deps, actor, controls, {});
11924
+ const selection = selectionKey(params);
11925
+ const cursor = yield* Effect.try({
11926
+ try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
11927
+ catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
11928
+ });
11929
+ const state = cursor ?? {
11930
+ version: 1,
11931
+ scope: scopeKey(scope),
11932
+ selection,
11933
+ checkpoint: Date.now(),
11934
+ source: "indexed",
11935
+ paymentCursor: null,
11936
+ paymentDone: false,
11937
+ paymentPending: [],
11938
+ transferBefore: null,
11939
+ transferDone: false,
11940
+ degraded: false
11941
+ };
11942
+ const range = boundedRange(params?.range, state.checkpoint);
11943
+ if (range.from !== void 0 && range.from > range.to) return {
11944
+ items: [],
11439
11945
  cursor: null,
11440
- checkpoint: observedAt,
11441
- observedAt,
11442
- ...readWarning === void 0 ? {} : { readWarning }
11946
+ checkpoint: state.checkpoint,
11947
+ observedAt: Date.now()
11443
11948
  };
11949
+ if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
11950
+ if (scope.payments === void 0) return yield* unavailablePage(deps, scope, controls, params, state, cursor === null);
11951
+ return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
11952
+ });
11953
+ }
11954
+ function readActivitySummary(deps, actor, controls, params) {
11955
+ return Effect.gen(function* () {
11956
+ const totals = /* @__PURE__ */ new Map();
11957
+ let cursor;
11958
+ let count = 0;
11959
+ let readWarning;
11960
+ do {
11961
+ const page = yield* readActivityPage(deps, actor, controls, {
11962
+ limit: BATCH,
11963
+ ...cursor === void 0 ? {} : { cursor },
11964
+ ...params?.window === void 0 ? {} : { range: params.window }
11965
+ });
11966
+ count += page.items.length;
11967
+ readWarning ??= page.readWarning;
11968
+ for (const total of summarizeItems(page.items)) {
11969
+ const key = `${total.currency}:${total.decimals}`;
11970
+ const old = totals.get(key);
11971
+ totals.set(key, old === void 0 ? total : {
11972
+ ...total,
11973
+ in: String(BigInt(old.in) + BigInt(total.in)),
11974
+ out: String(BigInt(old.out) + BigInt(total.out)),
11975
+ net: String(BigInt(old.net) + BigInt(total.net)),
11976
+ pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
11977
+ });
11978
+ }
11979
+ cursor = page.cursor ?? void 0;
11980
+ } while (cursor !== void 0);
11444
11981
  return {
11445
- items: items.slice(offset, offset + limit),
11446
- cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
11447
- checkpoint: observedAt,
11448
- observedAt
11982
+ window: {
11983
+ from: params?.window?.from ?? null,
11984
+ to: params?.window?.to ?? null
11985
+ },
11986
+ totals: [...totals.values()],
11987
+ count,
11988
+ observedAt: Date.now(),
11989
+ ...readWarning === void 0 ? {} : { readWarning }
11449
11990
  };
11450
- }));
11991
+ });
11451
11992
  }
11452
- function readActivitySummary(deps, actor, controls, params) {
11453
- return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items, readWarning }) => ({
11454
- window: {
11455
- from: params?.window?.from ?? null,
11456
- to: params?.window?.to ?? null
11457
- },
11458
- totals: summarizeItems(items),
11459
- count: items.length,
11460
- observedAt: Date.now(),
11461
- ...readWarning === void 0 ? {} : { readWarning }
11462
- })));
11993
+ /** An indexed Movement has no Convex materialization. Read its exact native row. */
11994
+ function readIndexedMovement(deps, actor, controls, eventId) {
11995
+ return Effect.gen(function* () {
11996
+ const scope = yield* overlay(deps, actor, controls, { references: [{
11997
+ kind: "movement",
11998
+ id: eventId
11999
+ }] });
12000
+ if (scope.safe === null) return null;
12001
+ const safe = scope.safe.toLowerCase();
12002
+ const row = (yield* deps.indexer.read("transfer", {
12003
+ safe: toAddress(safe),
12004
+ limit: 1,
12005
+ eventId
12006
+ }).pipe(Effect.catch((error) => Effect.fail({ publicError: error.publicError }))))[0];
12007
+ if (row === void 0) return null;
12008
+ const blockNumber = Number(row.blockNumber);
12009
+ 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") });
12010
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
12011
+ return {
12012
+ kind: "movement",
12013
+ movementId: row.id,
12014
+ viewer: scope.actor,
12015
+ annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
12016
+ evidence: {
12017
+ chainId: row.chainId,
12018
+ contractAddress: row.token,
12019
+ tokenAddress: row.token,
12020
+ transactionHash: row.txHash,
12021
+ blockNumber,
12022
+ blockHash: row.blockHash,
12023
+ inclusion: "included",
12024
+ verification: "verified",
12025
+ canonicalState: "finalized",
12026
+ transferLogIndex: row.logIndex,
12027
+ source: row.from,
12028
+ destination: row.to,
12029
+ rawAmount: row.amount.toString(),
12030
+ link: {
12031
+ movementId: row.id,
12032
+ depositPaymentId: null
12033
+ }
12034
+ }
12035
+ };
12036
+ });
11463
12037
  }
11464
- /**
11465
- * Tell the caller when the indexed page for this Safe changed.
11466
- *
11467
- * The transport is the indexer's one SSE connection, wired in the Ponder
11468
- * adapter under `activity.subscribe`. A reactive snapshot is not a product
11469
- * event, so nothing here emits telemetry: the adapter reports a teardown, and
11470
- * the caller re-reads.
11471
- */
11472
12038
  function subscribeActivity(deps, actor, controls, onChange) {
11473
12039
  return Effect.gen(function* () {
11474
12040
  const scope = yield* overlay(deps, actor, controls, {});
11475
12041
  if (scope.safe === null) return () => {};
11476
- const page = {
11477
- safe: scope.safe,
11478
- limit: INTENT_WINDOW
11479
- };
11480
12042
  let delivered = false;
11481
- const notify = () => {
11482
- if (delivered) onChange();
11483
- delivered = true;
11484
- };
11485
- return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
11486
- if (snapshot.status === "ok") notify();
12043
+ return yield* deps.indexer.subscribe("transfer", {
12044
+ safe: toAddress(scope.safe),
12045
+ limit: BATCH
12046
+ }, (snapshot) => {
12047
+ if (snapshot.status === "ok") {
12048
+ if (delivered) onChange();
12049
+ delivered = true;
12050
+ }
11487
12051
  if (snapshot.status === "error") {
11488
12052
  delivered = true;
11489
12053
  onChange();
@@ -11493,7 +12057,7 @@ function subscribeActivity(deps, actor, controls, onChange) {
11493
12057
  }
11494
12058
  //#endregion
11495
12059
  //#region ../../packages/sdk/package.json
11496
- var version$1 = "4.20.0-beta.1";
12060
+ var version$1 = "4.20.0-beta.3";
11497
12061
  //#endregion
11498
12062
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
11499
12063
  /** Fixed fallback for failures that have no safe message. */
@@ -13059,7 +13623,10 @@ function makeFinancialOpsMethods(deps) {
13059
13623
  ...params?.limit === void 0 ? {} : { limit: params.limit },
13060
13624
  ...params?.range === void 0 ? {} : { range: params.range },
13061
13625
  ...params?.filter === void 0 ? {} : { filter: params.filter }
13062
- } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
13626
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
13627
+ ...page,
13628
+ items: page.items.filter(isActivityVisible)
13629
+ })), Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
13063
13630
  },
13064
13631
  summary: (params, options) => {
13065
13632
  const controls = deps.invocationControls?.(options) ?? options;
@@ -13080,7 +13647,10 @@ function makeFinancialOpsMethods(deps) {
13080
13647
  actor: scope,
13081
13648
  referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
13082
13649
  });
13083
- }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
13650
+ }).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" ? {
13651
+ kind: "organization",
13652
+ orgId: actor.orgId
13653
+ } : void 0, controls, reference.id) : deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
13084
13654
  input: {
13085
13655
  kind: reference.kind,
13086
13656
  id: reference.id,
@@ -13547,6 +14117,381 @@ async function recoverRawDigestSigner(input) {
13547
14117
  }
13548
14118
  }
13549
14119
  //#endregion
14120
+ //#region ../../packages/observability/src/engineering-delivery.ts
14121
+ /** Fixed admission limits for one CLI invocation and its SDK client. */
14122
+ const CLI_ENGINEERING_LIMITS = Object.freeze({
14123
+ records: 80,
14124
+ ordinaryRecords: 64,
14125
+ recordBytes: 16384,
14126
+ requests: 5,
14127
+ ordinaryRequests: 2,
14128
+ requestBytes: 1572864,
14129
+ metricSeries: 16,
14130
+ requestTimeoutMs: 1e3
14131
+ });
14132
+ const metricNames = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
14133
+ const metricValues = {
14134
+ command: CLI_COMMAND_NAMES,
14135
+ capability: CLI_CAPABILITIES,
14136
+ invocation_kind: CLI_INVOCATION_KINDS,
14137
+ outcome: [
14138
+ "succeeded",
14139
+ "refused",
14140
+ "failed",
14141
+ "cancelled"
14142
+ ],
14143
+ capxul_env: [
14144
+ "development",
14145
+ "staging",
14146
+ "production",
14147
+ "local"
14148
+ ],
14149
+ unit: [
14150
+ "1",
14151
+ "s",
14152
+ "ms"
14153
+ ]
14154
+ };
14155
+ /** Only this bounded registry is supplied to the CLI's metric producers. */
14156
+ var CliMetricRegistry = class extends Map {
14157
+ dropped;
14158
+ enabled;
14159
+ constructor(dropped, enabled = true) {
14160
+ super();
14161
+ this.dropped = dropped;
14162
+ this.enabled = enabled;
14163
+ }
14164
+ set(key, entry) {
14165
+ if (!this.enabled || !metricNames.has(entry.id) || !this.has(key) && this.size >= CLI_ENGINEERING_LIMITS.metricSeries) {
14166
+ this.dropped();
14167
+ return this;
14168
+ }
14169
+ const labels = Object.entries(entry.attributes ?? {});
14170
+ const state = entry.hooks.get(Context.empty());
14171
+ const buckets = typeof state === "object" && state !== null && "buckets" in state ? state.buckets : void 0;
14172
+ if (!["Counter", "Histogram"].includes(entry.type) || (entry.description?.length ?? 0) > 128 || labels.length > 6 || labels.some(([name, value]) => !metricValues[name]?.includes(String(value))) || entry.type === "Histogram" && (!Array.isArray(buckets) || buckets.length > 32)) {
14173
+ this.dropped();
14174
+ return this;
14175
+ }
14176
+ return super.set(key, entry);
14177
+ }
14178
+ };
14179
+ /** Shared, closure-owned admission state; no queue or mutable counters are exposed. */
14180
+ function makeCliEngineeringBudget() {
14181
+ let records = 0, peakRecords = 0, activeRequests = 0, requestBytes = 0;
14182
+ let peakRequests = 0, peakRequestBytes = 0, dropped = 0;
14183
+ const drop = () => {
14184
+ dropped += 1;
14185
+ };
14186
+ const metricRegistry = new CliMetricRegistry(drop);
14187
+ return Object.freeze({
14188
+ metricRegistry,
14189
+ emptyMetricRegistry: () => new CliMetricRegistry(drop, false),
14190
+ drop,
14191
+ admitRecord(terminal) {
14192
+ if (records >= (terminal ? CLI_ENGINEERING_LIMITS.records : CLI_ENGINEERING_LIMITS.ordinaryRecords)) {
14193
+ drop();
14194
+ return;
14195
+ }
14196
+ records += 1;
14197
+ peakRecords = Math.max(peakRecords, records);
14198
+ let released = false;
14199
+ return () => {
14200
+ if (!released) {
14201
+ released = true;
14202
+ records -= 1;
14203
+ }
14204
+ };
14205
+ },
14206
+ admitRequest(bytes, terminal) {
14207
+ if (bytes > CLI_ENGINEERING_LIMITS.requestBytes || activeRequests >= (terminal ? CLI_ENGINEERING_LIMITS.requests : CLI_ENGINEERING_LIMITS.ordinaryRequests)) {
14208
+ drop();
14209
+ return;
14210
+ }
14211
+ activeRequests += 1;
14212
+ requestBytes += bytes;
14213
+ peakRequests = Math.max(peakRequests, activeRequests);
14214
+ peakRequestBytes = Math.max(peakRequestBytes, requestBytes);
14215
+ return () => {
14216
+ activeRequests -= 1;
14217
+ requestBytes -= bytes;
14218
+ };
14219
+ },
14220
+ snapshot() {
14221
+ return {
14222
+ records,
14223
+ reservedRecordBytes: records * CLI_ENGINEERING_LIMITS.recordBytes,
14224
+ peakRecords,
14225
+ peakReservedRecordBytes: peakRecords * CLI_ENGINEERING_LIMITS.recordBytes,
14226
+ activeRequests,
14227
+ requestBytes,
14228
+ peakRequests,
14229
+ peakRequestBytes,
14230
+ dropped,
14231
+ metricSeries: metricRegistry.size
14232
+ };
14233
+ }
14234
+ });
14235
+ }
14236
+ const text$1 = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
14237
+ const attributes = (input, limit = 32) => {
14238
+ const out = {};
14239
+ let count = 0;
14240
+ let bytes = 0;
14241
+ try {
14242
+ for (const key in input) {
14243
+ if (count++ >= limit) break;
14244
+ if (key.length > 64) continue;
14245
+ const value = Object.getOwnPropertyDescriptor(input, key)?.value;
14246
+ const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
14247
+ if (scalar === void 0) continue;
14248
+ bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
14249
+ if (bytes > (limit >= 24 ? 2e3 : 300)) break;
14250
+ out[key] = scalar;
14251
+ }
14252
+ } catch {}
14253
+ return out;
14254
+ };
14255
+ const terminalName = (name) => name === "cli.command.completed" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
14256
+ /** Native exporters retain their serialization and scheduling; this owns admission only. */
14257
+ function cliEngineeringDelivery(budget, ownsMetrics) {
14258
+ const closed = Deferred.makeUnsafe();
14259
+ let isClosed = false;
14260
+ let metricsRunning = false;
14261
+ const logs = [];
14262
+ const spans = /* @__PURE__ */ new Map();
14263
+ const terminalBodies = /* @__PURE__ */ new WeakSet();
14264
+ const bodyLeases = /* @__PURE__ */ new WeakMap();
14265
+ const pendingBodies = /* @__PURE__ */ new Set();
14266
+ const holdBody = (body, releases) => {
14267
+ if (releases.length === 0) return;
14268
+ const lease = {
14269
+ started: false,
14270
+ release: () => {
14271
+ if (!pendingBodies.delete(lease)) return;
14272
+ bodyLeases.delete(body);
14273
+ for (const release of releases) release();
14274
+ }
14275
+ };
14276
+ bodyLeases.set(body, lease);
14277
+ pendingBodies.add(lease);
14278
+ };
14279
+ const discardBody = (body) => bodyLeases.get(body)?.release();
14280
+ const drainLogs = () => {
14281
+ for (const release of logs.splice(0)) release();
14282
+ };
14283
+ const close = () => {
14284
+ isClosed = true;
14285
+ Deferred.doneUnsafe(closed, Effect.void);
14286
+ for (const lease of pendingBodies) if (!lease.started) lease.release();
14287
+ drainLogs();
14288
+ for (const release of spans.values()) release();
14289
+ spans.clear();
14290
+ if (ownsMetrics) budget.metricRegistry.clear();
14291
+ };
14292
+ return {
14293
+ close,
14294
+ discardBody,
14295
+ metrics(run) {
14296
+ return Effect.suspend(() => {
14297
+ if (isClosed || metricsRunning) return Effect.void;
14298
+ metricsRunning = true;
14299
+ return run.pipe(Effect.ensuring(Effect.sync(() => {
14300
+ metricsRunning = false;
14301
+ })));
14302
+ });
14303
+ },
14304
+ logger(delegate) {
14305
+ return Logger.make((options) => {
14306
+ if (isClosed) return;
14307
+ let release;
14308
+ try {
14309
+ const message = text$1(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
14310
+ release = budget.admitRecord(terminalName(message));
14311
+ if (release === void 0) return;
14312
+ const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
14313
+ const fiber = Object.create(options.fiber);
14314
+ Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
14315
+ logs.push(release);
14316
+ delegate.log({
14317
+ ...options,
14318
+ message,
14319
+ cause: Cause.empty,
14320
+ fiber
14321
+ });
14322
+ } catch {
14323
+ if (release !== void 0) {
14324
+ const index = logs.indexOf(release);
14325
+ if (index !== -1) logs.splice(index, 1);
14326
+ release();
14327
+ }
14328
+ budget.drop();
14329
+ }
14330
+ });
14331
+ },
14332
+ tracer(delegate) {
14333
+ return Tracer.make({
14334
+ span(options) {
14335
+ const name = text$1(options.name);
14336
+ const release = isClosed ? void 0 : budget.admitRecord(terminalName(name));
14337
+ const spanOptions = {
14338
+ ...options,
14339
+ name,
14340
+ annotations: Context.empty(),
14341
+ links: []
14342
+ };
14343
+ const span = release === void 0 ? new Tracer.NativeSpan({
14344
+ ...spanOptions,
14345
+ sampled: false
14346
+ }) : delegate.span(spanOptions);
14347
+ if (release !== void 0) spans.set(span.spanId, release);
14348
+ const wrapped = Object.create(span);
14349
+ let fields = 0, events = 0, links = 0, attributeBytes = 0;
14350
+ let ended = false;
14351
+ Object.defineProperties(wrapped, {
14352
+ attribute: { value: (key, value) => {
14353
+ if (fields >= 32 && !span.attributes.has(key)) {
14354
+ budget.drop();
14355
+ return;
14356
+ }
14357
+ const safe = attributes({ [key]: value }, 1);
14358
+ if (!Object.hasOwn(safe, key)) return;
14359
+ const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
14360
+ const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
14361
+ if (attributeBytes - previous + next > 2e3) {
14362
+ budget.drop();
14363
+ return;
14364
+ }
14365
+ attributeBytes += next - previous;
14366
+ if (!span.attributes.has(key)) fields += 1;
14367
+ span.attribute(key, safe[key]);
14368
+ } },
14369
+ event: { value: (eventName, time, attrs) => {
14370
+ if (events++ >= 2) {
14371
+ budget.drop();
14372
+ return;
14373
+ }
14374
+ span.event(text$1(eventName), time, attributes(attrs ?? {}, 4));
14375
+ } },
14376
+ addLinks: { value: (items) => {
14377
+ for (const item of items.slice(0, Math.max(0, 2 - links))) {
14378
+ if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
14379
+ budget.drop();
14380
+ continue;
14381
+ }
14382
+ span.addLinks([{
14383
+ span: Tracer.externalSpan({
14384
+ traceId: item.span.traceId,
14385
+ spanId: item.span.spanId,
14386
+ sampled: item.span.sampled
14387
+ }),
14388
+ attributes: attributes(item.attributes, 4)
14389
+ }]);
14390
+ links += 1;
14391
+ }
14392
+ } },
14393
+ end: { value: (time, exit) => {
14394
+ if (ended) return;
14395
+ ended = true;
14396
+ span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
14397
+ } }
14398
+ });
14399
+ wrapped.addLinks(options.links ?? []);
14400
+ return wrapped;
14401
+ },
14402
+ ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
14403
+ });
14404
+ },
14405
+ serialization(delegate) {
14406
+ return {
14407
+ logs(data) {
14408
+ const releases = logs.splice(0);
14409
+ try {
14410
+ const body = delegate.logs(data);
14411
+ if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => l.body?.stringValue === "cli.command.completed")))) terminalBodies.add(body);
14412
+ holdBody(body, releases);
14413
+ return body;
14414
+ } catch (error) {
14415
+ for (const release of releases) release();
14416
+ throw error;
14417
+ }
14418
+ },
14419
+ traces(data) {
14420
+ const releases = [];
14421
+ for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
14422
+ const release = spans.get(span.spanId);
14423
+ if (release !== void 0) releases.push(release);
14424
+ spans.delete(span.spanId);
14425
+ }
14426
+ try {
14427
+ const body = delegate.traces(data);
14428
+ if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) terminalBodies.add(body);
14429
+ holdBody(body, releases);
14430
+ return body;
14431
+ } catch (error) {
14432
+ for (const release of releases) release();
14433
+ throw error;
14434
+ }
14435
+ },
14436
+ metrics(data) {
14437
+ const body = delegate.metrics(data);
14438
+ terminalBodies.add(body);
14439
+ return body;
14440
+ }
14441
+ };
14442
+ },
14443
+ transport(client, enabled = () => true) {
14444
+ const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
14445
+ if (enabled()) return response;
14446
+ budget.drop();
14447
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
14448
+ })).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
14449
+ let received = 0;
14450
+ return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
14451
+ received += chunk.byteLength;
14452
+ if (received > 65536) throw new Error("Engineering response limit exceeded");
14453
+ })).pipe(Effect.catchTag("HttpClientError", (error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error)));
14454
+ }))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
14455
+ times: 1,
14456
+ schedule: Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
14457
+ if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
14458
+ const header = input.reason.response.headers["retry-after"];
14459
+ const numeric = Number(header);
14460
+ const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
14461
+ return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
14462
+ })))
14463
+ }));
14464
+ return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
14465
+ if (isClosed) {
14466
+ discardBody(request.body);
14467
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
14468
+ }
14469
+ const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
14470
+ if (request.body._tag === "Empty") {
14471
+ discardBody(request.body);
14472
+ return Effect.succeed(droppedResponse());
14473
+ }
14474
+ const bytes = request.body.contentLength;
14475
+ const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, terminalBodies.has(request.body));
14476
+ if (release === void 0) {
14477
+ discardBody(request.body);
14478
+ return Effect.succeed(droppedResponse());
14479
+ }
14480
+ const lease = bodyLeases.get(request.body);
14481
+ if (lease !== void 0) lease.started = true;
14482
+ 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(() => {
14483
+ if (Option.isNone(result)) budget.drop();
14484
+ })), Effect.catchCause(() => Effect.sync(() => {
14485
+ budget.drop();
14486
+ })), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
14487
+ release();
14488
+ discardBody(request.body);
14489
+ })));
14490
+ }));
14491
+ }
14492
+ };
14493
+ }
14494
+ //#endregion
13550
14495
  //#region ../../packages/observability/src/engineering.ts
13551
14496
  const ENGINEERING_CAPXUL_ENVS = [
13552
14497
  "development",
@@ -13721,10 +14666,13 @@ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
13721
14666
  const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
13722
14667
  "correlation_id",
13723
14668
  "journey_id",
13724
- "invocation_id"
14669
+ "invocation_id",
14670
+ "posthogDistinctId"
13725
14671
  ]);
13726
14672
  const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
13727
14673
  "command",
14674
+ "capability",
14675
+ "invocation_kind",
13728
14676
  "outcome",
13729
14677
  "capxul_env",
13730
14678
  "unit"
@@ -13732,7 +14680,7 @@ const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
13732
14680
  const MINIMAL_METRIC_NAMES = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
13733
14681
  const traceId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{32}$/u.test(value);
13734
14682
  const spanId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{16}$/u.test(value);
13735
- const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value)) ? value : "capxul.engineering";
14683
+ 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";
13736
14684
  const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
13737
14685
  ["outcome", [
13738
14686
  "succeeded",
@@ -13745,7 +14693,10 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
13745
14693
  ["failure_mode", FAILURE_MODES$1],
13746
14694
  ["capxul_env", ENGINEERING_CAPXUL_ENVS],
13747
14695
  ["producer", ["browser", "server"]],
13748
- ["command", ["doctor"]],
14696
+ ["command", CLI_COMMAND_NAMES],
14697
+ ["capability", CLI_CAPABILITIES],
14698
+ ["invocation_kind", CLI_INVOCATION_KINDS],
14699
+ ["cli_identity_kind", ["anonymous"]],
13749
14700
  ["output_mode", ["human", "json"]],
13750
14701
  ["unit", [
13751
14702
  "1",
@@ -13963,10 +14914,17 @@ function minimalSerialization(config) {
13963
14914
  }
13964
14915
  /** Attempt to drain all engineering signals; completion does not prove ingestion. */
13965
14916
  const flushEngineeringTelemetry = Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
14917
+ var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
14918
+ /** Deliver an owned engineering record without changing the caller's console verbosity. */
14919
+ const logEngineeringInfo = (message, attributes) => {
14920
+ const record = Effect.logInfo(message).pipe(Effect.annotateLogs(attributes));
14921
+ return Effect.flatMap(LogLevel.isEnabled("Info"), (visible) => visible ? record : Effect.flatMap(EngineeringLog, (logger) => record.pipe(Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([logger])), Effect.provideService(References.MinimumLogLevel, "All"))));
14922
+ };
13966
14923
  /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
13967
14924
  const makeEngineeringTelemetryLayer = (config) => {
13968
14925
  const validated = validateEngineeringTelemetryConfig(config);
13969
14926
  return Layer.suspend(() => {
14927
+ const bounded = validated.deliveryBudget === void 0 ? void 0 : cliEngineeringDelivery(validated.deliveryBudget, validated.serviceName === "capxul-cli");
13970
14928
  const endpoints = postHogOtlpEndpoints(validated.host);
13971
14929
  let disabled = false;
13972
14930
  const enabled = () => {
@@ -13980,7 +14938,12 @@ const makeEngineeringTelemetryLayer = (config) => {
13980
14938
  };
13981
14939
  const delivery = {
13982
14940
  ...validated.shutdownTimeout === void 0 ? {} : { shutdownTimeout: validated.shutdownTimeout },
13983
- ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval }
14941
+ ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval },
14942
+ ...bounded === void 0 ? {} : {
14943
+ maxBatchSize: Number.MAX_SAFE_INTEGER,
14944
+ exportInterval: 1e3,
14945
+ shutdownTimeout: validated.serviceName === "capxul-cli" ? 0 : 500
14946
+ }
13984
14947
  };
13985
14948
  const resource = {
13986
14949
  serviceName: validated.serviceName ?? "capxul-sdk",
@@ -13996,22 +14959,38 @@ const makeEngineeringTelemetryLayer = (config) => {
13996
14959
  headers: validated.headers,
13997
14960
  resource,
13998
14961
  ...delivery
13999
- }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
14000
- const logging = Logger.layer([OtlpLogger.make({
14962
+ }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(bounded?.tracer(tracer) ?? tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
14963
+ const ownedLogging = Layer.effect(EngineeringLog, OtlpLogger.make({
14001
14964
  url: endpoints.logs,
14002
14965
  headers: validated.headers,
14003
14966
  resource,
14004
- ...delivery
14005
- }).pipe(Effect.map((logger) => Logger.make((options) => {
14006
- if (enabled()) logger.log(options);
14007
- })))], { mergeWithExisting: true }).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
14008
- const metrics = OtlpMetrics.layer({
14967
+ ...delivery,
14968
+ ...bounded === void 0 ? {} : { excludeLogSpans: true }
14969
+ }).pipe(Effect.map((nativeLogger) => {
14970
+ const logger = bounded?.logger(nativeLogger) ?? nativeLogger;
14971
+ return Logger.make((options) => {
14972
+ if (enabled()) logger.log(options);
14973
+ });
14974
+ }))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
14975
+ const logging = Logger.layer([EngineeringLog], { mergeWithExisting: true }).pipe(Layer.provideMerge(ownedLogging));
14976
+ const metricConfig = {
14009
14977
  url: endpoints.metrics,
14010
14978
  headers: validated.headers,
14011
14979
  resource,
14012
14980
  temporality: "delta",
14013
14981
  ...delivery
14014
- });
14982
+ };
14983
+ const metrics = bounded === void 0 ? OtlpMetrics.layer(metricConfig) : validated.serviceName !== "capxul-cli" ? Layer.empty : Layer.effectDiscard(Effect.gen(function* () {
14984
+ const flusher = yield* OtlpExporter.Flusher;
14985
+ yield* OtlpMetrics.make({
14986
+ ...metricConfig,
14987
+ exportInterval: Number.POSITIVE_INFINITY,
14988
+ shutdownTimeout: 0
14989
+ }).pipe(Effect.provideService(OtlpExporter.Flusher, {
14990
+ ...flusher,
14991
+ register: (run) => flusher.register(bounded.metrics(run))
14992
+ }));
14993
+ })).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
14015
14994
  const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
14016
14995
  const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
14017
14996
  if (validated.producer !== "browser" || !hasBrowserDocument()) return;
@@ -14034,17 +15013,25 @@ const makeEngineeringTelemetryLayer = (config) => {
14034
15013
  }));
14035
15014
  }));
14036
15015
  const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => {
14037
- const selectedTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
14038
- return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => enabled() && request.body._tag !== "Empty" ? response : Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })))));
15016
+ const originalTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
15017
+ const selectedTransport = bounded?.transport(originalTransport, enabled) ?? originalTransport;
15018
+ return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => {
15019
+ if (enabled() && request.body._tag !== "Empty") return response;
15020
+ bounded?.discardBody(request.body);
15021
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })));
15022
+ }));
14039
15023
  })).pipe(Layer.provide(FetchHttpClient.layer));
14040
15024
  const serialization = Layer.effect(OtlpSerialization.OtlpSerialization, Effect.map(OtlpSerialization.OtlpSerialization, (json) => {
14041
15025
  const projected = minimalSerialization(validated);
14042
- return validated.privacy === "minimal" ? projected : {
15026
+ const selected = validated.privacy === "minimal" ? projected : {
14043
15027
  ...json,
14044
15028
  metrics: projected.metrics
14045
15029
  };
15030
+ return bounded?.serialization(selected) ?? selected;
14046
15031
  })).pipe(Layer.provide(OtlpSerialization.layerJson));
14047
- return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
15032
+ const combined = browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
15033
+ const scoped = bounded === void 0 ? combined : combined.pipe(Layer.provideMerge(Layer.effectDiscard(Effect.addFinalizer(() => Effect.sync(bounded.close)))));
15034
+ return validated.deliveryBudget === void 0 ? scoped : scoped.pipe(Layer.provideMerge(Layer.succeed(Metric.MetricRegistry, validated.serviceName === "capxul-cli" ? validated.deliveryBudget.metricRegistry : validated.deliveryBudget.emptyMetricRegistry())));
14048
15035
  });
14049
15036
  };
14050
15037
  //#endregion
@@ -47563,6 +48550,7 @@ function postHogObservability(client, options = {}) {
47563
48550
  product: postHogProductTelemetry(policy)
47564
48551
  };
47565
48552
  Object.defineProperty(module, HOST_ENGINEERING_POLICY, { value: Object.freeze({
48553
+ ...options.engineeringDeliveryBudget === void 0 ? {} : { engineeringDeliveryBudget: options.engineeringDeliveryBudget },
47566
48554
  ...options.engineeringEnabled === void 0 ? {} : { engineeringEnabled: options.engineeringEnabled },
47567
48555
  ...options.engineeringPrivacy === void 0 ? {} : { engineeringPrivacy: options.engineeringPrivacy }
47568
48556
  }) });
@@ -47600,6 +48588,7 @@ function isPromiseLike(value) {
47600
48588
  function hostEngineeringPolicy(observability) {
47601
48589
  const policy = observability?.[HOST_ENGINEERING_POLICY];
47602
48590
  return {
48591
+ ...policy?.engineeringDeliveryBudget === void 0 ? {} : { deliveryBudget: policy.engineeringDeliveryBudget },
47603
48592
  ...policy?.engineeringEnabled === void 0 ? {} : { enabled: policy.engineeringEnabled },
47604
48593
  ...policy?.engineeringPrivacy === void 0 ? {} : { privacy: policy.engineeringPrivacy }
47605
48594
  };
@@ -47668,7 +48657,8 @@ function decodeTransfer(value) {
47668
48657
  token: toAddress(hex(row, "token")),
47669
48658
  from: toAddress(hex(row, "from")),
47670
48659
  to: toAddress(hex(row, "to")),
47671
- amount: big(row, "amount")
48660
+ amount: big(row, "amount"),
48661
+ ..."payment_settlement_id" in row ? { paymentSettlementId: row.payment_settlement_id === null ? null : hex(row, "payment_settlement_id") } : {}
47672
48662
  };
47673
48663
  }
47674
48664
  function decodePayment(value) {
@@ -47754,6 +48744,60 @@ const ACTOR_PARAMS = {
47754
48744
  */
47755
48745
  function ponderQuery(table, input) {
47756
48746
  const safe = assertIndexerPage(input);
48747
+ if (input.eventId !== void 0) {
48748
+ if (table !== "transfer") throw Errors.invalidInput("eventId", "only transfer reads accept a logical event ID");
48749
+ return {
48750
+ 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`,
48751
+ params: [
48752
+ safe,
48753
+ safe,
48754
+ input.eventId,
48755
+ 1
48756
+ ],
48757
+ typings: [
48758
+ "none",
48759
+ "none",
48760
+ "none",
48761
+ "none"
48762
+ ]
48763
+ };
48764
+ }
48765
+ if (input.settlements !== void 0) {
48766
+ if (table !== "payment") throw Errors.invalidInput("settlements", "only Payment reads accept settlement IDs");
48767
+ const params = [
48768
+ safe,
48769
+ safe,
48770
+ ...input.settlements,
48771
+ input.limit
48772
+ ];
48773
+ return {
48774
+ 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}`,
48775
+ params,
48776
+ typings: params.map(() => "none")
48777
+ };
48778
+ }
48779
+ if (input.timeline !== void 0) {
48780
+ if (table !== "transfer") throw Errors.invalidInput("timeline", "only transfer reads have a timeline");
48781
+ const { from, to, before } = input.timeline;
48782
+ const params = [
48783
+ safe,
48784
+ safe,
48785
+ from,
48786
+ to
48787
+ ];
48788
+ const select = HEADS.transfer.slice(0, HEADS.transfer.indexOf(" from \"transfer\"")) + ", \"payment_settlement_id\" from \"activity_transfer\" ";
48789
+ let where = "(\"activity_transfer\".\"from\" = $1 or \"activity_transfer\".\"to\" = $2) and \"activity_transfer\".\"timestamp\" >= $3 and \"activity_transfer\".\"timestamp\" <= $4";
48790
+ if (before !== void 0) {
48791
+ params.push(before.timestamp, before.timestamp, before.id);
48792
+ where += " and (\"activity_transfer\".\"timestamp\" < $5 or (\"activity_transfer\".\"timestamp\" = $6 and \"activity_transfer\".\"id\" < $7))";
48793
+ }
48794
+ params.push(input.limit);
48795
+ return {
48796
+ sql: `${select}where (${where}) order by "activity_transfer"."timestamp" desc, "activity_transfer"."id" desc limit $${params.length}`,
48797
+ params,
48798
+ typings: params.map(() => "none")
48799
+ };
48800
+ }
47757
48801
  const offset = input.offset ?? 0;
47758
48802
  const actors = ACTOR_PARAMS[table];
47759
48803
  const params = [];
@@ -48759,6 +49803,82 @@ function captureException(telemetry, error, context) {
48759
49803
  }).pipe(Effect.flatten), () => Effect.void);
48760
49804
  }
48761
49805
  //#endregion
49806
+ //#region src/platform/observation-identity.ts
49807
+ const uuid = Schema.String.check(Schema.isPattern(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u));
49808
+ const person = Schema.String.check(Schema.isPattern(/^[^\s]{1,256}$/u));
49809
+ const binding = Schema.Struct({
49810
+ authUserId: person,
49811
+ eventId: uuid
49812
+ });
49813
+ const epoch = Schema.Struct({
49814
+ id: uuid,
49815
+ binding: Schema.NullOr(binding)
49816
+ });
49817
+ const ObservationIdentity = Schema.Struct({
49818
+ version: Schema.Literal(1),
49819
+ firstUse: Schema.Struct({
49820
+ eventId: uuid,
49821
+ observedAt: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
49822
+ }),
49823
+ epoch
49824
+ });
49825
+ const ObservationIdentityChange = Schema.Union([
49826
+ Schema.Struct({ kind: Schema.Literal("acquire") }),
49827
+ Schema.Struct({
49828
+ kind: Schema.Literal("bind"),
49829
+ expectedEpoch: uuid,
49830
+ authUserId: person
49831
+ }),
49832
+ Schema.Struct({
49833
+ kind: Schema.Literal("rotate"),
49834
+ expectedEpoch: uuid
49835
+ })
49836
+ ]);
49837
+ /** Observation bookkeeping only. The caller must obtain verified person facts from AUTH. */
49838
+ function transitionObservationIdentity(previous, change, fresh) {
49839
+ if (change.kind !== "acquire" && previous?.epoch.id !== change.expectedEpoch) return {
49840
+ state: previous,
49841
+ result: { status: "stale" }
49842
+ };
49843
+ let state = previous ?? {
49844
+ version: 1,
49845
+ firstUse: {
49846
+ eventId: fresh.eventId,
49847
+ observedAt: fresh.observedAt
49848
+ },
49849
+ epoch: {
49850
+ id: fresh.epochId,
49851
+ binding: null
49852
+ }
49853
+ };
49854
+ if (change.kind === "rotate" || change.kind === "acquire" && state.epoch.binding !== null) state = {
49855
+ ...state,
49856
+ epoch: {
49857
+ id: fresh.epochId,
49858
+ binding: null
49859
+ }
49860
+ };
49861
+ else if (change.kind === "bind" && state.epoch.binding?.authUserId !== change.authUserId) state = {
49862
+ ...state,
49863
+ epoch: {
49864
+ id: state.epoch.binding === null ? state.epoch.id : fresh.epochId,
49865
+ binding: {
49866
+ authUserId: change.authUserId,
49867
+ eventId: fresh.eventId
49868
+ }
49869
+ }
49870
+ };
49871
+ return {
49872
+ state,
49873
+ result: {
49874
+ status: "ready",
49875
+ firstUse: state.firstUse,
49876
+ firstUseClaimed: previous === null,
49877
+ epoch: state.epoch
49878
+ }
49879
+ };
49880
+ }
49881
+ //#endregion
48762
49882
  //#region src/platform/state-files.ts
48763
49883
  const Settings = Schema.Struct({
48764
49884
  version: Schema.Literal(1),
@@ -48902,11 +50022,12 @@ const withLock = (file, body) => Effect.scoped(Effect.gen(function* () {
48902
50022
  }));
48903
50023
  return yield* body(path.join(lock, `${owner}.data`));
48904
50024
  }));
48905
- const replace = (file, content, validate) => Effect.gen(function* () {
50025
+ const replace = (file, update) => Effect.gen(function* () {
48906
50026
  const fs = yield* FileSystem.FileSystem;
48907
- if (new TextEncoder().encode(content).length > 65536) return yield* Effect.fail(fail("Corrupt"));
48908
50027
  return yield* withLock(file, (temporary) => Effect.gen(function* () {
48909
- yield* validate;
50028
+ const { content, value } = yield* update;
50029
+ if (content === null) return value;
50030
+ if (new TextEncoder().encode(content).length > 65536) return yield* Effect.fail(fail("Corrupt"));
48910
50031
  const handle = yield* fs.open(temporary, {
48911
50032
  flag: "wx",
48912
50033
  mode: 384
@@ -48918,6 +50039,7 @@ const replace = (file, content, validate) => Effect.gen(function* () {
48918
50039
  const path = yield* Path.Path;
48919
50040
  yield* (yield* fs.open(path.dirname(file))).sync;
48920
50041
  }));
50042
+ return value;
48921
50043
  }));
48922
50044
  });
48923
50045
  const setCollectionEnabled = (file, enabled) => Effect.gen(function* () {
@@ -48926,9 +50048,33 @@ const setCollectionEnabled = (file, enabled) => Effect.gen(function* () {
48926
50048
  version: 1,
48927
50049
  collection: { enabled }
48928
50050
  };
48929
- yield* replace(location, JSON.stringify(settings), readSettings(location));
50051
+ yield* replace(location, readSettings(location).pipe(Effect.as({
50052
+ content: JSON.stringify(settings),
50053
+ value: void 0
50054
+ })));
48930
50055
  return settings;
48931
50056
  }).pipe(Effect.mapError(storageError));
50057
+ const changeObservationIdentity = (directory, input) => Effect.gen(function* () {
50058
+ const change = yield* Schema.decodeUnknownEffect(ObservationIdentityChange, { onExcessProperty: "error" })(input).pipe(Effect.mapError(() => fail("Corrupt")));
50059
+ const path = yield* Path.Path;
50060
+ const location = yield* locate(path.join(directory, "observation.json"), true);
50061
+ return yield* replace(location, Effect.gen(function* () {
50062
+ const content = yield* readFile(location);
50063
+ const previous = content === null ? null : yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ObservationIdentity), { onExcessProperty: "error" })(content).pipe(Effect.mapError(() => fail("Corrupt")));
50064
+ const crypto = yield* Crypto.Crypto;
50065
+ const next = transitionObservationIdentity(previous, change, {
50066
+ epochId: yield* crypto.randomUUIDv4,
50067
+ eventId: yield* crypto.randomUUIDv4,
50068
+ observedAt: yield* Clock.currentTimeMillis
50069
+ });
50070
+ return {
50071
+ content: next.state === previous ? null : JSON.stringify(next.state),
50072
+ value: next.result
50073
+ };
50074
+ }));
50075
+ }).pipe(Effect.mapError(storageError));
50076
+ /** Call only for eligible collection. This never infers AUTH identity from stored state. */
50077
+ const acquireObservationIdentity = (directory) => changeObservationIdentity(directory, { kind: "acquire" });
48932
50078
  //#endregion
48933
50079
  //#region src/platform/collection-policy.ts
48934
50080
  const decode = Schema.decodeUnknownOption(Schema.fromJsonString(Settings), { onExcessProperty: "error" });
@@ -49038,6 +50184,8 @@ function parsedCommand(cause) {
49038
50184
  }
49039
50185
  //#endregion
49040
50186
  //#region src/runtime/config.ts
50187
+ const defaultHost = "https://us.i.posthog.com";
50188
+ const defaultProjectToken = Redacted.make("phc_BZuAVofrkNXzwpJkuDG7Rdfurir45fj2dudQvJZk847e");
49041
50189
  const optionalText = (name) => Config.option(Config.string(name));
49042
50190
  function httpOrigin(value, field) {
49043
50191
  return Effect.try({
@@ -49051,14 +50199,12 @@ function httpOrigin(value, field) {
49051
50199
  catch: () => configurationFailure(`The ${field} URL must be a valid approved origin.`)
49052
50200
  });
49053
50201
  }
49054
- const loadConfig = Effect.gen(function* () {
50202
+ const loadObservationConfig = Effect.gen(function* () {
49055
50203
  const path = yield* Path.Path;
49056
50204
  const values = yield* Config.all({
49057
50205
  cliHome: optionalText("CAPXUL_CLI_HOME"),
49058
50206
  home: optionalText("HOME"),
49059
50207
  xdg: optionalText("XDG_CONFIG_HOME"),
49060
- key: Config.option(Config.redacted("CAPXUL_PUBLISHABLE_KEY")),
49061
- bootstrap: Config.string("CAPXUL_BOOTSTRAP_URL").pipe(Config.withDefault("https://api.staging.capxul.com")),
49062
50208
  host: optionalText("CAPXUL_POSTHOG_HOST"),
49063
50209
  token: Config.option(Config.redacted("CAPXUL_POSTHOG_PROJECT_TOKEN")),
49064
50210
  disabled: Config.boolean("CAPXUL_TELEMETRY_DISABLED").pipe(Config.withDefault(false))
@@ -49072,19 +50218,29 @@ const loadConfig = Effect.gen(function* () {
49072
50218
  else return yield* Effect.fail(configurationFailure("Set HOME or CAPXUL_CLI_HOME."));
49073
50219
  }
49074
50220
  const settingsPath = path.join(path.resolve(directory), "settings.json");
49075
- const bootstrapBaseUrl = yield* httpOrigin(values.bootstrap, "bootstrap");
49076
- const posthogHost = Option.isSome(values.host) ? yield* httpOrigin(values.host.value, "PostHog") : void 0;
50221
+ const host = Option.getOrUndefined(values.host) ?? defaultHost;
49077
50222
  return {
49078
50223
  settingsPath,
49079
- publishableKey: Option.getOrUndefined(values.key),
49080
- bootstrapBaseUrl,
49081
50224
  environment: "staging",
49082
- posthogHost,
49083
- projectToken: Option.getOrUndefined(values.token),
50225
+ posthogHost: host === void 0 ? void 0 : yield* httpOrigin(host, "PostHog"),
50226
+ projectToken: Option.getOrUndefined(values.token) ?? defaultProjectToken,
49084
50227
  forcedDisabled: values.disabled,
49085
50228
  collectionEnabled: collectionPolicy(settingsPath, values.disabled)
49086
50229
  };
49087
50230
  });
50231
+ const loadConfig = Effect.gen(function* () {
50232
+ const observation = yield* loadObservationConfig;
50233
+ const values = yield* Config.all({
50234
+ key: Config.option(Config.redacted("CAPXUL_PUBLISHABLE_KEY")),
50235
+ bootstrap: Config.string("CAPXUL_BOOTSTRAP_URL").pipe(Config.withDefault("https://api.staging.capxul.com"))
50236
+ }).pipe(Effect.mapError(() => configurationFailure("CLI environment configuration is invalid.")));
50237
+ const bootstrapBaseUrl = yield* httpOrigin(values.bootstrap, "bootstrap");
50238
+ return {
50239
+ ...observation,
50240
+ bootstrapBaseUrl,
50241
+ publishableKey: Option.getOrUndefined(values.key)
50242
+ };
50243
+ });
49088
50244
  const collectionStatus = (config) => Effect.gen(function* () {
49089
50245
  const stored = (yield* readSettings(config.settingsPath))?.collection.enabled ?? null;
49090
50246
  const enabled = config.collectionEnabled();
@@ -49108,33 +50264,13 @@ const codes = [
49108
50264
  ];
49109
50265
  const identifier = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u));
49110
50266
  const version = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u));
49111
- const outcomeSchema = Schema.Literals([
49112
- "succeeded",
49113
- "refused",
49114
- "failed",
49115
- "cancelled"
49116
- ]);
49117
50267
  const completionSchema = Schema.Struct({
49118
- schema_version: Schema.Literal(1),
49119
- command: Schema.Literal("doctor"),
49120
- invocation_id: identifier,
50268
+ ...CliCommandCompletedTelemetryEventSchema.fields.props.fields,
49121
50269
  correlation_id: identifier,
49122
50270
  journey_id: identifier,
49123
- capxul_env: Schema.Literals([
49124
- "development",
49125
- "staging",
49126
- "production",
49127
- "local"
49128
- ]),
49129
- producer: Schema.Literal("server"),
49130
- cli_version: version,
49131
- sdk_version: version,
49132
- outcome: outcomeSchema,
49133
- duration_ms: Schema.Number,
50271
+ posthogDistinctId: Schema.optional(identifier),
49134
50272
  wait_duration_ms: Schema.Number,
49135
- retry_count: Schema.Literal(0),
49136
- output_mode: Schema.Literals(["human", "json"]),
49137
- error_code: Schema.optional(Schema.Literals(codes))
50273
+ retry_count: Schema.Literal(0)
49138
50274
  });
49139
50275
  const CAPTURE_CAPACITY = 32;
49140
50276
  const CAPTURE_BYTES = 8192;
@@ -49148,13 +50284,15 @@ const own = (value, key) => {
49148
50284
  return;
49149
50285
  }
49150
50286
  };
49151
- const inactiveHost = () => postHogObservability(null, {
50287
+ const inactiveHost = (budget) => postHogObservability(null, {
49152
50288
  enabled: false,
49153
- engineeringEnabled: false
50289
+ engineeringEnabled: false,
50290
+ engineeringDeliveryBudget: budget
49154
50291
  });
49155
50292
  /** One invocation owns its records, metric registry, callback queue, and total flush deadline. */
49156
50293
  function observeInvocation(input, work) {
49157
50294
  return Effect.suspend(() => {
50295
+ const deliveryBudget = makeCliEngineeringBudget();
49158
50296
  const finalizers = [];
49159
50297
  const onFinalize = (finalizer) => {
49160
50298
  finalizers.push(finalizer);
@@ -49163,7 +50301,7 @@ function observeInvocation(input, work) {
49163
50301
  concurrency: "unbounded",
49164
50302
  discard: true
49165
50303
  }));
49166
- const inactiveWork = Effect.acquireUseRelease(Effect.void, () => Effect.suspend(() => work(inactiveHost(), onFinalize)), () => finalize.pipe(Effect.interruptible, Effect.timeoutOption("2 seconds"), Effect.asVoid));
50304
+ const inactiveWork = Effect.acquireUseRelease(Effect.void, () => Effect.suspend(() => work(inactiveHost(deliveryBudget), onFinalize)), () => finalize.pipe(Effect.interruptible, Effect.timeoutOption("2 seconds"), Effect.asVoid));
49167
50305
  let disabled = false;
49168
50306
  const enabled = () => {
49169
50307
  if (disabled) return false;
@@ -49174,12 +50312,13 @@ function observeInvocation(input, work) {
49174
50312
  }
49175
50313
  return !disabled;
49176
50314
  };
49177
- if (input.host === void 0 || input.projectToken === void 0 || !enabled()) return inactiveWork;
50315
+ if (input.kind === "completion" || input.kind === "collection_control" || input.host === void 0 || input.projectToken === void 0 || !enabled()) return inactiveWork;
49178
50316
  const host = input.host;
49179
50317
  const token = Redacted.value(input.projectToken);
49180
50318
  let engineering;
49181
50319
  try {
49182
50320
  Schema.decodeUnknownSync(identifier)(input.invocationId);
50321
+ if (input.anonymousIdentity !== void 0) Schema.decodeUnknownSync(identifier)(input.anonymousIdentity.id);
49183
50322
  Schema.decodeUnknownSync(version)(input.cliVersion);
49184
50323
  engineering = makeEngineeringTelemetryLayer({
49185
50324
  host,
@@ -49188,6 +50327,7 @@ function observeInvocation(input, work) {
49188
50327
  producer: "server",
49189
50328
  sdkVersion: input.sdkVersion,
49190
50329
  serviceName: "capxul-cli",
50330
+ deliveryBudget,
49191
50331
  enabled,
49192
50332
  shutdownTimeout: 0,
49193
50333
  exportInterval: "1 hour"
@@ -49195,7 +50335,7 @@ function observeInvocation(input, work) {
49195
50335
  } catch {
49196
50336
  return inactiveWork;
49197
50337
  }
49198
- const layer = Layer.merge(engineering, FetchHttpClient.layer).pipe(Layer.provideMerge(Layer.succeed(Metric.MetricRegistry, /* @__PURE__ */ new Map())), Layer.provideMerge(Layer.succeed(Logger.LogToStderr, true)));
50338
+ const layer = Layer.merge(engineering, FetchHttpClient.layer).pipe(Layer.provideMerge(Layer.succeed(Logger.LogToStderr, true)));
49199
50339
  return Effect.scoped(Effect.gen(function* () {
49200
50340
  const built = yield* Effect.exit(Layer.build(layer));
49201
50341
  if (Exit.isFailure(built)) return yield* inactiveWork;
@@ -49211,30 +50351,47 @@ function observeInvocation(input, work) {
49211
50351
  producer: "server",
49212
50352
  cli_version: input.cliVersion,
49213
50353
  sdk_version: input.sdkVersion,
50354
+ schema_version: 1,
50355
+ command: input.command,
50356
+ capability: input.capability,
50357
+ invocation_kind: input.kind,
50358
+ release: input.cliVersion,
50359
+ platform: input.platform ?? "unknown",
50360
+ architecture: input.architecture ?? "unknown",
50361
+ ci: input.ci ?? false,
50362
+ tty: input.tty ?? false,
50363
+ output_mode: input.outputMode ?? "human",
50364
+ ...input.anonymousIdentity === void 0 ? {} : {
50365
+ anonymous_id: input.anonymousIdentity.id,
50366
+ cli_identity_kind: "anonymous"
50367
+ },
49214
50368
  invocation_id: input.invocationId,
49215
50369
  correlation_id: input.invocationId,
49216
50370
  journey_id: input.invocationId
49217
50371
  };
49218
- const capture = (event, properties) => {
50372
+ const capture = (event, properties, identity) => {
49219
50373
  if (!enabled()) {
49220
50374
  dropped += 1;
49221
50375
  return;
49222
50376
  }
50377
+ const occurredAt = identity?.observedAt ?? clock.currentTimeMillisUnsafe();
49223
50378
  const record = sanitizePostHogBrowserEvent({
49224
50379
  event,
49225
50380
  properties: {
49226
50381
  ...properties,
49227
50382
  ...envelope,
50383
+ occurred_at: occurredAt,
49228
50384
  ...nativeContext
49229
50385
  }
49230
50386
  });
49231
50387
  const body = JSON.stringify({
49232
50388
  api_key: token,
49233
50389
  ...record,
49234
- uuid: crypto.randomUUID(),
50390
+ uuid: identity?.eventId ?? crypto.randomUUID(),
50391
+ timestamp: new Date(occurredAt).toISOString(),
49235
50392
  properties: {
49236
50393
  ...record.properties,
49237
- distinct_id: input.invocationId,
50394
+ distinct_id: input.anonymousIdentity?.id ?? input.invocationId,
49238
50395
  $process_person_profile: false
49239
50396
  }
49240
50397
  });
@@ -49267,9 +50424,40 @@ function observeInvocation(input, work) {
49267
50424
  ...envelope
49268
50425
  }
49269
50426
  });
49270
- if (Option.isSome(decoded) && Queue.sizeUnsafe(queue) < 30) capture(event, { ...decoded.value.props });
50427
+ if (Option.isSome(decoded) && Queue.sizeUnsafe(queue) < 29) capture(event, { ...decoded.value.props });
49271
50428
  else dropped += 1;
49272
50429
  } };
50430
+ const clientHttp = HttpClient.transform(yield* HttpClient.HttpClient, (response, request) => Effect.suspend(() => {
50431
+ if (enabled()) return response;
50432
+ dropped += 1;
50433
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
50434
+ })).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
50435
+ times: 1,
50436
+ schedule: Schedule.spaced("100 millis")
50437
+ }));
50438
+ const flushCaptures = Effect.gen(function* () {
50439
+ const pending = yield* Queue.clear(queue);
50440
+ if (!enabled()) {
50441
+ dropped += pending.length;
50442
+ return;
50443
+ }
50444
+ pendingCaptures += pending.length;
50445
+ yield* Effect.forEach(pending, (item) => Effect.suspend(() => {
50446
+ if (!enabled()) {
50447
+ dropped += 1;
50448
+ return Effect.void;
50449
+ }
50450
+ return clientHttp.execute(HttpClientRequest.post(`${host.replace(/\/+$/, "")}/capture/`).pipe(HttpClientRequest.bodyText(item, "application/json"))).pipe(Effect.asVoid, Effect.catchCause(() => Effect.sync(() => {
50451
+ dropped += 1;
50452
+ })));
50453
+ }).pipe(Effect.ensuring(Effect.sync(() => {
50454
+ pendingCaptures -= 1;
50455
+ }))), {
50456
+ concurrency: 4,
50457
+ discard: true
50458
+ });
50459
+ }).pipe(Effect.withTracerEnabled(false), Effect.provideService(HttpClient.TracerPropagationEnabled, false));
50460
+ let startDelivery;
49273
50461
  let commandResult;
49274
50462
  yield* Effect.gen(function* () {
49275
50463
  const span = yield* Effect.currentSpan.pipe(Effect.orDie);
@@ -49282,15 +50470,39 @@ function observeInvocation(input, work) {
49282
50470
  enabled,
49283
50471
  engineeringEnabled: enabled,
49284
50472
  engineeringPrivacy: "minimal",
50473
+ engineeringDeliveryBudget: deliveryBudget,
49285
50474
  capxulEnv: input.environment,
49286
50475
  context: {
49287
50476
  application: "capxul-cli",
50477
+ ...input.anonymousIdentity === void 0 ? {} : { anonymousId: input.anonymousIdentity.id },
49288
50478
  release: input.cliVersion,
49289
50479
  correlationId: input.invocationId,
49290
50480
  journeyId: input.invocationId,
49291
50481
  traceparent
49292
50482
  }
49293
50483
  });
50484
+ const firstUse = input.anonymousIdentity?.firstUse;
50485
+ if (firstUse !== void 0) {
50486
+ const first = Schema.decodeUnknownSync(CliFirstUsedTelemetryEventSchema)({
50487
+ name: "cli_first_used",
50488
+ props: {
50489
+ ...envelope,
50490
+ occurred_at: firstUse.observedAt
50491
+ }
50492
+ });
50493
+ capture("cli_first_used", { ...first.props }, firstUse);
50494
+ }
50495
+ if (input.kind !== "parse_refusal") {
50496
+ const start = Schema.decodeUnknownSync(CliCommandStartedTelemetryEventSchema)({
50497
+ name: "cli_command_started",
50498
+ props: {
50499
+ ...envelope,
50500
+ occurred_at: clock.currentTimeMillisUnsafe()
50501
+ }
50502
+ });
50503
+ capture("cli_command_started", { ...start.props });
50504
+ startDelivery = yield* Effect.forkScoped(flushCaptures.pipe(Effect.interruptible), { startImmediately: true });
50505
+ }
49294
50506
  const waitStarted = clock.currentTimeMillisUnsafe();
49295
50507
  const result = yield* Effect.exit(restore(Effect.suspend(() => work(observation, onFinalize))));
49296
50508
  commandResult = result;
@@ -49303,8 +50515,12 @@ function observeInvocation(input, work) {
49303
50515
  const duration = Math.max(0, clock.currentTimeMillisUnsafe() - started);
49304
50516
  const completion = Schema.decodeUnknownSync(completionSchema)({
49305
50517
  ...envelope,
50518
+ ...input.anonymousIdentity === void 0 ? {} : { posthogDistinctId: input.anonymousIdentity.id },
50519
+ occurred_at: clock.currentTimeMillisUnsafe(),
49306
50520
  schema_version: 1,
49307
50521
  command: input.command,
50522
+ capability: input.capability,
50523
+ invocation_kind: input.kind,
49308
50524
  outcome,
49309
50525
  duration_ms: duration,
49310
50526
  wait_duration_ms: waitDuration,
@@ -49313,13 +50529,15 @@ function observeInvocation(input, work) {
49313
50529
  ...errorCode === void 0 ? {} : { error_code: errorCode }
49314
50530
  });
49315
50531
  yield* Effect.annotateCurrentSpan(completion);
49316
- yield* Effect.logInfo("cli.command.completed").pipe(Effect.annotateLogs(completion));
50532
+ yield* logEngineeringInfo("cli.command.completed", completion);
49317
50533
  for (const diagnostic of sdkDiagnostics) yield* Effect.logInfo("cli.sdk.operation.failed").pipe(Effect.annotateLogs({
49318
50534
  ...envelope,
49319
50535
  ...diagnostic
49320
50536
  }));
49321
50537
  const metricAttributes = {
49322
50538
  command: input.command,
50539
+ capability: input.capability,
50540
+ invocation_kind: input.kind,
49323
50541
  outcome,
49324
50542
  capxul_env: input.environment
49325
50543
  };
@@ -49339,15 +50557,27 @@ function observeInvocation(input, work) {
49339
50557
  unit: "s"
49340
50558
  }
49341
50559
  }), duration / 1e3);
49342
- const event = {
49343
- name: "cli_diagnostic_completed",
50560
+ const terminal = Schema.decodeUnknownSync(CliCommandCompletedTelemetryEventSchema)({
50561
+ name: "cli_command_completed",
49344
50562
  props: {
49345
50563
  ...envelope,
49346
- outcome
50564
+ occurred_at: clock.currentTimeMillisUnsafe(),
50565
+ outcome,
50566
+ duration_ms: duration,
50567
+ ...errorCode === void 0 ? {} : { error_code: errorCode }
49347
50568
  }
49348
- };
49349
- const decoded = Schema.decodeUnknownSync(CliDiagnosticCompletedTelemetryEventSchema)(event);
49350
- capture(event.name, { ...decoded.props });
50569
+ });
50570
+ capture("cli_command_completed", { ...terminal.props });
50571
+ if (input.command === "doctor" && input.kind === "command" && input.network) {
50572
+ const diagnostic = Schema.decodeUnknownSync(CliDiagnosticCompletedTelemetryEventSchema)({
50573
+ name: "cli_diagnostic_completed",
50574
+ props: {
50575
+ ...envelope,
50576
+ outcome
50577
+ }
50578
+ });
50579
+ capture("cli_diagnostic_completed", { ...diagnostic.props });
50580
+ }
49351
50581
  if (Exit.isFailure(result)) {
49352
50582
  const defect = result.cause.reasons.find(Cause.isDieReason);
49353
50583
  if (defect !== void 0) {
@@ -49358,7 +50588,7 @@ function observeInvocation(input, work) {
49358
50588
  if (typeof stack === "string") error.stack = stack.slice(0, 8192);
49359
50589
  } catch {}
49360
50590
  yield* captureException({
49361
- emit: (exception) => Effect.sync(() => capture("$exception", projectException(exception.props))),
50591
+ emit: (exception) => Effect.sync(() => capture("$exception", projectException(exception.props, input.command))),
49362
50592
  identify: () => Effect.void,
49363
50593
  group: () => Effect.void,
49364
50594
  reset: () => Effect.void
@@ -49367,41 +50597,12 @@ function observeInvocation(input, work) {
49367
50597
  }
49368
50598
  }).pipe(Effect.catchCause(() => Effect.void));
49369
50599
  if (Exit.isFailure(result)) return yield* Effect.fail("CLI command failed");
49370
- }).pipe(Effect.withSpan("cli.command.doctor"), Effect.exit);
49371
- const clientHttp = HttpClient.transform(yield* HttpClient.HttpClient, (response, request) => Effect.suspend(() => {
49372
- if (enabled()) return response;
49373
- dropped += 1;
49374
- return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
49375
- })).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
49376
- times: 1,
49377
- schedule: Schedule.spaced("100 millis")
49378
- }));
49379
- const flushCaptures = Effect.gen(function* () {
49380
- const pending = yield* Queue.clear(queue);
49381
- if (!enabled()) {
49382
- dropped += pending.length;
49383
- return;
49384
- }
49385
- pendingCaptures = pending.length;
49386
- yield* Effect.forEach(pending, (item) => Effect.suspend(() => {
49387
- if (!enabled()) {
49388
- dropped += 1;
49389
- return Effect.void;
49390
- }
49391
- return clientHttp.execute(HttpClientRequest.post(`${host.replace(/\/+$/, "")}/capture/`).pipe(HttpClientRequest.bodyText(item, "application/json"))).pipe(Effect.asVoid, Effect.catchCause(() => Effect.sync(() => {
49392
- dropped += 1;
49393
- })));
49394
- }).pipe(Effect.ensuring(Effect.sync(() => {
49395
- pendingCaptures -= 1;
49396
- }))), {
49397
- concurrency: 4,
49398
- discard: true
49399
- });
49400
- }).pipe(Effect.withTracerEnabled(false), Effect.provideService(HttpClient.TracerPropagationEnabled, false));
50600
+ }).pipe(Effect.withSpan(`cli.command.${input.command}`), Effect.exit);
49401
50601
  yield* Effect.all([
49402
50602
  finalize,
49403
50603
  flushCaptures,
49404
- flushEngineeringTelemetry
50604
+ flushEngineeringTelemetry,
50605
+ startDelivery === void 0 ? Effect.void : Fiber.join(startDelivery)
49405
50606
  ], {
49406
50607
  concurrency: "unbounded",
49407
50608
  discard: true
@@ -49411,6 +50612,7 @@ function observeInvocation(input, work) {
49411
50612
  yield* Queue.shutdown(queue);
49412
50613
  try {
49413
50614
  input.onDropped?.(dropped);
50615
+ input.onEngineeringDropped?.(deliveryBudget.snapshot().dropped);
49414
50616
  } catch {}
49415
50617
  if (commandResult === void 0) return yield* Effect.die("CLI observation could not start");
49416
50618
  return yield* commandResult;
@@ -49445,7 +50647,7 @@ function projectSdkProperties(properties) {
49445
50647
  if (failureMode !== void 0) output.failure_mode = failureMode;
49446
50648
  return output;
49447
50649
  }
49448
- function projectException(properties) {
50650
+ function projectException(properties, command) {
49449
50651
  const sanitized = sanitizePostHogBrowserEvent({
49450
50652
  event: "$exception",
49451
50653
  properties: { ...properties }
@@ -49469,8 +50671,8 @@ function projectException(properties) {
49469
50671
  $exception_type: "CLI_DEFECT",
49470
50672
  $exception_message: "CLI command failed",
49471
50673
  $exception_level: "error",
49472
- $exception_fingerprint: `cli.doctor.CLI_DEFECT:${frames[0]?.filename ?? "boundary"}:${frames[0]?.function ?? "unknown"}`,
49473
- $issue_name: "CLI doctor defect",
50674
+ $exception_fingerprint: `cli.${command}.CLI_DEFECT:${frames[0]?.filename ?? "boundary"}:${frames[0]?.function ?? "unknown"}`,
50675
+ $issue_name: `CLI ${command} defect`,
49474
50676
  $exception_list: [{
49475
50677
  type: "CLI_DEFECT",
49476
50678
  value: "CLI command failed",
@@ -49496,41 +50698,101 @@ function renderResult(mode, envelope, lines = []) {
49496
50698
  }
49497
50699
  //#endregion
49498
50700
  //#region src/version.ts
49499
- const cliVersion = "4.20.0-beta.1";
49500
- const sdkVersion = "4.20.0-beta.1";
50701
+ const cliVersion = "4.20.0-beta.3";
50702
+ const sdkVersion = "4.20.0-beta.3";
50703
+ //#endregion
50704
+ //#region src/runtime/command-metadata.ts
50705
+ var CliCommandMetadata = class extends Context.Service()("capxul-cli/CommandMetadata") {};
50706
+ const collectionControl = (name) => name === "telemetry" || name === "telemetry.status" || name === "telemetry.enable" || name === "telemetry.disable";
50707
+ function metadataAtPath(root, path) {
50708
+ let selected = root;
50709
+ for (const part of path.slice(1)) {
50710
+ const child = selected.subcommands.flatMap((group) => group.commands).find((command) => command.name === part || command.alias === part);
50711
+ if (child === void 0) break;
50712
+ selected = child;
50713
+ }
50714
+ const metadata = Context.getOption(selected.annotations, CliCommandMetadata);
50715
+ return Option.isSome(metadata) && CLI_COMMAND_NAMES.includes(metadata.value.name) ? metadata.value : {
50716
+ name: "unknown",
50717
+ capability: "cli",
50718
+ observation: "command"
50719
+ };
50720
+ }
49501
50721
  //#endregion
49502
50722
  //#region src/runtime/run-command.ts
49503
50723
  var CliRuntime = class extends Context.Service()("capxul-cli/Runtime") {};
49504
- const runCommand = (options, work) => Effect.gen(function* () {
49505
- const runtime = yield* CliRuntime;
49506
- const invocationId = yield* (yield* Crypto.Crypto).randomUUIDv4;
49507
- yield* Ref.set(runtime.current, {
49508
- command: options.name,
49509
- invocationId
49510
- });
49511
- const config = yield* loadConfig;
50724
+ const acquireInvocationIdentity = (config, eligible) => Effect.gen(function* () {
50725
+ if (!eligible || config?.posthogHost === void 0 || config.projectToken === void 0 || !config.collectionEnabled()) return void 0;
50726
+ const path = yield* Path.Path;
50727
+ const acquired = yield* acquireObservationIdentity(path.dirname(config.settingsPath));
50728
+ return acquired.status === "ready" ? acquired : void 0;
50729
+ }).pipe(Effect.catchCause(() => Effect.succeed(void 0)));
50730
+ const runInvocation = (runtime, metadata, kind, work, network = false) => Effect.gen(function* () {
50731
+ const current = yield* Ref.get(runtime.current);
50732
+ if (current !== null) return yield* work(current.host, current.onFinalize);
50733
+ const invocationKind = kind === "completion" ? kind : collectionControl(metadata.name) ? "collection_control" : kind;
50734
+ const silent = invocationKind === "completion" || invocationKind === "collection_control";
50735
+ const invocation = {
50736
+ command: metadata.name,
50737
+ invocationId: runtime.invocationId,
50738
+ kind
50739
+ };
50740
+ yield* Ref.set(runtime.current, invocation);
50741
+ const config = runtime.observation;
50742
+ const identity = yield* acquireInvocationIdentity(config, !silent);
49512
50743
  let dropped = 0;
49513
- const result = options.network ? yield* observeInvocation({
49514
- command: "doctor",
49515
- invocationId,
50744
+ let engineeringDropped = 0;
50745
+ return yield* observeInvocation({
50746
+ command: metadata.name,
50747
+ capability: metadata.capability,
50748
+ kind: invocationKind,
50749
+ network,
50750
+ invocationId: runtime.invocationId,
50751
+ ...identity === void 0 ? {} : { anonymousIdentity: {
50752
+ id: identity.epoch.id,
50753
+ ...identity.firstUseClaimed ? { firstUse: identity.firstUse } : {}
50754
+ } },
49516
50755
  cliVersion,
49517
50756
  sdkVersion,
49518
- environment: config.environment,
49519
- ...config.posthogHost === void 0 ? {} : { host: config.posthogHost },
49520
- ...config.projectToken === void 0 ? {} : { projectToken: config.projectToken },
49521
- enabled: config.collectionEnabled,
50757
+ environment: config?.environment ?? "staging",
50758
+ platform: runtime.platform === "darwin" || runtime.platform === "linux" ? runtime.platform : "unknown",
50759
+ architecture: runtime.architecture === "arm64" || runtime.architecture === "x64" ? runtime.architecture : "unknown",
50760
+ ci: runtime.ci,
50761
+ tty: runtime.tty,
50762
+ ...!silent && config?.posthogHost !== void 0 ? { host: config.posthogHost } : {},
50763
+ ...!silent && config?.projectToken !== void 0 ? { projectToken: config.projectToken } : {},
50764
+ enabled: identity === void 0 ? () => false : config?.collectionEnabled ?? (() => false),
49522
50765
  outputMode: runtime.mode,
49523
50766
  onDropped: (count) => {
49524
50767
  dropped = count;
50768
+ },
50769
+ onEngineeringDropped: (count) => {
50770
+ engineeringDropped = count;
49525
50771
  }
49526
- }, (observation, onFinalize) => work(config, observation, onFinalize)).pipe(Effect.onExit(() => dropped > 0 ? writeStderr(`Telemetry dropped ${dropped} records.\n`) : Effect.void)) : yield* work(config);
49527
- yield* renderResult(runtime.mode, {
49528
- version: 1,
49529
- command: options.name,
49530
- invocationId,
49531
- outcome: "succeeded",
49532
- data: result.data
49533
- }, result.lines);
50772
+ }, (host, onFinalize) => Effect.gen(function* () {
50773
+ yield* Ref.set(runtime.current, {
50774
+ ...invocation,
50775
+ host,
50776
+ onFinalize
50777
+ });
50778
+ return yield* work(host, onFinalize);
50779
+ })).pipe(Effect.onExit(() => {
50780
+ const notice = (dropped > 0 ? `Telemetry dropped ${dropped} records.\n` : "") + (engineeringDropped > 0 ? `Engineering telemetry dropped ${engineeringDropped} items.\n` : "");
50781
+ return notice === "" ? Effect.void : writeStderr(notice).pipe(Effect.catchCause(() => Effect.void));
50782
+ }));
50783
+ });
50784
+ const runCommand = (options, work) => Effect.gen(function* () {
50785
+ const runtime = yield* CliRuntime;
50786
+ return yield* runInvocation(runtime, options, "command", (host, onFinalize) => Effect.gen(function* () {
50787
+ const result = yield* work(yield* loadConfig, host, onFinalize);
50788
+ yield* renderResult(runtime.mode, {
50789
+ version: 1,
50790
+ command: options.name,
50791
+ invocationId: runtime.invocationId,
50792
+ outcome: "succeeded",
50793
+ data: result.data
50794
+ }, result.lines);
50795
+ }), options.network);
49534
50796
  });
49535
50797
  //#endregion
49536
50798
  //#region src/platform/capxul.ts
@@ -49614,42 +50876,63 @@ const checkDoctor = (config, options) => Effect.gen(function* () {
49614
50876
  });
49615
50877
  //#endregion
49616
50878
  //#region src/commands/doctor.ts
50879
+ const metadata = {
50880
+ name: "doctor",
50881
+ capability: "diagnostics",
50882
+ observation: "command"
50883
+ };
49617
50884
  const doctor = Command.make("doctor", {
49618
50885
  online: Flag.boolean("online").pipe(Flag.withDefault(false), Flag.withDescription("Also check the backend through the public SDK")),
49619
50886
  timeoutMs: Flag.integer("timeout-ms").pipe(Flag.filter((value) => value >= 1 && value <= 3e5, () => "Use --timeout-ms between 1 and 300000."), Flag.withDefault(3e4), Flag.withDescription("Online check deadline in milliseconds (1 to 300000)"))
49620
50887
  }, ({ online, timeoutMs }) => runCommand({
49621
- name: "doctor",
50888
+ ...metadata,
49622
50889
  network: online
49623
50890
  }, (config, observation, onFinalize) => checkDoctor(config, {
49624
50891
  online,
49625
50892
  timeoutMs,
49626
50893
  ...observation === void 0 ? {} : { observation },
49627
50894
  ...onFinalize === void 0 ? {} : { onFinalize }
49628
- }))).pipe(Command.withDescription("Check local configuration; --online also checks the backend"));
50895
+ }))).pipe(Command.annotate(CliCommandMetadata, metadata), Command.withDescription("Check local configuration; --online also checks the backend"));
49629
50896
  //#endregion
49630
50897
  //#region src/commands/telemetry.ts
50898
+ const statusMetadata = {
50899
+ name: "telemetry.status",
50900
+ capability: "collection",
50901
+ observation: "collection-control"
50902
+ };
50903
+ const enableMetadata = {
50904
+ ...statusMetadata,
50905
+ name: "telemetry.enable"
50906
+ };
50907
+ const disableMetadata = {
50908
+ ...statusMetadata,
50909
+ name: "telemetry.disable"
50910
+ };
49631
50911
  const report = (config) => collectionStatus(config).pipe(Effect.map((status) => ({
49632
50912
  data: status,
49633
50913
  lines: [
49634
50914
  `Remote CLI observation ${status.enabled ? "enabled" : "disabled"}.`,
49635
50915
  ...status.configured ? [] : ["Remote delivery is not configured."],
49636
50916
  ...config.forcedDisabled ? ["CAPXUL_TELEMETRY_DISABLED overrides the saved preference."] : [],
49637
- "Local commands send nothing. Use capxul telemetry disable to opt out."
50917
+ "Commands collect usage and diagnostics. Collection controls and completions send nothing."
49638
50918
  ]
49639
50919
  })));
49640
50920
  const status = Command.make("status", {}, () => runCommand({
49641
- name: "telemetry.status",
50921
+ ...statusMetadata,
49642
50922
  network: false
49643
- }, report));
50923
+ }, report)).pipe(Command.annotate(CliCommandMetadata, statusMetadata));
49644
50924
  const enable = Command.make("enable", {}, () => runCommand({
49645
- name: "telemetry.enable",
50925
+ ...enableMetadata,
49646
50926
  network: false
49647
- }, (config) => setCollectionEnabled(config.settingsPath, true).pipe(Effect.andThen(report(config)))));
50927
+ }, (config) => setCollectionEnabled(config.settingsPath, true).pipe(Effect.andThen(report(config))))).pipe(Command.annotate(CliCommandMetadata, enableMetadata));
49648
50928
  const disable = Command.make("disable", {}, () => runCommand({
49649
- name: "telemetry.disable",
50929
+ ...disableMetadata,
49650
50930
  network: false
49651
- }, (config) => setCollectionEnabled(config.settingsPath, false).pipe(Effect.andThen(report(config)))));
49652
- const telemetry = Command.make("telemetry").pipe(Command.withDescription("Show or change this CLI's collection preference"), Command.withSubcommands([
50931
+ }, (config) => setCollectionEnabled(config.settingsPath, false).pipe(Effect.andThen(report(config))))).pipe(Command.annotate(CliCommandMetadata, disableMetadata));
50932
+ const telemetry = Command.make("telemetry").pipe(Command.annotate(CliCommandMetadata, {
50933
+ ...statusMetadata,
50934
+ name: "telemetry"
50935
+ }), Command.withDescription("Show or change this CLI's collection preference"), Command.withSubcommands([
49653
50936
  status,
49654
50937
  enable,
49655
50938
  disable
@@ -49679,52 +50962,87 @@ const outputMode = (args) => Effect.gen(function* () {
49679
50962
  });
49680
50963
  //#endregion
49681
50964
  //#region src/command.ts
49682
- const command = Command.make("capxul").pipe(Command.withDescription("Capxul diagnostics and configuration"), Command.withSubcommands([doctor, telemetry]), Command.withGlobalFlags([JsonOutput]));
50965
+ const command = Command.make("capxul").pipe(Command.annotate(CliCommandMetadata, {
50966
+ name: "capxul",
50967
+ capability: "cli",
50968
+ observation: "command"
50969
+ }), Command.withDescription("Capxul diagnostics and configuration"), Command.withSubcommands([doctor, telemetry]), Command.withGlobalFlags([JsonOutput]));
49683
50970
  //#endregion
49684
50971
  //#region src/runtime/entry.ts
50972
+ const completionFailure = (error) => {
50973
+ if (!CliError.isCliError(error)) return false;
50974
+ if (error._tag === "ShowHelp") return error.errors.some(completionFailure);
50975
+ return (error._tag === "InvalidValue" || error._tag === "MissingOption" || error._tag === "DuplicateOption") && (error.option === "completions" || error.option === "--completions");
50976
+ };
49685
50977
  const runCli = (platform) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
49686
50978
  const stdio = yield* Stdio.Stdio;
50979
+ const stateServices = yield* Effect.context().pipe(Effect.map(Context.pick(Crypto.Crypto, FileSystem.FileSystem, Path.Path)));
49687
50980
  const args = yield* stdio.args;
49688
50981
  const mode = yield* outputMode(args);
49689
50982
  const interactive = yield* stdio.stdinIsTerminal;
49690
50983
  const cliConfig = yield* CliConfig.CliConfig;
49691
50984
  const current = yield* Ref.make(null);
50985
+ const invocationId = yield* (yield* Crypto.Crypto).randomUUIDv4;
50986
+ const observation = yield* loadObservationConfig.pipe(Effect.catch(() => Effect.succeed(void 0)));
50987
+ const ci = yield* Config.string("CI").pipe(Config.withDefault("false"), Effect.map((value) => value !== "" && value !== "false" && value !== "0"), Effect.catch(() => Effect.succeed(false)));
50988
+ const runtime = {
50989
+ mode,
50990
+ current,
50991
+ invocationId,
50992
+ observation,
50993
+ ...platform,
50994
+ architecture: arch,
50995
+ ci,
50996
+ tty: interactive
50997
+ };
49692
50998
  const nativeConsole = yield* Console.Console;
49693
50999
  const output = [];
49694
51000
  const capture = (...values) => {
49695
51001
  output.push(values.map(String).join(" "));
49696
51002
  };
51003
+ const flushNative = Effect.suspend(() => {
51004
+ if (output.length === 0) return Effect.void;
51005
+ const text = output.splice(0).join("\n");
51006
+ return mode === "json" ? renderResult(mode, {
51007
+ version: 1,
51008
+ command: "capxul",
51009
+ invocationId: null,
51010
+ outcome: "succeeded",
51011
+ data: { text }
51012
+ }) : writeStdout(`${text}\n`);
51013
+ });
51014
+ const decorate = (flag, kind) => GlobalFlag.action({
51015
+ flag: flag.flag,
51016
+ run: (value, context) => runInvocation(runtime, metadataAtPath(context.command, context.commandPath), kind, () => flag.run(value, context).pipe(Effect.andThen(flushNative))).pipe(Effect.provide(stateServices), Effect.provideService(Stdio.Stdio, stdio), Effect.orDie)
51017
+ });
51018
+ const builtIns = cliConfig.builtIns.filter((flag) => interactive && !ci && mode === "human" || flag !== GlobalFlag.Wizard).map((flag) => flag === GlobalFlag.Help ? decorate(GlobalFlag.Help, "help") : flag === GlobalFlag.Version ? decorate(GlobalFlag.Version, "version") : flag === GlobalFlag.Completions ? decorate(GlobalFlag.Completions, "completion") : flag);
49697
51019
  const exit = yield* restore(Command.runWith(command, {
49698
51020
  version: cliVersion,
49699
51021
  renderErrors: false
49700
- })(args).pipe(Effect.provideService(CliRuntime, {
49701
- mode,
49702
- current,
49703
- ...platform
49704
- }), Effect.provideService(CliConfig.CliConfig, { builtIns: interactive && mode === "human" ? cliConfig.builtIns : cliConfig.builtIns.filter((flag) => flag !== GlobalFlag.Wizard) }), Effect.provideService(Console.Console, {
51022
+ })(args).pipe(Effect.provideService(CliRuntime, runtime), Effect.provideService(CliConfig.CliConfig, { builtIns }), Effect.provideService(Console.Console, {
49705
51023
  ...nativeConsole,
49706
51024
  log: capture,
49707
51025
  info: capture
49708
51026
  }))).pipe(Effect.exit);
49709
51027
  if (Exit.isSuccess(exit)) {
49710
- if (output.length > 0) {
49711
- if (mode === "json") yield* renderResult(mode, {
49712
- version: 1,
49713
- command: "capxul",
49714
- invocationId: null,
49715
- outcome: "succeeded",
49716
- data: { text: output.join("\n") }
49717
- });
49718
- else yield* writeStdout(`${output.join("\n")}\n`);
49719
- }
51028
+ if ((yield* Ref.get(current)) === null && output.length > 0) yield* runInvocation(runtime, metadataAtPath(command, [command.name]), "help", () => flushNative);
51029
+ else yield* flushNative;
49720
51030
  return 0;
49721
51031
  }
51032
+ const error = Cause.squash(exit.cause);
51033
+ if ((yield* Ref.get(current)) === null) {
51034
+ const path = CliError.isCliError(error) && error._tag === "ShowHelp" ? error.commandPath : [];
51035
+ if (path.length > 0) {
51036
+ const metadata = metadataAtPath(command, path);
51037
+ if (metadata.name !== "unknown") yield* runInvocation(runtime, metadata, completionFailure(error) ? "completion" : "parse_refusal", () => Effect.failCause(exit.cause)).pipe(Effect.exit);
51038
+ }
51039
+ }
49722
51040
  const invocation = yield* Ref.get(current);
49723
51041
  const failure = classifyFailure(exit.cause);
49724
51042
  yield* renderResult(mode, {
49725
51043
  version: 1,
49726
- command: invocation?.command ?? parsedCommand(exit.cause),
49727
- invocationId: invocation?.invocationId ?? null,
51044
+ command: invocation?.kind === "parse_refusal" || invocation?.kind === "completion" || invocation?.kind === "collection_control" ? parsedCommand(exit.cause) : invocation?.command ?? parsedCommand(exit.cause),
51045
+ invocationId: invocation?.kind === "command" ? invocation.invocationId : null,
49728
51046
  outcome: failure.outcome,
49729
51047
  error: {
49730
51048
  code: failure.code,