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

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 +823 -540
  2. package/package.json +3 -3
package/dist/main.mjs CHANGED
@@ -1227,7 +1227,7 @@ const FIELD_RULES = {
1227
1227
  },
1228
1228
  anonymousId: {
1229
1229
  maxLength: 128,
1230
- pattern: /^anon_[A-Za-z0-9-]+$/u
1230
+ pattern: /^(?:anon_[A-Za-z0-9-]+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/u
1231
1231
  },
1232
1232
  traceparent: {
1233
1233
  maxLength: 55,
@@ -9610,10 +9610,19 @@ const ApprovalNetworkSchema = Schema.Struct({
9610
9610
  name: Schema.String,
9611
9611
  explorerUrl: Schema.String
9612
9612
  });
9613
+ /**
9614
+ * Whose money the operation moves: the person's own, or an Organization's,
9615
+ * which a reader follows through that Organization's scope.
9616
+ */
9617
+ const ApprovalActorSchema = Schema.Union([Schema.Struct({ kind: Schema.Literal("personal") }), Schema.Struct({
9618
+ kind: Schema.Literal("organization"),
9619
+ orgId: Schema.NonEmptyString
9620
+ })]);
9613
9621
  /** What the person reads before they sign. */
9614
9622
  const ApprovalSummarySchema = Schema.Struct({
9615
9623
  title: Schema.String,
9616
9624
  from: Schema.Struct({
9625
+ kind: Schema.Literals(["personal", "organization"]),
9617
9626
  label: Schema.String,
9618
9627
  address: AddressSchema$1
9619
9628
  }),
@@ -9635,6 +9644,7 @@ const ApprovalSchema = Schema.Struct({
9635
9644
  url: Schema.String,
9636
9645
  kind: Schema.Literals(APPROVAL_KINDS),
9637
9646
  state: Schema.Literals(APPROVAL_STATES),
9647
+ actor: ApprovalActorSchema,
9638
9648
  summary: ApprovalSummarySchema,
9639
9649
  operation: ApprovalOperationSchema,
9640
9650
  /** Who must sign: the owner key of the paying Safe. */
@@ -10302,6 +10312,28 @@ const probes = {
10302
10312
  })
10303
10313
  };
10304
10314
  //#endregion
10315
+ //#region ../../packages/protocol/src/evm/role-dsl.ts
10316
+ const ORG_BUDGET_ACTIONS = [
10317
+ "send",
10318
+ "sendBatch",
10319
+ "createCommitment"
10320
+ ];
10321
+ /**
10322
+ * How a capped Budget's allowance refills (#2298). `monthly` refills the
10323
+ * allowance to its cap every 30 days. Roles v2 counts fixed periods from the
10324
+ * moment the allowance is set; it has no calendar month. Absent means `none`:
10325
+ * the cap is one pot that spending drains for good.
10326
+ */
10327
+ const ORG_BUDGET_REFILLS = ["monthly", "none"];
10328
+ /** The fixed Roles v2 refill period of a `monthly` Budget: 30 days, in seconds. */
10329
+ const MONTHLY_REFILL_PERIOD_SECONDS = 30n * 24n * 60n * 60n;
10330
+ /** Derive the Zodiac substrate key for one durable Permission identity. */
10331
+ function roleKeyForPermission(permissionId) {
10332
+ const normalized = permissionId.trim();
10333
+ if (normalized.length === 0 || normalized !== permissionId) throw Errors.invalidInput("permissionId", "must be a non-empty canonical Permission ID");
10334
+ return keccak256$1(toBytes$1(`permission ${permissionId}`));
10335
+ }
10336
+ //#endregion
10305
10337
  //#region ../../packages/protocol/src/permissions/permission-schema.ts
10306
10338
  /**
10307
10339
  * One stored `setAllowance` call inside a command's expected authority set.
@@ -10340,12 +10372,19 @@ const BudgetScopeSchema = Schema.Struct({
10340
10372
  })]),
10341
10373
  actions: Schema.NonEmptyArray(Schema.Literals(["pay", "commitments"]))
10342
10374
  }).pipe(Schema.refine((scope) => new Set(scope.actions).size === scope.actions.length && (scope.recipients.type === "anyone" || scope.recipients.accounts.length >= 1 && scope.recipients.accounts.length <= 32 && new Set(scope.recipients.accounts).size === scope.recipients.accounts.length), { message: "Budget scope requires unique actions and one to 32 unique allowed Accounts" }));
10375
+ /**
10376
+ * How a capped Budget refills (#2298): `monthly` tops the cap back up every 30
10377
+ * days (a fixed Roles v2 period, not a calendar month); `none` never refills.
10378
+ * Absent means `none`, and writers store only `monthly`.
10379
+ */
10380
+ const BudgetRefillSchema = Schema.Literals(ORG_BUDGET_REFILLS);
10343
10381
  /** The accepted product policy stored with one Permission revision. */
10344
10382
  const CurrentPolicySchema = Schema.Union([Schema.Struct({
10345
10383
  type: Schema.Literal("budget"),
10346
10384
  asset: AssetIdSchema,
10347
10385
  limit: Schema.NullOr(Schema.String),
10348
- scope: BudgetScopeSchema
10386
+ scope: BudgetScopeSchema,
10387
+ refill: Schema.optional(BudgetRefillSchema)
10349
10388
  }), Schema.Struct({ type: Schema.Literal("managePeople") })]);
10350
10389
  const BudgetIntentSchema = CurrentPolicySchema.pipe(Schema.refine((value) => {
10351
10390
  if (value.type !== "budget") return false;
@@ -10410,7 +10449,8 @@ const ResolvedPermissionBudgetPolicySchema = Schema.Struct({
10410
10449
  "send",
10411
10450
  "sendBatch",
10412
10451
  "createCommitment"
10413
- ]))
10452
+ ])),
10453
+ refill: Schema.optional(BudgetRefillSchema)
10414
10454
  });
10415
10455
  const PermissionRecipientBindingSchema = Schema.Struct({
10416
10456
  accountId: AccountIdSchema$1,
@@ -10422,16 +10462,22 @@ const StoredPermissionIntentSchema = Schema.Union([Schema.Struct({
10422
10462
  type: Schema.Literal("budget"),
10423
10463
  asset: AssetIdSchema,
10424
10464
  limit: Schema.NullOr(AssetAmountSchema),
10425
- scope: Schema.optional(BudgetScopeSchema)
10465
+ scope: Schema.optional(BudgetScopeSchema),
10466
+ refill: Schema.optional(BudgetRefillSchema)
10426
10467
  }).pipe(Schema.refine((intent) => intent.limit === null || intent.limit.asset === intent.asset, { message: "Budget limit must use the Budget asset" })), Schema.Struct({ type: Schema.Literal("managePeople") })]);
10427
10468
  /** Writes state every granted right and use a finite positive AssetAmount cap. */
10428
10469
  const PermissionIntentSchema = Schema.Union([Schema.Struct({
10429
10470
  type: Schema.Literal("budget"),
10430
10471
  asset: AssetIdSchema,
10431
10472
  limit: AssetAmountSchema,
10432
- scope: BudgetScopeSchema
10473
+ scope: BudgetScopeSchema,
10474
+ refill: Schema.optional(BudgetRefillSchema)
10433
10475
  }).pipe(Schema.refine((intent) => intent.limit.asset === intent.asset && /[1-9]/u.test(intent.limit.value), { message: "Budget cap must be positive and use the Budget asset" })), Schema.Struct({ type: Schema.Literal("managePeople") })]);
10434
- /** Decode once and give set-like scope fields one stable command representation. */
10476
+ /**
10477
+ * Decode once and give set-like scope fields one stable command representation.
10478
+ * An explicit `refill: "none"` is kept: on a change it turns a refill off,
10479
+ * where a missing `refill` keeps the Budget's current one (`keepRefillOnChange`).
10480
+ */
10435
10481
  function decodePermissionIntent$1(input) {
10436
10482
  const intent = Schema.decodeUnknownSync(PermissionIntentSchema, { onExcessProperty: "error" })(input);
10437
10483
  if (intent.type === "managePeople") return intent;
@@ -10446,6 +10492,19 @@ function decodePermissionIntent$1(input) {
10446
10492
  }
10447
10493
  });
10448
10494
  }
10495
+ /**
10496
+ * A Budget change that leaves `refill` out keeps the Budget's current refill:
10497
+ * a change never turns a refill off by omission. An explicit `"none"` turns it
10498
+ * off. The backend plans, the SDK verifies and the CLI confirms the same
10499
+ * result. A create has no current refill, so a missing one means `none`.
10500
+ */
10501
+ function keepRefillOnChange(intent, before) {
10502
+ if (intent.type !== "budget" || intent.refill !== void 0 || before?.type !== "budget" || before.refill !== "monthly") return intent;
10503
+ return {
10504
+ ...intent,
10505
+ refill: before.refill
10506
+ };
10507
+ }
10449
10508
  //#endregion
10450
10509
  //#region ../../packages/protocol/src/permissions/replacement-schema.ts
10451
10510
  const InvitationPhaseSchema = Schema.Literals([
@@ -10650,7 +10709,7 @@ const InviteGrantsSchema = Schema.Array(Schema.Union([
10650
10709
  Schema.Struct({
10651
10710
  type: Schema.Literal("new_budget"),
10652
10711
  label: Schema.String.pipe(Schema.refine((value) => value.trim().length >= 1 && value.trim().length <= 128)),
10653
- intent: BudgetIntentSchema
10712
+ intent: BudgetIntentSchema.pipe(Schema.refine((intent) => intent.refill !== "monthly", { message: "An invitation's new Budget cannot refill; change it once granted" }))
10654
10713
  })
10655
10714
  ])).pipe(Schema.refine((grants) => {
10656
10715
  if (grants.length < 1 || grants.length > 16) return false;
@@ -13524,7 +13583,9 @@ const PEOPLE_OPERATIONS = {
13524
13583
  emitSafeConfirmed: "accounts.safes.emitSafeConfirmed",
13525
13584
  readSafe: "accounts.safes.readSafe",
13526
13585
  recordDeployment: "accounts.safes.recordDeployment"
13527
- }
13586
+ },
13587
+ /** The home's requests to pay and Payments to retry, read in one query. */
13588
+ summaries: { loadNeedsYou: "accounts.summaries.loadNeedsYou" }
13528
13589
  },
13529
13590
  /** The host observer's names for `client.approvals.*`. */
13530
13591
  approvals: {
@@ -13705,7 +13766,11 @@ const ORGANIZATION_OPERATIONS = {
13705
13766
  seedNativeReadiness: "operator.fixtures.seedNativeReadiness",
13706
13767
  seedPrerequisites: "operator.fixtures.seedPrerequisites"
13707
13768
  },
13708
- identities: { purgeByEmail: "operator.identities.purgeByEmail" }
13769
+ identities: {
13770
+ purgeByEmail: "operator.identities.purgeByEmail",
13771
+ /** One Account and every row that refers to it, unless it has Payments or an Organization tie. */
13772
+ purgeAccount: "operator.identities.purgeAccount"
13773
+ }
13709
13774
  },
13710
13775
  /** Organizations' internal functions; its public rows join from `OPERATIONS`. */
13711
13776
  organizations: {
@@ -13779,7 +13844,9 @@ emitSaved: "activity.annotations.emitSaved" },
13779
13844
  pay: "organizationPayments.pay",
13780
13845
  payBatch: "organizationPayments.payBatch",
13781
13846
  prepareExecution: "organizationPayments.prepareExecution",
13782
- retry: "organizationPayments.retry"
13847
+ retry: "organizationPayments.retry",
13848
+ cancel: "organizationPayments.cancel",
13849
+ redirect: "organizationPayments.redirect"
13783
13850
  },
13784
13851
  /**
13785
13852
  * Payments' internal functions and the client's own method names; its public
@@ -13801,7 +13868,9 @@ emitSaved: "activity.annotations.emitSaved" },
13801
13868
  loadContext: "payments.commands.loadContext",
13802
13869
  loadExecutionContext: "payments.commands.loadExecutionContext",
13803
13870
  /** The capped Budget a prepared Organization command spends from, before its allowance is read on chain. */
13804
- loadBudgetAllowance: "payments.commands.loadBudgetAllowance"
13871
+ loadBudgetAllowance: "payments.commands.loadBudgetAllowance",
13872
+ /** What a new Payment command would spend and from which Safe, read before it is born. */
13873
+ loadBirthFunding: "payments.commands.loadBirthFunding"
13805
13874
  },
13806
13875
  lifecycles: { prepareIntent: "payments.lifecycles.prepareIntent" },
13807
13876
  /** The stored UserOperation of every money command: Payment, Permission and configuration. */
@@ -13955,7 +14024,11 @@ const PLATFORM_OPERATIONS = {
13955
14024
  recordUse: "applications.bootstraps.recordUse",
13956
14025
  resolve: "applications.bootstraps.resolve"
13957
14026
  },
13958
- keys: { issue: "applications.keys.issue" },
14027
+ keys: {
14028
+ issue: "applications.keys.issue",
14029
+ /** Operator-only: revoke one key by its id, whoever owns it. */
14030
+ revokeById: "applications.keys.revokeById"
14031
+ },
13959
14032
  quickstarts: {
13960
14033
  claim: "applications.quickstarts.claim",
13961
14034
  mint: "applications.quickstarts.mint"
@@ -14234,6 +14307,42 @@ const UiStepProps = {
14234
14307
  organization_id: OptionalOrgId,
14235
14308
  reason_code: OptionalReasonCode
14236
14309
  };
14310
+ /**
14311
+ * A journey's variants (`JOURNEYS`): the way a person came into a journey, or
14312
+ * the way they took through it. A variant is a property of the journey's step
14313
+ * events, never a journey of its own, so each journey funnel breaks down by
14314
+ * it. A step event takes only the variants that a journey contract names on
14315
+ * it, picked one by one; the analytics plan test checks both directions.
14316
+ * `pay_path` and `funding_source` have no carrier until their Phase 2 events.
14317
+ * `payment_*` events already carry `recipient_is_capxul_account`, and
14318
+ * `invoice_issued` carries `issuer_scope_kind`; neither is repeated here.
14319
+ */
14320
+ const JourneyVariantProps = {
14321
+ /** How a person came to Get started: by themselves, an invite, money that waited, or an invoice. */
14322
+ entry_path: Schema.optional(Schema.Literals([
14323
+ "signup",
14324
+ "invite",
14325
+ "claim",
14326
+ "invoice"
14327
+ ])),
14328
+ /** Where the person acted. `agent` is an AI agent driving the CLI. */
14329
+ surface: Schema.optional(Schema.Literals([
14330
+ "web",
14331
+ "cli",
14332
+ "agent"
14333
+ ])),
14334
+ /** Who pays: a person from their own account, or an Organization from a Budget. */
14335
+ payer_kind: Schema.optional(Schema.Literals(["person", "organization"])),
14336
+ /** How a request was paid: from the payer's inbox, or by its link at checkout. */
14337
+ pay_path: Schema.optional(Schema.Literals(["inbox", "link"])),
14338
+ /** Where an account's first money came from. `bank` is the planned on-ramp. */
14339
+ funding_source: Schema.optional(Schema.Literals([
14340
+ "deposit",
14341
+ "payment",
14342
+ "claim",
14343
+ "bank"
14344
+ ]))
14345
+ };
14237
14346
  //#endregion
14238
14347
  //#region ../../packages/observability/src/events/money.ts
14239
14348
  /**
@@ -14323,6 +14432,8 @@ const PaymentInitiatedProps = Schema.Struct({
14323
14432
  execution_id: OptionalString,
14324
14433
  payment_lifecycle_id: OptionalString,
14325
14434
  ...TelemetryEnvelopeProps,
14435
+ payer_kind: JourneyVariantProps.payer_kind,
14436
+ surface: JourneyVariantProps.surface,
14326
14437
  ...RecipientTelemetryProps,
14327
14438
  kind: Schema.String,
14328
14439
  payment_id: Schema.String
@@ -14338,6 +14449,8 @@ const PaymentLifecycleProps = {
14338
14449
  const PaymentSettledL2Props = Schema.Struct({
14339
14450
  ...PaymentLifecycleProps,
14340
14451
  ...TelemetryEnvelopeProps,
14452
+ payer_kind: JourneyVariantProps.payer_kind,
14453
+ surface: JourneyVariantProps.surface,
14341
14454
  ...RecipientTelemetryProps,
14342
14455
  kind: Schema.String,
14343
14456
  chain_id: Schema.Finite,
@@ -14545,6 +14658,7 @@ const OrganizationCreationStartedProps = Schema.Struct({
14545
14658
  });
14546
14659
  const OrganizationCreationReadyProps = Schema.Struct({
14547
14660
  ...TelemetryEnvelopeProps,
14661
+ surface: JourneyVariantProps.surface,
14548
14662
  $insert_id: Schema.String,
14549
14663
  organization_id: OrgIdTelemetrySchema,
14550
14664
  attempt_number: AttemptNumberSchema,
@@ -14569,45 +14683,6 @@ const OrganizationCreationFailedProps = Schema.Struct({
14569
14683
  ...ORGANIZATION_SETUP_REASONS
14570
14684
  ]))
14571
14685
  });
14572
- const OrgCreateStartedProps = Schema.Struct({
14573
- ...TelemetryEnvelopeProps,
14574
- org_id: OptionalOrgId,
14575
- email_domain: OptionalString,
14576
- template: OptionalString
14577
- });
14578
- const OrgSafeCreatedProps = Schema.Struct({
14579
- ...TelemetryEnvelopeProps,
14580
- org_id: OptionalOrgId,
14581
- chainId: OptionalChainId,
14582
- safe_address: OptionalAddress
14583
- });
14584
- const OrgSafeConfirmedProps = Schema.Struct({
14585
- ...TelemetryEnvelopeProps,
14586
- org_id: OptionalOrgId,
14587
- chainId: OptionalChainId,
14588
- safe_address: OptionalAddress,
14589
- durationMs: OptionalDurationMs
14590
- });
14591
- const OrgRolesSeededProps = Schema.Struct({
14592
- ...TelemetryEnvelopeProps,
14593
- org_id: OptionalOrgId,
14594
- chainId: OptionalChainId,
14595
- role: OptionalString,
14596
- txHash: OptionalTxHash
14597
- });
14598
- const OrgCreatedProps = Schema.Struct({
14599
- ...TelemetryEnvelopeProps,
14600
- org_id: OptionalOrgId,
14601
- chainId: OptionalChainId,
14602
- safe_address: OptionalAddress,
14603
- durationMs: OptionalDurationMs
14604
- });
14605
- const OrgCreateFailedProps = Schema.Struct({
14606
- ...TelemetryEnvelopeProps,
14607
- ...ChainCauseTelemetryProps,
14608
- org_id: OptionalOrgId,
14609
- reason: OptionalString
14610
- });
14611
14686
  const OrgInviteSentProps = Schema.Struct({
14612
14687
  ...TelemetryEnvelopeProps,
14613
14688
  org_id: OptionalOrgId,
@@ -14652,30 +14727,6 @@ Schema.Struct({
14652
14727
  name: Schema.Literal("organization_creation_failed"),
14653
14728
  props: OrganizationCreationFailedProps
14654
14729
  });
14655
- Schema.Struct({
14656
- name: Schema.Literal("org_create_started"),
14657
- props: OrgCreateStartedProps
14658
- });
14659
- Schema.Struct({
14660
- name: Schema.Literal("org_safe_created"),
14661
- props: OrgSafeCreatedProps
14662
- });
14663
- Schema.Struct({
14664
- name: Schema.Literal("org_safe_confirmed"),
14665
- props: OrgSafeConfirmedProps
14666
- });
14667
- Schema.Struct({
14668
- name: Schema.Literal("org_roles_seeded"),
14669
- props: OrgRolesSeededProps
14670
- });
14671
- Schema.Struct({
14672
- name: Schema.Literal("org_created"),
14673
- props: OrgCreatedProps
14674
- });
14675
- Schema.Struct({
14676
- name: Schema.Literal("org_create_failed"),
14677
- props: OrgCreateFailedProps
14678
- });
14679
14730
  Schema.Struct({
14680
14731
  name: Schema.Literal("org_invite_sent"),
14681
14732
  props: OrgInviteSentProps
@@ -15549,10 +15600,14 @@ const BootstrapFailedProps = Schema.Struct({
15549
15600
  });
15550
15601
  const MemberActivationStartedProps = Schema.Struct({
15551
15602
  ...TelemetryEnvelopeProps,
15603
+ entry_path: JourneyVariantProps.entry_path,
15604
+ surface: JourneyVariantProps.surface,
15552
15605
  $insert_id: Schema.String
15553
15606
  });
15554
15607
  const MemberActivationReadyProps = Schema.Struct({
15555
15608
  ...TelemetryEnvelopeProps,
15609
+ entry_path: JourneyVariantProps.entry_path,
15610
+ surface: JourneyVariantProps.surface,
15556
15611
  $insert_id: Schema.String,
15557
15612
  duration_ms: DurationMsSchema
15558
15613
  });
@@ -17098,7 +17153,7 @@ function mapAccountLifecycle(input) {
17098
17153
  }
17099
17154
  //#endregion
17100
17155
  //#region ../../packages/sdk/package.json
17101
- var version$2 = "4.20.0-beta.26";
17156
+ var version$2 = "4.20.0-beta.28";
17102
17157
  //#endregion
17103
17158
  //#region ../../packages/sdk/src/observability/telemetry/exception-projection.ts
17104
17159
  /** Fixed fallback for failures that have no safe message. */
@@ -20356,6 +20411,12 @@ function paymentsV2Signature(name) {
20356
20411
  const SEND_SIGNATURE = paymentsV2Signature("send");
20357
20412
  const SEND_BATCH_SIGNATURE = paymentsV2Signature("sendBatch");
20358
20413
  const CREATE_COMMITMENT_SIGNATURE = paymentsV2Signature("createCommitment");
20414
+ /**
20415
+ * #2299 ruling C: only an Owner cancels or redirects an Organization-funded
20416
+ * commitment, so these two sit in the Owner role's scope, never a Budget's.
20417
+ * The contract admits only the commitment's creator, the Organization Safe.
20418
+ */
20419
+ const OWNER_COMMITMENT_SIGNATURES = [paymentsV2Signature("cancel"), paymentsV2Signature("redirect")];
20359
20420
  //#endregion
20360
20421
  //#region ../../packages/protocol/src/evm/organization-payments.ts
20361
20422
  /** Zodiac ModuleProxyFactory on Base Sepolia. */
@@ -20592,24 +20653,18 @@ function encodePreparedPaymentCallData(command) {
20592
20653
  }));
20593
20654
  }
20594
20655
  const [item] = command.items;
20595
- if (command.executionRoute.kind !== "direct" || command.items.length !== 1 || item === void 0 || isBirth(item)) throw Errors.invalidInput("items", "Commitment lifecycle requires one direct execution");
20596
- return safe4337ExecuteUserOpCallData({
20656
+ const route = command.executionRoute;
20657
+ if (command.items.length !== 1 || item === void 0 || isBirth(item) || route.kind !== "direct" && item.operation === "claim_commitment") throw Errors.invalidInput("items", "Commitment lifecycle requires one lone execution");
20658
+ const call = {
20597
20659
  to: command.contractAddress,
20660
+ value: 0n,
20598
20661
  data: lifecycleData(item)
20599
- });
20600
- }
20601
- //#endregion
20602
- //#region ../../packages/protocol/src/evm/role-dsl.ts
20603
- const ORG_BUDGET_ACTIONS = [
20604
- "send",
20605
- "sendBatch",
20606
- "createCommitment"
20607
- ];
20608
- /** Derive the Zodiac substrate key for one durable Permission identity. */
20609
- function roleKeyForPermission(permissionId) {
20610
- const normalized = permissionId.trim();
20611
- if (normalized.length === 0 || normalized !== permissionId) throw Errors.invalidInput("permissionId", "must be a non-empty canonical Permission ID");
20612
- return keccak256$1(toBytes$1(`permission ${permissionId}`));
20662
+ };
20663
+ return route.kind === "direct" ? safe4337ExecuteUserOpCallData(call) : buildOrgRoleSpendPersonalCallData(buildOrgRoleMultiCall({
20664
+ rolesModuleAddress: route.rolesModule,
20665
+ roleKey: route.roleKey,
20666
+ calls: [call]
20667
+ }));
20613
20668
  }
20614
20669
  //#endregion
20615
20670
  //#region ../../packages/sdk/src/payments/operation-verification.ts
@@ -20638,6 +20693,16 @@ function moneySafeOpDigest(operation, chainId) {
20638
20693
  userOp: safe4337UserOperation(operation)
20639
20694
  });
20640
20695
  }
20696
+ /**
20697
+ * An Organization-funded cancel or redirect runs through the Owner role
20698
+ * (#2299), which no Budget names. Its role key cannot widen what the signer
20699
+ * may do: the Roles module admits only a role the signer's Safe holds, and the
20700
+ * contract admits only the commitment's creator.
20701
+ */
20702
+ function isOwnerLifecycle(command) {
20703
+ const [item] = command.items;
20704
+ return command.items.length === 1 && (item?.operation === "cancel_commitment" || item?.operation === "redirect_commitment");
20705
+ }
20641
20706
  /** Verify every actual Money signing target against configured trust anchors. */
20642
20707
  function verifiesMoneyOperation(evidence, expectedSafe, chainId, permissionId) {
20643
20708
  try {
@@ -20648,7 +20713,7 @@ function verifiesMoneyOperation(evidence, expectedSafe, chainId, permissionId) {
20648
20713
  const route = command.executionRoute;
20649
20714
  if (route.kind === "direct") {
20650
20715
  if (permissionId !== void 0 || command.senderSafe.toLowerCase() !== expectedSafe.toLowerCase()) return false;
20651
- } else if (permissionId === void 0 || route.roleKey.toLowerCase() !== roleKeyForPermission(permissionId).toLowerCase() || command.senderSafe.toLowerCase() !== route.organizationSafe.toLowerCase()) return false;
20716
+ } else if ((permissionId === void 0 ? !isOwnerLifecycle(command) : route.roleKey.toLowerCase() !== roleKeyForPermission(permissionId).toLowerCase()) || command.senderSafe.toLowerCase() !== route.organizationSafe.toLowerCase()) return false;
20652
20717
  if (operation.callData.toLowerCase() !== encodePreparedPaymentCallData(command).toLowerCase()) return false;
20653
20718
  const digest = moneySafeOpDigest(operation, chainId);
20654
20719
  return evidence.digest.toLowerCase() === digest.toLowerCase() && evidence.executionId === `money_execution_${digest.slice(2).toLowerCase()}`;
@@ -21365,6 +21430,15 @@ function subscribeFreshQuery(deps, args, listener, operation, projection, option
21365
21430
  }
21366
21431
  //#endregion
21367
21432
  //#region ../../packages/sdk/src/payments/lifecycle-execution.ts
21433
+ /**
21434
+ * The prepared command is this lifecycle intent; a retry also names the
21435
+ * original birth command of the scope it was asked for. A claim, and an
21436
+ * Account's own cancel or redirect, runs directly from the signer's Safe; an
21437
+ * Organization's cancel or redirect runs through its Owner role (#2299).
21438
+ */
21439
+ const lifecycleCommandCheck = (intent, expectedOrganizationId) => async (request, evidence) => await matchesIntent(intent)(request, evidence) && (intent.kind === "retry" ? expectedOrganizationId === void 0 ? evidence.sourceCommand?.kind === "birth" && evidence.sourceCommand.actor.kind === "account" : evidence.sourceCommand?.kind === "birth" && evidence.sourceCommand.actor.kind === "organization" && evidence.sourceCommand.actor.orgId === expectedOrganizationId : evidence.preparedCommand?.executionRoute.kind === (intent.kind === "claim" || expectedOrganizationId === void 0 ? "direct" : "zodiac_role"));
21440
+ /** The prepared command pays exactly the request the payer confirmed. */
21441
+ const requestPaymentCheck = (confirmation) => async (request) => "kind" in request && request.kind === "payment_request" && canonicalJson(request.confirmation) === canonicalJson(confirmation);
21368
21442
  function executePaymentLifecycle(deps, intent, signal, controls, expectedOrganizationId, onPrepared) {
21369
21443
  return executePrepared(deps, (signerAddress) => runIfActive(signal, CAPXUL_OPERATIONS.payments.prepareLifecycleExecution, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, lifecycles.prepareScoped, copyInvocationObservation(controls, {
21370
21444
  signerAddress,
@@ -21373,13 +21447,13 @@ function executePaymentLifecycle(deps, intent, signal, controls, expectedOrganiz
21373
21447
  kind: "organization",
21374
21448
  orgId: expectedOrganizationId
21375
21449
  }
21376
- })).pipe(Effect.mapError((publicError) => ({ publicError })))), controls), deps.runPromise), async (request, evidence) => await matchesIntent(intent)(request, evidence) && (intent.kind !== "retry" || (expectedOrganizationId === void 0 ? evidence.sourceCommand?.kind === "birth" && evidence.sourceCommand.actor.kind === "account" : evidence.sourceCommand?.kind === "birth" && evidence.sourceCommand.actor.kind === "organization" && evidence.sourceCommand.actor.orgId === expectedOrganizationId)), false, void 0, signal, controls, onPrepared);
21450
+ })).pipe(Effect.mapError((publicError) => ({ publicError })))), controls), deps.runPromise), lifecycleCommandCheck(intent, expectedOrganizationId), false, void 0, signal, controls, onPrepared);
21377
21451
  }
21378
21452
  function executeRequestPayment(deps, confirmation, controls, onPrepared) {
21379
21453
  return executePrepared(deps, (signerAddress) => runIfActive(controls?.signal, commands$1.prepareRequest.name, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, commands$1.prepareRequest, copyInvocationObservation(controls, {
21380
21454
  confirmation,
21381
21455
  signerAddress
21382
- })).pipe(Effect.mapError((publicError) => ({ publicError })))), controls), deps.runPromise), async (request) => "kind" in request && request.kind === "payment_request" && canonicalJson(request.confirmation) === canonicalJson(confirmation), false, void 0, controls?.signal, controls, onPrepared);
21456
+ })).pipe(Effect.mapError((publicError) => ({ publicError })))), controls), deps.runPromise), requestPaymentCheck(confirmation), false, void 0, controls?.signal, controls, onPrepared);
21383
21457
  }
21384
21458
  function executeOrganizationPayment(deps, input, signal, onPreparedClosed, controls, onPrepared) {
21385
21459
  return executePrepared(deps, (signerAddress) => runIfActive(signal, CAPXUL_OPERATIONS.organizationPayments.prepareExecution, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, commands$1.prepareOrganization, copyInvocationObservation(controls, {
@@ -22252,17 +22326,21 @@ function paymentExecutionIntent(input) {
22252
22326
  const to = normalizeTargetForBackend(input.to, "to");
22253
22327
  if (!to.ok) return to;
22254
22328
  try {
22255
- return {
22329
+ const decoded = Schema.decodeResult(PaymentExecutionIntentSchema, { onExcessProperty: "error" })({
22330
+ to: to.value,
22331
+ amount: input.amount,
22332
+ ...input.externalAddressAcknowledged === void 0 ? {} : { externalAddressAcknowledged: input.externalAddressAcknowledged },
22333
+ ...input.paymentType === void 0 ? {} : { paymentType: input.paymentType },
22334
+ ...input.document === void 0 ? {} : { document: input.document },
22335
+ ...input.supportingReceipts === void 0 ? {} : { supportingReceipts: input.supportingReceipts },
22336
+ ...input.timing === void 0 ? {} : { timing: input.timing }
22337
+ });
22338
+ return Result.isSuccess(decoded) ? {
22256
22339
  ok: true,
22257
- value: Schema.decodeSync(PaymentExecutionIntentSchema, { onExcessProperty: "error" })({
22258
- to: to.value,
22259
- amount: input.amount,
22260
- ...input.externalAddressAcknowledged === void 0 ? {} : { externalAddressAcknowledged: input.externalAddressAcknowledged },
22261
- ...input.paymentType === void 0 ? {} : { paymentType: input.paymentType },
22262
- ...input.document === void 0 ? {} : { document: input.document },
22263
- ...input.supportingReceipts === void 0 ? {} : { supportingReceipts: input.supportingReceipts },
22264
- ...input.timing === void 0 ? {} : { timing: input.timing }
22265
- })
22340
+ value: decoded.success
22341
+ } : {
22342
+ ok: false,
22343
+ error: invalidPaymentField(decoded.failure.issue)
22266
22344
  };
22267
22345
  } catch {
22268
22346
  return {
@@ -22271,6 +22349,13 @@ function paymentExecutionIntent(input) {
22271
22349
  };
22272
22350
  }
22273
22351
  }
22352
+ const formatIssue = SchemaIssue.makeFormatterStandardSchemaV1();
22353
+ /** The first failing field and why, e.g. `amount.value`, as the backend's row decode names it (#2381). */
22354
+ function invalidPaymentField(issue) {
22355
+ const first = formatIssue(issue).issues[0];
22356
+ const field = first?.path?.map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
22357
+ return Errors.invalidInput(field === void 0 || field.length === 0 ? "payment" : field, first?.message ?? "invalid personal payment input");
22358
+ }
22274
22359
  function normalizePaymentTiming(payment) {
22275
22360
  const { release: _release, ...rest } = payment;
22276
22361
  return {
@@ -23087,6 +23172,27 @@ function makeOrganizationPaymentsMethods(deps, orgId) {
23087
23172
  flights: /* @__PURE__ */ new Map()
23088
23173
  };
23089
23174
  const execute = (input, signal, operation = "organization-pay-batch", controls, onPrepared) => executeOrganizationBatch(scope, input, signal, operation, controls, onPrepared);
23175
+ /** One Owner-signed cancel or redirect, answering the one Payment it acted on (#2299). */
23176
+ const ownerLifecycle = (intent, controls, operation) => runCapxulEffect(Effect.gen(function* () {
23177
+ const captured = carryInvocationParentSpan(controls, yield* Effect.currentSpan.pipe(Effect.orDie));
23178
+ return yield* awaitCapxulResult(async () => {
23179
+ const execution = executionDependencies$1(deps);
23180
+ if (execution === null) return {
23181
+ ok: false,
23182
+ error: Errors.notImplemented("organizationPayments", "executionComposition")
23183
+ };
23184
+ const result = await executePaymentLifecycle(execution, intent, controls?.signal, captured, orgId);
23185
+ if (!result.ok) return result;
23186
+ const [payment, ...rest] = result.value.payments;
23187
+ return payment === void 0 || rest.length !== 0 ? {
23188
+ ok: false,
23189
+ error: Errors.unknown()
23190
+ } : {
23191
+ ok: true,
23192
+ value: normalizePaymentTiming(payment)
23193
+ };
23194
+ });
23195
+ }), observationOnlyControls(controls), operation, deps.runPromise);
23090
23196
  return {
23091
23197
  ...makeOrganizationPaymentReadMethods(deps, orgId),
23092
23198
  retry: (paymentId, options) => {
@@ -23145,6 +23251,19 @@ function makeOrganizationPaymentsMethods(deps, orgId) {
23145
23251
  });
23146
23252
  }), observationOnlyControls(controls), CAPXUL_OPERATIONS.organizationPayments.pay, deps.runPromise);
23147
23253
  },
23254
+ cancel: (paymentId, options) => ownerLifecycle({
23255
+ kind: "cancel",
23256
+ paymentId
23257
+ }, deps.invocationControls?.(options) ?? options, CAPXUL_OPERATIONS.organizationPayments.cancel),
23258
+ redirect: (input, options) => {
23259
+ const controls = deps.invocationControls?.(options) ?? options;
23260
+ const recipient = normalizeTargetForBackend(input.to, "to");
23261
+ return recipient.ok ? ownerLifecycle({
23262
+ kind: "redirect",
23263
+ paymentId: input.paymentId,
23264
+ recipient: recipient.value
23265
+ }, controls, CAPXUL_OPERATIONS.organizationPayments.redirect) : runCapxulEffect(Effect.fail(recipient.error), observationOnlyControls(controls), CAPXUL_OPERATIONS.organizationPayments.redirect, deps.runPromise);
23266
+ },
23148
23267
  payBatch: (input, options) => {
23149
23268
  const controls = deps.invocationControls?.(options) ?? options;
23150
23269
  return runCapxulEffect(Effect.gen(function* () {
@@ -23859,6 +23978,524 @@ function sleep(ms, signal) {
23859
23978
  });
23860
23979
  }
23861
23980
  //#endregion
23981
+ //#region ../../packages/protocol/src/payroll/model.ts
23982
+ function toPayrollRunId(raw) {
23983
+ return toNonEmptyStringBrand(raw, "payrollRunId", (value) => isNonEmptyString(value));
23984
+ }
23985
+ function toPayrollGroupId(raw) {
23986
+ return toNonEmptyStringBrand(raw, "payrollGroupId", (value) => isNonEmptyString(value));
23987
+ }
23988
+ //#endregion
23989
+ //#region ../../packages/sdk/src/payroll/run-checks.ts
23990
+ /** Match the caller's compensation to the committed V3 Payslip. */
23991
+ function payslipIdentityMatches(message, item, index, runId, period) {
23992
+ return message.runId === runId && message.itemIndex === index && message.employeeRef === item.partyId && message.periodStart === period.start && message.periodEnd === period.end && message.adjustmentsHash === hashAdjustmentsV3(item.adjustments);
23993
+ }
23994
+ function payslipAmountMatches(message, item) {
23995
+ const exact = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
23996
+ return message.gross.asset === exact.asset && message.gross.minorUnits === item.gross && message.gross.decimals === exact.decimals && message.net.asset === exact.asset && message.net.minorUnits === item.net && message.net.decimals === exact.decimals;
23997
+ }
23998
+ function isRunItem(prepared, item, evidence, index, runId, period) {
23999
+ if (prepared === void 0) return false;
24000
+ const bundle = evidence.preparedCommand?.documentBundles?.[index];
24001
+ const preparedItem = evidence.preparedCommand?.items[index];
24002
+ const payslip = bundle?.envelope;
24003
+ if (bundle === void 0 || payslip?.primaryType !== "Payslip") return false;
24004
+ return canonicalJson(prepared.to) === canonicalJson(item.to) && prepared.paymentType === "payroll" && canonicalJson(prepared.amount) === canonicalJson(item.amount) && bundle.adjustments !== void 0 && canonicalJson(bundle.adjustments) === canonicalJson(item.adjustments) && payslipIdentityMatches(payslip.message, item, index, runId, period) && payslipAmountMatches(payslip.message, item) && (preparedItem?.operation === "send" || preparedItem?.operation === "create_commitment") && preparedItem.documentHash === evidence.preparedCommand?.instructionDocuments?.[index]?.documentHash;
24005
+ }
24006
+ /**
24007
+ * The batch the backend derived from a payroll intent must be the batch this
24008
+ * caller asked for, checked before the digest is signed.
24009
+ *
24010
+ * The payslip envelope on each item is server-built and carries the run row's
24011
+ * own `issuedAt`, so the batch cannot be fingerprinted against a local copy
24012
+ * (`matchesIntent` is unavailable here). What CAN be verified is every field
24013
+ * the caller supplied — the org, the Budget, the request key, and per item the
24014
+ * recipient, the amount and the payslip figures — plus the lineage binding the
24015
+ * batch to the run just written. A prepared command that pays a different
24016
+ * person, a different amount, from a different Budget, or under a different
24017
+ * run never reaches the signer.
24018
+ */
24019
+ function isRunBatch(input) {
24020
+ return (batch, evidence) => Promise.resolve(!("kind" in batch) && batch.orgId === input.orgId && batch.permissionId === input.permissionId && batch.requestKey === input.requestKey && batch.lineage?.kind === "payroll" && batch.lineage.runId === input.runId && batch.items.length === input.items.length && batch.items.every((item) => item.amount.asset === input.asset) && input.items.every((item, index) => isRunItem(batch.items[index], item, evidence, index, input.runId, input.period)));
24021
+ }
24022
+ /**
24023
+ * The batch is `isRunBatch` for the run the backend just wrote, and that run
24024
+ * is the period the caller asked for: the payslip's `period` is a
24025
+ * server-formatted label, so the stored run is where the caller's own two
24026
+ * numbers are checkable.
24027
+ */
24028
+ function isAuthorizedRunBatch(input, run) {
24029
+ return (request, evidence) => run === void 0 || run.periodStart !== input.period.start || run.periodEnd !== input.period.end ? Promise.resolve(false) : isRunBatch({
24030
+ ...input,
24031
+ runId: run.id
24032
+ })(request, evidence);
24033
+ }
24034
+ /** A decoded detail is the run asked for, whole: its scope, membership and quantities agree. */
24035
+ function checkPayrollRunDetail(value, orgId, runId) {
24036
+ if (value === null) return {
24037
+ ok: true,
24038
+ value: null
24039
+ };
24040
+ if (value.orgId !== orgId || value.run.id !== runId || value.items.length === 0 || value.items.length !== value.run.recipientCount || new Set(value.items.map((item) => item.payment.id)).size !== value.items.length || value.items.some((item, index) => item.itemIndex !== index || item.amount.asset !== value.run.total.asset || item.payment.amount.asset !== item.amount.asset || item.payment.paymentType !== "payroll")) return {
24041
+ ok: false,
24042
+ error: Errors.invalidInput("run", "Payroll run scope or membership does not match")
24043
+ };
24044
+ try {
24045
+ const total = toExactAssetAmount(value.run.total, CONFIGURED_MONEY_ASSETS);
24046
+ let rawTotal = 0n;
24047
+ for (const item of value.items) {
24048
+ const amount = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
24049
+ const payment = toExactAssetAmount(item.payment.amount, CONFIGURED_MONEY_ASSETS);
24050
+ if (amount.minorUnits !== payment.minorUnits) return {
24051
+ ok: false,
24052
+ error: Errors.invalidInput("run", "Payroll item quantity does not match its Payment")
24053
+ };
24054
+ rawTotal += BigInt(amount.minorUnits);
24055
+ }
24056
+ if (rawTotal !== BigInt(total.minorUnits)) return {
24057
+ ok: false,
24058
+ error: Errors.invalidInput("run", "Payroll run total does not match its items")
24059
+ };
24060
+ } catch {
24061
+ return {
24062
+ ok: false,
24063
+ error: Errors.invalidInput("run", "Payroll run quantities are invalid")
24064
+ };
24065
+ }
24066
+ return {
24067
+ ok: true,
24068
+ value
24069
+ };
24070
+ }
24071
+ //#endregion
24072
+ //#region ../../packages/sdk/src/payroll/run-decoding.ts
24073
+ function toPayrollRun(wire) {
24074
+ return {
24075
+ id: toPayrollRunId(wire.id),
24076
+ status: wire.status,
24077
+ periodStart: wire.periodStart,
24078
+ periodEnd: wire.periodEnd,
24079
+ total: wire.total,
24080
+ recipientCount: wire.recipientCount
24081
+ };
24082
+ }
24083
+ function toPayrollRuns(wire) {
24084
+ return {
24085
+ ...mapPage(wire, toPayrollRun),
24086
+ settledThisMonth: wire.settledThisMonth
24087
+ };
24088
+ }
24089
+ function toPayrollGroup(wire) {
24090
+ return {
24091
+ id: toPayrollGroupId(wire.id),
24092
+ name: wire.name,
24093
+ tone: wire.tone,
24094
+ members: wire.members.map((member) => ({
24095
+ partyId: member.partyId,
24096
+ amount: member.amount,
24097
+ currency: member.currency
24098
+ }))
24099
+ };
24100
+ }
24101
+ function validateRunItems(input) {
24102
+ for (const [index, item] of input.items.entries()) {
24103
+ if (Schema.decodeResult(PayrollRunItemInputSchema, { onExcessProperty: "error" })(item)._tag === "Failure") return Errors.invalidInput(`items.${index}`, "invalid Payroll item");
24104
+ try {
24105
+ const exact = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
24106
+ const net = BigInt(item.net);
24107
+ const gross = BigInt(item.gross);
24108
+ const adjustments = item.adjustments.reduce((sum, row) => sum + BigInt(row.amount), 0n);
24109
+ if (item.amount.asset !== input.asset || item.net !== exact.minorUnits || gross + adjustments !== net) return Errors.invalidInput(`items.${index}`, "invalid Payroll units");
24110
+ } catch {
24111
+ return Errors.invalidInput(`items.${index}`, "invalid Payroll units");
24112
+ }
24113
+ }
24114
+ return null;
24115
+ }
24116
+ const RunInputSchema = Schema.Struct({
24117
+ permissionId: AuthorizePayrollRunExecutionInputSchema.fields.permissionId,
24118
+ asset: AuthorizePayrollRunExecutionInputSchema.fields.asset,
24119
+ period: AuthorizePayrollRunExecutionInputSchema.fields.period,
24120
+ items: AuthorizePayrollRunExecutionInputSchema.fields.items,
24121
+ requestKey: Schema.optional(AuthorizePayrollRunExecutionInputSchema.fields.requestKey)
24122
+ });
24123
+ /** Decode caller input before session or signer work. Keep routing outside the input. */
24124
+ function decodePayrollRunInput(value) {
24125
+ try {
24126
+ const decoded = Schema.decodeUnknownSync(RunInputSchema, { onExcessProperty: "error" })(value);
24127
+ const input = {
24128
+ permissionId: decoded.permissionId,
24129
+ asset: decoded.asset,
24130
+ period: decoded.period,
24131
+ ...decoded.requestKey === void 0 ? {} : { requestKey: decoded.requestKey },
24132
+ items: decoded.items.map((item) => ({
24133
+ amount: item.amount,
24134
+ gross: item.gross,
24135
+ net: item.net,
24136
+ adjustments: item.adjustments,
24137
+ partyId: toPartyId(item.partyId),
24138
+ to: item.to.kind === "party" ? {
24139
+ kind: "party",
24140
+ partyId: toPartyId(item.to.partyId)
24141
+ } : item.to.kind === "address" ? {
24142
+ kind: "address",
24143
+ address: toAddress(item.to.address)
24144
+ } : item.to
24145
+ }))
24146
+ };
24147
+ if (input.items.length === 0 || input.period.start > input.period.end) throw Errors.invalidInput("input", "Payroll requires items and an ordered period");
24148
+ const error = validateRunItems(input);
24149
+ if (error !== null) throw error;
24150
+ return input;
24151
+ } catch (cause) {
24152
+ throw cause instanceof CapxulError ? cause : Errors.invalidInput("input", "Invalid Payroll input");
24153
+ }
24154
+ }
24155
+ /** Read only the exact backend prepare receipt, never an arbitrary signer failure. */
24156
+ function payrollResolution(error, requestKey) {
24157
+ const details = error.details;
24158
+ if (error.code !== "WRONG_STATE" || details?.method !== runs.authorize.name || details.requestKey !== requestKey || typeof details.commandId !== "string" || typeof details.runId !== "string") return void 0;
24159
+ return details.payrollResolution === "failed" || details.payrollResolution === "settled" ? details.payrollResolution : void 0;
24160
+ }
24161
+ function validatePayrollRunLookup(orgId, runId) {
24162
+ return !Schema.is(PayrollRunIdSchema)(runId) || orgId.length === 0 ? {
24163
+ ok: false,
24164
+ error: Errors.invalidInput("runId", "Organization and run ID must be valid")
24165
+ } : {
24166
+ ok: true,
24167
+ value: void 0
24168
+ };
24169
+ }
24170
+ /** Strict saved detail decoder shared by the read and fresh stream. */
24171
+ function decodePayrollRunDetail(raw, orgId, runId) {
24172
+ const decoded = Schema.decodeUnknownResult(Schema.NullOr(PayrollRunDetailSchema), { onExcessProperty: "error" })(raw);
24173
+ if (decoded._tag === "Failure") return {
24174
+ ok: false,
24175
+ error: Errors.invalidInput("run", "Invalid Payroll run detail")
24176
+ };
24177
+ return checkPayrollRunDetail(decoded.success, orgId, runId);
24178
+ }
24179
+ //#endregion
24180
+ //#region ../../packages/sdk/src/payroll/run-subscription.ts
24181
+ function detailValue(result) {
24182
+ if (!result.ok) throw result.error;
24183
+ return result.value;
24184
+ }
24185
+ function subscribePayrollRun(deps, orgId, runId, listener, options) {
24186
+ const operation = CAPXUL_OPERATIONS.org.payroll.subscribeRun;
24187
+ const valid = deps.validate(orgId, runId);
24188
+ if (!valid.ok) return runCapxulEffect(Effect.fail(valid.error), observationOnlyControls(deps.invocationControls?.(options) ?? options), operation, deps.runPromise);
24189
+ const decode = (raw) => detailValue(deps.decode(raw, orgId, runId));
24190
+ return subscribeFreshQuery(deps, {
24191
+ orgId,
24192
+ runId
24193
+ }, listener, operation, {
24194
+ decode,
24195
+ validateCached: (raw) => {
24196
+ decode(raw);
24197
+ },
24198
+ invalidValue: (cause) => isCapxulError(cause) ? cause : Errors.invalidInput("run", "Invalid Payroll run detail"),
24199
+ invalidScope: () => Errors.invalidInput("run", "Payroll run subscription scope does not match"),
24200
+ lostValue: () => Errors.invalidInput("run", "Payroll run is no longer readable"),
24201
+ unavailable: () => Errors.notImplemented("payroll", "subscribeRun")
24202
+ }, options);
24203
+ }
24204
+ //#endregion
24205
+ //#region ../../packages/sdk/src/payroll/run-authorization.ts
24206
+ /**
24207
+ * Authorize one payroll run: a stable request key, the prepared batch checked
24208
+ * against the run the caller asked for, the signature, and the submission.
24209
+ */
24210
+ async function authorizePayrollRun(scope, input, controls, onRequestKey, onResolved) {
24211
+ const { deps, orgId, requestKeyScope } = scope;
24212
+ const signal = controls?.signal;
24213
+ const execution = executionDependencies(deps);
24214
+ if (execution === null) return {
24215
+ ok: false,
24216
+ error: Errors.notImplemented("payroll", "executionComposition")
24217
+ };
24218
+ try {
24219
+ input = decodePayrollRunInput(input);
24220
+ } catch (cause) {
24221
+ return {
24222
+ ok: false,
24223
+ error: asCapxulError(cause)
24224
+ };
24225
+ }
24226
+ const { requestKey: provided, ...canonicalInput } = input;
24227
+ const actorId = onRequestKey === void 0 ? void 0 : execution.actor.authSession()?.authUserId;
24228
+ if (onRequestKey !== void 0 && actorId === void 0) return {
24229
+ ok: false,
24230
+ error: Errors.notAuthenticated()
24231
+ };
24232
+ const requestKey = await paymentRequestKeyLifecycle(`${requestKeyScope}:${AUTHORIZE_RUN}`, onRequestKey === void 0 ? {
24233
+ orgId,
24234
+ input
24235
+ } : {
24236
+ actorId,
24237
+ orgId,
24238
+ input: canonicalInput
24239
+ }, provided, onRequestKey === void 0 ? void 0 : { manageProvidedKey: true });
24240
+ try {
24241
+ await onRequestKey?.(requestKey.key);
24242
+ if (onRequestKey !== void 0 && execution.actor.authSession()?.authUserId !== actorId) {
24243
+ await requestKey.finish(false).catch(() => void 0);
24244
+ return {
24245
+ ok: false,
24246
+ error: Errors.notAuthenticated()
24247
+ };
24248
+ }
24249
+ } catch (cause) {
24250
+ await requestKey.finish(false).catch(() => void 0);
24251
+ return {
24252
+ ok: false,
24253
+ error: asCapxulError(cause)
24254
+ };
24255
+ }
24256
+ let authorized;
24257
+ let acceptedReplay;
24258
+ let preparedClosed = false;
24259
+ let terminalReplay;
24260
+ const submitted = await executePrepared(execution, async (signerAddress) => {
24261
+ const prepared = await runIfActive(signal, AUTHORIZE_RUN, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, runs.authorize, copyInvocationObservation(controls, {
24262
+ orgId,
24263
+ permissionId: input.permissionId,
24264
+ asset: input.asset,
24265
+ requestKey: requestKey.key,
24266
+ signerAddress,
24267
+ period: input.period,
24268
+ items: input.items
24269
+ }))).pipe(Effect.mapError((publicError) => ({ publicError }))), controls), deps.runPromise);
24270
+ if (!prepared.ok) {
24271
+ terminalReplay = payrollResolution(prepared.error, requestKey.key);
24272
+ return prepared;
24273
+ }
24274
+ authorized = prepared.value.run;
24275
+ acceptedReplay = prepared.value.replay.kind === "accepted" ? prepared.value.replay.submission : void 0;
24276
+ return {
24277
+ ok: true,
24278
+ value: prepared.value.execution
24279
+ };
24280
+ }, (request, evidence) => isAuthorizedRunBatch({
24281
+ orgId,
24282
+ permissionId: input.permissionId,
24283
+ requestKey: requestKey.key,
24284
+ asset: input.asset,
24285
+ period: input.period,
24286
+ items: input.items
24287
+ }, authorized)(request, evidence), true, () => preparedClosed = true, signal, controls, void 0, () => acceptedReplay);
24288
+ const resolution = preparedClosed ? "failed" : terminalReplay;
24289
+ if (resolution !== void 0) try {
24290
+ await onResolved?.(resolution);
24291
+ } catch {}
24292
+ try {
24293
+ await requestKey.finish(submitted.ok || resolution !== void 0);
24294
+ } catch {}
24295
+ if (!submitted.ok) return submitted;
24296
+ return authorized === void 0 ? {
24297
+ ok: false,
24298
+ error: Errors.unknown()
24299
+ } : {
24300
+ ok: true,
24301
+ value: toPayrollRun(authorized)
24302
+ };
24303
+ }
24304
+ const AUTHORIZE_RUN = runs.authorize.name;
24305
+ function executionDependencies(deps) {
24306
+ return deps.actor === void 0 || deps.chainId === void 0 || deps.signer === void 0 ? null : {
24307
+ actor: deps.actor,
24308
+ chainId: deps.chainId,
24309
+ convexCall: deps.convexCall,
24310
+ runPromise: deps.runPromise,
24311
+ signer: deps.signer,
24312
+ ...deps.telemetry === void 0 ? {} : { telemetry: deps.telemetry }
24313
+ };
24314
+ }
24315
+ //#endregion
24316
+ //#region ../../packages/sdk/src/payroll/runs.ts
24317
+ function makePayrollMethods(deps, orgId) {
24318
+ const requestKeyScope = deps.requestKeyScope ?? `client-${crypto.randomUUID()}`;
24319
+ /** One observed call of one payroll row, its reply projected inside the call, under the row's name. */
24320
+ const call = (row, args, options, project) => {
24321
+ const controls = deps.invocationControls?.(options) ?? options;
24322
+ return runPortEffect(callOperation(deps.convexCall, row, copyInvocationObservation(controls, args)).pipe(Effect.map(project), Effect.mapError((publicError) => ({ publicError }))), controls, row.name, deps.runPromise);
24323
+ };
24324
+ return {
24325
+ subscribeRun: (runId, listener, options) => subscribePayrollRun({
24326
+ actor: deps.actor,
24327
+ convexCall: deps.convexCall,
24328
+ query: reference(runs.get),
24329
+ runPromise: deps.runPromise,
24330
+ invocationControls: deps.invocationControls,
24331
+ decode: decodePayrollRunDetail,
24332
+ validate: validatePayrollRunLookup
24333
+ }, orgId, runId, listener, options),
24334
+ getRun: (runId, options) => {
24335
+ const controls = deps.invocationControls?.(options) ?? options;
24336
+ const lifetime = deps.actor?.authLifetime();
24337
+ const operation = runs.get.name;
24338
+ return runCapxulEffect(Effect.gen(function* () {
24339
+ const lookup = validatePayrollRunLookup(orgId, runId);
24340
+ if (!lookup.ok) return yield* Effect.fail(lookup.error);
24341
+ if (lifetime === void 0 || lifetime.authUserId === null || deps.actor === void 0) return yield* Effect.fail(Errors.notAuthenticated());
24342
+ const current = () => deps.actor?.matchesAuthLifetime(lifetime, lifetime.authUserId) === true;
24343
+ if (!current()) return yield* Effect.fail(Errors.notAuthenticated());
24344
+ const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
24345
+ const captured = carryInvocationParentSpan(controls, parent);
24346
+ const run = yield* awaitCapxulResult(() => runIfActive(controls?.signal, operation, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, runs.get, copyInvocationObservation(captured, {
24347
+ orgId,
24348
+ runId
24349
+ }))).pipe(Effect.mapError((publicError) => ({ publicError }))), captured), deps.runPromise));
24350
+ if (!current()) return yield* Effect.fail(Errors.cancelled({ operation }));
24351
+ const checked = checkPayrollRunDetail(run, orgId, runId);
24352
+ return yield* checked.ok ? Effect.succeed(checked.value) : Effect.fail(checked.error);
24353
+ }), observationOnlyControls(controls), operation, deps.runPromise);
24354
+ },
24355
+ runs: (options) => call(runs.list, { orgId }, options, toPayrollRuns),
24356
+ authorizeRun: (input, options) => {
24357
+ const controls = deps.invocationControls?.(options) ?? options;
24358
+ return runCapxulEffect(Effect.gen(function* () {
24359
+ if (controls?.signal?.aborted === true) return yield* Effect.fail(Errors.cancelled({ operation: AUTHORIZE_RUN }));
24360
+ const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
24361
+ return yield* awaitCapxulResult(() => authorizePayrollRun({
24362
+ deps,
24363
+ orgId,
24364
+ requestKeyScope
24365
+ }, input, carryInvocationParentSpan(controls, parent), options?.onRequestKey, options?.onResolved));
24366
+ }), observationOnlyControls(controls), AUTHORIZE_RUN, deps.runPromise);
24367
+ },
24368
+ terms: (options) => call(terms.list, { orgId }, options, (rates) => rates),
24369
+ groups: {
24370
+ list: (options) => call(groups.list, { orgId }, options, (saved) => mapPage(saved, toPayrollGroup)),
24371
+ save: (input, options) => call(groups.save, {
24372
+ orgId,
24373
+ ...input.id === void 0 ? {} : { id: String(input.id) },
24374
+ name: input.name,
24375
+ tone: input.tone,
24376
+ members: input.members
24377
+ }, options, toPayrollGroup),
24378
+ remove: (id, options) => call(groups.remove, {
24379
+ orgId,
24380
+ id
24381
+ }, options, () => void 0)
24382
+ }
24383
+ };
24384
+ }
24385
+ const payrollUnavailable = () => Promise.resolve({
24386
+ ok: false,
24387
+ error: Errors.notImplemented("payroll", "executionComposition")
24388
+ });
24389
+ /** The bundle an unconfigured client exposes — every verb refuses, none throws. */
24390
+ function makeUnavailablePayrollMethods() {
24391
+ return {
24392
+ getRun: payrollUnavailable,
24393
+ subscribeRun: payrollUnavailable,
24394
+ runs: payrollUnavailable,
24395
+ authorizeRun: payrollUnavailable,
24396
+ terms: payrollUnavailable,
24397
+ groups: {
24398
+ list: payrollUnavailable,
24399
+ save: payrollUnavailable,
24400
+ remove: payrollUnavailable
24401
+ }
24402
+ };
24403
+ }
24404
+ //#endregion
24405
+ //#region ../../packages/sdk/src/accounts/command-approvals.ts
24406
+ /**
24407
+ * Keep a prepared command only when it is the one asked for and its operation
24408
+ * is the person's own. A mismatch is closed unsent when `close`, as the signed
24409
+ * path closes a new Payment; a retry or a request keeps its Payment open.
24410
+ */
24411
+ async function checked(deps, signerAddress, prepared, expected, close) {
24412
+ if (!prepared.ok) return prepared;
24413
+ const session = deps.actor?.authSession() ?? null;
24414
+ if (session === null || deps.chainId === void 0) return {
24415
+ ok: false,
24416
+ error: Errors.notAuthenticated()
24417
+ };
24418
+ const evidence = prepared.value;
24419
+ if (await expected(evidence.request, evidence) && await verifiedPreparedCommand(evidence, session.email, signerAddress, deps.chainId)) return prepared;
24420
+ const refused = {
24421
+ ok: false,
24422
+ error: Errors.invalidInput("execution", "prepared command mismatch")
24423
+ };
24424
+ return close ? abandonPrepared(deps, evidence.executionId, "prepared_mismatch", refused) : refused;
24425
+ }
24426
+ /** A retry, a request payment or a payroll run, rather than a new Payment. */
24427
+ const isCommandApproval = (input) => "retry" in input || "request" in input || "payrollRun" in input;
24428
+ /** Prepare a retry, a request payment or a payroll run for `signerAddress`, checked. */
24429
+ function prepareVerifiedCommand(deps, input, signerAddress, signal) {
24430
+ if ("retry" in input) return prepareVerifiedRetry(deps, input, signerAddress, signal);
24431
+ if ("request" in input) return prepareVerifiedRequestPayment(deps, input, signerAddress, signal);
24432
+ return prepareVerifiedPayrollRun(deps, input, signerAddress, signal);
24433
+ }
24434
+ /** The retry of one Payment, prepared in the scope that paid it. */
24435
+ async function prepareVerifiedRetry(deps, input, signerAddress, signal) {
24436
+ const intent = {
24437
+ kind: "retry",
24438
+ paymentId: input.retry
24439
+ };
24440
+ return checked(deps, signerAddress, await runIfActive(signal, CAPXUL_OPERATIONS.payments.prepareLifecycleExecution, () => callOperation(deps.convexCall, lifecycles.prepareScoped, {
24441
+ signerAddress,
24442
+ intent,
24443
+ expectedScope: input.organizationId === void 0 ? { kind: "account" } : {
24444
+ kind: "organization",
24445
+ orgId: input.organizationId
24446
+ }
24447
+ }).pipe(Effect.mapError((publicError) => ({ publicError }))), deps.runPromise), lifecycleCommandCheck(intent, input.organizationId), false);
24448
+ }
24449
+ /** The Payment of one request, exactly as the request froze it. */
24450
+ async function prepareVerifiedRequestPayment(deps, input, signerAddress, signal) {
24451
+ const decoded = Schema.decodeResult(RequestPaymentConfirmationSchema, { onExcessProperty: "error" })(input.request);
24452
+ if (decoded._tag === "Failure") return {
24453
+ ok: false,
24454
+ error: Errors.invalidInput("confirmation", "invalid request confirmation")
24455
+ };
24456
+ const confirmation = decoded.success;
24457
+ return checked(deps, signerAddress, await runIfActive(signal, commands$1.prepareRequest.name, () => callOperation(deps.convexCall, commands$1.prepareRequest, {
24458
+ confirmation,
24459
+ signerAddress
24460
+ }).pipe(Effect.mapError((publicError) => ({ publicError }))), deps.runPromise), requestPaymentCheck(confirmation), false);
24461
+ }
24462
+ /** One payroll run: the run the backend writes and the batch that pays it, checked together. */
24463
+ async function prepareVerifiedPayrollRun(deps, input, signerAddress, signal) {
24464
+ let run;
24465
+ try {
24466
+ run = decodePayrollRunInput(input.payrollRun);
24467
+ } catch (cause) {
24468
+ return {
24469
+ ok: false,
24470
+ error: asCapxulError(cause)
24471
+ };
24472
+ }
24473
+ const orgId = input.organizationId;
24474
+ const requestKey = run.requestKey ?? `payroll_${crypto.randomUUID()}`;
24475
+ const { permissionId, asset, period, items } = run;
24476
+ const authorized = await runIfActive(signal, runs.authorize.name, () => callOperation(deps.convexCall, runs.authorize, {
24477
+ orgId,
24478
+ permissionId,
24479
+ asset,
24480
+ requestKey,
24481
+ signerAddress,
24482
+ period,
24483
+ items
24484
+ }).pipe(Effect.mapError((publicError) => ({ publicError }))), deps.runPromise);
24485
+ if (!authorized.ok) return authorized;
24486
+ return checked(deps, signerAddress, {
24487
+ ok: true,
24488
+ value: authorized.value.execution
24489
+ }, isAuthorizedRunBatch({
24490
+ orgId,
24491
+ permissionId,
24492
+ requestKey,
24493
+ asset,
24494
+ period,
24495
+ items
24496
+ }, authorized.value.run), authorized.value.replay.kind !== "accepted");
24497
+ }
24498
+ //#endregion
23862
24499
  //#region ../../packages/sdk/src/accounts/organization-approvals.ts
23863
24500
  /** The Organization Payment prepared for `signerAddress` and checked, before any approval. */
23864
24501
  async function prepareVerifiedOrganizationPayment(deps, input, signerAddress, signal) {
@@ -23965,6 +24602,7 @@ function makeApprovalsMethods(deps) {
23965
24602
  };
23966
24603
  const prepare = async (input, options) => {
23967
24604
  const signal = options?.signal;
24605
+ if (isCommandApproval(input)) return openApproval(signal, (signerAddress) => prepareVerifiedCommand(deps, input, signerAddress, signal));
23968
24606
  if ("organizationId" in input) return openApproval(signal, (signerAddress) => prepareVerifiedOrganizationPayment(deps, input, signerAddress, signal));
23969
24607
  const intent = paymentExecutionIntent(input);
23970
24608
  if (!intent.ok) return intent;
@@ -24057,7 +24695,7 @@ function makeApprovalsMethods(deps) {
24057
24695
  ok: true,
24058
24696
  value: null
24059
24697
  };
24060
- const payments = await readApprovalPayments(deps, approval.operation.paymentIds, organizationId, signal);
24698
+ const payments = await readApprovalPayments(deps, approval.operation.paymentIds, organizationId ?? (approval.actor.kind === "organization" ? approval.actor.orgId : void 0), signal);
24061
24699
  if (!payments.ok) return payments;
24062
24700
  if (unsent(approval, payments.value)) {
24063
24701
  const sent = await submit(approvalId);
@@ -26894,14 +27532,6 @@ function executeIdentityProductObservation(telemetry, record, state) {
26894
27532
  return ignoreTransportFailure(() => invocationTelemetry.emit(mapped), "emit", mapped.name).pipe(Effect.andThen(ignoreTransportFailure(after, mapped.name === "auth_signed_out" ? "reset" : "identify", mapped.name)));
26895
27533
  }
26896
27534
  //#endregion
26897
- //#region ../../packages/protocol/src/payroll/model.ts
26898
- function toPayrollRunId(raw) {
26899
- return toNonEmptyStringBrand(raw, "payrollRunId", (value) => isNonEmptyString(value));
26900
- }
26901
- function toPayrollGroupId(raw) {
26902
- return toNonEmptyStringBrand(raw, "payrollGroupId", (value) => isNonEmptyString(value));
26903
- }
26904
- //#endregion
26905
27535
  //#region ../../packages/sdk/src/permissions/command-decoding.ts
26906
27536
  /** The caller's input as a command: ids branded and the intent decoded, or invalid input thrown. */
26907
27537
  const buildCommand = {
@@ -36563,13 +37193,15 @@ function normalizeOrgBudgetPolicy(policy) {
36563
37193
  if (actionSet.has(action)) throw Errors.invalidInput("policy.actions", `duplicate Budget action: ${action}`);
36564
37194
  actionSet.add(action);
36565
37195
  }
37196
+ if (policy.refill === "monthly" && limit.kind === "unlimited") throw Errors.invalidInput("policy.refill", "only a capped Budget can refill");
36566
37197
  return {
36567
37198
  assetId,
36568
37199
  tokenAddress,
36569
37200
  decimals: policy.asset.decimals,
36570
37201
  limit,
36571
37202
  recipients,
36572
- actions: ORG_BUDGET_ACTIONS.filter((action) => actionSet.has(action))
37203
+ actions: ORG_BUDGET_ACTIONS.filter((action) => actionSet.has(action)),
37204
+ refillPeriod: policy.refill === "monthly" ? MONTHLY_REFILL_PERIOD_SECONDS : 0n
36573
37205
  };
36574
37206
  }
36575
37207
  function resolvedBudgetApprovalPermission(tokenAddress) {
@@ -36644,8 +37276,8 @@ function buildOrgBudgetCallsWithEncoder(input, encodeRoles) {
36644
37276
  allowanceKey,
36645
37277
  BigInt(policy.limit.rawUnits),
36646
37278
  BigInt(policy.limit.rawUnits),
36647
- 0n,
36648
- 0n,
37279
+ policy.refillPeriod === 0n ? 0n : BigInt(policy.limit.rawUnits),
37280
+ policy.refillPeriod,
36649
37281
  0n
36650
37282
  ]
36651
37283
  }),
@@ -36839,13 +37471,20 @@ function buildExplicitBootstrapRoleCalls(input, encodeRoles) {
36839
37471
  roles: [{
36840
37472
  key: managementRoleKey,
36841
37473
  members: [input.ownerAddress],
36842
- permissions: [{
36843
- targetAddress: input.rolesModuleAddress,
36844
- signature: "assignRoles(address,bytes32[],bool[])"
36845
- }, ...ROLE_MANAGEMENT_SIGNATURES.map((signature) => ({
36846
- targetAddress: input.rolesModuleAddress,
36847
- signature
36848
- }))]
37474
+ permissions: [
37475
+ {
37476
+ targetAddress: input.rolesModuleAddress,
37477
+ signature: "assignRoles(address,bytes32[],bool[])"
37478
+ },
37479
+ ...ROLE_MANAGEMENT_SIGNATURES.map((signature) => ({
37480
+ targetAddress: input.rolesModuleAddress,
37481
+ signature
37482
+ })),
37483
+ ...OWNER_COMMITMENT_SIGNATURES.map((signature) => ({
37484
+ targetAddress: CAPXUL_PAYMENTS_V2_ADDRESS,
37485
+ signature
37486
+ }))
37487
+ ]
36849
37488
  }]
36850
37489
  }).map((call) => ({
36851
37490
  to: toLowerHex(call.to, "setUpRoles.call.to"),
@@ -37310,7 +37949,7 @@ function matchesCurrentPolicy(intent, policy) {
37310
37949
  asset: policy.asset,
37311
37950
  value: policy.limit
37312
37951
  }, CONFIGURED_MONEY_ASSETS).minorUnits;
37313
- return policy.asset === intent.asset && raw === recorded && (intent.scope === void 0 || canonicalJson(intent.scope) === canonicalJson(policy.scope));
37952
+ return policy.asset === intent.asset && raw === recorded && intent.refill === "monthly" === (policy.refill === "monthly") && (intent.scope === void 0 || canonicalJson(intent.scope) === canonicalJson(policy.scope));
37314
37953
  }
37315
37954
  function previousPolicyForChange(effect, command) {
37316
37955
  if (effect.previousIntent.type === "budget" && (effect.previousIntent.scope === void 0 || effect.previousRecipientBindings === void 0)) throw new EvidenceMismatch("Prior complete Budget proof is unavailable");
@@ -37328,8 +37967,9 @@ function policyCalls(evidence, context, command) {
37328
37967
  const roleKey = effect.operation === "change" ? effect.roleKey : roleKeyForPermission(permissionId);
37329
37968
  const revision = effect.operation === "change" ? effect.revision : 0;
37330
37969
  if (effect.operation !== command.operation || effect.operation === "change" && effect.permissionId !== permissionId || evidence.expectedRevision !== revision) throw new EvidenceMismatch("Permission policy binding mismatch");
37970
+ const intent = effect.operation === "change" ? keepRefillOnChange(command.intent, effect.previousCurrentPolicy) : command.intent;
37331
37971
  const value = evidence.value;
37332
- if (Array.isArray(value) || !("type" in value) || !matchesCurrentPolicy(command.intent, value.currentPolicy) || !permissionMatches(value, context.orgId, permissionId, command.operation === "create" ? 0 : revision + 1, command.intent, roleKey)) throw new EvidenceMismatch("Permission value mismatch");
37972
+ if (Array.isArray(value) || !("type" in value) || !matchesCurrentPolicy(intent, value.currentPolicy) || !permissionMatches(value, context.orgId, permissionId, command.operation === "create" ? 0 : revision + 1, intent, roleKey)) throw new EvidenceMismatch("Permission value mismatch");
37333
37973
  const planned = compilePermissionPolicyCallsWithEncoder({
37334
37974
  permissionId,
37335
37975
  roleKey,
@@ -37337,7 +37977,7 @@ function policyCalls(evidence, context, command) {
37337
37977
  actorSafe: evidence.userOpSenderSafe,
37338
37978
  rolesModule: context.rolesModule,
37339
37979
  managerRoleKey: context.managerRoleKey,
37340
- nextIntent: encodingIntent(command.intent, effect.recipientBindings),
37980
+ nextIntent: encodingIntent(intent, effect.recipientBindings),
37341
37981
  ...previous === void 0 ? {} : { previousIntent: previous },
37342
37982
  ...effect.operation !== "change" || effect.previousAllowanceKey === null ? {} : { previousAllowanceKey: effect.previousAllowanceKey }
37343
37983
  }, encodeRoles);
@@ -37389,7 +38029,8 @@ function encodingIntent(intent, bindings) {
37389
38029
  "send",
37390
38030
  "sendBatch",
37391
38031
  "createCommitment"
37392
- ].filter((action) => action === "createCommitment" ? intent.scope.actions.includes("commitments") : intent.scope.actions.includes("pay"))
38032
+ ].filter((action) => action === "createCommitment" ? intent.scope.actions.includes("commitments") : intent.scope.actions.includes("pay")),
38033
+ ...intent.refill === "monthly" ? { refill: intent.refill } : {}
37393
38034
  }
37394
38035
  };
37395
38036
  }
@@ -38042,411 +38683,6 @@ function makePermissionMethods(deps, orgId) {
38042
38683
  };
38043
38684
  }
38044
38685
  //#endregion
38045
- //#region ../../packages/sdk/src/payroll/run-checks.ts
38046
- /** Match the caller's compensation to the committed V3 Payslip. */
38047
- function payslipIdentityMatches(message, item, index, runId, period) {
38048
- return message.runId === runId && message.itemIndex === index && message.employeeRef === item.partyId && message.periodStart === period.start && message.periodEnd === period.end && message.adjustmentsHash === hashAdjustmentsV3(item.adjustments);
38049
- }
38050
- function payslipAmountMatches(message, item) {
38051
- const exact = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
38052
- return message.gross.asset === exact.asset && message.gross.minorUnits === item.gross && message.gross.decimals === exact.decimals && message.net.asset === exact.asset && message.net.minorUnits === item.net && message.net.decimals === exact.decimals;
38053
- }
38054
- function isRunItem(prepared, item, evidence, index, runId, period) {
38055
- if (prepared === void 0) return false;
38056
- const bundle = evidence.preparedCommand?.documentBundles?.[index];
38057
- const preparedItem = evidence.preparedCommand?.items[index];
38058
- const payslip = bundle?.envelope;
38059
- if (bundle === void 0 || payslip?.primaryType !== "Payslip") return false;
38060
- return canonicalJson(prepared.to) === canonicalJson(item.to) && prepared.paymentType === "payroll" && canonicalJson(prepared.amount) === canonicalJson(item.amount) && bundle.adjustments !== void 0 && canonicalJson(bundle.adjustments) === canonicalJson(item.adjustments) && payslipIdentityMatches(payslip.message, item, index, runId, period) && payslipAmountMatches(payslip.message, item) && (preparedItem?.operation === "send" || preparedItem?.operation === "create_commitment") && preparedItem.documentHash === evidence.preparedCommand?.instructionDocuments?.[index]?.documentHash;
38061
- }
38062
- /**
38063
- * The batch the backend derived from a payroll intent must be the batch this
38064
- * caller asked for, checked before the digest is signed.
38065
- *
38066
- * The payslip envelope on each item is server-built and carries the run row's
38067
- * own `issuedAt`, so the batch cannot be fingerprinted against a local copy
38068
- * (`matchesIntent` is unavailable here). What CAN be verified is every field
38069
- * the caller supplied — the org, the Budget, the request key, and per item the
38070
- * recipient, the amount and the payslip figures — plus the lineage binding the
38071
- * batch to the run just written. A prepared command that pays a different
38072
- * person, a different amount, from a different Budget, or under a different
38073
- * run never reaches the signer.
38074
- */
38075
- function isRunBatch(input) {
38076
- return (batch, evidence) => Promise.resolve(!("kind" in batch) && batch.orgId === input.orgId && batch.permissionId === input.permissionId && batch.requestKey === input.requestKey && batch.lineage?.kind === "payroll" && batch.lineage.runId === input.runId && batch.items.length === input.items.length && batch.items.every((item) => item.amount.asset === input.asset) && input.items.every((item, index) => isRunItem(batch.items[index], item, evidence, index, input.runId, input.period)));
38077
- }
38078
- /** A decoded detail is the run asked for, whole: its scope, membership and quantities agree. */
38079
- function checkPayrollRunDetail(value, orgId, runId) {
38080
- if (value === null) return {
38081
- ok: true,
38082
- value: null
38083
- };
38084
- if (value.orgId !== orgId || value.run.id !== runId || value.items.length === 0 || value.items.length !== value.run.recipientCount || new Set(value.items.map((item) => item.payment.id)).size !== value.items.length || value.items.some((item, index) => item.itemIndex !== index || item.amount.asset !== value.run.total.asset || item.payment.amount.asset !== item.amount.asset || item.payment.paymentType !== "payroll")) return {
38085
- ok: false,
38086
- error: Errors.invalidInput("run", "Payroll run scope or membership does not match")
38087
- };
38088
- try {
38089
- const total = toExactAssetAmount(value.run.total, CONFIGURED_MONEY_ASSETS);
38090
- let rawTotal = 0n;
38091
- for (const item of value.items) {
38092
- const amount = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
38093
- const payment = toExactAssetAmount(item.payment.amount, CONFIGURED_MONEY_ASSETS);
38094
- if (amount.minorUnits !== payment.minorUnits) return {
38095
- ok: false,
38096
- error: Errors.invalidInput("run", "Payroll item quantity does not match its Payment")
38097
- };
38098
- rawTotal += BigInt(amount.minorUnits);
38099
- }
38100
- if (rawTotal !== BigInt(total.minorUnits)) return {
38101
- ok: false,
38102
- error: Errors.invalidInput("run", "Payroll run total does not match its items")
38103
- };
38104
- } catch {
38105
- return {
38106
- ok: false,
38107
- error: Errors.invalidInput("run", "Payroll run quantities are invalid")
38108
- };
38109
- }
38110
- return {
38111
- ok: true,
38112
- value
38113
- };
38114
- }
38115
- //#endregion
38116
- //#region ../../packages/sdk/src/payroll/run-decoding.ts
38117
- function toPayrollRun(wire) {
38118
- return {
38119
- id: toPayrollRunId(wire.id),
38120
- status: wire.status,
38121
- periodStart: wire.periodStart,
38122
- periodEnd: wire.periodEnd,
38123
- total: wire.total,
38124
- recipientCount: wire.recipientCount
38125
- };
38126
- }
38127
- function toPayrollRuns(wire) {
38128
- return {
38129
- ...mapPage(wire, toPayrollRun),
38130
- settledThisMonth: wire.settledThisMonth
38131
- };
38132
- }
38133
- function toPayrollGroup(wire) {
38134
- return {
38135
- id: toPayrollGroupId(wire.id),
38136
- name: wire.name,
38137
- tone: wire.tone,
38138
- members: wire.members.map((member) => ({
38139
- partyId: member.partyId,
38140
- amount: member.amount,
38141
- currency: member.currency
38142
- }))
38143
- };
38144
- }
38145
- function validateRunItems(input) {
38146
- for (const [index, item] of input.items.entries()) {
38147
- if (Schema.decodeResult(PayrollRunItemInputSchema, { onExcessProperty: "error" })(item)._tag === "Failure") return Errors.invalidInput(`items.${index}`, "invalid Payroll item");
38148
- try {
38149
- const exact = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
38150
- const net = BigInt(item.net);
38151
- const gross = BigInt(item.gross);
38152
- const adjustments = item.adjustments.reduce((sum, row) => sum + BigInt(row.amount), 0n);
38153
- if (item.amount.asset !== input.asset || item.net !== exact.minorUnits || gross + adjustments !== net) return Errors.invalidInput(`items.${index}`, "invalid Payroll units");
38154
- } catch {
38155
- return Errors.invalidInput(`items.${index}`, "invalid Payroll units");
38156
- }
38157
- }
38158
- return null;
38159
- }
38160
- const RunInputSchema = Schema.Struct({
38161
- permissionId: AuthorizePayrollRunExecutionInputSchema.fields.permissionId,
38162
- asset: AuthorizePayrollRunExecutionInputSchema.fields.asset,
38163
- period: AuthorizePayrollRunExecutionInputSchema.fields.period,
38164
- items: AuthorizePayrollRunExecutionInputSchema.fields.items,
38165
- requestKey: Schema.optional(AuthorizePayrollRunExecutionInputSchema.fields.requestKey)
38166
- });
38167
- /** Decode caller input before session or signer work. Keep routing outside the input. */
38168
- function decodePayrollRunInput(value) {
38169
- try {
38170
- const decoded = Schema.decodeUnknownSync(RunInputSchema, { onExcessProperty: "error" })(value);
38171
- const input = {
38172
- permissionId: decoded.permissionId,
38173
- asset: decoded.asset,
38174
- period: decoded.period,
38175
- ...decoded.requestKey === void 0 ? {} : { requestKey: decoded.requestKey },
38176
- items: decoded.items.map((item) => ({
38177
- amount: item.amount,
38178
- gross: item.gross,
38179
- net: item.net,
38180
- adjustments: item.adjustments,
38181
- partyId: toPartyId(item.partyId),
38182
- to: item.to.kind === "party" ? {
38183
- kind: "party",
38184
- partyId: toPartyId(item.to.partyId)
38185
- } : item.to.kind === "address" ? {
38186
- kind: "address",
38187
- address: toAddress(item.to.address)
38188
- } : item.to
38189
- }))
38190
- };
38191
- if (input.items.length === 0 || input.period.start > input.period.end) throw Errors.invalidInput("input", "Payroll requires items and an ordered period");
38192
- const error = validateRunItems(input);
38193
- if (error !== null) throw error;
38194
- return input;
38195
- } catch (cause) {
38196
- throw cause instanceof CapxulError ? cause : Errors.invalidInput("input", "Invalid Payroll input");
38197
- }
38198
- }
38199
- /** Read only the exact backend prepare receipt, never an arbitrary signer failure. */
38200
- function payrollResolution(error, requestKey) {
38201
- const details = error.details;
38202
- if (error.code !== "WRONG_STATE" || details?.method !== runs.authorize.name || details.requestKey !== requestKey || typeof details.commandId !== "string" || typeof details.runId !== "string") return void 0;
38203
- return details.payrollResolution === "failed" || details.payrollResolution === "settled" ? details.payrollResolution : void 0;
38204
- }
38205
- function validatePayrollRunLookup(orgId, runId) {
38206
- return !Schema.is(PayrollRunIdSchema)(runId) || orgId.length === 0 ? {
38207
- ok: false,
38208
- error: Errors.invalidInput("runId", "Organization and run ID must be valid")
38209
- } : {
38210
- ok: true,
38211
- value: void 0
38212
- };
38213
- }
38214
- /** Strict saved detail decoder shared by the read and fresh stream. */
38215
- function decodePayrollRunDetail(raw, orgId, runId) {
38216
- const decoded = Schema.decodeUnknownResult(Schema.NullOr(PayrollRunDetailSchema), { onExcessProperty: "error" })(raw);
38217
- if (decoded._tag === "Failure") return {
38218
- ok: false,
38219
- error: Errors.invalidInput("run", "Invalid Payroll run detail")
38220
- };
38221
- return checkPayrollRunDetail(decoded.success, orgId, runId);
38222
- }
38223
- //#endregion
38224
- //#region ../../packages/sdk/src/payroll/run-subscription.ts
38225
- function detailValue(result) {
38226
- if (!result.ok) throw result.error;
38227
- return result.value;
38228
- }
38229
- function subscribePayrollRun(deps, orgId, runId, listener, options) {
38230
- const operation = CAPXUL_OPERATIONS.org.payroll.subscribeRun;
38231
- const valid = deps.validate(orgId, runId);
38232
- if (!valid.ok) return runCapxulEffect(Effect.fail(valid.error), observationOnlyControls(deps.invocationControls?.(options) ?? options), operation, deps.runPromise);
38233
- const decode = (raw) => detailValue(deps.decode(raw, orgId, runId));
38234
- return subscribeFreshQuery(deps, {
38235
- orgId,
38236
- runId
38237
- }, listener, operation, {
38238
- decode,
38239
- validateCached: (raw) => {
38240
- decode(raw);
38241
- },
38242
- invalidValue: (cause) => isCapxulError(cause) ? cause : Errors.invalidInput("run", "Invalid Payroll run detail"),
38243
- invalidScope: () => Errors.invalidInput("run", "Payroll run subscription scope does not match"),
38244
- lostValue: () => Errors.invalidInput("run", "Payroll run is no longer readable"),
38245
- unavailable: () => Errors.notImplemented("payroll", "subscribeRun")
38246
- }, options);
38247
- }
38248
- //#endregion
38249
- //#region ../../packages/sdk/src/payroll/run-authorization.ts
38250
- /**
38251
- * Authorize one payroll run: a stable request key, the prepared batch checked
38252
- * against the run the caller asked for, the signature, and the submission.
38253
- */
38254
- async function authorizePayrollRun(scope, input, controls, onRequestKey, onResolved) {
38255
- const { deps, orgId, requestKeyScope } = scope;
38256
- const signal = controls?.signal;
38257
- const execution = executionDependencies(deps);
38258
- if (execution === null) return {
38259
- ok: false,
38260
- error: Errors.notImplemented("payroll", "executionComposition")
38261
- };
38262
- try {
38263
- input = decodePayrollRunInput(input);
38264
- } catch (cause) {
38265
- return {
38266
- ok: false,
38267
- error: asCapxulError(cause)
38268
- };
38269
- }
38270
- const { requestKey: provided, ...canonicalInput } = input;
38271
- const actorId = onRequestKey === void 0 ? void 0 : execution.actor.authSession()?.authUserId;
38272
- if (onRequestKey !== void 0 && actorId === void 0) return {
38273
- ok: false,
38274
- error: Errors.notAuthenticated()
38275
- };
38276
- const requestKey = await paymentRequestKeyLifecycle(`${requestKeyScope}:${AUTHORIZE_RUN}`, onRequestKey === void 0 ? {
38277
- orgId,
38278
- input
38279
- } : {
38280
- actorId,
38281
- orgId,
38282
- input: canonicalInput
38283
- }, provided, onRequestKey === void 0 ? void 0 : { manageProvidedKey: true });
38284
- try {
38285
- await onRequestKey?.(requestKey.key);
38286
- if (onRequestKey !== void 0 && execution.actor.authSession()?.authUserId !== actorId) {
38287
- await requestKey.finish(false).catch(() => void 0);
38288
- return {
38289
- ok: false,
38290
- error: Errors.notAuthenticated()
38291
- };
38292
- }
38293
- } catch (cause) {
38294
- await requestKey.finish(false).catch(() => void 0);
38295
- return {
38296
- ok: false,
38297
- error: asCapxulError(cause)
38298
- };
38299
- }
38300
- let authorized;
38301
- let acceptedReplay;
38302
- let preparedClosed = false;
38303
- let terminalReplay;
38304
- const submitted = await executePrepared(execution, async (signerAddress) => {
38305
- const prepared = await runIfActive(signal, AUTHORIZE_RUN, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, runs.authorize, copyInvocationObservation(controls, {
38306
- orgId,
38307
- permissionId: input.permissionId,
38308
- asset: input.asset,
38309
- requestKey: requestKey.key,
38310
- signerAddress,
38311
- period: input.period,
38312
- items: input.items
38313
- }))).pipe(Effect.mapError((publicError) => ({ publicError }))), controls), deps.runPromise);
38314
- if (!prepared.ok) {
38315
- terminalReplay = payrollResolution(prepared.error, requestKey.key);
38316
- return prepared;
38317
- }
38318
- authorized = prepared.value.run;
38319
- acceptedReplay = prepared.value.replay.kind === "accepted" ? prepared.value.replay.submission : void 0;
38320
- return {
38321
- ok: true,
38322
- value: prepared.value.execution
38323
- };
38324
- }, (request, evidence) => authorized === void 0 || authorized.periodStart !== input.period.start || authorized.periodEnd !== input.period.end ? Promise.resolve(false) : isRunBatch({
38325
- orgId,
38326
- permissionId: input.permissionId,
38327
- requestKey: requestKey.key,
38328
- runId: authorized.id,
38329
- asset: input.asset,
38330
- period: input.period,
38331
- items: input.items
38332
- })(request, evidence), true, () => preparedClosed = true, signal, controls, void 0, () => acceptedReplay);
38333
- const resolution = preparedClosed ? "failed" : terminalReplay;
38334
- if (resolution !== void 0) try {
38335
- await onResolved?.(resolution);
38336
- } catch {}
38337
- try {
38338
- await requestKey.finish(submitted.ok || resolution !== void 0);
38339
- } catch {}
38340
- if (!submitted.ok) return submitted;
38341
- return authorized === void 0 ? {
38342
- ok: false,
38343
- error: Errors.unknown()
38344
- } : {
38345
- ok: true,
38346
- value: toPayrollRun(authorized)
38347
- };
38348
- }
38349
- const AUTHORIZE_RUN = runs.authorize.name;
38350
- function executionDependencies(deps) {
38351
- return deps.actor === void 0 || deps.chainId === void 0 || deps.signer === void 0 ? null : {
38352
- actor: deps.actor,
38353
- chainId: deps.chainId,
38354
- convexCall: deps.convexCall,
38355
- runPromise: deps.runPromise,
38356
- signer: deps.signer,
38357
- ...deps.telemetry === void 0 ? {} : { telemetry: deps.telemetry }
38358
- };
38359
- }
38360
- //#endregion
38361
- //#region ../../packages/sdk/src/payroll/runs.ts
38362
- function makePayrollMethods(deps, orgId) {
38363
- const requestKeyScope = deps.requestKeyScope ?? `client-${crypto.randomUUID()}`;
38364
- /** One observed call of one payroll row, its reply projected inside the call, under the row's name. */
38365
- const call = (row, args, options, project) => {
38366
- const controls = deps.invocationControls?.(options) ?? options;
38367
- return runPortEffect(callOperation(deps.convexCall, row, copyInvocationObservation(controls, args)).pipe(Effect.map(project), Effect.mapError((publicError) => ({ publicError }))), controls, row.name, deps.runPromise);
38368
- };
38369
- return {
38370
- subscribeRun: (runId, listener, options) => subscribePayrollRun({
38371
- actor: deps.actor,
38372
- convexCall: deps.convexCall,
38373
- query: reference(runs.get),
38374
- runPromise: deps.runPromise,
38375
- invocationControls: deps.invocationControls,
38376
- decode: decodePayrollRunDetail,
38377
- validate: validatePayrollRunLookup
38378
- }, orgId, runId, listener, options),
38379
- getRun: (runId, options) => {
38380
- const controls = deps.invocationControls?.(options) ?? options;
38381
- const lifetime = deps.actor?.authLifetime();
38382
- const operation = runs.get.name;
38383
- return runCapxulEffect(Effect.gen(function* () {
38384
- const lookup = validatePayrollRunLookup(orgId, runId);
38385
- if (!lookup.ok) return yield* Effect.fail(lookup.error);
38386
- if (lifetime === void 0 || lifetime.authUserId === null || deps.actor === void 0) return yield* Effect.fail(Errors.notAuthenticated());
38387
- const current = () => deps.actor?.matchesAuthLifetime(lifetime, lifetime.authUserId) === true;
38388
- if (!current()) return yield* Effect.fail(Errors.notAuthenticated());
38389
- const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
38390
- const captured = carryInvocationParentSpan(controls, parent);
38391
- const run = yield* awaitCapxulResult(() => runIfActive(controls?.signal, operation, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, runs.get, copyInvocationObservation(captured, {
38392
- orgId,
38393
- runId
38394
- }))).pipe(Effect.mapError((publicError) => ({ publicError }))), captured), deps.runPromise));
38395
- if (!current()) return yield* Effect.fail(Errors.cancelled({ operation }));
38396
- const checked = checkPayrollRunDetail(run, orgId, runId);
38397
- return yield* checked.ok ? Effect.succeed(checked.value) : Effect.fail(checked.error);
38398
- }), observationOnlyControls(controls), operation, deps.runPromise);
38399
- },
38400
- runs: (options) => call(runs.list, { orgId }, options, toPayrollRuns),
38401
- authorizeRun: (input, options) => {
38402
- const controls = deps.invocationControls?.(options) ?? options;
38403
- return runCapxulEffect(Effect.gen(function* () {
38404
- if (controls?.signal?.aborted === true) return yield* Effect.fail(Errors.cancelled({ operation: AUTHORIZE_RUN }));
38405
- const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
38406
- return yield* awaitCapxulResult(() => authorizePayrollRun({
38407
- deps,
38408
- orgId,
38409
- requestKeyScope
38410
- }, input, carryInvocationParentSpan(controls, parent), options?.onRequestKey, options?.onResolved));
38411
- }), observationOnlyControls(controls), AUTHORIZE_RUN, deps.runPromise);
38412
- },
38413
- terms: (options) => call(terms.list, { orgId }, options, (rates) => rates),
38414
- groups: {
38415
- list: (options) => call(groups.list, { orgId }, options, (saved) => mapPage(saved, toPayrollGroup)),
38416
- save: (input, options) => call(groups.save, {
38417
- orgId,
38418
- ...input.id === void 0 ? {} : { id: String(input.id) },
38419
- name: input.name,
38420
- tone: input.tone,
38421
- members: input.members
38422
- }, options, toPayrollGroup),
38423
- remove: (id, options) => call(groups.remove, {
38424
- orgId,
38425
- id
38426
- }, options, () => void 0)
38427
- }
38428
- };
38429
- }
38430
- const payrollUnavailable = () => Promise.resolve({
38431
- ok: false,
38432
- error: Errors.notImplemented("payroll", "executionComposition")
38433
- });
38434
- /** The bundle an unconfigured client exposes — every verb refuses, none throws. */
38435
- function makeUnavailablePayrollMethods() {
38436
- return {
38437
- getRun: payrollUnavailable,
38438
- subscribeRun: payrollUnavailable,
38439
- runs: payrollUnavailable,
38440
- authorizeRun: payrollUnavailable,
38441
- terms: payrollUnavailable,
38442
- groups: {
38443
- list: payrollUnavailable,
38444
- save: payrollUnavailable,
38445
- remove: payrollUnavailable
38446
- }
38447
- };
38448
- }
38449
- //#endregion
38450
38686
  //#region ../../node_modules/.pnpm/axios@1.17.0/node_modules/axios/lib/helpers/bind.js
38451
38687
  /**
38452
38688
  * Create a bound version of a function with a specified `this` context
@@ -62221,6 +62457,22 @@ function normalizeRuntimeUrl(field, raw) {
62221
62457
  }
62222
62458
  //#endregion
62223
62459
  //#region ../../packages/sdk/src/applications/bootstrap-adapter.ts
62460
+ /** A cached envelope this close to its expiry is fetched again, so it cannot lapse mid-command. */
62461
+ const CACHE_EXPIRY_MARGIN_MS = 3e4;
62462
+ /**
62463
+ * The cached envelope, when it is still inside its own lifetime. It is decoded
62464
+ * as a fresh body is; anything that does not decode, or a store that fails,
62465
+ * is a miss.
62466
+ */
62467
+ async function cachedEnvelope(cache, now) {
62468
+ try {
62469
+ const cached = decodeBootstrapBody(await cache.load());
62470
+ const expiresAt = cached.issuedAt + cached.expiresIn - CACHE_EXPIRY_MARGIN_MS;
62471
+ return cached.issuedAt <= now && now < expiresAt ? cached : null;
62472
+ } catch {
62473
+ return null;
62474
+ }
62475
+ }
62224
62476
  const DEFAULT_RETRY = {
62225
62477
  attempts: 2,
62226
62478
  baseDelayMs: 400
@@ -62261,19 +62513,24 @@ var HttpBootstrapAdapter = class {
62261
62513
  fetchImpl;
62262
62514
  observation;
62263
62515
  retry;
62516
+ cache;
62264
62517
  constructor(deps) {
62265
62518
  this.bootstrapBaseUrl = deps.bootstrapBaseUrl.replace(/\/$/, "");
62266
62519
  this.bootstrapHost = hostOf(this.bootstrapBaseUrl);
62267
62520
  this.observation = deps.observation;
62268
62521
  this.retry = deps.retry ?? DEFAULT_RETRY;
62522
+ this.cache = deps.cache;
62269
62523
  this.fetchImpl = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
62270
62524
  }
62271
62525
  resolve(input) {
62272
- return this.attempt(input).pipe(Effect.retry({
62526
+ const fetched = this.attempt(input).pipe(Effect.retry({
62273
62527
  times: this.retry.attempts,
62274
62528
  while: isTransientBootstrapFailure,
62275
62529
  schedule: Schedule.exponential(Duration.millis(this.retry.baseDelayMs))
62276
62530
  }));
62531
+ const cache = this.cache;
62532
+ if (cache === void 0) return fetched;
62533
+ return Effect.promise(() => cachedEnvelope(cache, Date.now())).pipe(Effect.filterOrElse((cached) => cached !== null, () => fetched));
62277
62534
  }
62278
62535
  attempt(input) {
62279
62536
  return Effect.tryPromise({
@@ -62298,7 +62555,13 @@ var HttpBootstrapAdapter = class {
62298
62555
  }
62299
62556
  mapResponse(res, input) {
62300
62557
  if (res.ok) return Effect.tryPromise({
62301
- try: async () => decodeBootstrapBody(await res.json()),
62558
+ try: async () => {
62559
+ const body = await res.json();
62560
+ const resolution = decodeBootstrapBody(body);
62561
+ const cache = this.cache;
62562
+ if (cache !== void 0) await Promise.resolve().then(() => cache.save(body)).catch(() => void 0);
62563
+ return resolution;
62564
+ },
62302
62565
  catch: (cause) => {
62303
62566
  if (cause instanceof CapxulError && cause.code === "INVALID_INPUT") return bootstrapErrorFromCapxul("invalidInput", cause);
62304
62567
  return bootstrapErrorFromCapxul("malformedBody", Errors.providerError("convex", "bootstrap", cause instanceof Error ? cause : new Error(String(cause))));
@@ -63965,7 +64228,9 @@ function makeNotImplementedOrganizationPaymentsMethods() {
63965
64228
  subscribe: organizationPaymentExecutionUnavailable,
63966
64229
  pay: organizationPaymentExecutionUnavailable,
63967
64230
  payBatch: organizationPaymentExecutionUnavailable,
63968
- retry: organizationPaymentExecutionUnavailable
64231
+ retry: organizationPaymentExecutionUnavailable,
64232
+ cancel: organizationPaymentExecutionUnavailable,
64233
+ redirect: organizationPaymentExecutionUnavailable
63969
64234
  };
63970
64235
  }
63971
64236
  const orgMeUnavailable = () => Promise.resolve({
@@ -68158,6 +68423,23 @@ function subscribeConvexQuery(client, fn, args, callback, options) {
68158
68423
  * ever needs a different one.
68159
68424
  */
68160
68425
  const DEFAULT_CLOSE_TIMEOUT_MS = 5e3;
68426
+ /**
68427
+ * The fetcher the Convex client's auth handshake gets. Convex first asks for
68428
+ * "a possibly cached token" (`forceRefreshToken: false`) and, as soon as the
68429
+ * server accepts it, for a fresh one (`true`): the token whose `exp` it
68430
+ * schedules the next refresh from. The Better Auth clients keep no token: each
68431
+ * ask mints one over the network, so the cached ask was a second token fetch
68432
+ * and a second server verification (a JWKS read) on every session restore. It
68433
+ * answers "none" instead, and Convex makes its one fetch forced, as a fresh
68434
+ * token. Every forced ask still reaches the auth client: that first fetch, the
68435
+ * refresh before `exp`, and the retry after the server rejects a token.
68436
+ * ponytail: the OAuth bearer client can hold a cached exchange; it now pays one
68437
+ * exchange for the first authentication instead of reusing it (never more
68438
+ * fetches than before). Pass the cache through if that start ever matters.
68439
+ */
68440
+ function freshTokenHandshake(fetchToken) {
68441
+ return async (args) => args.forceRefreshToken ? fetchToken(args) : null;
68442
+ }
68161
68443
  /** Run a Convex call under the current trace parent, when there is one. */
68162
68444
  const withTraceparent = (run) => Effect.flatMap(Effect.serviceOption(Tracer.ParentSpan), (parent) => run(parent._tag === "Some" ? formatTraceparent(parent.value) : void 0));
68163
68445
  var ConvexCallAdapter = class {
@@ -68182,10 +68464,10 @@ var ConvexCallAdapter = class {
68182
68464
  this.#tokenProvider = deps.tokenProvider;
68183
68465
  this.#applicationId = deps.applicationId;
68184
68466
  this.#observation = deps.observation;
68185
- if (this.#tokenProvider) this.#client.setAuth(this.#tokenProvider);
68467
+ this.refreshAuth();
68186
68468
  }
68187
68469
  refreshAuth() {
68188
- if (this.#tokenProvider) this.#client.setAuth(this.#tokenProvider);
68470
+ if (this.#tokenProvider) this.#client.setAuth(freshTokenHandshake(this.#tokenProvider));
68189
68471
  }
68190
68472
  observeConnection(observer) {
68191
68473
  return this.#connectionMonitor.observe(this.#client, observer);
@@ -69623,7 +69905,8 @@ async function createProductionAdapters(input) {
69623
69905
  const bootstrapLayer = HttpBootstrapLayer({
69624
69906
  bootstrapBaseUrl: resolvedInput.value.bootstrapBaseUrl,
69625
69907
  ...input.fetch === void 0 ? {} : { fetch: input.fetch },
69626
- ...invocationObservation === void 0 ? {} : { observation: invocationObservation }
69908
+ ...invocationObservation === void 0 ? {} : { observation: invocationObservation },
69909
+ ...input.bootstrapCache === void 0 ? {} : { cache: input.bootstrapCache }
69627
69910
  });
69628
69911
  try {
69629
69912
  const bootstrapContext = await Effect.runPromise(Layer.buildWithScope(bootstrapLayer, scope));
@@ -71068,7 +71351,7 @@ function stdioIdentity(env = process.env, options = {}) {
71068
71351
  }
71069
71352
  //#endregion
71070
71353
  //#region package.json
71071
- var version = "4.20.0-beta.26";
71354
+ var version = "4.20.0-beta.28";
71072
71355
  //#endregion
71073
71356
  //#region src/deps.ts
71074
71357
  const NO_IDENTITY = async () => void 0;