@capxul/sdk 2.3.2 → 2.5.0
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/README.md +22 -0
- package/dist/{InMemoryAuthCacheAdapter-qMpBOGb3.mjs → InMemoryAuthCacheAdapter-D5Cv0yz0.mjs} +7 -1
- package/dist/{create-capxul-client-DgvnboPR.mjs → create-capxul-client-Btl4qyNC.mjs} +1623 -1245
- package/dist/index.d.mts +39 -4
- package/dist/index.mjs +94 -10
- package/dist/node/index.d.mts +1 -1
- package/dist/node/index.mjs +1 -1
- package/dist/{observation-CLhrvkF6.d.mts → observation-CyNjyQxv.d.mts} +233 -31
- package/dist/{signer-BejoR3bA.d.mts → signer-CRzTIrau.d.mts} +3 -2
- package/dist/testing/index.d.mts +1 -1
- package/dist/testing/index.mjs +2 -2
- package/package.json +6 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as toPartyId,
|
|
1
|
+
import { A as toPartyId, B as Errors, M as toPayrollRunId, P as toRoleKey, R as CapxulError, S as toCurrencyCode, V as isCapxulError, b as toChainId, c as BYTES32_RE, d as WEI_RE, f as ZERO_BYTES32, h as toAddress, j as toPayrollGroupId, k as toOrgId, l as EVM_ADDRESS_RE$1, m as toAccountId, n as BrowserAuthCacheAdapter, s as APP_ID_RE, t as InMemoryAuthCacheAdapter, u as SUPPORTED_CURRENCY_CODES, v as toAuthUserId, w as toEmail, x as toCountryCode, y as toBudgetId, z as EXPECTED_OPERATION_OUTCOMES } from "./InMemoryAuthCacheAdapter-D5Cv0yz0.mjs";
|
|
2
2
|
import { concat, encodeAbiParameters, encodeFunctionData, encodePacked, formatUnits, getContractAddress, keccak256, padHex, parseUnits, recoverAddress, stringToHex, toBytes, toEventSelector, toFunctionSelector, toFunctionSignature } from "viem";
|
|
3
3
|
import { Context, Data, Deferred, Effect, Exit, Fiber, Layer, Queue, Ref, Result, Schema, SchemaGetter, Scope } from "effect";
|
|
4
4
|
import { makeFunctionReference } from "convex/server";
|
|
@@ -1291,1078 +1291,249 @@ new Map([
|
|
|
1291
1291
|
["setAllowance(bytes32,uint128,uint128,uint128,uint64,uint64)", "SetAllowance(bytes32,uint128,uint128,uint128,uint64,uint64)"]
|
|
1292
1292
|
].map(([functionSignature, eventSignature]) => [toFunctionSelector(functionSignature), toEventSelector(eventSignature)]));
|
|
1293
1293
|
//#endregion
|
|
1294
|
-
//#region src/
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1313
|
-
//#endregion
|
|
1314
|
-
//#region src/telemetry/get-failure-mode.ts
|
|
1294
|
+
//#region ../wire/src/brands.ts
|
|
1295
|
+
const lowercasedString = Schema.String.pipe(Schema.decodeTo(Schema.String, {
|
|
1296
|
+
decode: SchemaGetter.transform((value) => value.toLowerCase()),
|
|
1297
|
+
encode: SchemaGetter.transform((value) => value)
|
|
1298
|
+
}));
|
|
1299
|
+
const addressSchema = (name) => lowercasedString.pipe(Schema.refine((value) => EVM_ADDRESS_RE$1.test(value), { message: `${name} must be a 0x-prefixed EVM address` }));
|
|
1300
|
+
const bytes32BrandSchema = (name) => lowercasedString.pipe(Schema.refine((value) => BYTES32_RE.test(value), { message: `${name} must be a 0x-prefixed bytes32 value` }));
|
|
1301
|
+
/** Lowercase, 0x-prefixed bytes32 evidence without a public brand. */
|
|
1302
|
+
const Bytes32Schema = bytes32BrandSchema("value");
|
|
1303
|
+
const AddressSchema$1 = addressSchema("address");
|
|
1304
|
+
const SafeAddressSchema = addressSchema("safe address");
|
|
1305
|
+
const ModuleAddressSchema = addressSchema("module address");
|
|
1306
|
+
const TxHashSchema$1 = bytes32BrandSchema("transaction hash");
|
|
1307
|
+
const RoleKeySchema = bytes32BrandSchema("role key");
|
|
1308
|
+
const AllowanceKeySchema = bytes32BrandSchema("allowance key");
|
|
1309
|
+
const BlockNumberSchema$1 = Schema.Number.pipe(Schema.refine((value) => Number.isSafeInteger(value) && value >= 0, { message: "must be a non-negative safe integer" }));
|
|
1310
|
+
const LogIndexSchema = Schema.Number.pipe(Schema.refine((value) => Number.isSafeInteger(value) && value >= 0, { message: "must be a non-negative safe integer" }));
|
|
1311
|
+
const WeiAmountSchema$1 = Schema.String.pipe(Schema.refine((value) => WEI_RE.test(value), { message: "must be a non-negative integer string" }));
|
|
1312
|
+
const SettlementIdSchema = bytes32BrandSchema("settlementId").pipe(Schema.refine((value) => value !== ZERO_BYTES32, { message: "settlementId must not be zero" }));
|
|
1315
1313
|
/**
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
* and its guard can never drift apart.
|
|
1314
|
+
* `AppId` schema. Mirrors `toAppId` from `@capxul/types`:
|
|
1315
|
+
* `app_` + Crockford-base32 ULID (26 chars, first char in `[0-7]`).
|
|
1319
1316
|
*/
|
|
1320
|
-
const
|
|
1321
|
-
"auth-origin-mismatch",
|
|
1322
|
-
"stale-openfort-cache",
|
|
1323
|
-
"app-env-allowlist",
|
|
1324
|
-
"no-secure-context",
|
|
1325
|
-
"unknown"
|
|
1326
|
-
]);
|
|
1327
|
-
function isFailureMode(value) {
|
|
1328
|
-
return typeof value === "string" && FAILURE_MODES.has(value);
|
|
1329
|
-
}
|
|
1317
|
+
const AppIdSchema$1 = Schema.String.pipe(Schema.refine((s) => APP_ID_RE.test(s), { message: "must be app_ plus a ULID" }));
|
|
1330
1318
|
/**
|
|
1331
|
-
*
|
|
1332
|
-
*
|
|
1333
|
-
* Reads `details.failure_mode` and returns it only when it is one of the five
|
|
1334
|
-
* canonical members; any other value (a legacy free string, a typo, a
|
|
1335
|
-
* non-string) yields `undefined` so downstream telemetry never reports an
|
|
1336
|
-
* unrecognised cause.
|
|
1319
|
+
* `ChainId` schema. Mirrors `toChainId`: positive safe integer.
|
|
1337
1320
|
*/
|
|
1338
|
-
|
|
1339
|
-
if (!isCapxulError(error)) return void 0;
|
|
1340
|
-
const details = error.details;
|
|
1341
|
-
if (details === void 0) return void 0;
|
|
1342
|
-
return isFailureMode(details.failure_mode) ? details.failure_mode : void 0;
|
|
1343
|
-
}
|
|
1321
|
+
const ChainIdSchema$1 = Schema.Number.pipe(Schema.refine((n) => Number.isSafeInteger(n) && n > 0, { message: "must be a positive safe integer" }));
|
|
1344
1322
|
/**
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
* Resolution order:
|
|
1349
|
-
* 1. the structured mode on the error's `details.failure_mode` (already guarded);
|
|
1350
|
-
* 2. a caller-supplied `contextFailureMode`, but ONLY when it passes the same
|
|
1351
|
-
* runtime membership check — the static `FailureMode` type is erased at
|
|
1352
|
-
* runtime, so an operation string (e.g. `"signer-get-address"`) injected via
|
|
1353
|
-
* a JS caller or `as` cast is rejected here rather than leaking to telemetry;
|
|
1354
|
-
* 3. `"unknown"` otherwise, so an unclassifiable error is still tagged with a
|
|
1355
|
-
* canonical value instead of being emitted with no `failure_mode` at all.
|
|
1323
|
+
* `CurrencyCode` schema. Mirrors `toCurrencyCode`: currently supported
|
|
1324
|
+
* consumer-facing ISO-ish currency code set.
|
|
1356
1325
|
*/
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
}
|
|
1360
|
-
//#endregion
|
|
1361
|
-
//#region src/signer.ts
|
|
1362
|
-
const EVM_ADDRESS_HEX = /^0x[0-9a-fA-F]{40}$/;
|
|
1363
|
-
const ECDSA_SIGNATURE_HEX = /^0x[0-9a-fA-F]{130}$/;
|
|
1364
|
-
const SAFE_OP_DIGEST_HEX = /^0x[0-9a-fA-F]{64}$/;
|
|
1326
|
+
const CurrencyCodeSchema$1 = Schema.String.pipe(Schema.refine((s) => SUPPORTED_CURRENCY_CODES.includes(s), { message: "must be a supported currency code" }));
|
|
1327
|
+
const DOCUMENT_HASH_HEX_RE = /^[0-9a-fA-F]{64}$/;
|
|
1365
1328
|
/**
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
-
* so a gate that reads this never opens on a guess.
|
|
1329
|
+
* `DocumentHash` schema. Mirrors `toDocumentHash`: bare or 0x-prefixed bytes32,
|
|
1330
|
+
* normalized to lowercase 0x-prefixed form.
|
|
1369
1331
|
*/
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
};
|
|
1332
|
+
const DocumentHashSchema = Schema.String.pipe(Schema.decodeTo(Schema.String, {
|
|
1333
|
+
decode: SchemaGetter.transform((s) => {
|
|
1334
|
+
const stripped = s.startsWith("0x") || s.startsWith("0X") ? s.slice(2) : s;
|
|
1335
|
+
return DOCUMENT_HASH_HEX_RE.test(stripped) ? `0x${stripped.toLowerCase()}` : s;
|
|
1336
|
+
}),
|
|
1337
|
+
encode: SchemaGetter.transform((s) => s)
|
|
1338
|
+
}), Schema.refine((s) => BYTES32_RE.test(s), { message: "must be 32 bytes of hex" }));
|
|
1339
|
+
/** L2 orchestrated money evidence requires a nonzero Document hash. */
|
|
1340
|
+
const NonzeroDocumentHashSchema = DocumentHashSchema.pipe(Schema.refine((value) => value !== ZERO_BYTES32, { message: "documentHash must not be zero" }));
|
|
1374
1341
|
/**
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1342
|
+
* `SessionToken` schema. Mirrors `toSessionToken`: non-empty string.
|
|
1343
|
+
* Issuance source distinguishes SDK-handshake tokens from auth-session
|
|
1344
|
+
* tokens; the brand itself is opaque.
|
|
1377
1345
|
*/
|
|
1378
|
-
|
|
1379
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1380
|
-
let current = cause;
|
|
1381
|
-
while (typeof current === "object" && current !== null && !seen.has(current)) {
|
|
1382
|
-
seen.add(current);
|
|
1383
|
-
yield current;
|
|
1384
|
-
current = current.cause;
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
/** Fold a signer throw into the public error contract. */
|
|
1388
|
-
function signerFailure(source, operation, cause) {
|
|
1389
|
-
let failureMode;
|
|
1390
|
-
for (const link of causeChain(cause)) {
|
|
1391
|
-
if (link instanceof CapxulError && link.code === "SIGNER_REJECTED") return link;
|
|
1392
|
-
failureMode = getFailureMode(link) ?? failureMode;
|
|
1393
|
-
const error = link;
|
|
1394
|
-
if (error.code === 4001 || error.error === "passkey_user_cancelled") return Errors.signerRejected({
|
|
1395
|
-
source,
|
|
1396
|
-
cause
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
return Errors.providerError("signer", operation, cause, failureMode === void 0 ? void 0 : { failure_mode: failureMode });
|
|
1400
|
-
}
|
|
1346
|
+
const SessionTokenSchema = Schema.String.pipe(Schema.refine((s) => s.length > 0, { message: "must be a non-empty string" }));
|
|
1401
1347
|
/**
|
|
1402
|
-
*
|
|
1403
|
-
* Signs the SafeOp digest via `eth_sign`, then verifies the returned signature
|
|
1404
|
-
* recovers the selected account against that raw digest. Wallets that prefix
|
|
1405
|
-
* `eth_sign` payloads are rejected before the backend submits an invalid SafeOp.
|
|
1406
|
-
* The node key signer lives in `@capxul/sdk/node` (`localPrivateKeySigner`).
|
|
1348
|
+
* `EpochMs` schema. Mirrors `toEpochMs`: non-negative safe integer.
|
|
1407
1349
|
*/
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
source: "injected-eip1193",
|
|
1418
|
-
getAddress: resolveAddress,
|
|
1419
|
-
async signUserOpHash(hash) {
|
|
1420
|
-
if (!SAFE_OP_DIGEST_HEX.test(hash)) throw new Error("injectedWalletSigner: SafeOp digest must be a 0x-prefixed 32-byte hex");
|
|
1421
|
-
const address = await resolveAddress();
|
|
1422
|
-
let signature;
|
|
1423
|
-
try {
|
|
1424
|
-
signature = await provider.request({
|
|
1425
|
-
method: "eth_sign",
|
|
1426
|
-
params: [address, hash]
|
|
1427
|
-
});
|
|
1428
|
-
} catch (cause) {
|
|
1429
|
-
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
1430
|
-
throw new Error(`injectedWalletSigner: eth_sign failed; enable raw-hash signing for deployment (${detail})`, { cause });
|
|
1431
|
-
}
|
|
1432
|
-
if (typeof signature !== "string") throw new Error("injectedWalletSigner: wallet returned a non-string signature");
|
|
1433
|
-
if (!ECDSA_SIGNATURE_HEX.test(signature)) throw new Error("injectedWalletSigner: wallet returned invalid signature format");
|
|
1434
|
-
if ((await recoverRawDigestSigner({
|
|
1435
|
-
hash,
|
|
1436
|
-
signature
|
|
1437
|
-
})).toLowerCase() !== address.toLowerCase()) throw new Error("injectedWalletSigner: wallet signature did not recover the selected account for the raw SafeOp digest; use a raw-hash-capable wallet or @capxul/sdk/node localPrivateKeySigner for deployed flows");
|
|
1438
|
-
return signature;
|
|
1439
|
-
}
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
async function recoverRawDigestSigner(input) {
|
|
1350
|
+
const EpochMsSchema$1 = Schema.Number.pipe(Schema.refine((n) => Number.isSafeInteger(n) && n >= 0, { message: "must be a non-negative safe integer" }));
|
|
1351
|
+
/**
|
|
1352
|
+
* `DurationMs` schema. Mirrors `toDurationMs`: non-negative safe integer.
|
|
1353
|
+
*/
|
|
1354
|
+
const DurationMsSchema$1 = Schema.Number.pipe(Schema.refine((n) => Number.isSafeInteger(n) && n >= 0, { message: "must be a non-negative safe integer" }));
|
|
1355
|
+
//#endregion
|
|
1356
|
+
//#region ../wire/src/bootstrap.ts
|
|
1357
|
+
const PUBLIC_POSTHOG_PROJECT_TOKEN = /^phc_[A-Za-z0-9_-]{1,191}$/u;
|
|
1358
|
+
const PostHogIngestOrigin = Schema.String.pipe(Schema.refine((value) => {
|
|
1443
1359
|
try {
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1360
|
+
const url = new URL(value);
|
|
1361
|
+
return 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"));
|
|
1362
|
+
} catch {
|
|
1363
|
+
return false;
|
|
1448
1364
|
}
|
|
1449
|
-
}
|
|
1365
|
+
}, { message: "must be a credential-free PostHog HTTPS ingest origin" }));
|
|
1366
|
+
const EngineeringTelemetryBootstrapPolicy = Schema.Struct({
|
|
1367
|
+
host: PostHogIngestOrigin,
|
|
1368
|
+
projectToken: Schema.String.pipe(Schema.refine((value) => PUBLIC_POSTHOG_PROJECT_TOKEN.test(value), { message: "must be a public PostHog project token" })),
|
|
1369
|
+
capxulEnv: Schema.Union([
|
|
1370
|
+
Schema.Literal("development"),
|
|
1371
|
+
Schema.Literal("staging"),
|
|
1372
|
+
Schema.Literal("production"),
|
|
1373
|
+
Schema.Literal("local")
|
|
1374
|
+
])
|
|
1375
|
+
});
|
|
1376
|
+
/**
|
|
1377
|
+
* `BootstrapEnvelope` v1.
|
|
1378
|
+
*
|
|
1379
|
+
* - `protocol`: discriminator that lets future protocols coexist on the
|
|
1380
|
+
* same endpoint without a wire-shape conflict.
|
|
1381
|
+
* - `version`: numeric version inside the protocol. Unknown versions MUST
|
|
1382
|
+
* fail decode with an INVALID_INPUT-class `CapxulError` at the SDK seam.
|
|
1383
|
+
* - `state`: the resolved bootstrap payload. Field shape matches the
|
|
1384
|
+
* `BootstrapResolution` port; the SDK consumer can pass `state`
|
|
1385
|
+
* directly (after `normalizeRuntimeUrl` on the two URL fields) into
|
|
1386
|
+
* the port without re-branding.
|
|
1387
|
+
*/
|
|
1388
|
+
const BootstrapEnvelope = Schema.Struct({
|
|
1389
|
+
protocol: Schema.Literal("capxul.bootstrap"),
|
|
1390
|
+
version: Schema.Literal(1),
|
|
1391
|
+
state: Schema.Struct({
|
|
1392
|
+
applicationId: AppIdSchema$1,
|
|
1393
|
+
chainId: ChainIdSchema$1,
|
|
1394
|
+
sessionToken: SessionTokenSchema,
|
|
1395
|
+
issuedAt: EpochMsSchema$1,
|
|
1396
|
+
expiresIn: DurationMsSchema$1,
|
|
1397
|
+
authBaseUrl: Schema.String,
|
|
1398
|
+
convexUrl: Schema.String,
|
|
1399
|
+
siteBaseUrl: Schema.String,
|
|
1400
|
+
openfortPublishableKey: Schema.String,
|
|
1401
|
+
shieldPublishableKey: Schema.String,
|
|
1402
|
+
engineeringTelemetry: Schema.optional(EngineeringTelemetryBootstrapPolicy)
|
|
1403
|
+
})
|
|
1404
|
+
});
|
|
1450
1405
|
//#endregion
|
|
1451
|
-
//#region src/
|
|
1452
|
-
const INVOCATION_OBSERVATION = Symbol("capxul.invocation-observation");
|
|
1453
|
-
const FAILURE_INVOCATION_SNAPSHOT = Symbol("capxul.failure-invocation-snapshot");
|
|
1454
|
-
/** @internal Attach one immutable, non-wire invocation snapshot to an SDK-owned object. */
|
|
1455
|
-
function attachInvocationObservation(target, source) {
|
|
1456
|
-
const snapshot = Object.freeze(source.context === void 0 ? { active: source.active } : {
|
|
1457
|
-
active: source.active,
|
|
1458
|
-
context: Object.freeze({ ...source.context })
|
|
1459
|
-
});
|
|
1460
|
-
Object.defineProperty(target, INVOCATION_OBSERVATION, {
|
|
1461
|
-
configurable: false,
|
|
1462
|
-
enumerable: false,
|
|
1463
|
-
value: snapshot,
|
|
1464
|
-
writable: false
|
|
1465
|
-
});
|
|
1466
|
-
return target;
|
|
1467
|
-
}
|
|
1468
|
-
/** @internal Read the snapshot without exposing its symbol or adding a wire field. */
|
|
1469
|
-
function readInvocationObservation(source) {
|
|
1470
|
-
if (typeof source !== "object" || source === null) return void 0;
|
|
1471
|
-
return source[INVOCATION_OBSERVATION];
|
|
1472
|
-
}
|
|
1473
|
-
/** @internal Carry a snapshot across an SDK adapter projection without resolving again. */
|
|
1474
|
-
function copyInvocationObservation(source, target) {
|
|
1475
|
-
const snapshot = readInvocationObservation(source);
|
|
1476
|
-
return snapshot === void 0 ? target : attachInvocationObservation(target, snapshot);
|
|
1477
|
-
}
|
|
1478
|
-
/** @internal Carry the public call-start delivery decision with its failure envelope. */
|
|
1479
|
-
function markFailureInvocationSnapshot(failure, snapshot) {
|
|
1480
|
-
Object.defineProperty(failure, FAILURE_INVOCATION_SNAPSHOT, {
|
|
1481
|
-
configurable: false,
|
|
1482
|
-
enumerable: false,
|
|
1483
|
-
value: Object.freeze(snapshot),
|
|
1484
|
-
writable: false
|
|
1485
|
-
});
|
|
1486
|
-
return failure;
|
|
1487
|
-
}
|
|
1488
|
-
/** @internal Read the call-start delivery decision; undefined means a direct adapter call. */
|
|
1489
|
-
function readFailureInvocationSnapshot(failure) {
|
|
1490
|
-
if (typeof failure !== "object" || failure === null) return void 0;
|
|
1491
|
-
const snapshot = failure[FAILURE_INVOCATION_SNAPSHOT];
|
|
1492
|
-
return typeof snapshot === "object" && snapshot !== null && "active" in snapshot ? snapshot : void 0;
|
|
1493
|
-
}
|
|
1494
|
-
//#endregion
|
|
1495
|
-
//#region src/domain/machine/telemetry.ts
|
|
1496
|
-
const definedEntries = (values) => Object.fromEntries(Object.entries(values).filter((entry) => entry[1] !== void 0));
|
|
1497
|
-
const SAFE_ENGINEERING_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
1498
|
-
const safeEngineeringIdentifier = (value) => value !== void 0 && SAFE_ENGINEERING_ID.test(value) ? value : void 0;
|
|
1499
|
-
const portFailureOutcome = (code) => code === "CANCELLED" ? "cancelled" : "failed";
|
|
1500
|
-
function formatTraceparent(span) {
|
|
1501
|
-
const traceId = span.traceId.toLowerCase();
|
|
1502
|
-
const spanId = span.spanId.toLowerCase();
|
|
1503
|
-
if (!/^[0-9a-f]{32}$/u.test(traceId) || !/^[0-9a-f]{16}$/u.test(spanId)) return void 0;
|
|
1504
|
-
if (/^0+$/u.test(traceId) || /^0+$/u.test(spanId)) return void 0;
|
|
1505
|
-
return `00-${traceId}-${spanId}-${span.sampled ? "01" : "00"}`;
|
|
1506
|
-
}
|
|
1507
|
-
/** Canonical, bounded fields for the one wide engineering log owned by P3. */
|
|
1508
|
-
const transitionLogFields = (record) => {
|
|
1509
|
-
const { correlation_id, journey_id, ...canonical } = record;
|
|
1510
|
-
const safeCorrelation = safeEngineeringIdentifier(correlation_id);
|
|
1511
|
-
const safeJourney = safeEngineeringIdentifier(journey_id);
|
|
1512
|
-
return definedEntries({
|
|
1513
|
-
...canonical,
|
|
1514
|
-
...safeCorrelation === void 0 ? {} : { correlation_id: safeCorrelation },
|
|
1515
|
-
...safeJourney === void 0 ? {} : { journey_id: safeJourney }
|
|
1516
|
-
});
|
|
1517
|
-
};
|
|
1518
|
-
/** The shell span owns transition/refusal classification and no other seam's fields. */
|
|
1519
|
-
const transitionSpanFields = (record) => {
|
|
1520
|
-
if (record.outcome === "applied") return {
|
|
1521
|
-
machine: record.machine,
|
|
1522
|
-
from: record.from,
|
|
1523
|
-
event: record.event,
|
|
1524
|
-
to: record.to
|
|
1525
|
-
};
|
|
1526
|
-
return {
|
|
1527
|
-
machine: record.machine,
|
|
1528
|
-
state: record.state,
|
|
1529
|
-
event: record.event,
|
|
1530
|
-
...record.outcome === "refused" ? { refused: record.refusal_code } : {}
|
|
1531
|
-
};
|
|
1532
|
-
};
|
|
1406
|
+
//#region ../wire/src/functions.ts
|
|
1533
1407
|
/**
|
|
1534
|
-
*
|
|
1535
|
-
*
|
|
1408
|
+
* Backend function paths, keyed by Convex module path then export name.
|
|
1409
|
+
*
|
|
1410
|
+
* Invariant (gate-enforced): `CAPXUL_FUNCTIONS[m][e] === \`${m}:${e}\``.
|
|
1536
1411
|
*/
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
if (held === void 0 || held.cursor !== cursor) return Effect.void;
|
|
1663
|
-
slots.delete(cursor.slot);
|
|
1664
|
-
return Effect.asVoid(Deferred.succeed(held.reply, state));
|
|
1665
|
-
};
|
|
1666
|
-
const recoverFailure = (state, cursor, failure) => {
|
|
1667
|
-
const event = spec.recoverFailure?.(state, cursor.event, failure);
|
|
1668
|
-
if (event === void 0) return Effect.succeed(state);
|
|
1669
|
-
const outcome = spec.transition(state, event);
|
|
1670
|
-
if ("refused" in outcome) return Effect.succeed(state);
|
|
1671
|
-
return Ref.set(cell, outcome.next).pipe(Effect.tap(() => {
|
|
1672
|
-
return emit(applied(state, outcome.next, event, cursor.slot, cursor.epoch, {
|
|
1673
|
-
invocation: cursor.invocation,
|
|
1674
|
-
startedAt: cursor.startedAt
|
|
1675
|
-
})).pipe(Effect.andThen(Effect.sync(() => {
|
|
1676
|
-
notify(outcome.next);
|
|
1677
|
-
})));
|
|
1678
|
-
}), Effect.as(outcome.next));
|
|
1679
|
-
};
|
|
1680
|
-
const step = (env) => Effect.gen(function* () {
|
|
1681
|
-
const state = yield* Ref.get(cell);
|
|
1682
|
-
if (env.origin !== null && (slotEpochs.get(env.origin.slot) ?? 0) !== env.origin.epoch) {
|
|
1683
|
-
const held = slots.get(env.origin.slot);
|
|
1684
|
-
if (held === void 0 || held.cursor !== env.origin) return;
|
|
1685
|
-
slots.delete(env.origin.slot);
|
|
1686
|
-
const event = { _tag: env.origin.event };
|
|
1687
|
-
yield* emit(nonApplied(state, event, "STALE_EPOCH", "refused", env));
|
|
1688
|
-
yield* failReply(held.reply, makeFailure(state, event, "STALE_EPOCH"));
|
|
1689
|
-
return;
|
|
1690
|
-
}
|
|
1691
|
-
if (env.origin !== null && env.event._tag === "~lane/exit") {
|
|
1692
|
-
const exit = env.event;
|
|
1693
|
-
const held = slots.get(env.origin.slot);
|
|
1694
|
-
if (held === void 0 || held.cursor !== env.origin) return;
|
|
1695
|
-
if (exit.defect !== void 0) {
|
|
1696
|
-
slots.delete(env.origin.slot);
|
|
1697
|
-
defect(exit.defect);
|
|
1698
|
-
const event = { _tag: env.origin.event };
|
|
1699
|
-
const failedState = yield* recoverFailure(state, env.origin, {
|
|
1700
|
-
code: "WORK_DIED",
|
|
1701
|
-
message: "Identity work died"
|
|
1702
|
-
});
|
|
1703
|
-
yield* emit(nonApplied(failedState, event, "WORK_DIED", "failed", env));
|
|
1704
|
-
yield* failReply(held.reply, makeFailure(failedState, event, "WORK_DIED"));
|
|
1705
|
-
return;
|
|
1706
|
-
}
|
|
1707
|
-
if (exit.failure !== void 0) {
|
|
1708
|
-
slots.delete(env.origin.slot);
|
|
1709
|
-
const outcome = exit.failure.code === "CANCELLED" ? "cancelled" : "failed";
|
|
1710
|
-
const event = { _tag: env.origin.event };
|
|
1711
|
-
const failedState = exit.failure === timeoutFailure || exit.failure === cancelledFailure ? yield* recoverFailure(state, env.origin, exit.failure) : state;
|
|
1712
|
-
yield* emit(nonApplied(failedState, event, exit.failure.code, outcome, env));
|
|
1713
|
-
const details = exit.failure === timeoutFailure ? { reason: "timeout" } : void 0;
|
|
1714
|
-
yield* failReply(held.reply, makeFailure(failedState, event, exit.failure.code, details));
|
|
1715
|
-
return;
|
|
1716
|
-
}
|
|
1717
|
-
yield* finish(env.origin, state);
|
|
1718
|
-
return;
|
|
1719
|
-
}
|
|
1720
|
-
const outcome = spec.transition(state, env.event);
|
|
1721
|
-
if ("refused" in outcome) {
|
|
1722
|
-
yield* emit(nonApplied(state, env.event, outcome.refused, "refused", env));
|
|
1723
|
-
yield* failReply(env.reply, makeFailure(state, env.event, outcome.refused));
|
|
1724
|
-
return;
|
|
1725
|
-
}
|
|
1726
|
-
const next = outcome.next;
|
|
1727
|
-
yield* Ref.set(cell, next);
|
|
1728
|
-
const emitApplied = (slot, epoch) => emit(applied(state, next, env.event, slot, epoch, env));
|
|
1729
|
-
if (env.origin !== null) {
|
|
1730
|
-
yield* emitApplied(env.origin.slot, env.origin.epoch);
|
|
1731
|
-
notify(next);
|
|
1732
|
-
return;
|
|
1733
|
-
}
|
|
1734
|
-
for (const slot of spec.invalidates?.(env.event) ?? []) {
|
|
1735
|
-
if (!slots.has(slot)) continue;
|
|
1736
|
-
slotEpochs.set(slot, (slotEpochs.get(slot) ?? 0) + 1);
|
|
1737
|
-
}
|
|
1738
|
-
const work = env.reply === null ? void 0 : spec.work?.({
|
|
1739
|
-
state: next,
|
|
1740
|
-
event: env.event
|
|
1741
|
-
});
|
|
1742
|
-
if (work === void 0 || env.reply === null) {
|
|
1743
|
-
const slot = spec.slot(env.event);
|
|
1744
|
-
yield* emitApplied(slot, slotEpochs.get(slot) ?? 0);
|
|
1745
|
-
notify(next);
|
|
1746
|
-
if (env.reply !== null) yield* Deferred.succeed(env.reply, next).pipe(Effect.asVoid);
|
|
1747
|
-
return;
|
|
1748
|
-
}
|
|
1749
|
-
const previous = slots.get(work.slot);
|
|
1750
|
-
if (previous !== void 0) {
|
|
1751
|
-
slots.delete(work.slot);
|
|
1752
|
-
yield* Fiber.interrupt(previous.fiber);
|
|
1753
|
-
const previousEvent = { _tag: previous.cursor.event };
|
|
1754
|
-
const reason = previous.cursor.epoch === (slotEpochs.get(work.slot) ?? 0) ? "SUPERSEDED" : "STALE_EPOCH";
|
|
1755
|
-
yield* emit(nonApplied(next, previousEvent, reason, "refused", {
|
|
1756
|
-
origin: previous.cursor,
|
|
1757
|
-
invocation: previous.cursor.invocation,
|
|
1758
|
-
startedAt: previous.cursor.startedAt
|
|
1759
|
-
}));
|
|
1760
|
-
yield* failReply(previous.reply, makeFailure(next, previousEvent, reason));
|
|
1761
|
-
}
|
|
1762
|
-
const epoch = (slotEpochs.get(work.slot) ?? 0) + 1;
|
|
1763
|
-
slotEpochs.set(work.slot, epoch);
|
|
1764
|
-
const cursor = {
|
|
1765
|
-
slot: work.slot,
|
|
1766
|
-
epoch,
|
|
1767
|
-
event: env.event._tag,
|
|
1768
|
-
startedAt: env.startedAt,
|
|
1769
|
-
invocation: env.invocation
|
|
1770
|
-
};
|
|
1771
|
-
yield* emitApplied(work.slot, epoch);
|
|
1772
|
-
notify(next);
|
|
1773
|
-
const send = (event) => Queue.offer(mailbox, {
|
|
1774
|
-
event,
|
|
1775
|
-
reply: null,
|
|
1776
|
-
origin: cursor,
|
|
1777
|
-
invocation: env.invocation,
|
|
1778
|
-
startedAt: env.startedAt
|
|
1779
|
-
}).pipe(Effect.asVoid);
|
|
1780
|
-
const complete = (event) => Queue.offer(mailbox, {
|
|
1781
|
-
event,
|
|
1782
|
-
reply: null,
|
|
1783
|
-
origin: cursor,
|
|
1784
|
-
invocation: env.invocation,
|
|
1785
|
-
startedAt: env.startedAt
|
|
1786
|
-
}).pipe(Effect.asVoid);
|
|
1787
|
-
const guarded = Effect.gen(function* () {
|
|
1788
|
-
yield* Effect.annotateCurrentSpan({
|
|
1789
|
-
slot: work.slot,
|
|
1790
|
-
epoch,
|
|
1791
|
-
port: work.port
|
|
1792
|
-
});
|
|
1793
|
-
return yield* control(work.run(send, env.invocation), env.invocation);
|
|
1794
|
-
}).pipe(Effect.withSpan(`identity.work.${work.slot}`)).pipe(Effect.matchEffect({
|
|
1795
|
-
onFailure: (failure) => complete({
|
|
1796
|
-
_tag: "~lane/exit",
|
|
1797
|
-
failure
|
|
1798
|
-
}),
|
|
1799
|
-
onSuccess: () => complete({ _tag: "~lane/exit" })
|
|
1800
|
-
}), Effect.catchDefect((cause) => complete({
|
|
1801
|
-
_tag: "~lane/exit",
|
|
1802
|
-
defect: cause
|
|
1803
|
-
})));
|
|
1804
|
-
const fiber = yield* Effect.forkChild(guarded, { startImmediately: true });
|
|
1805
|
-
slots.set(work.slot, {
|
|
1806
|
-
fiber,
|
|
1807
|
-
reply: env.reply,
|
|
1808
|
-
cursor
|
|
1809
|
-
});
|
|
1810
|
-
});
|
|
1811
|
-
const loop = yield* Effect.forkScoped(Effect.forever(Queue.take(mailbox).pipe(Effect.flatMap((env) => withInvocationParentSpan(step(env), env.invocation)))));
|
|
1812
|
-
yield* Effect.addFinalizer(() => Effect.gen(function* () {
|
|
1813
|
-
stopped = true;
|
|
1814
|
-
yield* Fiber.interrupt(loop);
|
|
1815
|
-
const state = yield* Ref.get(cell);
|
|
1816
|
-
const outstanding = [...slots.values()];
|
|
1817
|
-
slots.clear();
|
|
1818
|
-
for (const held of outstanding) {
|
|
1819
|
-
yield* Fiber.interrupt(held.fiber);
|
|
1820
|
-
const env = {
|
|
1821
|
-
origin: held.cursor,
|
|
1822
|
-
invocation: held.cursor.invocation,
|
|
1823
|
-
startedAt: held.cursor.startedAt
|
|
1824
|
-
};
|
|
1825
|
-
const event = { _tag: held.cursor.event };
|
|
1826
|
-
yield* emit(nonApplied(state, event, "ACTOR_STOPPED", "refused", env));
|
|
1827
|
-
yield* failReply(held.reply, makeFailure(state, event, "ACTOR_STOPPED"));
|
|
1828
|
-
}
|
|
1829
|
-
stateObservers.clear();
|
|
1830
|
-
transitionObservers.clear();
|
|
1831
|
-
yield* Queue.shutdown(mailbox);
|
|
1832
|
-
}));
|
|
1833
|
-
const offer = (event, reply, invocation) => {
|
|
1834
|
-
const state = Ref.getUnsafe(cell);
|
|
1835
|
-
if (stopped) return withInvocationParentSpan(emit(nonApplied(state, event, "ACTOR_STOPPED", "refused", {
|
|
1836
|
-
origin: null,
|
|
1837
|
-
invocation,
|
|
1838
|
-
startedAt: Date.now()
|
|
1839
|
-
})).pipe(Effect.andThen(Effect.fail(makeFailure(state, event, "ACTOR_STOPPED")))), invocation);
|
|
1840
|
-
const startedAt = Date.now();
|
|
1841
|
-
return Queue.offer(mailbox, {
|
|
1842
|
-
event,
|
|
1843
|
-
reply,
|
|
1844
|
-
origin: null,
|
|
1845
|
-
invocation,
|
|
1846
|
-
startedAt
|
|
1847
|
-
}).pipe(Effect.asVoid);
|
|
1848
|
-
};
|
|
1849
|
-
return {
|
|
1850
|
-
ask: (event, invocation = {}) => Effect.gen(function* () {
|
|
1851
|
-
const reply = yield* Deferred.make();
|
|
1852
|
-
yield* offer(event, reply, invocation);
|
|
1853
|
-
return yield* Deferred.await(reply);
|
|
1854
|
-
}),
|
|
1855
|
-
tell: (event, invocation = {}) => offer(event, null, invocation),
|
|
1856
|
-
snapshot: () => Ref.getUnsafe(cell),
|
|
1857
|
-
subscribe: (observer) => {
|
|
1858
|
-
stateObservers.add(observer);
|
|
1859
|
-
return () => {
|
|
1860
|
-
stateObservers.delete(observer);
|
|
1861
|
-
};
|
|
1862
|
-
},
|
|
1863
|
-
subscribeTransitions: (observer) => {
|
|
1864
|
-
transitionObservers.add(observer);
|
|
1865
|
-
return () => {
|
|
1866
|
-
transitionObservers.delete(observer);
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
};
|
|
1870
|
-
});
|
|
1871
|
-
//#endregion
|
|
1872
|
-
//#region src/surface/to-capxul-result.ts
|
|
1873
|
-
/** The one place an Effect becomes a Promise on the public surface. */
|
|
1874
|
-
async function runProgram(program, runPromise = Effect.runPromise) {
|
|
1875
|
-
const result = await runPromise(program.pipe(Effect.catchDefect((defect) => Effect.fail(Errors.unknown(defect))), Effect.result));
|
|
1876
|
-
if (Result.isFailure(result)) return {
|
|
1877
|
-
ok: false,
|
|
1878
|
-
error: result.failure
|
|
1879
|
-
};
|
|
1880
|
-
return {
|
|
1881
|
-
ok: true,
|
|
1882
|
-
value: result.success
|
|
1883
|
-
};
|
|
1884
|
-
}
|
|
1885
|
-
async function toCapxulResult(program, layer) {
|
|
1886
|
-
return runProgram(program.pipe(Effect.provide(layer)));
|
|
1887
|
-
}
|
|
1888
|
-
/**
|
|
1889
|
-
* Signal-aware bridge for the Convex-backed method bundles. The adapters fail
|
|
1890
|
-
* with `{ publicError }` rather than a bare `CapxulError`, so the wrapper is
|
|
1891
|
-
* unwrapped here — once — instead of at every call site.
|
|
1892
|
-
*/
|
|
1893
|
-
async function runIfActive(signal, operation, effect, runPromise = Effect.runPromise) {
|
|
1894
|
-
if (signal?.aborted === true) return {
|
|
1895
|
-
ok: false,
|
|
1896
|
-
error: Errors.cancelled({ operation })
|
|
1897
|
-
};
|
|
1898
|
-
const program = effect().pipe(Effect.mapError((error) => error.publicError));
|
|
1899
|
-
return runProgram(signal === void 0 ? program : Effect.raceFirst(program, Effect.callback((resume) => {
|
|
1900
|
-
if (signal.aborted) {
|
|
1901
|
-
resume(Effect.fail(Errors.cancelled({ operation })));
|
|
1902
|
-
return Effect.void;
|
|
1903
|
-
}
|
|
1904
|
-
const onAbort = () => resume(Effect.fail(Errors.cancelled({ operation })));
|
|
1905
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1906
|
-
return Effect.sync(() => signal.removeEventListener("abort", onAbort));
|
|
1907
|
-
})), runPromise);
|
|
1908
|
-
}
|
|
1909
|
-
//#endregion
|
|
1910
|
-
//#region src/surface/_shared/effect-actor-bridge.ts
|
|
1911
|
-
const facadeOutcome = (value) => {
|
|
1912
|
-
if (typeof value !== "object" || value === null || !("ok" in value) || value.ok !== false) return "succeeded";
|
|
1913
|
-
const reason = value.reason;
|
|
1914
|
-
if (reason === "CANCELLED") return "cancelled";
|
|
1915
|
-
return typeof reason === "string" && EXPECTED_OPERATION_OUTCOMES.has(reason) ? "refused" : "failed";
|
|
1916
|
-
};
|
|
1917
|
-
const rejectedFacadeOutcome = (cause) => {
|
|
1918
|
-
if (cause instanceof CapxulError) return cause.code === "CANCELLED" ? "cancelled" : "failed";
|
|
1919
|
-
if (typeof cause !== "object" || cause === null) return "failed";
|
|
1920
|
-
const candidate = cause;
|
|
1921
|
-
return candidate.code === "CANCELLED" || candidate.reason === "CANCELLED" || candidate.error?.code === "CANCELLED" || candidate.publicError?.code === "CANCELLED" ? "cancelled" : "failed";
|
|
1922
|
-
};
|
|
1923
|
-
const RESOLVED_FACADE_SPAN_FAILURE = Symbol("resolved facade span failure");
|
|
1924
|
-
/** The single facade-span bridge consumed by the renderer-neutral React facade. */
|
|
1925
|
-
async function runIdentityFacade(verb, controls, run, runPromise = Effect.runPromise) {
|
|
1926
|
-
const correlationId = safeEngineeringIdentifier(controls?.correlation_id);
|
|
1927
|
-
let resolvedFailure;
|
|
1928
|
-
return runPromise(Effect.gen(function* () {
|
|
1929
|
-
const parent = yield* Effect.currentSpan;
|
|
1930
|
-
return yield* Effect.tryPromise({
|
|
1931
|
-
try: () => run(carryInvocationParentSpan(controls, parent)),
|
|
1932
|
-
catch: (cause) => cause
|
|
1933
|
-
});
|
|
1934
|
-
}).pipe(Effect.tapError((cause) => Effect.annotateCurrentSpan({ outcome: rejectedFacadeOutcome(cause) })), Effect.flatMap((value) => {
|
|
1935
|
-
const outcome = facadeOutcome(value);
|
|
1936
|
-
return Effect.annotateCurrentSpan({ outcome }).pipe(Effect.andThen(outcome === "failed" || outcome === "cancelled" ? Effect.sync(() => {
|
|
1937
|
-
resolvedFailure = value;
|
|
1938
|
-
}).pipe(Effect.andThen(Effect.fail(RESOLVED_FACADE_SPAN_FAILURE))) : Effect.succeed(value)));
|
|
1939
|
-
}), Effect.withSpan(`identity.${verb}`), Effect.annotateSpans({
|
|
1940
|
-
verb,
|
|
1941
|
-
...correlationId === void 0 ? {} : { correlation_id: correlationId }
|
|
1942
|
-
}), Effect.catch((cause) => cause === RESOLVED_FACADE_SPAN_FAILURE ? Effect.succeed(resolvedFailure) : Effect.fail(cause))));
|
|
1943
|
-
}
|
|
1944
|
-
/**
|
|
1945
|
-
* Bridge an Effect whose typed failure carries `{ publicError: CapxulError }`
|
|
1946
|
-
* into the `Promise<CapxulResult<T>>` shape the consumer-facing method bundles
|
|
1947
|
-
* return. Applies to every port that surfaces a `publicError` (smart-account,
|
|
1948
|
-
* identity, auth-cache, account provision/deploy).
|
|
1949
|
-
*/
|
|
1950
|
-
async function runPortEffect(effect, controls, operation = "port", runPromise = Effect.runPromise) {
|
|
1951
|
-
const deadlineDelay = controls?.deadlineMs === void 0 ? void 0 : Math.max(0, controls.deadlineMs - Date.now());
|
|
1952
|
-
const timeoutMs = controls?.timeoutMs === void 0 ? deadlineDelay : deadlineDelay === void 0 ? controls.timeoutMs : Math.min(controls.timeoutMs, deadlineDelay);
|
|
1953
|
-
const observed = (timeoutMs === void 0 ? effect : effect.pipe(Effect.timeoutOrElse({
|
|
1954
|
-
duration: `${timeoutMs} millis`,
|
|
1955
|
-
orElse: () => Effect.fail({ publicError: Errors.providerTimeout("sdk", operation, timeoutMs) })
|
|
1956
|
-
}))).pipe(Effect.tap(() => Effect.annotateCurrentSpan({ outcome: "succeeded" })), Effect.tapError((failure) => {
|
|
1957
|
-
const mode = failure.publicError.details?.failure_mode;
|
|
1958
|
-
return Effect.annotateCurrentSpan({
|
|
1959
|
-
outcome: portFailureOutcome(failure.publicError.code),
|
|
1960
|
-
failure_code: failure.publicError.code,
|
|
1961
|
-
...typeof mode === "string" ? { mode } : {}
|
|
1962
|
-
});
|
|
1963
|
-
}), Effect.tapDefect(() => Effect.annotateCurrentSpan({
|
|
1964
|
-
outcome: "failed",
|
|
1965
|
-
failure_code: "UNKNOWN"
|
|
1966
|
-
})), Effect.withSpan(`identity.port.${operation}`));
|
|
1967
|
-
return runIfActive(controls?.signal, operation, () => withInvocationParentSpan(observed, controls), runPromise);
|
|
1968
|
-
}
|
|
1969
|
-
/** Map machine-internal failure state onto the public SDK error vocabulary. */
|
|
1970
|
-
function publicIdentityFailure(failure) {
|
|
1971
|
-
if (failure.error !== void 0) return failure.error;
|
|
1972
|
-
return new CapxulError(failure.code === "WORK_DIED" ? "UNKNOWN" : failure.code, failure.message, {
|
|
1973
|
-
...failure.mode === void 0 ? {} : { details: { failure_mode: failure.mode } },
|
|
1974
|
-
layer: "identity"
|
|
1975
|
-
});
|
|
1976
|
-
}
|
|
1977
|
-
/** The session carried by the actor's current snapshot, or `null`. */
|
|
1978
|
-
function sessionFromActor(actor) {
|
|
1979
|
-
return actor.authSession();
|
|
1980
|
-
}
|
|
1981
|
-
//#endregion
|
|
1982
|
-
//#region src/surface/account-deps.ts
|
|
1983
|
-
/** The single Context tag the account atom + `account.getStatus` resolve. */
|
|
1984
|
-
var AccountDepsTag = class extends Context.Service()("@capxul/sdk/AccountDeps") {};
|
|
1985
|
-
/** Wrap a bundle as the `Layer<AccountDepsTag>` the React provider consumes. */
|
|
1986
|
-
function accountDepsLayer(deps) {
|
|
1987
|
-
return Layer.succeed(AccountDepsTag, deps);
|
|
1988
|
-
}
|
|
1989
|
-
/**
|
|
1990
|
-
* `account.getStatus` as a single Effect requiring ONLY `AccountDepsTag`.
|
|
1991
|
-
* Faithful transcription of the current public method body (`account.ts`):
|
|
1992
|
-
* resolve the session (actor first, then the resume-path `authCache`), then
|
|
1993
|
-
* walk the readiness ladder. The `SmartAccountPort` failure narrows to its
|
|
1994
|
-
* `publicError`; a consumer `AccountProvider.getAddress` rejection becomes a
|
|
1995
|
-
* `providerError` (defending the public `CapxulResult` contract).
|
|
1996
|
-
*/
|
|
1997
|
-
const accountStatusProgram = Effect.gen(function* () {
|
|
1998
|
-
const deps = yield* AccountDepsTag;
|
|
1999
|
-
const session = yield* Effect.promise(() => currentSession(deps.actor, deps.authCache));
|
|
2000
|
-
if (session === null) return { status: "notAuthenticated" };
|
|
2001
|
-
const current = yield* deps.smartAccountPort.loadByAuthUserId(session.authUserId).pipe(Effect.mapError((failure) => failure.publicError));
|
|
2002
|
-
if (current !== null) return statusFromAccount(current, deps.requirement);
|
|
2003
|
-
if (deps.requirement === "none") return {
|
|
2004
|
-
status: "accountReady",
|
|
2005
|
-
requirement: deps.requirement,
|
|
2006
|
-
account: null,
|
|
2007
|
-
deployment: { status: "counterfactual" }
|
|
2008
|
-
};
|
|
2009
|
-
const signer = deps.signer;
|
|
2010
|
-
if (signer === void 0) return {
|
|
2011
|
-
status: "accountRequired",
|
|
2012
|
-
requirement: deps.requirement,
|
|
2013
|
-
chainId: deps.chainId
|
|
2014
|
-
};
|
|
2015
|
-
const signerAddress = yield* Effect.tryPromise({
|
|
2016
|
-
try: () => signer.getAddress(),
|
|
2017
|
-
catch: (cause) => signerFailure(signer.source, "getAddress", cause)
|
|
2018
|
-
});
|
|
2019
|
-
return {
|
|
2020
|
-
status: "accountProviderReady",
|
|
2021
|
-
requirement: deps.requirement,
|
|
2022
|
-
chainId: deps.chainId,
|
|
2023
|
-
source: signer.source,
|
|
2024
|
-
signerAddress
|
|
2025
|
-
};
|
|
2026
|
-
});
|
|
2027
|
-
/**
|
|
2028
|
-
* Resume-path session resolution. Consults the actor first; if the actor has
|
|
2029
|
-
* no session (e.g. page refresh before any sign-in event), reads
|
|
2030
|
-
* `authCache.getSession`. `AuthCacheError` is non-fatal at this read point —
|
|
2031
|
-
* treat as "no session" and let the consumer's `auth.signIn` path resolve.
|
|
2032
|
-
*/
|
|
2033
|
-
async function currentSession(actor, authCache) {
|
|
2034
|
-
const fromActor = sessionFromActor(actor);
|
|
2035
|
-
if (fromActor !== null) return fromActor;
|
|
2036
|
-
if (authCache === void 0) return null;
|
|
2037
|
-
const cached = await Effect.runPromise(Effect.result(authCache.getSession));
|
|
2038
|
-
if (Result.isSuccess(cached)) {
|
|
2039
|
-
if (cached.success !== null && typeof actor.restoreAuthSession === "function") {
|
|
2040
|
-
const restored = await Effect.runPromise(Effect.result(actor.restoreAuthSession(cached.success)));
|
|
2041
|
-
if (Result.isFailure(restored)) return null;
|
|
2042
|
-
}
|
|
2043
|
-
return cached.success;
|
|
2044
|
-
}
|
|
2045
|
-
return null;
|
|
2046
|
-
}
|
|
2047
|
-
/** Map a backend `SmartAccount` row onto the readiness `AccountStatus`. */
|
|
2048
|
-
function statusFromAccount(account, requirement) {
|
|
2049
|
-
if (requirement === "deployed") {
|
|
2050
|
-
if (account.deployedAt === null) return {
|
|
2051
|
-
status: "accountPrepared",
|
|
2052
|
-
requirement,
|
|
2053
|
-
account,
|
|
2054
|
-
deployment: { status: "counterfactual" }
|
|
2055
|
-
};
|
|
2056
|
-
return {
|
|
2057
|
-
status: "accountReady",
|
|
2058
|
-
requirement,
|
|
2059
|
-
account,
|
|
2060
|
-
deployment: {
|
|
2061
|
-
status: "deployed",
|
|
2062
|
-
deployedAt: account.deployedAt
|
|
2063
|
-
}
|
|
2064
|
-
};
|
|
2065
|
-
}
|
|
2066
|
-
return {
|
|
2067
|
-
status: "accountReady",
|
|
2068
|
-
requirement,
|
|
2069
|
-
account,
|
|
2070
|
-
deployment: account.deployedAt === null ? { status: "counterfactual" } : {
|
|
2071
|
-
status: "deployed",
|
|
2072
|
-
deployedAt: account.deployedAt
|
|
2073
|
-
}
|
|
2074
|
-
};
|
|
2075
|
-
}
|
|
2076
|
-
//#endregion
|
|
2077
|
-
//#region src/surface/account-lifecycle.ts
|
|
2078
|
-
function isActiveProvisioningPhase(phase) {
|
|
2079
|
-
return phase.status === "wallet" || phase.status === "identity" || phase.status === "provision" || phase.status === "deploy";
|
|
2080
|
-
}
|
|
2081
|
-
function isRequirementMet(status, requirement) {
|
|
2082
|
-
if (requirement === "none") return status.status !== "notAuthenticated";
|
|
2083
|
-
if (status.status !== "accountReady") return false;
|
|
2084
|
-
return requirement !== "deployed" || status.deployment.status === "deployed";
|
|
2085
|
-
}
|
|
2086
|
-
function mapProvisioningFailureStep(step) {
|
|
2087
|
-
switch (step) {
|
|
2088
|
-
case "wallet": return "connecting";
|
|
2089
|
-
case "identity": return "registering";
|
|
2090
|
-
case "provision":
|
|
2091
|
-
case "deploy": return "activating";
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
function mapProvisioningPhaseToSetupStep(phase) {
|
|
2095
|
-
switch (phase.status) {
|
|
2096
|
-
case "idle":
|
|
2097
|
-
case "wallet": return "connecting";
|
|
2098
|
-
case "identity": return "registering";
|
|
2099
|
-
case "provision":
|
|
2100
|
-
case "deploy": return "activating";
|
|
2101
|
-
case "ready":
|
|
2102
|
-
case "failed": return "connecting";
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
function isSettingUpLifecycle(lifecycle) {
|
|
2106
|
-
return lifecycle.status === "settingUp";
|
|
2107
|
-
}
|
|
2108
|
-
function readyLifecycle(status, accountId) {
|
|
2109
|
-
return {
|
|
2110
|
-
status: "ready",
|
|
2111
|
-
accountId,
|
|
2112
|
-
canTransact: status.status === "accountReady" && status.deployment.status === "deployed"
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
function mapAccountLifecycle(input) {
|
|
2116
|
-
const { status, phase, requirement, accountId } = input;
|
|
2117
|
-
if (status.status === "notAuthenticated") return { status: "loading" };
|
|
2118
|
-
if (phase.status === "failed") return {
|
|
2119
|
-
status: "failed",
|
|
2120
|
-
at: mapProvisioningFailureStep(phase.at),
|
|
2121
|
-
error: phase.error
|
|
2122
|
-
};
|
|
2123
|
-
if (isRequirementMet(status, requirement) || phase.status === "ready") {
|
|
2124
|
-
if (accountId === void 0) return { status: "loading" };
|
|
2125
|
-
return readyLifecycle(status, accountId);
|
|
2126
|
-
}
|
|
2127
|
-
if (isActiveProvisioningPhase(phase) || phase.status === "idle") return {
|
|
2128
|
-
status: "settingUp",
|
|
2129
|
-
step: mapProvisioningPhaseToSetupStep(phase)
|
|
2130
|
-
};
|
|
2131
|
-
return { status: "loading" };
|
|
2132
|
-
}
|
|
2133
|
-
//#endregion
|
|
2134
|
-
//#region ../wire/src/brands.ts
|
|
2135
|
-
const lowercasedString = Schema.String.pipe(Schema.decodeTo(Schema.String, {
|
|
2136
|
-
decode: SchemaGetter.transform((value) => value.toLowerCase()),
|
|
2137
|
-
encode: SchemaGetter.transform((value) => value)
|
|
2138
|
-
}));
|
|
2139
|
-
const addressSchema = (name) => lowercasedString.pipe(Schema.refine((value) => EVM_ADDRESS_RE$1.test(value), { message: `${name} must be a 0x-prefixed EVM address` }));
|
|
2140
|
-
const bytes32BrandSchema = (name) => lowercasedString.pipe(Schema.refine((value) => BYTES32_RE.test(value), { message: `${name} must be a 0x-prefixed bytes32 value` }));
|
|
2141
|
-
/** Lowercase, 0x-prefixed bytes32 evidence without a public brand. */
|
|
2142
|
-
const Bytes32Schema = bytes32BrandSchema("value");
|
|
2143
|
-
const AddressSchema$1 = addressSchema("address");
|
|
2144
|
-
const SafeAddressSchema = addressSchema("safe address");
|
|
2145
|
-
const ModuleAddressSchema = addressSchema("module address");
|
|
2146
|
-
const TxHashSchema$1 = bytes32BrandSchema("transaction hash");
|
|
2147
|
-
const RoleKeySchema = bytes32BrandSchema("role key");
|
|
2148
|
-
const AllowanceKeySchema = bytes32BrandSchema("allowance key");
|
|
2149
|
-
const BlockNumberSchema$1 = Schema.Number.pipe(Schema.refine((value) => Number.isSafeInteger(value) && value >= 0, { message: "must be a non-negative safe integer" }));
|
|
2150
|
-
const LogIndexSchema = Schema.Number.pipe(Schema.refine((value) => Number.isSafeInteger(value) && value >= 0, { message: "must be a non-negative safe integer" }));
|
|
2151
|
-
const WeiAmountSchema$1 = Schema.String.pipe(Schema.refine((value) => WEI_RE.test(value), { message: "must be a non-negative integer string" }));
|
|
2152
|
-
const SettlementIdSchema = bytes32BrandSchema("settlementId").pipe(Schema.refine((value) => value !== ZERO_BYTES32, { message: "settlementId must not be zero" }));
|
|
2153
|
-
/**
|
|
2154
|
-
* `AppId` schema. Mirrors `toAppId` from `@capxul/types`:
|
|
2155
|
-
* `app_` + Crockford-base32 ULID (26 chars, first char in `[0-7]`).
|
|
2156
|
-
*/
|
|
2157
|
-
const AppIdSchema$1 = Schema.String.pipe(Schema.refine((s) => APP_ID_RE.test(s), { message: "must be app_ plus a ULID" }));
|
|
2158
|
-
/**
|
|
2159
|
-
* `ChainId` schema. Mirrors `toChainId`: positive safe integer.
|
|
2160
|
-
*/
|
|
2161
|
-
const ChainIdSchema$1 = Schema.Number.pipe(Schema.refine((n) => Number.isSafeInteger(n) && n > 0, { message: "must be a positive safe integer" }));
|
|
2162
|
-
/**
|
|
2163
|
-
* `CurrencyCode` schema. Mirrors `toCurrencyCode`: currently supported
|
|
2164
|
-
* consumer-facing ISO-ish currency code set.
|
|
2165
|
-
*/
|
|
2166
|
-
const CurrencyCodeSchema$1 = Schema.String.pipe(Schema.refine((s) => SUPPORTED_CURRENCY_CODES.includes(s), { message: "must be a supported currency code" }));
|
|
2167
|
-
const DOCUMENT_HASH_HEX_RE = /^[0-9a-fA-F]{64}$/;
|
|
2168
|
-
/**
|
|
2169
|
-
* `DocumentHash` schema. Mirrors `toDocumentHash`: bare or 0x-prefixed bytes32,
|
|
2170
|
-
* normalized to lowercase 0x-prefixed form.
|
|
2171
|
-
*/
|
|
2172
|
-
const DocumentHashSchema = Schema.String.pipe(Schema.decodeTo(Schema.String, {
|
|
2173
|
-
decode: SchemaGetter.transform((s) => {
|
|
2174
|
-
const stripped = s.startsWith("0x") || s.startsWith("0X") ? s.slice(2) : s;
|
|
2175
|
-
return DOCUMENT_HASH_HEX_RE.test(stripped) ? `0x${stripped.toLowerCase()}` : s;
|
|
2176
|
-
}),
|
|
2177
|
-
encode: SchemaGetter.transform((s) => s)
|
|
2178
|
-
}), Schema.refine((s) => BYTES32_RE.test(s), { message: "must be 32 bytes of hex" }));
|
|
2179
|
-
/** L2 orchestrated money evidence requires a nonzero Document hash. */
|
|
2180
|
-
const NonzeroDocumentHashSchema = DocumentHashSchema.pipe(Schema.refine((value) => value !== ZERO_BYTES32, { message: "documentHash must not be zero" }));
|
|
2181
|
-
/**
|
|
2182
|
-
* `SessionToken` schema. Mirrors `toSessionToken`: non-empty string.
|
|
2183
|
-
* Issuance source distinguishes SDK-handshake tokens from auth-session
|
|
2184
|
-
* tokens; the brand itself is opaque.
|
|
2185
|
-
*/
|
|
2186
|
-
const SessionTokenSchema = Schema.String.pipe(Schema.refine((s) => s.length > 0, { message: "must be a non-empty string" }));
|
|
2187
|
-
/**
|
|
2188
|
-
* `EpochMs` schema. Mirrors `toEpochMs`: non-negative safe integer.
|
|
2189
|
-
*/
|
|
2190
|
-
const EpochMsSchema$1 = Schema.Number.pipe(Schema.refine((n) => Number.isSafeInteger(n) && n >= 0, { message: "must be a non-negative safe integer" }));
|
|
2191
|
-
/**
|
|
2192
|
-
* `DurationMs` schema. Mirrors `toDurationMs`: non-negative safe integer.
|
|
2193
|
-
*/
|
|
2194
|
-
const DurationMsSchema$1 = Schema.Number.pipe(Schema.refine((n) => Number.isSafeInteger(n) && n >= 0, { message: "must be a non-negative safe integer" }));
|
|
2195
|
-
//#endregion
|
|
2196
|
-
//#region ../wire/src/bootstrap.ts
|
|
2197
|
-
const PUBLIC_POSTHOG_PROJECT_TOKEN = /^phc_[A-Za-z0-9_-]{1,191}$/u;
|
|
2198
|
-
const PostHogIngestOrigin = Schema.String.pipe(Schema.refine((value) => {
|
|
2199
|
-
try {
|
|
2200
|
-
const url = new URL(value);
|
|
2201
|
-
return 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"));
|
|
2202
|
-
} catch {
|
|
2203
|
-
return false;
|
|
2204
|
-
}
|
|
2205
|
-
}, { message: "must be a credential-free PostHog HTTPS ingest origin" }));
|
|
2206
|
-
const EngineeringTelemetryBootstrapPolicy = Schema.Struct({
|
|
2207
|
-
host: PostHogIngestOrigin,
|
|
2208
|
-
projectToken: Schema.String.pipe(Schema.refine((value) => PUBLIC_POSTHOG_PROJECT_TOKEN.test(value), { message: "must be a public PostHog project token" })),
|
|
2209
|
-
capxulEnv: Schema.Union([
|
|
2210
|
-
Schema.Literal("development"),
|
|
2211
|
-
Schema.Literal("staging"),
|
|
2212
|
-
Schema.Literal("production")
|
|
2213
|
-
])
|
|
2214
|
-
});
|
|
2215
|
-
/**
|
|
2216
|
-
* `BootstrapEnvelope` v1.
|
|
2217
|
-
*
|
|
2218
|
-
* - `protocol`: discriminator that lets future protocols coexist on the
|
|
2219
|
-
* same endpoint without a wire-shape conflict.
|
|
2220
|
-
* - `version`: numeric version inside the protocol. Unknown versions MUST
|
|
2221
|
-
* fail decode with an INVALID_INPUT-class `CapxulError` at the SDK seam.
|
|
2222
|
-
* - `state`: the resolved bootstrap payload. Field shape matches the
|
|
2223
|
-
* `BootstrapResolution` port; the SDK consumer can pass `state`
|
|
2224
|
-
* directly (after `normalizeRuntimeUrl` on the two URL fields) into
|
|
2225
|
-
* the port without re-branding.
|
|
2226
|
-
*/
|
|
2227
|
-
const BootstrapEnvelope = Schema.Struct({
|
|
2228
|
-
protocol: Schema.Literal("capxul.bootstrap"),
|
|
2229
|
-
version: Schema.Literal(1),
|
|
2230
|
-
state: Schema.Struct({
|
|
2231
|
-
applicationId: AppIdSchema$1,
|
|
2232
|
-
chainId: ChainIdSchema$1,
|
|
2233
|
-
sessionToken: SessionTokenSchema,
|
|
2234
|
-
issuedAt: EpochMsSchema$1,
|
|
2235
|
-
expiresIn: DurationMsSchema$1,
|
|
2236
|
-
authBaseUrl: Schema.String,
|
|
2237
|
-
convexUrl: Schema.String,
|
|
2238
|
-
siteBaseUrl: Schema.String,
|
|
2239
|
-
openfortPublishableKey: Schema.String,
|
|
2240
|
-
shieldPublishableKey: Schema.String,
|
|
2241
|
-
engineeringTelemetry: Schema.optional(EngineeringTelemetryBootstrapPolicy)
|
|
2242
|
-
})
|
|
2243
|
-
});
|
|
2244
|
-
//#endregion
|
|
2245
|
-
//#region ../wire/src/functions.ts
|
|
2246
|
-
/**
|
|
2247
|
-
* Backend function paths, keyed by Convex module path then export name.
|
|
2248
|
-
*
|
|
2249
|
-
* Invariant (gate-enforced): `CAPXUL_FUNCTIONS[m][e] === \`${m}:${e}\``.
|
|
2250
|
-
*/
|
|
2251
|
-
const CAPXUL_FUNCTIONS = {
|
|
2252
|
-
"account/actions": {
|
|
2253
|
-
faucetMint: "account/actions:faucetMint",
|
|
2254
|
-
readBalance: "account/actions:readBalance"
|
|
2255
|
-
},
|
|
2256
|
-
"financialOps/addressBook": {
|
|
2257
|
-
add: "financialOps/addressBook:add",
|
|
2258
|
-
get: "financialOps/addressBook:get",
|
|
2259
|
-
hide: "financialOps/addressBook:hide",
|
|
2260
|
-
label: "financialOps/addressBook:label",
|
|
2261
|
-
list: "financialOps/addressBook:list",
|
|
2262
|
-
unhide: "financialOps/addressBook:unhide"
|
|
2263
|
-
},
|
|
2264
|
-
"financialOps/destinations": {
|
|
2265
|
-
add: "financialOps/destinations:add",
|
|
2266
|
-
list: "financialOps/destinations:list",
|
|
2267
|
-
remove: "financialOps/destinations:remove"
|
|
2268
|
-
},
|
|
2269
|
-
"financialOps/mutations": { createPayee: "financialOps/mutations:createPayee" },
|
|
2270
|
-
"financialOps/queries": {
|
|
2271
|
-
depositInstructions: "financialOps/queries:depositInstructions",
|
|
2272
|
-
getPayee: "financialOps/queries:getPayee",
|
|
2273
|
-
getPayment: "financialOps/queries:getPayment",
|
|
2274
|
-
listPayments: "financialOps/queries:listPayments",
|
|
2275
|
-
me: "financialOps/queries:me",
|
|
2276
|
-
renderStoredDocument: "financialOps/queries:renderStoredDocument",
|
|
2277
|
-
resolveHandle: "financialOps/queries:resolveHandle",
|
|
2278
|
-
resolvePayee: "financialOps/queries:resolvePayee",
|
|
2279
|
-
verifyPaymentDocument: "financialOps/queries:verifyPaymentDocument"
|
|
2280
|
-
},
|
|
2281
|
-
"financialOps/requestsInbox": {
|
|
2282
|
-
approve: "financialOps/requestsInbox:approve",
|
|
2283
|
-
cancel: "financialOps/requestsInbox:cancel",
|
|
2284
|
-
decline: "financialOps/requestsInbox:decline",
|
|
2285
|
-
get: "financialOps/requestsInbox:get",
|
|
2286
|
-
inboxList: "financialOps/requestsInbox:inboxList",
|
|
2287
|
-
issue: "financialOps/requestsInbox:issue",
|
|
2288
|
-
list: "financialOps/requestsInbox:list"
|
|
2289
|
-
},
|
|
2290
|
-
"identity/mutations": {
|
|
2291
|
-
completeOnboarding: "identity/mutations:completeOnboarding",
|
|
2292
|
-
create: "identity/mutations:create",
|
|
2293
|
-
update: "identity/mutations:update"
|
|
2294
|
-
},
|
|
2295
|
-
"identity/queries": {
|
|
2296
|
-
loadByAuthUserId: "identity/queries:loadByAuthUserId",
|
|
2297
|
-
usernameAvailable: "identity/queries:usernameAvailable"
|
|
2298
|
-
},
|
|
2299
|
-
"holdings/actions": { current: "holdings/actions:current" },
|
|
2300
|
-
"movement/activity": {
|
|
2301
|
-
annotate: "movement/activity:annotate",
|
|
2302
|
-
get: "movement/activity:get",
|
|
2303
|
-
list: "movement/activity:list"
|
|
2304
|
-
},
|
|
2305
|
-
"moneyExecution/actions": {
|
|
2306
|
-
preparePermissionExecution: "moneyExecution/actions:preparePermissionExecution",
|
|
2307
|
-
preparePaymentExecution: "moneyExecution/actions:preparePaymentExecution",
|
|
2308
|
-
submitPermissionExecution: "moneyExecution/actions:submitPermissionExecution",
|
|
2309
|
-
submitPaymentExecution: "moneyExecution/actions:submitPaymentExecution"
|
|
2310
|
-
},
|
|
2311
|
-
"moneyExecution/paymentCommandActions": {
|
|
2312
|
-
preparePaymentLifecycleExecution: "moneyExecution/paymentCommandActions:preparePaymentLifecycleExecution",
|
|
2313
|
-
prepareOrganizationPaymentExecution: "moneyExecution/paymentCommandActions:prepareOrganizationPaymentExecution",
|
|
2314
|
-
submitPaymentCommandExecution: "moneyExecution/paymentCommandActions:submitPaymentCommandExecution"
|
|
2315
|
-
},
|
|
2316
|
-
media: {
|
|
2317
|
-
generateUploadUrl: "media:generateUploadUrl",
|
|
2318
|
-
setOrgLogo: "media:setOrgLogo",
|
|
2319
|
-
setProfileImage: "media:setProfileImage"
|
|
2320
|
-
},
|
|
2321
|
-
"org/actions": {
|
|
2322
|
-
confirmBootstrap: "org/actions:confirmBootstrap",
|
|
2323
|
-
detectAndAcceptPendingInvitations: "org/actions:detectAndAcceptPendingInvitations",
|
|
2324
|
-
inviteMember: "org/actions:inviteMember",
|
|
2325
|
-
prepareBootstrap: "org/actions:prepareBootstrap",
|
|
2326
|
-
prepareFounderAccount: "org/actions:prepareFounderAccount",
|
|
2327
|
-
readTreasury: "org/actions:readTreasury",
|
|
2328
|
-
resumeBootstrapSubmission: "org/actions:resumeBootstrapSubmission",
|
|
2329
|
-
submitBootstrap: "org/actions:submitBootstrap"
|
|
2330
|
-
},
|
|
2331
|
-
"permission/actions": { verify: "permission/actions:verify" },
|
|
2332
|
-
"permission/mutations": { command: "permission/mutations:command" },
|
|
2333
|
-
"permission/queries": {
|
|
2334
|
-
authorize: "permission/queries:authorize",
|
|
2335
|
-
read: "permission/queries:read"
|
|
2336
|
-
},
|
|
2337
|
-
"org/lifecycle": {
|
|
2338
|
-
getProofReceipt: "org/lifecycle:getProofReceipt",
|
|
2339
|
-
load: "org/lifecycle:load",
|
|
2340
|
-
recordFailure: "org/lifecycle:recordFailure",
|
|
2341
|
-
retry: "org/lifecycle:retry",
|
|
2342
|
-
startOrResume: "org/lifecycle:startOrResume"
|
|
2343
|
-
},
|
|
2344
|
-
"org/mutations": {
|
|
2345
|
-
createOrg: "org/mutations:createOrg",
|
|
2346
|
-
resendInviteToken: "org/mutations:resendInviteToken"
|
|
2347
|
-
},
|
|
2348
|
-
"org/queries": {
|
|
2349
|
-
listAll: "org/queries:listAll",
|
|
2350
|
-
listMembersByOrgId: "org/queries:listMembersByOrgId",
|
|
2351
|
-
listMine: "org/queries:listMine",
|
|
2352
|
-
listRolesByOrgId: "org/queries:listRolesByOrgId",
|
|
2353
|
-
loadByOrgId: "org/queries:loadByOrgId",
|
|
2354
|
-
me: "org/queries:me"
|
|
2355
|
-
},
|
|
2356
|
-
"smartAccount/actions": {
|
|
2357
|
-
claim: "smartAccount/actions:claim",
|
|
2358
|
-
confirmDeployment: "smartAccount/actions:confirmDeployment"
|
|
2359
|
-
},
|
|
2360
|
-
"smartAccount/mutations": { provision: "smartAccount/mutations:provision" },
|
|
2361
|
-
"smartAccount/queries": {
|
|
2362
|
-
loadByAuthUserId: "smartAccount/queries:loadByAuthUserId",
|
|
2363
|
-
loadBySmartAccountAddress: "smartAccount/queries:loadBySmartAccountAddress"
|
|
2364
|
-
},
|
|
2365
|
-
system: { health: "system:health" }
|
|
1412
|
+
const CAPXUL_FUNCTIONS = {
|
|
1413
|
+
"account/actions": {
|
|
1414
|
+
faucetMint: "account/actions:faucetMint",
|
|
1415
|
+
readBalance: "account/actions:readBalance"
|
|
1416
|
+
},
|
|
1417
|
+
"financialOps/addressBook": {
|
|
1418
|
+
add: "financialOps/addressBook:add",
|
|
1419
|
+
get: "financialOps/addressBook:get",
|
|
1420
|
+
hide: "financialOps/addressBook:hide",
|
|
1421
|
+
label: "financialOps/addressBook:label",
|
|
1422
|
+
list: "financialOps/addressBook:list",
|
|
1423
|
+
unhide: "financialOps/addressBook:unhide"
|
|
1424
|
+
},
|
|
1425
|
+
"financialOps/destinations": {
|
|
1426
|
+
add: "financialOps/destinations:add",
|
|
1427
|
+
list: "financialOps/destinations:list",
|
|
1428
|
+
remove: "financialOps/destinations:remove"
|
|
1429
|
+
},
|
|
1430
|
+
"financialOps/mutations": { createPayee: "financialOps/mutations:createPayee" },
|
|
1431
|
+
"financialOps/queries": {
|
|
1432
|
+
depositInstructions: "financialOps/queries:depositInstructions",
|
|
1433
|
+
getPayee: "financialOps/queries:getPayee",
|
|
1434
|
+
getPayment: "financialOps/queries:getPayment",
|
|
1435
|
+
listPayments: "financialOps/queries:listPayments",
|
|
1436
|
+
me: "financialOps/queries:me",
|
|
1437
|
+
renderStoredDocument: "financialOps/queries:renderStoredDocument",
|
|
1438
|
+
resolveHandle: "financialOps/queries:resolveHandle",
|
|
1439
|
+
resolvePayee: "financialOps/queries:resolvePayee",
|
|
1440
|
+
verifyPaymentDocument: "financialOps/queries:verifyPaymentDocument"
|
|
1441
|
+
},
|
|
1442
|
+
"financialOps/requestsInbox": {
|
|
1443
|
+
approve: "financialOps/requestsInbox:approve",
|
|
1444
|
+
cancel: "financialOps/requestsInbox:cancel",
|
|
1445
|
+
decline: "financialOps/requestsInbox:decline",
|
|
1446
|
+
get: "financialOps/requestsInbox:get",
|
|
1447
|
+
inboxList: "financialOps/requestsInbox:inboxList",
|
|
1448
|
+
issue: "financialOps/requestsInbox:issue",
|
|
1449
|
+
list: "financialOps/requestsInbox:list"
|
|
1450
|
+
},
|
|
1451
|
+
"identity/mutations": {
|
|
1452
|
+
completeOnboarding: "identity/mutations:completeOnboarding",
|
|
1453
|
+
create: "identity/mutations:create",
|
|
1454
|
+
update: "identity/mutations:update"
|
|
1455
|
+
},
|
|
1456
|
+
"identity/queries": {
|
|
1457
|
+
loadByAuthUserId: "identity/queries:loadByAuthUserId",
|
|
1458
|
+
usernameAvailable: "identity/queries:usernameAvailable"
|
|
1459
|
+
},
|
|
1460
|
+
"holdings/actions": { current: "holdings/actions:current" },
|
|
1461
|
+
"movement/activity": {
|
|
1462
|
+
annotate: "movement/activity:annotate",
|
|
1463
|
+
get: "movement/activity:get",
|
|
1464
|
+
list: "movement/activity:list",
|
|
1465
|
+
summary: "movement/activity:summary"
|
|
1466
|
+
},
|
|
1467
|
+
"moneyExecution/actions": {
|
|
1468
|
+
preparePermissionExecution: "moneyExecution/actions:preparePermissionExecution",
|
|
1469
|
+
preparePaymentExecution: "moneyExecution/actions:preparePaymentExecution",
|
|
1470
|
+
submitPermissionExecution: "moneyExecution/actions:submitPermissionExecution",
|
|
1471
|
+
submitPaymentExecution: "moneyExecution/actions:submitPaymentExecution"
|
|
1472
|
+
},
|
|
1473
|
+
"moneyExecution/paymentCommandActions": {
|
|
1474
|
+
preparePaymentLifecycleExecution: "moneyExecution/paymentCommandActions:preparePaymentLifecycleExecution",
|
|
1475
|
+
prepareOrganizationPaymentExecution: "moneyExecution/paymentCommandActions:prepareOrganizationPaymentExecution",
|
|
1476
|
+
submitPaymentCommandExecution: "moneyExecution/paymentCommandActions:submitPaymentCommandExecution"
|
|
1477
|
+
},
|
|
1478
|
+
media: {
|
|
1479
|
+
generateUploadUrl: "media:generateUploadUrl",
|
|
1480
|
+
setOrgLogo: "media:setOrgLogo",
|
|
1481
|
+
setProfileImage: "media:setProfileImage"
|
|
1482
|
+
},
|
|
1483
|
+
"org/actions": {
|
|
1484
|
+
confirmBootstrap: "org/actions:confirmBootstrap",
|
|
1485
|
+
detectAndAcceptPendingInvitations: "org/actions:detectAndAcceptPendingInvitations",
|
|
1486
|
+
inviteMember: "org/actions:inviteMember",
|
|
1487
|
+
prepareBootstrap: "org/actions:prepareBootstrap",
|
|
1488
|
+
prepareFounderAccount: "org/actions:prepareFounderAccount",
|
|
1489
|
+
readTreasury: "org/actions:readTreasury",
|
|
1490
|
+
resumeBootstrapSubmission: "org/actions:resumeBootstrapSubmission",
|
|
1491
|
+
submitBootstrap: "org/actions:submitBootstrap"
|
|
1492
|
+
},
|
|
1493
|
+
"payroll/actions": { authorizeRun: "payroll/actions:authorizeRun" },
|
|
1494
|
+
"payroll/mutations": {
|
|
1495
|
+
removeGroup: "payroll/mutations:removeGroup",
|
|
1496
|
+
saveGroup: "payroll/mutations:saveGroup"
|
|
1497
|
+
},
|
|
1498
|
+
"payroll/queries": {
|
|
1499
|
+
groups: "payroll/queries:groups",
|
|
1500
|
+
runs: "payroll/queries:runs"
|
|
1501
|
+
},
|
|
1502
|
+
"permission/actions": { verify: "permission/actions:verify" },
|
|
1503
|
+
"permission/mutations": { command: "permission/mutations:command" },
|
|
1504
|
+
"permission/queries": {
|
|
1505
|
+
authorize: "permission/queries:authorize",
|
|
1506
|
+
read: "permission/queries:read"
|
|
1507
|
+
},
|
|
1508
|
+
"org/lifecycle": {
|
|
1509
|
+
getProofReceipt: "org/lifecycle:getProofReceipt",
|
|
1510
|
+
load: "org/lifecycle:load",
|
|
1511
|
+
recordFailure: "org/lifecycle:recordFailure",
|
|
1512
|
+
retry: "org/lifecycle:retry",
|
|
1513
|
+
startOrResume: "org/lifecycle:startOrResume"
|
|
1514
|
+
},
|
|
1515
|
+
"org/mutations": {
|
|
1516
|
+
createOrg: "org/mutations:createOrg",
|
|
1517
|
+
resendInviteToken: "org/mutations:resendInviteToken"
|
|
1518
|
+
},
|
|
1519
|
+
"org/queries": {
|
|
1520
|
+
listAll: "org/queries:listAll",
|
|
1521
|
+
listMembersByOrgId: "org/queries:listMembersByOrgId",
|
|
1522
|
+
listMine: "org/queries:listMine",
|
|
1523
|
+
listRolesByOrgId: "org/queries:listRolesByOrgId",
|
|
1524
|
+
loadByOrgId: "org/queries:loadByOrgId",
|
|
1525
|
+
me: "org/queries:me"
|
|
1526
|
+
},
|
|
1527
|
+
"smartAccount/actions": {
|
|
1528
|
+
claim: "smartAccount/actions:claim",
|
|
1529
|
+
confirmDeployment: "smartAccount/actions:confirmDeployment"
|
|
1530
|
+
},
|
|
1531
|
+
"smartAccount/mutations": { provision: "smartAccount/mutations:provision" },
|
|
1532
|
+
"smartAccount/queries": {
|
|
1533
|
+
loadByAuthUserId: "smartAccount/queries:loadByAuthUserId",
|
|
1534
|
+
loadBySmartAccountAddress: "smartAccount/queries:loadBySmartAccountAddress"
|
|
1535
|
+
},
|
|
1536
|
+
system: { health: "system:health" }
|
|
2366
1537
|
};
|
|
2367
1538
|
//#endregion
|
|
2368
1539
|
//#region ../wire/src/status.ts
|
|
@@ -3521,7 +2692,7 @@ Schema.Struct({
|
|
|
3521
2692
|
items: Schema.Array(OrganizationPaymentExecutionItemSchema),
|
|
3522
2693
|
lineage: Schema.optional(PaymentCommandLineageSchema)
|
|
3523
2694
|
});
|
|
3524
|
-
Schema.Struct({
|
|
2695
|
+
const PreparedPaymentCommandExecutionSchema = Schema.Struct({
|
|
3525
2696
|
executionId: MoneyExecutionIdSchema,
|
|
3526
2697
|
commandId: PaymentCommandIdSchema,
|
|
3527
2698
|
paymentIds: Schema.Array(PaymentIdSchema),
|
|
@@ -3594,155 +2765,1058 @@ Schema.Struct({
|
|
|
3594
2765
|
signerAddress: AddressSchema$1,
|
|
3595
2766
|
command: PermissionCommandSchema
|
|
3596
2767
|
});
|
|
3597
|
-
Schema.Struct({
|
|
3598
|
-
executionId: MoneyExecutionIdSchema,
|
|
3599
|
-
commandId: PaymentCommandIdSchema,
|
|
3600
|
-
operation: PermissionOperationSchema,
|
|
3601
|
-
orgId: nonEmpty("orgId"),
|
|
3602
|
-
command: PermissionCommandSchema,
|
|
3603
|
-
expectedRevision: nonNegativeInteger("expectedRevision"),
|
|
3604
|
-
chainId: BaseSepoliaChainIdSchema,
|
|
3605
|
-
signerAddress: AddressSchema$1,
|
|
3606
|
-
userOpSenderSafe: SafeAddressSchema,
|
|
3607
|
-
digest: Bytes32Schema,
|
|
3608
|
-
value: PermissionResourceSchema
|
|
2768
|
+
Schema.Struct({
|
|
2769
|
+
executionId: MoneyExecutionIdSchema,
|
|
2770
|
+
commandId: PaymentCommandIdSchema,
|
|
2771
|
+
operation: PermissionOperationSchema,
|
|
2772
|
+
orgId: nonEmpty("orgId"),
|
|
2773
|
+
command: PermissionCommandSchema,
|
|
2774
|
+
expectedRevision: nonNegativeInteger("expectedRevision"),
|
|
2775
|
+
chainId: BaseSepoliaChainIdSchema,
|
|
2776
|
+
signerAddress: AddressSchema$1,
|
|
2777
|
+
userOpSenderSafe: SafeAddressSchema,
|
|
2778
|
+
digest: Bytes32Schema,
|
|
2779
|
+
value: PermissionResourceSchema
|
|
2780
|
+
});
|
|
2781
|
+
Schema.Struct({
|
|
2782
|
+
status: Schema.Literal("submitted"),
|
|
2783
|
+
executionId: MoneyExecutionIdSchema,
|
|
2784
|
+
commandId: PaymentCommandIdSchema,
|
|
2785
|
+
userOpHash: Bytes32Schema,
|
|
2786
|
+
value: PermissionResourceSchema
|
|
2787
|
+
});
|
|
2788
|
+
const PayrollRunStatusSchema = Schema.Literals([
|
|
2789
|
+
"draft",
|
|
2790
|
+
"authorized",
|
|
2791
|
+
"settled"
|
|
2792
|
+
]);
|
|
2793
|
+
const PayrollRunIdSchema = Schema.String.pipe(Schema.refine((value) => /^payroll_run_[0-9a-f]{64}$/u.test(value), { message: "payrollRunId must be a payroll_run_ digest identifier" }));
|
|
2794
|
+
const SignedMinorUnitSchema = Schema.String.pipe(Schema.refine((value) => /^-?(0|[1-9][0-9]*)$/u.test(value), { message: "must be an integer minor-unit string" }));
|
|
2795
|
+
const MinorUnitSchema = Schema.String.pipe(Schema.refine((value) => /^(0|[1-9][0-9]*)$/u.test(value), { message: "must be a non-negative integer minor-unit string" }));
|
|
2796
|
+
const PayrollRunItemInputSchema = Schema.Struct({
|
|
2797
|
+
to: PaymentRef,
|
|
2798
|
+
partyId: nonEmpty("partyId"),
|
|
2799
|
+
gross: MinorUnitSchema,
|
|
2800
|
+
net: MinorUnitSchema,
|
|
2801
|
+
adjustments: Schema.Array(Schema.Struct({
|
|
2802
|
+
label: Schema.String,
|
|
2803
|
+
amount: SignedMinorUnitSchema
|
|
2804
|
+
}))
|
|
2805
|
+
});
|
|
2806
|
+
Schema.Struct({
|
|
2807
|
+
orgId: nonEmpty("orgId"),
|
|
2808
|
+
permissionId: PermissionIdSchema,
|
|
2809
|
+
requestKey: nonEmpty("requestKey"),
|
|
2810
|
+
signerAddress: AddressSchema$1,
|
|
2811
|
+
period: Schema.Struct({
|
|
2812
|
+
start: nonNegativeInteger("period.start"),
|
|
2813
|
+
end: nonNegativeInteger("period.end")
|
|
2814
|
+
}),
|
|
2815
|
+
items: Schema.Array(PayrollRunItemInputSchema)
|
|
2816
|
+
});
|
|
2817
|
+
const PayrollRunSchema = Schema.Struct({
|
|
2818
|
+
id: PayrollRunIdSchema,
|
|
2819
|
+
status: PayrollRunStatusSchema,
|
|
2820
|
+
periodStart: nonNegativeInteger("periodStart"),
|
|
2821
|
+
periodEnd: nonNegativeInteger("periodEnd"),
|
|
2822
|
+
total: FinancialOpsMoney,
|
|
2823
|
+
recipientCount: nonNegativeInteger("recipientCount")
|
|
2824
|
+
});
|
|
2825
|
+
Schema.Struct({
|
|
2826
|
+
runs: Schema.Array(PayrollRunSchema),
|
|
2827
|
+
settledThisMonth: FinancialOpsMoney
|
|
2828
|
+
});
|
|
2829
|
+
Schema.Struct({
|
|
2830
|
+
run: PayrollRunSchema,
|
|
2831
|
+
execution: PreparedPaymentCommandExecutionSchema
|
|
2832
|
+
});
|
|
2833
|
+
const PayrollGroupIdSchema = Schema.String.pipe(Schema.refine((value) => /^payroll_group_[0-9A-Z]{26}$/u.test(value), { message: "payrollGroupId must be a payroll_group_ ULID identifier" }));
|
|
2834
|
+
const PayrollGroupMemberSchema = Schema.Struct({
|
|
2835
|
+
partyId: PartyIdSchema,
|
|
2836
|
+
amount: Schema.String,
|
|
2837
|
+
currency: CurrencyCodeSchema$1
|
|
2838
|
+
});
|
|
2839
|
+
Schema.Struct({
|
|
2840
|
+
id: PayrollGroupIdSchema,
|
|
2841
|
+
name: nonEmpty("name"),
|
|
2842
|
+
tone: Schema.String,
|
|
2843
|
+
members: Schema.Array(PayrollGroupMemberSchema)
|
|
2844
|
+
});
|
|
2845
|
+
Schema.Struct({
|
|
2846
|
+
id: Schema.optional(PayrollGroupIdSchema),
|
|
2847
|
+
name: Schema.String,
|
|
2848
|
+
tone: Schema.String,
|
|
2849
|
+
members: Schema.Array(PayrollGroupMemberSchema)
|
|
2850
|
+
});
|
|
2851
|
+
`
|
|
2852
|
+
.capxul-doc{--ink:#1d1d1f;--muted:#6e6e73;--line:#e7e7ea;--accent:#0a7d4b;--bg:#fff;
|
|
2853
|
+
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
|
|
2854
|
+
color:var(--ink);background:var(--bg);max-width:44rem;margin:0 auto;padding:2.75rem 3rem;
|
|
2855
|
+
border:1px solid var(--line);border-radius:16px;box-shadow:0 1px 2px rgba(0,0,0,.04),0 12px 32px rgba(0,0,0,.06);
|
|
2856
|
+
line-height:1.5;font-size:15px;overflow-wrap:anywhere;word-break:break-word}
|
|
2857
|
+
.capxul-doc *{box-sizing:border-box;min-width:0}
|
|
2858
|
+
.capxul-doc .doc-header{display:flex;flex-direction:column;gap:1.25rem;padding-bottom:1.5rem;border-bottom:1px solid var(--line);margin-bottom:1.75rem}
|
|
2859
|
+
.capxul-doc .doc-brand{display:flex;align-items:center;gap:.5rem;color:var(--accent);font-weight:600}
|
|
2860
|
+
.capxul-doc .doc-brand-mark{font-size:1.1rem}
|
|
2861
|
+
.capxul-doc .doc-brand-name{letter-spacing:.02em}
|
|
2862
|
+
.capxul-doc .doc-headline{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;flex-wrap:wrap}
|
|
2863
|
+
.capxul-doc .doc-title{font-size:1.9rem;font-weight:700;letter-spacing:-.02em;margin:0}
|
|
2864
|
+
.capxul-doc .doc-badge{font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
|
|
2865
|
+
color:var(--accent);background:rgba(10,125,75,.1);padding:.3rem .6rem;border-radius:999px;max-width:100%;text-align:right}
|
|
2866
|
+
.capxul-doc .doc-parties{display:grid;grid-template-columns:1fr 1fr;gap:1.25rem;margin-bottom:1.75rem}
|
|
2867
|
+
.capxul-doc .doc-party{display:flex;flex-direction:column;gap:.15rem}
|
|
2868
|
+
.capxul-doc .doc-party-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
|
|
2869
|
+
.capxul-doc .doc-party-name{font-weight:600}
|
|
2870
|
+
.capxul-doc .doc-meta{display:flex;flex-direction:column;gap:.4rem;margin-bottom:1.75rem}
|
|
2871
|
+
.capxul-doc .doc-meta-row{display:flex;justify-content:space-between;gap:1rem;font-size:.92rem}
|
|
2872
|
+
.capxul-doc .doc-meta-label{color:var(--muted);flex-shrink:0}
|
|
2873
|
+
.capxul-doc .doc-meta-value{font-weight:500;text-align:right}
|
|
2874
|
+
.capxul-doc time{color:var(--ink);font-variant-numeric:tabular-nums}
|
|
2875
|
+
.capxul-doc .doc-line-items{width:100%;border-collapse:collapse;margin:.5rem 0 1.5rem;font-size:.92rem}
|
|
2876
|
+
.capxul-doc .doc-line-items th{text-align:left;font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;
|
|
2877
|
+
color:var(--muted);font-weight:600;padding:.5rem .25rem;border-bottom:1px solid var(--line)}
|
|
2878
|
+
.capxul-doc .doc-line-items td{padding:.7rem .25rem;border-bottom:1px solid var(--line)}
|
|
2879
|
+
.capxul-doc .doc-li-qty,.capxul-doc .doc-li-unit,.capxul-doc .doc-li-total{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
2880
|
+
.capxul-doc .doc-li-desc{width:100%}
|
|
2881
|
+
.capxul-doc .doc-totals{display:flex;flex-direction:column;gap:.5rem;margin-top:.5rem}
|
|
2882
|
+
.capxul-doc .doc-total-line{display:flex;justify-content:space-between;align-items:baseline;gap:1rem}
|
|
2883
|
+
.capxul-doc .doc-total-label{color:var(--muted)}
|
|
2884
|
+
.capxul-doc .doc-total-deduction .doc-amount-value{color:var(--muted)}
|
|
2885
|
+
.capxul-doc .doc-total-grand{border-top:2px solid var(--ink);margin-top:.5rem;padding-top:.75rem;font-size:1.15rem}
|
|
2886
|
+
.capxul-doc .doc-total-grand .doc-amount-value{font-weight:700}
|
|
2887
|
+
.capxul-doc .doc-amount-value{font-variant-numeric:tabular-nums;font-weight:600}
|
|
2888
|
+
.capxul-doc .doc-hero{text-align:center;padding:1.5rem 0 2rem}
|
|
2889
|
+
.capxul-doc .doc-hero-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
|
|
2890
|
+
.capxul-doc .doc-hero-amount{font-size:2.6rem;font-weight:700;letter-spacing:-.02em;margin-top:.35rem}
|
|
2891
|
+
.capxul-doc .doc-note{color:var(--ink);background:#f7f7f8;border-radius:10px;padding:.9rem 1.1rem;margin:0}
|
|
2892
|
+
.capxul-doc .doc-dest-address{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9rem}
|
|
2893
|
+
.capxul-doc .doc-footer{margin-top:1.75rem;padding-top:1.25rem;border-top:1px solid var(--line);color:var(--muted);font-size:.9rem}
|
|
2894
|
+
.capxul-doc code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.82rem;color:var(--muted);word-break:break-all}
|
|
2895
|
+
@media (max-width:540px){.capxul-doc{padding:1.75rem 1.25rem}.capxul-doc .doc-parties{grid-template-columns:1fr}}
|
|
2896
|
+
`.trim();
|
|
2897
|
+
//#endregion
|
|
2898
|
+
//#region ../wire/src/secret-material.ts
|
|
2899
|
+
const SENSITIVE_MATERIAL_PATTERNS = [
|
|
2900
|
+
/0x[a-fA-F0-9]{40,}/u,
|
|
2901
|
+
/(?:^|[^a-fA-F0-9])[a-fA-F0-9]{64}(?:$|[^a-fA-F0-9])/u,
|
|
2902
|
+
/eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/u,
|
|
2903
|
+
/(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/iu
|
|
2904
|
+
];
|
|
2905
|
+
/**
|
|
2906
|
+
* Reject: does the value carry any known secret material? Best effort — callers
|
|
2907
|
+
* drop the whole value on a match; a false negative is a leak, a false positive
|
|
2908
|
+
* merely omits an observation field.
|
|
2909
|
+
*/
|
|
2910
|
+
function containsSensitiveMaterial(value) {
|
|
2911
|
+
return SENSITIVE_MATERIAL_PATTERNS.some((pattern) => pattern.test(value));
|
|
2912
|
+
}
|
|
2913
|
+
//#endregion
|
|
2914
|
+
//#region ../wire/src/observation-context.ts
|
|
2915
|
+
/** Single bounded HTTP carrier used before a Convex action envelope exists. */
|
|
2916
|
+
const OBSERVATION_CONTEXT_HEADER = "x-capxul-observation-context";
|
|
2917
|
+
const FIELD_RULES = {
|
|
2918
|
+
application: {
|
|
2919
|
+
maxLength: 64,
|
|
2920
|
+
pattern: /^[A-Za-z0-9][A-Za-z0-9._-]*$/u
|
|
2921
|
+
},
|
|
2922
|
+
applicationId: {
|
|
2923
|
+
maxLength: 30,
|
|
2924
|
+
pattern: APP_ID_RE
|
|
2925
|
+
},
|
|
2926
|
+
release: {
|
|
2927
|
+
maxLength: 128,
|
|
2928
|
+
pattern: /^[A-Za-z0-9][A-Za-z0-9._+@:/-]*$/u
|
|
2929
|
+
},
|
|
2930
|
+
sessionId: {
|
|
2931
|
+
maxLength: 128,
|
|
2932
|
+
pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u
|
|
2933
|
+
},
|
|
2934
|
+
organizationId: {
|
|
2935
|
+
maxLength: 128,
|
|
2936
|
+
pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u
|
|
2937
|
+
},
|
|
2938
|
+
journeyId: {
|
|
2939
|
+
maxLength: 128,
|
|
2940
|
+
pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u
|
|
2941
|
+
},
|
|
2942
|
+
correlationId: {
|
|
2943
|
+
maxLength: 128,
|
|
2944
|
+
pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u
|
|
2945
|
+
},
|
|
2946
|
+
anonymousId: {
|
|
2947
|
+
maxLength: 128,
|
|
2948
|
+
pattern: /^anon_[A-Za-z0-9-]+$/u
|
|
2949
|
+
},
|
|
2950
|
+
traceparent: {
|
|
2951
|
+
maxLength: 55,
|
|
2952
|
+
pattern: /^00-[0-9a-f]{32}-[0-9a-f]{16}-0[01]$/u
|
|
2953
|
+
}
|
|
2954
|
+
};
|
|
2955
|
+
/**
|
|
2956
|
+
* Copy only the canonical allowlist and silently omit malformed/sensitive
|
|
2957
|
+
* values. Observation metadata is best effort and may never reject a domain
|
|
2958
|
+
* operation.
|
|
2959
|
+
*/
|
|
2960
|
+
function sanitizeObservationContext(input) {
|
|
2961
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return void 0;
|
|
2962
|
+
const source = input;
|
|
2963
|
+
const sanitized = {};
|
|
2964
|
+
for (const field of Object.keys(FIELD_RULES)) {
|
|
2965
|
+
const value = source[field];
|
|
2966
|
+
if (!isSafeField(field, value)) continue;
|
|
2967
|
+
sanitized[field] = value;
|
|
2968
|
+
}
|
|
2969
|
+
return Object.keys(sanitized).length === 0 ? void 0 : sanitized;
|
|
2970
|
+
}
|
|
2971
|
+
/** Encode only the sanitized allowlist; absence stays absence. */
|
|
2972
|
+
function encodeObservationContextHeader(input) {
|
|
2973
|
+
const sanitized = sanitizeObservationContext(input);
|
|
2974
|
+
return sanitized === void 0 ? void 0 : JSON.stringify(sanitized);
|
|
2975
|
+
}
|
|
2976
|
+
function isSafeField(field, value) {
|
|
2977
|
+
if (typeof value !== "string") return false;
|
|
2978
|
+
const rule = FIELD_RULES[field];
|
|
2979
|
+
return value.length > 0 && value.length <= rule.maxLength && value === value.trim() && !value.includes("://") && !containsSensitiveMaterial(value) && rule.pattern.test(value);
|
|
2980
|
+
}
|
|
2981
|
+
//#endregion
|
|
2982
|
+
//#region src/surface/_shared/provisioning-telemetry.ts
|
|
2983
|
+
async function emitProvisioningTelemetry(telemetry, smartAccount) {
|
|
2984
|
+
if (telemetry === void 0) return;
|
|
2985
|
+
await Effect.runPromise(telemetry.emit({
|
|
2986
|
+
name: "provisioning_safe_created",
|
|
2987
|
+
props: { safe_address: smartAccount.smartAccountAddress }
|
|
2988
|
+
}).pipe(Effect.catch((cause) => Effect.sync(() => reportProvisioningTelemetryFailure("error", smartAccount, cause))), Effect.catchDefect((cause) => Effect.sync(() => reportProvisioningTelemetryFailure("defect", smartAccount, cause)))));
|
|
2989
|
+
}
|
|
2990
|
+
function reportProvisioningTelemetryFailure(kind, smartAccount, cause) {
|
|
2991
|
+
if (!isProvisioningTelemetryDebugEnabled()) return;
|
|
2992
|
+
globalThis.console?.warn?.("[capxul] provisioning telemetry dropped", {
|
|
2993
|
+
kind,
|
|
2994
|
+
safeAddress: smartAccount.smartAccountAddress,
|
|
2995
|
+
cause
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
function isProvisioningTelemetryDebugEnabled() {
|
|
2999
|
+
return globalThis.process?.env?.CAPXUL_DEBUG_TELEMETRY === "1";
|
|
3000
|
+
}
|
|
3001
|
+
//#endregion
|
|
3002
|
+
//#region src/telemetry/get-failure-mode.ts
|
|
3003
|
+
/**
|
|
3004
|
+
* The five canonical {@link FailureMode} members, for runtime membership checks.
|
|
3005
|
+
* Single source of truth — tests assert against this exact set so the taxonomy
|
|
3006
|
+
* and its guard can never drift apart.
|
|
3007
|
+
*/
|
|
3008
|
+
const FAILURE_MODES = new Set([
|
|
3009
|
+
"auth-origin-mismatch",
|
|
3010
|
+
"stale-openfort-cache",
|
|
3011
|
+
"app-env-allowlist",
|
|
3012
|
+
"no-secure-context",
|
|
3013
|
+
"unknown"
|
|
3014
|
+
]);
|
|
3015
|
+
function isFailureMode(value) {
|
|
3016
|
+
return typeof value === "string" && FAILURE_MODES.has(value);
|
|
3017
|
+
}
|
|
3018
|
+
/**
|
|
3019
|
+
* Extract the structured {@link FailureMode} from a CapxulError.
|
|
3020
|
+
*
|
|
3021
|
+
* Reads `details.failure_mode` and returns it only when it is one of the five
|
|
3022
|
+
* canonical members; any other value (a legacy free string, a typo, a
|
|
3023
|
+
* non-string) yields `undefined` so downstream telemetry never reports an
|
|
3024
|
+
* unrecognised cause.
|
|
3025
|
+
*/
|
|
3026
|
+
function getFailureMode(error) {
|
|
3027
|
+
if (!isCapxulError(error)) return void 0;
|
|
3028
|
+
const details = error.details;
|
|
3029
|
+
if (details === void 0) return void 0;
|
|
3030
|
+
return isFailureMode(details.failure_mode) ? details.failure_mode : void 0;
|
|
3031
|
+
}
|
|
3032
|
+
/**
|
|
3033
|
+
* Resolve the canonical {@link FailureMode} for a `$exception`, guaranteeing a
|
|
3034
|
+
* taxonomy member is always returned — never `undefined`, never a free string.
|
|
3035
|
+
*
|
|
3036
|
+
* Resolution order:
|
|
3037
|
+
* 1. the structured mode on the error's `details.failure_mode` (already guarded);
|
|
3038
|
+
* 2. a caller-supplied `contextFailureMode`, but ONLY when it passes the same
|
|
3039
|
+
* runtime membership check — the static `FailureMode` type is erased at
|
|
3040
|
+
* runtime, so an operation string (e.g. `"signer-get-address"`) injected via
|
|
3041
|
+
* a JS caller or `as` cast is rejected here rather than leaking to telemetry;
|
|
3042
|
+
* 3. `"unknown"` otherwise, so an unclassifiable error is still tagged with a
|
|
3043
|
+
* canonical value instead of being emitted with no `failure_mode` at all.
|
|
3044
|
+
*/
|
|
3045
|
+
function resolveFailureMode(error, contextFailureMode) {
|
|
3046
|
+
return getFailureMode(error) ?? (isFailureMode(contextFailureMode) ? contextFailureMode : "unknown");
|
|
3047
|
+
}
|
|
3048
|
+
//#endregion
|
|
3049
|
+
//#region src/signer.ts
|
|
3050
|
+
const EVM_ADDRESS_HEX = /^0x[0-9a-fA-F]{40}$/;
|
|
3051
|
+
const ECDSA_SIGNATURE_HEX = /^0x[0-9a-fA-F]{130}$/;
|
|
3052
|
+
const SAFE_OP_DIGEST_HEX = /^0x[0-9a-fA-F]{64}$/;
|
|
3053
|
+
/**
|
|
3054
|
+
* The readiness store a signer reports when it runs no readiness cycle. It
|
|
3055
|
+
* fails CLOSED: a node key signer or an injected wallet never claims `ready`,
|
|
3056
|
+
* so a gate that reads this never opens on a guess.
|
|
3057
|
+
*/
|
|
3058
|
+
const UNOBSERVABLE_SIGNER_STATUS = {
|
|
3059
|
+
status: () => "unknown",
|
|
3060
|
+
subscribe: () => () => void 0
|
|
3061
|
+
};
|
|
3062
|
+
/**
|
|
3063
|
+
* Walk an error and its `cause` links once each. A self-referential chain
|
|
3064
|
+
* terminates. Both signer predicates read the chain, so they read it here.
|
|
3065
|
+
*/
|
|
3066
|
+
function* causeChain(cause) {
|
|
3067
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3068
|
+
let current = cause;
|
|
3069
|
+
while (typeof current === "object" && current !== null && !seen.has(current)) {
|
|
3070
|
+
seen.add(current);
|
|
3071
|
+
yield current;
|
|
3072
|
+
current = current.cause;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
/** Fold a signer throw into the public error contract. */
|
|
3076
|
+
function signerFailure(source, operation, cause) {
|
|
3077
|
+
let failureMode;
|
|
3078
|
+
for (const link of causeChain(cause)) {
|
|
3079
|
+
if (link instanceof CapxulError && link.code === "SIGNER_REJECTED") return link;
|
|
3080
|
+
failureMode = getFailureMode(link) ?? failureMode;
|
|
3081
|
+
const error = link;
|
|
3082
|
+
if (error.code === 4001 || error.error === "passkey_user_cancelled") return Errors.signerRejected({
|
|
3083
|
+
source,
|
|
3084
|
+
cause
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3087
|
+
return Errors.providerError("signer", operation, cause, failureMode === void 0 ? void 0 : { failure_mode: failureMode });
|
|
3088
|
+
}
|
|
3089
|
+
/**
|
|
3090
|
+
* Browser `CapxulSigner` backed by an injected EIP-1193 wallet (MetaMask, etc.).
|
|
3091
|
+
* Signs the SafeOp digest via `eth_sign`, then verifies the returned signature
|
|
3092
|
+
* recovers the selected account against that raw digest. Wallets that prefix
|
|
3093
|
+
* `eth_sign` payloads are rejected before the backend submits an invalid SafeOp.
|
|
3094
|
+
* The node key signer lives in `@capxul/sdk/node` (`localPrivateKeySigner`).
|
|
3095
|
+
*/
|
|
3096
|
+
function injectedWalletSigner(provider) {
|
|
3097
|
+
const resolveAddress = async () => {
|
|
3098
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
3099
|
+
const first = Array.isArray(accounts) ? accounts[0] : void 0;
|
|
3100
|
+
if (typeof first !== "string") throw new Error("injectedWalletSigner: wallet returned no accounts");
|
|
3101
|
+
if (!EVM_ADDRESS_HEX.test(first)) throw new Error("injectedWalletSigner: wallet returned invalid address format");
|
|
3102
|
+
return toAddress(first);
|
|
3103
|
+
};
|
|
3104
|
+
return {
|
|
3105
|
+
source: "injected-eip1193",
|
|
3106
|
+
getAddress: resolveAddress,
|
|
3107
|
+
async signUserOpHash(hash) {
|
|
3108
|
+
if (!SAFE_OP_DIGEST_HEX.test(hash)) throw new Error("injectedWalletSigner: SafeOp digest must be a 0x-prefixed 32-byte hex");
|
|
3109
|
+
const address = await resolveAddress();
|
|
3110
|
+
let signature;
|
|
3111
|
+
try {
|
|
3112
|
+
signature = await provider.request({
|
|
3113
|
+
method: "eth_sign",
|
|
3114
|
+
params: [address, hash]
|
|
3115
|
+
});
|
|
3116
|
+
} catch (cause) {
|
|
3117
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
3118
|
+
throw new Error(`injectedWalletSigner: eth_sign failed; enable raw-hash signing for deployment (${detail})`, { cause });
|
|
3119
|
+
}
|
|
3120
|
+
if (typeof signature !== "string") throw new Error("injectedWalletSigner: wallet returned a non-string signature");
|
|
3121
|
+
if (!ECDSA_SIGNATURE_HEX.test(signature)) throw new Error("injectedWalletSigner: wallet returned invalid signature format");
|
|
3122
|
+
if ((await recoverRawDigestSigner({
|
|
3123
|
+
hash,
|
|
3124
|
+
signature
|
|
3125
|
+
})).toLowerCase() !== address.toLowerCase()) throw new Error("injectedWalletSigner: wallet signature did not recover the selected account for the raw SafeOp digest; use a raw-hash-capable wallet or @capxul/sdk/node localPrivateKeySigner for deployed flows");
|
|
3126
|
+
return signature;
|
|
3127
|
+
}
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
async function recoverRawDigestSigner(input) {
|
|
3131
|
+
try {
|
|
3132
|
+
return toAddress(await recoverAddress(input));
|
|
3133
|
+
} catch (cause) {
|
|
3134
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
3135
|
+
throw new Error(`injectedWalletSigner: could not verify raw SafeOp digest signature (${detail})`, { cause });
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
//#endregion
|
|
3139
|
+
//#region src/internal/invocation-observation.ts
|
|
3140
|
+
const INVOCATION_OBSERVATION = Symbol("capxul.invocation-observation");
|
|
3141
|
+
const FAILURE_INVOCATION_SNAPSHOT = Symbol("capxul.failure-invocation-snapshot");
|
|
3142
|
+
/** @internal Attach one immutable, non-wire invocation snapshot to an SDK-owned object. */
|
|
3143
|
+
function attachInvocationObservation(target, source) {
|
|
3144
|
+
const snapshot = Object.freeze(source.context === void 0 ? { active: source.active } : {
|
|
3145
|
+
active: source.active,
|
|
3146
|
+
context: Object.freeze({ ...source.context })
|
|
3147
|
+
});
|
|
3148
|
+
Object.defineProperty(target, INVOCATION_OBSERVATION, {
|
|
3149
|
+
configurable: false,
|
|
3150
|
+
enumerable: false,
|
|
3151
|
+
value: snapshot,
|
|
3152
|
+
writable: false
|
|
3153
|
+
});
|
|
3154
|
+
return target;
|
|
3155
|
+
}
|
|
3156
|
+
/** @internal Read the snapshot without exposing its symbol or adding a wire field. */
|
|
3157
|
+
function readInvocationObservation(source) {
|
|
3158
|
+
if (typeof source !== "object" || source === null) return void 0;
|
|
3159
|
+
return source[INVOCATION_OBSERVATION];
|
|
3160
|
+
}
|
|
3161
|
+
/** @internal Carry a snapshot across an SDK adapter projection without resolving again. */
|
|
3162
|
+
function copyInvocationObservation(source, target) {
|
|
3163
|
+
const snapshot = readInvocationObservation(source);
|
|
3164
|
+
return snapshot === void 0 ? target : attachInvocationObservation(target, snapshot);
|
|
3165
|
+
}
|
|
3166
|
+
/** @internal Carry the public call-start delivery decision with its failure envelope. */
|
|
3167
|
+
function markFailureInvocationSnapshot(failure, snapshot) {
|
|
3168
|
+
Object.defineProperty(failure, FAILURE_INVOCATION_SNAPSHOT, {
|
|
3169
|
+
configurable: false,
|
|
3170
|
+
enumerable: false,
|
|
3171
|
+
value: Object.freeze(snapshot),
|
|
3172
|
+
writable: false
|
|
3173
|
+
});
|
|
3174
|
+
return failure;
|
|
3175
|
+
}
|
|
3176
|
+
/** @internal Read the call-start delivery decision; undefined means a direct adapter call. */
|
|
3177
|
+
function readFailureInvocationSnapshot(failure) {
|
|
3178
|
+
if (typeof failure !== "object" || failure === null) return void 0;
|
|
3179
|
+
const snapshot = failure[FAILURE_INVOCATION_SNAPSHOT];
|
|
3180
|
+
return typeof snapshot === "object" && snapshot !== null && "active" in snapshot ? snapshot : void 0;
|
|
3181
|
+
}
|
|
3182
|
+
//#endregion
|
|
3183
|
+
//#region src/domain/machine/telemetry.ts
|
|
3184
|
+
const definedEntries = (values) => Object.fromEntries(Object.entries(values).filter((entry) => entry[1] !== void 0));
|
|
3185
|
+
const SAFE_ENGINEERING_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
3186
|
+
const safeEngineeringIdentifier = (value) => value !== void 0 && SAFE_ENGINEERING_ID.test(value) ? value : void 0;
|
|
3187
|
+
const portFailureOutcome = (code) => code === "CANCELLED" ? "cancelled" : "failed";
|
|
3188
|
+
function formatTraceparent(span) {
|
|
3189
|
+
const traceId = span.traceId.toLowerCase();
|
|
3190
|
+
const spanId = span.spanId.toLowerCase();
|
|
3191
|
+
if (!/^[0-9a-f]{32}$/u.test(traceId) || !/^[0-9a-f]{16}$/u.test(spanId)) return void 0;
|
|
3192
|
+
if (/^0+$/u.test(traceId) || /^0+$/u.test(spanId)) return void 0;
|
|
3193
|
+
return `00-${traceId}-${spanId}-${span.sampled ? "01" : "00"}`;
|
|
3194
|
+
}
|
|
3195
|
+
/** Canonical, bounded fields for the one wide engineering log owned by P3. */
|
|
3196
|
+
const transitionLogFields = (record) => {
|
|
3197
|
+
const { correlation_id, journey_id, ...canonical } = record;
|
|
3198
|
+
const safeCorrelation = safeEngineeringIdentifier(correlation_id);
|
|
3199
|
+
const safeJourney = safeEngineeringIdentifier(journey_id);
|
|
3200
|
+
return definedEntries({
|
|
3201
|
+
...canonical,
|
|
3202
|
+
...safeCorrelation === void 0 ? {} : { correlation_id: safeCorrelation },
|
|
3203
|
+
...safeJourney === void 0 ? {} : { journey_id: safeJourney }
|
|
3204
|
+
});
|
|
3205
|
+
};
|
|
3206
|
+
/** The shell span owns transition/refusal classification and no other seam's fields. */
|
|
3207
|
+
const transitionSpanFields = (record) => {
|
|
3208
|
+
if (record.outcome === "applied") return {
|
|
3209
|
+
machine: record.machine,
|
|
3210
|
+
from: record.from,
|
|
3211
|
+
event: record.event,
|
|
3212
|
+
to: record.to
|
|
3213
|
+
};
|
|
3214
|
+
return {
|
|
3215
|
+
machine: record.machine,
|
|
3216
|
+
state: record.state,
|
|
3217
|
+
event: record.event,
|
|
3218
|
+
...record.outcome === "refused" ? { refused: record.refusal_code } : {}
|
|
3219
|
+
};
|
|
3220
|
+
};
|
|
3221
|
+
/**
|
|
3222
|
+
* Emit engineering telemetry as an isolated side effect. Exporter/logger defects
|
|
3223
|
+
* can never change the actor's transition, reply, or P3 observer cardinality.
|
|
3224
|
+
*/
|
|
3225
|
+
const logIdentityTransition = (record) => (record.outcome === "applied" ? Effect.logInfo("identity.transition") : record.outcome === "failed" ? Effect.logError("identity.transition") : Effect.logWarning("identity.transition")).pipe(Effect.annotateLogs(transitionLogFields(record)), Effect.andThen(record.outcome === "applied" ? Effect.void : Effect.fail({ code: record.outcome === "refused" ? record.refusal_code : record.error_code })), Effect.withSpan("identity.transition"), Effect.annotateSpans(transitionSpanFields(record)), Effect.catchCause(() => Effect.void));
|
|
3226
|
+
//#endregion
|
|
3227
|
+
//#region src/domain/machine/shell.ts
|
|
3228
|
+
const INVOCATION_PARENT_SPAN = Symbol("@capxul/sdk/identity-invocation-parent-span");
|
|
3229
|
+
function carryInvocationParentSpan(controls, parent) {
|
|
3230
|
+
return {
|
|
3231
|
+
...controls,
|
|
3232
|
+
[INVOCATION_PARENT_SPAN]: parent
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
function withInvocationParentSpan(effect, controls) {
|
|
3236
|
+
const parent = controls?.[INVOCATION_PARENT_SPAN];
|
|
3237
|
+
return parent === void 0 ? effect : effect.pipe(Effect.withParentSpan(parent));
|
|
3238
|
+
}
|
|
3239
|
+
var ActorFailure = class extends Error {
|
|
3240
|
+
reason;
|
|
3241
|
+
machine;
|
|
3242
|
+
state;
|
|
3243
|
+
event;
|
|
3244
|
+
details;
|
|
3245
|
+
_tag = "ActorFailure";
|
|
3246
|
+
constructor(reason, machine, state, event, details) {
|
|
3247
|
+
super(`${machine}:${state}:${event} ${reason}`);
|
|
3248
|
+
this.reason = reason;
|
|
3249
|
+
this.machine = machine;
|
|
3250
|
+
this.state = state;
|
|
3251
|
+
this.event = event;
|
|
3252
|
+
this.details = details;
|
|
3253
|
+
this.name = "ActorFailure";
|
|
3254
|
+
}
|
|
3255
|
+
};
|
|
3256
|
+
const duration = (startedAt) => Math.max(0, Date.now() - startedAt);
|
|
3257
|
+
const failReply = (reply, failure) => reply === null ? Effect.void : Effect.asVoid(Deferred.fail(reply, failure));
|
|
3258
|
+
const withCarriage = (controls) => ({
|
|
3259
|
+
...controls.correlation_id === void 0 ? {} : { correlation_id: controls.correlation_id },
|
|
3260
|
+
...controls.journey_id === void 0 ? {} : { journey_id: controls.journey_id }
|
|
3261
|
+
});
|
|
3262
|
+
const timeoutFailure = {
|
|
3263
|
+
code: "PROVIDER_ERROR",
|
|
3264
|
+
message: "Identity work timed out"
|
|
3265
|
+
};
|
|
3266
|
+
const cancelledFailure = {
|
|
3267
|
+
code: "CANCELLED",
|
|
3268
|
+
message: "Identity work cancelled"
|
|
3269
|
+
};
|
|
3270
|
+
const abort = (signal) => signal.aborted ? Effect.fail(cancelledFailure) : Effect.callback((resume) => {
|
|
3271
|
+
const onAbort = () => resume(Effect.fail(cancelledFailure));
|
|
3272
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3273
|
+
return Effect.sync(() => signal.removeEventListener("abort", onAbort));
|
|
3609
3274
|
});
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3275
|
+
const control = (effect, controls) => {
|
|
3276
|
+
let controlled = effect;
|
|
3277
|
+
const deadlineDelay = controls.deadlineMs === void 0 ? void 0 : Math.max(0, controls.deadlineMs - Date.now());
|
|
3278
|
+
const timeoutMs = controls.timeoutMs === void 0 ? deadlineDelay : deadlineDelay === void 0 ? controls.timeoutMs : Math.min(controls.timeoutMs, deadlineDelay);
|
|
3279
|
+
if (timeoutMs !== void 0) controlled = controlled.pipe(Effect.timeoutOrElse({
|
|
3280
|
+
duration: `${timeoutMs} millis`,
|
|
3281
|
+
orElse: () => Effect.fail(timeoutFailure)
|
|
3282
|
+
}));
|
|
3283
|
+
if (controls.signal !== void 0) controlled = Effect.raceFirst(controlled, abort(controls.signal));
|
|
3284
|
+
return controlled;
|
|
3285
|
+
};
|
|
3286
|
+
const boot = (spec, options = {}) => Effect.gen(function* () {
|
|
3287
|
+
const mailbox = yield* Queue.unbounded();
|
|
3288
|
+
const cell = yield* Ref.make(spec.initial);
|
|
3289
|
+
const slotEpochs = /* @__PURE__ */ new Map();
|
|
3290
|
+
const slots = /* @__PURE__ */ new Map();
|
|
3291
|
+
const stateObservers = /* @__PURE__ */ new Set();
|
|
3292
|
+
const transitionObservers = /* @__PURE__ */ new Set();
|
|
3293
|
+
let bootTransitionObserver = options.onTransition;
|
|
3294
|
+
let stopped = false;
|
|
3295
|
+
const defect = (cause) => {
|
|
3296
|
+
try {
|
|
3297
|
+
options.onDefect?.(cause);
|
|
3298
|
+
} catch {}
|
|
3299
|
+
};
|
|
3300
|
+
const emit = (record) => Effect.sync(() => {
|
|
3301
|
+
if (bootTransitionObserver !== void 0) try {
|
|
3302
|
+
bootTransitionObserver(record);
|
|
3303
|
+
} catch (cause) {
|
|
3304
|
+
bootTransitionObserver = void 0;
|
|
3305
|
+
defect(cause);
|
|
3306
|
+
}
|
|
3307
|
+
for (const observer of transitionObservers) try {
|
|
3308
|
+
observer(record);
|
|
3309
|
+
} catch (cause) {
|
|
3310
|
+
transitionObservers.delete(observer);
|
|
3311
|
+
defect(cause);
|
|
3312
|
+
}
|
|
3313
|
+
}).pipe(Effect.andThen(logIdentityTransition(record)));
|
|
3314
|
+
const notify = (state) => {
|
|
3315
|
+
for (const observer of stateObservers) try {
|
|
3316
|
+
observer(state);
|
|
3317
|
+
} catch (cause) {
|
|
3318
|
+
stateObservers.delete(observer);
|
|
3319
|
+
defect(cause);
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
const makeFailure = (state, event, reason, details) => new ActorFailure(reason, spec.machine, spec.label(state), event._tag, details);
|
|
3323
|
+
const nonApplied = (state, event, reason, outcome, env) => {
|
|
3324
|
+
const slot = env.origin?.slot ?? spec.slot(event);
|
|
3325
|
+
return copyInvocationObservation(env.invocation, {
|
|
3326
|
+
machine: spec.machine,
|
|
3327
|
+
state: spec.label(state),
|
|
3328
|
+
event: event._tag,
|
|
3329
|
+
slot,
|
|
3330
|
+
epoch: env.origin?.epoch ?? slotEpochs.get(slot) ?? 0,
|
|
3331
|
+
outcome,
|
|
3332
|
+
duration_ms: duration(env.startedAt),
|
|
3333
|
+
...withCarriage(env.invocation),
|
|
3334
|
+
...outcome === "refused" ? { refusal_code: reason } : { error_code: reason }
|
|
3335
|
+
});
|
|
3336
|
+
};
|
|
3337
|
+
const applied = (from, to, event, slot, epoch, env) => copyInvocationObservation(env.invocation, {
|
|
3338
|
+
machine: spec.machine,
|
|
3339
|
+
from: spec.label(from),
|
|
3340
|
+
event: event._tag,
|
|
3341
|
+
to: spec.label(to),
|
|
3342
|
+
slot,
|
|
3343
|
+
epoch,
|
|
3344
|
+
outcome: "applied",
|
|
3345
|
+
duration_ms: duration(env.startedAt),
|
|
3346
|
+
...withCarriage(env.invocation)
|
|
3347
|
+
});
|
|
3348
|
+
const finish = (cursor, state) => {
|
|
3349
|
+
const held = slots.get(cursor.slot);
|
|
3350
|
+
if (held === void 0 || held.cursor !== cursor) return Effect.void;
|
|
3351
|
+
slots.delete(cursor.slot);
|
|
3352
|
+
return Effect.asVoid(Deferred.succeed(held.reply, state));
|
|
3353
|
+
};
|
|
3354
|
+
const recoverFailure = (state, cursor, failure) => {
|
|
3355
|
+
const event = spec.recoverFailure?.(state, cursor.event, failure);
|
|
3356
|
+
if (event === void 0) return Effect.succeed(state);
|
|
3357
|
+
const outcome = spec.transition(state, event);
|
|
3358
|
+
if ("refused" in outcome) return Effect.succeed(state);
|
|
3359
|
+
return Ref.set(cell, outcome.next).pipe(Effect.tap(() => {
|
|
3360
|
+
return emit(applied(state, outcome.next, event, cursor.slot, cursor.epoch, {
|
|
3361
|
+
invocation: cursor.invocation,
|
|
3362
|
+
startedAt: cursor.startedAt
|
|
3363
|
+
})).pipe(Effect.andThen(Effect.sync(() => {
|
|
3364
|
+
notify(outcome.next);
|
|
3365
|
+
})));
|
|
3366
|
+
}), Effect.as(outcome.next));
|
|
3367
|
+
};
|
|
3368
|
+
const step = (env) => Effect.gen(function* () {
|
|
3369
|
+
const state = yield* Ref.get(cell);
|
|
3370
|
+
if (env.origin !== null && (slotEpochs.get(env.origin.slot) ?? 0) !== env.origin.epoch) {
|
|
3371
|
+
const held = slots.get(env.origin.slot);
|
|
3372
|
+
if (held === void 0 || held.cursor !== env.origin) return;
|
|
3373
|
+
slots.delete(env.origin.slot);
|
|
3374
|
+
const event = { _tag: env.origin.event };
|
|
3375
|
+
yield* emit(nonApplied(state, event, "STALE_EPOCH", "refused", env));
|
|
3376
|
+
yield* failReply(held.reply, makeFailure(state, event, "STALE_EPOCH"));
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
if (env.origin !== null && env.event._tag === "~lane/exit") {
|
|
3380
|
+
const exit = env.event;
|
|
3381
|
+
const held = slots.get(env.origin.slot);
|
|
3382
|
+
if (held === void 0 || held.cursor !== env.origin) return;
|
|
3383
|
+
if (exit.defect !== void 0) {
|
|
3384
|
+
slots.delete(env.origin.slot);
|
|
3385
|
+
defect(exit.defect);
|
|
3386
|
+
const event = { _tag: env.origin.event };
|
|
3387
|
+
const failedState = yield* recoverFailure(state, env.origin, {
|
|
3388
|
+
code: "WORK_DIED",
|
|
3389
|
+
message: "Identity work died"
|
|
3390
|
+
});
|
|
3391
|
+
yield* emit(nonApplied(failedState, event, "WORK_DIED", "failed", env));
|
|
3392
|
+
yield* failReply(held.reply, makeFailure(failedState, event, "WORK_DIED"));
|
|
3393
|
+
return;
|
|
3394
|
+
}
|
|
3395
|
+
if (exit.failure !== void 0) {
|
|
3396
|
+
slots.delete(env.origin.slot);
|
|
3397
|
+
const outcome = exit.failure.code === "CANCELLED" ? "cancelled" : "failed";
|
|
3398
|
+
const event = { _tag: env.origin.event };
|
|
3399
|
+
const failedState = exit.failure === timeoutFailure || exit.failure === cancelledFailure ? yield* recoverFailure(state, env.origin, exit.failure) : state;
|
|
3400
|
+
yield* emit(nonApplied(failedState, event, exit.failure.code, outcome, env));
|
|
3401
|
+
const details = exit.failure === timeoutFailure ? { reason: "timeout" } : void 0;
|
|
3402
|
+
yield* failReply(held.reply, makeFailure(failedState, event, exit.failure.code, details));
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
yield* finish(env.origin, state);
|
|
3406
|
+
return;
|
|
3407
|
+
}
|
|
3408
|
+
const outcome = spec.transition(state, env.event);
|
|
3409
|
+
if ("refused" in outcome) {
|
|
3410
|
+
yield* emit(nonApplied(state, env.event, outcome.refused, "refused", env));
|
|
3411
|
+
yield* failReply(env.reply, makeFailure(state, env.event, outcome.refused));
|
|
3412
|
+
return;
|
|
3413
|
+
}
|
|
3414
|
+
const next = outcome.next;
|
|
3415
|
+
yield* Ref.set(cell, next);
|
|
3416
|
+
const emitApplied = (slot, epoch) => emit(applied(state, next, env.event, slot, epoch, env));
|
|
3417
|
+
if (env.origin !== null) {
|
|
3418
|
+
yield* emitApplied(env.origin.slot, env.origin.epoch);
|
|
3419
|
+
notify(next);
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
for (const slot of spec.invalidates?.(env.event) ?? []) {
|
|
3423
|
+
if (!slots.has(slot)) continue;
|
|
3424
|
+
slotEpochs.set(slot, (slotEpochs.get(slot) ?? 0) + 1);
|
|
3425
|
+
}
|
|
3426
|
+
const work = env.reply === null ? void 0 : spec.work?.({
|
|
3427
|
+
state: next,
|
|
3428
|
+
event: env.event
|
|
3429
|
+
});
|
|
3430
|
+
if (work === void 0 || env.reply === null) {
|
|
3431
|
+
const slot = spec.slot(env.event);
|
|
3432
|
+
yield* emitApplied(slot, slotEpochs.get(slot) ?? 0);
|
|
3433
|
+
notify(next);
|
|
3434
|
+
if (env.reply !== null) yield* Deferred.succeed(env.reply, next).pipe(Effect.asVoid);
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3437
|
+
const previous = slots.get(work.slot);
|
|
3438
|
+
if (previous !== void 0) {
|
|
3439
|
+
slots.delete(work.slot);
|
|
3440
|
+
yield* Fiber.interrupt(previous.fiber);
|
|
3441
|
+
const previousEvent = { _tag: previous.cursor.event };
|
|
3442
|
+
const reason = previous.cursor.epoch === (slotEpochs.get(work.slot) ?? 0) ? "SUPERSEDED" : "STALE_EPOCH";
|
|
3443
|
+
yield* emit(nonApplied(next, previousEvent, reason, "refused", {
|
|
3444
|
+
origin: previous.cursor,
|
|
3445
|
+
invocation: previous.cursor.invocation,
|
|
3446
|
+
startedAt: previous.cursor.startedAt
|
|
3447
|
+
}));
|
|
3448
|
+
yield* failReply(previous.reply, makeFailure(next, previousEvent, reason));
|
|
3449
|
+
}
|
|
3450
|
+
const epoch = (slotEpochs.get(work.slot) ?? 0) + 1;
|
|
3451
|
+
slotEpochs.set(work.slot, epoch);
|
|
3452
|
+
const cursor = {
|
|
3453
|
+
slot: work.slot,
|
|
3454
|
+
epoch,
|
|
3455
|
+
event: env.event._tag,
|
|
3456
|
+
startedAt: env.startedAt,
|
|
3457
|
+
invocation: env.invocation
|
|
3458
|
+
};
|
|
3459
|
+
yield* emitApplied(work.slot, epoch);
|
|
3460
|
+
notify(next);
|
|
3461
|
+
const send = (event) => Queue.offer(mailbox, {
|
|
3462
|
+
event,
|
|
3463
|
+
reply: null,
|
|
3464
|
+
origin: cursor,
|
|
3465
|
+
invocation: env.invocation,
|
|
3466
|
+
startedAt: env.startedAt
|
|
3467
|
+
}).pipe(Effect.asVoid);
|
|
3468
|
+
const complete = (event) => Queue.offer(mailbox, {
|
|
3469
|
+
event,
|
|
3470
|
+
reply: null,
|
|
3471
|
+
origin: cursor,
|
|
3472
|
+
invocation: env.invocation,
|
|
3473
|
+
startedAt: env.startedAt
|
|
3474
|
+
}).pipe(Effect.asVoid);
|
|
3475
|
+
const guarded = Effect.gen(function* () {
|
|
3476
|
+
yield* Effect.annotateCurrentSpan({
|
|
3477
|
+
slot: work.slot,
|
|
3478
|
+
epoch,
|
|
3479
|
+
port: work.port
|
|
3480
|
+
});
|
|
3481
|
+
return yield* control(work.run(send, env.invocation), env.invocation);
|
|
3482
|
+
}).pipe(Effect.withSpan(`identity.work.${work.slot}`)).pipe(Effect.matchEffect({
|
|
3483
|
+
onFailure: (failure) => complete({
|
|
3484
|
+
_tag: "~lane/exit",
|
|
3485
|
+
failure
|
|
3486
|
+
}),
|
|
3487
|
+
onSuccess: () => complete({ _tag: "~lane/exit" })
|
|
3488
|
+
}), Effect.catchDefect((cause) => complete({
|
|
3489
|
+
_tag: "~lane/exit",
|
|
3490
|
+
defect: cause
|
|
3491
|
+
})));
|
|
3492
|
+
const fiber = yield* Effect.forkChild(guarded, { startImmediately: true });
|
|
3493
|
+
slots.set(work.slot, {
|
|
3494
|
+
fiber,
|
|
3495
|
+
reply: env.reply,
|
|
3496
|
+
cursor
|
|
3497
|
+
});
|
|
3498
|
+
});
|
|
3499
|
+
const loop = yield* Effect.forkScoped(Effect.forever(Queue.take(mailbox).pipe(Effect.flatMap((env) => withInvocationParentSpan(step(env), env.invocation)))));
|
|
3500
|
+
yield* Effect.addFinalizer(() => Effect.gen(function* () {
|
|
3501
|
+
stopped = true;
|
|
3502
|
+
yield* Fiber.interrupt(loop);
|
|
3503
|
+
const state = yield* Ref.get(cell);
|
|
3504
|
+
const outstanding = [...slots.values()];
|
|
3505
|
+
slots.clear();
|
|
3506
|
+
for (const held of outstanding) {
|
|
3507
|
+
yield* Fiber.interrupt(held.fiber);
|
|
3508
|
+
const env = {
|
|
3509
|
+
origin: held.cursor,
|
|
3510
|
+
invocation: held.cursor.invocation,
|
|
3511
|
+
startedAt: held.cursor.startedAt
|
|
3512
|
+
};
|
|
3513
|
+
const event = { _tag: held.cursor.event };
|
|
3514
|
+
yield* emit(nonApplied(state, event, "ACTOR_STOPPED", "refused", env));
|
|
3515
|
+
yield* failReply(held.reply, makeFailure(state, event, "ACTOR_STOPPED"));
|
|
3516
|
+
}
|
|
3517
|
+
stateObservers.clear();
|
|
3518
|
+
transitionObservers.clear();
|
|
3519
|
+
yield* Queue.shutdown(mailbox);
|
|
3520
|
+
}));
|
|
3521
|
+
const offer = (event, reply, invocation) => {
|
|
3522
|
+
const state = Ref.getUnsafe(cell);
|
|
3523
|
+
if (stopped) return withInvocationParentSpan(emit(nonApplied(state, event, "ACTOR_STOPPED", "refused", {
|
|
3524
|
+
origin: null,
|
|
3525
|
+
invocation,
|
|
3526
|
+
startedAt: Date.now()
|
|
3527
|
+
})).pipe(Effect.andThen(Effect.fail(makeFailure(state, event, "ACTOR_STOPPED")))), invocation);
|
|
3528
|
+
const startedAt = Date.now();
|
|
3529
|
+
return Queue.offer(mailbox, {
|
|
3530
|
+
event,
|
|
3531
|
+
reply,
|
|
3532
|
+
origin: null,
|
|
3533
|
+
invocation,
|
|
3534
|
+
startedAt
|
|
3535
|
+
}).pipe(Effect.asVoid);
|
|
3536
|
+
};
|
|
3537
|
+
return {
|
|
3538
|
+
ask: (event, invocation = {}) => Effect.gen(function* () {
|
|
3539
|
+
const reply = yield* Deferred.make();
|
|
3540
|
+
yield* offer(event, reply, invocation);
|
|
3541
|
+
return yield* Deferred.await(reply);
|
|
3542
|
+
}),
|
|
3543
|
+
tell: (event, invocation = {}) => offer(event, null, invocation),
|
|
3544
|
+
snapshot: () => Ref.getUnsafe(cell),
|
|
3545
|
+
subscribe: (observer) => {
|
|
3546
|
+
stateObservers.add(observer);
|
|
3547
|
+
return () => {
|
|
3548
|
+
stateObservers.delete(observer);
|
|
3549
|
+
};
|
|
3550
|
+
},
|
|
3551
|
+
subscribeTransitions: (observer) => {
|
|
3552
|
+
transitionObservers.add(observer);
|
|
3553
|
+
return () => {
|
|
3554
|
+
transitionObservers.delete(observer);
|
|
3555
|
+
};
|
|
3556
|
+
}
|
|
3557
|
+
};
|
|
3616
3558
|
});
|
|
3617
|
-
`
|
|
3618
|
-
.capxul-doc{--ink:#1d1d1f;--muted:#6e6e73;--line:#e7e7ea;--accent:#0a7d4b;--bg:#fff;
|
|
3619
|
-
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
|
|
3620
|
-
color:var(--ink);background:var(--bg);max-width:44rem;margin:0 auto;padding:2.75rem 3rem;
|
|
3621
|
-
border:1px solid var(--line);border-radius:16px;box-shadow:0 1px 2px rgba(0,0,0,.04),0 12px 32px rgba(0,0,0,.06);
|
|
3622
|
-
line-height:1.5;font-size:15px;overflow-wrap:anywhere;word-break:break-word}
|
|
3623
|
-
.capxul-doc *{box-sizing:border-box;min-width:0}
|
|
3624
|
-
.capxul-doc .doc-header{display:flex;flex-direction:column;gap:1.25rem;padding-bottom:1.5rem;border-bottom:1px solid var(--line);margin-bottom:1.75rem}
|
|
3625
|
-
.capxul-doc .doc-brand{display:flex;align-items:center;gap:.5rem;color:var(--accent);font-weight:600}
|
|
3626
|
-
.capxul-doc .doc-brand-mark{font-size:1.1rem}
|
|
3627
|
-
.capxul-doc .doc-brand-name{letter-spacing:.02em}
|
|
3628
|
-
.capxul-doc .doc-headline{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;flex-wrap:wrap}
|
|
3629
|
-
.capxul-doc .doc-title{font-size:1.9rem;font-weight:700;letter-spacing:-.02em;margin:0}
|
|
3630
|
-
.capxul-doc .doc-badge{font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
|
|
3631
|
-
color:var(--accent);background:rgba(10,125,75,.1);padding:.3rem .6rem;border-radius:999px;max-width:100%;text-align:right}
|
|
3632
|
-
.capxul-doc .doc-parties{display:grid;grid-template-columns:1fr 1fr;gap:1.25rem;margin-bottom:1.75rem}
|
|
3633
|
-
.capxul-doc .doc-party{display:flex;flex-direction:column;gap:.15rem}
|
|
3634
|
-
.capxul-doc .doc-party-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
|
|
3635
|
-
.capxul-doc .doc-party-name{font-weight:600}
|
|
3636
|
-
.capxul-doc .doc-meta{display:flex;flex-direction:column;gap:.4rem;margin-bottom:1.75rem}
|
|
3637
|
-
.capxul-doc .doc-meta-row{display:flex;justify-content:space-between;gap:1rem;font-size:.92rem}
|
|
3638
|
-
.capxul-doc .doc-meta-label{color:var(--muted);flex-shrink:0}
|
|
3639
|
-
.capxul-doc .doc-meta-value{font-weight:500;text-align:right}
|
|
3640
|
-
.capxul-doc time{color:var(--ink);font-variant-numeric:tabular-nums}
|
|
3641
|
-
.capxul-doc .doc-line-items{width:100%;border-collapse:collapse;margin:.5rem 0 1.5rem;font-size:.92rem}
|
|
3642
|
-
.capxul-doc .doc-line-items th{text-align:left;font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;
|
|
3643
|
-
color:var(--muted);font-weight:600;padding:.5rem .25rem;border-bottom:1px solid var(--line)}
|
|
3644
|
-
.capxul-doc .doc-line-items td{padding:.7rem .25rem;border-bottom:1px solid var(--line)}
|
|
3645
|
-
.capxul-doc .doc-li-qty,.capxul-doc .doc-li-unit,.capxul-doc .doc-li-total{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
3646
|
-
.capxul-doc .doc-li-desc{width:100%}
|
|
3647
|
-
.capxul-doc .doc-totals{display:flex;flex-direction:column;gap:.5rem;margin-top:.5rem}
|
|
3648
|
-
.capxul-doc .doc-total-line{display:flex;justify-content:space-between;align-items:baseline;gap:1rem}
|
|
3649
|
-
.capxul-doc .doc-total-label{color:var(--muted)}
|
|
3650
|
-
.capxul-doc .doc-total-deduction .doc-amount-value{color:var(--muted)}
|
|
3651
|
-
.capxul-doc .doc-total-grand{border-top:2px solid var(--ink);margin-top:.5rem;padding-top:.75rem;font-size:1.15rem}
|
|
3652
|
-
.capxul-doc .doc-total-grand .doc-amount-value{font-weight:700}
|
|
3653
|
-
.capxul-doc .doc-amount-value{font-variant-numeric:tabular-nums;font-weight:600}
|
|
3654
|
-
.capxul-doc .doc-hero{text-align:center;padding:1.5rem 0 2rem}
|
|
3655
|
-
.capxul-doc .doc-hero-label{font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
|
|
3656
|
-
.capxul-doc .doc-hero-amount{font-size:2.6rem;font-weight:700;letter-spacing:-.02em;margin-top:.35rem}
|
|
3657
|
-
.capxul-doc .doc-note{color:var(--ink);background:#f7f7f8;border-radius:10px;padding:.9rem 1.1rem;margin:0}
|
|
3658
|
-
.capxul-doc .doc-dest-address{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9rem}
|
|
3659
|
-
.capxul-doc .doc-footer{margin-top:1.75rem;padding-top:1.25rem;border-top:1px solid var(--line);color:var(--muted);font-size:.9rem}
|
|
3660
|
-
.capxul-doc code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.82rem;color:var(--muted);word-break:break-all}
|
|
3661
|
-
@media (max-width:540px){.capxul-doc{padding:1.75rem 1.25rem}.capxul-doc .doc-parties{grid-template-columns:1fr}}
|
|
3662
|
-
`.trim();
|
|
3663
3559
|
//#endregion
|
|
3664
|
-
//#region
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3560
|
+
//#region src/surface/to-capxul-result.ts
|
|
3561
|
+
/** The one place an Effect becomes a Promise on the public surface. */
|
|
3562
|
+
async function runProgram(program, runPromise = Effect.runPromise) {
|
|
3563
|
+
const result = await runPromise(program.pipe(Effect.catchDefect((defect) => Effect.fail(Errors.unknown(defect))), Effect.result));
|
|
3564
|
+
if (Result.isFailure(result)) return {
|
|
3565
|
+
ok: false,
|
|
3566
|
+
error: result.failure
|
|
3567
|
+
};
|
|
3568
|
+
return {
|
|
3569
|
+
ok: true,
|
|
3570
|
+
value: result.success
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
async function toCapxulResult(program, layer) {
|
|
3574
|
+
return runProgram(program.pipe(Effect.provide(layer)));
|
|
3575
|
+
}
|
|
3671
3576
|
/**
|
|
3672
|
-
*
|
|
3673
|
-
*
|
|
3674
|
-
*
|
|
3577
|
+
* Signal-aware bridge for the Convex-backed method bundles. The adapters fail
|
|
3578
|
+
* with `{ publicError }` rather than a bare `CapxulError`, so the wrapper is
|
|
3579
|
+
* unwrapped here — once — instead of at every call site.
|
|
3675
3580
|
*/
|
|
3676
|
-
function
|
|
3677
|
-
|
|
3581
|
+
async function runIfActive(signal, operation, effect, runPromise = Effect.runPromise) {
|
|
3582
|
+
if (signal?.aborted === true) return {
|
|
3583
|
+
ok: false,
|
|
3584
|
+
error: Errors.cancelled({ operation })
|
|
3585
|
+
};
|
|
3586
|
+
const program = effect().pipe(Effect.mapError((error) => error.publicError));
|
|
3587
|
+
return runProgram(signal === void 0 ? program : Effect.raceFirst(program, Effect.callback((resume) => {
|
|
3588
|
+
if (signal.aborted) {
|
|
3589
|
+
resume(Effect.fail(Errors.cancelled({ operation })));
|
|
3590
|
+
return Effect.void;
|
|
3591
|
+
}
|
|
3592
|
+
const onAbort = () => resume(Effect.fail(Errors.cancelled({ operation })));
|
|
3593
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3594
|
+
return Effect.sync(() => signal.removeEventListener("abort", onAbort));
|
|
3595
|
+
})), runPromise);
|
|
3596
|
+
}
|
|
3597
|
+
//#endregion
|
|
3598
|
+
//#region src/surface/_shared/effect-actor-bridge.ts
|
|
3599
|
+
const facadeOutcome = (value) => {
|
|
3600
|
+
if (typeof value !== "object" || value === null || !("ok" in value) || value.ok !== false) return "succeeded";
|
|
3601
|
+
const reason = value.reason;
|
|
3602
|
+
if (reason === "CANCELLED") return "cancelled";
|
|
3603
|
+
return typeof reason === "string" && EXPECTED_OPERATION_OUTCOMES.has(reason) ? "refused" : "failed";
|
|
3604
|
+
};
|
|
3605
|
+
const rejectedFacadeOutcome = (cause) => {
|
|
3606
|
+
if (cause instanceof CapxulError) return cause.code === "CANCELLED" ? "cancelled" : "failed";
|
|
3607
|
+
if (typeof cause !== "object" || cause === null) return "failed";
|
|
3608
|
+
const candidate = cause;
|
|
3609
|
+
return candidate.code === "CANCELLED" || candidate.reason === "CANCELLED" || candidate.error?.code === "CANCELLED" || candidate.publicError?.code === "CANCELLED" ? "cancelled" : "failed";
|
|
3610
|
+
};
|
|
3611
|
+
const RESOLVED_FACADE_SPAN_FAILURE = Symbol("resolved facade span failure");
|
|
3612
|
+
/** The single facade-span bridge consumed by the renderer-neutral React facade. */
|
|
3613
|
+
async function runIdentityFacade(verb, controls, run, runPromise = Effect.runPromise) {
|
|
3614
|
+
const correlationId = safeEngineeringIdentifier(controls?.correlation_id);
|
|
3615
|
+
let resolvedFailure;
|
|
3616
|
+
return runPromise(Effect.gen(function* () {
|
|
3617
|
+
const parent = yield* Effect.currentSpan;
|
|
3618
|
+
return yield* Effect.tryPromise({
|
|
3619
|
+
try: () => run(carryInvocationParentSpan(controls, parent)),
|
|
3620
|
+
catch: (cause) => cause
|
|
3621
|
+
});
|
|
3622
|
+
}).pipe(Effect.tapError((cause) => Effect.annotateCurrentSpan({ outcome: rejectedFacadeOutcome(cause) })), Effect.flatMap((value) => {
|
|
3623
|
+
const outcome = facadeOutcome(value);
|
|
3624
|
+
return Effect.annotateCurrentSpan({ outcome }).pipe(Effect.andThen(outcome === "failed" || outcome === "cancelled" ? Effect.sync(() => {
|
|
3625
|
+
resolvedFailure = value;
|
|
3626
|
+
}).pipe(Effect.andThen(Effect.fail(RESOLVED_FACADE_SPAN_FAILURE))) : Effect.succeed(value)));
|
|
3627
|
+
}), Effect.withSpan(`identity.${verb}`), Effect.annotateSpans({
|
|
3628
|
+
verb,
|
|
3629
|
+
...correlationId === void 0 ? {} : { correlation_id: correlationId }
|
|
3630
|
+
}), Effect.catch((cause) => cause === RESOLVED_FACADE_SPAN_FAILURE ? Effect.succeed(resolvedFailure) : Effect.fail(cause))));
|
|
3631
|
+
}
|
|
3632
|
+
/**
|
|
3633
|
+
* Bridge an Effect whose typed failure carries `{ publicError: CapxulError }`
|
|
3634
|
+
* into the `Promise<CapxulResult<T>>` shape the consumer-facing method bundles
|
|
3635
|
+
* return. Applies to every port that surfaces a `publicError` (smart-account,
|
|
3636
|
+
* identity, auth-cache, account provision/deploy).
|
|
3637
|
+
*/
|
|
3638
|
+
async function runPortEffect(effect, controls, operation = "port", runPromise = Effect.runPromise) {
|
|
3639
|
+
const deadlineDelay = controls?.deadlineMs === void 0 ? void 0 : Math.max(0, controls.deadlineMs - Date.now());
|
|
3640
|
+
const timeoutMs = controls?.timeoutMs === void 0 ? deadlineDelay : deadlineDelay === void 0 ? controls.timeoutMs : Math.min(controls.timeoutMs, deadlineDelay);
|
|
3641
|
+
const observed = (timeoutMs === void 0 ? effect : effect.pipe(Effect.timeoutOrElse({
|
|
3642
|
+
duration: `${timeoutMs} millis`,
|
|
3643
|
+
orElse: () => Effect.fail({ publicError: Errors.providerTimeout("sdk", operation, timeoutMs) })
|
|
3644
|
+
}))).pipe(Effect.tap(() => Effect.annotateCurrentSpan({ outcome: "succeeded" })), Effect.tapError((failure) => {
|
|
3645
|
+
const mode = failure.publicError.details?.failure_mode;
|
|
3646
|
+
return Effect.annotateCurrentSpan({
|
|
3647
|
+
outcome: portFailureOutcome(failure.publicError.code),
|
|
3648
|
+
failure_code: failure.publicError.code,
|
|
3649
|
+
...typeof mode === "string" ? { mode } : {}
|
|
3650
|
+
});
|
|
3651
|
+
}), Effect.tapDefect(() => Effect.annotateCurrentSpan({
|
|
3652
|
+
outcome: "failed",
|
|
3653
|
+
failure_code: "UNKNOWN"
|
|
3654
|
+
})), Effect.withSpan(`identity.port.${operation}`));
|
|
3655
|
+
return runIfActive(controls?.signal, operation, () => withInvocationParentSpan(observed, controls), runPromise);
|
|
3656
|
+
}
|
|
3657
|
+
/** Map machine-internal failure state onto the public SDK error vocabulary. */
|
|
3658
|
+
function publicIdentityFailure(failure) {
|
|
3659
|
+
if (failure.error !== void 0) return failure.error;
|
|
3660
|
+
return new CapxulError(failure.code === "WORK_DIED" ? "UNKNOWN" : failure.code, failure.message, {
|
|
3661
|
+
...failure.mode === void 0 ? {} : { details: { failure_mode: failure.mode } },
|
|
3662
|
+
layer: "identity"
|
|
3663
|
+
});
|
|
3664
|
+
}
|
|
3665
|
+
/** The session carried by the actor's current snapshot, or `null`. */
|
|
3666
|
+
function sessionFromActor(actor) {
|
|
3667
|
+
return actor.authSession();
|
|
3668
|
+
}
|
|
3669
|
+
//#endregion
|
|
3670
|
+
//#region src/surface/account-deps.ts
|
|
3671
|
+
/** The single Context tag the account atom + `account.getStatus` resolve. */
|
|
3672
|
+
var AccountDepsTag = class extends Context.Service()("@capxul/sdk/AccountDeps") {};
|
|
3673
|
+
/** Wrap a bundle as the `Layer<AccountDepsTag>` the React provider consumes. */
|
|
3674
|
+
function accountDepsLayer(deps) {
|
|
3675
|
+
return Layer.succeed(AccountDepsTag, deps);
|
|
3676
|
+
}
|
|
3677
|
+
/**
|
|
3678
|
+
* `account.getStatus` as a single Effect requiring ONLY `AccountDepsTag`.
|
|
3679
|
+
* Faithful transcription of the current public method body (`account.ts`):
|
|
3680
|
+
* resolve the session (actor first, then the resume-path `authCache`), then
|
|
3681
|
+
* walk the readiness ladder. The `SmartAccountPort` failure narrows to its
|
|
3682
|
+
* `publicError`; a consumer `AccountProvider.getAddress` rejection becomes a
|
|
3683
|
+
* `providerError` (defending the public `CapxulResult` contract).
|
|
3684
|
+
*/
|
|
3685
|
+
const accountStatusProgram = Effect.gen(function* () {
|
|
3686
|
+
const deps = yield* AccountDepsTag;
|
|
3687
|
+
const session = yield* Effect.promise(() => currentSession(deps.actor, deps.authCache));
|
|
3688
|
+
if (session === null) return { status: "notAuthenticated" };
|
|
3689
|
+
const current = yield* deps.smartAccountPort.loadByAuthUserId(session.authUserId).pipe(Effect.mapError((failure) => failure.publicError));
|
|
3690
|
+
if (current !== null) return statusFromAccount(current, deps.requirement);
|
|
3691
|
+
if (deps.requirement === "none") return {
|
|
3692
|
+
status: "accountReady",
|
|
3693
|
+
requirement: deps.requirement,
|
|
3694
|
+
account: null,
|
|
3695
|
+
deployment: { status: "counterfactual" }
|
|
3696
|
+
};
|
|
3697
|
+
const signer = deps.signer;
|
|
3698
|
+
if (signer === void 0) return {
|
|
3699
|
+
status: "accountRequired",
|
|
3700
|
+
requirement: deps.requirement,
|
|
3701
|
+
chainId: deps.chainId
|
|
3702
|
+
};
|
|
3703
|
+
const signerAddress = yield* Effect.tryPromise({
|
|
3704
|
+
try: () => signer.getAddress(),
|
|
3705
|
+
catch: (cause) => signerFailure(signer.source, "getAddress", cause)
|
|
3706
|
+
});
|
|
3707
|
+
return {
|
|
3708
|
+
status: "accountProviderReady",
|
|
3709
|
+
requirement: deps.requirement,
|
|
3710
|
+
chainId: deps.chainId,
|
|
3711
|
+
source: signer.source,
|
|
3712
|
+
signerAddress
|
|
3713
|
+
};
|
|
3714
|
+
});
|
|
3715
|
+
/**
|
|
3716
|
+
* Resume-path session resolution. Consults the actor first; if the actor has
|
|
3717
|
+
* no session (e.g. page refresh before any sign-in event), reads
|
|
3718
|
+
* `authCache.getSession`. `AuthCacheError` is non-fatal at this read point —
|
|
3719
|
+
* treat as "no session" and let the consumer's `auth.signIn` path resolve.
|
|
3720
|
+
*/
|
|
3721
|
+
async function currentSession(actor, authCache) {
|
|
3722
|
+
const fromActor = sessionFromActor(actor);
|
|
3723
|
+
if (fromActor !== null) return fromActor;
|
|
3724
|
+
if (authCache === void 0) return null;
|
|
3725
|
+
const cached = await Effect.runPromise(Effect.result(authCache.getSession));
|
|
3726
|
+
if (Result.isSuccess(cached)) {
|
|
3727
|
+
if (cached.success !== null && typeof actor.restoreAuthSession === "function") {
|
|
3728
|
+
const restored = await Effect.runPromise(Effect.result(actor.restoreAuthSession(cached.success)));
|
|
3729
|
+
if (Result.isFailure(restored)) return null;
|
|
3730
|
+
}
|
|
3731
|
+
return cached.success;
|
|
3732
|
+
}
|
|
3733
|
+
return null;
|
|
3734
|
+
}
|
|
3735
|
+
/** Map a backend `SmartAccount` row onto the readiness `AccountStatus`. */
|
|
3736
|
+
function statusFromAccount(account, requirement) {
|
|
3737
|
+
if (requirement === "deployed") {
|
|
3738
|
+
if (account.deployedAt === null) return {
|
|
3739
|
+
status: "accountPrepared",
|
|
3740
|
+
requirement,
|
|
3741
|
+
account,
|
|
3742
|
+
deployment: { status: "counterfactual" }
|
|
3743
|
+
};
|
|
3744
|
+
return {
|
|
3745
|
+
status: "accountReady",
|
|
3746
|
+
requirement,
|
|
3747
|
+
account,
|
|
3748
|
+
deployment: {
|
|
3749
|
+
status: "deployed",
|
|
3750
|
+
deployedAt: account.deployedAt
|
|
3751
|
+
}
|
|
3752
|
+
};
|
|
3753
|
+
}
|
|
3754
|
+
return {
|
|
3755
|
+
status: "accountReady",
|
|
3756
|
+
requirement,
|
|
3757
|
+
account,
|
|
3758
|
+
deployment: account.deployedAt === null ? { status: "counterfactual" } : {
|
|
3759
|
+
status: "deployed",
|
|
3760
|
+
deployedAt: account.deployedAt
|
|
3761
|
+
}
|
|
3762
|
+
};
|
|
3678
3763
|
}
|
|
3679
3764
|
//#endregion
|
|
3680
|
-
//#region
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
},
|
|
3696
|
-
sessionId: {
|
|
3697
|
-
maxLength: 128,
|
|
3698
|
-
pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u
|
|
3699
|
-
},
|
|
3700
|
-
organizationId: {
|
|
3701
|
-
maxLength: 128,
|
|
3702
|
-
pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u
|
|
3703
|
-
},
|
|
3704
|
-
journeyId: {
|
|
3705
|
-
maxLength: 128,
|
|
3706
|
-
pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u
|
|
3707
|
-
},
|
|
3708
|
-
correlationId: {
|
|
3709
|
-
maxLength: 128,
|
|
3710
|
-
pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u
|
|
3711
|
-
},
|
|
3712
|
-
anonymousId: {
|
|
3713
|
-
maxLength: 128,
|
|
3714
|
-
pattern: /^anon_[A-Za-z0-9-]+$/u
|
|
3715
|
-
},
|
|
3716
|
-
traceparent: {
|
|
3717
|
-
maxLength: 55,
|
|
3718
|
-
pattern: /^00-[0-9a-f]{32}-[0-9a-f]{16}-0[01]$/u
|
|
3765
|
+
//#region src/surface/account-lifecycle.ts
|
|
3766
|
+
function isActiveProvisioningPhase(phase) {
|
|
3767
|
+
return phase.status === "wallet" || phase.status === "identity" || phase.status === "provision" || phase.status === "deploy";
|
|
3768
|
+
}
|
|
3769
|
+
function isRequirementMet(status, requirement) {
|
|
3770
|
+
if (requirement === "none") return status.status !== "notAuthenticated";
|
|
3771
|
+
if (status.status !== "accountReady") return false;
|
|
3772
|
+
return requirement !== "deployed" || status.deployment.status === "deployed";
|
|
3773
|
+
}
|
|
3774
|
+
function mapProvisioningFailureStep(step) {
|
|
3775
|
+
switch (step) {
|
|
3776
|
+
case "wallet": return "connecting";
|
|
3777
|
+
case "identity": return "registering";
|
|
3778
|
+
case "provision":
|
|
3779
|
+
case "deploy": return "activating";
|
|
3719
3780
|
}
|
|
3720
|
-
}
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
for (const field of Object.keys(FIELD_RULES)) {
|
|
3731
|
-
const value = source[field];
|
|
3732
|
-
if (!isSafeField(field, value)) continue;
|
|
3733
|
-
sanitized[field] = value;
|
|
3781
|
+
}
|
|
3782
|
+
function mapProvisioningPhaseToSetupStep(phase) {
|
|
3783
|
+
switch (phase.status) {
|
|
3784
|
+
case "idle":
|
|
3785
|
+
case "wallet": return "connecting";
|
|
3786
|
+
case "identity": return "registering";
|
|
3787
|
+
case "provision":
|
|
3788
|
+
case "deploy": return "activating";
|
|
3789
|
+
case "ready":
|
|
3790
|
+
case "failed": return "connecting";
|
|
3734
3791
|
}
|
|
3735
|
-
return Object.keys(sanitized).length === 0 ? void 0 : sanitized;
|
|
3736
3792
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
const sanitized = sanitizeObservationContext(input);
|
|
3740
|
-
return sanitized === void 0 ? void 0 : JSON.stringify(sanitized);
|
|
3793
|
+
function isSettingUpLifecycle(lifecycle) {
|
|
3794
|
+
return lifecycle.status === "settingUp";
|
|
3741
3795
|
}
|
|
3742
|
-
function
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3796
|
+
function readyLifecycle(status, accountId) {
|
|
3797
|
+
return {
|
|
3798
|
+
status: "ready",
|
|
3799
|
+
accountId,
|
|
3800
|
+
canTransact: status.status === "accountReady" && status.deployment.status === "deployed"
|
|
3801
|
+
};
|
|
3802
|
+
}
|
|
3803
|
+
function mapAccountLifecycle(input) {
|
|
3804
|
+
const { status, phase, requirement, accountId } = input;
|
|
3805
|
+
if (status.status === "notAuthenticated") return { status: "loading" };
|
|
3806
|
+
if (phase.status === "failed") return {
|
|
3807
|
+
status: "failed",
|
|
3808
|
+
at: mapProvisioningFailureStep(phase.at),
|
|
3809
|
+
error: phase.error
|
|
3810
|
+
};
|
|
3811
|
+
if (isRequirementMet(status, requirement) || phase.status === "ready") {
|
|
3812
|
+
if (accountId === void 0) return { status: "loading" };
|
|
3813
|
+
return readyLifecycle(status, accountId);
|
|
3814
|
+
}
|
|
3815
|
+
if (isActiveProvisioningPhase(phase) || phase.status === "idle") return {
|
|
3816
|
+
status: "settingUp",
|
|
3817
|
+
step: mapProvisioningPhaseToSetupStep(phase)
|
|
3818
|
+
};
|
|
3819
|
+
return { status: "loading" };
|
|
3746
3820
|
}
|
|
3747
3821
|
//#endregion
|
|
3748
3822
|
//#region src/contract/actor-scope.ts
|
|
@@ -3979,7 +4053,7 @@ function mapActorRequest(request, payer) {
|
|
|
3979
4053
|
...resolvedPayer === void 0 ? {} : { payer: resolvedPayer },
|
|
3980
4054
|
amount: request.amount,
|
|
3981
4055
|
reference: request.reference,
|
|
3982
|
-
status:
|
|
4056
|
+
status: request.status,
|
|
3983
4057
|
expiresAt: request.expiresAt ?? null
|
|
3984
4058
|
};
|
|
3985
4059
|
}
|
|
@@ -4037,28 +4111,17 @@ function mapApprovedInboxPayment(command, input) {
|
|
|
4037
4111
|
};
|
|
4038
4112
|
}
|
|
4039
4113
|
}
|
|
4040
|
-
function
|
|
4114
|
+
function mapInboxStatus(status) {
|
|
4041
4115
|
switch (status) {
|
|
4042
4116
|
case "draft":
|
|
4043
4117
|
case "sent":
|
|
4044
|
-
case "viewed":
|
|
4118
|
+
case "viewed": return "open";
|
|
4119
|
+
case "pending_settlement": return "approved";
|
|
4045
4120
|
case "paid":
|
|
4046
|
-
case "pending_settlement":
|
|
4047
|
-
case "declined":
|
|
4048
|
-
case "cancelled":
|
|
4049
|
-
case "expired": return status;
|
|
4050
|
-
default: return "sent";
|
|
4051
|
-
}
|
|
4052
|
-
}
|
|
4053
|
-
function mapInboxStatus(status) {
|
|
4054
|
-
switch (status) {
|
|
4055
|
-
case "approved":
|
|
4056
4121
|
case "declined":
|
|
4057
|
-
case "paid":
|
|
4058
4122
|
case "cancelled":
|
|
4059
4123
|
case "expired": return status;
|
|
4060
|
-
|
|
4061
|
-
default: return "open";
|
|
4124
|
+
default: throw Errors.invalidInput("status", "unknown request status");
|
|
4062
4125
|
}
|
|
4063
4126
|
}
|
|
4064
4127
|
function normalizeRefForBackend$1(ref, field) {
|
|
@@ -4377,6 +4440,7 @@ const financialOpsContract = {
|
|
|
4377
4440
|
listPayments: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].listPayments),
|
|
4378
4441
|
getPayment: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].getPayment),
|
|
4379
4442
|
activityList: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].list),
|
|
4443
|
+
activitySummary: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].summary),
|
|
4380
4444
|
activityGet: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].get),
|
|
4381
4445
|
activityAnnotate: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].annotate),
|
|
4382
4446
|
verifyPaymentDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].verifyPaymentDocument),
|
|
@@ -4550,7 +4614,10 @@ function canonicalJson(value) {
|
|
|
4550
4614
|
async function fingerprintPaymentIntent(intent) {
|
|
4551
4615
|
return keccak256(toBytes(canonicalJson(intent)));
|
|
4552
4616
|
}
|
|
4553
|
-
|
|
4617
|
+
function matchesIntent(expected) {
|
|
4618
|
+
return async (request) => await fingerprintPaymentIntent(request) === await fingerprintPaymentIntent(expected);
|
|
4619
|
+
}
|
|
4620
|
+
async function executePrepared(deps, prepare, isExpectedRequest, signal) {
|
|
4554
4621
|
if (isAborted$1(signal)) return {
|
|
4555
4622
|
ok: false,
|
|
4556
4623
|
error: Errors.cancelled({ operation: "payments" })
|
|
@@ -4571,7 +4638,7 @@ async function executePrepared(deps, prepare, expectedRequest, signal) {
|
|
|
4571
4638
|
}
|
|
4572
4639
|
const prepared = await prepare(signerAddress);
|
|
4573
4640
|
if (!prepared.ok) return prepared;
|
|
4574
|
-
if (await
|
|
4641
|
+
if (!await isExpectedRequest(prepared.value.request)) return {
|
|
4575
4642
|
ok: false,
|
|
4576
4643
|
error: Errors.invalidInput("payment", "prepared command mismatch")
|
|
4577
4644
|
};
|
|
@@ -4606,13 +4673,13 @@ function executePaymentLifecycle(deps, intent, signal) {
|
|
|
4606
4673
|
return executePrepared(deps, (signerAddress) => runIfActive(signal, "payments.prepareLifecycleExecution", () => deps.convexCall.action(deps.functions.preparePaymentLifecycleExecution, { input: {
|
|
4607
4674
|
signerAddress,
|
|
4608
4675
|
intent
|
|
4609
|
-
} })), intent, signal);
|
|
4676
|
+
} })), matchesIntent(intent), signal);
|
|
4610
4677
|
}
|
|
4611
4678
|
function executeOrganizationPayment(deps, input, signal) {
|
|
4612
4679
|
return executePrepared(deps, (signerAddress) => runIfActive(signal, "organizationPayments.prepareExecution", () => deps.convexCall.action(deps.functions.prepareOrganizationPaymentExecution, { input: {
|
|
4613
4680
|
...input,
|
|
4614
4681
|
signerAddress
|
|
4615
|
-
} })), input, signal);
|
|
4682
|
+
} })), matchesIntent(input), signal);
|
|
4616
4683
|
}
|
|
4617
4684
|
//#endregion
|
|
4618
4685
|
//#region src/surface/money.ts
|
|
@@ -4628,6 +4695,40 @@ function actorReferenceToBackend(actor) {
|
|
|
4628
4695
|
case "org": return actor;
|
|
4629
4696
|
}
|
|
4630
4697
|
}
|
|
4698
|
+
/**
|
|
4699
|
+
* The activity backend gives its actor a different name than the other
|
|
4700
|
+
* financialOps functions: `{kind: "organization", orgId}`, and not
|
|
4701
|
+
* `{kind: "org", orgId}`. Thus it does not use `actorReferenceToBackend`.
|
|
4702
|
+
*
|
|
4703
|
+
* The `personal` case sends NO actor. This is correct, and it does not lose
|
|
4704
|
+
* data. `requireActivityActor` uses the signed-in Account when the actor is
|
|
4705
|
+
* absent, and `canReadActor` refuses all other account identifiers
|
|
4706
|
+
* (`packages/backend/convex/movement/activityAuth.ts`). Thus "personal" and
|
|
4707
|
+
* "absent" both name the one Account that an SDK caller can read. The SDK does
|
|
4708
|
+
* not hold an account identifier to send.
|
|
4709
|
+
*
|
|
4710
|
+
* The `switch` is exhaustive. A new `ActorReference` variant is a compile error
|
|
4711
|
+
* here. The conditional expression that this function replaced removed each
|
|
4712
|
+
* value that was not an organization.
|
|
4713
|
+
*/
|
|
4714
|
+
function activityActorField(actor) {
|
|
4715
|
+
if (actor === void 0) return {};
|
|
4716
|
+
switch (actor.kind) {
|
|
4717
|
+
case "personal": return {};
|
|
4718
|
+
case "organization": return { actor: {
|
|
4719
|
+
kind: "organization",
|
|
4720
|
+
orgId: actor.organizationId
|
|
4721
|
+
} };
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
/** An absent actor and a `personal` actor both name the signed-in Account. */
|
|
4725
|
+
function actorScopeKey(actor) {
|
|
4726
|
+
return actor === void 0 || actor.kind === "personal" ? "personal" : `org:${actor.organizationId}`;
|
|
4727
|
+
}
|
|
4728
|
+
/** Two actor references name the same scope. */
|
|
4729
|
+
function sameActor(left, right) {
|
|
4730
|
+
return actorScopeKey(left) === actorScopeKey(right);
|
|
4731
|
+
}
|
|
4631
4732
|
const memoryPaymentRequestKeys = /* @__PURE__ */ new Map();
|
|
4632
4733
|
const paymentAttemptReleases = /* @__PURE__ */ new Set();
|
|
4633
4734
|
let paymentAttemptPagehideInstalled = false;
|
|
@@ -5058,31 +5159,48 @@ function makeFinancialOpsMethods(deps) {
|
|
|
5058
5159
|
},
|
|
5059
5160
|
activity: {
|
|
5060
5161
|
list: (params, options) => runIfActive(options?.signal, "activity.list", () => deps.convexCall.query(fns.activityList, { input: {
|
|
5061
|
-
...params?.actor
|
|
5062
|
-
kind: "organization",
|
|
5063
|
-
orgId: params.actor.organizationId
|
|
5064
|
-
} } : {},
|
|
5162
|
+
...activityActorField(params?.actor),
|
|
5065
5163
|
...params?.cursor === void 0 ? {} : { cursor: params.cursor },
|
|
5066
|
-
...params?.limit === void 0 ? {} : { limit: params.limit }
|
|
5164
|
+
...params?.limit === void 0 ? {} : { limit: params.limit },
|
|
5165
|
+
...params?.range === void 0 ? {} : { range: params.range },
|
|
5166
|
+
...params?.filter === void 0 ? {} : { filter: params.filter }
|
|
5067
5167
|
} })),
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
...reference.kind === "payment" && reference.actor?.kind === "organization" ? { actor: {
|
|
5072
|
-
kind: "organization",
|
|
5073
|
-
orgId: reference.actor.organizationId
|
|
5074
|
-
} } : {}
|
|
5168
|
+
summary: (params, options) => runIfActive(options?.signal, "activity.summary", () => deps.convexCall.query(fns.activitySummary, { input: {
|
|
5169
|
+
...activityActorField(params?.actor),
|
|
5170
|
+
...params?.window === void 0 ? {} : { window: params.window }
|
|
5075
5171
|
} })),
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5172
|
+
get: (reference, options) => {
|
|
5173
|
+
const actor = actorReferenceToBackend(options?.actor);
|
|
5174
|
+
return runIfActive(options?.signal, "activity.get", () => deps.convexCall.query(fns.activityGet, {
|
|
5175
|
+
input: {
|
|
5176
|
+
kind: reference.kind,
|
|
5177
|
+
id: reference.id,
|
|
5178
|
+
...reference.kind === "payment" ? activityActorField(reference.actor) : {}
|
|
5179
|
+
},
|
|
5180
|
+
...actor === void 0 ? {} : { actor }
|
|
5181
|
+
}));
|
|
5182
|
+
},
|
|
5183
|
+
annotate: (input, options) => {
|
|
5184
|
+
if (input.actor !== void 0 && input.reference.kind === "payment" && input.reference.actor !== void 0 && !sameActor(input.actor, input.reference.actor)) return Promise.resolve({
|
|
5185
|
+
ok: false,
|
|
5186
|
+
error: Errors.invalidInput("reference.actor", "must match the annotation actor")
|
|
5187
|
+
});
|
|
5188
|
+
const referenceActor = input.reference.kind === "payment" ? activityActorField(input.reference.actor).actor : void 0;
|
|
5189
|
+
return runIfActive(options?.signal, "activity.annotate", () => deps.convexCall.mutation(fns.activityAnnotate, { input: {
|
|
5190
|
+
...activityActorField(input.actor),
|
|
5191
|
+
reference: input.reference.kind === "payment" && referenceActor !== void 0 ? {
|
|
5192
|
+
kind: "payment",
|
|
5193
|
+
id: input.reference.id,
|
|
5194
|
+
actor: referenceActor
|
|
5195
|
+
} : {
|
|
5196
|
+
kind: input.reference.kind,
|
|
5197
|
+
id: input.reference.id
|
|
5198
|
+
},
|
|
5199
|
+
...input.counterpartyLabel === void 0 ? {} : { counterpartyLabel: input.counterpartyLabel },
|
|
5200
|
+
...input.accountingCategory === void 0 ? {} : { accountingCategory: input.accountingCategory },
|
|
5201
|
+
...input.memo === void 0 ? {} : { memo: input.memo }
|
|
5202
|
+
} }));
|
|
5203
|
+
}
|
|
5086
5204
|
},
|
|
5087
5205
|
offramp: {
|
|
5088
5206
|
quote: (input, options) => {
|
|
@@ -5410,7 +5528,7 @@ function mapOk(result, f) {
|
|
|
5410
5528
|
}
|
|
5411
5529
|
//#endregion
|
|
5412
5530
|
//#region package.json
|
|
5413
|
-
var version = "2.
|
|
5531
|
+
var version = "2.5.0";
|
|
5414
5532
|
//#endregion
|
|
5415
5533
|
//#region src/ports/auth-client.ts
|
|
5416
5534
|
var AuthClientError = class extends Data.TaggedError("AuthClientError") {};
|
|
@@ -5584,7 +5702,8 @@ const TelemetryEnvelopeProps = {
|
|
|
5584
5702
|
"development",
|
|
5585
5703
|
"staging",
|
|
5586
5704
|
"production",
|
|
5587
|
-
"unknown"
|
|
5705
|
+
"unknown",
|
|
5706
|
+
"local"
|
|
5588
5707
|
]),
|
|
5589
5708
|
producer: Schema.Literals([
|
|
5590
5709
|
"server",
|
|
@@ -7476,7 +7595,7 @@ function makePermissionMethods(deps, orgId) {
|
|
|
7476
7595
|
}
|
|
7477
7596
|
//#endregion
|
|
7478
7597
|
//#region src/surface/organization-payments.ts
|
|
7479
|
-
function executionDependencies(input) {
|
|
7598
|
+
function executionDependencies$1(input) {
|
|
7480
7599
|
return input.actor === void 0 || input.chainId === void 0 || input.signer === void 0 ? null : {
|
|
7481
7600
|
actor: input.actor,
|
|
7482
7601
|
chainId: input.chainId,
|
|
@@ -7493,7 +7612,7 @@ function makeOrganizationPaymentsMethods(deps, orgId) {
|
|
|
7493
7612
|
ok: false,
|
|
7494
7613
|
error: Errors.cancelled({ operation })
|
|
7495
7614
|
};
|
|
7496
|
-
const execution = executionDependencies(deps);
|
|
7615
|
+
const execution = executionDependencies$1(deps);
|
|
7497
7616
|
if (execution === null) return {
|
|
7498
7617
|
ok: false,
|
|
7499
7618
|
error: Errors.notImplemented("organizationPayments", "executionComposition")
|
|
@@ -7588,6 +7707,197 @@ function makeOrganizationPaymentsMethods(deps, orgId) {
|
|
|
7588
7707
|
};
|
|
7589
7708
|
}
|
|
7590
7709
|
//#endregion
|
|
7710
|
+
//#region src/contract/payroll.ts
|
|
7711
|
+
const payrollContract = {
|
|
7712
|
+
authorizeRun: makeFunctionReference(CAPXUL_FUNCTIONS["payroll/actions"].authorizeRun),
|
|
7713
|
+
runs: makeFunctionReference(CAPXUL_FUNCTIONS["payroll/queries"].runs),
|
|
7714
|
+
groups: makeFunctionReference(CAPXUL_FUNCTIONS["payroll/queries"].groups),
|
|
7715
|
+
saveGroup: makeFunctionReference(CAPXUL_FUNCTIONS["payroll/mutations"].saveGroup),
|
|
7716
|
+
removeGroup: makeFunctionReference(CAPXUL_FUNCTIONS["payroll/mutations"].removeGroup)
|
|
7717
|
+
};
|
|
7718
|
+
//#endregion
|
|
7719
|
+
//#region src/surface/payroll.ts
|
|
7720
|
+
const AUTHORIZE_RUN = "payroll.authorizeRun";
|
|
7721
|
+
function toPayrollRun(wire) {
|
|
7722
|
+
return {
|
|
7723
|
+
id: toPayrollRunId(wire.id),
|
|
7724
|
+
status: wire.status,
|
|
7725
|
+
periodStart: wire.periodStart,
|
|
7726
|
+
periodEnd: wire.periodEnd,
|
|
7727
|
+
total: wire.total,
|
|
7728
|
+
recipientCount: wire.recipientCount
|
|
7729
|
+
};
|
|
7730
|
+
}
|
|
7731
|
+
function toPayrollRuns(wire) {
|
|
7732
|
+
return {
|
|
7733
|
+
runs: wire.runs.map(toPayrollRun),
|
|
7734
|
+
settledThisMonth: wire.settledThisMonth
|
|
7735
|
+
};
|
|
7736
|
+
}
|
|
7737
|
+
function toPayrollGroup(wire) {
|
|
7738
|
+
return {
|
|
7739
|
+
id: toPayrollGroupId(wire.id),
|
|
7740
|
+
name: wire.name,
|
|
7741
|
+
tone: wire.tone,
|
|
7742
|
+
members: wire.members.map((member) => ({
|
|
7743
|
+
partyId: member.partyId,
|
|
7744
|
+
amount: member.amount,
|
|
7745
|
+
currency: member.currency
|
|
7746
|
+
}))
|
|
7747
|
+
};
|
|
7748
|
+
}
|
|
7749
|
+
const MINOR_UNIT_INTEGER = /^-?\d+$/u;
|
|
7750
|
+
/**
|
|
7751
|
+
* One prepared `Money` counted in the minor units `net` is written in, or
|
|
7752
|
+
* `null` when the value is not a decimal number. `amount.value` is a DECIMAL
|
|
7753
|
+
* string (`"200"` at 6 decimals is 200000000 minor units), so comparing it to
|
|
7754
|
+
* `net` directly would compare two different units and pass nothing.
|
|
7755
|
+
*/
|
|
7756
|
+
function minorUnits(amount) {
|
|
7757
|
+
const [whole = "", fraction = ""] = amount.value.split(".");
|
|
7758
|
+
if (!/^\d+$/u.test(whole) || !/^\d*$/u.test(fraction) || fraction.length > amount.decimals) return null;
|
|
7759
|
+
return BigInt(whole + fraction.padEnd(amount.decimals, "0"));
|
|
7760
|
+
}
|
|
7761
|
+
/**
|
|
7762
|
+
* One prepared item must pay the person this caller named, the amount this
|
|
7763
|
+
* caller set, under a payslip that attests to the same figures.
|
|
7764
|
+
*
|
|
7765
|
+
* `issuedAt` and `employerRef` are server facts the caller cannot contradict,
|
|
7766
|
+
* so they are not compared. Everything the caller DID supply is: the recipient
|
|
7767
|
+
* `Ref`, the transfer amount against `net`, and the payslip's employee, gross
|
|
7768
|
+
* and net. Adjustments are not in the signed command at all — the backend
|
|
7769
|
+
* enforces `gross + Σ adjustments === net` (ADR-0022 R8), and gross and net
|
|
7770
|
+
* being right is what the signer can prove here.
|
|
7771
|
+
*/
|
|
7772
|
+
function isRunItem(prepared, item) {
|
|
7773
|
+
if (prepared === void 0) return false;
|
|
7774
|
+
const payslip = prepared.document;
|
|
7775
|
+
return canonicalJson(prepared.to) === canonicalJson(item.to) && prepared.paymentType === "payroll" && MINOR_UNIT_INTEGER.test(item.net) && minorUnits(prepared.amount) === BigInt(item.net) && payslip?.primaryType === "Payslip" && payslip.message.employeeRef === item.partyId && payslip.message.gross === item.gross && payslip.message.net === item.net;
|
|
7776
|
+
}
|
|
7777
|
+
/**
|
|
7778
|
+
* The batch the backend derived from a payroll intent must be the batch this
|
|
7779
|
+
* caller asked for, checked before the digest is signed.
|
|
7780
|
+
*
|
|
7781
|
+
* The payslip envelope on each item is server-built and carries the run row's
|
|
7782
|
+
* own `issuedAt`, so the batch cannot be fingerprinted against a local copy
|
|
7783
|
+
* (`matchesIntent` is unavailable here). What CAN be verified is every field
|
|
7784
|
+
* the caller supplied — the org, the Budget, the request key, and per item the
|
|
7785
|
+
* recipient, the amount and the payslip figures — plus the lineage binding the
|
|
7786
|
+
* batch to the run just written. A prepared command that pays a different
|
|
7787
|
+
* person, a different amount, from a different Budget, or under a different
|
|
7788
|
+
* run never reaches the signer.
|
|
7789
|
+
*/
|
|
7790
|
+
function isRunBatch(input) {
|
|
7791
|
+
return (request) => {
|
|
7792
|
+
const batch = request;
|
|
7793
|
+
return Promise.resolve(batch.orgId === input.orgId && batch.permissionId === input.permissionId && batch.requestKey === input.requestKey && batch.lineage?.kind === "payroll" && batch.lineage.runId === input.runId && batch.items?.length === input.items.length && input.items.every((item, index) => isRunItem(batch.items?.[index], item)));
|
|
7794
|
+
};
|
|
7795
|
+
}
|
|
7796
|
+
function executionDependencies(deps) {
|
|
7797
|
+
return deps.actor === void 0 || deps.chainId === void 0 || deps.signer === void 0 ? null : {
|
|
7798
|
+
actor: deps.actor,
|
|
7799
|
+
chainId: deps.chainId,
|
|
7800
|
+
convexCall: deps.convexCall,
|
|
7801
|
+
functions: moneyExecutionContract,
|
|
7802
|
+
signer: deps.signer
|
|
7803
|
+
};
|
|
7804
|
+
}
|
|
7805
|
+
function makePayrollMethods(deps, orgId) {
|
|
7806
|
+
const requestKeyScope = deps.requestKeyScope ?? `client-${crypto.randomUUID()}`;
|
|
7807
|
+
async function authorize(input, signal) {
|
|
7808
|
+
const execution = executionDependencies(deps);
|
|
7809
|
+
if (execution === null) return {
|
|
7810
|
+
ok: false,
|
|
7811
|
+
error: Errors.notImplemented("payroll", "executionComposition")
|
|
7812
|
+
};
|
|
7813
|
+
const requestKey = await paymentRequestKeyLifecycle(`${requestKeyScope}:${AUTHORIZE_RUN}`, {
|
|
7814
|
+
orgId,
|
|
7815
|
+
input
|
|
7816
|
+
}, input.requestKey);
|
|
7817
|
+
let authorized;
|
|
7818
|
+
const submitted = await executePrepared(execution, async (signerAddress) => {
|
|
7819
|
+
const prepared = await runIfActive(signal, AUTHORIZE_RUN, () => deps.convexCall.action(payrollContract.authorizeRun, { input: {
|
|
7820
|
+
orgId,
|
|
7821
|
+
permissionId: input.permissionId,
|
|
7822
|
+
requestKey: requestKey.key,
|
|
7823
|
+
signerAddress,
|
|
7824
|
+
period: input.period,
|
|
7825
|
+
items: input.items
|
|
7826
|
+
} }));
|
|
7827
|
+
if (!prepared.ok) return prepared;
|
|
7828
|
+
authorized = prepared.value.run;
|
|
7829
|
+
return {
|
|
7830
|
+
ok: true,
|
|
7831
|
+
value: prepared.value.execution
|
|
7832
|
+
};
|
|
7833
|
+
}, (request) => authorized === void 0 || authorized.periodStart !== input.period.start || authorized.periodEnd !== input.period.end ? Promise.resolve(false) : isRunBatch({
|
|
7834
|
+
orgId,
|
|
7835
|
+
permissionId: input.permissionId,
|
|
7836
|
+
requestKey: requestKey.key,
|
|
7837
|
+
runId: authorized.id,
|
|
7838
|
+
items: input.items
|
|
7839
|
+
})(request), signal);
|
|
7840
|
+
try {
|
|
7841
|
+
await requestKey.finish(submitted.ok);
|
|
7842
|
+
} catch {}
|
|
7843
|
+
if (!submitted.ok) return submitted;
|
|
7844
|
+
return authorized === void 0 ? {
|
|
7845
|
+
ok: false,
|
|
7846
|
+
error: Errors.unknown()
|
|
7847
|
+
} : {
|
|
7848
|
+
ok: true,
|
|
7849
|
+
value: toPayrollRun(authorized)
|
|
7850
|
+
};
|
|
7851
|
+
}
|
|
7852
|
+
return {
|
|
7853
|
+
runs: (options) => runIfActive(options?.signal, "payroll.runs", () => Effect.map(deps.convexCall.query(payrollContract.runs, { orgId }), toPayrollRuns)),
|
|
7854
|
+
authorizeRun: async (input, options) => {
|
|
7855
|
+
if (options?.signal?.aborted === true) return {
|
|
7856
|
+
ok: false,
|
|
7857
|
+
error: Errors.cancelled({ operation: AUTHORIZE_RUN })
|
|
7858
|
+
};
|
|
7859
|
+
try {
|
|
7860
|
+
return await authorize(input, options?.signal);
|
|
7861
|
+
} catch (cause) {
|
|
7862
|
+
return {
|
|
7863
|
+
ok: false,
|
|
7864
|
+
error: cause instanceof CapxulError ? cause : Errors.unknown(cause)
|
|
7865
|
+
};
|
|
7866
|
+
}
|
|
7867
|
+
},
|
|
7868
|
+
groups: {
|
|
7869
|
+
list: (options) => runIfActive(options?.signal, "payroll.groups.list", () => Effect.map(deps.convexCall.query(payrollContract.groups, { orgId }), (groups) => groups.map(toPayrollGroup))),
|
|
7870
|
+
save: (input, options) => runIfActive(options?.signal, "payroll.groups.save", () => Effect.map(deps.convexCall.mutation(payrollContract.saveGroup, {
|
|
7871
|
+
orgId,
|
|
7872
|
+
...input.id === void 0 ? {} : { id: String(input.id) },
|
|
7873
|
+
name: input.name,
|
|
7874
|
+
tone: input.tone,
|
|
7875
|
+
members: input.members
|
|
7876
|
+
}), toPayrollGroup)),
|
|
7877
|
+
remove: (id, options) => runIfActive(options?.signal, "payroll.groups.remove", () => Effect.map(deps.convexCall.mutation(payrollContract.removeGroup, {
|
|
7878
|
+
orgId,
|
|
7879
|
+
id
|
|
7880
|
+
}), () => void 0))
|
|
7881
|
+
}
|
|
7882
|
+
};
|
|
7883
|
+
}
|
|
7884
|
+
const payrollUnavailable = () => Promise.resolve({
|
|
7885
|
+
ok: false,
|
|
7886
|
+
error: Errors.notImplemented("payroll", "executionComposition")
|
|
7887
|
+
});
|
|
7888
|
+
/** The bundle an unconfigured client exposes — every verb refuses, none throws. */
|
|
7889
|
+
function makeUnavailablePayrollMethods() {
|
|
7890
|
+
return {
|
|
7891
|
+
runs: payrollUnavailable,
|
|
7892
|
+
authorizeRun: payrollUnavailable,
|
|
7893
|
+
groups: {
|
|
7894
|
+
list: payrollUnavailable,
|
|
7895
|
+
save: payrollUnavailable,
|
|
7896
|
+
remove: payrollUnavailable
|
|
7897
|
+
}
|
|
7898
|
+
};
|
|
7899
|
+
}
|
|
7900
|
+
//#endregion
|
|
7591
7901
|
//#region src/surface/_shared/org-telemetry.ts
|
|
7592
7902
|
/** Extract the domain from an email for telemetry (never the local part / PII). */
|
|
7593
7903
|
function emailDomain$1(email) {
|
|
@@ -8125,6 +8435,13 @@ function makeOrgMethods(deps) {
|
|
|
8125
8435
|
...deps.actor === void 0 ? {} : { actor: deps.actor },
|
|
8126
8436
|
...deps.chainId === void 0 ? {} : { chainId: Number(deps.chainId) },
|
|
8127
8437
|
...deps.signer === void 0 ? {} : { signer: deps.signer }
|
|
8438
|
+
}, String(orgId)),
|
|
8439
|
+
payroll: convexCall === void 0 ? makeUnavailablePayrollMethods() : makePayrollMethods({
|
|
8440
|
+
convexCall,
|
|
8441
|
+
...deps.requestKeyScope === void 0 ? {} : { requestKeyScope: deps.requestKeyScope },
|
|
8442
|
+
...deps.actor === void 0 ? {} : { actor: deps.actor },
|
|
8443
|
+
...deps.chainId === void 0 ? {} : { chainId: Number(deps.chainId) },
|
|
8444
|
+
...deps.signer === void 0 ? {} : { signer: deps.signer }
|
|
8128
8445
|
}, String(orgId))
|
|
8129
8446
|
};
|
|
8130
8447
|
}
|
|
@@ -8163,13 +8480,74 @@ const organizationPaymentExecutionUnavailable = () => Promise.resolve({
|
|
|
8163
8480
|
//#region src/contract/holdings.ts
|
|
8164
8481
|
const holdingsContract = { current: makeFunctionReference(CAPXUL_FUNCTIONS["holdings/actions"].current) };
|
|
8165
8482
|
//#endregion
|
|
8483
|
+
//#region src/domain/money/primary-holding.ts
|
|
8484
|
+
const CURRENCY_BY_TOKEN_SYMBOL = new Map([["USDX", toCurrencyCode("USD")]]);
|
|
8485
|
+
/**
|
|
8486
|
+
* The designated display asset: the payable row holding the most money, or
|
|
8487
|
+
* `null` when no row is payable.
|
|
8488
|
+
*
|
|
8489
|
+
* A `HoldingRow` (`packages/wire/src/money.ts`) is a RAW CHAIN ROW, and three
|
|
8490
|
+
* of its shapes cannot become a `Holding`. Each is SKIPPED rather than
|
|
8491
|
+
* reported, because a row nobody can render is not a failure of the read:
|
|
8492
|
+
*
|
|
8493
|
+
* - no `decimals` — the raw balance cannot be lifted into major units;
|
|
8494
|
+
* - no `symbol` — the asset cannot be named;
|
|
8495
|
+
* - a symbol that stands for no supported `CurrencyCode` — the balance
|
|
8496
|
+
* cannot be stated as `Money` without inventing a currency for it.
|
|
8497
|
+
*
|
|
8498
|
+
* This replaces the app's hand pick in `formatCurrentHoldingsBalance`
|
|
8499
|
+
* (`dashboard-formatters.ts`): a `.find()` over a hardcoded `["USDX","USDC"]`
|
|
8500
|
+
* list falling back to `rows[0]`. Ties keep the earlier row, so the same
|
|
8501
|
+
* snapshot always yields the same answer.
|
|
8502
|
+
*/
|
|
8503
|
+
function primaryHolding(rows) {
|
|
8504
|
+
let primary = null;
|
|
8505
|
+
for (const row of rows) {
|
|
8506
|
+
const holding = toHolding(row);
|
|
8507
|
+
if (holding === null) continue;
|
|
8508
|
+
if (primary === null || compareDecimal(holding.available.value, primary.available.value) > 0) primary = holding;
|
|
8509
|
+
}
|
|
8510
|
+
return primary;
|
|
8511
|
+
}
|
|
8512
|
+
function toHolding(row) {
|
|
8513
|
+
if (row.symbol === void 0 || row.decimals === void 0) return null;
|
|
8514
|
+
const currency = currencyForTokenSymbol(row.symbol);
|
|
8515
|
+
if (currency === null) return null;
|
|
8516
|
+
return {
|
|
8517
|
+
id: row.assetKind === "native" ? "native" : row.tokenAddress.toLowerCase(),
|
|
8518
|
+
symbol: row.symbol,
|
|
8519
|
+
available: fromWei(row.rawBalance, row.decimals, currency)
|
|
8520
|
+
};
|
|
8521
|
+
}
|
|
8522
|
+
function currencyForTokenSymbol(symbol) {
|
|
8523
|
+
return CURRENCY_BY_TOKEN_SYMBOL.get(symbol.toUpperCase()) ?? null;
|
|
8524
|
+
}
|
|
8525
|
+
/**
|
|
8526
|
+
* Order two non-negative major-unit decimal strings without converting either
|
|
8527
|
+
* to a JavaScript number. Rows carry different `decimals`, so the raw integers
|
|
8528
|
+
* are not comparable: 1999999999 at 9 decimals is LESS than 2000000 at 6.
|
|
8529
|
+
*/
|
|
8530
|
+
function compareDecimal(first, second) {
|
|
8531
|
+
const [firstWhole = "0", firstFraction = ""] = first.split(".");
|
|
8532
|
+
const [secondWhole = "0", secondFraction = ""] = second.split(".");
|
|
8533
|
+
const width = Math.max(firstFraction.length, secondFraction.length);
|
|
8534
|
+
const firstScaled = BigInt(firstWhole + firstFraction.padEnd(width, "0"));
|
|
8535
|
+
const secondScaled = BigInt(secondWhole + secondFraction.padEnd(width, "0"));
|
|
8536
|
+
if (firstScaled === secondScaled) return 0;
|
|
8537
|
+
return firstScaled < secondScaled ? -1 : 1;
|
|
8538
|
+
}
|
|
8539
|
+
//#endregion
|
|
8166
8540
|
//#region src/surface/holdings.ts
|
|
8167
8541
|
function makeHoldingsMethods(deps) {
|
|
8168
8542
|
const functions = deps.functions ?? holdingsContract;
|
|
8169
|
-
|
|
8543
|
+
const read = (operation, actor, signal, pick) => runIfActive(signal, operation, () => deps.convexCall.action(functions.current, actor?.kind === "organization" ? { actor: {
|
|
8170
8544
|
kind: "organization",
|
|
8171
|
-
orgId:
|
|
8172
|
-
} } : {}))
|
|
8545
|
+
orgId: actor.organizationId
|
|
8546
|
+
} } : {}).pipe(Effect.map(pick)));
|
|
8547
|
+
return {
|
|
8548
|
+
current: (input, options) => read("holdings.current", input?.actor, options?.signal, (snapshot) => snapshot),
|
|
8549
|
+
primary: (input, options) => read("holdings.primary", input?.actor, options?.signal, (snapshot) => primaryHolding(snapshot.rows))
|
|
8550
|
+
};
|
|
8173
8551
|
}
|
|
8174
8552
|
//#endregion
|
|
8175
8553
|
//#region src/surface/factory.ts
|
|
@@ -8998,4 +9376,4 @@ function withHostObservation(actor, snapshot) {
|
|
|
8998
9376
|
};
|
|
8999
9377
|
}
|
|
9000
9378
|
//#endregion
|
|
9001
|
-
export { fingerprintPaymentIntent as A,
|
|
9379
|
+
export { isClaimed as $, fingerprintPaymentIntent as A, OBSERVATION_CONTEXT_HEADER as B, ClockPortTag as C, AuthClientError as D, authClientPortFromPromiseAdapter as E, copyInvocationObservation as F, CAPXUL_FUNCTIONS as G, sanitizeObservationContext as H, readInvocationObservation as I, CAPXUL_PAYMENTS_V2_ADDRESS as J, BootstrapEnvelope as K, causeChain as L, fromWei as M, isSettingUpLifecycle as N, AuthClientPortTag as O, formatTraceparent as P, destination as Q, injectedWalletSigner as R, ClockError as S, bootstrapErrorFromCapxul as T, PAYMENT_DIRECTIONS as U, encodeObservationContextHeader as V, PAYMENT_STATUSES as W, BASE_SEPOLIA_CHAIN_ID as X, normalizeBindingEmail as Y, deriveCapxulSafeAddress as Z, wireChainId as _, observeFailedResult as a, ConvexCallPortTag as b, captureExceptionSync as c, TelemetryPortTag as d, isRestoring as et, redactTelemetryEvent as f, accountReadErrorFromCapxul as g, AccountReadPortTag as h, observationContextProps as i, toWei as j, version as k, detectAuthCacheAdapter as l, smartAccountErrorFromCapxul as m, postHogProductTelemetry as n, postHogFailureObservation as o, SmartAccountPortTag as p, EngineeringTelemetryBootstrapPolicy as q, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT as r, captureException as s, assembleCapxulClient as t, PostHogTelemetryLayer as u, IdentityPortTag as v, BootstrapPortTag as w, convexCallErrorFromCapxul as x, identityErrorFromCapxul as y, signerFailure as z };
|