@capxul/mcp 4.2.0-rc.10 → 4.2.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "source": {
3
- "commit": "a7e55e5f1c2af5567a8b285a4ef70b8b5a3289f6",
4
- "tree": "91bf1b080c0006fe30131711c13c843afd627158",
3
+ "commit": "12a590f10914e8b056ce54732e6f484f36e060f3",
4
+ "tree": "90adc6656be3c1fb0a6e18170877d46081fa2538",
5
5
  "branch": "codex/programme-rc",
6
6
  "repository": "https://github.com/Xelmar-tech/infrastructure",
7
7
  "lockfileSha256": "5870f53b72706d103220c1bcf733cb4fb71196aea32a9cfbdc277d73cc959264"
8
8
  },
9
9
  "name": "@capxul/mcp",
10
- "version": "4.2.0-rc.10"
10
+ "version": "4.2.0-rc.11"
11
11
  }
package/dist/main.mjs CHANGED
@@ -931,6 +931,10 @@ function toChainId(raw) {
931
931
  if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw <= 0) throw Errors.invalidInput("chainId", invalidValueReason("must be a positive safe integer", raw));
932
932
  return raw;
933
933
  }
934
+ function toWeiAmount(raw) {
935
+ if (typeof raw !== "string" || !WEI_RE.test(raw)) throw Errors.invalidInput("weiAmount", invalidValueReason("must be a non-negative integer string", raw));
936
+ return raw;
937
+ }
934
938
  function toCountryCode(raw) {
935
939
  if (typeof raw !== "string") throw Errors.invalidInput("countryCode", "must be a string");
936
940
  const upper = raw.toUpperCase();
@@ -1087,8 +1091,18 @@ const SYNTHETIC_TEST_ASSETS = [
1087
1091
  }
1088
1092
  ];
1089
1093
  toChainId(84532);
1090
- [...CONFIGURED_MONEY_ASSETS, ...SYNTHETIC_TEST_ASSETS];
1094
+ /**
1095
+ * Every known asset in ONE identity space. The tiers above declare admission;
1096
+ * this is the only list an identity resolves against, so an asset is never
1097
+ * two different things depending on which list a caller happened to read.
1098
+ */
1099
+ const MONEY_ASSET_REGISTRY = [...CONFIGURED_MONEY_ASSETS, ...SYNTHETIC_TEST_ASSETS];
1091
1100
  CONFIGURED_MONEY_ASSETS.map((asset) => asset.tokenAddress.toLowerCase());
1101
+ /** The registry row for `assetId` across every tier, or `null`. */
1102
+ function configuredMoneyAssetById(assetId) {
1103
+ const canonical = toAssetId(assetId);
1104
+ return MONEY_ASSET_REGISTRY.find((asset) => asset.assetId === canonical) ?? null;
1105
+ }
1092
1106
  //#endregion
1093
1107
  //#region ../../node_modules/.pnpm/abitype@1.2.3_typescript@7.0.2_zod@4.4.3/node_modules/abitype/dist/esm/regex.js
1094
1108
  function execTyped(regex, string) {
@@ -7788,7 +7802,27 @@ const PermissionMirrorVerificationProps = Schema.Struct({
7788
7802
  "conflicting_replay",
7789
7803
  "revision_mismatch"
7790
7804
  ])),
7791
- command_id: OptionalString
7805
+ command_id: OptionalString,
7806
+ /**
7807
+ * #1961 R03. The authorized stored row names the Permission it changed; the
7808
+ * verified receipt names the chain facts that prove it. Every field is
7809
+ * optional because a pending or provider-failed verification has neither an
7810
+ * application result nor a receipt to report.
7811
+ */
7812
+ permission_id: OptionalString,
7813
+ permission_assignment_id: OptionalString,
7814
+ permission_operation: Schema.optional(Schema.Literals([
7815
+ "create",
7816
+ "change",
7817
+ "assign",
7818
+ "revoke",
7819
+ "replace"
7820
+ ])),
7821
+ revision: Schema.optional(Schema.Number),
7822
+ user_op_hash: OptionalString,
7823
+ transaction_hash: OptionalString,
7824
+ block_number: Schema.optional(Schema.Number),
7825
+ log_index: Schema.optional(Schema.Number)
7792
7826
  });
7793
7827
  const MovementScanWindowProps = Schema.Struct({
7794
7828
  ...TelemetryEnvelopeProps,
@@ -8713,6 +8747,7 @@ const CAPXUL_FUNCTIONS = {
8713
8747
  list: "movement/activity:list",
8714
8748
  summary: "movement/activity:summary"
8715
8749
  },
8750
+ "movement/annotations": { overlay: "movement/annotations:overlay" },
8716
8751
  "moneyExecution/actions": {
8717
8752
  abandonPaymentExecution: "moneyExecution/actions:abandonPaymentExecution",
8718
8753
  preparePermissionExecution: "moneyExecution/actions:preparePermissionExecution",
@@ -9311,7 +9346,19 @@ const StoredPermissionCommandSchema = Schema.Struct({
9311
9346
  roleKey: RoleKeySchema,
9312
9347
  allowanceKey: Schema.NullOr(AllowanceKeySchema),
9313
9348
  memberSafe: Schema.NullOr(SafeAddressSchema),
9314
- expectedAuthorityEvents: Schema.Array(Bytes32Schema)
9349
+ expectedAuthorityEvents: Schema.Array(Bytes32Schema),
9350
+ /**
9351
+ * #1961 R03. The authenticated user who created the command, and the browser
9352
+ * linkage captured with it. Delayed verification and bounded retries read
9353
+ * this snapshot instead of the current viewer, so one command keeps one
9354
+ * origin. Both are absent on commands created before this contract.
9355
+ *
9356
+ * The context stays `unknown` on purpose: `sanitizeObservationContext` owns
9357
+ * the field allowlist, and every reader passes the stored value through it
9358
+ * rather than repeating those rules here.
9359
+ */
9360
+ originatingAuthUserId: Schema.optional(Schema.String),
9361
+ originatingObservationContext: Schema.optional(Schema.Unknown)
9315
9362
  });
9316
9363
  Schema.Struct({
9317
9364
  command: StoredPermissionCommandSchema,
@@ -12361,6 +12408,7 @@ const financialOpsContract = {
12361
12408
  activitySummary: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].summary),
12362
12409
  activityGet: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].get),
12363
12410
  activityAnnotate: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].annotate),
12411
+ activityOverlay: makeFunctionReference(CAPXUL_FUNCTIONS["movement/annotations"].overlay),
12364
12412
  verifyPaymentDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].verifyPaymentDocument),
12365
12413
  renderStoredDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].renderStoredDocument)
12366
12414
  };
@@ -12377,8 +12425,357 @@ const moneyExecutionContract = {
12377
12425
  submitPaymentCommandExecution: makeFunctionReference(CAPXUL_FUNCTIONS["moneyExecution/paymentCommandActions"].submitPaymentCommandExecution)
12378
12426
  };
12379
12427
  //#endregion
12428
+ //#region ../../packages/sdk/src/ports/indexer-read.ts
12429
+ /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
12430
+ const INDEXER_MAX_LIMIT = 1e3;
12431
+ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
12432
+ /**
12433
+ * Refuse a page the indexer would refuse, before any implementation spends a
12434
+ * round trip on it. Every implementation calls this, so a consumer test written
12435
+ * against the hermetic adapter cannot pass on input production rejects. Throws
12436
+ * a `CapxulError`; callers convert it to an `IndexerReadError`.
12437
+ */
12438
+ function assertIndexerPage(input) {
12439
+ const safe = String(input.safe).toLowerCase();
12440
+ if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
12441
+ if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 1e3) throw Errors.invalidInput("limit", `must be an integer from 1 to ${INDEXER_MAX_LIMIT}`);
12442
+ const offset = input.offset ?? 0;
12443
+ if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
12444
+ return safe;
12445
+ }
12446
+ var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
12447
+ function indexerReadErrorFromCapxul(operation, error, cause = error) {
12448
+ return new IndexerReadError({
12449
+ operation,
12450
+ publicCode: error.code,
12451
+ publicError: error,
12452
+ cause,
12453
+ ...error.details === void 0 ? {} : { details: error.details }
12454
+ });
12455
+ }
12456
+ Context.Service()("@capxul/sdk/ports/IndexerReadPort");
12457
+ //#endregion
12458
+ //#region ../../packages/sdk/src/surface/activity-stitch.ts
12459
+ /** Epoch milliseconds of the block that carried the row. */
12460
+ function blockMillis(row) {
12461
+ return Number(row.timestamp) * 1e3;
12462
+ }
12463
+ /** `0x1234…abcd` — the counterparty of an unclaimed transfer is an address. */
12464
+ function shortAddressLabel(address) {
12465
+ return address.length <= 12 ? address : `${address.slice(0, 6)}…${address.slice(-4)}`;
12466
+ }
12467
+ function directionFor(safe, transfer) {
12468
+ const from = transfer.from.toLowerCase();
12469
+ const to = transfer.to.toLowerCase();
12470
+ if (from === safe && to === safe) return "self";
12471
+ return to === safe ? "in" : "out";
12472
+ }
12473
+ /**
12474
+ * The Transfer a protocol Payment moved is the same money as its `payment`
12475
+ * row. One actor sees one item for it, so the Transfer is claimed and does not
12476
+ * become a second row. Another transfer in the same transaction that this
12477
+ * payment did not move stays unclaimed and keeps its own row.
12478
+ */
12479
+ function claimedTransferIds(transfers, payments) {
12480
+ const claimed = /* @__PURE__ */ new Set();
12481
+ for (const payment of payments) {
12482
+ const match = transfers.find((transfer) => !claimed.has(transfer.id) && transfer.txHash === payment.txHash && transfer.token.toLowerCase() === payment.token.toLowerCase() && transfer.amount === payment.amount && transfer.to.toLowerCase() === payment.recipient.toLowerCase());
12483
+ if (match !== void 0) claimed.add(match.id);
12484
+ }
12485
+ return claimed;
12486
+ }
12487
+ function withAnnotation(item, annotation) {
12488
+ return annotation?.counterpartyLabel === void 0 ? item : {
12489
+ ...item,
12490
+ counterpartyLabel: annotation.counterpartyLabel
12491
+ };
12492
+ }
12493
+ /**
12494
+ * Confirmation comes from the chain, never from Convex.
12495
+ *
12496
+ * A Ponder row for this Payment's settlement means settled, and its receipt is
12497
+ * the indexed transaction. No Ponder row and a Convex row that still says
12498
+ * settled means the block that carried it was dropped: the row goes back to
12499
+ * `settling` and its annotation, which keys on the Payment id, is untouched.
12500
+ */
12501
+ function confirmPayment(intent, chain, hasSettlement) {
12502
+ if (chain !== void 0) return {
12503
+ ...intent,
12504
+ status: "settled",
12505
+ receipt: {
12506
+ chainId: chain.chainId,
12507
+ transactionHash: chain.txHash
12508
+ },
12509
+ updatedAt: blockMillis(chain)
12510
+ };
12511
+ if (hasSettlement && intent.status === "settled") return {
12512
+ ...intent,
12513
+ status: "settling",
12514
+ receipt: null
12515
+ };
12516
+ return intent;
12517
+ }
12518
+ /**
12519
+ * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
12520
+ * denominate its asset. The indexer serves only configured assets, so a row
12521
+ * the registry does not know means the SDK and that deployment disagree; it is
12522
+ * dropped rather than shown with a guessed currency.
12523
+ */
12524
+ function transferItem(safe, transfer, annotation) {
12525
+ const asset = configuredMoneyAssetById(transfer.assetId);
12526
+ if (asset === null) return null;
12527
+ const direction = directionFor(safe, transfer);
12528
+ const counterparty = direction === "in" ? transfer.from : transfer.to;
12529
+ const at = blockMillis(transfer);
12530
+ return withAnnotation({
12531
+ kind: "movement",
12532
+ id: transfer.id,
12533
+ classification: "movement_only_outflow",
12534
+ amount: {
12535
+ currency: asset.peg ?? asset.symbol,
12536
+ value: formatUnits(transfer.amount, asset.decimals),
12537
+ decimals: asset.decimals
12538
+ },
12539
+ direction,
12540
+ counterpartyLabel: shortAddressLabel(counterparty),
12541
+ receipt: {
12542
+ chainId: transfer.chainId,
12543
+ transactionHash: transfer.txHash
12544
+ },
12545
+ createdAt: at,
12546
+ updatedAt: at
12547
+ }, annotation);
12548
+ }
12549
+ /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
12550
+ function compareItems(left, right) {
12551
+ if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
12552
+ if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
12553
+ if (left.id === right.id) return 0;
12554
+ return left.id < right.id ? 1 : -1;
12555
+ }
12556
+ function stitchActivity(input) {
12557
+ const safe = input.safe.toLowerCase();
12558
+ const chainBySettlement = /* @__PURE__ */ new Map();
12559
+ for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
12560
+ const chainByPaymentId = /* @__PURE__ */ new Map();
12561
+ const settledPaymentIds = /* @__PURE__ */ new Set();
12562
+ for (const link of input.links) {
12563
+ settledPaymentIds.add(link.paymentId);
12564
+ const row = chainBySettlement.get(link.settlementId.toLowerCase());
12565
+ if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
12566
+ }
12567
+ const annotations = /* @__PURE__ */ new Map();
12568
+ for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
12569
+ const items = [];
12570
+ const claiming = [];
12571
+ for (const intent of input.intents) {
12572
+ if (intent.kind !== "payment") continue;
12573
+ const chain = chainByPaymentId.get(intent.id);
12574
+ if (chain !== void 0) claiming.push(chain);
12575
+ items.push(withAnnotation(confirmPayment(intent, chain, settledPaymentIds.has(intent.id)), annotations.get(`payment:${intent.id}`)));
12576
+ }
12577
+ const claimed = claimedTransferIds(input.transfers, claiming);
12578
+ for (const transfer of input.transfers) {
12579
+ if (claimed.has(transfer.id)) continue;
12580
+ const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
12581
+ if (item !== null) items.push(item);
12582
+ }
12583
+ return items.sort(compareItems);
12584
+ }
12585
+ /** The reference ids one stitched page needs its Convex overlay for. */
12586
+ function overlayReferences(items) {
12587
+ return items.map((item) => ({
12588
+ kind: item.kind,
12589
+ id: item.id
12590
+ }));
12591
+ }
12592
+ /**
12593
+ * Money in, money out and the pending share, one bucket per currency and
12594
+ * precision. Two assets that peg to the same currency at different precision
12595
+ * stay distinct buckets: their raw units are not interchangeable.
12596
+ */
12597
+ function summarizeItems(items) {
12598
+ const buckets = /* @__PURE__ */ new Map();
12599
+ for (const item of items) {
12600
+ const key = `${item.amount.currency}:${String(item.amount.decimals)}`;
12601
+ const bucket = buckets.get(key) ?? {
12602
+ currency: item.amount.currency,
12603
+ decimals: item.amount.decimals,
12604
+ in: 0n,
12605
+ out: 0n,
12606
+ pendingIn: 0n
12607
+ };
12608
+ const raw = rawUnits(item.amount.value, item.amount.decimals);
12609
+ if (item.direction === "in") {
12610
+ bucket.in += raw;
12611
+ if (item.kind === "payment" && item.status !== "settled") bucket.pendingIn += raw;
12612
+ } else if (item.direction === "out") bucket.out += raw;
12613
+ buckets.set(key, bucket);
12614
+ }
12615
+ return [...buckets.values()].map((bucket) => ({
12616
+ currency: bucket.currency,
12617
+ decimals: bucket.decimals,
12618
+ in: bucket.in.toString(),
12619
+ out: bucket.out.toString(),
12620
+ net: (bucket.in - bucket.out).toString(),
12621
+ pendingIn: bucket.pendingIn.toString()
12622
+ }));
12623
+ }
12624
+ /** The inverse of `formatUnits`, kept exact: `"1.25"` at 6 decimals is `1250000n`. */
12625
+ function rawUnits(value, decimals) {
12626
+ const negative = value.startsWith("-");
12627
+ const [whole = "0", fraction = ""] = (negative ? value.slice(1) : value).split(".");
12628
+ const raw = BigInt(`${whole}${fraction.padEnd(decimals, "0").slice(0, decimals)}`);
12629
+ return negative ? -raw : raw;
12630
+ }
12631
+ //#endregion
12632
+ //#region ../../packages/sdk/src/surface/activity-read.ts
12633
+ /** How many Convex intent rows one page reads for its labels and pending rows. */
12634
+ const INTENT_WINDOW = 100;
12635
+ /** The window a summary aggregates over. The indexer's own page bound. */
12636
+ const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
12637
+ function parseCursor(value) {
12638
+ if (value === void 0) return 0;
12639
+ try {
12640
+ const parsed = JSON.parse(value);
12641
+ return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
12642
+ } catch {
12643
+ return 0;
12644
+ }
12645
+ }
12646
+ function inRange(item, range) {
12647
+ if (range?.from !== void 0 && item.createdAt < range.from) return false;
12648
+ return range?.to === void 0 || item.createdAt <= range.to;
12649
+ }
12650
+ /** The caller's filter applied to the merged page. The join owns the order. */
12651
+ function matchesFilter(item, filter) {
12652
+ if (filter === void 0) return true;
12653
+ if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
12654
+ if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
12655
+ if (filter.status === void 0) return true;
12656
+ return item.kind === "payment" && filter.status.includes(item.status);
12657
+ }
12658
+ function overlay(deps, actor, controls, input) {
12659
+ return deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
12660
+ ...actor === void 0 ? {} : { actor },
12661
+ ...input.references === void 0 ? {} : { references: input.references },
12662
+ ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds }
12663
+ } }));
12664
+ }
12665
+ function convexPage(deps, actor, controls, input) {
12666
+ return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
12667
+ ...actor === void 0 ? {} : { actor },
12668
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor },
12669
+ ...input.limit === void 0 ? {} : { limit: input.limit },
12670
+ ...input.range === void 0 ? {} : { range: input.range }
12671
+ } }));
12672
+ }
12673
+ /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
12674
+ function chainRows(deps, safe, limit) {
12675
+ const page = {
12676
+ safe,
12677
+ limit: Math.min(limit, INDEXER_MAX_LIMIT)
12678
+ };
12679
+ return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
12680
+ transfers,
12681
+ payments
12682
+ })), Effect.catch(() => Effect.succeed(null)));
12683
+ }
12684
+ /** The stitched, filtered rows of one window, newest first. */
12685
+ function stitchedWindow(deps, actor, controls, window, params) {
12686
+ return Effect.gen(function* () {
12687
+ const scope = yield* overlay(deps, actor, controls, {});
12688
+ const convex = yield* convexPage(deps, actor, controls, {
12689
+ limit: INTENT_WINDOW,
12690
+ ...params.range === void 0 ? {} : { range: params.range }
12691
+ });
12692
+ const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
12693
+ if (rows === null || scope.safe === null) return {
12694
+ items: convex.items.filter((item) => matchesFilter(item, params.filter)),
12695
+ stitched: false
12696
+ };
12697
+ const decoration = yield* overlay(deps, actor, controls, {
12698
+ references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
12699
+ kind: "movement",
12700
+ id: row.id
12701
+ }))],
12702
+ settlementIds: rows.payments.map((row) => row.settlementId)
12703
+ });
12704
+ return {
12705
+ items: stitchActivity({
12706
+ safe: scope.safe,
12707
+ transfers: rows.transfers,
12708
+ payments: rows.payments,
12709
+ intents: convex.items,
12710
+ links: decoration.links,
12711
+ annotations: decoration.annotations
12712
+ }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
12713
+ stitched: true
12714
+ };
12715
+ });
12716
+ }
12717
+ function readActivityPage(deps, actor, controls, params) {
12718
+ const limit = params?.limit ?? 25;
12719
+ const offset = parseCursor(params?.cursor);
12720
+ return stitchedWindow(deps, actor, controls, offset + limit + 1, {
12721
+ ...params?.range === void 0 ? {} : { range: params.range },
12722
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
12723
+ }).pipe(Effect.map(({ items, stitched }) => {
12724
+ const observedAt = Date.now();
12725
+ if (!stitched) return {
12726
+ items: items.slice(0, limit),
12727
+ cursor: null,
12728
+ checkpoint: observedAt,
12729
+ observedAt
12730
+ };
12731
+ return {
12732
+ items: items.slice(offset, offset + limit),
12733
+ cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
12734
+ checkpoint: observedAt,
12735
+ observedAt
12736
+ };
12737
+ }));
12738
+ }
12739
+ function readActivitySummary(deps, actor, controls, params) {
12740
+ return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items }) => ({
12741
+ window: {
12742
+ from: params?.window?.from ?? null,
12743
+ to: params?.window?.to ?? null
12744
+ },
12745
+ totals: summarizeItems(items),
12746
+ count: items.length,
12747
+ observedAt: Date.now()
12748
+ })));
12749
+ }
12750
+ /**
12751
+ * Tell the caller when the indexed page for this Safe changed.
12752
+ *
12753
+ * The transport is the indexer's one SSE connection, wired in the Ponder
12754
+ * adapter under `activity.subscribe`. A reactive snapshot is not a product
12755
+ * event, so nothing here emits telemetry: the adapter reports a teardown, and
12756
+ * the caller re-reads.
12757
+ */
12758
+ function subscribeActivity(deps, actor, controls, onChange) {
12759
+ return Effect.gen(function* () {
12760
+ const scope = yield* overlay(deps, actor, controls, {});
12761
+ if (scope.safe === null) return () => {};
12762
+ const page = {
12763
+ safe: scope.safe,
12764
+ limit: INTENT_WINDOW
12765
+ };
12766
+ let delivered = false;
12767
+ const notify = () => {
12768
+ if (delivered) onChange();
12769
+ delivered = true;
12770
+ };
12771
+ return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
12772
+ if (snapshot.status === "ok") notify();
12773
+ });
12774
+ });
12775
+ }
12776
+ //#endregion
12380
12777
  //#region ../../packages/sdk/package.json
12381
- var version$1 = "4.2.0-rc.10";
12778
+ var version$1 = "4.2.0-rc.11";
12382
12779
  //#endregion
12383
12780
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
12384
12781
  /** Fixed fallback for failures that have no safe message. */
@@ -12480,10 +12877,15 @@ function observeFailedResult(result, adapter, operation, origin) {
12480
12877
  * A reactive stream is torn down after its call already returned, so nothing
12481
12878
  * downstream observes it. `captureException` is the right kind: the caller did
12482
12879
  * not ask for the end, so it is not an expected product outcome.
12880
+ *
12881
+ * A caller that DID ask for the end — the session it authenticated with
12882
+ * expired, and the service closed the stream on purpose — passes
12883
+ * `kind: "operation"` instead, so the refusal lands in the expected-outcome
12884
+ * stream its code already declares.
12483
12885
  */
12484
- function observeStreamFailure(adapter, operation, cause) {
12886
+ function observeStreamFailure(adapter, operation, cause, kind = "exception") {
12485
12887
  if (adapter === void 0) return;
12486
- report(adapter, "exception", operation, cause, resolveAdapterSnapshot(adapter));
12888
+ report(adapter, kind, operation, cause, resolveAdapterSnapshot(adapter));
12487
12889
  }
12488
12890
  /**
12489
12891
  * One failure, one event. The identity machine reports a failed transition
@@ -13475,6 +13877,11 @@ function paymentExecutionIntent(input) {
13475
13877
  }
13476
13878
  function makeFinancialOpsMethods(deps) {
13477
13879
  const fns = deps.functions ?? financialOpsContract;
13880
+ const indexerDeps = deps.indexerRead === void 0 ? void 0 : {
13881
+ indexer: deps.indexerRead,
13882
+ convexCall: deps.convexCall,
13883
+ functions: fns
13884
+ };
13478
13885
  const executionFns = deps.moneyExecutionFunctions ?? moneyExecutionContract;
13479
13886
  const requestKeyScope = deps.requestKeyScope ?? randomPaymentRequestKey();
13480
13887
  const inFlightPersonalPayments = /* @__PURE__ */ new Map();
@@ -13705,22 +14112,26 @@ function makeFinancialOpsMethods(deps) {
13705
14112
  }
13706
14113
  },
13707
14114
  activity: {
14115
+ subscribe: (onChange, params, options) => {
14116
+ const controls = deps.invocationControls?.(options) ?? options;
14117
+ return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => indexerDeps === void 0 ? Effect.succeed(() => {}) : Effect.suspend(() => subscribeActivity(indexerDeps, actor, controls, onChange)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.subscribe, deps.runPromise);
14118
+ },
13708
14119
  list: (params, options) => {
13709
14120
  const controls = deps.invocationControls?.(options) ?? options;
13710
- return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => deps.convexCall.query(fns.activityList, copyInvocationObservation(controls, { input: {
14121
+ return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => indexerDeps === void 0 ? deps.convexCall.query(fns.activityList, copyInvocationObservation(controls, { input: {
13711
14122
  ...actor === void 0 ? {} : { actor },
13712
14123
  ...params?.cursor === void 0 ? {} : { cursor: params.cursor },
13713
14124
  ...params?.limit === void 0 ? {} : { limit: params.limit },
13714
14125
  ...params?.range === void 0 ? {} : { range: params.range },
13715
14126
  ...params?.filter === void 0 ? {} : { filter: params.filter }
13716
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
14127
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
13717
14128
  },
13718
14129
  summary: (params, options) => {
13719
14130
  const controls = deps.invocationControls?.(options) ?? options;
13720
- return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => deps.convexCall.query(fns.activitySummary, copyInvocationObservation(controls, { input: {
14131
+ return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(params?.actor).actor)).pipe(Effect.flatMap((actor) => Effect.suspend(() => indexerDeps === void 0 ? deps.convexCall.query(fns.activitySummary, copyInvocationObservation(controls, { input: {
13721
14132
  ...actor === void 0 ? {} : { actor },
13722
14133
  ...params?.window === void 0 ? {} : { window: params.window }
13723
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
14134
+ } })) : readActivitySummary(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
13724
14135
  },
13725
14136
  get: (reference, options) => {
13726
14137
  const controls = deps.invocationControls?.(options) ?? options;
@@ -18687,6 +19098,9 @@ const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
18687
19098
  "org/actions:resumeBootstrapSubmission",
18688
19099
  "org/actions:confirmBootstrap",
18689
19100
  "org/actions:readTreasury",
19101
+ "moneyExecution/actions:preparePermissionExecution",
19102
+ "moneyExecution/actions:submitPermissionExecution",
19103
+ "permission/actions:verify",
18690
19104
  "moneyExecution/actions:preparePaymentExecution",
18691
19105
  "moneyExecution/actions:abandonPaymentExecution",
18692
19106
  "moneyExecution/actions:submitPaymentExecution",
@@ -18711,6 +19125,9 @@ const OBSERVED_CONVEX_QUERIES = /* @__PURE__ */ new Set([
18711
19125
  "payroll/queries:groups",
18712
19126
  "payroll/queries:terms",
18713
19127
  "financialOps/queries:resolveRecipient",
19128
+ "org/queries:me",
19129
+ "permission/queries:read",
19130
+ "permission/queries:authorize",
18714
19131
  "identity/queries:loadByAuthUserId",
18715
19132
  "smartAccount/queries:loadByAuthUserId",
18716
19133
  "org/lifecycle:load"
@@ -18729,6 +19146,7 @@ const OBSERVED_CONVEX_MUTATIONS = /* @__PURE__ */ new Set([
18729
19146
  "financialOps/requestsInbox:decline",
18730
19147
  "payroll/mutations:saveGroup",
18731
19148
  "payroll/mutations:removeGroup",
19149
+ "permission/mutations:command",
18732
19150
  "identity/mutations:create",
18733
19151
  "identity/mutations:update",
18734
19152
  "identity/mutations:completeOnboarding",
@@ -44732,8 +45150,33 @@ function toOrgMe(wire) {
44732
45150
  observedAt: wire.observedAt
44733
45151
  };
44734
45152
  }
45153
+ /**
45154
+ * The read runs through the canonical observed runner, so `org.me` owns one
45155
+ * `capxul_sdk_operation` record and one span like every other public method.
45156
+ *
45157
+ * The call-start invocation snapshot travels on the query arguments: the
45158
+ * Convex adapter reads it there and sends the browser linkage with the call,
45159
+ * so an account switch mid-flight cannot re-resolve the actor. A successful
45160
+ * read proves participation in this Organization, so the owning span carries
45161
+ * the verified Organization and the two authority booleans the backend
45162
+ * actually enforces. Labels and Budget caps stay off the record.
45163
+ */
44735
45164
  function makeOrgMeMethod(deps, orgId) {
44736
- return (options) => runIfActive(options?.signal, CAPXUL_OPERATIONS.org.me, () => Effect.map(deps.convexCall.query(orgReadContract.me, { orgId }), toOrgMe));
45165
+ return (options) => {
45166
+ const controls = deps.invocationControls?.(options) ?? options;
45167
+ return runCapxulEffect(Effect.gen(function* () {
45168
+ const wire = yield* deps.convexCall.query(orgReadContract.me, copyInvocationObservation(controls, { orgId })).pipe(Effect.mapError((failure) => failure.publicError));
45169
+ yield* Effect.annotateCurrentSpan({
45170
+ verified_organization_id: orgId,
45171
+ scope_kind: "organization",
45172
+ can_manage_people: wire.capabilities.canManagePeople,
45173
+ can_spend: wire.capabilities.canSpend,
45174
+ budget_count: wire.budgets.length,
45175
+ observed_at: wire.observedAt
45176
+ }).pipe(Effect.catchCause(() => Effect.void));
45177
+ return toOrgMe(wire);
45178
+ }), controls, CAPXUL_OPERATIONS.org.me, deps.runPromise);
45179
+ };
44737
45180
  }
44738
45181
  //#endregion
44739
45182
  //#region ../../packages/sdk/src/contract/permission.ts
@@ -44753,16 +45196,24 @@ function isAborted(signal) {
44753
45196
  */
44754
45197
  function observed(deps, operation, options, run) {
44755
45198
  const controls = deps.invocationControls?.(options) ?? options;
45199
+ let verifiedOrganizationId;
44756
45200
  return runCapxulEffect(Effect.gen(function* () {
44757
45201
  if (controls?.signal?.aborted === true) return yield* Effect.fail(Errors.cancelled({ operation }));
45202
+ const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
45203
+ const captured = carryInvocationParentSpan(controls, parent);
44758
45204
  const result = yield* Effect.tryPromise({
44759
- try: () => run(controls?.signal),
45205
+ try: () => run(controls?.signal, captured, (orgId) => {
45206
+ verifiedOrganizationId = orgId;
45207
+ }),
44760
45208
  catch: (cause) => cause instanceof CapxulError ? cause : Errors.unknown(cause)
44761
45209
  });
44762
45210
  return yield* result.ok ? Effect.succeed(result.value) : Effect.fail(result.error);
44763
- }), observationOnlyControls(controls), operation, deps.runPromise);
45211
+ }).pipe(Effect.ensuring(Effect.suspend(() => verifiedOrganizationId === void 0 ? Effect.void : Effect.annotateCurrentSpan({
45212
+ verified_organization_id: verifiedOrganizationId,
45213
+ scope_kind: "organization"
45214
+ })).pipe(Effect.catchCause(() => Effect.void)))), observationOnlyControls(controls), operation, deps.runPromise);
44764
45215
  }
44765
- async function execute(deps, orgId, command, signal) {
45216
+ async function execute(deps, orgId, command, signal, controls, verifiedOrganization) {
44766
45217
  if (isAborted(signal)) return {
44767
45218
  ok: false,
44768
45219
  error: Errors.cancelled({ operation: "permissions" })
@@ -44786,17 +45237,18 @@ async function execute(deps, orgId, command, signal) {
44786
45237
  };
44787
45238
  }
44788
45239
  const executionFns = deps.executionFunctions ?? moneyExecutionContract;
44789
- const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => deps.convexCall.action(executionFns.preparePermissionExecution, { input: {
45240
+ const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.preparePermissionExecution, copyInvocationObservation(controls, { input: {
44790
45241
  orgId,
44791
45242
  signerAddress,
44792
45243
  command
44793
- } }));
45244
+ } }))), controls), deps.runPromise);
44794
45245
  if (!prepared.ok) return prepared;
44795
45246
  const expectedSafe = deriveCapxulSafeAddress({ email: session.email });
44796
45247
  if (prepared.value.chainId !== deps.chainId || prepared.value.signerAddress.toLowerCase() !== signerAddress.toLowerCase() || prepared.value.userOpSenderSafe.toLowerCase() !== expectedSafe.toLowerCase() || prepared.value.operation !== command.operation || prepared.value.orgId !== orgId || canonicalJson(prepared.value.command) !== canonicalJson(command)) return {
44797
45248
  ok: false,
44798
45249
  error: Errors.invalidInput("permission", "prepared authority mismatch")
44799
45250
  };
45251
+ verifiedOrganization(prepared.value.orgId);
44800
45252
  if (isAborted(signal)) return {
44801
45253
  ok: false,
44802
45254
  error: Errors.cancelled({ operation: "permissions" })
@@ -44814,38 +45266,40 @@ async function execute(deps, orgId, command, signal) {
44814
45266
  ok: false,
44815
45267
  error: Errors.cancelled({ operation: "permissions" })
44816
45268
  };
44817
- return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => deps.convexCall.action(executionFns.submitPermissionExecution, { input: {
45269
+ return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.submitPermissionExecution, copyInvocationObservation(controls, { input: {
44818
45270
  executionId: prepared.value.executionId,
44819
45271
  signature
44820
- } }));
45272
+ } }))), controls), deps.runPromise);
44821
45273
  }
44822
45274
  function makePermissionMethods(deps, orgId) {
44823
45275
  const reads = deps.permissionFunctions ?? permissionContract;
44824
45276
  const ops = CAPXUL_OPERATIONS.org.permissions;
45277
+ const read = (signal, operation, controls) => runIfActive(signal, operation, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.query(reads.read, copyInvocationObservation(controls, { orgId }))), controls), deps.runPromise);
45278
+ const command = (operation, build, options) => observed(deps, operation, options, (signal, captured, verifiedOrganization) => execute(deps, orgId, build(), signal, captured, verifiedOrganization));
44825
45279
  return {
44826
- create: (input, options) => observed(deps, ops.create, options, (signal) => execute(deps, orgId, {
45280
+ create: (input, options) => command(ops.create, () => ({
44827
45281
  operation: "create",
44828
45282
  ...input
44829
- }, signal)),
44830
- change: (input, options) => observed(deps, ops.change, options, (signal) => execute(deps, orgId, {
45283
+ }), options),
45284
+ change: (input, options) => command(ops.change, () => ({
44831
45285
  operation: "change",
44832
45286
  ...input
44833
- }, signal)),
44834
- assign: (input, options) => observed(deps, ops.assign, options, (signal) => execute(deps, orgId, {
45287
+ }), options),
45288
+ assign: (input, options) => command(ops.assign, () => ({
44835
45289
  operation: "assign",
44836
45290
  ...input
44837
- }, signal)),
44838
- revoke: (input, options) => observed(deps, ops.revoke, options, (signal) => execute(deps, orgId, {
45291
+ }), options),
45292
+ revoke: (input, options) => command(ops.revoke, () => ({
44839
45293
  operation: "revoke",
44840
45294
  ...input
44841
- }, signal)),
44842
- replace: (input, options) => observed(deps, ops.replace, options, (signal) => execute(deps, orgId, {
45295
+ }), options),
45296
+ replace: (input, options) => command(ops.replace, () => ({
44843
45297
  operation: "replace",
44844
45298
  ...input
44845
- }, signal)),
44846
- list: (options) => observed(deps, ops.list, options, (signal) => runIfActive(signal, CAPXUL_OPERATIONS.permissions.list, () => deps.convexCall.query(reads.read, { orgId }))),
44847
- get: (permissionId, options) => observed(deps, ops.get, options, async (signal) => {
44848
- const listed = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.get, () => deps.convexCall.query(reads.read, { orgId }));
45299
+ }), options),
45300
+ list: (options) => observed(deps, ops.list, options, (signal, captured) => read(signal, CAPXUL_OPERATIONS.permissions.list, captured)),
45301
+ get: (permissionId, options) => observed(deps, ops.get, options, async (signal, captured) => {
45302
+ const listed = await read(signal, CAPXUL_OPERATIONS.permissions.get, captured);
44849
45303
  if (!listed.ok) return listed;
44850
45304
  return {
44851
45305
  ok: true,
@@ -45716,7 +46170,10 @@ function makeOrgMethods(deps) {
45716
46170
  },
45717
46171
  ...convexCall === void 0 ? {} : { convexCall }
45718
46172
  }),
45719
- me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({ convexCall }, String(orgId)),
46173
+ me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({
46174
+ convexCall,
46175
+ ...observedRunnerDeps(deps)
46176
+ }, String(orgId)),
45720
46177
  async getLifecycle(options) {
45721
46178
  if (options?.signal?.aborted) return {
45722
46179
  ok: false,
@@ -45953,14 +46410,65 @@ function compareDecimal(first, second) {
45953
46410
  }
45954
46411
  //#endregion
45955
46412
  //#region ../../packages/sdk/src/surface/holdings.ts
46413
+ /** SPEC Freshness: past this block age the read is labelled stale. Nothing refuses. */
46414
+ const FRESHNESS_LIMIT_SECONDS = 60;
46415
+ /**
46416
+ * The Ponder balance view for one Safe as `CurrentHoldings`.
46417
+ *
46418
+ * The view is the signed sum of every indexed transfer, so the balance IS what
46419
+ * the chain said — there is no snapshot to go stale behind it. `observedAt` is
46420
+ * the indexed block's own timestamp, which is what the last-good label reads,
46421
+ * and `stale` is that block's age. A stopped indexer therefore shows an old
46422
+ * "as of" and the stale flag; it never turns the read into a refusal.
46423
+ */
46424
+ function indexerHoldings(indexer, convexCall, overlay, actor, controls) {
46425
+ return Effect.gen(function* () {
46426
+ const scope = yield* convexCall.query(overlay, copyInvocationObservation(controls, actor?.kind === "organization" ? { input: { actor: {
46427
+ kind: "organization",
46428
+ orgId: actor.organizationId
46429
+ } } } : { input: {} }));
46430
+ if (scope.safe === null) return null;
46431
+ const balances = yield* indexer.read("balance", {
46432
+ safe: toAddress(scope.safe),
46433
+ limit: INDEXER_MAX_LIMIT
46434
+ });
46435
+ const status = yield* indexer.status.pipe(Effect.catch(() => Effect.succeed([])));
46436
+ const rows = [];
46437
+ for (const balance of balances) {
46438
+ const asset = configuredMoneyAssetById(balance.assetId);
46439
+ if (asset === null || balance.amount < 0n) continue;
46440
+ rows.push({
46441
+ assetKind: "erc20",
46442
+ tokenAddress: toAddress(asset.tokenAddress),
46443
+ symbol: asset.symbol,
46444
+ decimals: asset.decimals,
46445
+ rawBalance: toWeiAmount(balance.amount.toString())
46446
+ });
46447
+ }
46448
+ const chainId = balances[0]?.chainId ?? status[0]?.chainId;
46449
+ if (chainId === void 0) return null;
46450
+ const head = status.find((entry) => entry.chainId === chainId);
46451
+ const nowSeconds = Math.floor(Date.now() / 1e3);
46452
+ const headSeconds = head === void 0 ? void 0 : Number(head.blockTimestamp);
46453
+ return {
46454
+ actor: scope.actor,
46455
+ chainId: toChainId(chainId),
46456
+ observedAt: headSeconds === void 0 ? Date.now() : headSeconds * 1e3,
46457
+ stale: headSeconds === void 0 || nowSeconds - headSeconds > FRESHNESS_LIMIT_SECONDS,
46458
+ rows
46459
+ };
46460
+ }).pipe(Effect.catch(() => Effect.succeed(null)));
46461
+ }
45956
46462
  function makeHoldingsMethods(deps) {
45957
46463
  const functions = deps.functions ?? holdingsContract;
46464
+ const overlay = (deps.financialOpsFunctions ?? financialOpsContract).activityOverlay;
45958
46465
  const read = (operation, actor, options, pick) => {
45959
46466
  const controls = deps.invocationControls?.(options) ?? options;
45960
- return runCapxulEffect(retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
46467
+ const convexSnapshot = retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
45961
46468
  kind: "organization",
45962
46469
  orgId: actor.organizationId
45963
- } } : {})), operation, deps.telemetry, controls).pipe(Effect.map(pick), Effect.mapError((error) => error.publicError)), controls, operation, deps.runPromise);
46470
+ } } : {})), operation, deps.telemetry, controls).pipe(Effect.mapError((error) => error.publicError));
46471
+ return runCapxulEffect((deps.indexerRead === void 0 ? convexSnapshot : indexerHoldings(deps.indexerRead, deps.convexCall, overlay, actor, controls).pipe(Effect.flatMap((current) => current === null ? convexSnapshot : Effect.succeed(current)))).pipe(Effect.map(pick)), controls, operation, deps.runPromise);
45964
46472
  };
45965
46473
  return {
45966
46474
  current: (input, options) => read(CAPXUL_OPERATIONS.holdings.current, input?.actor, options, (snapshot) => snapshot),
@@ -46094,8 +46602,8 @@ const APPLIED_ACTION_BY_EVENT = {
46094
46602
  RestoreSession: "none",
46095
46603
  ResumeOtpEntry: "none",
46096
46604
  Reset: "none",
46097
- SessionRestored: "none",
46098
- SessionAbsent: "none",
46605
+ SessionRestored: "restoration",
46606
+ SessionAbsent: "restoration",
46099
46607
  EnsureAccount: "none",
46100
46608
  ClaimAccount: "none",
46101
46609
  RetryAccount: "none",
@@ -46107,8 +46615,8 @@ const APPLIED_ACTION_BY_EVENT = {
46107
46615
  OtpFailed: "failed",
46108
46616
  Verified: "verified",
46109
46617
  VerifyFailed: "failed",
46110
- SessionRead: "none",
46111
- SessionReadFailed: "none",
46618
+ SessionRead: "restoration",
46619
+ SessionReadFailed: "restoration",
46112
46620
  SignedOut: "signed-out",
46113
46621
  SignOutFailed: "failed",
46114
46622
  AccountAt: "none",
@@ -46134,6 +46642,49 @@ const authFailure = (record, reason, mode) => ({
46134
46642
  ...linkage(record)
46135
46643
  }
46136
46644
  });
46645
+ /**
46646
+ * The label of the restore window every client boots into. A settled session
46647
+ * fact that left this label is the restoration result (#1962 R04); the same
46648
+ * facts from a signed-out or authenticated state are the routine session probe
46649
+ * that `getSession()` runs, and a probe is not a restoration attempt.
46650
+ */
46651
+ const RESTORE_WINDOW = "restoring";
46652
+ /**
46653
+ * Which result the window settled on. `SessionRestored` and `SessionAbsent` are
46654
+ * the cache door and say it themselves. `ReadSession` is also valid inside the
46655
+ * window — `getSession()` drives it on a cache miss and its `SessionRead`
46656
+ * completion settles the window — so that one event reports both halves and the
46657
+ * state it produced is what separates them.
46658
+ */
46659
+ const restorationOutcome = (event, state) => {
46660
+ switch (event) {
46661
+ case "SessionRestored": return "restored";
46662
+ case "SessionAbsent": return "absent";
46663
+ case "SessionReadFailed": return "failed";
46664
+ case "SessionRead":
46665
+ if (state.phase === "authenticated") return "restored";
46666
+ return state.phase === "signed_out" ? "absent" : void 0;
46667
+ default: return;
46668
+ }
46669
+ };
46670
+ /**
46671
+ * The restore window's one result, or nothing when this settled session fact
46672
+ * belongs to a probe rather than to the window.
46673
+ */
46674
+ const restorationObservation = (record, state) => {
46675
+ if (record.slot !== "identity:session" || record.from !== RESTORE_WINDOW) return NONE;
46676
+ const outcome = restorationOutcome(record.event, state);
46677
+ if (outcome === void 0) return NONE;
46678
+ return {
46679
+ name: "access_recovery_completed",
46680
+ props: {
46681
+ recovery: "session_restoration",
46682
+ outcome,
46683
+ ...outcome === "failed" && state.phase === "faulted" ? { reason_code: state.failure.code } : {},
46684
+ ...linkage(record)
46685
+ }
46686
+ };
46687
+ };
46137
46688
  const REFUSAL_EVENTS = /* @__PURE__ */ new Set([
46138
46689
  "ClaimAccount",
46139
46690
  "RetryAccount",
@@ -46178,6 +46729,7 @@ function mapIdentityProductObservation(record, state) {
46178
46729
  name: "auth_signed_out",
46179
46730
  props: linkage(record)
46180
46731
  };
46732
+ case "restoration": return restorationObservation(record, state);
46181
46733
  }
46182
46734
  }
46183
46735
  const ignoreTransportFailure = (operation, operationName, eventName) => Effect.suspend(operation).pipe(Effect.catchCause(() => Effect.logWarning("identity.product.telemetry.dropped").pipe(Effect.annotateLogs({
@@ -46189,7 +46741,7 @@ function executeIdentityProductObservation(telemetry, record, state) {
46189
46741
  if (mapped === null) return Effect.void;
46190
46742
  copyInvocationObservation(record, mapped);
46191
46743
  const invocationTelemetry = bindProductTelemetryInvocation(telemetry, mapped);
46192
- const after = () => mapped.name === "auth_verified" && state.phase === "authenticated" ? invocationTelemetry.identify({
46744
+ const after = () => state.phase === "authenticated" && (mapped.name === "auth_verified" || mapped.name === "access_recovery_completed") ? invocationTelemetry.identify({
46193
46745
  distinctId: state.session.authUserId,
46194
46746
  traits: { email_domain: emailDomain(state.session.email) }
46195
46747
  }) : mapped.name === "auth_signed_out" ? invocationTelemetry.reset() : Effect.void;
@@ -46234,6 +46786,51 @@ function startSignerReadinessOnClaim(actor, signer) {
46234
46786
  browserSigner.ensureWalletReady?.().catch(() => {});
46235
46787
  });
46236
46788
  }
46789
+ /**
46790
+ * Report the result of each signer readiness cycle (#1962 R04).
46791
+ *
46792
+ * The readiness store already carries the cycle boundary this needs, so no
46793
+ * public signer hook, second identity subscriber, or render counter is added.
46794
+ * `ensureOpenfortWalletReady` writes `recovering` synchronously when it starts
46795
+ * a cycle, and only the cycle that is still the current one writes `ready` or
46796
+ * `unavailable` (`create-openfort-browser-signer.ts:501-521`). Callers that
46797
+ * join a live cycle share that single write, and a `resetSession` writes
46798
+ * `unknown` first, so a cycle that a reset or an account replacement outran
46799
+ * reports nothing.
46800
+ *
46801
+ * The host context is bound when the cycle starts, so a person who signs out
46802
+ * and back in while a cycle runs cannot relabel the result that began under
46803
+ * the previous person. A signer with no readiness cycle — a node or dev key,
46804
+ * and a headless client whose requirement never opens the account lane —
46805
+ * writes no status and reports nothing.
46806
+ */
46807
+ function observeSignerReadinessCycles(signer, telemetry, hostObservationSnapshot, runPromise) {
46808
+ const statusStore = signer?.statusStore;
46809
+ if (statusStore === void 0 || telemetry === void 0) return () => {};
46810
+ let running = false;
46811
+ let startContext;
46812
+ return statusStore.subscribe((status) => {
46813
+ if (status === "recovering") {
46814
+ running = true;
46815
+ startContext = hostObservationSnapshot?.();
46816
+ return;
46817
+ }
46818
+ const cycleContext = startContext;
46819
+ const completesCycle = running;
46820
+ running = false;
46821
+ startContext = void 0;
46822
+ if (!completesCycle || status !== "ready" && status !== "unavailable") return;
46823
+ const event = {
46824
+ name: "access_recovery_completed",
46825
+ props: {
46826
+ recovery: "signer_readiness",
46827
+ outcome: status
46828
+ }
46829
+ };
46830
+ if (cycleContext !== void 0) attachInvocationObservation(event, cycleContext);
46831
+ runPromise(Effect.suspend(() => bindProductTelemetryInvocation(telemetry, event).emit(event)).pipe(Effect.catchCause(() => Effect.void))).catch(() => {});
46832
+ });
46833
+ }
46237
46834
  function assembleCapxulClient(input) {
46238
46835
  const observation = input;
46239
46836
  const authCache = input.authCache ?? detectAuthCacheAdapter();
@@ -46263,12 +46860,14 @@ function assembleCapxulClient(input) {
46263
46860
  }, failureObservation, normalizeCapxulOperation(`identity.${record.event}`), "machine");
46264
46861
  });
46265
46862
  const unsubscribeSignerReadiness = startSignerReadinessOnClaim(actor, input.signer);
46863
+ const unsubscribeReadinessObservation = observeSignerReadinessCycles(input.signer, input.ports.telemetry, observation.hostObservationSnapshot, effectRunner.runPromise);
46266
46864
  let stopPromise = null;
46267
46865
  const stopActor = () => {
46268
46866
  if (stopPromise === null) {
46269
46867
  unsubscribeProductTelemetry();
46270
46868
  unsubscribeFailureObservation();
46271
46869
  unsubscribeSignerReadiness();
46870
+ unsubscribeReadinessObservation();
46272
46871
  stopPromise = effectRunner.runPromise(Scope.close(scope, Exit.void));
46273
46872
  }
46274
46873
  return stopPromise;
@@ -46285,7 +46884,8 @@ function assembleCapxulClient(input) {
46285
46884
  convexCall: input.ports.convexCall,
46286
46885
  ...input.signer === void 0 ? {} : { signer: input.signer },
46287
46886
  telemetry: input.ports.telemetry,
46288
- requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`
46887
+ requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`,
46888
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
46289
46889
  });
46290
46890
  const accountBundle = makeAccountMethods({
46291
46891
  actor,
@@ -46356,7 +46956,8 @@ function assembleCapxulClient(input) {
46356
46956
  convexCall: input.ports.convexCall,
46357
46957
  telemetry: input.ports.telemetry,
46358
46958
  runPromise: effectRunner.runPromise,
46359
- invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId)
46959
+ invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId),
46960
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
46360
46961
  });
46361
46962
  const accounts = makeAccountsMethods({
46362
46963
  accountReadPort: input.ports.accountRead,
@@ -46563,36 +47164,6 @@ function snapshotHostObservability(observability) {
46563
47164
  };
46564
47165
  }
46565
47166
  //#endregion
46566
- //#region ../../packages/sdk/src/ports/indexer-read.ts
46567
- /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
46568
- const INDEXER_MAX_LIMIT = 1e3;
46569
- const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
46570
- /**
46571
- * Refuse a page the indexer would refuse, before any implementation spends a
46572
- * round trip on it. Every implementation calls this, so a consumer test written
46573
- * against the hermetic adapter cannot pass on input production rejects. Throws
46574
- * a `CapxulError`; callers convert it to an `IndexerReadError`.
46575
- */
46576
- function assertIndexerPage(input) {
46577
- const safe = String(input.safe).toLowerCase();
46578
- if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
46579
- if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 1e3) throw Errors.invalidInput("limit", `must be an integer from 1 to ${INDEXER_MAX_LIMIT}`);
46580
- const offset = input.offset ?? 0;
46581
- if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
46582
- return safe;
46583
- }
46584
- var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
46585
- function indexerReadErrorFromCapxul(operation, error, cause = error) {
46586
- return new IndexerReadError({
46587
- operation,
46588
- publicCode: error.code,
46589
- publicError: error,
46590
- cause,
46591
- ...error.details === void 0 ? {} : { details: error.details }
46592
- });
46593
- }
46594
- Context.Service()("@capxul/sdk/ports/IndexerReadPort");
46595
- //#endregion
46596
47167
  //#region ../../packages/sdk/src/adapters/ponder/decode.ts
46597
47168
  /**
46598
47169
  * Decode the indexer's rows at the trust boundary.
@@ -46898,7 +47469,7 @@ var PonderIndexerAdapter = class {
46898
47469
  }
46899
47470
  if (stopped()) return;
46900
47471
  const error = failure ?? Errors.capabilityUnavailable("indexer", operation);
46901
- observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error);
47472
+ observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error, error.code === "NOT_AUTHENTICATED" ? "operation" : "exception");
46902
47473
  callback({
46903
47474
  status: "error",
46904
47475
  error
@@ -47743,6 +48314,7 @@ function idempotentClose(close) {
47743
48314
  * and Convex see one session with one scope.
47744
48315
  */
47745
48316
  function productionIndexerReadPort(input, ports, observation) {
48317
+ if (input.activityReadSource === "convex") return void 0;
47746
48318
  if (input.indexerUrl === void 0 || input.indexerUrl === "") return void 0;
47747
48319
  return new PonderIndexerAdapter({
47748
48320
  baseUrl: normalizeHttpUrl("indexerUrl", input.indexerUrl),
@@ -48369,7 +48941,7 @@ function stdioIdentity(env = process.env, options = {}) {
48369
48941
  }
48370
48942
  //#endregion
48371
48943
  //#region package.json
48372
- var version = "4.2.0-rc.10";
48944
+ var version = "4.2.0-rc.11";
48373
48945
  //#endregion
48374
48946
  //#region src/deps.ts
48375
48947
  const NO_IDENTITY = async () => void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/mcp",
3
- "version": "4.2.0-rc.10",
3
+ "version": "4.2.0-rc.11",
4
4
  "private": false,
5
5
  "description": "Capxul MCP server for auth, Account readiness, identity, Organizations, relationships, requests, destinations, and testnet funding over Streamable HTTP.",
6
6
  "keywords": [
@@ -51,8 +51,8 @@
51
51
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
52
52
  "vite-plus": "0.3.0",
53
53
  "@capxul/config": "0.3.0",
54
- "@capxul/sdk": "4.2.0-rc.10",
55
- "@capxul/typescript-config": "0.0.0"
54
+ "@capxul/typescript-config": "0.0.0",
55
+ "@capxul/sdk": "4.2.0-rc.11"
56
56
  },
57
57
  "scripts": {
58
58
  "dev": "vp exec tsx src/main.ts serve",