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

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,83 @@ 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
+
167
+ Workforce session staff IDs and assigned treasury/token IDs use the API's
168
+ canonical 22-character base62 bodies (`stf_`, `vlt_`, `ctk_`). Preserve these
169
+ opaque values exactly. Server SDK alpha.9 fixes alpha.8's incorrect rejection
170
+ of these API responses. The separate enrollment typed-data UUID strings must
171
+ also remain unchanged; they are not the session's public resource IDs.
172
+
96
173
  Call resource methods with typed inputs:
97
174
 
98
175
  ```ts
@@ -533,6 +610,40 @@ try {
533
610
  }
534
611
  ```
535
612
 
613
+ ## Treasury vaults and transfer proposals
614
+
615
+ Use `client.treasury` from a trusted backend with the workspace's explicitly
616
+ granted treasury scopes and roles. Discover the chain and eligible assets before
617
+ creating a vault or proposal. An existing same-chain vault retains its real
618
+ owner set and threshold.
619
+
620
+ ```ts
621
+ import { isHeldApproval } from '@xkova/sdk';
622
+
623
+ const vaults = await xkova.treasury.listWallets();
624
+ const proposal = await xkova.treasury.getTransfer('trf_...');
625
+ const counterparties = await xkova.treasury.listCounterparties({ limit: 20 });
626
+ ```
627
+
628
+ Vault and counterparty setup can return a governance hold. Check `isHeldApproval`
629
+ before reading entity fields. Keep the original request and idempotency key;
630
+ resume through normal policy after independently eligible approvers complete the
631
+ required approval. The SDK does not approve, sign or retry a held operation.
632
+
633
+ `draftTransfer` creates a proposal. `getTransfer` reads its frozen signing context
634
+ and current quorum. Each eligible owner produces their own signature over that
635
+ exact Safe transaction. `submitTransferSignature` submits an existing signature;
636
+ it does not create a wallet or sign on anyone's behalf. `executeTransfer` retains
637
+ current role, signer, nonce, quorum and policy checks. A returned relay result is
638
+ not settlement; read the proposal until its actual confirmed or failed outcome.
639
+ After a timeout, reconcile the existing proposal before creating another one.
640
+
641
+ The API key's attributed staff ID is audit metadata, not a per-human session or
642
+ permission to bootstrap a staff wallet. This namespace does not expose Console
643
+ credentials, staff-wallet bootstrap credentials, or raw private-key material.
644
+ The supported workforce authentication and independent signer ceremonies remain
645
+ separate requirements for a complete customer treasury application.
646
+
536
647
  ## Webhooks and generated types
537
648
 
538
649
  ```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) {