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