@capxul/sdk 1.0.0-alpha.17 → 1.0.0-alpha.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { A as toSessionToken, C as toEpochSeconds, D as toPublishableKey, E as toOrgId, F as isCapxulError, M as decodeConvexError, N as CapxulError, O as toPublishableKeyId, P as Errors, S as toEpochMs, T as toKycTier, _ as toChainId, b as toDurationMs, c as BYTES32_RE, d as toAccountId, f as toAddress, g as toAuthUserId, h as toAppId, j as toSubAccountId, k as toRoleKey, l as EVM_ADDRESS_RE$1, m as toAnonymousDistinctId, n as BrowserAuthCacheAdapter, o as AuthCachePortTag, p as toAllowedOrigin, s as APP_ID_RE, t as InMemoryAuthCacheAdapter, u as SUPPORTED_CURRENCY_CODES, v as toCountryCode, w as toJwtToken, x as toEmail, y as toCurrencyCode } from "./InMemoryAuthCacheAdapter-v5W-XB5M.mjs";
2
- import { formatUnits, keccak256, padHex, parseUnits, recoverAddress, stringToHex, toBytes } from "viem";
1
+ import { A as toSessionToken, C as toEpochSeconds, D as toPublishableKey, E as toOrgId, F as Errors, I as isCapxulError, M as decodeConvexError, N as CapxulError, O as toPublishableKeyId, P as EXPECTED_OPERATION_OUTCOMES, S as toEpochMs, T as toKycTier, _ as toChainId, b as toDurationMs, c as BYTES32_RE, d as toAccountId, f as toAddress, g as toAuthUserId, h as toAppId, j as toSubAccountId, k as toRoleKey, l as EVM_ADDRESS_RE$1, m as toAnonymousDistinctId, n as BrowserAuthCacheAdapter, o as AuthCachePortTag, p as toAllowedOrigin, s as APP_ID_RE, t as InMemoryAuthCacheAdapter, u as SUPPORTED_CURRENCY_CODES, v as toCountryCode, w as toJwtToken, x as toEmail, y as toCurrencyCode } from "./InMemoryAuthCacheAdapter-BuVZpnSx.mjs";
2
+ import { concat, formatUnits, keccak256, padHex, parseUnits, recoverAddress, stringToHex, toBytes, toEventSelector, toFunctionSelector } from "viem";
3
3
  import { Context, Data, Deferred, Duration, Effect, Either, Exit, Layer, Ref, Request, Scope } from "effect";
4
4
  import { getFunctionName, makeFunctionReference } from "convex/server";
5
5
  import { privateKeyToAccount } from "viem/accounts";
@@ -12,6 +12,10 @@ import { Machine } from "@effect/experimental";
12
12
  const USDX_ADDRESS_BASE_SEPOLIA = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
13
13
  USDX_ADDRESS_BASE_SEPOLIA.toLowerCase();
14
14
  //#endregion
15
+ //#region ../config/src/org-payments.ts
16
+ /** Zodiac MultiSendCallOnly module on Base Sepolia. */
17
+ const MULTI_SEND_CALL_ONLY = "0x9641d764fc13c8b624c04430c7356c1c7c8102e2";
18
+ //#endregion
15
19
  //#region ../config/src/safe.ts
16
20
  const BASE_SEPOLIA_CHAIN_ID = 84532;
17
21
  /**
@@ -175,6 +179,20 @@ padHex(stringToHex("FM_DAILY"), {
175
179
  size: 32,
176
180
  dir: "right"
177
181
  });
182
+ padHex(concat([MULTI_SEND_CALL_ONLY, "0x8d80ff0a"]), {
183
+ dir: "right",
184
+ size: 32
185
+ });
186
+ new Map([
187
+ ["assignRoles(address,bytes32[],bool[])", "AssignRoles(address,bytes32[],bool[])"],
188
+ ["allowTarget(bytes32,address,uint8)", "AllowTarget(bytes32,address,uint8)"],
189
+ ["scopeTarget(bytes32,address)", "ScopeTarget(bytes32,address)"],
190
+ ["revokeTarget(bytes32,address)", "RevokeTarget(bytes32,address)"],
191
+ ["allowFunction(bytes32,address,bytes4,uint8)", "AllowFunction(bytes32,address,bytes4,uint8)"],
192
+ ["scopeFunction(bytes32,address,bytes4,(uint8,uint8,uint8,bytes)[],uint8)", "ScopeFunction(bytes32,address,bytes4,(uint8,uint8,uint8,bytes)[],uint8)"],
193
+ ["revokeFunction(bytes32,address,bytes4)", "RevokeFunction(bytes32,address,bytes4)"],
194
+ ["setAllowance(bytes32,uint128,uint128,uint128,uint64,uint64)", "SetAllowance(bytes32,uint128,uint128,uint128,uint64,uint64)"]
195
+ ].map(([functionSignature, eventSignature]) => [toFunctionSelector(functionSignature), toEventSelector(eventSignature)]));
178
196
  //#endregion
179
197
  //#region src/telemetry/stack-frame-parser.ts
180
198
  /**
@@ -1600,7 +1618,7 @@ async function recoverRawDigestSigner(input) {
1600
1618
  }
1601
1619
  //#endregion
1602
1620
  //#region package.json
1603
- var version = "1.0.0-alpha.17";
1621
+ var version = "1.0.0-alpha.18";
1604
1622
  //#endregion
1605
1623
  //#region src/ports/auth-client.ts
1606
1624
  var AuthClientError = class extends Data.TaggedError("AuthClientError") {};
@@ -1646,6 +1664,22 @@ function resolveAuthClientUrl(authBaseUrl, path) {
1646
1664
  return `${base}${path}`;
1647
1665
  }
1648
1666
  //#endregion
1667
+ //#region ../wire/src/secret-material.ts
1668
+ const SENSITIVE_MATERIAL_PATTERNS = [
1669
+ /0x[a-fA-F0-9]{40,}/u,
1670
+ /(?:^|[^a-fA-F0-9])[a-fA-F0-9]{64}(?:$|[^a-fA-F0-9])/u,
1671
+ /eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/u,
1672
+ /(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/iu
1673
+ ];
1674
+ /**
1675
+ * Reject: does the value carry any known secret material? Best effort — callers
1676
+ * drop the whole value on a match; a false negative is a leak, a false positive
1677
+ * merely omits an observation field.
1678
+ */
1679
+ function containsSensitiveMaterial(value) {
1680
+ return SENSITIVE_MATERIAL_PATTERNS.some((pattern) => pattern.test(value));
1681
+ }
1682
+ //#endregion
1649
1683
  //#region ../wire/src/observation-context.ts
1650
1684
  /** Single bounded HTTP carrier used before a Convex action envelope exists. */
1651
1685
  const OBSERVATION_CONTEXT_HEADER = "x-capxul-observation-context";
@@ -1679,10 +1713,6 @@ const FIELD_RULES = {
1679
1713
  pattern: /^anon_[A-Za-z0-9-]+$/u
1680
1714
  }
1681
1715
  };
1682
- const EMBEDDED_WALLET_MATERIAL = /0x[a-fA-F0-9]{40,}/u;
1683
- const RAW_PRIVATE_KEY_MATERIAL = /(?:^|[^a-fA-F0-9])[a-fA-F0-9]{64}(?:$|[^a-fA-F0-9])/u;
1684
- const COMPACT_JWT = /eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/u;
1685
- const KNOWN_CREDENTIAL_PREFIX = /(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/iu;
1686
1716
  /**
1687
1717
  * Copy only the canonical allowlist and silently omit malformed/sensitive
1688
1718
  * values. Observation metadata is best effort and may never reject a domain
@@ -1709,9 +1739,6 @@ function isSafeField(field, value) {
1709
1739
  const rule = FIELD_RULES[field];
1710
1740
  return value.length > 0 && value.length <= rule.maxLength && value === value.trim() && !value.includes("://") && !containsSensitiveMaterial(value) && rule.pattern.test(value);
1711
1741
  }
1712
- function containsSensitiveMaterial(value) {
1713
- return EMBEDDED_WALLET_MATERIAL.test(value) || RAW_PRIVATE_KEY_MATERIAL.test(value) || COMPACT_JWT.test(value) || KNOWN_CREDENTIAL_PREFIX.test(value);
1714
- }
1715
1742
  //#endregion
1716
1743
  //#region src/internal/observation-http.ts
1717
1744
  /** Resolve one bounded pre-auth snapshot for an outbound SDK HTTP request. */
@@ -2733,7 +2760,7 @@ const PaymentSource = Schema.Union(Schema.Struct({
2733
2760
  * `hashLineItems` (payment-document-hash.ts) into the Invoice `lineItemsHash`,
2734
2761
  * and Σ(quantity × unitMinor) MUST equal the invoice `amount`. The hash binds
2735
2762
  * the documentHash to the exact items; the sum-check makes the items add up to
2736
- * the amount due. See the payment-document spec (canon/mcp/financial-ops.md).
2763
+ * the amount due. See the financial-operations contract in `packages/wire/CONTEXT.md`.
2737
2764
  */
2738
2765
  const LineItem = Schema.Struct({
2739
2766
  description: Schema.String,
@@ -3717,7 +3744,7 @@ function fromWei(rawBalance, decimals, currency) {
3717
3744
  }
3718
3745
  //#endregion
3719
3746
  //#region src/adapters/account-read/ConvexAccountAdapter.ts
3720
- const DEFAULT_FUNCTIONS$3 = {
3747
+ const DEFAULT_FUNCTIONS$5 = {
3721
3748
  readBalance: makeFunctionReference("account/actions:readBalance"),
3722
3749
  faucetMint: makeFunctionReference("account/actions:faucetMint")
3723
3750
  };
@@ -3726,7 +3753,7 @@ var ConvexAccountAdapter = class {
3726
3753
  #fns;
3727
3754
  constructor(deps) {
3728
3755
  this.#convex = deps.convex;
3729
- this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$3;
3756
+ this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$5;
3730
3757
  }
3731
3758
  readBalance(input) {
3732
3759
  return this.#convex.action(this.#fns.readBalance, { chainId: wireChainId(input.chainId) }).pipe(Effect.mapError((error) => accountReadErrorFromCapxul("readBalance", error.publicError, error)), Effect.flatMap((wire) => brandAccountEffect("readBalance", wire)), Effect.catchAllDefect((cause) => Effect.fail(accountReadErrorFromUnknown("readBalance", cause))));
@@ -3779,7 +3806,7 @@ function subAccountErrorFromCapxul(operation, error, cause = error) {
3779
3806
  var SubAccountPortTag = class extends Context.Tag("@capxul/sdk/ports/SubAccountPort")() {};
3780
3807
  //#endregion
3781
3808
  //#region src/adapters/sub-account/ConvexSubAccountAdapter.ts
3782
- const DEFAULT_FUNCTIONS$2 = {
3809
+ const DEFAULT_FUNCTIONS$4 = {
3783
3810
  create: makeFunctionReference("subAccount/mutations:create"),
3784
3811
  get: makeFunctionReference("subAccount/queries:get"),
3785
3812
  list: makeFunctionReference("subAccount/queries:list"),
@@ -3794,7 +3821,7 @@ var ConvexSubAccountAdapter = class {
3794
3821
  constructor(deps) {
3795
3822
  this.#convex = deps.convex;
3796
3823
  this.#chainId = deps.chainId;
3797
- this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$2;
3824
+ this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$4;
3798
3825
  }
3799
3826
  create(input) {
3800
3827
  return this.#runMutation("create", this.#fns.create, {
@@ -3878,7 +3905,7 @@ function smartAccountErrorFromCapxul(operation, error, cause = error) {
3878
3905
  var SmartAccountPortTag = class extends Context.Tag("@capxul/sdk/ports/SmartAccountPort")() {};
3879
3906
  //#endregion
3880
3907
  //#region src/adapters/smart-account/ConvexSmartAccountAdapter.ts
3881
- const DEFAULT_FUNCTIONS$1 = {
3908
+ const DEFAULT_FUNCTIONS$3 = {
3882
3909
  loadByAuthUserId: makeFunctionReference("smartAccount/queries:loadByAuthUserId"),
3883
3910
  loadBySmartAccountAddress: makeFunctionReference("smartAccount/queries:loadBySmartAccountAddress"),
3884
3911
  provision: makeFunctionReference("smartAccount/mutations:provision"),
@@ -3890,7 +3917,7 @@ var ConvexSmartAccountAdapter = class {
3890
3917
  #fns;
3891
3918
  constructor(deps) {
3892
3919
  this.#convex = deps.convex;
3893
- this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$1;
3920
+ this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$3;
3894
3921
  }
3895
3922
  loadByAuthUserId(authUserId) {
3896
3923
  return this.#convex.query(this.#fns.loadByAuthUserId, { authUserId }).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("loadByAuthUserId", error.publicError, error)), Effect.flatMap((row) => brandSmartAccountEffect("loadByAuthUserId", row)), Effect.catchAllDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("loadByAuthUserId", cause))));
@@ -3965,6 +3992,424 @@ function smartAccountErrorFromUnknown(operation, cause) {
3965
3992
  toAddress("0xa6b71e26c5e0845f74c812102ca7114b6a896ab2");
3966
3993
  keccak256("0x");
3967
3994
  //#endregion
3995
+ //#region src/ports/org.ts
3996
+ var OrgError = class extends Data.TaggedError("OrgError") {};
3997
+ function orgErrorFromCapxul(operation, error, cause = error) {
3998
+ return new OrgError({
3999
+ operation,
4000
+ publicCode: error.code,
4001
+ publicError: error,
4002
+ cause,
4003
+ ...error.details === void 0 ? {} : { details: error.details }
4004
+ });
4005
+ }
4006
+ Context.Tag("@capxul/sdk/ports/OrgPort")();
4007
+ Context.Tag("@capxul/sdk/ports/OrgRolesDeploymentPort")();
4008
+ Context.Tag("@capxul/sdk/ports/OrgSpendPort")();
4009
+ //#endregion
4010
+ //#region src/adapters/org/parse.ts
4011
+ /**
4012
+ * Map a `WireOrg` + its (separately read) treasury `Account` into the branded
4013
+ * `OrgView`. The viewer role is projected by the authenticated backend read;
4014
+ * it must never be inferred from Organization ownership. Brands at the read
4015
+ * edge: `orgId`, `safeAddress`.
4016
+ */
4017
+ function brandOrgView(wire, treasury, viewerRole) {
4018
+ return {
4019
+ id: toOrgId(wire.orgId),
4020
+ name: wire.name,
4021
+ handle: wire.slug,
4022
+ safeAddress: toAddress(wire.safeAddress.toLowerCase()),
4023
+ role: viewerRole,
4024
+ treasury
4025
+ };
4026
+ }
4027
+ /**
4028
+ * Build the Org treasury `Account` from the raw on-chain `balanceOf` integer
4029
+ * (the D3 RPC read). `available === balance` for a treasury with no envelope
4030
+ * partition yet (a fresh Org reads back $0).
4031
+ */
4032
+ function brandOrgTreasury(input) {
4033
+ return {
4034
+ id: toAccountId(`account_${orgIdBody(input.orgId)}`),
4035
+ balance: input.money,
4036
+ available: input.money
4037
+ };
4038
+ }
4039
+ function brandOrgRole(wire) {
4040
+ return {
4041
+ orgId: toOrgId(wire.orgId),
4042
+ label: wire.label,
4043
+ roleKey: toRoleKey(wire.roleKey),
4044
+ definition: parseRoleDefinition(wire.definitionJson)
4045
+ };
4046
+ }
4047
+ function brandOrgMember(wire) {
4048
+ return {
4049
+ orgId: toOrgId(wire.orgId),
4050
+ email: toEmail(wire.email),
4051
+ name: wire.name,
4052
+ personalSafeAddress: wire.personalSafeAddress === null ? null : toAddress(wire.personalSafeAddress),
4053
+ role: wire.role,
4054
+ roleKey: wire.roleKey === null ? null : toRoleKey(wire.roleKey),
4055
+ status: wire.status,
4056
+ grantTxHash: wire.grantTxHash,
4057
+ revokeTxHash: wire.revokeTxHash
4058
+ };
4059
+ }
4060
+ function parseRoleDefinition(json) {
4061
+ let raw;
4062
+ try {
4063
+ const parsed = JSON.parse(json);
4064
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("expected object");
4065
+ raw = parsed;
4066
+ } catch (err) {
4067
+ throw new Error(`Invalid role definition: malformed JSON - ${err instanceof Error ? err.message : String(err)}`, { cause: err });
4068
+ }
4069
+ if (typeof raw.label !== "string" || raw.label.trim().length === 0) throw new Error("Invalid role definition: missing label");
4070
+ return {
4071
+ label: raw.label,
4072
+ ...raw.spend === void 0 ? {} : { spend: {
4073
+ ...raw.spend.perTx === void 0 ? {} : { perTx: parseRoleMoney(raw.spend.perTx, "perTx") },
4074
+ ...raw.spend.perDay === void 0 ? {} : { perDay: parseRoleMoney(raw.spend.perDay, "perDay") },
4075
+ ...raw.spend.toRecipients === void 0 ? {} : { toRecipients: parseRoleRecipients(raw.spend.toRecipients) }
4076
+ } },
4077
+ ...raw.subAccounts === void 0 ? {} : { subAccounts: parseRoleSubAccounts(raw.subAccounts) },
4078
+ ...typeof raw.canManageMembers === "boolean" ? { canManageMembers: raw.canManageMembers } : {},
4079
+ ...typeof raw.canManageRoles === "boolean" ? { canManageRoles: raw.canManageRoles } : {}
4080
+ };
4081
+ }
4082
+ function parseRoleMoney(raw, field) {
4083
+ if (typeof raw.currency !== "string" || typeof raw.value !== "string" || !/^\d+$/.test(raw.value) || raw.decimals !== 6) throw new Error(`Invalid role money: ${field}`);
4084
+ return {
4085
+ currency: toCurrencyCode(raw.currency),
4086
+ value: raw.value,
4087
+ decimals: raw.decimals
4088
+ };
4089
+ }
4090
+ function parseRoleRecipients(raw) {
4091
+ if (raw === "anyone") return "anyone";
4092
+ if (!Array.isArray(raw)) throw new Error("Invalid role definition: toRecipients must be \"anyone\" or an array");
4093
+ return raw.map((recipient) => {
4094
+ if (typeof recipient !== "string") throw new Error("Invalid role definition: toRecipients must be \"anyone\" or an array");
4095
+ return toAddress(recipient);
4096
+ });
4097
+ }
4098
+ function parseRoleSubAccounts(raw) {
4099
+ if (raw.scope === "all") return { scope: "all" };
4100
+ if (!Array.isArray(raw.scope)) throw new Error("Invalid role definition: subAccounts.scope must be \"all\" or an array");
4101
+ return { scope: raw.scope.map((subAccountId) => {
4102
+ if (typeof subAccountId !== "string") throw new Error("Invalid role definition: subAccounts.scope must be \"all\" or an array");
4103
+ return toSubAccountId(subAccountId);
4104
+ }) };
4105
+ }
4106
+ /** Strip the `org_` prefix + non-alphanumerics so the body re-seeds `account_`. */
4107
+ function orgIdBody(orgId) {
4108
+ const underscore = orgId.indexOf("_");
4109
+ const cleaned = (underscore < 0 ? orgId : orgId.slice(underscore + 1)).replace(/[^0-9A-Za-z]/g, "");
4110
+ return cleaned.length > 0 ? cleaned : "0";
4111
+ }
4112
+ //#endregion
4113
+ //#region src/adapters/org/ConvexOrganizationAdapter.ts
4114
+ const DEFAULT_FUNCTIONS$2 = {
4115
+ listAll: makeFunctionReference("org/queries:listAll"),
4116
+ listRoles: makeFunctionReference("org/queries:listRolesByOrgId"),
4117
+ listMembers: makeFunctionReference("org/queries:listMembersByOrgId"),
4118
+ readTreasury: makeFunctionReference("org/actions:readTreasury"),
4119
+ inviteMember: makeFunctionReference("org/actions:inviteMember"),
4120
+ resendInvite: makeFunctionReference("org/mutations:resendInviteToken"),
4121
+ detectInvitations: makeFunctionReference("org/actions:detectAndAcceptPendingInvitations")
4122
+ };
4123
+ /**
4124
+ * Standard production Organization read adapter. It intentionally has no
4125
+ * deployer/RPC/test configuration: authenticated Convex actions own live chain
4126
+ * reads, while the lifecycle adapter owns the single sponsored bootstrap.
4127
+ */
4128
+ var ConvexOrganizationAdapter = class {
4129
+ #convex;
4130
+ #fns;
4131
+ constructor(input) {
4132
+ this.#convex = input.convex;
4133
+ this.#fns = input.functions ?? DEFAULT_FUNCTIONS$2;
4134
+ }
4135
+ createOrg(_input) {
4136
+ return Effect.fail(orgErrorFromCapxul("createOrg", Errors.notImplemented("organizationSetup", "use onboarding.completeOrganization")));
4137
+ }
4138
+ listOrgs(_input) {
4139
+ return this.#convex.query(this.#fns.listAll, {}).pipe(Effect.mapError((error) => orgErrorFromCapxul("listOrgs", error.publicError, error)), Effect.flatMap((wires) => Effect.forEach(wires, (wire) => this.#readTreasuryWire(wire.orgId).pipe(Effect.map((treasury) => {
4140
+ const viewerRole = wire.viewerRole.trim();
4141
+ if (viewerRole.length === 0) throw Errors.wrongState({
4142
+ method: "listOrgs",
4143
+ currentState: "viewerRoleMissing",
4144
+ validStates: ["activeViewerRole"]
4145
+ });
4146
+ return brandOrgView(wire, treasury, viewerRole);
4147
+ })))), Effect.catchAllDefect((cause) => Effect.fail(toOrgError("listOrgs", cause))));
4148
+ }
4149
+ readTreasury(input) {
4150
+ return this.#readTreasuryWire(String(input.orgId)).pipe(Effect.catchAllDefect((cause) => Effect.fail(toOrgError("readTreasury", cause))));
4151
+ }
4152
+ listRoles(input) {
4153
+ return this.#convex.query(this.#fns.listRoles, { orgId: input.orgId }).pipe(Effect.mapError((error) => orgErrorFromCapxul("listRoles", error.publicError, error)), Effect.flatMap((rows) => rows.length === 0 ? Effect.fail(partialOrgTruth("listRoles", "activeRoleMissing")) : Effect.succeed(rows.map(brandOrgRole))), Effect.catchAllDefect((cause) => Effect.fail(toOrgError("listRoles", cause))));
4154
+ }
4155
+ listMembers(input) {
4156
+ return this.#convex.query(this.#fns.listMembers, { orgId: input.orgId }).pipe(Effect.mapError((error) => orgErrorFromCapxul("listMembers", error.publicError, error)), Effect.flatMap((rows) => rows.length === 0 ? Effect.fail(partialOrgTruth("listMembers", "activeMemberMissing")) : Effect.succeed(rows.map(brandOrgMember))), Effect.catchAllDefect((cause) => Effect.fail(toOrgError("listMembers", cause))));
4157
+ }
4158
+ pendingMembers(input) {
4159
+ return this.listMembers(input).pipe(Effect.map((members) => members.filter((member) => member.status === "pending" || member.status === "pending_safe" || member.status === "pending_grant")));
4160
+ }
4161
+ inviteMember(input) {
4162
+ return this.#convex.action(this.#fns.inviteMember, {
4163
+ orgId: input.orgId,
4164
+ email: input.input.email,
4165
+ role: input.input.role
4166
+ }).pipe(Effect.mapError((error) => orgErrorFromCapxul("inviteMember", error.publicError, error)), Effect.map(brandOrgMember), Effect.catchAllDefect((cause) => Effect.fail(toOrgError("inviteMember", cause))));
4167
+ }
4168
+ resendInviteToken(input) {
4169
+ return this.#convex.mutation(this.#fns.resendInvite, {
4170
+ orgId: input.orgId,
4171
+ email: input.email
4172
+ }).pipe(Effect.mapError((error) => orgErrorFromCapxul("resendInviteToken", error.publicError, error)), Effect.map(brandOrgMember), Effect.catchAllDefect((cause) => Effect.fail(toOrgError("resendInviteToken", cause))));
4173
+ }
4174
+ detectAndAcceptPendingInvitations(_input) {
4175
+ return this.#convex.action(this.#fns.detectInvitations, {}).pipe(Effect.mapError((error) => orgErrorFromCapxul("detectAndAcceptPendingInvitations", error.publicError, error)), Effect.map((result) => ({ matched: result.matched.map(toOrgId) })), Effect.catchAllDefect((cause) => Effect.fail(toOrgError("detectAndAcceptPendingInvitations", cause))));
4176
+ }
4177
+ #readTreasuryWire(orgId) {
4178
+ return this.#convex.action(this.#fns.readTreasury, { orgId }).pipe(Effect.mapError((error) => orgErrorFromCapxul("readTreasury", error.publicError, error)), Effect.map((wire) => {
4179
+ if (wire.orgId !== orgId) throw Errors.invalidInput("orgId", "Organization treasury scope does not match");
4180
+ const balance = fromWei(wire.rawBalance, wire.decimals, wire.currency);
4181
+ const available = fromWei(wire.rawAvailableBalance, wire.decimals, wire.currency);
4182
+ return {
4183
+ ...brandOrgTreasury({
4184
+ orgId: wire.orgId,
4185
+ money: balance
4186
+ }),
4187
+ available
4188
+ };
4189
+ }));
4190
+ }
4191
+ };
4192
+ function toOrgError(operation, cause) {
4193
+ if (cause instanceof CapxulError) return orgErrorFromCapxul(operation, cause);
4194
+ return orgErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
4195
+ }
4196
+ function partialOrgTruth(operation, currentState) {
4197
+ return orgErrorFromCapxul(operation, Errors.wrongState({
4198
+ method: operation,
4199
+ currentState,
4200
+ validStates: ["completeProductionOrganizationTruth"]
4201
+ }));
4202
+ }
4203
+ //#endregion
4204
+ //#region src/adapters/org/ConvexOrganizationSetupAdapter.ts
4205
+ const DEFAULT_FUNCTIONS$1 = {
4206
+ startOrResume: makeFunctionReference("org/lifecycle:startOrResume"),
4207
+ prepareFounderAccount: makeFunctionReference("org/actions:prepareFounderAccount"),
4208
+ prepareBootstrap: makeFunctionReference("org/actions:prepareBootstrap"),
4209
+ submitBootstrap: makeFunctionReference("org/actions:submitBootstrap"),
4210
+ confirmBootstrap: makeFunctionReference("org/actions:confirmBootstrap"),
4211
+ recordFailure: makeFunctionReference("org/lifecycle:recordFailure"),
4212
+ load: makeFunctionReference("org/lifecycle:load"),
4213
+ retry: makeFunctionReference("org/lifecycle:retry")
4214
+ };
4215
+ /** Durable Organization setup capability composed by the standard client. */
4216
+ var ConvexOrganizationSetupAdapter = class {
4217
+ #convex;
4218
+ #signer;
4219
+ #fns;
4220
+ constructor(input) {
4221
+ this.#convex = input.convex;
4222
+ this.#signer = input.signer;
4223
+ this.#fns = input.functions ?? DEFAULT_FUNCTIONS$1;
4224
+ }
4225
+ async startOrResume(input) {
4226
+ const result = await runCall("startOrResume", this.#convex.mutation(this.#fns.startOrResume, input));
4227
+ if (!result.ok) return result;
4228
+ const lifecycle = parseLifecycle("startOrResume", result.value.lifecycle);
4229
+ if (!lifecycle.ok) return lifecycle;
4230
+ const orgId = parseOrgId("startOrResume", result.value.orgId);
4231
+ if (!orgId.ok) return orgId;
4232
+ if (String(orgId.value) !== String(lifecycle.value.orgId)) return fail$2(Errors.invalidInput("orgId", "Organization lifecycle scope does not match"));
4233
+ return {
4234
+ ok: true,
4235
+ value: {
4236
+ orgId: orgId.value,
4237
+ lifecycle: lifecycle.value
4238
+ }
4239
+ };
4240
+ }
4241
+ prepareFounderAccount(input) {
4242
+ return this.#lifecycleAction("prepareFounderAccount", input, () => this.#convex.action(this.#fns.prepareFounderAccount, { orgId: input.orgId }));
4243
+ }
4244
+ async authorizeAndSubmitBootstrap(input) {
4245
+ const cancelled = cancellation(input.signal);
4246
+ if (cancelled !== void 0) return cancelled;
4247
+ const signerAddress = await signerResult("getAddress", () => this.#signer.getAddress());
4248
+ if (!signerAddress.ok) return signerAddress;
4249
+ const prepared = await runCall("prepareBootstrap", this.#convex.action(this.#fns.prepareBootstrap, {
4250
+ orgId: input.orgId,
4251
+ signerAddress: signerAddress.value
4252
+ }));
4253
+ if (!prepared.ok) return prepared;
4254
+ const authority = validatePreparedAuthorities(prepared.value, signerAddress.value);
4255
+ if (!authority.ok) return authority;
4256
+ const cancelledAfterPrepare = cancellation(input.signal);
4257
+ if (cancelledAfterPrepare !== void 0) return cancelledAfterPrepare;
4258
+ const signature = await signerResult("signUserOpHash", () => this.#signer.signUserOpHash(prepared.value.digest));
4259
+ if (!signature.ok) return signature;
4260
+ const cancelledAfterSign = cancellation(input.signal);
4261
+ if (cancelledAfterSign !== void 0) return cancelledAfterSign;
4262
+ const submitted = await runCall("submitBootstrap", this.#convex.action(this.#fns.submitBootstrap, {
4263
+ orgId: input.orgId,
4264
+ signerAddress: signerAddress.value,
4265
+ signature: signature.value,
4266
+ userOp: prepared.value.userOp
4267
+ }));
4268
+ if (!submitted.ok) return submitted;
4269
+ return parseLifecycle("submitBootstrap", submitted.value);
4270
+ }
4271
+ confirmSubmittedBootstrap(input) {
4272
+ return this.#lifecycleAction("confirmBootstrap", input, () => this.#convex.action(this.#fns.confirmBootstrap, { orgId: input.orgId }));
4273
+ }
4274
+ async recordFailure(input) {
4275
+ const result = await runCall("recordFailure", this.#convex.mutation(this.#fns.recordFailure, {
4276
+ orgId: input.orgId,
4277
+ at: input.at,
4278
+ errorCode: input.error.code,
4279
+ retryable: input.retryable
4280
+ }));
4281
+ return result.ok ? parseLifecycle("recordFailure", result.value) : result;
4282
+ }
4283
+ async loadLifecycle(input) {
4284
+ const result = await runCall("loadLifecycle", this.#convex.query(this.#fns.load, input));
4285
+ if (!result.ok) return result;
4286
+ if (result.value === null) return fail$2(Errors.invalidInput("orgId", "Organization lifecycle was not found"));
4287
+ return parseLifecycle("loadLifecycle", result.value);
4288
+ }
4289
+ async retry(input) {
4290
+ const cancelled = cancellation(input.signal);
4291
+ if (cancelled !== void 0) return cancelled;
4292
+ const result = await runCall("retry", this.#convex.mutation(this.#fns.retry, { orgId: input.orgId }));
4293
+ return result.ok ? parseLifecycle("retry", result.value) : result;
4294
+ }
4295
+ async #lifecycleAction(operation, input, call) {
4296
+ const cancelled = cancellation(input.signal);
4297
+ if (cancelled !== void 0) return cancelled;
4298
+ const result = await runCall(operation, call());
4299
+ if (!result.ok) return result;
4300
+ const cancelledAfter = cancellation(input.signal);
4301
+ if (cancelledAfter !== void 0) return cancelledAfter;
4302
+ return parseLifecycle(operation, result.value);
4303
+ }
4304
+ };
4305
+ async function runCall(operation, effect) {
4306
+ try {
4307
+ const result = await Effect.runPromise(Effect.either(effect));
4308
+ return Either.isRight(result) ? {
4309
+ ok: true,
4310
+ value: result.right
4311
+ } : fail$2(publicError(operation, result.left));
4312
+ } catch (cause) {
4313
+ return fail$2(publicError(operation, cause));
4314
+ }
4315
+ }
4316
+ function publicError(operation, cause) {
4317
+ if (cause instanceof CapxulError) return cause;
4318
+ if (typeof cause === "object" && cause !== null) {
4319
+ const carried = cause.publicError;
4320
+ if (carried instanceof CapxulError) return carried;
4321
+ }
4322
+ return Errors.providerError("convex-organization", operation, cause);
4323
+ }
4324
+ async function signerResult(operation, run) {
4325
+ try {
4326
+ return {
4327
+ ok: true,
4328
+ value: await run()
4329
+ };
4330
+ } catch (cause) {
4331
+ return fail$2(Errors.providerError("organization-signer", operation, cause));
4332
+ }
4333
+ }
4334
+ function validatePreparedAuthorities(prepared, signerAddress) {
4335
+ const signer = signerAddress.toLowerCase();
4336
+ const preparedSigner = prepared.signerAddress.toLowerCase();
4337
+ const founder = prepared.founderPersonalAccount.toLowerCase();
4338
+ const organization = prepared.organizationAccountAddress.toLowerCase();
4339
+ const sender = prepared.userOp.sender.toLowerCase();
4340
+ if (preparedSigner !== signer) return fail$2(Errors.invalidInput("signerAddress", "Prepared signer does not match configured signer"));
4341
+ if (founder === signer || organization === signer || organization === founder) return fail$2(Errors.invalidInput("organizationAuthority", "Signer EOA, founder Account, and Organization Account must be distinct"));
4342
+ if (sender !== founder) return fail$2(Errors.invalidInput("userOp.sender", "Bootstrap sender must be the founder Account"));
4343
+ if (!/^0x[0-9a-fA-F]{64}$/u.test(prepared.digest)) return fail$2(Errors.invalidInput("digest", "Prepared bootstrap digest must be 32-byte hex"));
4344
+ return {
4345
+ ok: true,
4346
+ value: void 0
4347
+ };
4348
+ }
4349
+ function parseLifecycle(operation, wire) {
4350
+ const orgId = parseOrgId(operation, wire.orgId);
4351
+ if (!orgId.ok) return orgId;
4352
+ if (wire.status === "loading") return {
4353
+ ok: true,
4354
+ value: {
4355
+ status: "loading",
4356
+ orgId: orgId.value
4357
+ }
4358
+ };
4359
+ if (wire.status === "ready") return {
4360
+ ok: true,
4361
+ value: {
4362
+ status: "ready",
4363
+ orgId: orgId.value,
4364
+ canTransact: true
4365
+ }
4366
+ };
4367
+ if (wire.status === "failed") {
4368
+ if (!isSetupStep(wire.at)) return fail$2(Errors.invalidInput("lifecycle.at", "Unknown setup step"));
4369
+ return {
4370
+ ok: true,
4371
+ value: {
4372
+ status: "failed",
4373
+ orgId: orgId.value,
4374
+ at: wire.at,
4375
+ error: new CapxulError(wire.error.code, wire.error.message),
4376
+ retryable: wire.retryable
4377
+ }
4378
+ };
4379
+ }
4380
+ if (!isSetupStep(wire.step)) return fail$2(Errors.invalidInput("lifecycle.step", `Unknown setup step from ${operation}`));
4381
+ return {
4382
+ ok: true,
4383
+ value: {
4384
+ status: "settingUp",
4385
+ orgId: orgId.value,
4386
+ step: wire.step
4387
+ }
4388
+ };
4389
+ }
4390
+ function parseOrgId(operation, value) {
4391
+ try {
4392
+ return {
4393
+ ok: true,
4394
+ value: toOrgId(value)
4395
+ };
4396
+ } catch (cause) {
4397
+ return fail$2(Errors.providerError("convex-organization", operation, cause));
4398
+ }
4399
+ }
4400
+ function isSetupStep(value) {
4401
+ return value === "preparingFounderAccount" || value === "awaitingFounderAuthorization" || value === "submittingBootstrap" || value === "confirmingBootstrap";
4402
+ }
4403
+ function cancellation(signal) {
4404
+ return signal?.aborted ? fail$2(Errors.cancelled({ operation: "organization.setup" })) : void 0;
4405
+ }
4406
+ function fail$2(error) {
4407
+ return {
4408
+ ok: false,
4409
+ error
4410
+ };
4411
+ }
4412
+ //#endregion
3968
4413
  //#region ../observability/src/index.ts
3969
4414
  const PII = Schema.String.pipe(Schema.brand("PII"));
3970
4415
  const OptionalString = Schema.optional(Schema.String);
@@ -7451,6 +7896,76 @@ function listOrgsProgram() {
7451
7896
  });
7452
7897
  }
7453
7898
  //#endregion
7899
+ //#region src/client/org-lifecycle.ts
7900
+ function fail$1(error) {
7901
+ return {
7902
+ ok: false,
7903
+ error
7904
+ };
7905
+ }
7906
+ function validateOrganizationLifecycleScope(orgId, lifecycle) {
7907
+ if (String(lifecycle.orgId) !== String(orgId)) return fail$1(Errors.invalidInput("orgId", "Organization lifecycle scope does not match"));
7908
+ return {
7909
+ ok: true,
7910
+ value: lifecycle
7911
+ };
7912
+ }
7913
+ /** Advance only the steps still required by one durable Organization lane. */
7914
+ async function advanceOrganizationSetup(setup, orgId, initial, signal) {
7915
+ const scopedInitial = validateOrganizationLifecycleScope(orgId, initial);
7916
+ if (!scopedInitial.ok) return scopedInitial;
7917
+ let lifecycle = scopedInitial.value;
7918
+ for (let transition = 0; transition < 3; transition += 1) {
7919
+ if (lifecycle.status !== "settingUp") return {
7920
+ ok: true,
7921
+ value: lifecycle
7922
+ };
7923
+ const currentStep = lifecycle.step;
7924
+ if (signal?.aborted) {
7925
+ const cancelled = Errors.cancelled({ operation: "organization.setup" });
7926
+ return recordRetryableCancellation(setup, orgId, lifecycle.step, cancelled);
7927
+ }
7928
+ const stepInput = {
7929
+ orgId,
7930
+ ...signal === void 0 ? {} : { signal }
7931
+ };
7932
+ const next = lifecycle.step === "preparingFounderAccount" ? await setup.prepareFounderAccount(stepInput) : lifecycle.step === "awaitingFounderAuthorization" || lifecycle.step === "submittingBootstrap" ? await setup.authorizeAndSubmitBootstrap(stepInput) : await setup.confirmSubmittedBootstrap(stepInput);
7933
+ if (!next.ok) return next.error.code === "CANCELLED" ? recordRetryableCancellation(setup, orgId, lifecycle.step, next.error) : next;
7934
+ const scopedNext = validateOrganizationLifecycleScope(orgId, next.value);
7935
+ if (!scopedNext.ok) return scopedNext;
7936
+ lifecycle = scopedNext.value;
7937
+ if (currentStep === "confirmingBootstrap") return {
7938
+ ok: true,
7939
+ value: lifecycle
7940
+ };
7941
+ }
7942
+ if (lifecycle.status !== "settingUp" || lifecycle.step === "confirmingBootstrap") return {
7943
+ ok: true,
7944
+ value: lifecycle
7945
+ };
7946
+ return fail$1(Errors.wrongState({
7947
+ method: "organization.setup",
7948
+ currentState: lifecycle.step,
7949
+ validStates: [
7950
+ "confirmingBootstrap",
7951
+ "ready",
7952
+ "failed"
7953
+ ]
7954
+ }));
7955
+ }
7956
+ async function recordRetryableCancellation(setup, orgId, at, cancelled) {
7957
+ const recorded = await setup.recordFailure({
7958
+ orgId,
7959
+ at,
7960
+ error: cancelled,
7961
+ retryable: true
7962
+ });
7963
+ if (!recorded.ok) return recorded;
7964
+ const scopedRecorded = validateOrganizationLifecycleScope(orgId, recorded.value);
7965
+ if (!scopedRecorded.ok) return scopedRecorded;
7966
+ return fail$1(cancelled);
7967
+ }
7968
+ //#endregion
7454
7969
  //#region src/client/org.ts
7455
7970
  /**
7456
7971
  * Org method surface (canon §C2/§C3, D13). S1 (#274) wires `createOrg` /
@@ -7499,6 +8014,36 @@ function makeOrgMethods(deps) {
7499
8014
  },
7500
8015
  ...convexCall === void 0 ? {} : { convexCall }
7501
8016
  }),
8017
+ async getLifecycle(options) {
8018
+ if (options?.signal?.aborted) return {
8019
+ ok: false,
8020
+ error: Errors.cancelled({ operation: "org.getLifecycle" })
8021
+ };
8022
+ if (deps.organizationSetup === void 0) return {
8023
+ ok: false,
8024
+ error: Errors.notImplemented("organizationSetup", "getLifecycle")
8025
+ };
8026
+ const loaded = await deps.organizationSetup.loadLifecycle({ orgId });
8027
+ if (!loaded.ok) return loaded;
8028
+ return validateOrganizationLifecycleScope(orgId, loaded.value);
8029
+ },
8030
+ async retrySetup(options) {
8031
+ if (options?.signal?.aborted) return {
8032
+ ok: false,
8033
+ error: Errors.cancelled({ operation: "org.retrySetup" })
8034
+ };
8035
+ const setup = deps.organizationSetup;
8036
+ if (setup === void 0) return {
8037
+ ok: false,
8038
+ error: Errors.notImplemented("organizationSetup", "retrySetup")
8039
+ };
8040
+ const retried = await setup.retry({
8041
+ orgId,
8042
+ ...options?.signal === void 0 ? {} : { signal: options.signal }
8043
+ });
8044
+ if (!retried.ok) return retried;
8045
+ return advanceOrganizationSetup(setup, orgId, retried.value, options?.signal);
8046
+ },
7502
8047
  profile: {
7503
8048
  get(_options) {
7504
8049
  if (_options?.signal?.aborted) return Promise.resolve({
@@ -7532,12 +8077,12 @@ function makeOrgMethods(deps) {
7532
8077
  ok: false,
7533
8078
  error: treasury.error
7534
8079
  };
7535
- const orgs = await toCapxulResult(listOrgsProgram(), layer);
7536
- if (!orgs.ok) return {
8080
+ const orgList = await toCapxulResult(listOrgsProgram(), layer);
8081
+ if (!orgList.ok) return {
7537
8082
  ok: false,
7538
- error: orgs.error
8083
+ error: orgList.error
7539
8084
  };
7540
- const org = orgs.value.find((candidate) => candidate.id === orgId);
8085
+ const org = orgList.value.find((candidate) => candidate.id === orgId);
7541
8086
  return {
7542
8087
  ok: true,
7543
8088
  value: {
@@ -7725,7 +8270,6 @@ function normalizePaymentTiming(payment) {
7725
8270
  }
7726
8271
  //#endregion
7727
8272
  //#region src/flows/onboarding.ts
7728
- const DEFAULT_ORG_TEMPLATE = "Startup";
7729
8273
  async function runCompletePersonalOnboarding(ops, input) {
7730
8274
  const validated = validatePersonalInput(input);
7731
8275
  if (!validated.ok) return validated;
@@ -7749,11 +8293,13 @@ async function runCompletePersonalOnboarding(ops, input) {
7749
8293
  value: { lifecycle: lifecycle.value }
7750
8294
  };
7751
8295
  }
7752
- async function runCompleteOrganizationOnboarding(ops, input) {
8296
+ async function runCompleteOrganizationOnboarding(ops, input, options) {
7753
8297
  const validated = validateOrganizationInput(input);
7754
8298
  if (!validated.ok) return validated;
7755
8299
  const session = await ops.currentSession();
7756
8300
  if (session === null) return fail(Errors.notAuthenticated());
8301
+ const setup = ops.organizationSetup;
8302
+ if (setup === void 0) return fail(Errors.notImplemented("organizationSetup", "completeOrganization"));
7757
8303
  const written = await ops.completeIdentityOnboarding({
7758
8304
  authUserId: session.authUserId,
7759
8305
  email: session.email,
@@ -7761,22 +8307,18 @@ async function runCompleteOrganizationOnboarding(ops, input) {
7761
8307
  country: validated.value.country
7762
8308
  });
7763
8309
  if (!written.ok) return fail(written.error);
7764
- const provisioned = await ops.triggerProvisioning();
7765
- if (!provisioned.ok) return fail(provisioned.error);
7766
- const org = await ops.createOrg({
8310
+ const started = await setup.startOrResume({
7767
8311
  name: validated.value.organizationName,
7768
8312
  handle: validated.value.handle,
7769
- template: DEFAULT_ORG_TEMPLATE,
7770
8313
  country: validated.value.country
7771
8314
  });
7772
- if (!org.ok) return fail(org.error);
7773
- ops.kickProvisioning();
7774
- const lifecycle = await ops.readLifecycle();
7775
- if (!lifecycle.ok) return fail(lifecycle.error);
8315
+ if (!started.ok) return fail(started.error);
8316
+ const lifecycle = await advanceOrganizationSetup(setup, started.value.orgId, started.value.lifecycle, options?.signal);
8317
+ if (!lifecycle.ok) return lifecycle;
7776
8318
  return {
7777
8319
  ok: true,
7778
8320
  value: {
7779
- org: org.value,
8321
+ orgId: started.value.orgId,
7780
8322
  lifecycle: lifecycle.value
7781
8323
  }
7782
8324
  };
@@ -7867,8 +8409,7 @@ function fail(error) {
7867
8409
  * Onboarding method surface. The thin namespace over the `flows/onboarding.ts`
7868
8410
  * choreography: it adds the `signal`-abort pre-check and delegates to the run
7869
8411
  * functions. The `OnboardingOps` seam is supplied by `assembleCapxulClient`
7870
- * (real ops over the account lane + createOrg + IdentityPort) or by tests
7871
- * (in-memory double).
8412
+ * (real identity/Account ops plus Organization setup capability) or by tests.
7872
8413
  */
7873
8414
  function makeOnboardingMethods(ops) {
7874
8415
  return {
@@ -7884,7 +8425,7 @@ function makeOnboardingMethods(ops) {
7884
8425
  ok: false,
7885
8426
  error: Errors.cancelled({ operation: "onboarding.completeOrganization" })
7886
8427
  });
7887
- return runCompleteOrganizationOnboarding(ops, input);
8428
+ return runCompleteOrganizationOnboarding(ops, input, options);
7888
8429
  }
7889
8430
  };
7890
8431
  }
@@ -7963,7 +8504,8 @@ function assembleCapxulClient(input) {
7963
8504
  ...selectedOrgPort === void 0 ? {} : { orgPort: selectedOrgPort },
7964
8505
  ...input.orgRolesDeploymentPort === void 0 ? {} : { orgRolesDeploymentPort: input.orgRolesDeploymentPort },
7965
8506
  ...input.orgSpendPort === void 0 ? {} : { orgSpendPort: input.orgSpendPort },
7966
- convexCall: input.ports.convexCall
8507
+ convexCall: input.ports.convexCall,
8508
+ ...input.organizationSetup === void 0 ? {} : { organizationSetup: input.organizationSetup }
7967
8509
  });
7968
8510
  if (selectedOrgPort !== void 0) detectPendingOrgInvitations = async () => {
7969
8511
  await orgMethods.orgs.detectAndAcceptPendingInvitations();
@@ -7986,7 +8528,7 @@ function assembleCapxulClient(input) {
7986
8528
  triggerProvisioning: () => account._internal.provision(),
7987
8529
  kickProvisioning: () => kickProvisioning?.(),
7988
8530
  readLifecycle: () => account.getLifecycle(),
7989
- createOrg: (createOrgInput) => orgMethods.createOrg(createOrgInput)
8531
+ ...input.organizationSetup === void 0 ? {} : { organizationSetup: input.organizationSetup }
7990
8532
  });
7991
8533
  return {
7992
8534
  auth,
@@ -8028,19 +8570,6 @@ function assembleCapxulClient(input) {
8028
8570
  const SDK_VERSION$1 = version;
8029
8571
  /** Stable PostHog event used for typed failures that are expected product outcomes. */
8030
8572
  const CAPXUL_SDK_EXPECTED_OUTCOME_EVENT = "capxul_sdk_expected_outcome";
8031
- const EXPECTED_OPERATION_OUTCOMES = new Set([
8032
- "INVALID_INPUT",
8033
- "NOT_AUTHENTICATED",
8034
- "CANCELLED",
8035
- "SIGNER_REJECTED",
8036
- "VERIFICATION_REQUIRED",
8037
- "INSUFFICIENT_BALANCE",
8038
- "INVALID_RECIPIENT",
8039
- "ROLE_PERMISSION_DENIED",
8040
- "RATE_LIMITED",
8041
- "OTP_EXPIRED",
8042
- "WRONG_STATE"
8043
- ]);
8044
8573
  /**
8045
8574
  * Adapt the host's already-initialized PostHog-like client. This function does
8046
8575
  * not import, initialize, configure, or own PostHog.
@@ -8077,7 +8606,7 @@ function fromPostHog(client, options = {}) {
8077
8606
  return {
8078
8607
  resolveContext: () => {
8079
8608
  if (!isEnabled(options.enabled) || client === null || client === void 0) return void 0;
8080
- return resolveContext(options.context) ?? {};
8609
+ return resolveRawContext(options.context) ?? {};
8081
8610
  },
8082
8611
  captureOperationFailure,
8083
8612
  captureException
@@ -8091,6 +8620,34 @@ function deliver(capture) {
8091
8620
  ignoreDeliveryFailure(capture());
8092
8621
  } catch {}
8093
8622
  }
8623
+ /**
8624
+ * Fan one SDK failure out to several adapters (issue #877). The SDK's own
8625
+ * first-party relay and an optional host-provided adapter both observe the same
8626
+ * failure. Order matters for `resolveContext`: the FIRST adapter that returns a
8627
+ * context wins, so pass the host adapter first to keep its call-start snapshot.
8628
+ * Each capture is isolated — a throwing adapter never blocks its peers or the
8629
+ * SDK. Returns `undefined` when no adapters are present (zero observation work).
8630
+ */
8631
+ function composeObservationAdapters(...adapters) {
8632
+ const present = adapters.filter((adapter) => adapter !== void 0);
8633
+ if (present.length === 0) return void 0;
8634
+ if (present.length === 1) return present[0];
8635
+ const fanOut = (select) => {
8636
+ for (const adapter of present) try {
8637
+ ignoreDeliveryFailure(select(adapter));
8638
+ } catch {}
8639
+ };
8640
+ return {
8641
+ resolveContext: () => {
8642
+ for (const adapter of present) try {
8643
+ const context = adapter.resolveContext?.();
8644
+ if (context !== void 0) return context;
8645
+ } catch {}
8646
+ },
8647
+ captureOperationFailure: (failure) => fanOut((adapter) => adapter.captureOperationFailure(failure)),
8648
+ captureException: (failure) => fanOut((adapter) => adapter.captureException(failure))
8649
+ };
8650
+ }
8094
8651
  /** @internal Decorates public client method bundles at the owning SDK boundary. */
8095
8652
  function observeSdkClient(client, adapter) {
8096
8653
  if (adapter === void 0) return client;
@@ -8194,7 +8751,7 @@ function report(adapter, kind, operation, cause, invocationContext) {
8194
8751
  }
8195
8752
  function resolveAdapterContext(adapter) {
8196
8753
  try {
8197
- return sanitizeObservationContext(adapter.resolveContext?.());
8754
+ return adapter.resolveContext?.();
8198
8755
  } catch {
8199
8756
  return;
8200
8757
  }
@@ -8225,8 +8782,12 @@ function postHogProperties(failure, context) {
8225
8782
  return properties;
8226
8783
  }
8227
8784
  function resolveContext(context) {
8785
+ return sanitizeObservationContext(resolveRawContext(context));
8786
+ }
8787
+ /** Resolve the host context closure without sanitizing — the call-start snapshot. */
8788
+ function resolveRawContext(context) {
8228
8789
  try {
8229
- return sanitizeObservationContext(typeof context === "function" ? context() : context);
8790
+ return typeof context === "function" ? context() : context;
8230
8791
  } catch {
8231
8792
  return;
8232
8793
  }
@@ -8288,6 +8849,43 @@ function isCapxulResult(value) {
8288
8849
  function isFailedResult(value) {
8289
8850
  return isCapxulResult(value) && value.ok === false && "error" in value;
8290
8851
  }
8852
+ //#endregion
8853
+ //#region src/observation-relay.ts
8854
+ /**
8855
+ * Build the SDK-owned first-party relay adapter. Both capture entry points send
8856
+ * the same redacted envelope fire-and-forget; delivery is fully isolated from
8857
+ * the SDK's `CapxulResult` and can never throw into the caller.
8858
+ */
8859
+ function createCapxulFirstPartyRelay(options) {
8860
+ const fetchImpl = options.fetch ?? resolveGlobalFetch();
8861
+ const send = (failure) => {
8862
+ if (fetchImpl === void 0) return;
8863
+ try {
8864
+ const headers = { "content-type": "application/json" };
8865
+ const encodedContext = encodeObservationContextHeader(failure.context);
8866
+ if (encodedContext !== void 0) headers[OBSERVATION_CONTEXT_HEADER] = encodedContext;
8867
+ const body = JSON.stringify({
8868
+ operation: failure.operation,
8869
+ error_kind: failure.errorKind,
8870
+ sdk_version: failure.sdkVersion
8871
+ });
8872
+ Promise.resolve(fetchImpl(options.ingestUrl, {
8873
+ method: "POST",
8874
+ headers,
8875
+ body,
8876
+ keepalive: true
8877
+ })).catch(() => void 0);
8878
+ } catch {}
8879
+ };
8880
+ return {
8881
+ captureOperationFailure: send,
8882
+ captureException: send
8883
+ };
8884
+ }
8885
+ function resolveGlobalFetch() {
8886
+ const candidate = globalThis.fetch;
8887
+ return typeof candidate === "function" ? candidate : void 0;
8888
+ }
8291
8889
  const SDK_VERSION = version;
8292
8890
  const collectProductionFlowPorts = Effect.gen(function* () {
8293
8891
  const authClient = yield* AuthClientPortTag;
@@ -8575,7 +9173,7 @@ async function createCapxulClient$1(input) {
8575
9173
  ...input.authBaseUrl === void 0 ? {} : { override: input.authBaseUrl }
8576
9174
  });
8577
9175
  let signer = input.signer;
8578
- if (signer === void 0 && (input.requirement ?? "none") === "deployed" && runtime === "browser") signer = createOpenfortBrowserSignerFromBootstrap({
9176
+ if (signer === void 0 && runtime === "browser") signer = createOpenfortBrowserSignerFromBootstrap({
8579
9177
  ...adapters.value.bootstrap,
8580
9178
  authBaseUrl: resolvedAuthBaseUrl
8581
9179
  });
@@ -8585,23 +9183,33 @@ async function createCapxulClient$1(input) {
8585
9183
  authCache: adapters.value.ports.authCache,
8586
9184
  requirement: input.requirement ?? "none",
8587
9185
  ...signer === void 0 ? {} : { signer },
9186
+ orgPort: new ConvexOrganizationAdapter({ convex: adapters.value.ports.convexCall }),
9187
+ ...signer === void 0 ? {} : { organizationSetup: new ConvexOrganizationSetupAdapter({
9188
+ convex: adapters.value.ports.convexCall,
9189
+ signer
9190
+ }) },
8588
9191
  ...input.signal === void 0 ? {} : { signal: input.signal },
8589
9192
  ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
8590
9193
  invokeTimeoutMs: input.invokeTimeoutMs ?? 3e4
8591
9194
  }), signer);
8592
9195
  const upstreamClose = adapters.value.close;
9196
+ const value = {
9197
+ ...client,
9198
+ _internal: {
9199
+ ...client._internal,
9200
+ close: async () => {
9201
+ if ("resetSession" in (signer ?? {})) signer.resetSession();
9202
+ await upstreamClose();
9203
+ }
9204
+ }
9205
+ };
9206
+ const relay = runtime === "browser" ? createCapxulFirstPartyRelay({
9207
+ ingestUrl: deriveObserveIngestUrl(adapters.value.bootstrap),
9208
+ ...input.fetch === void 0 ? {} : { fetch: input.fetch }
9209
+ }) : void 0;
8593
9210
  return {
8594
9211
  ok: true,
8595
- value: observeSdkClient({
8596
- ...client,
8597
- _internal: {
8598
- ...client._internal,
8599
- close: async () => {
8600
- if ("resetSession" in (signer ?? {})) signer.resetSession();
8601
- await upstreamClose();
8602
- }
8603
- }
8604
- }, input.observation)
9212
+ value: observeSdkClient(value, composeObservationAdapters(input.observation, relay))
8605
9213
  };
8606
9214
  } catch (cause) {
8607
9215
  await adapters.value.close().catch(() => void 0);
@@ -8643,6 +9251,38 @@ function resolveInput(input) {
8643
9251
  };
8644
9252
  }
8645
9253
  }
9254
+ /**
9255
+ * Address of the Capxul client-relay ingest endpoint (Pipe 1, issue #877). The
9256
+ * HTTP router that serves `/v1/client/observe` lives on the deployment's
9257
+ * `.convex.site` host, while the bootstrap-resolved `convexUrl` is the sibling
9258
+ * `.convex.cloud` (WebSocket/query) host — the same deterministic pairing
9259
+ * `mintQuickstartKey` inverts. For any standard Convex deployment (including
9260
+ * production) rewriting the suffix targets the router directly with no host
9261
+ * proxy, so it is the primary rule.
9262
+ *
9263
+ * `siteBaseUrl` is deliberately NOT the primary source: it is an app-configured
9264
+ * field that defaults to a placeholder (`https://capxul.local`, see
9265
+ * `credentials/applications.ts`) for registered applications, so trusting it
9266
+ * outright would POST to a dead host for the common case. It is only consulted
9267
+ * as a fallback for a custom-domain `convexUrl` (no deterministic `.convex.site`
9268
+ * sibling) when it carries a real, non-placeholder origin.
9269
+ */
9270
+ function deriveObserveIngestUrl(bootstrap) {
9271
+ const convexUrl = bootstrap.convexUrl.replace(/\/+$/, "");
9272
+ if (convexUrl.endsWith(".convex.cloud")) return `${convexUrl.replace(/\.convex\.cloud$/, ".convex.site")}/v1/client/observe`;
9273
+ return `${(usableSiteOrigin(bootstrap.siteBaseUrl) ?? convexUrl).replace(/\/+$/, "")}/v1/client/observe`;
9274
+ }
9275
+ /** A configured `siteBaseUrl` usable as a router host, or `undefined` if it is the placeholder. */
9276
+ function usableSiteOrigin(siteBaseUrl) {
9277
+ try {
9278
+ const url = new URL(siteBaseUrl);
9279
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
9280
+ if (url.hostname === "capxul.local") return void 0;
9281
+ return url.origin;
9282
+ } catch {
9283
+ return;
9284
+ }
9285
+ }
8646
9286
  function detectRuntime() {
8647
9287
  const globalAny = globalThis;
8648
9288
  return globalAny.window !== void 0 || globalAny.document !== void 0 ? "browser" : "node";