@capxul/mcp 4.20.0-beta.24 → 4.20.0-beta.26

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.
Files changed (2) hide show
  1. package/dist/main.mjs +1890 -1768
  2. package/package.json +2 -2
package/dist/main.mjs CHANGED
@@ -206,12 +206,17 @@ const Errors = {
206
206
  * a PERMISSION denial — explicitly NOT an `INSUFFICIENT_BALANCE` (the treasury
207
207
  * held the funds; the role's authority is what bound). `reason` discriminates
208
208
  * the violated condition (`over_cap` / `daily_cap` / `not_member` /
209
- * `disallowed_recipient` / `condition_violation`); leak-safe — no on-chain
210
- * identifiers ever enter the details.
209
+ * `disallowed_recipient` / `condition_violation` / `budget_exhausted`);
210
+ * no Safe, Roles module or role key enters the details.
211
+ * `budget_exhausted` carries what the Budget's allowance still holds and
212
+ * what the spend needs, as asset amounts in major units (#2297); the asset
213
+ * id names the public token contract.
211
214
  */
212
- rolePermissionDenied: (details) => new CapxulError("ROLE_PERMISSION_DENIED", `Org role denied this spend on-chain (${details.reason}).`, { details: details.operation === void 0 ? { reason: details.reason } : {
215
+ rolePermissionDenied: (details) => new CapxulError("ROLE_PERMISSION_DENIED", `Org role denied this spend on-chain (${details.reason}).`, { details: {
213
216
  reason: details.reason,
214
- operation: details.operation
217
+ ...details.operation === void 0 ? {} : { operation: details.operation },
218
+ ...details.remaining === void 0 ? {} : { remaining: details.remaining },
219
+ ...details.required === void 0 ? {} : { required: details.required }
215
220
  } }),
216
221
  /**
217
222
  * A transaction (or sponsored UserOp) failed. `details.reason` discriminates
@@ -4257,7 +4262,7 @@ const ActivityItemSchema = Schema.Union([Schema.Struct({
4257
4262
  }), Schema.Struct({
4258
4263
  kind: Schema.Literal("movement"),
4259
4264
  id: Schema.String,
4260
- classification: Schema.Literal("movement_only_outflow"),
4265
+ classification: Schema.Literals(["deposit", "movement_only_outflow"]),
4261
4266
  amount: Amount,
4262
4267
  direction: Direction,
4263
4268
  counterpartyLabel: Schema.String,
@@ -9862,7 +9867,44 @@ const approvals = {
9862
9867
  ]
9863
9868
  })
9864
9869
  };
9870
+ /**
9871
+ * The home screen in one read. A section whose read failed is null, so the
9872
+ * caller shows it as unavailable, never as empty.
9873
+ */
9874
+ const HomeSchema = Schema.Struct({
9875
+ email: Schema.String,
9876
+ actingAs: Schema.Union([Schema.Struct({ kind: Schema.Literal("personal") }), Schema.Struct({
9877
+ kind: Schema.Literal("organization"),
9878
+ orgId: Schema.String
9879
+ })]),
9880
+ /** The acting Safe's position in each settlement asset of its chain. */
9881
+ balances: Schema.NullOr(Schema.Array(AssetPositionSchema)),
9882
+ /** The ids of what waits on the caller, newest first. */
9883
+ needsYou: Schema.NullOr(Schema.Struct({
9884
+ requestsToPay: Schema.Array(Schema.String),
9885
+ payrollToApprove: Schema.Array(Schema.String),
9886
+ paymentsToRetry: Schema.Array(Schema.String)
9887
+ })),
9888
+ /** The newest rows of the actor's activity. */
9889
+ recentActivity: Schema.NullOr(Schema.Array(ActivityItemSchema))
9890
+ });
9865
9891
  const summaries = {
9892
+ /**
9893
+ * Where the caller acts, its balances, what needs it and its newest activity,
9894
+ * for the person or for an Organization they take part in. One round trip.
9895
+ */
9896
+ home: Operation.action("accounts.summaries.home", {
9897
+ input: Schema.Struct({
9898
+ chainId: ChainIdSchema$1,
9899
+ orgId: Schema.optionalKey(Schema.NonEmptyString)
9900
+ }),
9901
+ output: HomeSchema,
9902
+ errors: [
9903
+ ...CALL$10,
9904
+ ...DEFECTS,
9905
+ "ROLE_PERMISSION_DENIED"
9906
+ ]
9907
+ }),
9866
9908
  /** The signed-in person: id, verified email and Profile display name. */
9867
9909
  me: Operation.query("accounts.summaries.me", {
9868
9910
  input: Schema.Struct({}),
@@ -11737,7 +11779,11 @@ const OrgId$2 = Schema.NonEmptyString;
11737
11779
  const CALL$4 = ["NOT_AUTHENTICATED", "INVALID_INPUT"];
11738
11780
  /** The caller's own Account and Safe, which a Payment is signed from. */
11739
11781
  const ACCOUNT$1 = ["SMART_ACCOUNT_MISSING", "ACCOUNT_NOT_FOUND"];
11740
- /** Not a participant of the Organization, or no Budget in it that may pay for this. */
11782
+ /**
11783
+ * Not a participant of the Organization, no Budget in it that may pay for
11784
+ * this, or a Budget whose on-chain allowance holds less than the batch spends
11785
+ * (`budget_exhausted`, naming what is left).
11786
+ */
11741
11787
  const DENIED$1 = ["ROLE_PERMISSION_DENIED"];
11742
11788
  /** The command, its Payment or its execution is not in a state this applies to. */
11743
11789
  const STATE$3 = ["WRONG_STATE"];
@@ -12048,7 +12094,9 @@ const OneOrganization = Schema.Struct({ orgId: OrgId$1 });
12048
12094
  const CALL$3 = ["NOT_AUTHENTICATED", "INVALID_INPUT"];
12049
12095
  /**
12050
12096
  * The caller does not take part in the Organization, or holds no Budget in it
12051
- * that may pay for this. A caller outside the Organization learns nothing more.
12097
+ * that may pay for this, or the Budget's on-chain allowance holds less than the
12098
+ * run pays (`budget_exhausted`, naming what is left). A caller outside the
12099
+ * Organization learns nothing more.
12052
12100
  */
12053
12101
  const DENIED = ["ROLE_PERMISSION_DENIED"];
12054
12102
  /**
@@ -13751,7 +13799,9 @@ emitSaved: "activity.annotations.emitSaved" },
13751
13799
  linkObservation: "payments.commands.linkObservation",
13752
13800
  failPrepared: "payments.commands.failPrepared",
13753
13801
  loadContext: "payments.commands.loadContext",
13754
- loadExecutionContext: "payments.commands.loadExecutionContext"
13802
+ loadExecutionContext: "payments.commands.loadExecutionContext",
13803
+ /** The capped Budget a prepared Organization command spends from, before its allowance is read on chain. */
13804
+ loadBudgetAllowance: "payments.commands.loadBudgetAllowance"
13755
13805
  },
13756
13806
  lifecycles: { prepareIntent: "payments.lifecycles.prepareIntent" },
13757
13807
  /** The stored UserOperation of every money command: Payment, Permission and configuration. */
@@ -15241,95 +15291,83 @@ Schema.Struct({
15241
15291
  });
15242
15292
  //#endregion
15243
15293
  //#region ../../packages/observability/src/catalog/cli.ts
15244
- /** One completed online CLI diagnostic. No command input or provider text. */
15294
+ /** Every command path of the `capxul` tree, dot-separated, plus the root and `unknown`. */
15245
15295
  const CLI_COMMAND_NAMES = [
15246
- "status",
15247
- "request",
15248
- "request.list",
15249
- "request.get",
15250
- "request.cancel",
15251
- "offer",
15252
- "offer.create",
15253
- "offer.list",
15254
- "offer.get",
15255
- "offer.revise",
15256
- "offer.deactivate",
15257
- "org.offer",
15258
- "org.offer.create",
15259
- "org.offer.list",
15260
- "org.offer.get",
15261
- "org.offer.revise",
15262
- "org.offer.deactivate",
15263
- "request.issue",
15264
- "inbox",
15265
- CAPXUL_OPERATIONS.inbox.list,
15266
- "inbox.get",
15267
- "inbox.pay",
15268
- CAPXUL_OPERATIONS.inbox.decline,
15269
- "org.request",
15270
- "org.request.list",
15271
- "org.request.get",
15272
- "org.request.cancel",
15273
- "org.request.issue",
15274
- "org.inbox",
15275
- CAPXUL_OPERATIONS.org.inbox.list,
15276
- "org.inbox.get",
15277
- "org.inbox.pay",
15278
- CAPXUL_OPERATIONS.org.inbox.decline,
15279
15296
  "capxul",
15297
+ "home",
15280
15298
  "whoami",
15281
15299
  "use",
15282
15300
  "use.personal",
15283
15301
  "use.org",
15284
15302
  "use.env",
15285
- "doctor",
15286
- "telemetry",
15287
- "telemetry.status",
15288
- "telemetry.enable",
15289
- "telemetry.disable",
15290
15303
  "auth",
15291
15304
  "auth.login",
15292
- "auth.signup",
15293
- "auth.send",
15294
15305
  "auth.verify",
15295
15306
  "auth.switch",
15296
- "auth.profile",
15297
- "auth.status",
15298
15307
  "auth.logout",
15299
15308
  "account",
15300
- CAPXUL_OPERATIONS.account.status,
15301
- CAPXUL_OPERATIONS.account.balance,
15302
- "account.deposit",
15309
+ "account.show",
15303
15310
  "account.retry",
15304
- "document",
15305
- "document.get",
15306
- "document.verify",
15307
- "document.render",
15308
- "document.export",
15311
+ "balance",
15312
+ "address",
15309
15313
  "payment",
15314
+ "payment.send",
15310
15315
  "payment.list",
15311
15316
  "payment.get",
15312
15317
  "payment.wait",
15313
- "payment.send",
15314
15318
  "payment.retry",
15315
- "activity",
15316
- "activity.list",
15317
- "activity.get",
15318
- "activity.summary",
15319
- "activity.annotate",
15319
+ "inbox",
15320
+ CAPXUL_OPERATIONS.inbox.list,
15321
+ "inbox.get",
15322
+ "inbox.pay",
15323
+ CAPXUL_OPERATIONS.inbox.decline,
15324
+ "request",
15325
+ "request.issue",
15326
+ "request.list",
15327
+ "request.get",
15328
+ "request.cancel",
15329
+ "offer",
15330
+ "offer.create",
15331
+ "offer.list",
15332
+ "offer.get",
15333
+ "offer.revise",
15334
+ "offer.deactivate",
15320
15335
  "contact",
15336
+ "contact.add",
15321
15337
  "contact.list",
15322
15338
  "contact.get",
15323
- "contact.add",
15324
15339
  "contact.label",
15325
15340
  "contact.hide",
15326
15341
  "contact.unhide",
15342
+ "document",
15343
+ "document.get",
15344
+ "document.render",
15345
+ "document.export",
15346
+ "document.verify",
15347
+ "activity",
15348
+ "activity.list",
15349
+ "activity.get",
15350
+ "activity.summary",
15351
+ "activity.annotate",
15352
+ "invite",
15353
+ "invite.list",
15354
+ "invite.accept",
15355
+ "invite.decline",
15327
15356
  "org",
15328
15357
  CAPXUL_OPERATIONS.org.list,
15329
- "org.status",
15358
+ CAPXUL_OPERATIONS.org.create,
15359
+ "org.show",
15330
15360
  CAPXUL_OPERATIONS.org.me,
15331
- "org.balance",
15332
- "org.deposit",
15361
+ "org.wait",
15362
+ CAPXUL_OPERATIONS.org.retry,
15363
+ "org.member",
15364
+ "org.member.list",
15365
+ "org.member.get",
15366
+ "org.member.invite",
15367
+ CAPXUL_OPERATIONS.org.invite,
15368
+ "org.invite.list",
15369
+ "org.invite.cancel",
15370
+ "org.invite.resend",
15333
15371
  "org.permission",
15334
15372
  "org.permission.list",
15335
15373
  "org.permission.get",
@@ -15341,46 +15379,32 @@ const CLI_COMMAND_NAMES = [
15341
15379
  "org.permission.command",
15342
15380
  "org.permission.command.get",
15343
15381
  "org.permission.command.wait",
15344
- CAPXUL_OPERATIONS.org.payment,
15345
- "org.payment.list",
15346
- "org.payment.get",
15347
- "org.payment.wait",
15348
- "org.payment.retry",
15349
- "org.payment.send",
15350
15382
  "org.payroll",
15383
+ "org.payroll.run",
15384
+ "org.payroll.list",
15385
+ "org.payroll.get",
15386
+ "org.payroll.wait",
15387
+ CAPXUL_OPERATIONS.org.payroll.terms,
15351
15388
  "org.payroll.groups",
15352
15389
  CAPXUL_OPERATIONS.org.payroll.groups.list,
15353
15390
  CAPXUL_OPERATIONS.org.payroll.groups.save,
15354
15391
  CAPXUL_OPERATIONS.org.payroll.groups.remove,
15355
- CAPXUL_OPERATIONS.org.payroll.terms,
15356
- "org.payroll.list",
15357
- "org.payroll.run",
15358
- "org.payroll.get",
15359
- "org.payroll.wait",
15360
- CAPXUL_OPERATIONS.org.members,
15361
- "org.member",
15362
- "org.member.get",
15363
- "org.member.invite",
15364
- "org.contact",
15365
- "org.contact.list",
15366
- "org.contact.get",
15367
- "org.contact.add",
15368
- "org.contact.label",
15369
- "org.contact.hide",
15370
- "org.contact.unhide",
15371
- CAPXUL_OPERATIONS.org.invite,
15372
- "org.invite.list",
15392
+ "config",
15393
+ "config.telemetry",
15394
+ "config.telemetry.on",
15395
+ "config.telemetry.off",
15396
+ "config.telemetry.status",
15397
+ "invite.get",
15398
+ "invite.review",
15399
+ "invite.wait",
15373
15400
  "org.invite.get",
15374
15401
  "org.invite.wait",
15375
- "org.invite.review",
15376
- "org.invite.accept",
15377
- "org.invite.decline",
15378
- "org.invite.cancel",
15379
- "org.invite.resend",
15380
15402
  "org.invite.retry",
15381
- "org.wait",
15382
- CAPXUL_OPERATIONS.org.create,
15383
- CAPXUL_OPERATIONS.org.retry,
15403
+ "schema",
15404
+ "doctor",
15405
+ "dev",
15406
+ "dev.key",
15407
+ "dev.fund",
15384
15408
  "unknown"
15385
15409
  ];
15386
15410
  const CLI_CAPABILITIES = [
@@ -17074,7 +17098,7 @@ function mapAccountLifecycle(input) {
17074
17098
  }
17075
17099
  //#endregion
17076
17100
  //#region ../../packages/sdk/package.json
17077
- var version$2 = "4.20.0-beta.24";
17101
+ var version$2 = "4.20.0-beta.26";
17078
17102
  //#endregion
17079
17103
  //#region ../../packages/sdk/src/observability/telemetry/exception-projection.ts
17080
17104
  /** Fixed fallback for failures that have no safe message. */
@@ -22808,63 +22832,6 @@ function makeOfframpMethods() {
22808
22832
  };
22809
22833
  }
22810
22834
  //#endregion
22811
- //#region ../../packages/sdk/src/payments/organization-payment-reads.ts
22812
- const exactPaymentId = (value) => Schema.decodeEffect(PaymentIdSchema)(value).pipe(Effect.mapError(() => Errors.invalidInput("paymentId", "Provide one exact Payment ID")));
22813
- /** Read the backend-authorized Organization wrapper before projecting any Payment. */
22814
- function makeOrganizationPaymentReadMethods(deps, orgId) {
22815
- return {
22816
- previewRetry(paymentId, options) {
22817
- const controls = deps.invocationControls?.(options) ?? options;
22818
- return runCapxulEffect(Effect.gen(function* () {
22819
- const id = yield* exactPaymentId(paymentId);
22820
- const preview = yield* callOperation(deps.convexCall, lifecycles.previewRetry, copyInvocationObservation(controls, {
22821
- orgId,
22822
- paymentId: id
22823
- }));
22824
- if (preview.orgId !== orgId || !preview.paymentIds.includes(id) || new Set(preview.paymentIds).size !== preview.paymentIds.length || preview.payments.length !== preview.paymentIds.length || preview.payments.some((payment, index) => payment.id !== preview.paymentIds[index] || payment.direction !== "out")) return yield* Effect.fail(Errors.invalidInput("paymentId", "Invalid Organization retry preview"));
22825
- return {
22826
- ...preview,
22827
- paymentIds: [...preview.paymentIds],
22828
- payments: preview.payments.map((payment) => normalizePaymentTiming(payment))
22829
- };
22830
- }), controls, lifecycles.previewRetry.name, deps.runPromise);
22831
- },
22832
- list(options) {
22833
- const controls = deps.invocationControls?.(options) ?? options;
22834
- return runCapxulEffect(callOperation(deps.convexCall, records.listForOrganization, copyInvocationObservation(controls, { orgId })).pipe(Effect.filterOrFail((result) => result.orgId === orgId, () => Errors.invalidInput("orgId", "Invalid Organization Payment list")), Effect.map((result) => mapPage(result, (payment) => normalizePaymentTiming(payment)))), controls, records.listForOrganization.name, deps.runPromise);
22835
- },
22836
- get(paymentId, options) {
22837
- const controls = deps.invocationControls?.(options) ?? options;
22838
- return runCapxulEffect(Effect.gen(function* () {
22839
- const id = yield* exactPaymentId(paymentId);
22840
- const response = yield* callOperation(deps.convexCall, records.getForOrganization, copyInvocationObservation(controls, {
22841
- orgId,
22842
- paymentId: id
22843
- }));
22844
- if (response.orgId !== orgId) return yield* Effect.fail(Errors.invalidInput("orgId", "Payment read scope does not match"));
22845
- const decoded = response.payment;
22846
- if (decoded === null) return null;
22847
- if (decoded.id !== id) return yield* Effect.fail(Errors.invalidInput("paymentId", "Invalid Organization Payment response"));
22848
- return normalizePaymentTiming(decoded);
22849
- }), controls, records.getForOrganization.name, deps.runPromise);
22850
- },
22851
- subscribe(paymentId, listener, options) {
22852
- const parsed = Schema.decodeResult(PaymentIdSchema)(paymentId);
22853
- if (parsed._tag === "Failure") return Promise.resolve({
22854
- ok: false,
22855
- error: Errors.invalidInput("paymentId", "Provide one exact Payment ID")
22856
- });
22857
- return subscribeOrganizationPayment({
22858
- actor: deps.actor,
22859
- convexCall: deps.convexCall,
22860
- getPayment: reference(records.getForOrganization),
22861
- runPromise: deps.runPromise,
22862
- invocationControls: deps.invocationControls
22863
- }, orgId, parsed.success, listener, options);
22864
- }
22865
- };
22866
- }
22867
- //#endregion
22868
22835
  //#region ../../packages/sdk/src/payments/organization-payment-requests.ts
22869
22836
  const organizationPaymentIntentSchema = Schema.Struct({
22870
22837
  orgId: PrepareOrganizationPaymentExecutionInputSchema.fields.orgId,
@@ -22925,6 +22892,63 @@ async function organizationPaymentRequest(scope, actor, intent, providedKey) {
22925
22892
  }
22926
22893
  }
22927
22894
  //#endregion
22895
+ //#region ../../packages/sdk/src/payments/organization-payment-reads.ts
22896
+ const exactPaymentId = (value) => Schema.decodeEffect(PaymentIdSchema)(value).pipe(Effect.mapError(() => Errors.invalidInput("paymentId", "Provide one exact Payment ID")));
22897
+ /** Read the backend-authorized Organization wrapper before projecting any Payment. */
22898
+ function makeOrganizationPaymentReadMethods(deps, orgId) {
22899
+ return {
22900
+ previewRetry(paymentId, options) {
22901
+ const controls = deps.invocationControls?.(options) ?? options;
22902
+ return runCapxulEffect(Effect.gen(function* () {
22903
+ const id = yield* exactPaymentId(paymentId);
22904
+ const preview = yield* callOperation(deps.convexCall, lifecycles.previewRetry, copyInvocationObservation(controls, {
22905
+ orgId,
22906
+ paymentId: id
22907
+ }));
22908
+ if (preview.orgId !== orgId || !preview.paymentIds.includes(id) || new Set(preview.paymentIds).size !== preview.paymentIds.length || preview.payments.length !== preview.paymentIds.length || preview.payments.some((payment, index) => payment.id !== preview.paymentIds[index] || payment.direction !== "out")) return yield* Effect.fail(Errors.invalidInput("paymentId", "Invalid Organization retry preview"));
22909
+ return {
22910
+ ...preview,
22911
+ paymentIds: [...preview.paymentIds],
22912
+ payments: preview.payments.map((payment) => normalizePaymentTiming(payment))
22913
+ };
22914
+ }), controls, lifecycles.previewRetry.name, deps.runPromise);
22915
+ },
22916
+ list(options) {
22917
+ const controls = deps.invocationControls?.(options) ?? options;
22918
+ return runCapxulEffect(callOperation(deps.convexCall, records.listForOrganization, copyInvocationObservation(controls, { orgId })).pipe(Effect.filterOrFail((result) => result.orgId === orgId, () => Errors.invalidInput("orgId", "Invalid Organization Payment list")), Effect.map((result) => mapPage(result, (payment) => normalizePaymentTiming(payment)))), controls, records.listForOrganization.name, deps.runPromise);
22919
+ },
22920
+ get(paymentId, options) {
22921
+ const controls = deps.invocationControls?.(options) ?? options;
22922
+ return runCapxulEffect(Effect.gen(function* () {
22923
+ const id = yield* exactPaymentId(paymentId);
22924
+ const response = yield* callOperation(deps.convexCall, records.getForOrganization, copyInvocationObservation(controls, {
22925
+ orgId,
22926
+ paymentId: id
22927
+ }));
22928
+ if (response.orgId !== orgId) return yield* Effect.fail(Errors.invalidInput("orgId", "Payment read scope does not match"));
22929
+ const decoded = response.payment;
22930
+ if (decoded === null) return null;
22931
+ if (decoded.id !== id) return yield* Effect.fail(Errors.invalidInput("paymentId", "Invalid Organization Payment response"));
22932
+ return normalizePaymentTiming(decoded);
22933
+ }), controls, records.getForOrganization.name, deps.runPromise);
22934
+ },
22935
+ subscribe(paymentId, listener, options) {
22936
+ const parsed = Schema.decodeResult(PaymentIdSchema)(paymentId);
22937
+ if (parsed._tag === "Failure") return Promise.resolve({
22938
+ ok: false,
22939
+ error: Errors.invalidInput("paymentId", "Provide one exact Payment ID")
22940
+ });
22941
+ return subscribeOrganizationPayment({
22942
+ actor: deps.actor,
22943
+ convexCall: deps.convexCall,
22944
+ getPayment: reference(records.getForOrganization),
22945
+ runPromise: deps.runPromise,
22946
+ invocationControls: deps.invocationControls
22947
+ }, orgId, parsed.success, listener, options);
22948
+ }
22949
+ };
22950
+ }
22951
+ //#endregion
22928
22952
  //#region ../../packages/sdk/src/payments/organization-flights.ts
22929
22953
  function executionDependencies$1(input) {
22930
22954
  return input.actor === void 0 || input.chainId === void 0 || input.signer === void 0 ? null : {
@@ -23835,20 +23859,86 @@ function sleep(ms, signal) {
23835
23859
  });
23836
23860
  }
23837
23861
  //#endregion
23862
+ //#region ../../packages/sdk/src/accounts/organization-approvals.ts
23863
+ /** The Organization Payment prepared for `signerAddress` and checked, before any approval. */
23864
+ async function prepareVerifiedOrganizationPayment(deps, input, signerAddress, signal) {
23865
+ const { organizationId, permissionId, requestKey, lineage, ...item } = input;
23866
+ const intent = organizationPaymentIntent(organizationId, {
23867
+ permissionId,
23868
+ items: [item],
23869
+ ...lineage === void 0 ? {} : { lineage },
23870
+ ...requestKey === void 0 ? {} : { requestKey }
23871
+ });
23872
+ if (!intent.ok) return intent;
23873
+ const session = deps.actor?.authSession() ?? null;
23874
+ if (session === null || deps.chainId === void 0) return {
23875
+ ok: false,
23876
+ error: Errors.notAuthenticated()
23877
+ };
23878
+ const expected = {
23879
+ ...intent.value,
23880
+ requestKey: requestKey ?? `pay_${crypto.randomUUID()}`
23881
+ };
23882
+ const prepared = await runIfActive(signal, CAPXUL_OPERATIONS.organizationPayments.prepareExecution, () => callOperation(deps.convexCall, commands$1.prepareOrganization, {
23883
+ ...expected,
23884
+ signerAddress
23885
+ }).pipe(Effect.mapError((publicError) => ({ publicError }))), deps.runPromise);
23886
+ if (!prepared.ok) return prepared;
23887
+ const evidence = prepared.value;
23888
+ return await matchesIntent(expected)(evidence.request, evidence) && await verifiedPreparedCommand(evidence, session.email, signerAddress, deps.chainId) ? {
23889
+ ok: true,
23890
+ value: evidence
23891
+ } : abandonPrepared(deps, evidence.executionId, "prepared_mismatch", {
23892
+ ok: false,
23893
+ error: Errors.invalidInput("execution", "prepared Organization Payment mismatch")
23894
+ });
23895
+ }
23896
+ /** One fresh Payment read: the caller's own, or one in the Organization's scope. */
23897
+ const readPayment = (deps, paymentId, organizationId, signal) => organizationId === void 0 ? runIfActive(signal, records.get.name, () => callOperation(deps.convexCall, records.get, { paymentId }, { fresh: true }).pipe(Effect.map((payment) => ({ payment })), Effect.mapError((publicError) => ({ publicError }))), deps.runPromise) : runIfActive(signal, records.getForOrganization.name, () => callOperation(deps.convexCall, records.getForOrganization, {
23898
+ orgId: organizationId,
23899
+ paymentId
23900
+ }, { fresh: true }).pipe(Effect.mapError((publicError) => ({ publicError }))), deps.runPromise);
23901
+ /**
23902
+ * The Payments an approval carries, as they stand. A personal read names only
23903
+ * the caller's own Account's Payments, so an Organization's is read with its
23904
+ * `organizationId`; one that cannot be read is refused, never taken as ended.
23905
+ */
23906
+ async function readApprovalPayments(deps, paymentIds, organizationId, signal) {
23907
+ const reads = await Promise.all(paymentIds.map((paymentId) => readPayment(deps, paymentId, organizationId, signal)));
23908
+ const payments = [];
23909
+ for (const [index, read] of reads.entries()) {
23910
+ if (!read.ok) return read;
23911
+ const { orgId, payment } = read.value;
23912
+ if (payment === null && organizationId === void 0) return {
23913
+ ok: false,
23914
+ error: Errors.invalidInput("organizationId", "this approval pays from an Organization: wait with its organizationId")
23915
+ };
23916
+ if (payment === null || payment.id !== paymentIds[index] || orgId !== organizationId) return {
23917
+ ok: false,
23918
+ error: Errors.invalidInput("paymentId", "the approval's Payments are not this scope's")
23919
+ };
23920
+ payments.push(normalizePaymentTiming(payment));
23921
+ }
23922
+ return {
23923
+ ok: true,
23924
+ value: payments
23925
+ };
23926
+ }
23927
+ //#endregion
23838
23928
  //#region ../../packages/sdk/src/accounts/approvals.ts
23839
23929
  function makeApprovalsMethods(deps) {
23840
23930
  const call = (name, row, args, signal) => runIfActive(signal, name, () => callOperation(deps.convexCall, row, args, { fresh: true }).pipe(Effect.mapError((publicError) => ({ publicError }))), deps.runPromise);
23841
23931
  const get = (approvalId) => call(approvals.get.name, approvals.get, { approvalId });
23842
- const prepare = async (input, options) => {
23843
- const signal = options?.signal;
23844
- const intent = paymentExecutionIntent(input);
23845
- if (!intent.ok) return intent;
23846
- const { actor, chainId } = deps;
23847
- if (actor === void 0 || chainId === void 0) return {
23932
+ /**
23933
+ * Prepare for the signer the backend recorded when the Safe was claimed (the
23934
+ * person's own key signs later, on the approval page) and open the approval.
23935
+ */
23936
+ const openApproval = async (signal, prepareFor) => {
23937
+ const session = deps.actor?.authSession() ?? null;
23938
+ if (deps.actor === void 0 || deps.chainId === void 0) return {
23848
23939
  ok: false,
23849
23940
  error: Errors.notImplemented("approvals", "prepareComposition")
23850
23941
  };
23851
- const session = actor.authSession();
23852
23942
  if (session === null) return {
23853
23943
  ok: false,
23854
23944
  error: Errors.notAuthenticated()
@@ -23867,18 +23957,33 @@ function makeApprovalsMethods(deps) {
23867
23957
  validStates: ["claimed"]
23868
23958
  })
23869
23959
  };
23870
- const prepared = await prepareVerifiedPersonalPayment({
23871
- actor,
23872
- chainId,
23873
- convexCall: deps.convexCall,
23874
- runPromise: deps.runPromise
23875
- }, intent.value, safe.value.signerAddress, input.requestKey ?? `pay_${crypto.randomUUID()}`, signal);
23960
+ const prepared = await prepareFor(safe.value.signerAddress);
23876
23961
  if (!prepared.ok) return prepared;
23877
- await emitPreparedPaymentInitiated(deps, prepared.value);
23878
23962
  const { executionId } = prepared.value;
23879
23963
  const opened = await call(approvals.request.name, approvals.request, { executionId });
23880
23964
  return opened.ok ? opened : abandonPrepared(deps, executionId, "cancelled", opened);
23881
23965
  };
23966
+ const prepare = async (input, options) => {
23967
+ const signal = options?.signal;
23968
+ if ("organizationId" in input) return openApproval(signal, (signerAddress) => prepareVerifiedOrganizationPayment(deps, input, signerAddress, signal));
23969
+ const intent = paymentExecutionIntent(input);
23970
+ if (!intent.ok) return intent;
23971
+ const { actor, chainId } = deps;
23972
+ return openApproval(signal, async (signerAddress) => {
23973
+ if (actor === void 0 || chainId === void 0) return {
23974
+ ok: false,
23975
+ error: Errors.notImplemented("approvals", "prepareComposition")
23976
+ };
23977
+ const prepared = await prepareVerifiedPersonalPayment({
23978
+ actor,
23979
+ chainId,
23980
+ convexCall: deps.convexCall,
23981
+ runPromise: deps.runPromise
23982
+ }, intent.value, signerAddress, input.requestKey ?? `pay_${crypto.randomUUID()}`, signal);
23983
+ if (prepared.ok) await emitPreparedPaymentInitiated(deps, prepared.value);
23984
+ return prepared;
23985
+ });
23986
+ };
23882
23987
  const approve = async (approvalId) => {
23883
23988
  const { signer } = deps;
23884
23989
  if (signer === void 0) return {
@@ -23939,28 +24044,12 @@ function makeApprovalsMethods(deps) {
23939
24044
  }
23940
24045
  };
23941
24046
  };
23942
- const readPayments = async (approval, signal) => {
23943
- const reads = await Promise.all(approval.operation.paymentIds.map((paymentId) => call(records.get.name, records.get, { paymentId }, signal)));
23944
- const payments = [];
23945
- for (const read of reads) {
23946
- if (!read.ok) return read;
23947
- if (read.value === null) return {
23948
- ok: false,
23949
- error: Errors.notImplemented("approvals", "wait.organizationPayment")
23950
- };
23951
- payments.push(normalizePaymentTiming(read.value));
23952
- }
23953
- return {
23954
- ok: true,
23955
- value: payments
23956
- };
23957
- };
23958
24047
  /**
23959
24048
  * One read of the approval: its outcome when it has ended, or null to read
23960
24049
  * again. The Payments are read before anything is sent, so a send is never
23961
24050
  * made for Payments this caller cannot follow.
23962
24051
  */
23963
- const step = async (approvalId, signal) => {
24052
+ const step = async (approvalId, signal, organizationId) => {
23964
24053
  const read = await call(approvals.get.name, approvals.get, { approvalId }, signal);
23965
24054
  if (!read.ok) return read;
23966
24055
  const approval = read.value;
@@ -23968,7 +24057,7 @@ function makeApprovalsMethods(deps) {
23968
24057
  ok: true,
23969
24058
  value: null
23970
24059
  };
23971
- const payments = await readPayments(approval, signal);
24060
+ const payments = await readApprovalPayments(deps, approval.operation.paymentIds, organizationId, signal);
23972
24061
  if (!payments.ok) return payments;
23973
24062
  if (unsent(approval, payments.value)) {
23974
24063
  const sent = await submit(approvalId);
@@ -23989,7 +24078,7 @@ function makeApprovalsMethods(deps) {
23989
24078
  const signal = options?.signal;
23990
24079
  const intervalMs = options?.intervalMs ?? 2e3;
23991
24080
  const poll = async () => {
23992
- const stepped = await step(approvalId, signal);
24081
+ const stepped = await step(approvalId, signal, options?.organizationId);
23993
24082
  if (!stepped.ok) return stepped;
23994
24083
  if (stepped.value !== null) return {
23995
24084
  ok: true,
@@ -25143,1656 +25232,1667 @@ function makeSmartAccountMethods(deps) {
25143
25232
  };
25144
25233
  }
25145
25234
  //#endregion
25146
- //#region ../../packages/sdk/src/accounts/summaries.ts
25147
- function makeMeMethods(deps) {
25148
- return {
25149
- get: (options) => runIfActive(options?.signal, summaries.me.name, () => callOperation(deps.convexCall, summaries.me, {}).pipe(Effect.mapError((publicError) => ({ publicError }))), deps.runPromise),
25150
- depositInstructions: (options) => {
25151
- const controls = deps.invocationControls?.(options) ?? options;
25152
- return runPortEffect(callOperation(deps.convexCall, summaries.depositInstructions, copyInvocationObservation(controls, {})).pipe(Effect.mapError((publicError) => ({ publicError })), Effect.tap((instructions) => emitDepositInitiated(deps.telemetry, instructions, controls))), controls, summaries.depositInstructions.name, deps.runPromise);
25153
- }
25154
- };
25235
+ //#region ../../packages/sdk/src/activity/documents.ts
25236
+ /** A reply its row does not admit is refused without the document body its decode error would carry. */
25237
+ function withoutDocumentBody(error) {
25238
+ return error.details?.step === "decodeOutput" ? Errors.invalidInput("documentHash", "invalid document read") : error;
25155
25239
  }
25156
- //#endregion
25157
- //#region ../../packages/sdk/src/access/identity/account-lane.ts
25158
- const staleAccountFailure = {
25159
- code: "CANCELLED",
25160
- message: "Account authority changed"
25161
- };
25162
- const requireAccountAuthority = (isCurrent, send, step) => Effect.suspend(() => isCurrent() ? Effect.void : send({
25163
- _tag: "AccountFailed",
25164
- step,
25165
- failure: staleAccountFailure
25166
- }).pipe(Effect.andThen(Effect.fail(staleAccountFailure))));
25167
- const accountCall = (name, make, isCurrent) => Effect.suspend(() => isCurrent() ? call$1(name, make()) : Effect.fail(staleAccountFailure));
25168
- const modelSession = (session) => ({
25169
- authUserId: String(session.authUserId),
25170
- email: String(session.email)
25171
- });
25172
- const claimAccount = (input, authUserId, signer, isCurrent) => {
25173
- const cancelled = smartAccountErrorFromCapxul("claim", Errors.cancelled({ operation: CAPXUL_OPERATIONS.smartAccount.claim }));
25174
- return call$1("smart-account.claim", Effect.tryPromise({
25175
- try: () => {
25176
- if (!isCurrent()) throw cancelled;
25177
- return signer.getAddress();
25178
- },
25179
- catch: (cause) => cause === cancelled ? cancelled : signerFailure(signer.source, "getAddress", cause)
25180
- }).pipe(Effect.flatMap((signerAddress) => Effect.suspend(() => isCurrent() ? input.ports.smartAccount.claim({
25181
- authUserId: toAuthUserId(authUserId),
25182
- chainId: toChainId(input.chainId),
25183
- signerAddress
25184
- }) : Effect.fail(cancelled)))));
25185
- };
25186
- const hasDurableClaim = (account) => account.deployedAt !== null && account.claimedAt !== null && account.signerAddress !== null;
25187
- const profileComplete = (profile) => profile?.onboarded === true;
25188
- const sessionProfile = (input, session) => {
25189
- if (session === null) return Effect.succeed({
25190
- session: null,
25191
- profileComplete: false
25192
- });
25193
- return call$1("identity.load-by-auth-user-id", input.ports.identity.loadByAuthUserId(session.authUserId)).pipe(Effect.map((profile) => ({
25194
- session: modelSession(session),
25195
- profileComplete: profileComplete(profile)
25196
- })));
25197
- };
25198
- const accountLane = (input, config, session, send, isCurrent) => Effect.gen(function* () {
25199
- let step = "wallet";
25200
- const failAt = (at, failure) => send({
25201
- _tag: "AccountFailed",
25202
- step: at,
25203
- failure
25204
- }).pipe(Effect.andThen(Effect.fail(failure)));
25205
- yield* requireAccountAuthority(isCurrent, send, step);
25206
- step = "identity";
25207
- yield* send({
25208
- _tag: "AccountAt",
25209
- step: "identity"
25210
- });
25211
- step = "provision";
25212
- yield* send({
25213
- _tag: "AccountAt",
25214
- step: "provision"
25215
- });
25216
- const existing = yield* Effect.result(accountCall("smart-account.load-by-auth-user-id", () => input.ports.smartAccount.loadByAuthUserId(toAuthUserId(session.authUserId)), isCurrent));
25217
- yield* requireAccountAuthority(isCurrent, send, step);
25218
- if (Result.isFailure(existing)) return yield* failAt("provision", existing.failure);
25219
- const provisioned = yield* Effect.result(existing.success === null ? accountCall("smart-account.provision", () => input.ports.smartAccount.provision({
25220
- authUserId: toAuthUserId(session.authUserId),
25221
- chainId: toChainId(input.chainId),
25222
- email: toEmail(session.email)
25223
- }), isCurrent) : Effect.succeed(existing.success));
25224
- yield* requireAccountAuthority(isCurrent, send, step);
25225
- if (Result.isFailure(provisioned)) return yield* failAt("provision", provisioned.failure);
25226
- const account = provisioned.success;
25227
- const address = String(account.smartAccountAddress);
25228
- yield* send({
25229
- _tag: "AccountDerived",
25230
- address
25231
- });
25232
- yield* requireAccountAuthority(isCurrent, send, step);
25233
- if (hasDurableClaim(account)) return yield* send({
25234
- _tag: "AccountClaimed",
25235
- address
25236
- });
25237
- if (config.requirement !== "deployed" || input.signer === void 0) return;
25238
- yield* requireAccountAuthority(isCurrent, send, step);
25239
- step = "deploy";
25240
- yield* send({ _tag: "AccountClaiming" });
25241
- const claimed = yield* Effect.result(claimAccount(input, session.authUserId, input.signer, isCurrent));
25242
- yield* requireAccountAuthority(isCurrent, send, step);
25243
- if (Result.isFailure(claimed)) return yield* failAt("deploy", claimed.failure);
25244
- return yield* send(hasDurableClaim(claimed.success) ? {
25245
- _tag: "AccountClaimed",
25246
- address: String(claimed.success.smartAccountAddress)
25247
- } : {
25248
- _tag: "AccountDerived",
25249
- address: String(claimed.success.smartAccountAddress)
25250
- });
25251
- });
25252
- //#endregion
25253
- //#region ../../packages/sdk/src/access/identity/spec.ts
25254
- const SETTLED_BY_TRANSITION_TAGS = /* @__PURE__ */ new Set([
25255
- "RestoreSession",
25256
- "ResumeOtpEntry",
25257
- "RestoreOtpEntry",
25258
- "Reset",
25259
- "SessionRestored",
25260
- "SessionAbsent",
25261
- "AttachOrganization",
25262
- "LeaveOrganization",
25263
- "ObserveOrganization"
25264
- ]);
25265
- function isSettledByTransition(event) {
25266
- return SETTLED_BY_TRANSITION_TAGS.has(event._tag);
25240
+ /** Schema errors must not retain document bodies. */
25241
+ function decodeDocumentRead(schema, input) {
25242
+ return Effect.fromOption(Schema.decodeUnknownOption(schema)(input)).pipe(Effect.mapError(() => ({ publicError: Errors.invalidInput("documentHash", "invalid document read") })));
25267
25243
  }
25268
- const identitySpec = (input, sessionStore, authority) => {
25269
- const config = {
25270
- ...DEFAULT_CONFIG,
25271
- requirement: input.requirement,
25272
- hasSigner: input.signer !== void 0,
25273
- ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs }
25244
+ function makePaymentDocumentsMethods(deps) {
25245
+ /**
25246
+ * One document read by its row. The pair is decoded before anything is sent,
25247
+ * and a reply the row does not admit is refused without the body it carried.
25248
+ */
25249
+ const readDocument = (row, ref, options) => {
25250
+ const controls = deps.invocationControls?.(options) ?? options;
25251
+ return runPortEffect(Effect.gen(function* () {
25252
+ const pair = yield* decodeDocumentRead(PaymentDocumentReadRefSchema, ref);
25253
+ return yield* callOperation(deps.convexCall, row, copyInvocationObservation(controls, pair)).pipe(Effect.mapError((error) => ({ publicError: withoutDocumentBody(error) })));
25254
+ }), controls, row.name, deps.runPromise);
25274
25255
  };
25275
25256
  return {
25276
- machine: "identity",
25277
- initial: RESTORING,
25278
- label,
25279
- slot: identitySlot,
25280
- transition: (state, event) => transition(state, event, config),
25281
- recoverFailure: recoverIdentityFailure,
25282
- invalidates: (event) => event._tag === "Reset" ? [
25283
- IDENTITY_SLOT.auth,
25284
- IDENTITY_SLOT.session,
25285
- IDENTITY_SLOT.account,
25286
- IDENTITY_SLOT.org
25287
- ] : event._tag === "SignOut" ? [
25288
- IDENTITY_SLOT.session,
25289
- IDENTITY_SLOT.account,
25290
- IDENTITY_SLOT.org
25291
- ] : event._tag === "LeaveOrganization" || event._tag === "AttachOrganization" ? [IDENTITY_SLOT.org] : [],
25292
- work: ({ state, event }) => {
25293
- const activeSession = state.phase === "authenticated" ? state.session : null;
25294
- const org = state.phase === "authenticated" && state.account.at === "claimed" ? state.account.org : null;
25295
- if (isSettledByTransition(event)) return void 0;
25296
- switch (event._tag) {
25297
- case "RequestOtp": return {
25298
- slot: IDENTITY_SLOT.auth,
25299
- port: "auth-client",
25300
- run: (send, controls) => call$1("auth-client.send-otp", input.ports.authClient.sendOtp(copyInvocationObservation(controls, { email: toEmail(event.email) }))).pipe(Effect.andThen(call$1("clock.now", input.ports.clock.now)), Effect.flatMap((at) => send({
25301
- _tag: "OtpSent",
25302
- at
25303
- })), Effect.tapError((failure) => send({
25304
- _tag: "OtpFailed",
25305
- failure
25306
- })))
25307
- };
25308
- case "VerifyOtp": return {
25309
- slot: IDENTITY_SLOT.auth,
25310
- port: "auth-client",
25311
- run: (send, controls) => call$1("auth-client.verify-otp", input.ports.authClient.verifyOtp(copyInvocationObservation(controls, {
25312
- email: toEmail(event.email),
25313
- otp: event.otp
25314
- }))).pipe(Effect.tap((session) => Effect.sync(() => {
25315
- authority.recordProviderSession(session);
25316
- })), Effect.flatMap((session) => sessionProfile(input, session).pipe(Effect.flatMap((loaded) => send({
25317
- _tag: "Verified",
25318
- session: loaded.session ?? modelSession(session),
25319
- profileComplete: loaded.profileComplete
25320
- })))), Effect.tapError((failure) => send({
25321
- _tag: "VerifyFailed",
25322
- failure
25323
- })))
25324
- };
25325
- case "ReadSession": return {
25326
- slot: IDENTITY_SLOT.session,
25327
- port: "auth-client",
25328
- run: (send, controls) => call$1("auth-client.get-session", input.ports.authClient.getSession(copyInvocationObservation(controls, {}))).pipe(Effect.tap((session) => Effect.sync(() => {
25329
- authority.recordProviderSession(session, activeSession?.authUserId);
25330
- })), Effect.flatMap((session) => sessionProfile(input, session)), Effect.flatMap((loaded) => send({
25331
- _tag: "SessionRead",
25332
- ...loaded
25333
- })), Effect.tapError((failure) => Effect.sync(() => {
25334
- authority.recordProviderSession(null, failure.code === "NOT_AUTHENTICATED" ? activeSession?.authUserId : void 0);
25335
- }).pipe(Effect.andThen(activeSession !== null && failure.code === "NOT_AUTHENTICATED" ? Effect.void : send({
25336
- _tag: "SessionReadFailed",
25337
- failure
25338
- })))))
25339
- };
25340
- case "SignOut": return {
25341
- slot: IDENTITY_SLOT.auth,
25342
- port: "auth-client",
25343
- run: (send, controls) => call$1("auth-client.sign-out", input.ports.authClient.signOut(copyInvocationObservation(controls, {}))).pipe(Effect.andThen(send({ _tag: "SignedOut" })), Effect.tapError((failure) => send({
25344
- _tag: "SignOutFailed",
25345
- failure
25346
- })), Effect.ensuring(Effect.sync(() => {
25347
- authority.recordProviderSession(null);
25348
- })))
25349
- };
25350
- case "EnsureAccount":
25351
- case "RetryAccount": return activeSession === null ? void 0 : {
25352
- slot: IDENTITY_SLOT.account,
25353
- port: "smart-account",
25354
- run: (send) => {
25355
- const captured = authority.snapshot();
25356
- return accountLane(input, config, activeSession, send, () => authority.matches(captured, activeSession.authUserId));
25357
- }
25358
- };
25359
- case "ClaimAccount": {
25360
- const signer = input.signer;
25361
- const address = state.phase === "authenticated" && state.account.at === "claiming" ? state.account.address : null;
25362
- return address === null || activeSession === null || signer === void 0 ? void 0 : {
25363
- slot: IDENTITY_SLOT.account,
25364
- port: "smart-account",
25365
- run: (send) => {
25366
- const captured = authority.snapshot();
25367
- const isCurrent = () => authority.matches(captured, activeSession.authUserId);
25368
- return Effect.gen(function* () {
25369
- const claimed = yield* Effect.result(claimAccount(input, activeSession.authUserId, signer, isCurrent));
25370
- yield* requireAccountAuthority(isCurrent, send, "deploy");
25371
- yield* Result.isFailure(claimed) ? send({
25372
- _tag: "AccountFailed",
25373
- step: "deploy",
25374
- failure: claimed.failure
25375
- }).pipe(Effect.andThen(Effect.fail(claimed.failure))) : send(hasDurableClaim(claimed.success) ? {
25376
- _tag: "AccountClaimed",
25377
- address
25378
- } : {
25379
- _tag: "AccountDerived",
25380
- address
25381
- });
25382
- });
25383
- }
25384
- };
25385
- }
25386
- case "CreateOrganization": return input.organizationSetup === void 0 || org === null || org.orgId !== null ? void 0 : {
25387
- slot: IDENTITY_SLOT.org,
25388
- port: "organization-setup",
25389
- run: (send, controls) => beginOrganization$1(input.organizationSetup, event.draft, send, controls)
25390
- };
25391
- default: return absurd(event);
25392
- }
25393
- }
25257
+ get: (ref, options) => readDocument(documents.get, ref, options),
25258
+ verify: (ref, options) => readDocument(documents.verify, ref, options),
25259
+ render: (ref, options) => readDocument(documents.render, ref, options)
25394
25260
  };
25395
- };
25261
+ }
25396
25262
  //#endregion
25397
- //#region ../../packages/sdk/src/access/identity/authority.ts
25398
- function createIdentityAuthorityTracker(sessionStore, onGenerationChange = () => void 0) {
25399
- const invalidationListeners = /* @__PURE__ */ new Set();
25400
- let generation = 0;
25401
- let authUserId = null;
25402
- let stopped = false;
25403
- let readState = initialIdentityState;
25404
- const advanceGeneration = () => {
25405
- generation += 1;
25406
- authUserId = null;
25407
- const listeners = [...invalidationListeners];
25408
- for (const listener of listeners) try {
25409
- listener();
25410
- } catch {}
25411
- try {
25412
- onGenerationChange();
25413
- } catch {}
25414
- };
25415
- const revoke = () => {
25416
- if (stopped) return;
25417
- advanceGeneration();
25418
- authUserId = null;
25263
+ //#region ../../packages/sdk/src/activity/stitching.ts
25264
+ /** Epoch milliseconds of the block that carried the row. */
25265
+ function blockMillis(row) {
25266
+ return Number(row.timestamp) * 1e3;
25267
+ }
25268
+ /** `0x1234…abcd` — the counterparty of an unclaimed transfer is an address. */
25269
+ function shortAddressLabel(address) {
25270
+ return address.length <= 12 ? address : `${address.slice(0, 6)}…${address.slice(-4)}`;
25271
+ }
25272
+ function directionFor(safe, transfer) {
25273
+ const from = transfer.from.toLowerCase();
25274
+ const to = transfer.to.toLowerCase();
25275
+ if (from === safe && to === safe) return "self";
25276
+ return to === safe ? "in" : "out";
25277
+ }
25278
+ function withAnnotation(item, annotation) {
25279
+ return annotation?.counterpartyLabel === void 0 ? item : {
25280
+ ...item,
25281
+ counterpartyLabel: annotation.counterpartyLabel
25419
25282
  };
25420
- return {
25421
- snapshot: () => ({
25422
- generation,
25423
- authUserId
25424
- }),
25425
- subscribeInvalidation: (listener) => {
25426
- if (stopped) {
25427
- try {
25428
- listener();
25429
- } catch {}
25430
- return () => {};
25431
- }
25432
- invalidationListeners.add(listener);
25433
- return () => {
25434
- invalidationListeners.delete(listener);
25435
- };
25436
- },
25437
- bindState: (read) => {
25438
- readState = read;
25439
- },
25440
- recordProviderSession: (session, previousAuthUserId) => {
25441
- if (previousAuthUserId != null && String(session?.authUserId ?? "") !== previousAuthUserId) revoke();
25442
- sessionStore.current = session;
25283
+ }
25284
+ /**
25285
+ * An indexed settlement confirms the intent. A missing row can be outside
25286
+ * this page or still backfilling; only the correction owner can prove rollback.
25287
+ */
25288
+ function confirmPayment(intent, chain, expected) {
25289
+ if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
25290
+ ...intent,
25291
+ status: "settled",
25292
+ ...intent.execution === void 0 ? {} : { execution: {
25293
+ status: "settled",
25294
+ retryRequired: false
25295
+ } },
25296
+ receipt: {
25297
+ chainId: chain.chainId,
25298
+ transactionHash: chain.txHash
25443
25299
  },
25444
- observe: (state) => {
25445
- if (stopped) return;
25446
- const next = state.phase === "authenticated" ? state.session.authUserId : null;
25447
- if (authUserId !== null && next !== authUserId) advanceGeneration();
25448
- authUserId = next;
25300
+ updatedAt: blockMillis(chain)
25301
+ };
25302
+ return intent;
25303
+ }
25304
+ function matchesPaymentFact(chain, expected) {
25305
+ return chain !== void 0 && expected !== void 0 && chain.chainId === expected.chainId && chain.contract.toLowerCase() === expected.contract.toLowerCase() && chain.sender.toLowerCase() === expected.sender.toLowerCase() && chain.recipient.toLowerCase() === expected.recipient.toLowerCase() && chain.token.toLowerCase() === expected.token.toLowerCase() && chain.amount.toString() === expected.rawAmount && chain.documentHash.toLowerCase() === expected.documentHash.toLowerCase() && chain.kind === expected.kind;
25306
+ }
25307
+ /**
25308
+ * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
25309
+ * denominate its asset. The indexer serves only configured assets, so a row
25310
+ * the registry does not know means the SDK and that deployment disagree; it is
25311
+ * dropped rather than shown with a guessed currency.
25312
+ */
25313
+ function isActivityVisible(item) {
25314
+ return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
25315
+ }
25316
+ function transferItem(safe, transfer, annotation) {
25317
+ const asset = configuredMoneyAssetById(transfer.assetId);
25318
+ if (asset === null) return null;
25319
+ const direction = directionFor(safe.toLowerCase(), transfer);
25320
+ const counterparty = direction === "in" ? transfer.from : transfer.to;
25321
+ const at = blockMillis(transfer);
25322
+ return withAnnotation({
25323
+ kind: "movement",
25324
+ id: transfer.id,
25325
+ classification: direction === "in" ? "deposit" : "movement_only_outflow",
25326
+ amount: {
25327
+ asset: asset.assetId,
25328
+ value: formatUnits(transfer.amount, asset.decimals)
25449
25329
  },
25450
- invalidate: () => {
25451
- if (stopped) return;
25452
- stopped = true;
25453
- advanceGeneration();
25454
- invalidationListeners.clear();
25455
- authUserId = null;
25330
+ direction,
25331
+ counterpartyLabel: shortAddressLabel(counterparty),
25332
+ receipt: {
25333
+ chainId: transfer.chainId,
25334
+ transactionHash: transfer.txHash
25456
25335
  },
25457
- matches: (captured, expectedAuthUserId) => {
25458
- if (stopped || generation !== captured.generation || authUserId !== expectedAuthUserId) return false;
25459
- const state = readState();
25460
- if (expectedAuthUserId === null) return state.phase !== "authenticated" && sessionStore.current === null;
25461
- return state.phase === "authenticated" && state.session.authUserId === expectedAuthUserId && String(sessionStore.current?.authUserId ?? "") === expectedAuthUserId;
25462
- }
25463
- };
25336
+ createdAt: at,
25337
+ updatedAt: at
25338
+ }, annotation);
25464
25339
  }
25465
- //#endregion
25466
- //#region ../../packages/sdk/src/platform/actor/transitions.ts
25467
25340
  /**
25468
- * Who hears an actor: the boot observer, transition and state subscribers, and
25469
- * the defect reporter. An observer that throws is dropped and reported as a
25470
- * defect; it never owns actor truth.
25341
+ * Money in, money out and the pending share, one bucket per exact asset.
25471
25342
  */
25472
- const makeActorObservers = (options) => {
25473
- const stateObservers = /* @__PURE__ */ new Set();
25474
- const transitionObservers = /* @__PURE__ */ new Set();
25475
- let bootTransitionObserver = options.onTransition;
25476
- const defect = (cause) => {
25477
- try {
25478
- options.onDefect?.(cause);
25479
- } catch {}
25480
- };
25481
- const emit = (record) => Effect.sync(() => {
25482
- if (bootTransitionObserver !== void 0) try {
25483
- bootTransitionObserver(record);
25484
- } catch (cause) {
25485
- bootTransitionObserver = void 0;
25486
- defect(cause);
25487
- }
25488
- for (const observer of transitionObservers) try {
25489
- observer(record);
25490
- } catch (cause) {
25491
- transitionObservers.delete(observer);
25492
- defect(cause);
25493
- }
25494
- }).pipe(Effect.andThen(logIdentityTransition(record)));
25495
- const notify = (state) => {
25496
- for (const observer of stateObservers) try {
25497
- observer(state);
25498
- } catch (cause) {
25499
- stateObservers.delete(observer);
25500
- defect(cause);
25501
- }
25502
- };
25503
- return {
25504
- stateObservers,
25505
- transitionObservers,
25506
- defect,
25507
- emit,
25508
- notify
25509
- };
25510
- };
25511
- /** One machine's transition records and the failure a refused or failed reply carries. */
25512
- const makeTransitionRecords = (spec, slotEpochs) => {
25513
- const makeFailure = (state, event, reason, details, cause) => new ActorFailure(reason, spec.machine, spec.label(state), event._tag, details, cause);
25514
- const nonApplied = (state, event, reason, outcome, env, failure) => {
25515
- const slot = env.origin === null ? spec.slot(env.event) : env.origin.slot;
25516
- const base = {
25517
- machine: spec.machine,
25518
- state: spec.label(state),
25519
- event: event._tag,
25520
- slot,
25521
- epoch: env.origin?.epoch ?? slotEpochs.get(slot) ?? 0,
25522
- duration_ms: duration(env.startedAt),
25523
- ...withCarriage(env.invocation)
25524
- };
25525
- const record = outcome === "refused" ? {
25526
- ...base,
25527
- outcome,
25528
- refusal_code: reason
25529
- } : {
25530
- ...base,
25531
- outcome,
25532
- error_code: reason,
25533
- ...failure === void 0 ? {} : { failure }
25343
+ function summarizeItems(items) {
25344
+ const buckets = /* @__PURE__ */ new Map();
25345
+ for (const item of items) {
25346
+ const metadata = configuredMoneyAssetById(item.amount.asset);
25347
+ if (metadata === null) continue;
25348
+ const key = item.amount.asset;
25349
+ const bucket = buckets.get(key) ?? {
25350
+ asset: item.amount.asset,
25351
+ in: 0n,
25352
+ out: 0n,
25353
+ pendingIn: 0n
25534
25354
  };
25535
- return copyInvocationObservation(env.invocation, record);
25536
- };
25537
- const applied = (from, to, event, slot, epoch, env) => copyInvocationObservation(env.invocation, {
25538
- machine: spec.machine,
25539
- from: spec.label(from),
25540
- event: event._tag,
25541
- to: spec.label(to),
25542
- slot,
25543
- epoch,
25544
- outcome: "applied",
25545
- duration_ms: duration(env.startedAt),
25546
- ...withCarriage(env.invocation)
25547
- });
25548
- return {
25549
- makeFailure,
25550
- nonApplied,
25551
- applied
25552
- };
25553
- };
25355
+ const raw = rawUnits(item.amount.value, metadata.decimals);
25356
+ if (item.kind === "payment" && item.status !== "settled") {
25357
+ if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
25358
+ } else if (item.direction === "in") bucket.in += raw;
25359
+ else if (item.direction === "out") bucket.out += raw;
25360
+ buckets.set(key, bucket);
25361
+ }
25362
+ return [...buckets.values()].map((bucket) => ({
25363
+ asset: bucket.asset,
25364
+ in: bucket.in.toString(),
25365
+ out: bucket.out.toString(),
25366
+ net: (bucket.in - bucket.out).toString(),
25367
+ pendingIn: bucket.pendingIn.toString()
25368
+ }));
25369
+ }
25370
+ /** The inverse of `formatUnits`, kept exact: `"1.25"` at 6 decimals is `1250000n`. */
25371
+ function rawUnits(value, decimals) {
25372
+ const negative = value.startsWith("-");
25373
+ const [whole = "0", fraction = ""] = (negative ? value.slice(1) : value).split(".");
25374
+ const raw = BigInt(`${whole}${fraction.padEnd(decimals, "0").slice(0, decimals)}`);
25375
+ return negative ? -raw : raw;
25376
+ }
25554
25377
  //#endregion
25555
- //#region ../../packages/sdk/src/platform/actor/lane-exit.ts
25556
- /**
25557
- * How a work lane's exit settles its reply: finished, failed, cancelled, or
25558
- * died. A timeout, cancellation or death first lets the machine recover its
25559
- * own state through `spec.recoverFailure`.
25560
- */
25561
- const makeLaneSettlement = (actor) => {
25562
- const { spec, cell, slots } = actor;
25563
- const { emit, notify, defect } = actor.observers;
25564
- const { makeFailure, nonApplied, applied } = actor.records;
25565
- const finish = (cursor, state) => {
25566
- const held = slots.get(cursor.slot);
25567
- if (held === void 0 || held.cursor !== cursor) return Effect.void;
25568
- slots.delete(cursor.slot);
25569
- return Effect.asVoid(Deferred.succeed(held.reply, state));
25570
- };
25571
- const recoverFailure = (state, cursor, failure) => {
25572
- const event = spec.recoverFailure?.(state, cursor.event, failure);
25573
- if (event === void 0) return Effect.succeed(state);
25574
- const outcome = spec.transition(state, event);
25575
- if ("refused" in outcome) return Effect.succeed(state);
25576
- return Ref.set(cell, outcome.next).pipe(Effect.tap(() => {
25577
- const record = applied(state, outcome.next, event, cursor.slot, cursor.epoch, {
25578
- invocation: cursor.invocation,
25579
- startedAt: cursor.startedAt
25580
- });
25581
- return emit(record).pipe(Effect.andThen(Effect.sync(() => {
25582
- notify(outcome.next);
25583
- })));
25584
- }), Effect.as(outcome.next));
25378
+ //#region ../../packages/sdk/src/activity/cursor.ts
25379
+ function actorKey(actor) {
25380
+ return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
25381
+ }
25382
+ function scopeKey(scope) {
25383
+ return `${scope.principalAccountId}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
25384
+ }
25385
+ function selectionKey(params) {
25386
+ const filter = params?.filter;
25387
+ return JSON.stringify([
25388
+ params?.range?.from ?? null,
25389
+ params?.range?.to ?? null,
25390
+ filter?.kind ?? null,
25391
+ filter?.direction ?? null,
25392
+ filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
25393
+ ]);
25394
+ }
25395
+ function validString(value, max) {
25396
+ return typeof value === "string" && value.length <= max;
25397
+ }
25398
+ function validPosition(position) {
25399
+ return position === null || typeof position === "object" && "timestamp" in position && typeof position.timestamp === "number" && Number.isSafeInteger(position.timestamp) && position.timestamp >= 0 && "id" in position && typeof position.id === "string" && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(position.id);
25400
+ }
25401
+ function isActivityCursor(cursor, scope, selection) {
25402
+ return typeof cursor === "object" && cursor !== null && cursorScopeMatches(cursor, scope, selection) && cursorProgressIsValid(cursor);
25403
+ }
25404
+ /** The cursor was issued for this scope and filter selection, at a valid checkpoint. */
25405
+ function cursorScopeMatches(cursor, scope, selection) {
25406
+ return "version" in cursor && cursor.version === 1 && "scope" in cursor && cursor.scope === scope && "selection" in cursor && cursor.selection === selection && "checkpoint" in cursor && typeof cursor.checkpoint === "number" && Number.isSafeInteger(cursor.checkpoint) && cursor.checkpoint >= 0 && "source" in cursor && (cursor.source === "indexed" || cursor.source === "fallback");
25407
+ }
25408
+ /** Each source's progress is well-formed and bounded. */
25409
+ function cursorProgressIsValid(cursor) {
25410
+ return "paymentCursor" in cursor && (cursor.paymentCursor === null || validString(cursor.paymentCursor, 2048)) && "paymentDone" in cursor && typeof cursor.paymentDone === "boolean" && "transferDone" in cursor && typeof cursor.transferDone === "boolean" && "degraded" in cursor && typeof cursor.degraded === "boolean" && "paymentPending" in cursor && Array.isArray(cursor.paymentPending) && cursor.paymentPending.length <= 100 && new Set(cursor.paymentPending).size === cursor.paymentPending.length && cursor.paymentPending.every((id) => validString(id, 256)) && "transferBefore" in cursor && validPosition(cursor.transferBefore);
25411
+ }
25412
+ function parseCursor(value, scope, selection) {
25413
+ if (value === void 0) return null;
25414
+ if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
25415
+ const cursor = JSON.parse(value);
25416
+ if (!isActivityCursor(cursor, scope, selection)) throw Errors.invalidInput("cursor", "Activity scope or filters changed; restart from the first page");
25417
+ return cursor;
25418
+ }
25419
+ function requireParams(params) {
25420
+ const limit = params?.limit ?? 25;
25421
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
25422
+ for (const value of [params?.range?.from, params?.range?.to]) if (value !== void 0 && !Number.isSafeInteger(value)) throw Errors.invalidInput("range", "must contain integer epoch milliseconds");
25423
+ const f = params?.filter;
25424
+ if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
25425
+ "in",
25426
+ "out",
25427
+ "self"
25428
+ ].includes(f.direction) || f?.status !== void 0 && (!Array.isArray(f.status) || !f.status.every((status) => PAYMENT_STATUSES.includes(status)))) throw Errors.invalidInput("filter", "invalid Activity filter");
25429
+ }
25430
+ function matchesFilter(item, filter) {
25431
+ return (filter?.kind === void 0 || item.kind === filter.kind) && (filter?.direction === void 0 || item.direction === filter.direction) && (filter?.status === void 0 || item.kind === "payment" && filter.status.includes(item.status));
25432
+ }
25433
+ //#endregion
25434
+ //#region ../../packages/sdk/src/activity/indexed-pages.ts
25435
+ function chainRead(deps, table, input) {
25436
+ return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
25437
+ if (error.publicError.details?.reason === "scope-refused" || ![
25438
+ "NETWORK_ERROR",
25439
+ "RATE_LIMITED",
25440
+ "PROVIDER_ERROR",
25441
+ "CAPABILITY_UNAVAILABLE",
25442
+ "TIMEOUT"
25443
+ ].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
25444
+ return Effect.succeed(null);
25445
+ }));
25446
+ }
25447
+ function boundedRange(range, checkpoint) {
25448
+ return {
25449
+ ...range?.from === void 0 ? {} : { from: range.from },
25450
+ to: Math.min(checkpoint, range?.to ?? checkpoint)
25585
25451
  };
25586
- return (env, origin, exit, state) => Effect.gen(function* () {
25587
- const held = slots.get(origin.slot);
25588
- if (held === void 0 || held.cursor !== origin) return;
25589
- if (exit.defect !== void 0) {
25590
- slots.delete(origin.slot);
25591
- defect(exit.defect);
25592
- const event = { _tag: origin.event };
25593
- const died = {
25594
- code: "WORK_DIED",
25595
- message: exit.defect instanceof Error ? exit.defect.message : "Identity work died"
25596
- };
25597
- const failedState = yield* recoverFailure(state, origin, died);
25598
- yield* emit(nonApplied(failedState, event, "WORK_DIED", "failed", env, died));
25599
- yield* failReply(held.reply, makeFailure(failedState, event, "WORK_DIED", void 0, died));
25600
- return;
25601
- }
25602
- if (exit.failure !== void 0) {
25603
- slots.delete(origin.slot);
25604
- const outcome = exit.failure.code === "CANCELLED" ? "cancelled" : "failed";
25605
- const event = { _tag: origin.event };
25606
- const failedState = exit.failure === timeoutFailure || exit.failure === cancelledFailure ? yield* recoverFailure(state, origin, exit.failure) : state;
25607
- yield* emit(nonApplied(failedState, event, exit.failure.code, outcome, env, exit.failure));
25608
- const details = exit.failure === timeoutFailure ? { reason: "timeout" } : refusedInputField(exit.failure);
25609
- yield* failReply(held.reply, makeFailure(failedState, event, exit.failure.code, details, exit.failure.error ?? exit.failure));
25610
- return;
25452
+ }
25453
+ function unavailablePage(deps, scope, controls, params, state, first) {
25454
+ if (first) return fallbackPage(deps, scope, controls, params, state, true);
25455
+ return Effect.annotateCurrentSpan({
25456
+ read_source: "indexer",
25457
+ read_warning: "indexer-unavailable"
25458
+ }).pipe(Effect.as({
25459
+ items: [],
25460
+ nextCursor: null,
25461
+ checkpoint: state.checkpoint,
25462
+ observedAt: Date.now(),
25463
+ readWarning: "indexer-unavailable"
25464
+ }));
25465
+ }
25466
+ function readTransfers(deps, safe, range, state, wanted, first) {
25467
+ const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
25468
+ const to = Math.floor(range.to / 1e3);
25469
+ if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
25470
+ return chainRead(deps, "transfer", {
25471
+ safe: toAddress(safe),
25472
+ limit: wanted ? 100 : 1,
25473
+ timeline: {
25474
+ from,
25475
+ to,
25476
+ ...state.transferBefore === null ? {} : { before: state.transferBefore }
25611
25477
  }
25612
- yield* finish(origin, state);
25613
25478
  });
25614
- };
25615
- //#endregion
25616
- //#region ../../packages/sdk/src/platform/actor/shell.ts
25617
- const boot = (spec, options = {}) => Effect.gen(function* () {
25618
- const mailbox = yield* Queue.unbounded();
25619
- const cell = yield* Ref.make(spec.initial);
25620
- const slotEpochs = /* @__PURE__ */ new Map();
25621
- const slots = /* @__PURE__ */ new Map();
25622
- const { stateObservers, transitionObservers, defect, emit, notify } = makeActorObservers(options);
25623
- const { makeFailure, nonApplied, applied } = makeTransitionRecords(spec, slotEpochs);
25624
- let stopped = false;
25625
- const settleLaneExit = makeLaneSettlement({
25626
- spec,
25627
- cell,
25628
- slots,
25629
- observers: {
25630
- emit,
25631
- notify,
25632
- defect
25633
- },
25634
- records: {
25635
- makeFailure,
25636
- nonApplied,
25637
- applied
25638
- }
25479
+ }
25480
+ function readPayments(deps, scope, controls, range, state, wanted) {
25481
+ const done = state.paymentDone || !wanted;
25482
+ if (state.paymentPending.length > 0 || done) return Effect.succeed({
25483
+ items: [],
25484
+ pending: state.paymentPending,
25485
+ cursor: state.paymentCursor,
25486
+ done
25639
25487
  });
25640
- const step = (env) => Effect.gen(function* () {
25641
- const state = yield* Ref.get(cell);
25642
- if (env.origin !== null && (slotEpochs.get(env.origin.slot) ?? 0) !== env.origin.epoch) {
25643
- const held = slots.get(env.origin.slot);
25644
- if (held === void 0 || held.cursor !== env.origin) return;
25645
- slots.delete(env.origin.slot);
25646
- const event = { _tag: env.origin.event };
25647
- yield* emit(nonApplied(state, event, "STALE_EPOCH", "refused", env));
25648
- yield* failReply(held.reply, makeFailure(state, event, "STALE_EPOCH"));
25649
- return;
25650
- }
25651
- if (isLaneExitEnvelope(env)) {
25652
- yield* settleLaneExit(env, env.origin, env.event, state);
25653
- return;
25654
- }
25655
- const outcome = spec.transition(state, env.event);
25656
- if ("refused" in outcome) {
25657
- yield* emit(nonApplied(state, env.event, outcome.refused, "refused", env));
25658
- yield* failReply(env.reply, makeFailure(state, env.event, outcome.refused));
25659
- return;
25660
- }
25661
- const next = outcome.next;
25662
- yield* Ref.set(cell, next);
25663
- const emitApplied = (slot, epoch) => emit(applied(state, next, env.event, slot, epoch, env));
25664
- if (env.origin !== null) {
25665
- yield* emitApplied(env.origin.slot, env.origin.epoch);
25666
- notify(next);
25667
- return;
25668
- }
25669
- for (const slot of spec.invalidates?.(env.event) ?? []) {
25670
- if (!slots.has(slot)) continue;
25671
- slotEpochs.set(slot, (slotEpochs.get(slot) ?? 0) + 1);
25672
- }
25673
- const work = env.reply === null ? void 0 : spec.work?.({
25674
- state: next,
25675
- event: env.event
25676
- });
25677
- if (work === void 0 || env.reply === null) {
25678
- const slot = spec.slot(env.event);
25679
- yield* emitApplied(slot, slotEpochs.get(slot) ?? 0);
25680
- notify(next);
25681
- if (env.reply !== null) yield* Deferred.succeed(env.reply, next).pipe(Effect.asVoid);
25682
- return;
25683
- }
25684
- const previous = slots.get(work.slot);
25685
- if (previous !== void 0) {
25686
- slots.delete(work.slot);
25687
- yield* Fiber.interrupt(previous.fiber);
25688
- const previousEvent = { _tag: previous.cursor.event };
25689
- const reason = previous.cursor.epoch === (slotEpochs.get(work.slot) ?? 0) ? "SUPERSEDED" : "STALE_EPOCH";
25690
- yield* emit(nonApplied(next, previousEvent, reason, "refused", {
25691
- origin: previous.cursor,
25692
- invocation: previous.cursor.invocation,
25693
- startedAt: previous.cursor.startedAt
25694
- }));
25695
- yield* failReply(previous.reply, makeFailure(next, previousEvent, reason));
25696
- }
25697
- const epoch = (slotEpochs.get(work.slot) ?? 0) + 1;
25698
- slotEpochs.set(work.slot, epoch);
25699
- const cursor = {
25700
- slot: work.slot,
25701
- epoch,
25702
- event: env.event._tag,
25703
- startedAt: env.startedAt,
25704
- invocation: env.invocation
25488
+ return convexPage(deps, scope.actor, controls, {
25489
+ limit: 100,
25490
+ range,
25491
+ filter: { kind: "payment" },
25492
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
25493
+ }).pipe(Effect.map((page) => ({
25494
+ items: page.items,
25495
+ pending: page.items.map((item) => item.id),
25496
+ cursor: page.nextCursor,
25497
+ done: page.nextCursor === null
25498
+ })));
25499
+ }
25500
+ function readPaymentFacts(deps, safe, settlements) {
25501
+ const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
25502
+ return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
25503
+ safe: toAddress(safe),
25504
+ limit: 50,
25505
+ settlements: settlements.slice(offset, offset + 50)
25506
+ }), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
25507
+ }
25508
+ function indexedPage(deps, scope, controls, params, state, first) {
25509
+ return Effect.gen(function* () {
25510
+ const range = boundedRange(params?.range, state.checkpoint);
25511
+ const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
25512
+ const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
25513
+ if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
25514
+ const transfers = wantsTransfers ? found : [];
25515
+ const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
25516
+ const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
25517
+ if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
25518
+ const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
25519
+ const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
25520
+ if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
25521
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
25522
+ return {
25523
+ items,
25524
+ nextCursor: cursor,
25525
+ checkpoint: state.checkpoint,
25526
+ observedAt: Date.now()
25705
25527
  };
25706
- yield* emitApplied(work.slot, epoch);
25707
- notify(next);
25708
- const send = (event) => Queue.offer(mailbox, {
25709
- event,
25710
- reply: null,
25711
- origin: cursor,
25712
- invocation: env.invocation,
25713
- startedAt: env.startedAt
25714
- }).pipe(Effect.asVoid);
25715
- const complete = (event) => Queue.offer(mailbox, {
25716
- event,
25717
- reply: null,
25718
- origin: cursor,
25719
- invocation: env.invocation,
25720
- startedAt: env.startedAt
25721
- }).pipe(Effect.asVoid);
25722
- const guarded = Effect.gen(function* () {
25723
- yield* Effect.annotateCurrentSpan({
25724
- slot: work.slot,
25725
- epoch,
25726
- port: work.port
25727
- });
25728
- return yield* control(work.run(send, env.invocation), env.invocation);
25729
- }).pipe(Effect.withSpan(`identity.work.${work.slot}`)).pipe(Effect.matchEffect({
25730
- onFailure: (failure) => complete(laneExit({ failure })),
25731
- onSuccess: () => complete(laneExit({}))
25732
- }), Effect.catchDefect((cause) => complete(laneExit({ defect: cause }))));
25733
- const fiber = yield* Effect.forkChild(guarded, { startImmediately: true });
25734
- slots.set(work.slot, {
25735
- fiber,
25736
- reply: env.reply,
25737
- cursor
25528
+ });
25529
+ }
25530
+ //#endregion
25531
+ //#region ../../packages/sdk/src/activity/page-overlay.ts
25532
+ const MAX_OVERLAY_IDS = 200;
25533
+ function overlay(deps, actor, controls, input) {
25534
+ const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
25535
+ const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
25536
+ return Effect.forEach(offsets, (offset) => callOperation(deps.convexCall, overlays.get, copyInvocationObservation(controls, {
25537
+ ...actor === void 0 ? {} : { actor },
25538
+ ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
25539
+ ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
25540
+ })).pipe(Effect.mapError((publicError) => ({ publicError }))), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
25541
+ const first = pages[0];
25542
+ if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
25543
+ return Effect.succeed({
25544
+ ...first,
25545
+ links: pages.flatMap((page) => page.links),
25546
+ annotations: pages.flatMap((page) => page.annotations),
25547
+ payments: pages.flatMap((page) => page.payments)
25548
+ });
25549
+ }));
25550
+ }
25551
+ function decoratePage(deps, scope, controls, transfers, page, range) {
25552
+ return Effect.gen(function* () {
25553
+ const decoration = yield* overlay(deps, scope.actor, controls, {
25554
+ references: [...page.pending.map((id) => ({
25555
+ kind: "payment",
25556
+ id
25557
+ })), ...transfers.map((row) => ({
25558
+ kind: "movement",
25559
+ id: row.id
25560
+ }))],
25561
+ settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
25738
25562
  });
25563
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
25564
+ const byId = new Map([...page.items, ...decoration.payments].map((item) => [item.id, item]));
25565
+ const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
25566
+ const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
25567
+ if (facts === null) return null;
25568
+ const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
25569
+ const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
25570
+ fact: bySettlement.get(link.settlementId),
25571
+ expected: link.expected
25572
+ }]));
25573
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
25574
+ return {
25575
+ payments: page.pending.flatMap((id) => {
25576
+ const item = byId.get(id);
25577
+ if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
25578
+ const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
25579
+ return isActivityVisible(confirmed) ? [confirmed] : [];
25580
+ }),
25581
+ annotations,
25582
+ linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
25583
+ };
25739
25584
  });
25740
- const loop = yield* Effect.forkScoped(Effect.forever(Queue.take(mailbox).pipe(Effect.flatMap((env) => withInvocationParentSpan(step(env), env.invocation)))));
25741
- yield* Effect.addFinalizer(() => Effect.gen(function* () {
25742
- stopped = true;
25743
- yield* Fiber.interrupt(loop);
25744
- const state = yield* Ref.get(cell);
25745
- const outstanding = [...slots.values()];
25746
- slots.clear();
25747
- for (const held of outstanding) {
25748
- yield* Fiber.interrupt(held.fiber);
25749
- const env = {
25750
- origin: held.cursor,
25751
- invocation: held.cursor.invocation,
25752
- startedAt: held.cursor.startedAt
25585
+ }
25586
+ function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
25587
+ const { payments, annotations, linked } = decoration;
25588
+ let pi = 0;
25589
+ let ti = 0;
25590
+ const items = [];
25591
+ const transferEnd = !wantsTransfers || transfers.length < 100;
25592
+ let transferBefore = state.transferBefore;
25593
+ while (items.length < limit) {
25594
+ if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
25595
+ const payment = payments[pi];
25596
+ const row = transfers[ti];
25597
+ if (payment === void 0 && row === void 0) break;
25598
+ if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
25599
+ pi += 1;
25600
+ if (matchesFilter(payment, filter)) items.push(payment);
25601
+ } else if (row !== void 0) {
25602
+ ti += 1;
25603
+ transferBefore = {
25604
+ timestamp: Number(row.timestamp),
25605
+ id: row.id
25753
25606
  };
25754
- const event = { _tag: held.cursor.event };
25755
- yield* emit(nonApplied(state, event, "ACTOR_STOPPED", "refused", env));
25756
- yield* failReply(held.reply, makeFailure(state, event, "ACTOR_STOPPED"));
25607
+ if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
25608
+ const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
25609
+ if (item !== null && matchesFilter(item, filter)) items.push(item);
25757
25610
  }
25758
- stateObservers.clear();
25759
- transitionObservers.clear();
25760
- yield* Queue.shutdown(mailbox);
25761
- }));
25762
- const offer = (event, reply, invocation) => {
25763
- const state = Ref.getUnsafe(cell);
25764
- if (stopped) return withInvocationParentSpan(emit(nonApplied(state, event, "ACTOR_STOPPED", "refused", {
25765
- origin: null,
25766
- event,
25767
- invocation,
25768
- startedAt: Date.now()
25769
- })).pipe(Effect.andThen(Effect.fail(makeFailure(state, event, "ACTOR_STOPPED")))), invocation);
25770
- const startedAt = Date.now();
25771
- return Queue.offer(mailbox, {
25772
- event,
25773
- reply,
25774
- origin: null,
25775
- invocation,
25776
- startedAt
25777
- }).pipe(Effect.asVoid);
25778
- };
25611
+ }
25779
25612
  return {
25780
- ask: (event, invocation = {}) => Effect.gen(function* () {
25781
- const reply = yield* Deferred.make();
25782
- yield* offer(event, reply, invocation);
25783
- return yield* Deferred.await(reply);
25784
- }),
25785
- tell: (event, invocation = {}) => offer(event, null, invocation),
25786
- snapshot: () => Ref.getUnsafe(cell),
25787
- subscribe: (observer) => {
25788
- stateObservers.add(observer);
25789
- return () => {
25790
- stateObservers.delete(observer);
25791
- };
25792
- },
25793
- subscribeTransitions: (observer) => {
25794
- transitionObservers.add(observer);
25795
- return () => {
25796
- transitionObservers.delete(observer);
25797
- };
25613
+ items,
25614
+ next: {
25615
+ ...state,
25616
+ paymentCursor: page.cursor,
25617
+ paymentDone: page.done,
25618
+ paymentPending: payments.slice(pi).map((item) => item.id),
25619
+ transferBefore,
25620
+ transferDone: ti === transfers.length && transferEnd
25798
25621
  }
25799
25622
  };
25800
- });
25801
- //#endregion
25802
- //#region ../../packages/sdk/src/access/identity/boot.ts
25803
- const initialIdentityState = () => RESTORING;
25804
- const bootIdentityFlow = (input, options = {}) => Effect.gen(function* () {
25805
- const sessionStore = { current: null };
25806
- let invalidateOrganizationSubscriptions = () => input.organizationSetup?.invalidateSubscriptions?.();
25807
- const authority = createIdentityAuthorityTracker(sessionStore, () => invalidateOrganizationSubscriptions());
25808
- const actor = yield* boot(identitySpec(input, sessionStore, authority), options);
25809
- authority.bindState(actor.snapshot);
25810
- const runPromise = input.runPromise ?? Effect.runPromise;
25811
- let selectedOrgId = null;
25812
- let stopRemote = null;
25813
- let selectionEpoch = 0;
25814
- invalidateOrganizationSubscriptions = () => {
25815
- selectionEpoch += 1;
25816
- const stop = stopRemote;
25817
- stopRemote = null;
25818
- try {
25819
- stop?.();
25820
- } finally {
25821
- input.organizationSetup?.invalidateSubscriptions?.();
25822
- }
25823
- };
25824
- const observe = (state) => {
25825
- authority.observe(state);
25826
- const nextOrgId = state.phase === "authenticated" && state.account.at === "claimed" ? state.account.org?.orgId ?? null : null;
25827
- if (nextOrgId === selectedOrgId) return;
25828
- selectedOrgId = nextOrgId;
25829
- selectionEpoch += 1;
25830
- const epoch = selectionEpoch;
25831
- stopRemote?.();
25832
- stopRemote = null;
25833
- if (nextOrgId === null || input.organizationSetup === void 0) return;
25834
- const capturedLifetime = authority.snapshot();
25835
- const expectedAuthUserId = state.phase === "authenticated" ? state.session.authUserId : null;
25836
- const emit = (snapshot) => {
25837
- if (epoch !== selectionEpoch || !authority.matches(capturedLifetime, expectedAuthUserId)) return;
25838
- runPromise(actor.tell({
25839
- _tag: "ObserveOrganization",
25840
- orgId: nextOrgId,
25841
- snapshot
25842
- })).catch(() => void 0);
25623
+ }
25624
+ function convexPage(deps, actor, controls, input) {
25625
+ return callOperation(deps.convexCall, items.list, copyInvocationObservation(controls, {
25626
+ ...input,
25627
+ actor
25628
+ })).pipe(Effect.mapError((publicError) => ({ publicError })));
25629
+ }
25630
+ function fallbackPage(deps, scope, controls, params, state, degraded) {
25631
+ return Effect.gen(function* () {
25632
+ const page = yield* convexPage(deps, scope.actor, controls, {
25633
+ limit: params?.limit ?? 25,
25634
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
25635
+ range: {
25636
+ ...params?.range?.from === void 0 ? {} : { from: params.range.from },
25637
+ to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
25638
+ },
25639
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
25640
+ });
25641
+ const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
25642
+ kind: item.kind,
25643
+ id: item.id
25644
+ })) });
25645
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
25646
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
25647
+ yield* Effect.annotateCurrentSpan({
25648
+ read_source: "convex",
25649
+ ...degraded ? { read_warning: "indexer-unavailable" } : {}
25650
+ });
25651
+ return {
25652
+ ...page,
25653
+ items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
25654
+ checkpoint: state.checkpoint,
25655
+ nextCursor: page.nextCursor === null ? null : JSON.stringify({
25656
+ ...state,
25657
+ source: "fallback",
25658
+ paymentCursor: page.nextCursor,
25659
+ degraded
25660
+ }),
25661
+ ...degraded ? { readWarning: "indexer-unavailable" } : {}
25843
25662
  };
25844
- try {
25845
- const orgId = toOrgId(nextOrgId);
25846
- input.organizationSetup.subscribe(orgId, emit).then((result) => {
25847
- if (epoch !== selectionEpoch) {
25848
- if (result.ok) result.value();
25849
- return;
25850
- }
25851
- if (result.ok) stopRemote = result.value;
25852
- else emit({
25853
- status: "error",
25854
- error: result.error
25855
- });
25856
- }).catch((cause) => emit({
25857
- status: "error",
25858
- error: Errors.unknown(cause)
25859
- }));
25860
- } catch (cause) {
25861
- emit({
25862
- status: "error",
25863
- error: Errors.unknown(cause)
25864
- });
25865
- }
25866
- };
25867
- const unsubscribeAuthority = actor.subscribe(observe);
25868
- observe(actor.snapshot());
25869
- yield* Effect.addFinalizer(() => Effect.sync(() => {
25870
- authority.invalidate();
25871
- unsubscribeAuthority();
25872
- }));
25873
- return {
25874
- ...actor,
25875
- authLifetime: authority.snapshot,
25876
- matchesAuthLifetime: authority.matches,
25877
- invalidateAuthLifetime: authority.invalidate,
25878
- subscribeAuthInvalidation: authority.subscribeInvalidation,
25879
- authSession: () => sessionStore.current,
25880
- ask: (event, controls) => actor.ask(event, controls).pipe(Effect.tap(() => event._tag === "Reset" ? Effect.sync(() => {
25881
- authority.recordProviderSession(null);
25882
- }) : Effect.void)),
25883
- restoreAuthSession: (session, controls) => actor.ask({
25884
- _tag: actor.snapshot().phase === "restoring" ? "SessionRestored" : "RestoreSession",
25885
- session: modelSession(session),
25886
- profileComplete: false
25887
- }, controls).pipe(Effect.tap(() => Effect.sync(() => {
25888
- authority.recordProviderSession(session);
25889
- })))
25890
- };
25891
- });
25892
- //#endregion
25893
- //#region ../../packages/sdk/src/access/identity/restoration-observation.ts
25894
- /** The correlation a transition carries onto its product event. */
25895
- const linkage = (record) => ({
25896
- ...record.correlation_id === void 0 ? {} : { correlation_id: record.correlation_id },
25897
- ...record.journey_id === void 0 ? {} : { journey_id: record.journey_id }
25898
- });
25899
- /**
25900
- * The label of the restore window every client boots into. A settled session
25901
- * fact that left this label is the restoration result (#1962 R04); the same
25902
- * facts from a signed-out or authenticated state are the routine session probe
25903
- * that `getSession()` runs, and a probe is not a restoration attempt.
25904
- */
25905
- const RESTORE_WINDOW = "restoring";
25906
- /** The completions that settle the restore window by themselves, and what each says. */
25907
- const SETTLING_EVENTS = {
25908
- SessionRestored: "restored",
25909
- SessionAbsent: "absent",
25910
- SessionReadFailed: "failed"
25911
- };
25912
- /**
25913
- * Which result the window settled on. `SessionRestored` and `SessionAbsent` are
25914
- * the cache door and say it themselves. `ReadSession` is also valid inside the
25915
- * window — `getSession()` drives it on a cache miss and its `SessionRead`
25916
- * completion settles the window — so that one event reports both halves and the
25917
- * state it produced is what separates them.
25918
- */
25919
- const restorationOutcome = (event, state) => {
25920
- if (event === "SessionRead") {
25921
- if (state.phase === "authenticated") return "restored";
25922
- return state.phase === "signed_out" ? "absent" : void 0;
25923
- }
25924
- return SETTLING_EVENTS[event];
25925
- };
25926
- /**
25927
- * The restore window's one result, or nothing when this settled session fact
25928
- * belongs to a probe rather than to the window.
25929
- */
25930
- const restorationObservation = (record, event, state) => {
25931
- if (record.slot !== "identity:session" || record.from !== RESTORE_WINDOW) return null;
25932
- const outcome = restorationOutcome(event, state);
25933
- if (outcome === void 0) return null;
25934
- return {
25935
- name: "access_recovery_completed",
25936
- props: {
25937
- recovery: "session_restoration",
25938
- outcome,
25939
- ...outcome === "failed" && state.phase === "faulted" ? { reason_code: state.failure.code } : {},
25940
- ...linkage(record)
25941
- }
25942
- };
25943
- };
25944
- //#endregion
25945
- //#region ../../packages/sdk/src/access/identity/events.ts
25946
- const NONE = null;
25947
- const IDENTITY_EVENT_TAG_SET = new Set(IDENTITY_EVENT_TAGS);
25948
- const isIdentityEventTag = (event) => IDENTITY_EVENT_TAG_SET.has(event);
25949
- const APPLIED_ACTION_BY_EVENT = {
25950
- RequestOtp: "none",
25951
- VerifyOtp: "none",
25952
- ReadSession: "none",
25953
- SignOut: "none",
25954
- RestoreSession: "none",
25955
- ResumeOtpEntry: "none",
25956
- RestoreOtpEntry: "none",
25957
- Reset: "none",
25958
- SessionRestored: "restoration",
25959
- SessionAbsent: "restoration",
25960
- EnsureAccount: "none",
25961
- ClaimAccount: "none",
25962
- RetryAccount: "none",
25963
- CreateOrganization: "none",
25964
- AttachOrganization: "none",
25965
- LeaveOrganization: "none",
25966
- ObserveOrganization: "none",
25967
- OtpSent: "otp-requested",
25968
- OtpFailed: "failed",
25969
- Verified: "verified",
25970
- VerifyFailed: "failed",
25971
- SessionRead: "restoration",
25972
- SessionReadFailed: "restoration",
25973
- SignedOut: "signed-out",
25974
- SignOutFailed: "failed",
25975
- AccountAt: "none",
25976
- AccountDerived: "none",
25977
- AccountClaiming: "none",
25978
- AccountClaimed: "none",
25979
- AccountFailed: "none",
25980
- OrgSelected: "none",
25981
- OrgSelectionFailed: "none"
25982
- };
25983
- const emailDomain = (email) => email.split("@")[1]?.toLowerCase() ?? "unknown";
25984
- const authFailure = (record, reason, mode) => ({
25985
- name: "auth_failed",
25986
- props: {
25987
- auth_type: "otp",
25988
- reason,
25989
- ...mode === void 0 ? {} : { failure_mode: mode },
25990
- ...linkage(record)
25991
- }
25992
- });
25993
- const REFUSAL_EVENTS = /* @__PURE__ */ new Set([
25994
- "ClaimAccount",
25995
- "RetryAccount",
25996
- "CreateOrganization",
25997
- "AttachOrganization"
25998
- ]);
25999
- function mapIdentityProductObservation(record, state) {
26000
- const event = record.event;
26001
- if (record.machine !== "identity" || !isIdentityEventTag(event)) return NONE;
26002
- if (record.outcome === "cancelled") return NONE;
26003
- if (record.slot === "identity:auth" && record.outcome === "refused") return authFailure(record, record.refusal_code, void 0);
26004
- if (record.slot === "identity:auth" && record.outcome === "failed") return authFailure(record, record.error_code, record.failure?.mode);
26005
- if (record.outcome === "refused" && REFUSAL_EVENTS.has(record.event)) return {
26006
- name: "identity_refused",
26007
- props: {
26008
- event: record.event,
26009
- refusal_code: record.refusal_code,
26010
- slot: record.slot,
26011
- state: record.state,
26012
- ...linkage(record)
26013
- }
26014
- };
26015
- if (record.outcome !== "applied") return NONE;
26016
- const action = APPLIED_ACTION_BY_EVENT[event];
26017
- switch (action) {
26018
- case "none": return NONE;
26019
- case "failed": return state.phase !== "faulted" || state.failure.code === "CANCELLED" ? NONE : authFailure(record, state.failure.code, state.failure.mode);
26020
- case "otp-requested": return state.phase !== "otp_pending" ? NONE : {
26021
- name: "auth_otp_requested",
26022
- props: {
26023
- email_domain: emailDomain(state.email),
26024
- ...linkage(record)
26025
- }
25663
+ });
25664
+ }
25665
+ function readActivityPage(deps, actor, controls, params) {
25666
+ return Effect.gen(function* () {
25667
+ yield* Effect.try({
25668
+ try: () => requireParams(params),
25669
+ catch: (cause) => ({ publicError: asCapxulError(cause) })
25670
+ });
25671
+ const scope = yield* overlay(deps, actor, controls, {});
25672
+ const selection = selectionKey(params);
25673
+ const cursor = yield* Effect.try({
25674
+ try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
25675
+ catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
25676
+ });
25677
+ const state = cursor ?? {
25678
+ version: 1,
25679
+ scope: scopeKey(scope),
25680
+ selection,
25681
+ checkpoint: Date.now(),
25682
+ source: "indexed",
25683
+ paymentCursor: null,
25684
+ paymentDone: false,
25685
+ paymentPending: [],
25686
+ transferBefore: null,
25687
+ transferDone: false,
25688
+ degraded: false
26026
25689
  };
26027
- case "verified": return state.phase !== "authenticated" ? NONE : {
26028
- name: "auth_verified",
26029
- props: {
26030
- auth_type: "otp",
26031
- ...linkage(record)
26032
- }
25690
+ const range = boundedRange(params?.range, state.checkpoint);
25691
+ if (range.from !== void 0 && range.from > range.to) return {
25692
+ items: [],
25693
+ nextCursor: null,
25694
+ checkpoint: state.checkpoint,
25695
+ observedAt: Date.now()
26033
25696
  };
26034
- case "signed-out": return state.phase !== "signed_out" ? NONE : {
26035
- name: "auth_signed_out",
26036
- props: linkage(record)
25697
+ if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
25698
+ return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
25699
+ });
25700
+ }
25701
+ function readActivitySummary(deps, actor, controls, params) {
25702
+ return Effect.gen(function* () {
25703
+ const totals = /* @__PURE__ */ new Map();
25704
+ let cursor;
25705
+ let count = 0;
25706
+ let readWarning;
25707
+ do {
25708
+ const page = yield* readActivityPage(deps, actor, controls, {
25709
+ limit: 100,
25710
+ ...cursor === void 0 ? {} : { cursor },
25711
+ ...params?.window === void 0 ? {} : { range: params.window }
25712
+ });
25713
+ count += page.items.length;
25714
+ readWarning ??= page.readWarning;
25715
+ for (const total of summarizeItems(page.items)) {
25716
+ const key = total.asset;
25717
+ const old = totals.get(key);
25718
+ totals.set(key, old === void 0 ? total : {
25719
+ ...total,
25720
+ in: String(BigInt(old.in) + BigInt(total.in)),
25721
+ out: String(BigInt(old.out) + BigInt(total.out)),
25722
+ net: String(BigInt(old.net) + BigInt(total.net)),
25723
+ pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
25724
+ });
25725
+ }
25726
+ cursor = page.nextCursor ?? void 0;
25727
+ } while (cursor !== void 0);
25728
+ return {
25729
+ window: {
25730
+ from: params?.window?.from ?? null,
25731
+ to: params?.window?.to ?? null
25732
+ },
25733
+ totals: [...totals.values()],
25734
+ count,
25735
+ observedAt: Date.now(),
25736
+ ...readWarning === void 0 ? {} : { readWarning }
26037
25737
  };
26038
- case "restoration": return restorationObservation(record, event, state);
26039
- default: return absurd(action);
26040
- }
25738
+ });
26041
25739
  }
26042
- const ignoreTransportFailure = (operation, operationName, eventName) => Effect.suspend(operation).pipe(Effect.catchCause(() => Effect.logWarning("identity.product.telemetry.dropped").pipe(Effect.annotateLogs({
26043
- operation: operationName,
26044
- product_event: eventName
26045
- }), Effect.ignoreCause)));
26046
- function executeIdentityProductObservation(telemetry, record, state) {
26047
- const mapped = mapIdentityProductObservation(record, state);
26048
- if (mapped === null) return Effect.void;
26049
- copyInvocationObservation(record, mapped);
26050
- const invocationTelemetry = bindProductTelemetryInvocation(telemetry, mapped);
26051
- const after = () => state.phase === "authenticated" && (mapped.name === "auth_verified" || mapped.name === "access_recovery_completed") ? invocationTelemetry.identify({
26052
- distinctId: state.session.authUserId,
26053
- traits: { email_domain: emailDomain(state.session.email) }
26054
- }) : mapped.name === "auth_signed_out" ? invocationTelemetry.reset() : Effect.void;
26055
- return ignoreTransportFailure(() => invocationTelemetry.emit(mapped), "emit", mapped.name).pipe(Effect.andThen(ignoreTransportFailure(after, mapped.name === "auth_signed_out" ? "reset" : "identify", mapped.name)));
25740
+ function subscribeActivity(deps, actor, controls, onChange) {
25741
+ return Effect.gen(function* () {
25742
+ const scope = yield* overlay(deps, actor, controls, {});
25743
+ if (scope.safe === null) return () => {};
25744
+ let delivered = false;
25745
+ return yield* deps.indexer.subscribe("transfer", {
25746
+ safe: toAddress(scope.safe),
25747
+ limit: 100
25748
+ }, (snapshot) => {
25749
+ if (snapshot.status === "ok") {
25750
+ if (delivered) onChange();
25751
+ delivered = true;
25752
+ }
25753
+ if (snapshot.status === "error") {
25754
+ delivered = true;
25755
+ onChange();
25756
+ }
25757
+ });
25758
+ });
26056
25759
  }
26057
25760
  //#endregion
26058
- //#region ../../packages/sdk/src/activity/documents.ts
26059
- /** A reply its row does not admit is refused without the document body its decode error would carry. */
26060
- function withoutDocumentBody(error) {
26061
- return error.details?.step === "decodeOutput" ? Errors.invalidInput("documentHash", "invalid document read") : error;
25761
+ //#region ../../packages/sdk/src/activity/movement-detail.ts
25762
+ /** An indexed Movement has no Convex materialization. Read its exact native row. */
25763
+ function readIndexedMovement(deps, actor, controls, eventId) {
25764
+ return Effect.gen(function* () {
25765
+ const scope = yield* overlay(deps, actor, controls, { references: [{
25766
+ kind: "movement",
25767
+ id: eventId
25768
+ }] });
25769
+ if (scope.safe === null) return null;
25770
+ const safe = scope.safe.toLowerCase();
25771
+ const row = (yield* deps.indexer.read("transfer", {
25772
+ safe: toAddress(safe),
25773
+ limit: 1,
25774
+ eventId
25775
+ }).pipe(Effect.mapError((error) => ({ publicError: error.publicError }))))[0];
25776
+ if (row === void 0) return null;
25777
+ const blockNumber = Number(row.blockNumber);
25778
+ if (row.id !== eventId || row.from.toLowerCase() !== safe && row.to.toLowerCase() !== safe || !Number.isSafeInteger(blockNumber) || blockNumber < 0 || !Number.isSafeInteger(row.logIndex) || row.logIndex < 0) return yield* Effect.fail({ publicError: Errors.invalidInput("activity", "indexed Movement does not match the requested scope or evidence") });
25779
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
25780
+ const documentSet = scope.movementDocuments.find((set) => set.movementId === row.id);
25781
+ return {
25782
+ kind: "movement",
25783
+ movementId: row.id,
25784
+ documents: documentSet?.documents ?? [],
25785
+ documentState: documentSet?.state ?? "pending",
25786
+ viewer: scope.actor,
25787
+ annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
25788
+ evidence: {
25789
+ chainId: row.chainId,
25790
+ contractAddress: row.token,
25791
+ tokenAddress: row.token,
25792
+ transactionHash: row.txHash,
25793
+ blockNumber,
25794
+ blockHash: row.blockHash,
25795
+ inclusion: "included",
25796
+ verification: "verified",
25797
+ canonicalState: "finalized",
25798
+ transferLogIndex: row.logIndex,
25799
+ source: row.from,
25800
+ destination: row.to,
25801
+ rawAmount: row.amount.toString(),
25802
+ link: {
25803
+ movementId: row.id,
25804
+ depositPaymentId: null
25805
+ }
25806
+ }
25807
+ };
25808
+ });
26062
25809
  }
26063
- /** Schema errors must not retain document bodies. */
26064
- function decodeDocumentRead(schema, input) {
26065
- return Effect.fromOption(Schema.decodeUnknownOption(schema)(input)).pipe(Effect.mapError(() => ({ publicError: Errors.invalidInput("documentHash", "invalid document read") })));
25810
+ //#endregion
25811
+ //#region ../../packages/sdk/src/activity/scope.ts
25812
+ /**
25813
+ * The activity backend gives its actor a different name than the other
25814
+ * financialOps functions: `{kind: "organization", orgId}`, and not
25815
+ * `{kind: "org", orgId}`. Thus it does not use `actorReferenceToBackend`.
25816
+ *
25817
+ * The `personal` case sends NO actor. This is correct, and it does not lose
25818
+ * data. `requireActivityActor` uses the signed-in Account when the actor is
25819
+ * absent, and `canReadActor` refuses all other account identifiers
25820
+ * (`packages/backend/convex/movement/activityAuth.ts`). Thus "personal" and
25821
+ * "absent" both name the one Account that an SDK caller can read. The SDK does
25822
+ * not hold an account identifier to send.
25823
+ *
25824
+ * The `switch` is exhaustive. A new `ActorReference` variant is a compile error
25825
+ * here. The conditional expression that this function replaced removed each
25826
+ * value that was not an organization.
25827
+ */
25828
+ function activityActorField(actor) {
25829
+ if (actor === void 0 || actor.kind === "personal") return {};
25830
+ return { actor: {
25831
+ kind: "organization",
25832
+ orgId: actor.organizationId
25833
+ } };
26066
25834
  }
26067
- function makePaymentDocumentsMethods(deps) {
26068
- /**
26069
- * One document read by its row. The pair is decoded before anything is sent,
26070
- * and a reply the row does not admit is refused without the body it carried.
26071
- */
26072
- const readDocument = (row, ref, options) => {
26073
- const controls = deps.invocationControls?.(options) ?? options;
26074
- return runPortEffect(Effect.gen(function* () {
26075
- const pair = yield* decodeDocumentRead(PaymentDocumentReadRefSchema, ref);
26076
- return yield* callOperation(deps.convexCall, row, copyInvocationObservation(controls, pair)).pipe(Effect.mapError((error) => ({ publicError: withoutDocumentBody(error) })));
26077
- }), controls, row.name, deps.runPromise);
26078
- };
26079
- return {
26080
- get: (ref, options) => readDocument(documents.get, ref, options),
26081
- verify: (ref, options) => readDocument(documents.verify, ref, options),
26082
- render: (ref, options) => readDocument(documents.render, ref, options)
26083
- };
25835
+ /** Capture scope now; let the observed result boundary handle a malformed caller value. */
25836
+ function captureActivityInput(prepare) {
25837
+ try {
25838
+ return prepare();
25839
+ } catch (cause) {
25840
+ return Effect.die(cause);
25841
+ }
26084
25842
  }
26085
- //#endregion
26086
- //#region ../../packages/sdk/src/activity/stitching.ts
26087
- /** Epoch milliseconds of the block that carried the row. */
26088
- function blockMillis(row) {
26089
- return Number(row.timestamp) * 1e3;
25843
+ function observeActivityScope(actor) {
25844
+ const organizationId = actor?.kind === "organization" || actor?.kind === "org" ? safeEngineeringIdentifier(actor.orgId) : void 0;
25845
+ return Effect.annotateCurrentSpan({
25846
+ scope_kind: actor?.kind === "organization" || actor?.kind === "org" ? "organization" : "account",
25847
+ ...organizationId === void 0 ? {} : { verified_organization_id: organizationId }
25848
+ });
26090
25849
  }
26091
- /** `0x1234…abcd` — the counterparty of an unclaimed transfer is an address. */
26092
- function shortAddressLabel(address) {
26093
- return address.length <= 12 ? address : `${address.slice(0, 6)}…${address.slice(-4)}`;
25850
+ function decodeActivityViewer(value) {
25851
+ const viewer = Option.getOrUndefined(Schema.decodeUnknownOption(ActorRefSchema)(value));
25852
+ if (viewer === void 0) return void 0;
25853
+ const id = viewer.kind === "organization" ? viewer.orgId : viewer.accountId;
25854
+ return safeEngineeringIdentifier(id) === void 0 ? void 0 : viewer;
26094
25855
  }
26095
- function directionFor(safe, transfer) {
26096
- const from = transfer.from.toLowerCase();
26097
- const to = transfer.to.toLowerCase();
26098
- if (from === safe && to === safe) return "self";
26099
- return to === safe ? "in" : "out";
25856
+ /** The detail without the scope it was read for, which the SDK reports as telemetry instead. */
25857
+ function normalizeActivityDetail(response) {
25858
+ if (response === null) return null;
25859
+ const { viewer: _viewer, ...detail } = response;
25860
+ return detail;
26100
25861
  }
26101
- function withAnnotation(item, annotation) {
26102
- return annotation?.counterpartyLabel === void 0 ? item : {
26103
- ...item,
26104
- counterpartyLabel: annotation.counterpartyLabel
25862
+ //#endregion
25863
+ //#region ../../packages/sdk/src/activity/methods.ts
25864
+ function makeActivityMethods(deps) {
25865
+ const indexerDeps = deps.indexerRead === void 0 ? void 0 : {
25866
+ indexer: deps.indexerRead,
25867
+ convexCall: deps.convexCall
26105
25868
  };
26106
- }
26107
- /**
26108
- * An indexed settlement confirms the intent. A missing row can be outside
26109
- * this page or still backfilling; only the correction owner can prove rollback.
26110
- */
26111
- function confirmPayment(intent, chain, expected) {
26112
- if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
26113
- ...intent,
26114
- status: "settled",
26115
- ...intent.execution === void 0 ? {} : { execution: {
26116
- status: "settled",
26117
- retryRequired: false
26118
- } },
26119
- receipt: {
26120
- chainId: chain.chainId,
26121
- transactionHash: chain.txHash
26122
- },
26123
- updatedAt: blockMillis(chain)
25869
+ /** One public call for the caller's chosen actor, read once at call start. */
25870
+ const forActor = (actorRef, options, operation, program) => {
25871
+ const controls = deps.invocationControls?.(options) ?? options;
25872
+ return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(actorRef).actor)).pipe(Effect.flatMap((actor) => program(actor, controls))), controls, operation, deps.runPromise);
26124
25873
  };
26125
- return intent;
26126
- }
26127
- function matchesPaymentFact(chain, expected) {
26128
- return chain !== void 0 && expected !== void 0 && chain.chainId === expected.chainId && chain.contract.toLowerCase() === expected.contract.toLowerCase() && chain.sender.toLowerCase() === expected.sender.toLowerCase() && chain.recipient.toLowerCase() === expected.recipient.toLowerCase() && chain.token.toLowerCase() === expected.token.toLowerCase() && chain.amount.toString() === expected.rawAmount && chain.documentHash.toLowerCase() === expected.documentHash.toLowerCase() && chain.kind === expected.kind;
26129
- }
26130
- /**
26131
- * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
26132
- * denominate its asset. The indexer serves only configured assets, so a row
26133
- * the registry does not know means the SDK and that deployment disagree; it is
26134
- * dropped rather than shown with a guessed currency.
26135
- */
26136
- function isActivityVisible(item) {
26137
- return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
26138
- }
26139
- function transferItem(safe, transfer, annotation) {
26140
- const asset = configuredMoneyAssetById(transfer.assetId);
26141
- if (asset === null) return null;
26142
- const direction = directionFor(safe.toLowerCase(), transfer);
26143
- const counterparty = direction === "in" ? transfer.from : transfer.to;
26144
- const at = blockMillis(transfer);
26145
- return withAnnotation({
26146
- kind: "movement",
26147
- id: transfer.id,
26148
- classification: "movement_only_outflow",
26149
- amount: {
26150
- asset: asset.assetId,
26151
- value: formatUnits(transfer.amount, asset.decimals)
26152
- },
26153
- direction,
26154
- counterpartyLabel: shortAddressLabel(counterparty),
26155
- receipt: {
26156
- chainId: transfer.chainId,
26157
- transactionHash: transfer.txHash
25874
+ return {
25875
+ subscribe: (onChange, params, options) => forActor(params?.actor, options, CAPXUL_OPERATIONS.activity.subscribe, (actor, controls) => indexerDeps === void 0 ? Effect.succeed(() => {}) : Effect.suspend(() => subscribeActivity(indexerDeps, actor, controls, onChange)).pipe(Effect.tap(() => observeActivityScope(actor)))),
25876
+ list: (params, options) => forActor(params?.actor, options, items.list.name, (actor, controls) => Effect.suspend(() => indexerDeps === void 0 ? callOperation(deps.convexCall, items.list, copyInvocationObservation(controls, {
25877
+ ...actor === void 0 ? {} : { actor },
25878
+ ...params?.cursor === void 0 ? {} : { cursor: params.cursor },
25879
+ ...params?.limit === void 0 ? {} : { limit: params.limit },
25880
+ ...params?.range === void 0 ? {} : { range: params.range },
25881
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
25882
+ })).pipe(Effect.mapError((publicError) => ({ publicError }))) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
25883
+ ...page,
25884
+ items: page.items.filter(isActivityVisible)
25885
+ })), Effect.tap(() => observeActivityScope(actor)))),
25886
+ summary: (params, options) => forActor(params?.actor, options, items.summary.name, (actor, controls) => Effect.suspend(() => indexerDeps === void 0 ? callOperation(deps.convexCall, items.summary, copyInvocationObservation(controls, {
25887
+ ...actor === void 0 ? {} : { actor },
25888
+ ...params?.window === void 0 ? {} : { window: params.window }
25889
+ })).pipe(Effect.mapError((publicError) => ({ publicError }))) : readActivitySummary(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor)))),
25890
+ get: (reference, options) => {
25891
+ const controls = deps.invocationControls?.(options) ?? options;
25892
+ const requestedActor = actorReferenceToBackend(options?.actor);
25893
+ return runPortEffect(captureActivityInput(() => {
25894
+ const scope = requestedActor === void 0 ? void 0 : requestedActor.kind === "org" ? {
25895
+ kind: "org",
25896
+ orgId: requestedActor.orgId
25897
+ } : { kind: "account" };
25898
+ return Effect.succeed({
25899
+ actor: scope,
25900
+ referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
25901
+ });
25902
+ }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => indexerDeps !== void 0 && reference.kind === "movement" && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(reference.id) ? readIndexedMovement(indexerDeps, actor?.kind === "org" ? {
25903
+ kind: "organization",
25904
+ orgId: actor.orgId
25905
+ } : void 0, controls, reference.id) : callOperation(deps.convexCall, items.get, copyInvocationObservation(controls, {
25906
+ reference: {
25907
+ kind: reference.kind,
25908
+ id: reference.id,
25909
+ ...referenceActor === void 0 ? {} : { actor: referenceActor }
25910
+ },
25911
+ ...actor === void 0 ? {} : { actor }
25912
+ })).pipe(Effect.mapError((publicError) => ({ publicError })))).pipe(Effect.flatMap((response) => {
25913
+ const detail = normalizeActivityDetail(response);
25914
+ if (response === null) return Effect.succeed(detail);
25915
+ const viewer = response.viewer === void 0 ? actor ?? referenceActor : decodeActivityViewer(response.viewer);
25916
+ return viewer === void 0 ? Effect.succeed(detail) : observeActivityScope(viewer).pipe(Effect.as(detail));
25917
+ })))), controls, items.get.name, deps.runPromise);
26158
25918
  },
26159
- createdAt: at,
26160
- updatedAt: at
26161
- }, annotation);
26162
- }
26163
- /**
26164
- * Money in, money out and the pending share, one bucket per exact asset.
26165
- */
26166
- function summarizeItems(items) {
26167
- const buckets = /* @__PURE__ */ new Map();
26168
- for (const item of items) {
26169
- const metadata = configuredMoneyAssetById(item.amount.asset);
26170
- if (metadata === null) continue;
26171
- const key = item.amount.asset;
26172
- const bucket = buckets.get(key) ?? {
26173
- asset: item.amount.asset,
26174
- in: 0n,
26175
- out: 0n,
26176
- pendingIn: 0n
26177
- };
26178
- const raw = rawUnits(item.amount.value, metadata.decimals);
26179
- if (item.kind === "payment" && item.status !== "settled") {
26180
- if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
26181
- } else if (item.direction === "in") bucket.in += raw;
26182
- else if (item.direction === "out") bucket.out += raw;
26183
- buckets.set(key, bucket);
26184
- }
26185
- return [...buckets.values()].map((bucket) => ({
26186
- asset: bucket.asset,
26187
- in: bucket.in.toString(),
26188
- out: bucket.out.toString(),
26189
- net: (bucket.in - bucket.out).toString(),
26190
- pendingIn: bucket.pendingIn.toString()
26191
- }));
26192
- }
26193
- /** The inverse of `formatUnits`, kept exact: `"1.25"` at 6 decimals is `1250000n`. */
26194
- function rawUnits(value, decimals) {
26195
- const negative = value.startsWith("-");
26196
- const [whole = "0", fraction = ""] = (negative ? value.slice(1) : value).split(".");
26197
- const raw = BigInt(`${whole}${fraction.padEnd(decimals, "0").slice(0, decimals)}`);
26198
- return negative ? -raw : raw;
26199
- }
26200
- //#endregion
26201
- //#region ../../packages/sdk/src/activity/cursor.ts
26202
- function actorKey(actor) {
26203
- return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
26204
- }
26205
- function scopeKey(scope) {
26206
- return `${scope.principalAccountId}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
26207
- }
26208
- function selectionKey(params) {
26209
- const filter = params?.filter;
26210
- return JSON.stringify([
26211
- params?.range?.from ?? null,
26212
- params?.range?.to ?? null,
26213
- filter?.kind ?? null,
26214
- filter?.direction ?? null,
26215
- filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
26216
- ]);
26217
- }
26218
- function validString(value, max) {
26219
- return typeof value === "string" && value.length <= max;
26220
- }
26221
- function validPosition(position) {
26222
- return position === null || typeof position === "object" && "timestamp" in position && typeof position.timestamp === "number" && Number.isSafeInteger(position.timestamp) && position.timestamp >= 0 && "id" in position && typeof position.id === "string" && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(position.id);
26223
- }
26224
- function isActivityCursor(cursor, scope, selection) {
26225
- return typeof cursor === "object" && cursor !== null && cursorScopeMatches(cursor, scope, selection) && cursorProgressIsValid(cursor);
26226
- }
26227
- /** The cursor was issued for this scope and filter selection, at a valid checkpoint. */
26228
- function cursorScopeMatches(cursor, scope, selection) {
26229
- return "version" in cursor && cursor.version === 1 && "scope" in cursor && cursor.scope === scope && "selection" in cursor && cursor.selection === selection && "checkpoint" in cursor && typeof cursor.checkpoint === "number" && Number.isSafeInteger(cursor.checkpoint) && cursor.checkpoint >= 0 && "source" in cursor && (cursor.source === "indexed" || cursor.source === "fallback");
26230
- }
26231
- /** Each source's progress is well-formed and bounded. */
26232
- function cursorProgressIsValid(cursor) {
26233
- return "paymentCursor" in cursor && (cursor.paymentCursor === null || validString(cursor.paymentCursor, 2048)) && "paymentDone" in cursor && typeof cursor.paymentDone === "boolean" && "transferDone" in cursor && typeof cursor.transferDone === "boolean" && "degraded" in cursor && typeof cursor.degraded === "boolean" && "paymentPending" in cursor && Array.isArray(cursor.paymentPending) && cursor.paymentPending.length <= 100 && new Set(cursor.paymentPending).size === cursor.paymentPending.length && cursor.paymentPending.every((id) => validString(id, 256)) && "transferBefore" in cursor && validPosition(cursor.transferBefore);
26234
- }
26235
- function parseCursor(value, scope, selection) {
26236
- if (value === void 0) return null;
26237
- if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
26238
- const cursor = JSON.parse(value);
26239
- if (!isActivityCursor(cursor, scope, selection)) throw Errors.invalidInput("cursor", "Activity scope or filters changed; restart from the first page");
26240
- return cursor;
26241
- }
26242
- function requireParams(params) {
26243
- const limit = params?.limit ?? 25;
26244
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
26245
- for (const value of [params?.range?.from, params?.range?.to]) if (value !== void 0 && !Number.isSafeInteger(value)) throw Errors.invalidInput("range", "must contain integer epoch milliseconds");
26246
- const f = params?.filter;
26247
- if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
26248
- "in",
26249
- "out",
26250
- "self"
26251
- ].includes(f.direction) || f?.status !== void 0 && (!Array.isArray(f.status) || !f.status.every((status) => PAYMENT_STATUSES.includes(status)))) throw Errors.invalidInput("filter", "invalid Activity filter");
26252
- }
26253
- function matchesFilter(item, filter) {
26254
- return (filter?.kind === void 0 || item.kind === filter.kind) && (filter?.direction === void 0 || item.direction === filter.direction) && (filter?.status === void 0 || item.kind === "payment" && filter.status.includes(item.status));
25919
+ annotate: (input, options) => {
25920
+ const controls = deps.invocationControls?.(options) ?? options;
25921
+ return runPortEffect(captureActivityInput(() => {
25922
+ const actor = activityActorField(input.actor).actor;
25923
+ if (input.actor !== void 0 && input.reference.kind === "payment" && input.reference.actor !== void 0 && !sameActor(input.actor, input.reference.actor)) return Effect.fail({ publicError: Errors.invalidInput("reference.actor", "must match the annotation actor") });
25924
+ return Effect.succeed({
25925
+ actor,
25926
+ referenceActor: input.reference.kind === "payment" ? activityActorField(input.reference.actor).actor : void 0
25927
+ });
25928
+ }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => callOperation(deps.convexCall, annotations.save, copyInvocationObservation(controls, {
25929
+ ...actor === void 0 ? {} : { actor },
25930
+ reference: input.reference.kind === "payment" && referenceActor !== void 0 ? {
25931
+ kind: "payment",
25932
+ id: input.reference.id,
25933
+ actor: referenceActor
25934
+ } : {
25935
+ kind: input.reference.kind,
25936
+ id: input.reference.id
25937
+ },
25938
+ ...input.counterpartyLabel === void 0 ? {} : { counterpartyLabel: input.counterpartyLabel },
25939
+ ...input.accountingCategory === void 0 ? {} : { accountingCategory: input.accountingCategory },
25940
+ ...input.memo === void 0 ? {} : { memo: input.memo }
25941
+ })).pipe(Effect.mapError((publicError) => ({ publicError })))).pipe(Effect.tap((annotation) => {
25942
+ const scope = decodeActivityViewer(annotation.actor);
25943
+ return scope === void 0 ? Effect.void : observeActivityScope(scope);
25944
+ })))), controls, annotations.save.name, deps.runPromise);
25945
+ }
25946
+ };
26255
25947
  }
25948
+ Object.freeze({
25949
+ terminal: false,
25950
+ outcome: null,
25951
+ needsAction: false
25952
+ });
25953
+ Object.freeze({
25954
+ terminal: false,
25955
+ outcome: null,
25956
+ needsAction: true
25957
+ });
25958
+ Object.freeze({
25959
+ terminal: true,
25960
+ outcome: "good",
25961
+ needsAction: false
25962
+ });
25963
+ Object.freeze({
25964
+ terminal: true,
25965
+ outcome: "bad",
25966
+ needsAction: false
25967
+ });
25968
+ Object.freeze({
25969
+ terminal: true,
25970
+ outcome: "neutral",
25971
+ needsAction: false
25972
+ });
26256
25973
  //#endregion
26257
- //#region ../../packages/sdk/src/activity/indexed-pages.ts
26258
- function chainRead(deps, table, input) {
26259
- return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
26260
- if (error.publicError.details?.reason === "scope-refused" || ![
26261
- "NETWORK_ERROR",
26262
- "RATE_LIMITED",
26263
- "PROVIDER_ERROR",
26264
- "CAPABILITY_UNAVAILABLE",
26265
- "TIMEOUT"
26266
- ].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
26267
- return Effect.succeed(null);
26268
- }));
26269
- }
26270
- function boundedRange(range, checkpoint) {
25974
+ //#region ../../packages/sdk/src/accounts/summaries.ts
25975
+ function makeMeMethods(deps) {
26271
25976
  return {
26272
- ...range?.from === void 0 ? {} : { from: range.from },
26273
- to: Math.min(checkpoint, range?.to ?? checkpoint)
26274
- };
26275
- }
26276
- function unavailablePage(deps, scope, controls, params, state, first) {
26277
- if (first) return fallbackPage(deps, scope, controls, params, state, true);
26278
- return Effect.annotateCurrentSpan({
26279
- read_source: "indexer",
26280
- read_warning: "indexer-unavailable"
26281
- }).pipe(Effect.as({
26282
- items: [],
26283
- nextCursor: null,
26284
- checkpoint: state.checkpoint,
26285
- observedAt: Date.now(),
26286
- readWarning: "indexer-unavailable"
26287
- }));
26288
- }
26289
- function readTransfers(deps, safe, range, state, wanted, first) {
26290
- const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
26291
- const to = Math.floor(range.to / 1e3);
26292
- if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
26293
- return chainRead(deps, "transfer", {
26294
- safe: toAddress(safe),
26295
- limit: wanted ? 100 : 1,
26296
- timeline: {
26297
- from,
26298
- to,
26299
- ...state.transferBefore === null ? {} : { before: state.transferBefore }
25977
+ get: (options) => runIfActive(options?.signal, summaries.me.name, () => callOperation(deps.convexCall, summaries.me, {}).pipe(Effect.mapError((publicError) => ({ publicError }))), deps.runPromise),
25978
+ home: (options) => runIfActive(options?.signal, summaries.home.name, () => (deps.chainId === void 0 ? Effect.fail(Errors.notImplemented("accounts", "me.home")) : callOperation(deps.convexCall, summaries.home, {
25979
+ chainId: deps.chainId,
25980
+ ...options?.orgId === void 0 ? {} : { orgId: options.orgId }
25981
+ })).pipe(Effect.flatMap((wire) => Effect.try({
25982
+ try: () => ({
25983
+ ...wire,
25984
+ balances: wire.balances === null ? null : accountPositionsFromWire(wire.balances),
25985
+ recentActivity: wire.recentActivity?.filter(isActivityVisible) ?? null
25986
+ }),
25987
+ catch: asCapxulError
25988
+ })), Effect.mapError((publicError) => ({ publicError }))), deps.runPromise),
25989
+ depositInstructions: (options) => {
25990
+ const controls = deps.invocationControls?.(options) ?? options;
25991
+ return runPortEffect(callOperation(deps.convexCall, summaries.depositInstructions, copyInvocationObservation(controls, {})).pipe(Effect.mapError((publicError) => ({ publicError })), Effect.tap((instructions) => emitDepositInitiated(deps.telemetry, instructions, controls))), controls, summaries.depositInstructions.name, deps.runPromise);
26300
25992
  }
26301
- });
25993
+ };
26302
25994
  }
26303
- function readPayments(deps, scope, controls, range, state, wanted) {
26304
- const done = state.paymentDone || !wanted;
26305
- if (state.paymentPending.length > 0 || done) return Effect.succeed({
26306
- items: [],
26307
- pending: state.paymentPending,
26308
- cursor: state.paymentCursor,
26309
- done
25995
+ //#endregion
25996
+ //#region ../../packages/sdk/src/access/identity/account-lane.ts
25997
+ const staleAccountFailure = {
25998
+ code: "CANCELLED",
25999
+ message: "Account authority changed"
26000
+ };
26001
+ const requireAccountAuthority = (isCurrent, send, step) => Effect.suspend(() => isCurrent() ? Effect.void : send({
26002
+ _tag: "AccountFailed",
26003
+ step,
26004
+ failure: staleAccountFailure
26005
+ }).pipe(Effect.andThen(Effect.fail(staleAccountFailure))));
26006
+ const accountCall = (name, make, isCurrent) => Effect.suspend(() => isCurrent() ? call$1(name, make()) : Effect.fail(staleAccountFailure));
26007
+ const modelSession = (session) => ({
26008
+ authUserId: String(session.authUserId),
26009
+ email: String(session.email)
26010
+ });
26011
+ const claimAccount = (input, authUserId, signer, isCurrent) => {
26012
+ const cancelled = smartAccountErrorFromCapxul("claim", Errors.cancelled({ operation: CAPXUL_OPERATIONS.smartAccount.claim }));
26013
+ return call$1("smart-account.claim", Effect.tryPromise({
26014
+ try: () => {
26015
+ if (!isCurrent()) throw cancelled;
26016
+ return signer.getAddress();
26017
+ },
26018
+ catch: (cause) => cause === cancelled ? cancelled : signerFailure(signer.source, "getAddress", cause)
26019
+ }).pipe(Effect.flatMap((signerAddress) => Effect.suspend(() => isCurrent() ? input.ports.smartAccount.claim({
26020
+ authUserId: toAuthUserId(authUserId),
26021
+ chainId: toChainId(input.chainId),
26022
+ signerAddress
26023
+ }) : Effect.fail(cancelled)))));
26024
+ };
26025
+ const hasDurableClaim = (account) => account.deployedAt !== null && account.claimedAt !== null && account.signerAddress !== null;
26026
+ const profileComplete = (profile) => profile?.onboarded === true;
26027
+ const sessionProfile = (input, session) => {
26028
+ if (session === null) return Effect.succeed({
26029
+ session: null,
26030
+ profileComplete: false
26310
26031
  });
26311
- return convexPage(deps, scope.actor, controls, {
26312
- limit: 100,
26313
- range,
26314
- filter: { kind: "payment" },
26315
- ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
26316
- }).pipe(Effect.map((page) => ({
26317
- items: page.items,
26318
- pending: page.items.map((item) => item.id),
26319
- cursor: page.nextCursor,
26320
- done: page.nextCursor === null
26032
+ return call$1("identity.load-by-auth-user-id", input.ports.identity.loadByAuthUserId(session.authUserId)).pipe(Effect.map((profile) => ({
26033
+ session: modelSession(session),
26034
+ profileComplete: profileComplete(profile)
26321
26035
  })));
26322
- }
26323
- function readPaymentFacts(deps, safe, settlements) {
26324
- const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
26325
- return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
26326
- safe: toAddress(safe),
26327
- limit: 50,
26328
- settlements: settlements.slice(offset, offset + 50)
26329
- }), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
26330
- }
26331
- function indexedPage(deps, scope, controls, params, state, first) {
26332
- return Effect.gen(function* () {
26333
- const range = boundedRange(params?.range, state.checkpoint);
26334
- const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
26335
- const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
26336
- if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
26337
- const transfers = wantsTransfers ? found : [];
26338
- const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
26339
- const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
26340
- if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
26341
- const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
26342
- const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
26343
- if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
26344
- yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
26345
- return {
26346
- items,
26347
- nextCursor: cursor,
26348
- checkpoint: state.checkpoint,
26349
- observedAt: Date.now()
26350
- };
26036
+ };
26037
+ const accountLane = (input, config, session, send, isCurrent) => Effect.gen(function* () {
26038
+ let step = "wallet";
26039
+ const failAt = (at, failure) => send({
26040
+ _tag: "AccountFailed",
26041
+ step: at,
26042
+ failure
26043
+ }).pipe(Effect.andThen(Effect.fail(failure)));
26044
+ yield* requireAccountAuthority(isCurrent, send, step);
26045
+ step = "identity";
26046
+ yield* send({
26047
+ _tag: "AccountAt",
26048
+ step: "identity"
26351
26049
  });
26352
- }
26353
- //#endregion
26354
- //#region ../../packages/sdk/src/activity/page-overlay.ts
26355
- const MAX_OVERLAY_IDS = 200;
26356
- function overlay(deps, actor, controls, input) {
26357
- const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
26358
- const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
26359
- return Effect.forEach(offsets, (offset) => callOperation(deps.convexCall, overlays.get, copyInvocationObservation(controls, {
26360
- ...actor === void 0 ? {} : { actor },
26361
- ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
26362
- ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
26363
- })).pipe(Effect.mapError((publicError) => ({ publicError }))), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
26364
- const first = pages[0];
26365
- if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
26366
- return Effect.succeed({
26367
- ...first,
26368
- links: pages.flatMap((page) => page.links),
26369
- annotations: pages.flatMap((page) => page.annotations),
26370
- payments: pages.flatMap((page) => page.payments)
26371
- });
26372
- }));
26373
- }
26374
- function decoratePage(deps, scope, controls, transfers, page, range) {
26375
- return Effect.gen(function* () {
26376
- const decoration = yield* overlay(deps, scope.actor, controls, {
26377
- references: [...page.pending.map((id) => ({
26378
- kind: "payment",
26379
- id
26380
- })), ...transfers.map((row) => ({
26381
- kind: "movement",
26382
- id: row.id
26383
- }))],
26384
- settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
26385
- });
26386
- if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
26387
- const byId = new Map([...page.items, ...decoration.payments].map((item) => [item.id, item]));
26388
- const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
26389
- const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
26390
- if (facts === null) return null;
26391
- const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
26392
- const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
26393
- fact: bySettlement.get(link.settlementId),
26394
- expected: link.expected
26395
- }]));
26396
- const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
26397
- return {
26398
- payments: page.pending.flatMap((id) => {
26399
- const item = byId.get(id);
26400
- if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
26401
- const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
26402
- return isActivityVisible(confirmed) ? [confirmed] : [];
26403
- }),
26404
- annotations,
26405
- linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
26406
- };
26050
+ step = "provision";
26051
+ yield* send({
26052
+ _tag: "AccountAt",
26053
+ step: "provision"
26054
+ });
26055
+ const existing = yield* Effect.result(accountCall("smart-account.load-by-auth-user-id", () => input.ports.smartAccount.loadByAuthUserId(toAuthUserId(session.authUserId)), isCurrent));
26056
+ yield* requireAccountAuthority(isCurrent, send, step);
26057
+ if (Result.isFailure(existing)) return yield* failAt("provision", existing.failure);
26058
+ const provisioned = yield* Effect.result(existing.success === null ? accountCall("smart-account.provision", () => input.ports.smartAccount.provision({
26059
+ authUserId: toAuthUserId(session.authUserId),
26060
+ chainId: toChainId(input.chainId),
26061
+ email: toEmail(session.email)
26062
+ }), isCurrent) : Effect.succeed(existing.success));
26063
+ yield* requireAccountAuthority(isCurrent, send, step);
26064
+ if (Result.isFailure(provisioned)) return yield* failAt("provision", provisioned.failure);
26065
+ const account = provisioned.success;
26066
+ const address = String(account.smartAccountAddress);
26067
+ yield* send({
26068
+ _tag: "AccountDerived",
26069
+ address
26070
+ });
26071
+ yield* requireAccountAuthority(isCurrent, send, step);
26072
+ if (hasDurableClaim(account)) return yield* send({
26073
+ _tag: "AccountClaimed",
26074
+ address
26075
+ });
26076
+ if (config.requirement !== "deployed" || input.signer === void 0) return;
26077
+ yield* requireAccountAuthority(isCurrent, send, step);
26078
+ step = "deploy";
26079
+ yield* send({ _tag: "AccountClaiming" });
26080
+ const claimed = yield* Effect.result(claimAccount(input, session.authUserId, input.signer, isCurrent));
26081
+ yield* requireAccountAuthority(isCurrent, send, step);
26082
+ if (Result.isFailure(claimed)) return yield* failAt("deploy", claimed.failure);
26083
+ return yield* send(hasDurableClaim(claimed.success) ? {
26084
+ _tag: "AccountClaimed",
26085
+ address: String(claimed.success.smartAccountAddress)
26086
+ } : {
26087
+ _tag: "AccountDerived",
26088
+ address: String(claimed.success.smartAccountAddress)
26407
26089
  });
26090
+ });
26091
+ //#endregion
26092
+ //#region ../../packages/sdk/src/access/identity/spec.ts
26093
+ const SETTLED_BY_TRANSITION_TAGS = /* @__PURE__ */ new Set([
26094
+ "RestoreSession",
26095
+ "ResumeOtpEntry",
26096
+ "RestoreOtpEntry",
26097
+ "Reset",
26098
+ "SessionRestored",
26099
+ "SessionAbsent",
26100
+ "AttachOrganization",
26101
+ "LeaveOrganization",
26102
+ "ObserveOrganization"
26103
+ ]);
26104
+ function isSettledByTransition(event) {
26105
+ return SETTLED_BY_TRANSITION_TAGS.has(event._tag);
26408
26106
  }
26409
- function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
26410
- const { payments, annotations, linked } = decoration;
26411
- let pi = 0;
26412
- let ti = 0;
26413
- const items = [];
26414
- const transferEnd = !wantsTransfers || transfers.length < 100;
26415
- let transferBefore = state.transferBefore;
26416
- while (items.length < limit) {
26417
- if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
26418
- const payment = payments[pi];
26419
- const row = transfers[ti];
26420
- if (payment === void 0 && row === void 0) break;
26421
- if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
26422
- pi += 1;
26423
- if (matchesFilter(payment, filter)) items.push(payment);
26424
- } else if (row !== void 0) {
26425
- ti += 1;
26426
- transferBefore = {
26427
- timestamp: Number(row.timestamp),
26428
- id: row.id
26429
- };
26430
- if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
26431
- const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
26432
- if (item !== null && matchesFilter(item, filter)) items.push(item);
26107
+ const identitySpec = (input, sessionStore, authority) => {
26108
+ const config = {
26109
+ ...DEFAULT_CONFIG,
26110
+ requirement: input.requirement,
26111
+ hasSigner: input.signer !== void 0,
26112
+ ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs }
26113
+ };
26114
+ return {
26115
+ machine: "identity",
26116
+ initial: RESTORING,
26117
+ label,
26118
+ slot: identitySlot,
26119
+ transition: (state, event) => transition(state, event, config),
26120
+ recoverFailure: recoverIdentityFailure,
26121
+ invalidates: (event) => event._tag === "Reset" ? [
26122
+ IDENTITY_SLOT.auth,
26123
+ IDENTITY_SLOT.session,
26124
+ IDENTITY_SLOT.account,
26125
+ IDENTITY_SLOT.org
26126
+ ] : event._tag === "SignOut" ? [
26127
+ IDENTITY_SLOT.session,
26128
+ IDENTITY_SLOT.account,
26129
+ IDENTITY_SLOT.org
26130
+ ] : event._tag === "LeaveOrganization" || event._tag === "AttachOrganization" ? [IDENTITY_SLOT.org] : [],
26131
+ work: ({ state, event }) => {
26132
+ const activeSession = state.phase === "authenticated" ? state.session : null;
26133
+ const org = state.phase === "authenticated" && state.account.at === "claimed" ? state.account.org : null;
26134
+ if (isSettledByTransition(event)) return void 0;
26135
+ switch (event._tag) {
26136
+ case "RequestOtp": return {
26137
+ slot: IDENTITY_SLOT.auth,
26138
+ port: "auth-client",
26139
+ run: (send, controls) => call$1("auth-client.send-otp", input.ports.authClient.sendOtp(copyInvocationObservation(controls, { email: toEmail(event.email) }))).pipe(Effect.andThen(call$1("clock.now", input.ports.clock.now)), Effect.flatMap((at) => send({
26140
+ _tag: "OtpSent",
26141
+ at
26142
+ })), Effect.tapError((failure) => send({
26143
+ _tag: "OtpFailed",
26144
+ failure
26145
+ })))
26146
+ };
26147
+ case "VerifyOtp": return {
26148
+ slot: IDENTITY_SLOT.auth,
26149
+ port: "auth-client",
26150
+ run: (send, controls) => call$1("auth-client.verify-otp", input.ports.authClient.verifyOtp(copyInvocationObservation(controls, {
26151
+ email: toEmail(event.email),
26152
+ otp: event.otp
26153
+ }))).pipe(Effect.tap((session) => Effect.sync(() => {
26154
+ authority.recordProviderSession(session);
26155
+ })), Effect.flatMap((session) => sessionProfile(input, session).pipe(Effect.flatMap((loaded) => send({
26156
+ _tag: "Verified",
26157
+ session: loaded.session ?? modelSession(session),
26158
+ profileComplete: loaded.profileComplete
26159
+ })))), Effect.tapError((failure) => send({
26160
+ _tag: "VerifyFailed",
26161
+ failure
26162
+ })))
26163
+ };
26164
+ case "ReadSession": return {
26165
+ slot: IDENTITY_SLOT.session,
26166
+ port: "auth-client",
26167
+ run: (send, controls) => call$1("auth-client.get-session", input.ports.authClient.getSession(copyInvocationObservation(controls, {}))).pipe(Effect.tap((session) => Effect.sync(() => {
26168
+ authority.recordProviderSession(session, activeSession?.authUserId);
26169
+ })), Effect.flatMap((session) => sessionProfile(input, session)), Effect.flatMap((loaded) => send({
26170
+ _tag: "SessionRead",
26171
+ ...loaded
26172
+ })), Effect.tapError((failure) => Effect.sync(() => {
26173
+ authority.recordProviderSession(null, failure.code === "NOT_AUTHENTICATED" ? activeSession?.authUserId : void 0);
26174
+ }).pipe(Effect.andThen(activeSession !== null && failure.code === "NOT_AUTHENTICATED" ? Effect.void : send({
26175
+ _tag: "SessionReadFailed",
26176
+ failure
26177
+ })))))
26178
+ };
26179
+ case "SignOut": return {
26180
+ slot: IDENTITY_SLOT.auth,
26181
+ port: "auth-client",
26182
+ run: (send, controls) => call$1("auth-client.sign-out", input.ports.authClient.signOut(copyInvocationObservation(controls, {}))).pipe(Effect.andThen(send({ _tag: "SignedOut" })), Effect.tapError((failure) => send({
26183
+ _tag: "SignOutFailed",
26184
+ failure
26185
+ })), Effect.ensuring(Effect.sync(() => {
26186
+ authority.recordProviderSession(null);
26187
+ })))
26188
+ };
26189
+ case "EnsureAccount":
26190
+ case "RetryAccount": return activeSession === null ? void 0 : {
26191
+ slot: IDENTITY_SLOT.account,
26192
+ port: "smart-account",
26193
+ run: (send) => {
26194
+ const captured = authority.snapshot();
26195
+ return accountLane(input, config, activeSession, send, () => authority.matches(captured, activeSession.authUserId));
26196
+ }
26197
+ };
26198
+ case "ClaimAccount": {
26199
+ const signer = input.signer;
26200
+ const address = state.phase === "authenticated" && state.account.at === "claiming" ? state.account.address : null;
26201
+ return address === null || activeSession === null || signer === void 0 ? void 0 : {
26202
+ slot: IDENTITY_SLOT.account,
26203
+ port: "smart-account",
26204
+ run: (send) => {
26205
+ const captured = authority.snapshot();
26206
+ const isCurrent = () => authority.matches(captured, activeSession.authUserId);
26207
+ return Effect.gen(function* () {
26208
+ const claimed = yield* Effect.result(claimAccount(input, activeSession.authUserId, signer, isCurrent));
26209
+ yield* requireAccountAuthority(isCurrent, send, "deploy");
26210
+ yield* Result.isFailure(claimed) ? send({
26211
+ _tag: "AccountFailed",
26212
+ step: "deploy",
26213
+ failure: claimed.failure
26214
+ }).pipe(Effect.andThen(Effect.fail(claimed.failure))) : send(hasDurableClaim(claimed.success) ? {
26215
+ _tag: "AccountClaimed",
26216
+ address
26217
+ } : {
26218
+ _tag: "AccountDerived",
26219
+ address
26220
+ });
26221
+ });
26222
+ }
26223
+ };
26224
+ }
26225
+ case "CreateOrganization": return input.organizationSetup === void 0 || org === null || org.orgId !== null ? void 0 : {
26226
+ slot: IDENTITY_SLOT.org,
26227
+ port: "organization-setup",
26228
+ run: (send, controls) => beginOrganization$1(input.organizationSetup, event.draft, send, controls)
26229
+ };
26230
+ default: return absurd(event);
26231
+ }
26433
26232
  }
26434
- }
26233
+ };
26234
+ };
26235
+ //#endregion
26236
+ //#region ../../packages/sdk/src/access/identity/authority.ts
26237
+ function createIdentityAuthorityTracker(sessionStore, onGenerationChange = () => void 0) {
26238
+ const invalidationListeners = /* @__PURE__ */ new Set();
26239
+ let generation = 0;
26240
+ let authUserId = null;
26241
+ let stopped = false;
26242
+ let readState = initialIdentityState;
26243
+ const advanceGeneration = () => {
26244
+ generation += 1;
26245
+ authUserId = null;
26246
+ const listeners = [...invalidationListeners];
26247
+ for (const listener of listeners) try {
26248
+ listener();
26249
+ } catch {}
26250
+ try {
26251
+ onGenerationChange();
26252
+ } catch {}
26253
+ };
26254
+ const revoke = () => {
26255
+ if (stopped) return;
26256
+ advanceGeneration();
26257
+ authUserId = null;
26258
+ };
26435
26259
  return {
26436
- items,
26437
- next: {
26438
- ...state,
26439
- paymentCursor: page.cursor,
26440
- paymentDone: page.done,
26441
- paymentPending: payments.slice(pi).map((item) => item.id),
26442
- transferBefore,
26443
- transferDone: ti === transfers.length && transferEnd
26260
+ snapshot: () => ({
26261
+ generation,
26262
+ authUserId
26263
+ }),
26264
+ subscribeInvalidation: (listener) => {
26265
+ if (stopped) {
26266
+ try {
26267
+ listener();
26268
+ } catch {}
26269
+ return () => {};
26270
+ }
26271
+ invalidationListeners.add(listener);
26272
+ return () => {
26273
+ invalidationListeners.delete(listener);
26274
+ };
26275
+ },
26276
+ bindState: (read) => {
26277
+ readState = read;
26278
+ },
26279
+ recordProviderSession: (session, previousAuthUserId) => {
26280
+ if (previousAuthUserId != null && String(session?.authUserId ?? "") !== previousAuthUserId) revoke();
26281
+ sessionStore.current = session;
26282
+ },
26283
+ observe: (state) => {
26284
+ if (stopped) return;
26285
+ const next = state.phase === "authenticated" ? state.session.authUserId : null;
26286
+ if (authUserId !== null && next !== authUserId) advanceGeneration();
26287
+ authUserId = next;
26288
+ },
26289
+ invalidate: () => {
26290
+ if (stopped) return;
26291
+ stopped = true;
26292
+ advanceGeneration();
26293
+ invalidationListeners.clear();
26294
+ authUserId = null;
26295
+ },
26296
+ matches: (captured, expectedAuthUserId) => {
26297
+ if (stopped || generation !== captured.generation || authUserId !== expectedAuthUserId) return false;
26298
+ const state = readState();
26299
+ if (expectedAuthUserId === null) return state.phase !== "authenticated" && sessionStore.current === null;
26300
+ return state.phase === "authenticated" && state.session.authUserId === expectedAuthUserId && String(sessionStore.current?.authUserId ?? "") === expectedAuthUserId;
26444
26301
  }
26445
26302
  };
26446
26303
  }
26447
- function convexPage(deps, actor, controls, input) {
26448
- return callOperation(deps.convexCall, items.list, copyInvocationObservation(controls, {
26449
- ...input,
26450
- actor
26451
- })).pipe(Effect.mapError((publicError) => ({ publicError })));
26452
- }
26453
- function fallbackPage(deps, scope, controls, params, state, degraded) {
26454
- return Effect.gen(function* () {
26455
- const page = yield* convexPage(deps, scope.actor, controls, {
26456
- limit: params?.limit ?? 25,
26457
- ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
26458
- range: {
26459
- ...params?.range?.from === void 0 ? {} : { from: params.range.from },
26460
- to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
26461
- },
26462
- ...params?.filter === void 0 ? {} : { filter: params.filter }
26463
- });
26464
- const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
26465
- kind: item.kind,
26466
- id: item.id
26467
- })) });
26468
- if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
26469
- const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
26470
- yield* Effect.annotateCurrentSpan({
26471
- read_source: "convex",
26472
- ...degraded ? { read_warning: "indexer-unavailable" } : {}
26473
- });
26474
- return {
26475
- ...page,
26476
- items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
26477
- checkpoint: state.checkpoint,
26478
- nextCursor: page.nextCursor === null ? null : JSON.stringify({
26479
- ...state,
26480
- source: "fallback",
26481
- paymentCursor: page.nextCursor,
26482
- degraded
26483
- }),
26484
- ...degraded ? { readWarning: "indexer-unavailable" } : {}
26485
- };
26486
- });
26487
- }
26488
- function readActivityPage(deps, actor, controls, params) {
26489
- return Effect.gen(function* () {
26490
- yield* Effect.try({
26491
- try: () => requireParams(params),
26492
- catch: (cause) => ({ publicError: asCapxulError(cause) })
26493
- });
26494
- const scope = yield* overlay(deps, actor, controls, {});
26495
- const selection = selectionKey(params);
26496
- const cursor = yield* Effect.try({
26497
- try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
26498
- catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
26499
- });
26500
- const state = cursor ?? {
26501
- version: 1,
26502
- scope: scopeKey(scope),
26503
- selection,
26504
- checkpoint: Date.now(),
26505
- source: "indexed",
26506
- paymentCursor: null,
26507
- paymentDone: false,
26508
- paymentPending: [],
26509
- transferBefore: null,
26510
- transferDone: false,
26511
- degraded: false
26304
+ //#endregion
26305
+ //#region ../../packages/sdk/src/platform/actor/transitions.ts
26306
+ /**
26307
+ * Who hears an actor: the boot observer, transition and state subscribers, and
26308
+ * the defect reporter. An observer that throws is dropped and reported as a
26309
+ * defect; it never owns actor truth.
26310
+ */
26311
+ const makeActorObservers = (options) => {
26312
+ const stateObservers = /* @__PURE__ */ new Set();
26313
+ const transitionObservers = /* @__PURE__ */ new Set();
26314
+ let bootTransitionObserver = options.onTransition;
26315
+ const defect = (cause) => {
26316
+ try {
26317
+ options.onDefect?.(cause);
26318
+ } catch {}
26319
+ };
26320
+ const emit = (record) => Effect.sync(() => {
26321
+ if (bootTransitionObserver !== void 0) try {
26322
+ bootTransitionObserver(record);
26323
+ } catch (cause) {
26324
+ bootTransitionObserver = void 0;
26325
+ defect(cause);
26326
+ }
26327
+ for (const observer of transitionObservers) try {
26328
+ observer(record);
26329
+ } catch (cause) {
26330
+ transitionObservers.delete(observer);
26331
+ defect(cause);
26332
+ }
26333
+ }).pipe(Effect.andThen(logIdentityTransition(record)));
26334
+ const notify = (state) => {
26335
+ for (const observer of stateObservers) try {
26336
+ observer(state);
26337
+ } catch (cause) {
26338
+ stateObservers.delete(observer);
26339
+ defect(cause);
26340
+ }
26341
+ };
26342
+ return {
26343
+ stateObservers,
26344
+ transitionObservers,
26345
+ defect,
26346
+ emit,
26347
+ notify
26348
+ };
26349
+ };
26350
+ /** One machine's transition records and the failure a refused or failed reply carries. */
26351
+ const makeTransitionRecords = (spec, slotEpochs) => {
26352
+ const makeFailure = (state, event, reason, details, cause) => new ActorFailure(reason, spec.machine, spec.label(state), event._tag, details, cause);
26353
+ const nonApplied = (state, event, reason, outcome, env, failure) => {
26354
+ const slot = env.origin === null ? spec.slot(env.event) : env.origin.slot;
26355
+ const base = {
26356
+ machine: spec.machine,
26357
+ state: spec.label(state),
26358
+ event: event._tag,
26359
+ slot,
26360
+ epoch: env.origin?.epoch ?? slotEpochs.get(slot) ?? 0,
26361
+ duration_ms: duration(env.startedAt),
26362
+ ...withCarriage(env.invocation)
26512
26363
  };
26513
- const range = boundedRange(params?.range, state.checkpoint);
26514
- if (range.from !== void 0 && range.from > range.to) return {
26515
- items: [],
26516
- nextCursor: null,
26517
- checkpoint: state.checkpoint,
26518
- observedAt: Date.now()
26364
+ const record = outcome === "refused" ? {
26365
+ ...base,
26366
+ outcome,
26367
+ refusal_code: reason
26368
+ } : {
26369
+ ...base,
26370
+ outcome,
26371
+ error_code: reason,
26372
+ ...failure === void 0 ? {} : { failure }
26519
26373
  };
26520
- if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
26521
- return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
26374
+ return copyInvocationObservation(env.invocation, record);
26375
+ };
26376
+ const applied = (from, to, event, slot, epoch, env) => copyInvocationObservation(env.invocation, {
26377
+ machine: spec.machine,
26378
+ from: spec.label(from),
26379
+ event: event._tag,
26380
+ to: spec.label(to),
26381
+ slot,
26382
+ epoch,
26383
+ outcome: "applied",
26384
+ duration_ms: duration(env.startedAt),
26385
+ ...withCarriage(env.invocation)
26522
26386
  });
26523
- }
26524
- function readActivitySummary(deps, actor, controls, params) {
26525
- return Effect.gen(function* () {
26526
- const totals = /* @__PURE__ */ new Map();
26527
- let cursor;
26528
- let count = 0;
26529
- let readWarning;
26530
- do {
26531
- const page = yield* readActivityPage(deps, actor, controls, {
26532
- limit: 100,
26533
- ...cursor === void 0 ? {} : { cursor },
26534
- ...params?.window === void 0 ? {} : { range: params.window }
26387
+ return {
26388
+ makeFailure,
26389
+ nonApplied,
26390
+ applied
26391
+ };
26392
+ };
26393
+ //#endregion
26394
+ //#region ../../packages/sdk/src/platform/actor/lane-exit.ts
26395
+ /**
26396
+ * How a work lane's exit settles its reply: finished, failed, cancelled, or
26397
+ * died. A timeout, cancellation or death first lets the machine recover its
26398
+ * own state through `spec.recoverFailure`.
26399
+ */
26400
+ const makeLaneSettlement = (actor) => {
26401
+ const { spec, cell, slots } = actor;
26402
+ const { emit, notify, defect } = actor.observers;
26403
+ const { makeFailure, nonApplied, applied } = actor.records;
26404
+ const finish = (cursor, state) => {
26405
+ const held = slots.get(cursor.slot);
26406
+ if (held === void 0 || held.cursor !== cursor) return Effect.void;
26407
+ slots.delete(cursor.slot);
26408
+ return Effect.asVoid(Deferred.succeed(held.reply, state));
26409
+ };
26410
+ const recoverFailure = (state, cursor, failure) => {
26411
+ const event = spec.recoverFailure?.(state, cursor.event, failure);
26412
+ if (event === void 0) return Effect.succeed(state);
26413
+ const outcome = spec.transition(state, event);
26414
+ if ("refused" in outcome) return Effect.succeed(state);
26415
+ return Ref.set(cell, outcome.next).pipe(Effect.tap(() => {
26416
+ const record = applied(state, outcome.next, event, cursor.slot, cursor.epoch, {
26417
+ invocation: cursor.invocation,
26418
+ startedAt: cursor.startedAt
26535
26419
  });
26536
- count += page.items.length;
26537
- readWarning ??= page.readWarning;
26538
- for (const total of summarizeItems(page.items)) {
26539
- const key = total.asset;
26540
- const old = totals.get(key);
26541
- totals.set(key, old === void 0 ? total : {
26542
- ...total,
26543
- in: String(BigInt(old.in) + BigInt(total.in)),
26544
- out: String(BigInt(old.out) + BigInt(total.out)),
26545
- net: String(BigInt(old.net) + BigInt(total.net)),
26546
- pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
26547
- });
26548
- }
26549
- cursor = page.nextCursor ?? void 0;
26550
- } while (cursor !== void 0);
26551
- return {
26552
- window: {
26553
- from: params?.window?.from ?? null,
26554
- to: params?.window?.to ?? null
26555
- },
26556
- totals: [...totals.values()],
26557
- count,
26558
- observedAt: Date.now(),
26559
- ...readWarning === void 0 ? {} : { readWarning }
26560
- };
26561
- });
26562
- }
26563
- function subscribeActivity(deps, actor, controls, onChange) {
26564
- return Effect.gen(function* () {
26565
- const scope = yield* overlay(deps, actor, controls, {});
26566
- if (scope.safe === null) return () => {};
26567
- let delivered = false;
26568
- return yield* deps.indexer.subscribe("transfer", {
26569
- safe: toAddress(scope.safe),
26570
- limit: 100
26571
- }, (snapshot) => {
26572
- if (snapshot.status === "ok") {
26573
- if (delivered) onChange();
26574
- delivered = true;
26575
- }
26576
- if (snapshot.status === "error") {
26577
- delivered = true;
26578
- onChange();
26579
- }
26580
- });
26420
+ return emit(record).pipe(Effect.andThen(Effect.sync(() => {
26421
+ notify(outcome.next);
26422
+ })));
26423
+ }), Effect.as(outcome.next));
26424
+ };
26425
+ return (env, origin, exit, state) => Effect.gen(function* () {
26426
+ const held = slots.get(origin.slot);
26427
+ if (held === void 0 || held.cursor !== origin) return;
26428
+ if (exit.defect !== void 0) {
26429
+ slots.delete(origin.slot);
26430
+ defect(exit.defect);
26431
+ const event = { _tag: origin.event };
26432
+ const died = {
26433
+ code: "WORK_DIED",
26434
+ message: exit.defect instanceof Error ? exit.defect.message : "Identity work died"
26435
+ };
26436
+ const failedState = yield* recoverFailure(state, origin, died);
26437
+ yield* emit(nonApplied(failedState, event, "WORK_DIED", "failed", env, died));
26438
+ yield* failReply(held.reply, makeFailure(failedState, event, "WORK_DIED", void 0, died));
26439
+ return;
26440
+ }
26441
+ if (exit.failure !== void 0) {
26442
+ slots.delete(origin.slot);
26443
+ const outcome = exit.failure.code === "CANCELLED" ? "cancelled" : "failed";
26444
+ const event = { _tag: origin.event };
26445
+ const failedState = exit.failure === timeoutFailure || exit.failure === cancelledFailure ? yield* recoverFailure(state, origin, exit.failure) : state;
26446
+ yield* emit(nonApplied(failedState, event, exit.failure.code, outcome, env, exit.failure));
26447
+ const details = exit.failure === timeoutFailure ? { reason: "timeout" } : refusedInputField(exit.failure);
26448
+ yield* failReply(held.reply, makeFailure(failedState, event, exit.failure.code, details, exit.failure.error ?? exit.failure));
26449
+ return;
26450
+ }
26451
+ yield* finish(origin, state);
26581
26452
  });
26582
- }
26453
+ };
26583
26454
  //#endregion
26584
- //#region ../../packages/sdk/src/activity/movement-detail.ts
26585
- /** An indexed Movement has no Convex materialization. Read its exact native row. */
26586
- function readIndexedMovement(deps, actor, controls, eventId) {
26587
- return Effect.gen(function* () {
26588
- const scope = yield* overlay(deps, actor, controls, { references: [{
26589
- kind: "movement",
26590
- id: eventId
26591
- }] });
26592
- if (scope.safe === null) return null;
26593
- const safe = scope.safe.toLowerCase();
26594
- const row = (yield* deps.indexer.read("transfer", {
26595
- safe: toAddress(safe),
26596
- limit: 1,
26597
- eventId
26598
- }).pipe(Effect.mapError((error) => ({ publicError: error.publicError }))))[0];
26599
- if (row === void 0) return null;
26600
- const blockNumber = Number(row.blockNumber);
26601
- if (row.id !== eventId || row.from.toLowerCase() !== safe && row.to.toLowerCase() !== safe || !Number.isSafeInteger(blockNumber) || blockNumber < 0 || !Number.isSafeInteger(row.logIndex) || row.logIndex < 0) return yield* Effect.fail({ publicError: Errors.invalidInput("activity", "indexed Movement does not match the requested scope or evidence") });
26602
- yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
26603
- const documentSet = scope.movementDocuments.find((set) => set.movementId === row.id);
26604
- return {
26605
- kind: "movement",
26606
- movementId: row.id,
26607
- documents: documentSet?.documents ?? [],
26608
- documentState: documentSet?.state ?? "pending",
26609
- viewer: scope.actor,
26610
- annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
26611
- evidence: {
26612
- chainId: row.chainId,
26613
- contractAddress: row.token,
26614
- tokenAddress: row.token,
26615
- transactionHash: row.txHash,
26616
- blockNumber,
26617
- blockHash: row.blockHash,
26618
- inclusion: "included",
26619
- verification: "verified",
26620
- canonicalState: "finalized",
26621
- transferLogIndex: row.logIndex,
26622
- source: row.from,
26623
- destination: row.to,
26624
- rawAmount: row.amount.toString(),
26625
- link: {
26626
- movementId: row.id,
26627
- depositPaymentId: null
26628
- }
26629
- }
26455
+ //#region ../../packages/sdk/src/platform/actor/shell.ts
26456
+ const boot = (spec, options = {}) => Effect.gen(function* () {
26457
+ const mailbox = yield* Queue.unbounded();
26458
+ const cell = yield* Ref.make(spec.initial);
26459
+ const slotEpochs = /* @__PURE__ */ new Map();
26460
+ const slots = /* @__PURE__ */ new Map();
26461
+ const { stateObservers, transitionObservers, defect, emit, notify } = makeActorObservers(options);
26462
+ const { makeFailure, nonApplied, applied } = makeTransitionRecords(spec, slotEpochs);
26463
+ let stopped = false;
26464
+ const settleLaneExit = makeLaneSettlement({
26465
+ spec,
26466
+ cell,
26467
+ slots,
26468
+ observers: {
26469
+ emit,
26470
+ notify,
26471
+ defect
26472
+ },
26473
+ records: {
26474
+ makeFailure,
26475
+ nonApplied,
26476
+ applied
26477
+ }
26478
+ });
26479
+ const step = (env) => Effect.gen(function* () {
26480
+ const state = yield* Ref.get(cell);
26481
+ if (env.origin !== null && (slotEpochs.get(env.origin.slot) ?? 0) !== env.origin.epoch) {
26482
+ const held = slots.get(env.origin.slot);
26483
+ if (held === void 0 || held.cursor !== env.origin) return;
26484
+ slots.delete(env.origin.slot);
26485
+ const event = { _tag: env.origin.event };
26486
+ yield* emit(nonApplied(state, event, "STALE_EPOCH", "refused", env));
26487
+ yield* failReply(held.reply, makeFailure(state, event, "STALE_EPOCH"));
26488
+ return;
26489
+ }
26490
+ if (isLaneExitEnvelope(env)) {
26491
+ yield* settleLaneExit(env, env.origin, env.event, state);
26492
+ return;
26493
+ }
26494
+ const outcome = spec.transition(state, env.event);
26495
+ if ("refused" in outcome) {
26496
+ yield* emit(nonApplied(state, env.event, outcome.refused, "refused", env));
26497
+ yield* failReply(env.reply, makeFailure(state, env.event, outcome.refused));
26498
+ return;
26499
+ }
26500
+ const next = outcome.next;
26501
+ yield* Ref.set(cell, next);
26502
+ const emitApplied = (slot, epoch) => emit(applied(state, next, env.event, slot, epoch, env));
26503
+ if (env.origin !== null) {
26504
+ yield* emitApplied(env.origin.slot, env.origin.epoch);
26505
+ notify(next);
26506
+ return;
26507
+ }
26508
+ for (const slot of spec.invalidates?.(env.event) ?? []) {
26509
+ if (!slots.has(slot)) continue;
26510
+ slotEpochs.set(slot, (slotEpochs.get(slot) ?? 0) + 1);
26511
+ }
26512
+ const work = env.reply === null ? void 0 : spec.work?.({
26513
+ state: next,
26514
+ event: env.event
26515
+ });
26516
+ if (work === void 0 || env.reply === null) {
26517
+ const slot = spec.slot(env.event);
26518
+ yield* emitApplied(slot, slotEpochs.get(slot) ?? 0);
26519
+ notify(next);
26520
+ if (env.reply !== null) yield* Deferred.succeed(env.reply, next).pipe(Effect.asVoid);
26521
+ return;
26522
+ }
26523
+ const previous = slots.get(work.slot);
26524
+ if (previous !== void 0) {
26525
+ slots.delete(work.slot);
26526
+ yield* Fiber.interrupt(previous.fiber);
26527
+ const previousEvent = { _tag: previous.cursor.event };
26528
+ const reason = previous.cursor.epoch === (slotEpochs.get(work.slot) ?? 0) ? "SUPERSEDED" : "STALE_EPOCH";
26529
+ yield* emit(nonApplied(next, previousEvent, reason, "refused", {
26530
+ origin: previous.cursor,
26531
+ invocation: previous.cursor.invocation,
26532
+ startedAt: previous.cursor.startedAt
26533
+ }));
26534
+ yield* failReply(previous.reply, makeFailure(next, previousEvent, reason));
26535
+ }
26536
+ const epoch = (slotEpochs.get(work.slot) ?? 0) + 1;
26537
+ slotEpochs.set(work.slot, epoch);
26538
+ const cursor = {
26539
+ slot: work.slot,
26540
+ epoch,
26541
+ event: env.event._tag,
26542
+ startedAt: env.startedAt,
26543
+ invocation: env.invocation
26630
26544
  };
26545
+ yield* emitApplied(work.slot, epoch);
26546
+ notify(next);
26547
+ const send = (event) => Queue.offer(mailbox, {
26548
+ event,
26549
+ reply: null,
26550
+ origin: cursor,
26551
+ invocation: env.invocation,
26552
+ startedAt: env.startedAt
26553
+ }).pipe(Effect.asVoid);
26554
+ const complete = (event) => Queue.offer(mailbox, {
26555
+ event,
26556
+ reply: null,
26557
+ origin: cursor,
26558
+ invocation: env.invocation,
26559
+ startedAt: env.startedAt
26560
+ }).pipe(Effect.asVoid);
26561
+ const guarded = Effect.gen(function* () {
26562
+ yield* Effect.annotateCurrentSpan({
26563
+ slot: work.slot,
26564
+ epoch,
26565
+ port: work.port
26566
+ });
26567
+ return yield* control(work.run(send, env.invocation), env.invocation);
26568
+ }).pipe(Effect.withSpan(`identity.work.${work.slot}`)).pipe(Effect.matchEffect({
26569
+ onFailure: (failure) => complete(laneExit({ failure })),
26570
+ onSuccess: () => complete(laneExit({}))
26571
+ }), Effect.catchDefect((cause) => complete(laneExit({ defect: cause }))));
26572
+ const fiber = yield* Effect.forkChild(guarded, { startImmediately: true });
26573
+ slots.set(work.slot, {
26574
+ fiber,
26575
+ reply: env.reply,
26576
+ cursor
26577
+ });
26631
26578
  });
26632
- }
26633
- //#endregion
26634
- //#region ../../packages/sdk/src/activity/scope.ts
26635
- /**
26636
- * The activity backend gives its actor a different name than the other
26637
- * financialOps functions: `{kind: "organization", orgId}`, and not
26638
- * `{kind: "org", orgId}`. Thus it does not use `actorReferenceToBackend`.
26639
- *
26640
- * The `personal` case sends NO actor. This is correct, and it does not lose
26641
- * data. `requireActivityActor` uses the signed-in Account when the actor is
26642
- * absent, and `canReadActor` refuses all other account identifiers
26643
- * (`packages/backend/convex/movement/activityAuth.ts`). Thus "personal" and
26644
- * "absent" both name the one Account that an SDK caller can read. The SDK does
26645
- * not hold an account identifier to send.
26646
- *
26647
- * The `switch` is exhaustive. A new `ActorReference` variant is a compile error
26648
- * here. The conditional expression that this function replaced removed each
26649
- * value that was not an organization.
26650
- */
26651
- function activityActorField(actor) {
26652
- if (actor === void 0 || actor.kind === "personal") return {};
26653
- return { actor: {
26654
- kind: "organization",
26655
- orgId: actor.organizationId
26656
- } };
26657
- }
26658
- /** Capture scope now; let the observed result boundary handle a malformed caller value. */
26659
- function captureActivityInput(prepare) {
26660
- try {
26661
- return prepare();
26662
- } catch (cause) {
26663
- return Effect.die(cause);
26664
- }
26665
- }
26666
- function observeActivityScope(actor) {
26667
- const organizationId = actor?.kind === "organization" || actor?.kind === "org" ? safeEngineeringIdentifier(actor.orgId) : void 0;
26668
- return Effect.annotateCurrentSpan({
26669
- scope_kind: actor?.kind === "organization" || actor?.kind === "org" ? "organization" : "account",
26670
- ...organizationId === void 0 ? {} : { verified_organization_id: organizationId }
26671
- });
26672
- }
26673
- function decodeActivityViewer(value) {
26674
- const viewer = Option.getOrUndefined(Schema.decodeUnknownOption(ActorRefSchema)(value));
26675
- if (viewer === void 0) return void 0;
26676
- const id = viewer.kind === "organization" ? viewer.orgId : viewer.accountId;
26677
- return safeEngineeringIdentifier(id) === void 0 ? void 0 : viewer;
26678
- }
26679
- /** The detail without the scope it was read for, which the SDK reports as telemetry instead. */
26680
- function normalizeActivityDetail(response) {
26681
- if (response === null) return null;
26682
- const { viewer: _viewer, ...detail } = response;
26683
- return detail;
26684
- }
26685
- //#endregion
26686
- //#region ../../packages/sdk/src/activity/methods.ts
26687
- function makeActivityMethods(deps) {
26688
- const indexerDeps = deps.indexerRead === void 0 ? void 0 : {
26689
- indexer: deps.indexerRead,
26690
- convexCall: deps.convexCall
26691
- };
26692
- /** One public call for the caller's chosen actor, read once at call start. */
26693
- const forActor = (actorRef, options, operation, program) => {
26694
- const controls = deps.invocationControls?.(options) ?? options;
26695
- return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(actorRef).actor)).pipe(Effect.flatMap((actor) => program(actor, controls))), controls, operation, deps.runPromise);
26579
+ const loop = yield* Effect.forkScoped(Effect.forever(Queue.take(mailbox).pipe(Effect.flatMap((env) => withInvocationParentSpan(step(env), env.invocation)))));
26580
+ yield* Effect.addFinalizer(() => Effect.gen(function* () {
26581
+ stopped = true;
26582
+ yield* Fiber.interrupt(loop);
26583
+ const state = yield* Ref.get(cell);
26584
+ const outstanding = [...slots.values()];
26585
+ slots.clear();
26586
+ for (const held of outstanding) {
26587
+ yield* Fiber.interrupt(held.fiber);
26588
+ const env = {
26589
+ origin: held.cursor,
26590
+ invocation: held.cursor.invocation,
26591
+ startedAt: held.cursor.startedAt
26592
+ };
26593
+ const event = { _tag: held.cursor.event };
26594
+ yield* emit(nonApplied(state, event, "ACTOR_STOPPED", "refused", env));
26595
+ yield* failReply(held.reply, makeFailure(state, event, "ACTOR_STOPPED"));
26596
+ }
26597
+ stateObservers.clear();
26598
+ transitionObservers.clear();
26599
+ yield* Queue.shutdown(mailbox);
26600
+ }));
26601
+ const offer = (event, reply, invocation) => {
26602
+ const state = Ref.getUnsafe(cell);
26603
+ if (stopped) return withInvocationParentSpan(emit(nonApplied(state, event, "ACTOR_STOPPED", "refused", {
26604
+ origin: null,
26605
+ event,
26606
+ invocation,
26607
+ startedAt: Date.now()
26608
+ })).pipe(Effect.andThen(Effect.fail(makeFailure(state, event, "ACTOR_STOPPED")))), invocation);
26609
+ const startedAt = Date.now();
26610
+ return Queue.offer(mailbox, {
26611
+ event,
26612
+ reply,
26613
+ origin: null,
26614
+ invocation,
26615
+ startedAt
26616
+ }).pipe(Effect.asVoid);
26696
26617
  };
26697
26618
  return {
26698
- subscribe: (onChange, params, options) => forActor(params?.actor, options, CAPXUL_OPERATIONS.activity.subscribe, (actor, controls) => indexerDeps === void 0 ? Effect.succeed(() => {}) : Effect.suspend(() => subscribeActivity(indexerDeps, actor, controls, onChange)).pipe(Effect.tap(() => observeActivityScope(actor)))),
26699
- list: (params, options) => forActor(params?.actor, options, items.list.name, (actor, controls) => Effect.suspend(() => indexerDeps === void 0 ? callOperation(deps.convexCall, items.list, copyInvocationObservation(controls, {
26700
- ...actor === void 0 ? {} : { actor },
26701
- ...params?.cursor === void 0 ? {} : { cursor: params.cursor },
26702
- ...params?.limit === void 0 ? {} : { limit: params.limit },
26703
- ...params?.range === void 0 ? {} : { range: params.range },
26704
- ...params?.filter === void 0 ? {} : { filter: params.filter }
26705
- })).pipe(Effect.mapError((publicError) => ({ publicError }))) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
26706
- ...page,
26707
- items: page.items.filter(isActivityVisible)
26708
- })), Effect.tap(() => observeActivityScope(actor)))),
26709
- summary: (params, options) => forActor(params?.actor, options, items.summary.name, (actor, controls) => Effect.suspend(() => indexerDeps === void 0 ? callOperation(deps.convexCall, items.summary, copyInvocationObservation(controls, {
26710
- ...actor === void 0 ? {} : { actor },
26711
- ...params?.window === void 0 ? {} : { window: params.window }
26712
- })).pipe(Effect.mapError((publicError) => ({ publicError }))) : readActivitySummary(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor)))),
26713
- get: (reference, options) => {
26714
- const controls = deps.invocationControls?.(options) ?? options;
26715
- const requestedActor = actorReferenceToBackend(options?.actor);
26716
- return runPortEffect(captureActivityInput(() => {
26717
- const scope = requestedActor === void 0 ? void 0 : requestedActor.kind === "org" ? {
26718
- kind: "org",
26719
- orgId: requestedActor.orgId
26720
- } : { kind: "account" };
26721
- return Effect.succeed({
26722
- actor: scope,
26723
- referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
26724
- });
26725
- }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => indexerDeps !== void 0 && reference.kind === "movement" && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(reference.id) ? readIndexedMovement(indexerDeps, actor?.kind === "org" ? {
26726
- kind: "organization",
26727
- orgId: actor.orgId
26728
- } : void 0, controls, reference.id) : callOperation(deps.convexCall, items.get, copyInvocationObservation(controls, {
26729
- reference: {
26730
- kind: reference.kind,
26731
- id: reference.id,
26732
- ...referenceActor === void 0 ? {} : { actor: referenceActor }
26733
- },
26734
- ...actor === void 0 ? {} : { actor }
26735
- })).pipe(Effect.mapError((publicError) => ({ publicError })))).pipe(Effect.flatMap((response) => {
26736
- const detail = normalizeActivityDetail(response);
26737
- if (response === null) return Effect.succeed(detail);
26738
- const viewer = response.viewer === void 0 ? actor ?? referenceActor : decodeActivityViewer(response.viewer);
26739
- return viewer === void 0 ? Effect.succeed(detail) : observeActivityScope(viewer).pipe(Effect.as(detail));
26740
- })))), controls, items.get.name, deps.runPromise);
26619
+ ask: (event, invocation = {}) => Effect.gen(function* () {
26620
+ const reply = yield* Deferred.make();
26621
+ yield* offer(event, reply, invocation);
26622
+ return yield* Deferred.await(reply);
26623
+ }),
26624
+ tell: (event, invocation = {}) => offer(event, null, invocation),
26625
+ snapshot: () => Ref.getUnsafe(cell),
26626
+ subscribe: (observer) => {
26627
+ stateObservers.add(observer);
26628
+ return () => {
26629
+ stateObservers.delete(observer);
26630
+ };
26741
26631
  },
26742
- annotate: (input, options) => {
26743
- const controls = deps.invocationControls?.(options) ?? options;
26744
- return runPortEffect(captureActivityInput(() => {
26745
- const actor = activityActorField(input.actor).actor;
26746
- if (input.actor !== void 0 && input.reference.kind === "payment" && input.reference.actor !== void 0 && !sameActor(input.actor, input.reference.actor)) return Effect.fail({ publicError: Errors.invalidInput("reference.actor", "must match the annotation actor") });
26747
- return Effect.succeed({
26748
- actor,
26749
- referenceActor: input.reference.kind === "payment" ? activityActorField(input.reference.actor).actor : void 0
26750
- });
26751
- }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => callOperation(deps.convexCall, annotations.save, copyInvocationObservation(controls, {
26752
- ...actor === void 0 ? {} : { actor },
26753
- reference: input.reference.kind === "payment" && referenceActor !== void 0 ? {
26754
- kind: "payment",
26755
- id: input.reference.id,
26756
- actor: referenceActor
26757
- } : {
26758
- kind: input.reference.kind,
26759
- id: input.reference.id
26760
- },
26761
- ...input.counterpartyLabel === void 0 ? {} : { counterpartyLabel: input.counterpartyLabel },
26762
- ...input.accountingCategory === void 0 ? {} : { accountingCategory: input.accountingCategory },
26763
- ...input.memo === void 0 ? {} : { memo: input.memo }
26764
- })).pipe(Effect.mapError((publicError) => ({ publicError })))).pipe(Effect.tap((annotation) => {
26765
- const scope = decodeActivityViewer(annotation.actor);
26766
- return scope === void 0 ? Effect.void : observeActivityScope(scope);
26767
- })))), controls, annotations.save.name, deps.runPromise);
26632
+ subscribeTransitions: (observer) => {
26633
+ transitionObservers.add(observer);
26634
+ return () => {
26635
+ transitionObservers.delete(observer);
26636
+ };
26768
26637
  }
26769
26638
  };
26770
- }
26771
- Object.freeze({
26772
- terminal: false,
26773
- outcome: null,
26774
- needsAction: false
26775
26639
  });
26776
- Object.freeze({
26777
- terminal: false,
26778
- outcome: null,
26779
- needsAction: true
26780
- });
26781
- Object.freeze({
26782
- terminal: true,
26783
- outcome: "good",
26784
- needsAction: false
26640
+ //#endregion
26641
+ //#region ../../packages/sdk/src/access/identity/boot.ts
26642
+ const initialIdentityState = () => RESTORING;
26643
+ const bootIdentityFlow = (input, options = {}) => Effect.gen(function* () {
26644
+ const sessionStore = { current: null };
26645
+ let invalidateOrganizationSubscriptions = () => input.organizationSetup?.invalidateSubscriptions?.();
26646
+ const authority = createIdentityAuthorityTracker(sessionStore, () => invalidateOrganizationSubscriptions());
26647
+ const actor = yield* boot(identitySpec(input, sessionStore, authority), options);
26648
+ authority.bindState(actor.snapshot);
26649
+ const runPromise = input.runPromise ?? Effect.runPromise;
26650
+ let selectedOrgId = null;
26651
+ let stopRemote = null;
26652
+ let selectionEpoch = 0;
26653
+ invalidateOrganizationSubscriptions = () => {
26654
+ selectionEpoch += 1;
26655
+ const stop = stopRemote;
26656
+ stopRemote = null;
26657
+ try {
26658
+ stop?.();
26659
+ } finally {
26660
+ input.organizationSetup?.invalidateSubscriptions?.();
26661
+ }
26662
+ };
26663
+ const observe = (state) => {
26664
+ authority.observe(state);
26665
+ const nextOrgId = state.phase === "authenticated" && state.account.at === "claimed" ? state.account.org?.orgId ?? null : null;
26666
+ if (nextOrgId === selectedOrgId) return;
26667
+ selectedOrgId = nextOrgId;
26668
+ selectionEpoch += 1;
26669
+ const epoch = selectionEpoch;
26670
+ stopRemote?.();
26671
+ stopRemote = null;
26672
+ if (nextOrgId === null || input.organizationSetup === void 0) return;
26673
+ const capturedLifetime = authority.snapshot();
26674
+ const expectedAuthUserId = state.phase === "authenticated" ? state.session.authUserId : null;
26675
+ const emit = (snapshot) => {
26676
+ if (epoch !== selectionEpoch || !authority.matches(capturedLifetime, expectedAuthUserId)) return;
26677
+ runPromise(actor.tell({
26678
+ _tag: "ObserveOrganization",
26679
+ orgId: nextOrgId,
26680
+ snapshot
26681
+ })).catch(() => void 0);
26682
+ };
26683
+ try {
26684
+ const orgId = toOrgId(nextOrgId);
26685
+ input.organizationSetup.subscribe(orgId, emit).then((result) => {
26686
+ if (epoch !== selectionEpoch) {
26687
+ if (result.ok) result.value();
26688
+ return;
26689
+ }
26690
+ if (result.ok) stopRemote = result.value;
26691
+ else emit({
26692
+ status: "error",
26693
+ error: result.error
26694
+ });
26695
+ }).catch((cause) => emit({
26696
+ status: "error",
26697
+ error: Errors.unknown(cause)
26698
+ }));
26699
+ } catch (cause) {
26700
+ emit({
26701
+ status: "error",
26702
+ error: Errors.unknown(cause)
26703
+ });
26704
+ }
26705
+ };
26706
+ const unsubscribeAuthority = actor.subscribe(observe);
26707
+ observe(actor.snapshot());
26708
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
26709
+ authority.invalidate();
26710
+ unsubscribeAuthority();
26711
+ }));
26712
+ return {
26713
+ ...actor,
26714
+ authLifetime: authority.snapshot,
26715
+ matchesAuthLifetime: authority.matches,
26716
+ invalidateAuthLifetime: authority.invalidate,
26717
+ subscribeAuthInvalidation: authority.subscribeInvalidation,
26718
+ authSession: () => sessionStore.current,
26719
+ ask: (event, controls) => actor.ask(event, controls).pipe(Effect.tap(() => event._tag === "Reset" ? Effect.sync(() => {
26720
+ authority.recordProviderSession(null);
26721
+ }) : Effect.void)),
26722
+ restoreAuthSession: (session, controls) => actor.ask({
26723
+ _tag: actor.snapshot().phase === "restoring" ? "SessionRestored" : "RestoreSession",
26724
+ session: modelSession(session),
26725
+ profileComplete: false
26726
+ }, controls).pipe(Effect.tap(() => Effect.sync(() => {
26727
+ authority.recordProviderSession(session);
26728
+ })))
26729
+ };
26785
26730
  });
26786
- Object.freeze({
26787
- terminal: true,
26788
- outcome: "bad",
26789
- needsAction: false
26731
+ //#endregion
26732
+ //#region ../../packages/sdk/src/access/identity/restoration-observation.ts
26733
+ /** The correlation a transition carries onto its product event. */
26734
+ const linkage = (record) => ({
26735
+ ...record.correlation_id === void 0 ? {} : { correlation_id: record.correlation_id },
26736
+ ...record.journey_id === void 0 ? {} : { journey_id: record.journey_id }
26790
26737
  });
26791
- Object.freeze({
26792
- terminal: true,
26793
- outcome: "neutral",
26794
- needsAction: false
26738
+ /**
26739
+ * The label of the restore window every client boots into. A settled session
26740
+ * fact that left this label is the restoration result (#1962 R04); the same
26741
+ * facts from a signed-out or authenticated state are the routine session probe
26742
+ * that `getSession()` runs, and a probe is not a restoration attempt.
26743
+ */
26744
+ const RESTORE_WINDOW = "restoring";
26745
+ /** The completions that settle the restore window by themselves, and what each says. */
26746
+ const SETTLING_EVENTS = {
26747
+ SessionRestored: "restored",
26748
+ SessionAbsent: "absent",
26749
+ SessionReadFailed: "failed"
26750
+ };
26751
+ /**
26752
+ * Which result the window settled on. `SessionRestored` and `SessionAbsent` are
26753
+ * the cache door and say it themselves. `ReadSession` is also valid inside the
26754
+ * window — `getSession()` drives it on a cache miss and its `SessionRead`
26755
+ * completion settles the window — so that one event reports both halves and the
26756
+ * state it produced is what separates them.
26757
+ */
26758
+ const restorationOutcome = (event, state) => {
26759
+ if (event === "SessionRead") {
26760
+ if (state.phase === "authenticated") return "restored";
26761
+ return state.phase === "signed_out" ? "absent" : void 0;
26762
+ }
26763
+ return SETTLING_EVENTS[event];
26764
+ };
26765
+ /**
26766
+ * The restore window's one result, or nothing when this settled session fact
26767
+ * belongs to a probe rather than to the window.
26768
+ */
26769
+ const restorationObservation = (record, event, state) => {
26770
+ if (record.slot !== "identity:session" || record.from !== RESTORE_WINDOW) return null;
26771
+ const outcome = restorationOutcome(event, state);
26772
+ if (outcome === void 0) return null;
26773
+ return {
26774
+ name: "access_recovery_completed",
26775
+ props: {
26776
+ recovery: "session_restoration",
26777
+ outcome,
26778
+ ...outcome === "failed" && state.phase === "faulted" ? { reason_code: state.failure.code } : {},
26779
+ ...linkage(record)
26780
+ }
26781
+ };
26782
+ };
26783
+ //#endregion
26784
+ //#region ../../packages/sdk/src/access/identity/events.ts
26785
+ const NONE = null;
26786
+ const IDENTITY_EVENT_TAG_SET = new Set(IDENTITY_EVENT_TAGS);
26787
+ const isIdentityEventTag = (event) => IDENTITY_EVENT_TAG_SET.has(event);
26788
+ const APPLIED_ACTION_BY_EVENT = {
26789
+ RequestOtp: "none",
26790
+ VerifyOtp: "none",
26791
+ ReadSession: "none",
26792
+ SignOut: "none",
26793
+ RestoreSession: "none",
26794
+ ResumeOtpEntry: "none",
26795
+ RestoreOtpEntry: "none",
26796
+ Reset: "none",
26797
+ SessionRestored: "restoration",
26798
+ SessionAbsent: "restoration",
26799
+ EnsureAccount: "none",
26800
+ ClaimAccount: "none",
26801
+ RetryAccount: "none",
26802
+ CreateOrganization: "none",
26803
+ AttachOrganization: "none",
26804
+ LeaveOrganization: "none",
26805
+ ObserveOrganization: "none",
26806
+ OtpSent: "otp-requested",
26807
+ OtpFailed: "failed",
26808
+ Verified: "verified",
26809
+ VerifyFailed: "failed",
26810
+ SessionRead: "restoration",
26811
+ SessionReadFailed: "restoration",
26812
+ SignedOut: "signed-out",
26813
+ SignOutFailed: "failed",
26814
+ AccountAt: "none",
26815
+ AccountDerived: "none",
26816
+ AccountClaiming: "none",
26817
+ AccountClaimed: "none",
26818
+ AccountFailed: "none",
26819
+ OrgSelected: "none",
26820
+ OrgSelectionFailed: "none"
26821
+ };
26822
+ const emailDomain = (email) => email.split("@")[1]?.toLowerCase() ?? "unknown";
26823
+ const authFailure = (record, reason, mode) => ({
26824
+ name: "auth_failed",
26825
+ props: {
26826
+ auth_type: "otp",
26827
+ reason,
26828
+ ...mode === void 0 ? {} : { failure_mode: mode },
26829
+ ...linkage(record)
26830
+ }
26795
26831
  });
26832
+ const REFUSAL_EVENTS = /* @__PURE__ */ new Set([
26833
+ "ClaimAccount",
26834
+ "RetryAccount",
26835
+ "CreateOrganization",
26836
+ "AttachOrganization"
26837
+ ]);
26838
+ function mapIdentityProductObservation(record, state) {
26839
+ const event = record.event;
26840
+ if (record.machine !== "identity" || !isIdentityEventTag(event)) return NONE;
26841
+ if (record.outcome === "cancelled") return NONE;
26842
+ if (record.slot === "identity:auth" && record.outcome === "refused") return authFailure(record, record.refusal_code, void 0);
26843
+ if (record.slot === "identity:auth" && record.outcome === "failed") return authFailure(record, record.error_code, record.failure?.mode);
26844
+ if (record.outcome === "refused" && REFUSAL_EVENTS.has(record.event)) return {
26845
+ name: "identity_refused",
26846
+ props: {
26847
+ event: record.event,
26848
+ refusal_code: record.refusal_code,
26849
+ slot: record.slot,
26850
+ state: record.state,
26851
+ ...linkage(record)
26852
+ }
26853
+ };
26854
+ if (record.outcome !== "applied") return NONE;
26855
+ const action = APPLIED_ACTION_BY_EVENT[event];
26856
+ switch (action) {
26857
+ case "none": return NONE;
26858
+ case "failed": return state.phase !== "faulted" || state.failure.code === "CANCELLED" ? NONE : authFailure(record, state.failure.code, state.failure.mode);
26859
+ case "otp-requested": return state.phase !== "otp_pending" ? NONE : {
26860
+ name: "auth_otp_requested",
26861
+ props: {
26862
+ email_domain: emailDomain(state.email),
26863
+ ...linkage(record)
26864
+ }
26865
+ };
26866
+ case "verified": return state.phase !== "authenticated" ? NONE : {
26867
+ name: "auth_verified",
26868
+ props: {
26869
+ auth_type: "otp",
26870
+ ...linkage(record)
26871
+ }
26872
+ };
26873
+ case "signed-out": return state.phase !== "signed_out" ? NONE : {
26874
+ name: "auth_signed_out",
26875
+ props: linkage(record)
26876
+ };
26877
+ case "restoration": return restorationObservation(record, event, state);
26878
+ default: return absurd(action);
26879
+ }
26880
+ }
26881
+ const ignoreTransportFailure = (operation, operationName, eventName) => Effect.suspend(operation).pipe(Effect.catchCause(() => Effect.logWarning("identity.product.telemetry.dropped").pipe(Effect.annotateLogs({
26882
+ operation: operationName,
26883
+ product_event: eventName
26884
+ }), Effect.ignoreCause)));
26885
+ function executeIdentityProductObservation(telemetry, record, state) {
26886
+ const mapped = mapIdentityProductObservation(record, state);
26887
+ if (mapped === null) return Effect.void;
26888
+ copyInvocationObservation(record, mapped);
26889
+ const invocationTelemetry = bindProductTelemetryInvocation(telemetry, mapped);
26890
+ const after = () => state.phase === "authenticated" && (mapped.name === "auth_verified" || mapped.name === "access_recovery_completed") ? invocationTelemetry.identify({
26891
+ distinctId: state.session.authUserId,
26892
+ traits: { email_domain: emailDomain(state.session.email) }
26893
+ }) : mapped.name === "auth_signed_out" ? invocationTelemetry.reset() : Effect.void;
26894
+ return ignoreTransportFailure(() => invocationTelemetry.emit(mapped), "emit", mapped.name).pipe(Effect.andThen(ignoreTransportFailure(after, mapped.name === "auth_signed_out" ? "reset" : "identify", mapped.name)));
26895
+ }
26796
26896
  //#endregion
26797
26897
  //#region ../../packages/protocol/src/payroll/model.ts
26798
26898
  function toPayrollRunId(raw) {
@@ -67840,6 +67940,11 @@ const OBSERVED_CONVEX_QUERIES = new Set(OPERATIONS.filter((row) => row.kind ===
67840
67940
  const OBSERVED_CONVEX_MUTATIONS = new Set(OPERATIONS.filter((row) => row.kind === "mutation").map((row) => row.path));
67841
67941
  //#endregion
67842
67942
  //#region ../../packages/sdk/src/platform/transport/convex-errors.ts
67943
+ /**
67944
+ * All a redacting backend says about an error it raised itself: its request
67945
+ * id. The WebSocket client wraps it as `[CONVEX Q(path)] … Called by client`.
67946
+ */
67947
+ const REDACTED_ERROR = /^(?:\[CONVEX [QMA?]\([^)]*\)\] )?\[Request ID: ([^\]\s]+)\] Server Error(?:\s+Called by client)?$/u;
67843
67948
  function mapToCapxulError(operation, err, transport) {
67844
67949
  const decoded = decodeConvexError(err);
67845
67950
  if (decoded !== null) return decoded;
@@ -67864,9 +67969,26 @@ function mapToCapxulError(operation, err, transport) {
67864
67969
  * anything else (an uncaught "Server Error") is a backend defect. None is a
67865
67970
  * provider outage. The Convex request id rides in details, so the backend's
67866
67971
  * log line can be found.
67972
+ *
67973
+ * A deployed backend redacts these errors (`REDACT_LOGS_TO_CLIENT`, #2304):
67974
+ * the message is only the request id, and no structured cause exists, so the
67975
+ * message shape is the only discriminator. Neither case can be told apart, and
67976
+ * the reply says where to look instead. The text matches stay for a backend
67977
+ * that does not redact (development, local).
67867
67978
  */
67868
67979
  function backendFailure(operation, err) {
67869
67980
  const text = err instanceof Error ? err.message : String(err);
67981
+ const redacted = REDACTED_ERROR.exec(text.trim())?.[1];
67982
+ if (redacted !== void 0) return new CapxulError("UNKNOWN", `The backend refused ${operation} and gave no detail. Find Convex request ${redacted} in the backend logs. If the backend can be newer than this client, update the CLI or SDK.`, {
67983
+ cause: err,
67984
+ layer: "convex",
67985
+ details: {
67986
+ operation,
67987
+ provider: "convex",
67988
+ requestId: redacted,
67989
+ reason: "backend-error-redacted"
67990
+ }
67991
+ });
67870
67992
  const requestId = /\[Request ID: ([^\]\s]+)\]/u.exec(text)?.[1];
67871
67993
  const where = {
67872
67994
  operation,
@@ -70946,7 +71068,7 @@ function stdioIdentity(env = process.env, options = {}) {
70946
71068
  }
70947
71069
  //#endregion
70948
71070
  //#region package.json
70949
- var version = "4.20.0-beta.24";
71071
+ var version = "4.20.0-beta.26";
70950
71072
  //#endregion
70951
71073
  //#region src/deps.ts
70952
71074
  const NO_IDENTITY = async () => void 0;