@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.
@@ -0,0 +1,462 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { XKOVAClient } from './client.js';
3
+ import { XKOVAApiError } from './errors.js';
4
+ import { isAllowedProtocol } from './url.js';
5
+ import { WorkforceWalletsResource } from './resources/workforce-wallets.js';
6
+ // These helpers operate only on a customer's cookie/reference and CSRF proof.
7
+ // Reusing their cryptography does not reuse member identity or session authority.
8
+ export { serializeXKOVAMemberSessionCookie as serializeXKOVAWorkforceSessionCookie, serializeXKOVAMemberFlowCookie as serializeXKOVAWorkforceFlowCookie, serializeClearedXKOVAMemberCookie as serializeClearedXKOVAWorkforceCookie, readXKOVAMemberCookie as readXKOVAWorkforceCookie, readXKOVAMemberCallbackFlow as readXKOVAWorkforceCallbackFlow, xkovaMemberFlowCookieName as xkovaWorkforceFlowCookieName, createXKOVAMemberCsrfToken as createXKOVAWorkforceCsrfToken, verifyXKOVAMemberCsrfToken as verifyXKOVAWorkforceCsrfToken, assertXKOVAMemberRequestOrigin as assertXKOVAWorkforceRequestOrigin, } from './member-bff.js';
9
+ export class XKOVAWorkforceBffError extends Error {
10
+ code;
11
+ constructor(code) {
12
+ super('The workforce sign-in or session cannot safely continue. Start sign-in again.');
13
+ this.code = code;
14
+ this.name = 'XKOVAWorkforceBffError';
15
+ }
16
+ }
17
+ /** Confidential workforce protocol. It neither enrolls a subject nor creates local staff authority. */
18
+ export class XKOVAWorkforceBffClient {
19
+ options;
20
+ client;
21
+ fingerprint;
22
+ constructor(options) {
23
+ this.options = options;
24
+ if (!/^xkova_wf_[A-Za-z0-9_-]{43}$/.test(options.workforceKey)) {
25
+ throw new TypeError('Invalid workforce application selector');
26
+ }
27
+ this.client = XKOVAClient.forTenant(options);
28
+ const origin = new URL(options.baseUrl).origin;
29
+ this.fingerprint = digest(JSON.stringify([origin, options.apiKey, options.workforceKey]));
30
+ }
31
+ async beginAuthorization(input) {
32
+ const origin = new URL(input.browserOrigin);
33
+ const callback = exactCallback(input.redirectUri);
34
+ if (origin.origin !== input.browserOrigin ||
35
+ !isAllowedProtocol(origin) ||
36
+ callback.origin !== origin.origin) {
37
+ throw new TypeError('Workforce callback must belong to the exact application origin');
38
+ }
39
+ const state = random(), codeVerifier = random();
40
+ const transaction = {
41
+ clientFingerprint: this.fingerprint,
42
+ status: 'pending',
43
+ redirectUri: callback.href,
44
+ codeVerifier,
45
+ codeChallenge: digest(codeVerifier),
46
+ exchangeAttemptId: random(),
47
+ sessionReference: random(),
48
+ expiresAt: Date.now() + 300_000,
49
+ };
50
+ const key = digest(state);
51
+ await input.transactionStore.put(key, transaction);
52
+ const result = object(await this.postJson('/workforce-auth/transactions', {
53
+ browser_origin: origin.origin,
54
+ redirect_uri: callback.href,
55
+ code_challenge: transaction.codeChallenge,
56
+ code_challenge_method: 'S256',
57
+ }, transaction.exchangeAttemptId));
58
+ const expiresAt = timestamp(result, 'expires_at');
59
+ if (expiresAt <= Date.now() || expiresAt > transaction.expiresAt + 5_000)
60
+ throw new Error('Invalid workforce transaction lifetime');
61
+ const issuer = text(result, 'issuer');
62
+ secureUrl(issuer);
63
+ const authorizationEndpoint = secureUrl(text(result, 'authorization_endpoint')).href;
64
+ const audience = text(result, 'audience'), nonce = text(result, 'nonce');
65
+ opaque(nonce);
66
+ const maxAuthAgeSeconds = result.max_auth_age_seconds;
67
+ if (!Number.isSafeInteger(maxAuthAgeSeconds) || Number(maxAuthAgeSeconds) < 1)
68
+ throw new Error('Invalid workforce authentication policy');
69
+ const requiredAcrValues = strings(result.required_acr_values);
70
+ const bound = {
71
+ ...transaction,
72
+ transactionId: text(result, 'transaction_id'),
73
+ nonce,
74
+ issuer,
75
+ audience,
76
+ expiresAt,
77
+ };
78
+ await input.transactionStore.replace(key, bound);
79
+ return {
80
+ state,
81
+ nonce,
82
+ redirectUri: callback.href,
83
+ codeChallenge: transaction.codeChallenge,
84
+ codeChallengeMethod: 'S256',
85
+ issuer,
86
+ audience,
87
+ authorizationEndpoint,
88
+ maxAuthAgeSeconds: Number(maxAuthAgeSeconds),
89
+ requiredAcrValues,
90
+ expiresAt: new Date(expiresAt).toISOString(),
91
+ };
92
+ }
93
+ async completeAuthorizationCallback(input) {
94
+ const callback = secureUrl(input.callbackUrl), states = callback.searchParams.getAll('state');
95
+ opaque(input.expectedState);
96
+ if (states.length !== 1 ||
97
+ !equal(states[0], input.expectedState) ||
98
+ callback.searchParams.has('error')) {
99
+ throw new XKOVAWorkforceBffError('binding_mismatch');
100
+ }
101
+ const codes = callback.searchParams.getAll('code');
102
+ if (codes.length !== 1 || !codes[0] || codes[0].length > 8192)
103
+ throw new XKOVAWorkforceBffError('binding_mismatch');
104
+ const code = codes[0], codeHash = digest(code), key = digest(input.expectedState);
105
+ return input.transactionStore.runExclusive(key, async () => {
106
+ const initial = await input.transactionStore.get(key);
107
+ if (!initial ||
108
+ initial.expiresAt <= Date.now() ||
109
+ initial.clientFingerprint !== this.fingerprint ||
110
+ callback.origin + callback.pathname !== initial.redirectUri) {
111
+ throw new XKOVAWorkforceBffError('transaction_unavailable');
112
+ }
113
+ if (initial.providerExchangeStarted &&
114
+ initial.providerExchangeStarted.codeHash !== codeHash) {
115
+ throw new XKOVAWorkforceBffError('binding_mismatch');
116
+ }
117
+ const reference = initial.sessionReference;
118
+ return input.sessionStore.runExclusive(reference, async () => {
119
+ let transaction = initial;
120
+ if (transaction.stagedProofToken && !transaction.providerExchangeStarted) {
121
+ throw new XKOVAWorkforceBffError('transaction_unavailable');
122
+ }
123
+ const existing = await input.sessionStore.get(reference);
124
+ if (existing) {
125
+ this.assertSession(existing);
126
+ if (existing.authorizationCodeHash !== codeHash || existing.logoutPending) {
127
+ throw new XKOVAWorkforceBffError('session_unavailable');
128
+ }
129
+ await input.transactionStore.replace(key, { ...transaction, status: 'completed' });
130
+ return { reference, session: existing };
131
+ }
132
+ if (transaction.status === 'completed' ||
133
+ !transaction.transactionId ||
134
+ !transaction.nonce ||
135
+ !transaction.issuer ||
136
+ !transaction.audience)
137
+ throw new XKOVAWorkforceBffError('session_unavailable');
138
+ if (!transaction.stagedProofToken) {
139
+ if (transaction.providerExchangeStarted)
140
+ throw new XKOVAWorkforceBffError('provider_exchange_outcome_unknown');
141
+ // Freeze application data before consuming a code. A later retry cannot replace it.
142
+ transaction = {
143
+ ...transaction,
144
+ providerExchangeStarted: {
145
+ codeHash,
146
+ applicationData: JSON.parse(JSON.stringify({ value: input.applicationData })).value,
147
+ },
148
+ };
149
+ await input.transactionStore.replace(key, transaction);
150
+ const proof = await input.exchangeProviderCode({
151
+ code,
152
+ codeVerifier: transaction.codeVerifier,
153
+ redirectUri: transaction.redirectUri,
154
+ expectedNonce: transaction.nonce,
155
+ issuer: transaction.issuer,
156
+ audience: transaction.audience,
157
+ });
158
+ if (typeof proof.proofToken !== 'string' ||
159
+ !proof.proofToken ||
160
+ proof.proofToken.length > 65536) {
161
+ throw new Error('Invalid workforce provider proof result');
162
+ }
163
+ transaction = { ...transaction, stagedProofToken: proof.proofToken };
164
+ await input.transactionStore.replace(key, transaction);
165
+ }
166
+ const material = parseMaterial(await this.postJson('/workforce-auth/exchange', {
167
+ transaction_id: transaction.transactionId,
168
+ nonce: transaction.nonce,
169
+ proof_token: transaction.stagedProofToken,
170
+ exchange_attempt_id: transaction.exchangeAttemptId,
171
+ }, transaction.exchangeAttemptId));
172
+ const stored = {
173
+ clientFingerprint: this.fingerprint,
174
+ authorizationCodeHash: codeHash,
175
+ material,
176
+ applicationExpiresAt: timestamp(material.session, 'absolute_expires_at'),
177
+ applicationData: transaction.providerExchangeStarted.applicationData,
178
+ };
179
+ await input.sessionStore.put(reference, stored);
180
+ await input.transactionStore.replace(key, { ...transaction, status: 'completed' });
181
+ return { reference, session: stored };
182
+ });
183
+ });
184
+ }
185
+ async getSession(sessionToken) {
186
+ credential(sessionToken, 'xwfs');
187
+ return parseSession(await this.getJson('/workforce-auth/session', sessionToken));
188
+ }
189
+ async resolveSession(input) {
190
+ opaque(input.reference);
191
+ return input.sessionStore.runExclusive(input.reference, async () => {
192
+ let stored = await input.sessionStore.get(input.reference);
193
+ if (!stored || stored.logoutPending)
194
+ return null;
195
+ this.assertSession(stored);
196
+ if (stored.applicationExpiresAt <= Date.now())
197
+ return null;
198
+ try {
199
+ if (timestamp(stored.material.session, 'expires_at') <= Date.now() + 30_000) {
200
+ const material = parseMaterial(await this.postJson('/workforce-auth/refresh', {
201
+ refresh_token: stored.material.refresh_token,
202
+ refresh_attempt_token: stored.material.refresh_attempt_token,
203
+ }));
204
+ assertSameHuman(stored.material.session, material.session);
205
+ stored = { ...stored, material };
206
+ await input.sessionStore.replace(input.reference, stored);
207
+ }
208
+ const session = await this.getSession(stored.material.session_token);
209
+ assertSameHuman(stored.material.session, session);
210
+ stored = { ...stored, material: { ...stored.material, session } };
211
+ await input.sessionStore.replace(input.reference, stored);
212
+ return stored;
213
+ }
214
+ catch (error) {
215
+ if (error instanceof XKOVAApiError && (error.status === 401 || error.status === 403)) {
216
+ await input.sessionStore.replace(input.reference, { ...stored, logoutPending: true });
217
+ return null;
218
+ }
219
+ throw error;
220
+ }
221
+ });
222
+ }
223
+ /** Request-scoped resources only. Check the browser's Origin/CSRF before invoking writes. */
224
+ async resolveResources(input) {
225
+ const session = await this.resolveSession(input);
226
+ if (!session)
227
+ return null;
228
+ if (input.actionGrant) {
229
+ opaque(input.actionGrant.grant_token);
230
+ if (timestamp(input.actionGrant, 'expires_at') <= Date.now())
231
+ throw new XKOVAWorkforceBffError('session_unavailable');
232
+ }
233
+ const bound = XKOVAClient.forWorkforceSession(this.options, {
234
+ workforceKey: this.options.workforceKey,
235
+ sessionToken: session.material.session_token,
236
+ browserOrigin: input.browserOrigin,
237
+ ...(input.actionGrant && { humanAuthorization: input.actionGrant.grant_token }),
238
+ });
239
+ return {
240
+ session,
241
+ resources: {
242
+ treasury: bound.treasury,
243
+ tokenization: bound.tokenization,
244
+ wallets: new WorkforceWalletsResource(bound),
245
+ },
246
+ };
247
+ }
248
+ /** Review the locator returned by human_authorization_required. Never send it to a third-party host. */
249
+ async inspectAction(input) {
250
+ opaque(input.authorizationToken);
251
+ const raw = object(await this.getJson(`/workforce-auth/actions/${encodeURIComponent(input.authorizationToken)}`, input.sessionToken, input.browserOrigin));
252
+ text(raw, 'operation');
253
+ text(raw, 'proof_method');
254
+ object(raw.review);
255
+ validateReview(raw);
256
+ return raw;
257
+ }
258
+ /** Starts a real provider ceremony after the user reviews the exact request. No action is executed. */
259
+ async beginActionProof(input) {
260
+ opaque(input.authorizationToken);
261
+ const raw = object(await this.postJson(`/workforce-auth/actions/${encodeURIComponent(input.authorizationToken)}/begin`, undefined, undefined, input));
262
+ if (text(raw, 'nonce') !== input.authorizationToken)
263
+ throw new XKOVAWorkforceBffError('binding_mismatch');
264
+ secureUrl(text(raw, 'issuer'));
265
+ secureUrl(text(raw, 'authorization_endpoint'));
266
+ text(raw, 'audience');
267
+ const maxAge = raw.max_auth_age_seconds;
268
+ if (!Number.isSafeInteger(maxAge) || Number(maxAge) < 1 || Number(maxAge) > 300)
269
+ throw new Error('Invalid workforce action policy');
270
+ strings(raw.required_acr_values);
271
+ strings(raw.required_amr_values);
272
+ object(raw.review);
273
+ validateReview(raw);
274
+ return raw;
275
+ }
276
+ /** Submit the real, freshly verified provider token. A lost response is never automatically retried. */
277
+ async completeActionProof(input) {
278
+ opaque(input.authorizationToken);
279
+ if (input.nonce !== input.authorizationToken)
280
+ throw new XKOVAWorkforceBffError('binding_mismatch');
281
+ if (!input.proofToken || input.proofToken.length > 16384)
282
+ throw new TypeError('Invalid workforce action proof');
283
+ const raw = object(await this.postJson(`/workforce-auth/actions/${encodeURIComponent(input.authorizationToken)}/complete`, { nonce: input.nonce, proof_token: input.proofToken }, undefined, input));
284
+ opaque(text(raw, 'grant_token'));
285
+ if (timestamp(raw, 'expires_at') <= Date.now())
286
+ throw new XKOVAWorkforceBffError('session_unavailable');
287
+ return raw;
288
+ }
289
+ async logoutSession(input) {
290
+ opaque(input.reference);
291
+ await input.sessionStore.runExclusive(input.reference, async () => {
292
+ const stored = await input.sessionStore.get(input.reference);
293
+ if (!stored)
294
+ return;
295
+ this.assertSession(stored);
296
+ if (!stored.logoutPending)
297
+ await input.sessionStore.replace(input.reference, { ...stored, logoutPending: true });
298
+ await this.postJson('/workforce-auth/sign-out', {
299
+ session_token: stored.material.session_token,
300
+ });
301
+ await input.sessionStore.delete(input.reference);
302
+ });
303
+ }
304
+ assertSession(session) {
305
+ if (session.clientFingerprint !== this.fingerprint)
306
+ throw new XKOVAWorkforceBffError('binding_mismatch');
307
+ }
308
+ postJson(path, body, idempotencyKey, context) {
309
+ const client = context
310
+ ? XKOVAClient.forWorkforceSession(this.options, {
311
+ ...context,
312
+ workforceKey: this.options.workforceKey,
313
+ })
314
+ : this.client;
315
+ return client.requestJson({
316
+ method: 'POST',
317
+ path,
318
+ ...(body !== undefined && { body }),
319
+ headers: { 'X-XKOVA-Workforce-Key': this.options.workforceKey },
320
+ ...(idempotencyKey !== undefined && { idempotencyKey }),
321
+ });
322
+ }
323
+ getJson(path, sessionToken, browserOrigin) {
324
+ const client = browserOrigin
325
+ ? XKOVAClient.forWorkforceSession(this.options, {
326
+ sessionToken,
327
+ browserOrigin,
328
+ workforceKey: this.options.workforceKey,
329
+ })
330
+ : this.client;
331
+ return client.requestJson({
332
+ method: 'GET',
333
+ path,
334
+ headers: {
335
+ 'X-XKOVA-Workforce-Key': this.options.workforceKey,
336
+ 'X-Workforce-Session': sessionToken,
337
+ },
338
+ });
339
+ }
340
+ }
341
+ /** Advisory browser UI data only; provider identity, credentials and grants stay confidential. */
342
+ export function projectXKOVAWorkforceSession(value) {
343
+ return {
344
+ signedIn: true,
345
+ staffId: value.material.session.staff_id,
346
+ roles: [...value.material.session.roles],
347
+ expiresAt: value.material.session.expires_at,
348
+ };
349
+ }
350
+ function parseMaterial(value) {
351
+ const raw = object(value);
352
+ const sessionToken = text(raw, 'session_token'), refreshToken = text(raw, 'refresh_token'), attempt = text(raw, 'refresh_attempt_token');
353
+ credential(sessionToken, 'xwfs');
354
+ credential(refreshToken, 'xwfr');
355
+ credential(attempt, 'xwfa');
356
+ return {
357
+ session_token: sessionToken,
358
+ refresh_token: refreshToken,
359
+ refresh_attempt_token: attempt,
360
+ session: parseSession(raw.session),
361
+ };
362
+ }
363
+ function parseSession(value) {
364
+ const raw = object(value), grants = object(raw.resource_grants);
365
+ for (const name of ['session_id', 'application_id', 'person_id', 'tenant_id', 'workspace_id']) {
366
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(text(raw, name))) {
367
+ throw new Error('Invalid workforce session identity');
368
+ }
369
+ }
370
+ if (!/^stf_[0-9A-Za-z]{22}$/.test(text(raw, 'staff_id')))
371
+ throw new Error('Invalid workforce staff identity');
372
+ const expires = timestamp(raw, 'expires_at'), absolute = timestamp(raw, 'absolute_expires_at');
373
+ if (expires > absolute || typeof raw.action_authorization_enabled !== 'boolean')
374
+ throw new Error('Invalid workforce session policy');
375
+ text(raw, 'provider_namespace');
376
+ text(raw, 'provider_subject');
377
+ strings(raw.roles);
378
+ strings(raw.scopes);
379
+ strings(grants.treasury_wallet_ids);
380
+ strings(grants.customer_token_ids);
381
+ if (!strings(grants.treasury_wallet_ids).every((id) => /^vlt_[0-9A-Za-z]{22}$/.test(id)) ||
382
+ !strings(grants.customer_token_ids).every((id) => /^ctk_[0-9A-Za-z]{22}$/.test(id))) {
383
+ throw new Error('Invalid workforce resource identity');
384
+ }
385
+ return raw;
386
+ }
387
+ function assertSameHuman(before, after) {
388
+ for (const name of [
389
+ 'session_id',
390
+ 'application_id',
391
+ 'staff_id',
392
+ 'person_id',
393
+ 'tenant_id',
394
+ 'workspace_id',
395
+ 'provider_namespace',
396
+ 'provider_subject',
397
+ ]) {
398
+ if (before[name] !== after[name])
399
+ throw new XKOVAWorkforceBffError('binding_mismatch');
400
+ }
401
+ }
402
+ function random() {
403
+ return randomBytes(32).toString('base64url');
404
+ }
405
+ function digest(value) {
406
+ return createHash('sha256').update(value).digest('base64url');
407
+ }
408
+ function opaque(value) {
409
+ if (!/^[A-Za-z0-9_-]{43}$/.test(value))
410
+ throw new TypeError('Invalid opaque workforce reference');
411
+ }
412
+ function credential(value, prefix) {
413
+ if (!new RegExp(`^${prefix}_[A-Za-z0-9_-]{43}$`).test(value))
414
+ throw new TypeError('Invalid workforce credential');
415
+ }
416
+ function equal(a, b) {
417
+ const left = Buffer.from(a), right = Buffer.from(b);
418
+ return left.length === right.length && timingSafeEqual(left, right);
419
+ }
420
+ function secureUrl(value) {
421
+ const url = new URL(value);
422
+ if (!isAllowedProtocol(url) || url.username || url.password || url.hash)
423
+ throw new TypeError('Invalid workforce URL');
424
+ return url;
425
+ }
426
+ function exactCallback(value) {
427
+ const url = secureUrl(value);
428
+ if (url.search)
429
+ throw new TypeError('Workforce callback cannot contain a query');
430
+ return url;
431
+ }
432
+ function object(value) {
433
+ if (!value || typeof value !== 'object' || Array.isArray(value))
434
+ throw new Error('Invalid workforce API response');
435
+ return value;
436
+ }
437
+ function text(value, key) {
438
+ const field = value[key];
439
+ if (typeof field !== 'string' || !field || field.length > 65536)
440
+ throw new Error('Invalid workforce API response field');
441
+ return field;
442
+ }
443
+ function strings(value) {
444
+ if (!Array.isArray(value) ||
445
+ value.length > 1000 ||
446
+ !value.every((item) => typeof item === 'string' && item.length > 0 && item.length <= 512)) {
447
+ throw new Error('Invalid workforce API response list');
448
+ }
449
+ return value;
450
+ }
451
+ function timestamp(value, key) {
452
+ const result = Date.parse(text(value, key));
453
+ if (!Number.isFinite(result))
454
+ throw new Error('Invalid workforce API expiry');
455
+ return result;
456
+ }
457
+ function validateReview(raw) {
458
+ if (!/^[a-f0-9]{64}$/i.test(text(raw, 'review_digest')) ||
459
+ timestamp(raw, 'expires_at') <= Date.now()) {
460
+ throw new XKOVAWorkforceBffError('session_unavailable');
461
+ }
462
+ }
package/llms.txt CHANGED
@@ -9,6 +9,7 @@ Use this package on a Node.js 22+ server. Read the installed declarations before
9
9
  - `dist/generated/types.d.ts`: exact request bodies, responses, parameters, and operation IDs. Import types through @xkova/sdk/types.
10
10
  - `dist/member-bff.d.ts`: authorization, session persistence, refresh, logout, action reviews, and human authorization.
11
11
  - `dist/member-resources.d.ts`: typed resources bound to the exact authenticated member.
12
+ - `dist/workforce-bff.d.ts`: confidential workforce sessions, durable callback recovery, exact action reviews and fresh provider proof. Use @xkova/sdk/workforce-bff, separately from member login and Console operators.
12
13
  - [Quickstart](https://docs.xkova.com/getting-started.html): sandbox access and first authenticated call.
13
14
  - [Customer OpenAPI](https://github.com/XKOVA/typescript-sdk/blob/main/schema/customer-api.json): machine-readable contract for the public SDK source. For an installed release, use its generated declarations.
14
15
  - [SDK guide](https://docs.xkova.com/sdk-overview.html): client and resource examples.
@@ -22,6 +23,7 @@ Use this package on a Node.js 22+ server. Read the installed declarations before
22
23
  1. Server business integration: create XKOVAClient.forTenant({ baseUrl, apiKey }). Use https://sandbox-api.xkova.com for sandbox and https://api.xkova.com for production. The API key binds the workspace and mode.
23
24
  2. Member application: use XKOVAMemberBffClient from @xkova/sdk/member-bff in your BFF. Resolve confidential sessions to request-scoped resources with resolveResources(). The browser sends only its application cookie and CSRF proof to your BFF.
24
25
  3. Browser companion: optionally install @xkova/browser. It calls your same-origin routes; it does not create those routes or install a session store.
26
+ 4. Customer staff application: use XKOVAWorkforceBffClient from @xkova/sdk/workforce-bff, an explicitly registered dedicated principal and immutable admitted provider subjects. Resolve each authenticated person per request. API-key attribution, email, groups and member credentials cannot create staff authority.
25
27
 
26
28
  ## Find a capability
27
29
 
@@ -40,6 +42,7 @@ Each namespace below is a property of XKOVAClient. Read its declaration under di
40
42
  | Transaction history | transactions |
41
43
  | Fee schedules and platform fees | feeSchedules |
42
44
  | Customer tokens, deployment, minting, supply, access | tokenization |
45
+ | Tenant treasury vaults, proposals, owner signatures, quorum and transfer status | treasury |
43
46
  | Real-world assets, cap tables, distributions, corporate actions, provenance | rwa |
44
47
  | Gateway registration and conformance | integrationGateways |
45
48
  | Branding and sending-domain configuration | branding |
@@ -48,6 +51,7 @@ Each namespace below is a property of XKOVAClient. Read its declaration under di
48
51
  | Webhook subscriptions and delivery operations | webhooks |
49
52
  | Token deployment/mint polling and off-ramp initiation | workflows |
50
53
  | Session exchange, durable review recovery, fresh human authorization | @xkova/sdk/member-bff |
54
+ | Institution workforce login, scoped sessions and exact action proofs | @xkova/sdk/workforce-bff |
51
55
  | Verify webhook signatures over raw body bytes | @xkova/sdk/webhook |
52
56
  | Offline RWA transparency proofs | @xkova/sdk/rwa-verify |
53
57
  | Customer contract ABIs | @xkova/sdk/abi |
@@ -60,6 +64,7 @@ API availability does not grant permission. Product entitlement, selected API-ke
60
64
  - Use exported package entry points and typed resource methods. Do not import dist files as runtime subpaths, fabricate request fields, or suppress type errors with any.
61
65
  - Production writes require the bearer key plus an active asymmetric requestSigner credential. Its async callback signs through your key provider. The SDK supplies RFC 9421 signatures and RFC 9530 digests. Production reads use bearer authentication.
62
66
  - Generate one idempotency key before a logical mutation and persist it. Reuse it only with the identical request. The SDK does not automatically retry writes. A timeout or disconnect can leave the outcome unknown; reconcile the existing resource before starting another operation.
67
+ - Treasury methods preserve API roles, governance holds, independent owner signatures and Safe quorum. An API key is not an authenticated staff session or a wallet signer. Use isHeldApproval before reading an entity from a governed mutation; approval does not itself produce a signature or prove settlement.
63
68
  - Use decimal strings for monetary values according to the request type. Preserve asset and chain identity throughout a flow. Do not perform monetary calculations with JavaScript floating-point numbers.
64
69
  - Read pagination and response types. Use paginateCursor or collectCursor where applicable; bound collection sizes and polling. Propagate AbortSignal and request deadlines.
65
70
  - Handle XKOVAApiError with its code, status, correlationId, and retryAfter. Handle XKOVAUploadError for the object-storage stage. Do not expose transport headers, provider proofs, or signed URLs in user errors or logs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xkova/sdk",
3
- "version": "0.1.0-alpha.7",
3
+ "version": "0.1.0-alpha.9",
4
4
  "description": "TypeScript SDK for the XKOVA API.",
5
5
  "homepage": "https://docs.xkova.com",
6
6
  "repository": {
@@ -31,6 +31,10 @@
31
31
  "types": "./dist/member-bff.d.ts",
32
32
  "default": "./dist/member-bff.js"
33
33
  },
34
+ "./workforce-bff": {
35
+ "types": "./dist/workforce-bff.d.ts",
36
+ "default": "./dist/workforce-bff.js"
37
+ },
34
38
  "./webhook": {
35
39
  "types": "./dist/webhook.d.ts",
36
40
  "default": "./dist/webhook.js"