@xkova/sdk 0.1.0-alpha.7 → 0.1.0-alpha.8

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/README.md CHANGED
@@ -93,6 +93,77 @@ one live successor; either key can be revoked immediately.
93
93
 
94
94
  ## Typed resources
95
95
 
96
+ ### Customer workforce applications
97
+
98
+ Use `@xkova/sdk/workforce-bff` on a confidential server for an institution's
99
+ staff application. It is separate from member login and Console operator
100
+ sessions. The Console administrator first registers an exact issuer, audience,
101
+ JWKS endpoint, application origins/callbacks and dedicated workspace API
102
+ principal. They explicitly admit each immutable provider subject to an existing
103
+ staff membership with narrowed roles and assigned resources. Email and provider
104
+ groups never grant authority. A transport key cannot admit its own users.
105
+
106
+ ```ts
107
+ import { XKOVAWorkforceBffClient } from '@xkova/sdk/workforce-bff';
108
+
109
+ const workforce = new XKOVAWorkforceBffClient({
110
+ baseUrl: 'https://sandbox-api.xkova.com',
111
+ apiKey: process.env.XKOVA_WORKFORCE_API_KEY!,
112
+ workforceKey: process.env.XKOVA_WORKFORCE_KEY!,
113
+ });
114
+ ```
115
+
116
+ Use separate encrypted durable transaction/session stores implementing
117
+ `XKOVAWorkforceStore`. Each write must commit independently of a later callback
118
+ error. Serialize each reference across replicas, and make `replace` fail when
119
+ the record is absent. The included PostgreSQL adapter demonstrates this contract.
120
+ Use a distinct namespace from member sessions. Keep session material, provider
121
+ proofs, PKCE verifiers and refresh attempts on the server.
122
+
123
+ `beginAuthorization` returns the registered provider endpoint, audience, nonce,
124
+ PKCE challenge and policy. Use a short-lived HttpOnly flow cookie for its state.
125
+ Your BFF exchanges the real provider code and verifies its signature, issuer,
126
+ audience, nonce and PKCE through `completeAuthorizationCallback`. XKOVA performs
127
+ its own registered-provider verification before issuing the workforce session.
128
+ An uncertain provider exchange cannot redeem the code again. A staged proof can
129
+ recover the same API exchange attempt. A provider token's actual expiry bounds
130
+ the session; refreshing XKOVA credentials does not refresh upstream identity.
131
+
132
+ Read only `projectXKOVAWorkforceSession` into browser UI. Resolve resource access
133
+ per request with `resolveResources({ reference, sessionStore, browserOrigin })`.
134
+ The browser's Origin and session-bound CSRF proof must be checked before writes.
135
+ The BFF pins its registered origin, workforce application and resolved human
136
+ session. Roles/scopes/resources are intersected by the API, never combined with
137
+ the transport principal's authority. A member credential cannot supply workforce
138
+ authority, and workforce sessions cannot enter Console operator routes.
139
+
140
+ For `human_authorization_required`, preserve the exact request, body, origin and
141
+ idempotency key in a private review record. Use `inspectAction` and
142
+ `beginActionProof` for its exact `authorization_token`. Show the returned review
143
+ before starting real fresh provider authentication with the returned nonce and
144
+ qualified assurance policy. Validate the provider callback's own state, PKCE,
145
+ issuer, audience, nonce and current subject; require fresh authentication rather
146
+ than accepting a cached login. Submit the real token with `completeActionProof`.
147
+ Keep its short-lived grant server-side and pass it as `actionGrant` to
148
+ `resolveResources` for only the unchanged frozen request. A grant never approves
149
+ another request or replaces independent Safe signatures/quorum. These methods
150
+ do not automatically retry an action or an uncertain proof completion.
151
+
152
+ `logoutSession` durably blocks the local session before retiring it at XKOVA.
153
+ Retry only that same logout if the network result is uncertain. It does not
154
+ promise a global logout at the external provider. No request may fall back to a
155
+ machine principal after workforce authentication fails.
156
+
157
+ The resolved `wallets` resource supports `list`, `provision`, `register` and
158
+ `signingBootstrap`. Provisioning and signing need the exact one-use human grant.
159
+ Register only after signing the API's returned enrollment typed data. Signing
160
+ bootstrap requires the existing proposal ID, its frozen Safe digest and nonce.
161
+ Use `@xkova/browser/wallet` for the isolated browser ceremony, with a fresh
162
+ purpose-bound admission and the expected wallet address. Keep the BFF key,
163
+ provider proofs and workforce session credentials on the server. Provider
164
+ admission does not replace wallet possession, current signer eligibility,
165
+ independent-person quorum or confirmed chain settlement.
166
+
96
167
  Call resource methods with typed inputs:
97
168
 
98
169
  ```ts
@@ -533,6 +604,40 @@ try {
533
604
  }
534
605
  ```
535
606
 
607
+ ## Treasury vaults and transfer proposals
608
+
609
+ Use `client.treasury` from a trusted backend with the workspace's explicitly
610
+ granted treasury scopes and roles. Discover the chain and eligible assets before
611
+ creating a vault or proposal. An existing same-chain vault retains its real
612
+ owner set and threshold.
613
+
614
+ ```ts
615
+ import { isHeldApproval } from '@xkova/sdk';
616
+
617
+ const vaults = await xkova.treasury.listWallets();
618
+ const proposal = await xkova.treasury.getTransfer('trf_...');
619
+ const counterparties = await xkova.treasury.listCounterparties({ limit: 20 });
620
+ ```
621
+
622
+ Vault and counterparty setup can return a governance hold. Check `isHeldApproval`
623
+ before reading entity fields. Keep the original request and idempotency key;
624
+ resume through normal policy after independently eligible approvers complete the
625
+ required approval. The SDK does not approve, sign or retry a held operation.
626
+
627
+ `draftTransfer` creates a proposal. `getTransfer` reads its frozen signing context
628
+ and current quorum. Each eligible owner produces their own signature over that
629
+ exact Safe transaction. `submitTransferSignature` submits an existing signature;
630
+ it does not create a wallet or sign on anyone's behalf. `executeTransfer` retains
631
+ current role, signer, nonce, quorum and policy checks. A returned relay result is
632
+ not settlement; read the proposal until its actual confirmed or failed outcome.
633
+ After a timeout, reconcile the existing proposal before creating another one.
634
+
635
+ The API key's attributed staff ID is audit metadata, not a per-human session or
636
+ permission to bootstrap a staff wallet. This namespace does not expose Console
637
+ credentials, staff-wallet bootstrap credentials, or raw private-key material.
638
+ The supported workforce authentication and independent signer ceremonies remain
639
+ separate requirements for a complete customer treasury application.
640
+
536
641
  ## Webhooks and generated types
537
642
 
538
643
  ```ts
package/dist/client.d.ts CHANGED
@@ -13,6 +13,7 @@ import { RampsResource } from './resources/ramps.js';
13
13
  import { RwaResource } from './resources/rwa.js';
14
14
  import { TenantResource } from './resources/tenant.js';
15
15
  import { TokenizationResource } from './resources/tokenization.js';
16
+ import { TreasuryResource } from './resources/treasury.js';
16
17
  import { TransactionsResource } from './resources/transactions.js';
17
18
  import { WalletsResource } from './resources/wallets.js';
18
19
  import { WebhooksResource } from './resources/webhooks.js';
@@ -47,6 +48,7 @@ export interface XKOVAMutationOptions extends XKOVARequestControl {
47
48
  export declare class XKOVAClient {
48
49
  private readonly config;
49
50
  private readonly memberContext?;
51
+ private readonly workforceContext?;
50
52
  private readonly apiBaseUrl;
51
53
  private readonly fetchImpl;
52
54
  private constructor();
@@ -65,6 +67,8 @@ export declare class XKOVAClient {
65
67
  get rwa(): RwaResource;
66
68
  get tenant(): TenantResource;
67
69
  get tokenization(): TokenizationResource;
70
+ /** Tenant treasury operations retain server-side roles, governance and signer checks. */
71
+ get treasury(): TreasuryResource;
68
72
  get transactions(): TransactionsResource;
69
73
  get wallets(): WalletsResource;
70
74
  get webhooks(): WebhooksResource;
package/dist/client.js CHANGED
@@ -16,6 +16,7 @@ import { RampsResource } from './resources/ramps.js';
16
16
  import { RwaResource } from './resources/rwa.js';
17
17
  import { TenantResource } from './resources/tenant.js';
18
18
  import { TokenizationResource } from './resources/tokenization.js';
19
+ import { TreasuryResource } from './resources/treasury.js';
19
20
  import { TransactionsResource } from './resources/transactions.js';
20
21
  import { WalletsResource } from './resources/wallets.js';
21
22
  import { WebhooksResource } from './resources/webhooks.js';
@@ -25,11 +26,13 @@ import { WorkspacesResource } from './resources/workspaces.js';
25
26
  export class XKOVAClient {
26
27
  config;
27
28
  memberContext;
29
+ workforceContext;
28
30
  apiBaseUrl;
29
31
  fetchImpl;
30
- constructor(config, memberContext) {
32
+ constructor(config, memberContext, workforceContext) {
31
33
  this.config = config;
32
34
  this.memberContext = memberContext;
35
+ this.workforceContext = workforceContext;
33
36
  if (config.timeoutMs !== undefined &&
34
37
  (!Number.isFinite(config.timeoutMs) || config.timeoutMs <= 0)) {
35
38
  throw new Error('XKOVAClient timeoutMs must be a positive finite number');
@@ -83,6 +86,10 @@ export class XKOVAClient {
83
86
  get tokenization() {
84
87
  return (this.namespaces.tokenization ??= new TokenizationResource(this));
85
88
  }
89
+ /** Tenant treasury operations retain server-side roles, governance and signer checks. */
90
+ get treasury() {
91
+ return (this.namespaces.treasury ??= new TreasuryResource(this));
92
+ }
86
93
  get transactions() {
87
94
  return (this.namespaces.transactions ??= new TransactionsResource(this));
88
95
  }
@@ -108,6 +115,20 @@ export class XKOVAClient {
108
115
  static forMemberSession(options, context) {
109
116
  return new XKOVAClient(options, Object.freeze({ ...context }));
110
117
  }
118
+ /** @internal Created only after the workforce BFF resolves its confidential store. */
119
+ static forWorkforceSession(options, context) {
120
+ if (!/^xkova_wf_[A-Za-z0-9_-]{43}$/.test(context.workforceKey) ||
121
+ !/^xwfs_[A-Za-z0-9_-]{43}$/.test(context.sessionToken) ||
122
+ (context.humanAuthorization !== undefined &&
123
+ !/^[A-Za-z0-9_-]{43}$/.test(context.humanAuthorization))) {
124
+ throw new TypeError('Invalid workforce session material');
125
+ }
126
+ const origin = new URL(context.browserOrigin);
127
+ if (origin.origin !== context.browserOrigin || !isAllowedProtocol(origin)) {
128
+ throw new TypeError('Invalid workforce application origin');
129
+ }
130
+ return new XKOVAClient(options, undefined, Object.freeze({ ...context }));
131
+ }
111
132
  /** @internal Resource transport. Use typed namespaces instead. */
112
133
  async requestJson(options) {
113
134
  const prepared = await this.prepareRequest(options);
@@ -211,6 +232,9 @@ export class XKOVAClient {
211
232
  headers.set(name, value);
212
233
  }
213
234
  if (this.memberContext) {
235
+ if (headers.has('X-Workforce-Session') || headers.has('X-XKOVA-Workforce-Key')) {
236
+ throw new TypeError('Member and workforce authority cannot be combined');
237
+ }
214
238
  const authority = {
215
239
  Authorization: `Bearer ${this.config.apiKey}`,
216
240
  'X-Member-Session-Id': this.memberContext.memberSessionId,
@@ -224,6 +248,40 @@ export class XKOVAClient {
224
248
  headers.set(name, value);
225
249
  }
226
250
  }
251
+ if (this.workforceContext) {
252
+ if (headers.has('X-XKOVA-Human-Authorization') && !this.workforceContext.humanAuthorization) {
253
+ throw new TypeError('A workforce action grant must come from the confidential context');
254
+ }
255
+ for (const name of [
256
+ 'Cookie',
257
+ 'X-Member-Session-Id',
258
+ 'X-XKOVA-Publishable-Key',
259
+ 'X-Account-Holder-Id',
260
+ 'X-End-User-Id',
261
+ 'X-OIDC-ID-Token',
262
+ 'X-Active-Scope',
263
+ 'X-Workspace-Id',
264
+ 'X-Workspace-Type',
265
+ ]) {
266
+ if (headers.has(name))
267
+ throw new TypeError('A workforce operation cannot mix identity or workspace contexts');
268
+ }
269
+ const authority = {
270
+ Authorization: `Bearer ${this.config.apiKey}`,
271
+ 'X-XKOVA-Workforce-Key': this.workforceContext.workforceKey,
272
+ 'X-Workforce-Session': this.workforceContext.sessionToken,
273
+ Origin: this.workforceContext.browserOrigin,
274
+ ...(this.workforceContext.humanAuthorization !== undefined && {
275
+ 'X-XKOVA-Human-Authorization': this.workforceContext.humanAuthorization,
276
+ }),
277
+ };
278
+ for (const [name, value] of Object.entries(authority)) {
279
+ if (headers.has(name) && headers.get(name) !== value) {
280
+ throw new TypeError('A workforce-bound operation cannot replace its session authority');
281
+ }
282
+ headers.set(name, value);
283
+ }
284
+ }
227
285
  return headers;
228
286
  }
229
287
  requestSignal(signal) {