@cortexkit/claustrum-client 0.9.0 → 0.11.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 CHANGED
@@ -28,10 +28,25 @@ credential caching, refresh, account scheduling, and retry policy.
28
28
  no provider ids are set. Since 0.6.2 a daemon too old to send the field decodes the
29
29
  same way, so a newer client still lists against an older daemon. `authMethod` stays
30
30
  optional.
31
+ • Since 0.10.0 `listScoped` also returns `grants` (how many grants the caller holds)
32
+ and `grantTuples` (`{ selectorKind, selector, operation }` per grant). They tell two
33
+ empty listings apart: `rows: []` with `grants === 0` means the caller has no grant;
34
+ `rows: []` with `grants > 0` means it holds a grant that currently reaches no
35
+ credential, usually a category no credential carries yet (the operator's fix is
36
+ `ck auth set-category`, or `ck auth reclassify --from-registry` for credentials created
37
+ before categories existed). Both are `undefined` against a vault too old to send
38
+ them; treat that as unknown, not as zero. A present but malformed value refuses the
39
+ reply like any other malformed field.
31
40
 
32
41
  The client sends `consumerIdentity: null` for every managed request so inherited
33
42
  `SUBC_MODULE_ID` and `SUBC_LAUNCH_NONCE` cannot impersonate a supervising host.
34
43
 
44
+ **Changed in 0.11.0:** the client depends on `@cortexkit/subc-client` ^0.22.0 (was
45
+ ^0.16.0). A host that also uses subc-client must move to the 0.22 line, so both share one
46
+ copy. One behaviour reaches callers: while the vault is restarting or refusing route
47
+ opens as retryable, a call now keeps retrying for up to 90 s (paced, with jitter) instead
48
+ of 30 s. Give every vault call on a latency-sensitive path its own timeout.
49
+
35
50
  ## OpenCode handle-file exports
36
51
 
37
52
  `defaultHandleFilePath`, `readHandleFile`, `handleFileRevision`, and `parseHandleFile`
package/dist/index.d.ts CHANGED
@@ -3,5 +3,5 @@ export { storeIdentity, storageFingerprint } from './identity.js';
3
3
  export { writeEnrollmentTokenFile, type EnrollmentTokenFile, } from './enrollment.js';
4
4
  export { MANIFEST_LOCK, manifestLockQuarantinePrefix, withManifestLock, writeHandleFileLocked, type ManifestHandleAccount, type ManifestHandleFile, type ManifestHandleProvider, type ManifestLockError, type ManifestLockErrorCode, } from './manifest-lock.js';
5
5
  export { ClaustrumCredentialError, credentialErrorAction, ERROR_CLASS_WIRE_SET, type ClaustrumCredentialErrorAction, type ClaustrumCredentialErrorClass, } from './errors.js';
6
- export { ClaustrumClient, type ClaustrumClientOptions, type ClaustrumConnector, type ClaustrumReporterSource, type CredentialStatus, type EnrollmentPollOutcome, type ScopedInventory, type ScopedInventoryRow, type ServedCredential, } from './wire.js';
6
+ export { ClaustrumClient, type ClaustrumClientOptions, type ClaustrumConnector, type ClaustrumReporterSource, type CredentialStatus, type EnrollmentPollOutcome, type ScopedGrantTuple, type ScopedInventory, type ScopedInventoryRow, type ServedCredential, } from './wire.js';
7
7
  export { HANDLE_FILE_CONTRACT, HandleFileValidationError, credentialIdMatchesProvider, defaultHandleFilePath, handleFileRevision, identifierIsValid, parseHandleFile, readHandleFile, type HandleAccount, type HandleFileIo, type HandleProvider, type OpenCodeHandleFileV1, } from './handles.js';
package/dist/wire.d.ts CHANGED
@@ -250,10 +250,38 @@ export interface ScopedInventoryRow {
250
250
  * and deliberately NOT over `record_version`, so a routine token refresh does not move it.
251
251
  * Compare it against the previous call's to decide whether to reconcile at all; it is an
252
252
  * opaque string and nothing but equality is defined on it.
253
+ *
254
+ * `grants` and `grantTuples` say what this caller is GRANTED, independent of what those
255
+ * grants currently reach. They are the only way to tell two empty listings apart:
256
+ *
257
+ * - `rows: []` with `grants === 0`: the caller holds no grant at all. The remedy is a
258
+ * grant (`ck auth grant`).
259
+ * - `rows: []` with `grants > 0`: the caller holds a grant that currently reaches no
260
+ * credential, typically a category no credential carries yet. The remedy is on the
261
+ * credential side (`ck auth set-category`, or `ck auth reclassify --from-registry` for
262
+ * credentials created before categories existed), not another grant.
263
+ *
264
+ * `grantTuples` lists each grant as `{ selectorKind, selector, operation }` (for example
265
+ * `{ selectorKind: 'category', selector: 'openai-native', operation: 'read' }`), so a
266
+ * consumer can name the selector that matched nothing. `grants` is its length.
267
+ *
268
+ * Both are `undefined` when the vault is too old to send them. That means "unknown",
269
+ * not "no grant": do not read an absent count as zero.
253
270
  */
254
271
  export interface ScopedInventory {
255
272
  readonly rows: readonly ScopedInventoryRow[];
256
273
  readonly view: string;
274
+ readonly grants: number | undefined;
275
+ readonly grantTuples: readonly ScopedGrantTuple[] | undefined;
276
+ }
277
+ /** One grant the caller holds, as the vault's `list_scoped` reply names it. */
278
+ export interface ScopedGrantTuple {
279
+ /** `exact` or `category`; decoded as an open string. */
280
+ readonly selectorKind: string;
281
+ /** A credential id for `exact`, a bare category name for `category`. */
282
+ readonly selector: string;
283
+ /** The granted operation, e.g. `read` or `list`; decoded as an open string. */
284
+ readonly operation: string;
257
285
  }
258
286
  /**
259
287
  * Exported for the golden-reply test only; `index.ts` re-exports by name, so this does not
package/dist/wire.js CHANGED
@@ -346,6 +346,7 @@ export class ClaustrumClient {
346
346
  await this.#reconnecting;
347
347
  }
348
348
  }
349
+ const GRANT_TUPLE_KEYS = ['operation', 'selector', 'selector_kind'];
349
350
  /**
350
351
  * Exported for the golden-reply test only; `index.ts` re-exports by name, so this does not
351
352
  * widen the published surface. The test drives THIS function rather than asserting keys,
@@ -445,7 +446,38 @@ export function decodeScopedInventory(response, logUnknownClass) {
445
446
  orgName: row.org_name,
446
447
  };
447
448
  });
448
- return { rows: decoded, view };
449
+ // ABSENT IS UNKNOWN, PRESENT MUST BE WELL-FORMED. A vault from before these fields
450
+ // omits both, and refusing that would fail every listing against a still-supported
451
+ // vault. A present but malformed value is a broken reply, and decoding it leniently
452
+ // would hand the caller a grant count it cannot trust to tell "no grant" from "a grant
453
+ // that reaches nothing".
454
+ const rawGrants = result?.grants;
455
+ let grants;
456
+ if (rawGrants !== undefined) {
457
+ if (typeof rawGrants !== 'number' || !Number.isSafeInteger(rawGrants) || rawGrants < 0) {
458
+ throw asCredentialError(response, 'invalid_response', logUnknownClass);
459
+ }
460
+ grants = rawGrants;
461
+ }
462
+ const rawTuples = result?.grant_tuples;
463
+ let grantTuples;
464
+ if (rawTuples !== undefined) {
465
+ if (!Array.isArray(rawTuples))
466
+ throw asCredentialError(response, 'invalid_response', logUnknownClass);
467
+ grantTuples = rawTuples.map((tuple) => {
468
+ if (!isRecord(tuple) ||
469
+ Object.keys(tuple).length !== GRANT_TUPLE_KEYS.length ||
470
+ !GRANT_TUPLE_KEYS.every((key) => typeof tuple[key] === 'string')) {
471
+ throw asCredentialError(response, 'invalid_response', logUnknownClass);
472
+ }
473
+ return {
474
+ selectorKind: tuple.selector_kind,
475
+ selector: tuple.selector,
476
+ operation: tuple.operation,
477
+ };
478
+ });
479
+ }
480
+ return { rows: decoded, view, grants, grantTuples };
449
481
  }
450
482
  function decodeEnrollmentResult(response, logUnknownClass) {
451
483
  if (hasCredentialError(response))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/claustrum-client",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,6 +28,6 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@cortexkit/store": "^0.2.1",
31
- "@cortexkit/subc-client": "^0.16.0"
31
+ "@cortexkit/subc-client": "^0.22.0"
32
32
  }
33
33
  }