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

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": "fd5b4d0f124c75f609e469b8673c61f84253c0dc",
4
+ "tree": "d61f0f6b0c83dfc2d405ffc6b55eecf2e9d80f2d",
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.12"
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) {
@@ -7135,6 +7149,8 @@ const CAPXUL_OPERATIONS = {
7135
7149
  recipient: { resolve: "recipient.resolve" },
7136
7150
  payroll: {
7137
7151
  authorizeRun: "payroll.authorizeRun",
7152
+ emitGroupChanged: "payroll.emitGroupChanged",
7153
+ emitRunTransitioned: "payroll.emitRunTransitioned",
7138
7154
  groups: {
7139
7155
  access: "payroll.groups",
7140
7156
  list: "payroll.groups.list",
@@ -7788,7 +7804,27 @@ const PermissionMirrorVerificationProps = Schema.Struct({
7788
7804
  "conflicting_replay",
7789
7805
  "revision_mismatch"
7790
7806
  ])),
7791
- command_id: OptionalString
7807
+ command_id: OptionalString,
7808
+ /**
7809
+ * #1961 R03. The authorized stored row names the Permission it changed; the
7810
+ * verified receipt names the chain facts that prove it. Every field is
7811
+ * optional because a pending or provider-failed verification has neither an
7812
+ * application result nor a receipt to report.
7813
+ */
7814
+ permission_id: OptionalString,
7815
+ permission_assignment_id: OptionalString,
7816
+ permission_operation: Schema.optional(Schema.Literals([
7817
+ "create",
7818
+ "change",
7819
+ "assign",
7820
+ "revoke",
7821
+ "replace"
7822
+ ])),
7823
+ revision: Schema.optional(Schema.Number),
7824
+ user_op_hash: OptionalString,
7825
+ transaction_hash: OptionalString,
7826
+ block_number: Schema.optional(Schema.Number),
7827
+ log_index: Schema.optional(Schema.Number)
7792
7828
  });
7793
7829
  const MovementScanWindowProps = Schema.Struct({
7794
7830
  ...TelemetryEnvelopeProps,
@@ -8334,6 +8370,12 @@ Schema.Struct({
8334
8370
  "settled"
8335
8371
  ]),
8336
8372
  run_id: Schema.String,
8373
+ /**
8374
+ * The `paymentCommands` row at `payrollRuns.batchRef` — the durable owner of
8375
+ * this run's Payment membership. `payment_count` counts THAT command's
8376
+ * `paymentIds`, so an unrelated Payment quoting the run id changes neither.
8377
+ */
8378
+ command_id: OptionalString,
8337
8379
  payment_count: Schema.optional(Schema.Number),
8338
8380
  occurred_at: Schema.Number
8339
8381
  })
@@ -8713,6 +8755,7 @@ const CAPXUL_FUNCTIONS = {
8713
8755
  list: "movement/activity:list",
8714
8756
  summary: "movement/activity:summary"
8715
8757
  },
8758
+ "movement/annotations": { overlay: "movement/annotations:overlay" },
8716
8759
  "moneyExecution/actions": {
8717
8760
  abandonPaymentExecution: "moneyExecution/actions:abandonPaymentExecution",
8718
8761
  preparePermissionExecution: "moneyExecution/actions:preparePermissionExecution",
@@ -9311,7 +9354,19 @@ const StoredPermissionCommandSchema = Schema.Struct({
9311
9354
  roleKey: RoleKeySchema,
9312
9355
  allowanceKey: Schema.NullOr(AllowanceKeySchema),
9313
9356
  memberSafe: Schema.NullOr(SafeAddressSchema),
9314
- expectedAuthorityEvents: Schema.Array(Bytes32Schema)
9357
+ expectedAuthorityEvents: Schema.Array(Bytes32Schema),
9358
+ /**
9359
+ * #1961 R03. The authenticated user who created the command, and the browser
9360
+ * linkage captured with it. Delayed verification and bounded retries read
9361
+ * this snapshot instead of the current viewer, so one command keeps one
9362
+ * origin. Both are absent on commands created before this contract.
9363
+ *
9364
+ * The context stays `unknown` on purpose: `sanitizeObservationContext` owns
9365
+ * the field allowlist, and every reader passes the stored value through it
9366
+ * rather than repeating those rules here.
9367
+ */
9368
+ originatingAuthUserId: Schema.optional(Schema.String),
9369
+ originatingObservationContext: Schema.optional(Schema.Unknown)
9315
9370
  });
9316
9371
  Schema.Struct({
9317
9372
  command: StoredPermissionCommandSchema,
@@ -12361,6 +12416,7 @@ const financialOpsContract = {
12361
12416
  activitySummary: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].summary),
12362
12417
  activityGet: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].get),
12363
12418
  activityAnnotate: makeFunctionReference(CAPXUL_FUNCTIONS["movement/activity"].annotate),
12419
+ activityOverlay: makeFunctionReference(CAPXUL_FUNCTIONS["movement/annotations"].overlay),
12364
12420
  verifyPaymentDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].verifyPaymentDocument),
12365
12421
  renderStoredDocument: makeFunctionReference(CAPXUL_FUNCTIONS["financialOps/queries"].renderStoredDocument)
12366
12422
  };
@@ -12377,8 +12433,357 @@ const moneyExecutionContract = {
12377
12433
  submitPaymentCommandExecution: makeFunctionReference(CAPXUL_FUNCTIONS["moneyExecution/paymentCommandActions"].submitPaymentCommandExecution)
12378
12434
  };
12379
12435
  //#endregion
12436
+ //#region ../../packages/sdk/src/ports/indexer-read.ts
12437
+ /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
12438
+ const INDEXER_MAX_LIMIT = 1e3;
12439
+ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
12440
+ /**
12441
+ * Refuse a page the indexer would refuse, before any implementation spends a
12442
+ * round trip on it. Every implementation calls this, so a consumer test written
12443
+ * against the hermetic adapter cannot pass on input production rejects. Throws
12444
+ * a `CapxulError`; callers convert it to an `IndexerReadError`.
12445
+ */
12446
+ function assertIndexerPage(input) {
12447
+ const safe = String(input.safe).toLowerCase();
12448
+ if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
12449
+ 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}`);
12450
+ const offset = input.offset ?? 0;
12451
+ if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
12452
+ return safe;
12453
+ }
12454
+ var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
12455
+ function indexerReadErrorFromCapxul(operation, error, cause = error) {
12456
+ return new IndexerReadError({
12457
+ operation,
12458
+ publicCode: error.code,
12459
+ publicError: error,
12460
+ cause,
12461
+ ...error.details === void 0 ? {} : { details: error.details }
12462
+ });
12463
+ }
12464
+ Context.Service()("@capxul/sdk/ports/IndexerReadPort");
12465
+ //#endregion
12466
+ //#region ../../packages/sdk/src/surface/activity-stitch.ts
12467
+ /** Epoch milliseconds of the block that carried the row. */
12468
+ function blockMillis(row) {
12469
+ return Number(row.timestamp) * 1e3;
12470
+ }
12471
+ /** `0x1234…abcd` — the counterparty of an unclaimed transfer is an address. */
12472
+ function shortAddressLabel(address) {
12473
+ return address.length <= 12 ? address : `${address.slice(0, 6)}…${address.slice(-4)}`;
12474
+ }
12475
+ function directionFor(safe, transfer) {
12476
+ const from = transfer.from.toLowerCase();
12477
+ const to = transfer.to.toLowerCase();
12478
+ if (from === safe && to === safe) return "self";
12479
+ return to === safe ? "in" : "out";
12480
+ }
12481
+ /**
12482
+ * The Transfer a protocol Payment moved is the same money as its `payment`
12483
+ * row. One actor sees one item for it, so the Transfer is claimed and does not
12484
+ * become a second row. Another transfer in the same transaction that this
12485
+ * payment did not move stays unclaimed and keeps its own row.
12486
+ */
12487
+ function claimedTransferIds(transfers, payments) {
12488
+ const claimed = /* @__PURE__ */ new Set();
12489
+ for (const payment of payments) {
12490
+ 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());
12491
+ if (match !== void 0) claimed.add(match.id);
12492
+ }
12493
+ return claimed;
12494
+ }
12495
+ function withAnnotation(item, annotation) {
12496
+ return annotation?.counterpartyLabel === void 0 ? item : {
12497
+ ...item,
12498
+ counterpartyLabel: annotation.counterpartyLabel
12499
+ };
12500
+ }
12501
+ /**
12502
+ * Confirmation comes from the chain, never from Convex.
12503
+ *
12504
+ * A Ponder row for this Payment's settlement means settled, and its receipt is
12505
+ * the indexed transaction. No Ponder row and a Convex row that still says
12506
+ * settled means the block that carried it was dropped: the row goes back to
12507
+ * `settling` and its annotation, which keys on the Payment id, is untouched.
12508
+ */
12509
+ function confirmPayment(intent, chain, hasSettlement) {
12510
+ if (chain !== void 0) return {
12511
+ ...intent,
12512
+ status: "settled",
12513
+ receipt: {
12514
+ chainId: chain.chainId,
12515
+ transactionHash: chain.txHash
12516
+ },
12517
+ updatedAt: blockMillis(chain)
12518
+ };
12519
+ if (hasSettlement && intent.status === "settled") return {
12520
+ ...intent,
12521
+ status: "settling",
12522
+ receipt: null
12523
+ };
12524
+ return intent;
12525
+ }
12526
+ /**
12527
+ * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
12528
+ * denominate its asset. The indexer serves only configured assets, so a row
12529
+ * the registry does not know means the SDK and that deployment disagree; it is
12530
+ * dropped rather than shown with a guessed currency.
12531
+ */
12532
+ function transferItem(safe, transfer, annotation) {
12533
+ const asset = configuredMoneyAssetById(transfer.assetId);
12534
+ if (asset === null) return null;
12535
+ const direction = directionFor(safe, transfer);
12536
+ const counterparty = direction === "in" ? transfer.from : transfer.to;
12537
+ const at = blockMillis(transfer);
12538
+ return withAnnotation({
12539
+ kind: "movement",
12540
+ id: transfer.id,
12541
+ classification: "movement_only_outflow",
12542
+ amount: {
12543
+ currency: asset.peg ?? asset.symbol,
12544
+ value: formatUnits(transfer.amount, asset.decimals),
12545
+ decimals: asset.decimals
12546
+ },
12547
+ direction,
12548
+ counterpartyLabel: shortAddressLabel(counterparty),
12549
+ receipt: {
12550
+ chainId: transfer.chainId,
12551
+ transactionHash: transfer.txHash
12552
+ },
12553
+ createdAt: at,
12554
+ updatedAt: at
12555
+ }, annotation);
12556
+ }
12557
+ /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
12558
+ function compareItems(left, right) {
12559
+ if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
12560
+ if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
12561
+ if (left.id === right.id) return 0;
12562
+ return left.id < right.id ? 1 : -1;
12563
+ }
12564
+ function stitchActivity(input) {
12565
+ const safe = input.safe.toLowerCase();
12566
+ const chainBySettlement = /* @__PURE__ */ new Map();
12567
+ for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
12568
+ const chainByPaymentId = /* @__PURE__ */ new Map();
12569
+ const settledPaymentIds = /* @__PURE__ */ new Set();
12570
+ for (const link of input.links) {
12571
+ settledPaymentIds.add(link.paymentId);
12572
+ const row = chainBySettlement.get(link.settlementId.toLowerCase());
12573
+ if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
12574
+ }
12575
+ const annotations = /* @__PURE__ */ new Map();
12576
+ for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
12577
+ const items = [];
12578
+ const claiming = [];
12579
+ for (const intent of input.intents) {
12580
+ if (intent.kind !== "payment") continue;
12581
+ const chain = chainByPaymentId.get(intent.id);
12582
+ if (chain !== void 0) claiming.push(chain);
12583
+ items.push(withAnnotation(confirmPayment(intent, chain, settledPaymentIds.has(intent.id)), annotations.get(`payment:${intent.id}`)));
12584
+ }
12585
+ const claimed = claimedTransferIds(input.transfers, claiming);
12586
+ for (const transfer of input.transfers) {
12587
+ if (claimed.has(transfer.id)) continue;
12588
+ const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
12589
+ if (item !== null) items.push(item);
12590
+ }
12591
+ return items.sort(compareItems);
12592
+ }
12593
+ /** The reference ids one stitched page needs its Convex overlay for. */
12594
+ function overlayReferences(items) {
12595
+ return items.map((item) => ({
12596
+ kind: item.kind,
12597
+ id: item.id
12598
+ }));
12599
+ }
12600
+ /**
12601
+ * Money in, money out and the pending share, one bucket per currency and
12602
+ * precision. Two assets that peg to the same currency at different precision
12603
+ * stay distinct buckets: their raw units are not interchangeable.
12604
+ */
12605
+ function summarizeItems(items) {
12606
+ const buckets = /* @__PURE__ */ new Map();
12607
+ for (const item of items) {
12608
+ const key = `${item.amount.currency}:${String(item.amount.decimals)}`;
12609
+ const bucket = buckets.get(key) ?? {
12610
+ currency: item.amount.currency,
12611
+ decimals: item.amount.decimals,
12612
+ in: 0n,
12613
+ out: 0n,
12614
+ pendingIn: 0n
12615
+ };
12616
+ const raw = rawUnits(item.amount.value, item.amount.decimals);
12617
+ if (item.direction === "in") {
12618
+ bucket.in += raw;
12619
+ if (item.kind === "payment" && item.status !== "settled") bucket.pendingIn += raw;
12620
+ } else if (item.direction === "out") bucket.out += raw;
12621
+ buckets.set(key, bucket);
12622
+ }
12623
+ return [...buckets.values()].map((bucket) => ({
12624
+ currency: bucket.currency,
12625
+ decimals: bucket.decimals,
12626
+ in: bucket.in.toString(),
12627
+ out: bucket.out.toString(),
12628
+ net: (bucket.in - bucket.out).toString(),
12629
+ pendingIn: bucket.pendingIn.toString()
12630
+ }));
12631
+ }
12632
+ /** The inverse of `formatUnits`, kept exact: `"1.25"` at 6 decimals is `1250000n`. */
12633
+ function rawUnits(value, decimals) {
12634
+ const negative = value.startsWith("-");
12635
+ const [whole = "0", fraction = ""] = (negative ? value.slice(1) : value).split(".");
12636
+ const raw = BigInt(`${whole}${fraction.padEnd(decimals, "0").slice(0, decimals)}`);
12637
+ return negative ? -raw : raw;
12638
+ }
12639
+ //#endregion
12640
+ //#region ../../packages/sdk/src/surface/activity-read.ts
12641
+ /** How many Convex intent rows one page reads for its labels and pending rows. */
12642
+ const INTENT_WINDOW = 100;
12643
+ /** The window a summary aggregates over. The indexer's own page bound. */
12644
+ const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
12645
+ function parseCursor(value) {
12646
+ if (value === void 0) return 0;
12647
+ try {
12648
+ const parsed = JSON.parse(value);
12649
+ return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
12650
+ } catch {
12651
+ return 0;
12652
+ }
12653
+ }
12654
+ function inRange(item, range) {
12655
+ if (range?.from !== void 0 && item.createdAt < range.from) return false;
12656
+ return range?.to === void 0 || item.createdAt <= range.to;
12657
+ }
12658
+ /** The caller's filter applied to the merged page. The join owns the order. */
12659
+ function matchesFilter(item, filter) {
12660
+ if (filter === void 0) return true;
12661
+ if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
12662
+ if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
12663
+ if (filter.status === void 0) return true;
12664
+ return item.kind === "payment" && filter.status.includes(item.status);
12665
+ }
12666
+ function overlay(deps, actor, controls, input) {
12667
+ return deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
12668
+ ...actor === void 0 ? {} : { actor },
12669
+ ...input.references === void 0 ? {} : { references: input.references },
12670
+ ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds }
12671
+ } }));
12672
+ }
12673
+ function convexPage(deps, actor, controls, input) {
12674
+ return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
12675
+ ...actor === void 0 ? {} : { actor },
12676
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor },
12677
+ ...input.limit === void 0 ? {} : { limit: input.limit },
12678
+ ...input.range === void 0 ? {} : { range: input.range }
12679
+ } }));
12680
+ }
12681
+ /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
12682
+ function chainRows(deps, safe, limit) {
12683
+ const page = {
12684
+ safe,
12685
+ limit: Math.min(limit, INDEXER_MAX_LIMIT)
12686
+ };
12687
+ return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
12688
+ transfers,
12689
+ payments
12690
+ })), Effect.catch(() => Effect.succeed(null)));
12691
+ }
12692
+ /** The stitched, filtered rows of one window, newest first. */
12693
+ function stitchedWindow(deps, actor, controls, window, params) {
12694
+ return Effect.gen(function* () {
12695
+ const scope = yield* overlay(deps, actor, controls, {});
12696
+ const convex = yield* convexPage(deps, actor, controls, {
12697
+ limit: INTENT_WINDOW,
12698
+ ...params.range === void 0 ? {} : { range: params.range }
12699
+ });
12700
+ const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
12701
+ if (rows === null || scope.safe === null) return {
12702
+ items: convex.items.filter((item) => matchesFilter(item, params.filter)),
12703
+ stitched: false
12704
+ };
12705
+ const decoration = yield* overlay(deps, actor, controls, {
12706
+ references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
12707
+ kind: "movement",
12708
+ id: row.id
12709
+ }))],
12710
+ settlementIds: rows.payments.map((row) => row.settlementId)
12711
+ });
12712
+ return {
12713
+ items: stitchActivity({
12714
+ safe: scope.safe,
12715
+ transfers: rows.transfers,
12716
+ payments: rows.payments,
12717
+ intents: convex.items,
12718
+ links: decoration.links,
12719
+ annotations: decoration.annotations
12720
+ }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
12721
+ stitched: true
12722
+ };
12723
+ });
12724
+ }
12725
+ function readActivityPage(deps, actor, controls, params) {
12726
+ const limit = params?.limit ?? 25;
12727
+ const offset = parseCursor(params?.cursor);
12728
+ return stitchedWindow(deps, actor, controls, offset + limit + 1, {
12729
+ ...params?.range === void 0 ? {} : { range: params.range },
12730
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
12731
+ }).pipe(Effect.map(({ items, stitched }) => {
12732
+ const observedAt = Date.now();
12733
+ if (!stitched) return {
12734
+ items: items.slice(0, limit),
12735
+ cursor: null,
12736
+ checkpoint: observedAt,
12737
+ observedAt
12738
+ };
12739
+ return {
12740
+ items: items.slice(offset, offset + limit),
12741
+ cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
12742
+ checkpoint: observedAt,
12743
+ observedAt
12744
+ };
12745
+ }));
12746
+ }
12747
+ function readActivitySummary(deps, actor, controls, params) {
12748
+ return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items }) => ({
12749
+ window: {
12750
+ from: params?.window?.from ?? null,
12751
+ to: params?.window?.to ?? null
12752
+ },
12753
+ totals: summarizeItems(items),
12754
+ count: items.length,
12755
+ observedAt: Date.now()
12756
+ })));
12757
+ }
12758
+ /**
12759
+ * Tell the caller when the indexed page for this Safe changed.
12760
+ *
12761
+ * The transport is the indexer's one SSE connection, wired in the Ponder
12762
+ * adapter under `activity.subscribe`. A reactive snapshot is not a product
12763
+ * event, so nothing here emits telemetry: the adapter reports a teardown, and
12764
+ * the caller re-reads.
12765
+ */
12766
+ function subscribeActivity(deps, actor, controls, onChange) {
12767
+ return Effect.gen(function* () {
12768
+ const scope = yield* overlay(deps, actor, controls, {});
12769
+ if (scope.safe === null) return () => {};
12770
+ const page = {
12771
+ safe: scope.safe,
12772
+ limit: INTENT_WINDOW
12773
+ };
12774
+ let delivered = false;
12775
+ const notify = () => {
12776
+ if (delivered) onChange();
12777
+ delivered = true;
12778
+ };
12779
+ return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
12780
+ if (snapshot.status === "ok") notify();
12781
+ });
12782
+ });
12783
+ }
12784
+ //#endregion
12380
12785
  //#region ../../packages/sdk/package.json
12381
- var version$1 = "4.2.0-rc.10";
12786
+ var version$1 = "4.2.0-rc.12";
12382
12787
  //#endregion
12383
12788
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
12384
12789
  /** Fixed fallback for failures that have no safe message. */
@@ -12480,10 +12885,15 @@ function observeFailedResult(result, adapter, operation, origin) {
12480
12885
  * A reactive stream is torn down after its call already returned, so nothing
12481
12886
  * downstream observes it. `captureException` is the right kind: the caller did
12482
12887
  * not ask for the end, so it is not an expected product outcome.
12888
+ *
12889
+ * A caller that DID ask for the end — the session it authenticated with
12890
+ * expired, and the service closed the stream on purpose — passes
12891
+ * `kind: "operation"` instead, so the refusal lands in the expected-outcome
12892
+ * stream its code already declares.
12483
12893
  */
12484
- function observeStreamFailure(adapter, operation, cause) {
12894
+ function observeStreamFailure(adapter, operation, cause, kind = "exception") {
12485
12895
  if (adapter === void 0) return;
12486
- report(adapter, "exception", operation, cause, resolveAdapterSnapshot(adapter));
12896
+ report(adapter, kind, operation, cause, resolveAdapterSnapshot(adapter));
12487
12897
  }
12488
12898
  /**
12489
12899
  * One failure, one event. The identity machine reports a failed transition
@@ -13475,6 +13885,11 @@ function paymentExecutionIntent(input) {
13475
13885
  }
13476
13886
  function makeFinancialOpsMethods(deps) {
13477
13887
  const fns = deps.functions ?? financialOpsContract;
13888
+ const indexerDeps = deps.indexerRead === void 0 ? void 0 : {
13889
+ indexer: deps.indexerRead,
13890
+ convexCall: deps.convexCall,
13891
+ functions: fns
13892
+ };
13478
13893
  const executionFns = deps.moneyExecutionFunctions ?? moneyExecutionContract;
13479
13894
  const requestKeyScope = deps.requestKeyScope ?? randomPaymentRequestKey();
13480
13895
  const inFlightPersonalPayments = /* @__PURE__ */ new Map();
@@ -13705,22 +14120,26 @@ function makeFinancialOpsMethods(deps) {
13705
14120
  }
13706
14121
  },
13707
14122
  activity: {
14123
+ subscribe: (onChange, params, options) => {
14124
+ const controls = deps.invocationControls?.(options) ?? options;
14125
+ 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);
14126
+ },
13708
14127
  list: (params, options) => {
13709
14128
  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: {
14129
+ 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
14130
  ...actor === void 0 ? {} : { actor },
13712
14131
  ...params?.cursor === void 0 ? {} : { cursor: params.cursor },
13713
14132
  ...params?.limit === void 0 ? {} : { limit: params.limit },
13714
14133
  ...params?.range === void 0 ? {} : { range: params.range },
13715
14134
  ...params?.filter === void 0 ? {} : { filter: params.filter }
13716
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
14135
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
13717
14136
  },
13718
14137
  summary: (params, options) => {
13719
14138
  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: {
14139
+ 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
14140
  ...actor === void 0 ? {} : { actor },
13722
14141
  ...params?.window === void 0 ? {} : { window: params.window }
13723
- } }))).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
14142
+ } })) : readActivitySummary(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.summary, deps.runPromise);
13724
14143
  },
13725
14144
  get: (reference, options) => {
13726
14145
  const controls = deps.invocationControls?.(options) ?? options;
@@ -18687,6 +19106,9 @@ const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
18687
19106
  "org/actions:resumeBootstrapSubmission",
18688
19107
  "org/actions:confirmBootstrap",
18689
19108
  "org/actions:readTreasury",
19109
+ "moneyExecution/actions:preparePermissionExecution",
19110
+ "moneyExecution/actions:submitPermissionExecution",
19111
+ "permission/actions:verify",
18690
19112
  "moneyExecution/actions:preparePaymentExecution",
18691
19113
  "moneyExecution/actions:abandonPaymentExecution",
18692
19114
  "moneyExecution/actions:submitPaymentExecution",
@@ -18711,6 +19133,9 @@ const OBSERVED_CONVEX_QUERIES = /* @__PURE__ */ new Set([
18711
19133
  "payroll/queries:groups",
18712
19134
  "payroll/queries:terms",
18713
19135
  "financialOps/queries:resolveRecipient",
19136
+ "org/queries:me",
19137
+ "permission/queries:read",
19138
+ "permission/queries:authorize",
18714
19139
  "identity/queries:loadByAuthUserId",
18715
19140
  "smartAccount/queries:loadByAuthUserId",
18716
19141
  "org/lifecycle:load"
@@ -18729,6 +19154,7 @@ const OBSERVED_CONVEX_MUTATIONS = /* @__PURE__ */ new Set([
18729
19154
  "financialOps/requestsInbox:decline",
18730
19155
  "payroll/mutations:saveGroup",
18731
19156
  "payroll/mutations:removeGroup",
19157
+ "permission/mutations:command",
18732
19158
  "identity/mutations:create",
18733
19159
  "identity/mutations:update",
18734
19160
  "identity/mutations:completeOnboarding",
@@ -44732,8 +45158,33 @@ function toOrgMe(wire) {
44732
45158
  observedAt: wire.observedAt
44733
45159
  };
44734
45160
  }
45161
+ /**
45162
+ * The read runs through the canonical observed runner, so `org.me` owns one
45163
+ * `capxul_sdk_operation` record and one span like every other public method.
45164
+ *
45165
+ * The call-start invocation snapshot travels on the query arguments: the
45166
+ * Convex adapter reads it there and sends the browser linkage with the call,
45167
+ * so an account switch mid-flight cannot re-resolve the actor. A successful
45168
+ * read proves participation in this Organization, so the owning span carries
45169
+ * the verified Organization and the two authority booleans the backend
45170
+ * actually enforces. Labels and Budget caps stay off the record.
45171
+ */
44735
45172
  function makeOrgMeMethod(deps, orgId) {
44736
- return (options) => runIfActive(options?.signal, CAPXUL_OPERATIONS.org.me, () => Effect.map(deps.convexCall.query(orgReadContract.me, { orgId }), toOrgMe));
45173
+ return (options) => {
45174
+ const controls = deps.invocationControls?.(options) ?? options;
45175
+ return runCapxulEffect(Effect.gen(function* () {
45176
+ const wire = yield* deps.convexCall.query(orgReadContract.me, copyInvocationObservation(controls, { orgId })).pipe(Effect.mapError((failure) => failure.publicError));
45177
+ yield* Effect.annotateCurrentSpan({
45178
+ verified_organization_id: orgId,
45179
+ scope_kind: "organization",
45180
+ can_manage_people: wire.capabilities.canManagePeople,
45181
+ can_spend: wire.capabilities.canSpend,
45182
+ budget_count: wire.budgets.length,
45183
+ observed_at: wire.observedAt
45184
+ }).pipe(Effect.catchCause(() => Effect.void));
45185
+ return toOrgMe(wire);
45186
+ }), controls, CAPXUL_OPERATIONS.org.me, deps.runPromise);
45187
+ };
44737
45188
  }
44738
45189
  //#endregion
44739
45190
  //#region ../../packages/sdk/src/contract/permission.ts
@@ -44753,16 +45204,24 @@ function isAborted(signal) {
44753
45204
  */
44754
45205
  function observed(deps, operation, options, run) {
44755
45206
  const controls = deps.invocationControls?.(options) ?? options;
45207
+ let verifiedOrganizationId;
44756
45208
  return runCapxulEffect(Effect.gen(function* () {
44757
45209
  if (controls?.signal?.aborted === true) return yield* Effect.fail(Errors.cancelled({ operation }));
45210
+ const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
45211
+ const captured = carryInvocationParentSpan(controls, parent);
44758
45212
  const result = yield* Effect.tryPromise({
44759
- try: () => run(controls?.signal),
45213
+ try: () => run(controls?.signal, captured, (orgId) => {
45214
+ verifiedOrganizationId = orgId;
45215
+ }),
44760
45216
  catch: (cause) => cause instanceof CapxulError ? cause : Errors.unknown(cause)
44761
45217
  });
44762
45218
  return yield* result.ok ? Effect.succeed(result.value) : Effect.fail(result.error);
44763
- }), observationOnlyControls(controls), operation, deps.runPromise);
45219
+ }).pipe(Effect.ensuring(Effect.suspend(() => verifiedOrganizationId === void 0 ? Effect.void : Effect.annotateCurrentSpan({
45220
+ verified_organization_id: verifiedOrganizationId,
45221
+ scope_kind: "organization"
45222
+ })).pipe(Effect.catchCause(() => Effect.void)))), observationOnlyControls(controls), operation, deps.runPromise);
44764
45223
  }
44765
- async function execute(deps, orgId, command, signal) {
45224
+ async function execute(deps, orgId, command, signal, controls, verifiedOrganization) {
44766
45225
  if (isAborted(signal)) return {
44767
45226
  ok: false,
44768
45227
  error: Errors.cancelled({ operation: "permissions" })
@@ -44786,17 +45245,18 @@ async function execute(deps, orgId, command, signal) {
44786
45245
  };
44787
45246
  }
44788
45247
  const executionFns = deps.executionFunctions ?? moneyExecutionContract;
44789
- const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => deps.convexCall.action(executionFns.preparePermissionExecution, { input: {
45248
+ const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.permissions.prepareExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.preparePermissionExecution, copyInvocationObservation(controls, { input: {
44790
45249
  orgId,
44791
45250
  signerAddress,
44792
45251
  command
44793
- } }));
45252
+ } }))), controls), deps.runPromise);
44794
45253
  if (!prepared.ok) return prepared;
44795
45254
  const expectedSafe = deriveCapxulSafeAddress({ email: session.email });
44796
45255
  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
45256
  ok: false,
44798
45257
  error: Errors.invalidInput("permission", "prepared authority mismatch")
44799
45258
  };
45259
+ verifiedOrganization(prepared.value.orgId);
44800
45260
  if (isAborted(signal)) return {
44801
45261
  ok: false,
44802
45262
  error: Errors.cancelled({ operation: "permissions" })
@@ -44814,38 +45274,40 @@ async function execute(deps, orgId, command, signal) {
44814
45274
  ok: false,
44815
45275
  error: Errors.cancelled({ operation: "permissions" })
44816
45276
  };
44817
- return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => deps.convexCall.action(executionFns.submitPermissionExecution, { input: {
45277
+ return runIfActive(void 0, CAPXUL_OPERATIONS.permissions.submitExecution, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.action(executionFns.submitPermissionExecution, copyInvocationObservation(controls, { input: {
44818
45278
  executionId: prepared.value.executionId,
44819
45279
  signature
44820
- } }));
45280
+ } }))), controls), deps.runPromise);
44821
45281
  }
44822
45282
  function makePermissionMethods(deps, orgId) {
44823
45283
  const reads = deps.permissionFunctions ?? permissionContract;
44824
45284
  const ops = CAPXUL_OPERATIONS.org.permissions;
45285
+ const read = (signal, operation, controls) => runIfActive(signal, operation, () => withInvocationParentSpan(Effect.suspend(() => deps.convexCall.query(reads.read, copyInvocationObservation(controls, { orgId }))), controls), deps.runPromise);
45286
+ const command = (operation, build, options) => observed(deps, operation, options, (signal, captured, verifiedOrganization) => execute(deps, orgId, build(), signal, captured, verifiedOrganization));
44825
45287
  return {
44826
- create: (input, options) => observed(deps, ops.create, options, (signal) => execute(deps, orgId, {
45288
+ create: (input, options) => command(ops.create, () => ({
44827
45289
  operation: "create",
44828
45290
  ...input
44829
- }, signal)),
44830
- change: (input, options) => observed(deps, ops.change, options, (signal) => execute(deps, orgId, {
45291
+ }), options),
45292
+ change: (input, options) => command(ops.change, () => ({
44831
45293
  operation: "change",
44832
45294
  ...input
44833
- }, signal)),
44834
- assign: (input, options) => observed(deps, ops.assign, options, (signal) => execute(deps, orgId, {
45295
+ }), options),
45296
+ assign: (input, options) => command(ops.assign, () => ({
44835
45297
  operation: "assign",
44836
45298
  ...input
44837
- }, signal)),
44838
- revoke: (input, options) => observed(deps, ops.revoke, options, (signal) => execute(deps, orgId, {
45299
+ }), options),
45300
+ revoke: (input, options) => command(ops.revoke, () => ({
44839
45301
  operation: "revoke",
44840
45302
  ...input
44841
- }, signal)),
44842
- replace: (input, options) => observed(deps, ops.replace, options, (signal) => execute(deps, orgId, {
45303
+ }), options),
45304
+ replace: (input, options) => command(ops.replace, () => ({
44843
45305
  operation: "replace",
44844
45306
  ...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 }));
45307
+ }), options),
45308
+ list: (options) => observed(deps, ops.list, options, (signal, captured) => read(signal, CAPXUL_OPERATIONS.permissions.list, captured)),
45309
+ get: (permissionId, options) => observed(deps, ops.get, options, async (signal, captured) => {
45310
+ const listed = await read(signal, CAPXUL_OPERATIONS.permissions.get, captured);
44849
45311
  if (!listed.ok) return listed;
44850
45312
  return {
44851
45313
  ok: true,
@@ -45716,7 +46178,10 @@ function makeOrgMethods(deps) {
45716
46178
  },
45717
46179
  ...convexCall === void 0 ? {} : { convexCall }
45718
46180
  }),
45719
- me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({ convexCall }, String(orgId)),
46181
+ me: convexCall === void 0 ? orgMeUnavailable : makeOrgMeMethod({
46182
+ convexCall,
46183
+ ...observedRunnerDeps(deps)
46184
+ }, String(orgId)),
45720
46185
  async getLifecycle(options) {
45721
46186
  if (options?.signal?.aborted) return {
45722
46187
  ok: false,
@@ -45953,14 +46418,65 @@ function compareDecimal(first, second) {
45953
46418
  }
45954
46419
  //#endregion
45955
46420
  //#region ../../packages/sdk/src/surface/holdings.ts
46421
+ /** SPEC Freshness: past this block age the read is labelled stale. Nothing refuses. */
46422
+ const FRESHNESS_LIMIT_SECONDS = 60;
46423
+ /**
46424
+ * The Ponder balance view for one Safe as `CurrentHoldings`.
46425
+ *
46426
+ * The view is the signed sum of every indexed transfer, so the balance IS what
46427
+ * the chain said — there is no snapshot to go stale behind it. `observedAt` is
46428
+ * the indexed block's own timestamp, which is what the last-good label reads,
46429
+ * and `stale` is that block's age. A stopped indexer therefore shows an old
46430
+ * "as of" and the stale flag; it never turns the read into a refusal.
46431
+ */
46432
+ function indexerHoldings(indexer, convexCall, overlay, actor, controls) {
46433
+ return Effect.gen(function* () {
46434
+ const scope = yield* convexCall.query(overlay, copyInvocationObservation(controls, actor?.kind === "organization" ? { input: { actor: {
46435
+ kind: "organization",
46436
+ orgId: actor.organizationId
46437
+ } } } : { input: {} }));
46438
+ if (scope.safe === null) return null;
46439
+ const balances = yield* indexer.read("balance", {
46440
+ safe: toAddress(scope.safe),
46441
+ limit: INDEXER_MAX_LIMIT
46442
+ });
46443
+ const status = yield* indexer.status.pipe(Effect.catch(() => Effect.succeed([])));
46444
+ const rows = [];
46445
+ for (const balance of balances) {
46446
+ const asset = configuredMoneyAssetById(balance.assetId);
46447
+ if (asset === null || balance.amount < 0n) continue;
46448
+ rows.push({
46449
+ assetKind: "erc20",
46450
+ tokenAddress: toAddress(asset.tokenAddress),
46451
+ symbol: asset.symbol,
46452
+ decimals: asset.decimals,
46453
+ rawBalance: toWeiAmount(balance.amount.toString())
46454
+ });
46455
+ }
46456
+ const chainId = balances[0]?.chainId ?? status[0]?.chainId;
46457
+ if (chainId === void 0) return null;
46458
+ const head = status.find((entry) => entry.chainId === chainId);
46459
+ const nowSeconds = Math.floor(Date.now() / 1e3);
46460
+ const headSeconds = head === void 0 ? void 0 : Number(head.blockTimestamp);
46461
+ return {
46462
+ actor: scope.actor,
46463
+ chainId: toChainId(chainId),
46464
+ observedAt: headSeconds === void 0 ? Date.now() : headSeconds * 1e3,
46465
+ stale: headSeconds === void 0 || nowSeconds - headSeconds > FRESHNESS_LIMIT_SECONDS,
46466
+ rows
46467
+ };
46468
+ }).pipe(Effect.catch(() => Effect.succeed(null)));
46469
+ }
45956
46470
  function makeHoldingsMethods(deps) {
45957
46471
  const functions = deps.functions ?? holdingsContract;
46472
+ const overlay = (deps.financialOpsFunctions ?? financialOpsContract).activityOverlay;
45958
46473
  const read = (operation, actor, options, pick) => {
45959
46474
  const controls = deps.invocationControls?.(options) ?? options;
45960
- return runCapxulEffect(retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
46475
+ const convexSnapshot = retryIdempotentRead(deps.convexCall.action(functions.current, copyInvocationObservation(controls, actor?.kind === "organization" ? { actor: {
45961
46476
  kind: "organization",
45962
46477
  orgId: actor.organizationId
45963
- } } : {})), operation, deps.telemetry, controls).pipe(Effect.map(pick), Effect.mapError((error) => error.publicError)), controls, operation, deps.runPromise);
46478
+ } } : {})), operation, deps.telemetry, controls).pipe(Effect.mapError((error) => error.publicError));
46479
+ 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
46480
  };
45965
46481
  return {
45966
46482
  current: (input, options) => read(CAPXUL_OPERATIONS.holdings.current, input?.actor, options, (snapshot) => snapshot),
@@ -46094,8 +46610,8 @@ const APPLIED_ACTION_BY_EVENT = {
46094
46610
  RestoreSession: "none",
46095
46611
  ResumeOtpEntry: "none",
46096
46612
  Reset: "none",
46097
- SessionRestored: "none",
46098
- SessionAbsent: "none",
46613
+ SessionRestored: "restoration",
46614
+ SessionAbsent: "restoration",
46099
46615
  EnsureAccount: "none",
46100
46616
  ClaimAccount: "none",
46101
46617
  RetryAccount: "none",
@@ -46107,8 +46623,8 @@ const APPLIED_ACTION_BY_EVENT = {
46107
46623
  OtpFailed: "failed",
46108
46624
  Verified: "verified",
46109
46625
  VerifyFailed: "failed",
46110
- SessionRead: "none",
46111
- SessionReadFailed: "none",
46626
+ SessionRead: "restoration",
46627
+ SessionReadFailed: "restoration",
46112
46628
  SignedOut: "signed-out",
46113
46629
  SignOutFailed: "failed",
46114
46630
  AccountAt: "none",
@@ -46134,6 +46650,49 @@ const authFailure = (record, reason, mode) => ({
46134
46650
  ...linkage(record)
46135
46651
  }
46136
46652
  });
46653
+ /**
46654
+ * The label of the restore window every client boots into. A settled session
46655
+ * fact that left this label is the restoration result (#1962 R04); the same
46656
+ * facts from a signed-out or authenticated state are the routine session probe
46657
+ * that `getSession()` runs, and a probe is not a restoration attempt.
46658
+ */
46659
+ const RESTORE_WINDOW = "restoring";
46660
+ /**
46661
+ * Which result the window settled on. `SessionRestored` and `SessionAbsent` are
46662
+ * the cache door and say it themselves. `ReadSession` is also valid inside the
46663
+ * window — `getSession()` drives it on a cache miss and its `SessionRead`
46664
+ * completion settles the window — so that one event reports both halves and the
46665
+ * state it produced is what separates them.
46666
+ */
46667
+ const restorationOutcome = (event, state) => {
46668
+ switch (event) {
46669
+ case "SessionRestored": return "restored";
46670
+ case "SessionAbsent": return "absent";
46671
+ case "SessionReadFailed": return "failed";
46672
+ case "SessionRead":
46673
+ if (state.phase === "authenticated") return "restored";
46674
+ return state.phase === "signed_out" ? "absent" : void 0;
46675
+ default: return;
46676
+ }
46677
+ };
46678
+ /**
46679
+ * The restore window's one result, or nothing when this settled session fact
46680
+ * belongs to a probe rather than to the window.
46681
+ */
46682
+ const restorationObservation = (record, state) => {
46683
+ if (record.slot !== "identity:session" || record.from !== RESTORE_WINDOW) return NONE;
46684
+ const outcome = restorationOutcome(record.event, state);
46685
+ if (outcome === void 0) return NONE;
46686
+ return {
46687
+ name: "access_recovery_completed",
46688
+ props: {
46689
+ recovery: "session_restoration",
46690
+ outcome,
46691
+ ...outcome === "failed" && state.phase === "faulted" ? { reason_code: state.failure.code } : {},
46692
+ ...linkage(record)
46693
+ }
46694
+ };
46695
+ };
46137
46696
  const REFUSAL_EVENTS = /* @__PURE__ */ new Set([
46138
46697
  "ClaimAccount",
46139
46698
  "RetryAccount",
@@ -46178,6 +46737,7 @@ function mapIdentityProductObservation(record, state) {
46178
46737
  name: "auth_signed_out",
46179
46738
  props: linkage(record)
46180
46739
  };
46740
+ case "restoration": return restorationObservation(record, state);
46181
46741
  }
46182
46742
  }
46183
46743
  const ignoreTransportFailure = (operation, operationName, eventName) => Effect.suspend(operation).pipe(Effect.catchCause(() => Effect.logWarning("identity.product.telemetry.dropped").pipe(Effect.annotateLogs({
@@ -46189,7 +46749,7 @@ function executeIdentityProductObservation(telemetry, record, state) {
46189
46749
  if (mapped === null) return Effect.void;
46190
46750
  copyInvocationObservation(record, mapped);
46191
46751
  const invocationTelemetry = bindProductTelemetryInvocation(telemetry, mapped);
46192
- const after = () => mapped.name === "auth_verified" && state.phase === "authenticated" ? invocationTelemetry.identify({
46752
+ const after = () => state.phase === "authenticated" && (mapped.name === "auth_verified" || mapped.name === "access_recovery_completed") ? invocationTelemetry.identify({
46193
46753
  distinctId: state.session.authUserId,
46194
46754
  traits: { email_domain: emailDomain(state.session.email) }
46195
46755
  }) : mapped.name === "auth_signed_out" ? invocationTelemetry.reset() : Effect.void;
@@ -46234,6 +46794,51 @@ function startSignerReadinessOnClaim(actor, signer) {
46234
46794
  browserSigner.ensureWalletReady?.().catch(() => {});
46235
46795
  });
46236
46796
  }
46797
+ /**
46798
+ * Report the result of each signer readiness cycle (#1962 R04).
46799
+ *
46800
+ * The readiness store already carries the cycle boundary this needs, so no
46801
+ * public signer hook, second identity subscriber, or render counter is added.
46802
+ * `ensureOpenfortWalletReady` writes `recovering` synchronously when it starts
46803
+ * a cycle, and only the cycle that is still the current one writes `ready` or
46804
+ * `unavailable` (`create-openfort-browser-signer.ts:501-521`). Callers that
46805
+ * join a live cycle share that single write, and a `resetSession` writes
46806
+ * `unknown` first, so a cycle that a reset or an account replacement outran
46807
+ * reports nothing.
46808
+ *
46809
+ * The host context is bound when the cycle starts, so a person who signs out
46810
+ * and back in while a cycle runs cannot relabel the result that began under
46811
+ * the previous person. A signer with no readiness cycle — a node or dev key,
46812
+ * and a headless client whose requirement never opens the account lane —
46813
+ * writes no status and reports nothing.
46814
+ */
46815
+ function observeSignerReadinessCycles(signer, telemetry, hostObservationSnapshot, runPromise) {
46816
+ const statusStore = signer?.statusStore;
46817
+ if (statusStore === void 0 || telemetry === void 0) return () => {};
46818
+ let running = false;
46819
+ let startContext;
46820
+ return statusStore.subscribe((status) => {
46821
+ if (status === "recovering") {
46822
+ running = true;
46823
+ startContext = hostObservationSnapshot?.();
46824
+ return;
46825
+ }
46826
+ const cycleContext = startContext;
46827
+ const completesCycle = running;
46828
+ running = false;
46829
+ startContext = void 0;
46830
+ if (!completesCycle || status !== "ready" && status !== "unavailable") return;
46831
+ const event = {
46832
+ name: "access_recovery_completed",
46833
+ props: {
46834
+ recovery: "signer_readiness",
46835
+ outcome: status
46836
+ }
46837
+ };
46838
+ if (cycleContext !== void 0) attachInvocationObservation(event, cycleContext);
46839
+ runPromise(Effect.suspend(() => bindProductTelemetryInvocation(telemetry, event).emit(event)).pipe(Effect.catchCause(() => Effect.void))).catch(() => {});
46840
+ });
46841
+ }
46237
46842
  function assembleCapxulClient(input) {
46238
46843
  const observation = input;
46239
46844
  const authCache = input.authCache ?? detectAuthCacheAdapter();
@@ -46263,12 +46868,14 @@ function assembleCapxulClient(input) {
46263
46868
  }, failureObservation, normalizeCapxulOperation(`identity.${record.event}`), "machine");
46264
46869
  });
46265
46870
  const unsubscribeSignerReadiness = startSignerReadinessOnClaim(actor, input.signer);
46871
+ const unsubscribeReadinessObservation = observeSignerReadinessCycles(input.signer, input.ports.telemetry, observation.hostObservationSnapshot, effectRunner.runPromise);
46266
46872
  let stopPromise = null;
46267
46873
  const stopActor = () => {
46268
46874
  if (stopPromise === null) {
46269
46875
  unsubscribeProductTelemetry();
46270
46876
  unsubscribeFailureObservation();
46271
46877
  unsubscribeSignerReadiness();
46878
+ unsubscribeReadinessObservation();
46272
46879
  stopPromise = effectRunner.runPromise(Scope.close(scope, Exit.void));
46273
46880
  }
46274
46881
  return stopPromise;
@@ -46285,7 +46892,8 @@ function assembleCapxulClient(input) {
46285
46892
  convexCall: input.ports.convexCall,
46286
46893
  ...input.signer === void 0 ? {} : { signer: input.signer },
46287
46894
  telemetry: input.ports.telemetry,
46288
- requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`
46895
+ requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`,
46896
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
46289
46897
  });
46290
46898
  const accountBundle = makeAccountMethods({
46291
46899
  actor,
@@ -46356,7 +46964,8 @@ function assembleCapxulClient(input) {
46356
46964
  convexCall: input.ports.convexCall,
46357
46965
  telemetry: input.ports.telemetry,
46358
46966
  runPromise: effectRunner.runPromise,
46359
- invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId)
46967
+ invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId),
46968
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
46360
46969
  });
46361
46970
  const accounts = makeAccountsMethods({
46362
46971
  accountReadPort: input.ports.accountRead,
@@ -46563,36 +47172,6 @@ function snapshotHostObservability(observability) {
46563
47172
  };
46564
47173
  }
46565
47174
  //#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
47175
  //#region ../../packages/sdk/src/adapters/ponder/decode.ts
46597
47176
  /**
46598
47177
  * Decode the indexer's rows at the trust boundary.
@@ -46898,7 +47477,7 @@ var PonderIndexerAdapter = class {
46898
47477
  }
46899
47478
  if (stopped()) return;
46900
47479
  const error = failure ?? Errors.capabilityUnavailable("indexer", operation);
46901
- observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error);
47480
+ observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error, error.code === "NOT_AUTHENTICATED" ? "operation" : "exception");
46902
47481
  callback({
46903
47482
  status: "error",
46904
47483
  error
@@ -47743,6 +48322,7 @@ function idempotentClose(close) {
47743
48322
  * and Convex see one session with one scope.
47744
48323
  */
47745
48324
  function productionIndexerReadPort(input, ports, observation) {
48325
+ if (input.activityReadSource === "convex") return void 0;
47746
48326
  if (input.indexerUrl === void 0 || input.indexerUrl === "") return void 0;
47747
48327
  return new PonderIndexerAdapter({
47748
48328
  baseUrl: normalizeHttpUrl("indexerUrl", input.indexerUrl),
@@ -48369,7 +48949,7 @@ function stdioIdentity(env = process.env, options = {}) {
48369
48949
  }
48370
48950
  //#endregion
48371
48951
  //#region package.json
48372
- var version = "4.2.0-rc.10";
48952
+ var version = "4.2.0-rc.12";
48373
48953
  //#endregion
48374
48954
  //#region src/deps.ts
48375
48955
  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.12",
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": [
@@ -50,9 +50,9 @@
50
50
  "typescript": "npm:@typescript/typescript6@6.0.2",
51
51
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
52
52
  "vite-plus": "0.3.0",
53
- "@capxul/config": "0.3.0",
54
- "@capxul/sdk": "4.2.0-rc.10",
55
- "@capxul/typescript-config": "0.0.0"
53
+ "@capxul/sdk": "4.2.0-rc.12",
54
+ "@capxul/typescript-config": "0.0.0",
55
+ "@capxul/config": "0.3.0"
56
56
  },
57
57
  "scripts": {
58
58
  "dev": "vp exec tsx src/main.ts serve",