@cortexkit/claustrum-client 0.3.0 → 0.5.0
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 +5 -0
- package/dist/wire.d.ts +50 -1
- package/dist/wire.js +31 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,6 +16,11 @@ credential caching, refresh, account scheduling, and retry policy.
|
|
|
16
16
|
by design (the vault cannot enumerate which). Treat it as gone either way: re-run
|
|
17
17
|
`ck auth migrate-opencode` to mint a fresh handle; the prior record, if any, is kept.
|
|
18
18
|
• `auth_required` means the record is latched and needs reauthentication.
|
|
19
|
+
• `statusCredential` on an unknown or revoked handle RESOLVES, it does not throw: it
|
|
20
|
+
returns `ready: false, lastErrorCode: 'not_found'` with `recordVersion`, `credentialId`
|
|
21
|
+
and `stalePending` absent. Since 0.5.0 `CredentialStatus.recordVersion` is optional for
|
|
22
|
+
that reason; handle `undefined` rather than defaulting it to a number, which would read
|
|
23
|
+
as a version older than every real one.
|
|
19
24
|
• Unknown server error classes are bounded to `transient`; callers must retry them
|
|
20
25
|
rather than treating a forward-compatible class as permanent.
|
|
21
26
|
|
package/dist/wire.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ export type ClaustrumClientOptions = {
|
|
|
15
15
|
/**
|
|
16
16
|
* Credential material plus optional non-secret identity metadata. Missing wire fields remain
|
|
17
17
|
* `undefined`; a present non-string identity value rejects the response as invalid.
|
|
18
|
+
*
|
|
19
|
+
* An absent identity field means the vault had nothing to assert — NOT that identity failed.
|
|
20
|
+
* Consumers must not collapse absence into mismatch: assert on presence, refuse on a present
|
|
21
|
+
* difference, and serve (log, if it matters) when the field is missing. Absence proves neither
|
|
22
|
+
* the right identity nor the wrong one, so treating it as a failure converts a vault that could
|
|
23
|
+
* not answer into a consumer that cannot serve. The vault records no per-read success (at most
|
|
24
|
+
* the first scoped read per principal), and nothing at all when a consumer declines to serve, so
|
|
25
|
+
* an outage caused that way is visible only to the consumer that caused it.
|
|
18
26
|
*/
|
|
19
27
|
export type ServedCredential = {
|
|
20
28
|
material: string;
|
|
@@ -30,6 +38,12 @@ export type ServedCredential = {
|
|
|
30
38
|
/**
|
|
31
39
|
* Provider account identity the served token executes under. Account-scoped routing joins this
|
|
32
40
|
* value with `recordVersion`; it is neither the operator's credential label nor the bearer handle.
|
|
41
|
+
*
|
|
42
|
+
* BEST-EFFORT, NEVER GUARANTEED, including for JWT providers. The vault parses the claim live
|
|
43
|
+
* from the served access token and falls back to stored login-time identity; both legs can be
|
|
44
|
+
* empty, and the serve path yields nothing rather than fabricating an id. It is absent when the
|
|
45
|
+
* provider has no known claim, the token does not carry one, or no identity was captured at
|
|
46
|
+
* login. Do not gate serving on its presence.
|
|
33
47
|
*/
|
|
34
48
|
accountId?: string;
|
|
35
49
|
/** Non-secret account display metadata captured at login. */
|
|
@@ -37,14 +51,43 @@ export type ServedCredential = {
|
|
|
37
51
|
/** Non-secret organization or workspace display metadata captured at login. */
|
|
38
52
|
orgName?: string;
|
|
39
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Non-secret health for one handle, read without minting anything.
|
|
56
|
+
*
|
|
57
|
+
* An unresolved handle (unknown or revoked) is an ordinary answer, not an error: it decodes
|
|
58
|
+
* as `ready: false, lastErrorCode: 'not_found'` with `credentialId`, `recordVersion` and
|
|
59
|
+
* `stalePending` all absent. The vault omits them on purpose so a probe learns nothing about
|
|
60
|
+
* what exists and no placeholder value is asserted about a record it never saw.
|
|
61
|
+
*/
|
|
40
62
|
export type CredentialStatus = {
|
|
41
63
|
ready: boolean;
|
|
42
64
|
lastErrorCode: string | null;
|
|
43
65
|
leaseHeld: boolean;
|
|
44
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Operator-chosen record label the handle resolved to, for verifying a binding the caller
|
|
68
|
+
* already holds. Not a routing key. Absent when the handle did not resolve.
|
|
69
|
+
*/
|
|
70
|
+
credentialId?: string;
|
|
71
|
+
/**
|
|
72
|
+
* The record's change cursor, present only when the handle resolved to a record.
|
|
73
|
+
*
|
|
74
|
+
* Optional, and OMITTED from the object rather than defaulted, because there is no honest
|
|
75
|
+
* default: 0 or -1 would compare as older than every real version, so a poller would read a
|
|
76
|
+
* revoked handle as a change it had yet to see, forever. With the key absent, TypeScript
|
|
77
|
+
* refuses arithmetic or ordering on it until the caller has handled `undefined`, and
|
|
78
|
+
* `'recordVersion' in status` is false. Never replace absence with a number (`?? 0`).
|
|
79
|
+
*/
|
|
80
|
+
recordVersion?: number;
|
|
45
81
|
stalePending?: boolean;
|
|
46
82
|
};
|
|
47
83
|
export type ClaustrumReporterSource = 'direct' | 'relay_status_field' | 'relay_message_parse';
|
|
84
|
+
/**
|
|
85
|
+
* Exported for the golden-reply test only; `index.ts` re-exports by name, so this does not
|
|
86
|
+
* widen the published surface.
|
|
87
|
+
*/
|
|
88
|
+
export declare function decodeCredential(response: unknown, logUnknownClass: (errorClass: string) => void): ServedCredential;
|
|
89
|
+
/** Exported for the golden-reply test only, like `decodeCredential`. */
|
|
90
|
+
export declare function decodeStatus(response: unknown, logUnknownClass: (errorClass: string) => void): CredentialStatus;
|
|
48
91
|
export declare class ClaustrumClient {
|
|
49
92
|
#private;
|
|
50
93
|
private constructor();
|
|
@@ -198,6 +241,12 @@ export interface ScopedInventory {
|
|
|
198
241
|
readonly rows: readonly ScopedInventoryRow[];
|
|
199
242
|
readonly view: string;
|
|
200
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Exported for the golden-reply test only; `index.ts` re-exports by name, so this does not
|
|
246
|
+
* widen the published surface. The test drives THIS function rather than asserting keys,
|
|
247
|
+
* because a key check passes on a decoder that reads the wrong key.
|
|
248
|
+
*/
|
|
249
|
+
export declare function decodeScopedInventory(response: unknown, logUnknownClass: (errorClass: string) => void): ScopedInventory;
|
|
201
250
|
/** What a poll found. `pending` is not an error: the operator has not decided yet. */
|
|
202
251
|
export type EnrollmentPollOutcome = {
|
|
203
252
|
readonly status: 'pending';
|
package/dist/wire.js
CHANGED
|
@@ -15,7 +15,11 @@ function asRecordVersion(value) {
|
|
|
15
15
|
function isOptionalString(value) {
|
|
16
16
|
return value === undefined || typeof value === 'string';
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Exported for the golden-reply test only; `index.ts` re-exports by name, so this does not
|
|
20
|
+
* widen the published surface.
|
|
21
|
+
*/
|
|
22
|
+
export function decodeCredential(response, logUnknownClass) {
|
|
19
23
|
if (hasCredentialError(response))
|
|
20
24
|
throw asCredentialError(response, 'invalid_response', logUnknownClass);
|
|
21
25
|
const result = isRecord(response) && isRecord(response.result) ? response.result : undefined;
|
|
@@ -61,15 +65,22 @@ function decodeCredential(response, logUnknownClass) {
|
|
|
61
65
|
orgName,
|
|
62
66
|
};
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
/** Exported for the golden-reply test only, like `decodeCredential`. */
|
|
69
|
+
export function decodeStatus(response, logUnknownClass) {
|
|
65
70
|
if (hasCredentialError(response))
|
|
66
71
|
throw asCredentialError(response, 'invalid_response', logUnknownClass);
|
|
67
72
|
const result = isRecord(response) && isRecord(response.result) ? response.result : undefined;
|
|
68
|
-
|
|
73
|
+
// Absent is a valid reply (the handle did not resolve); a PRESENT value that is not a
|
|
74
|
+
// non-negative integer is malformed. The producer omits the key rather than nulling it,
|
|
75
|
+
// so an explicit null is refused too.
|
|
76
|
+
const rawRecordVersion = result?.record_version;
|
|
77
|
+
const recordVersion = rawRecordVersion === undefined ? undefined : asRecordVersion(rawRecordVersion);
|
|
78
|
+
const credentialId = result?.credential_id;
|
|
69
79
|
if (typeof result?.ready !== 'boolean' ||
|
|
70
80
|
(result?.last_error_code !== undefined && result?.last_error_code !== null && typeof result?.last_error_code !== 'string') ||
|
|
71
81
|
typeof result?.lease_held !== 'boolean' ||
|
|
72
|
-
recordVersion === undefined ||
|
|
82
|
+
(rawRecordVersion !== undefined && recordVersion === undefined) ||
|
|
83
|
+
!isOptionalString(credentialId) ||
|
|
73
84
|
(result?.stale_pending !== undefined && typeof result.stale_pending !== 'boolean')) {
|
|
74
85
|
throw asCredentialError(response, 'invalid_status', logUnknownClass);
|
|
75
86
|
}
|
|
@@ -77,7 +88,8 @@ function decodeStatus(response, logUnknownClass) {
|
|
|
77
88
|
ready: result.ready,
|
|
78
89
|
lastErrorCode: result.last_error_code ?? null,
|
|
79
90
|
leaseHeld: result.lease_held,
|
|
80
|
-
|
|
91
|
+
...(credentialId === undefined ? {} : { credentialId }),
|
|
92
|
+
...(recordVersion === undefined ? {} : { recordVersion }),
|
|
81
93
|
...(result.stale_pending === undefined ? {} : { stalePending: result.stale_pending }),
|
|
82
94
|
};
|
|
83
95
|
}
|
|
@@ -286,7 +298,14 @@ export class ClaustrumClient {
|
|
|
286
298
|
error instanceof SubcCallError &&
|
|
287
299
|
error.kind === 'terminal' &&
|
|
288
300
|
error.code !== 'missing_identity' &&
|
|
289
|
-
error.code !== 'invalid_control_body'
|
|
301
|
+
error.code !== 'invalid_control_body' &&
|
|
302
|
+
// The daemon answers unknown_module only when no module with this id is
|
|
303
|
+
// registered or supervised at all; a vault that is restarting or warming up
|
|
304
|
+
// is reported as module_warming or target_unavailable, which the SDK already
|
|
305
|
+
// retries in place. A fresh connection to the same daemon gets the same
|
|
306
|
+
// answer, and the wasted reconnect would also start the reconnect backoff,
|
|
307
|
+
// leaving a genuinely wedged connection unable to recover for a minute.
|
|
308
|
+
error.code !== 'unknown_module');
|
|
290
309
|
}
|
|
291
310
|
async #reconnect() {
|
|
292
311
|
if (this.#closed)
|
|
@@ -318,7 +337,12 @@ export class ClaustrumClient {
|
|
|
318
337
|
await this.#reconnecting;
|
|
319
338
|
}
|
|
320
339
|
}
|
|
321
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Exported for the golden-reply test only; `index.ts` re-exports by name, so this does not
|
|
342
|
+
* widen the published surface. The test drives THIS function rather than asserting keys,
|
|
343
|
+
* because a key check passes on a decoder that reads the wrong key.
|
|
344
|
+
*/
|
|
345
|
+
export function decodeScopedInventory(response, logUnknownClass) {
|
|
322
346
|
if (hasCredentialError(response))
|
|
323
347
|
throw asCredentialError(response, 'invalid_response', logUnknownClass);
|
|
324
348
|
const result = isRecord(response) && isRecord(response.result) ? response.result : undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/claustrum-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,6 +27,6 @@
|
|
|
27
27
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cortexkit/subc-client": "^0.
|
|
30
|
+
"@cortexkit/subc-client": "^0.16.0"
|
|
31
31
|
}
|
|
32
32
|
}
|