@cortexkit/anthropic-auth-core 2.0.3 → 2.1.1
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/dist/cachekeep.d.ts +2 -0
- package/dist/cch.d.ts +19 -0
- package/dist/claude-code.d.ts +8 -2
- package/dist/claustrum-enrollment.d.ts +5 -1
- package/dist/constants.d.ts +15 -3
- package/dist/dump.d.ts +2 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +2698 -478
- package/dist/insula-quota.d.ts +25 -0
- package/dist/internal-types/claustrum/availability.d.ts +89 -0
- package/dist/internal-types/claustrum/consumer.d.ts +41 -1
- package/dist/internal-types/claustrum/custody.d.ts +135 -14
- package/dist/internal-types/claustrum/errors.d.ts +1 -0
- package/dist/internal-types/claustrum/index.d.ts +5 -3
- package/dist/internal-types/claustrum/insula.d.ts +48 -0
- package/dist/internal-types/claustrum/watch.d.ts +141 -0
- package/dist/internal-types/quota/format.d.ts +17 -3
- package/dist/internal-types/quota/index.d.ts +2 -2
- package/dist/internal-types/quota/projection.d.ts +16 -0
- package/dist/internal-types/setup/command-error.d.ts +15 -0
- package/dist/internal-types/setup/index.d.ts +2 -2
- package/dist/internal-types/setup/types.d.ts +12 -0
- package/dist/mid-conversation-output-config.d.ts +12 -15
- package/dist/migration-source.d.ts +2 -0
- package/dist/native-account-runtime.d.ts +3 -2
- package/dist/native-custody.d.ts +130 -0
- package/dist/native-enrollment-authority.d.ts +5 -1
- package/dist/native-host-auth-read.d.ts +2 -1
- package/dist/native-host-auth-write.d.ts +2 -0
- package/dist/native-runtime.d.ts +5 -1
- package/dist/native-vault-invalidation.d.ts +9 -0
- package/dist/native-vault-runtime.d.ts +32 -3
- package/dist/owner-only.d.ts +19 -0
- package/dist/pool-authority.d.ts +2 -1
- package/dist/provider-error.d.ts +34 -0
- package/dist/publication-rename.d.ts +11 -0
- package/dist/quota-manager.d.ts +21 -4
- package/dist/quotas.d.ts +19 -3
- package/dist/request-gzip.d.ts +99 -0
- package/package.json +4 -4
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { InsulaQuotaContext, InsulaUsageEntry } from "./internal-types/claustrum/index.js";
|
|
2
|
+
import { type AccountQuotaWindow, type AccountScopedQuotaWindow, type OAuthQuotaSnapshot } from './accounts.ts';
|
|
3
|
+
/** An insula reading in the shape our usage poll produces, before the account identity is attached. */
|
|
4
|
+
export type InsulaClaudeQuota = OAuthQuotaSnapshot & {
|
|
5
|
+
five_hour: AccountQuotaWindow;
|
|
6
|
+
seven_day: AccountQuotaWindow;
|
|
7
|
+
scoped: AccountScopedQuotaWindow[];
|
|
8
|
+
checkedAt: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Convert insula's Claude entry into our usage-poll snapshot. Every window's
|
|
12
|
+
* checkedAt is when insula fetched the reading, never when we read its cache,
|
|
13
|
+
* so a newer header or poll reading still wins. Both standard windows must be
|
|
14
|
+
* present; otherwise this returns undefined and our own poll stays in charge.
|
|
15
|
+
* The tertiary slot (legacy seven_day_opus/sonnet) is not a window we model.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeInsulaClaudeQuota(entry: InsulaUsageEntry, context: InsulaQuotaContext): InsulaClaudeQuota | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Insula fetches on its own schedule, so the account's stored quota can hold
|
|
20
|
+
* a window read after insula's fetch (a response header, or our own poll).
|
|
21
|
+
* Stored quota is replaced as one snapshot, newest wins, so publishing the
|
|
22
|
+
* older insula reading as is would be refused and lose its other windows.
|
|
23
|
+
* Keep the newer reading of each window instead.
|
|
24
|
+
*/
|
|
25
|
+
export declare function mergeInsulaQuota(reading: InsulaClaudeQuota, stored: OAuthQuotaSnapshot | undefined): OAuthQuotaSnapshot;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ClaustrumConsumerError } from './errors.js';
|
|
2
|
+
export type VaultFailureClass = 'unreachable' | 'answered';
|
|
3
|
+
/**
|
|
4
|
+
* Codes from claustrum-client's decoder and its subc transport. A retry action
|
|
5
|
+
* alone is not evidence of an outage: even transient vault refusals must win
|
|
6
|
+
* over a previously served credential. New codes fail closed until reviewed.
|
|
7
|
+
*/
|
|
8
|
+
export declare const VAULT_FAILURE_CLASSES: Readonly<{
|
|
9
|
+
ECONNREFUSED: "unreachable";
|
|
10
|
+
ENOENT: "unreachable";
|
|
11
|
+
ECONNRESET: "unreachable";
|
|
12
|
+
EPIPE: "unreachable";
|
|
13
|
+
ETIMEDOUT: "unreachable";
|
|
14
|
+
transport_error: "unreachable";
|
|
15
|
+
unavailable: "unreachable";
|
|
16
|
+
timeout: "unreachable";
|
|
17
|
+
route_closed: "unreachable";
|
|
18
|
+
daemon_draining: "unreachable";
|
|
19
|
+
connection_closed: "unreachable";
|
|
20
|
+
connection_dropped: "unreachable";
|
|
21
|
+
request_deadline: "unreachable";
|
|
22
|
+
deadline_exceeded_no_drop_observed: "unreachable";
|
|
23
|
+
module_reloading: "unreachable";
|
|
24
|
+
module_warming: "unreachable";
|
|
25
|
+
target_unavailable: "unreachable";
|
|
26
|
+
module_timeout: "unreachable";
|
|
27
|
+
invalid_response: "answered";
|
|
28
|
+
invalid_record_version: "answered";
|
|
29
|
+
invalid_expiry: "answered";
|
|
30
|
+
invalid_status: "answered";
|
|
31
|
+
invalid_params: "answered";
|
|
32
|
+
ttl_unsatisfiable: "answered";
|
|
33
|
+
not_found: "answered";
|
|
34
|
+
enrollment_revoked: "answered";
|
|
35
|
+
enrollment_invalid: "answered";
|
|
36
|
+
grant_denied: "answered";
|
|
37
|
+
credential_unavailable: "answered";
|
|
38
|
+
needs_reauth: "answered";
|
|
39
|
+
declined: "answered";
|
|
40
|
+
disabled: "answered";
|
|
41
|
+
identity_changed: "answered";
|
|
42
|
+
closed: "answered";
|
|
43
|
+
client_closed: "answered";
|
|
44
|
+
pending_exists: "answered";
|
|
45
|
+
pending_queue_full: "answered";
|
|
46
|
+
already_consumed: "answered";
|
|
47
|
+
superseded: "answered";
|
|
48
|
+
stale_generation: "answered";
|
|
49
|
+
missing_identity: "answered";
|
|
50
|
+
invalid_control_body: "answered";
|
|
51
|
+
unknown_module: "answered";
|
|
52
|
+
module_removed: "answered";
|
|
53
|
+
capability_denied: "answered";
|
|
54
|
+
unknown_channel: "unreachable";
|
|
55
|
+
stale_route_epoch: "unreachable";
|
|
56
|
+
capability_forbidden: "answered";
|
|
57
|
+
capability_unprovided: "answered";
|
|
58
|
+
capability_ambiguous: "answered";
|
|
59
|
+
invalid_capability_identifier: "answered";
|
|
60
|
+
binary_body_required: "answered";
|
|
61
|
+
binary_call_requires_call_binary: "answered";
|
|
62
|
+
corr_exhausted: "answered";
|
|
63
|
+
late_route_cleanup_failed: "answered";
|
|
64
|
+
reverse_request_capability_not_declared: "answered";
|
|
65
|
+
stale_route_handle: "answered";
|
|
66
|
+
reverse_request_unhandled: "answered";
|
|
67
|
+
handler_error: "answered";
|
|
68
|
+
health_error: "answered";
|
|
69
|
+
duplicate_module_id: "answered";
|
|
70
|
+
route_rejected: "answered";
|
|
71
|
+
lock_busy: "answered";
|
|
72
|
+
owner_invalid: "answered";
|
|
73
|
+
renewal_failed: "answered";
|
|
74
|
+
unsupported_platform: "answered";
|
|
75
|
+
EACCES: "answered";
|
|
76
|
+
EPERM: "answered";
|
|
77
|
+
ERR_PRIVATE_FILES_SECURITY: "answered";
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* An injected adapter may throw this ONLY when its connection/transport is
|
|
81
|
+
* unreachable, including a failed connect before getScoped. Do not wrap an
|
|
82
|
+
* adapter shutdown, cancellation, validation error or vault refusal in it.
|
|
83
|
+
* Its fixed message cannot echo enrollment or credential material, and its
|
|
84
|
+
* kind stays `unavailable` for callers handling ClaustrumConsumerError.
|
|
85
|
+
*/
|
|
86
|
+
export declare class ClaustrumUnreachableError extends ClaustrumConsumerError {
|
|
87
|
+
constructor();
|
|
88
|
+
}
|
|
89
|
+
export declare function classifyVaultFailure(error: unknown): VaultFailureClass;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ClaustrumReporterSource } from '@cortexkit/claustrum-client';
|
|
2
2
|
import type { QuotaObservation } from '../quota/index.js';
|
|
3
3
|
import type { RoutingRow } from '../routing/index.js';
|
|
4
|
-
import { type ClaustrumFamily, type ClaustrumScopedAttempt, type ClaustrumScopedClient, type IdentityParser } from './custody.js';
|
|
4
|
+
import { type ClaustrumFamily, type ClaustrumScopedAttempt, type ClaustrumScopedClient, type IdentityParser, type KeptReceiptState, type VaultGrantReach } from './custody.js';
|
|
5
5
|
import { type ClaustrumLogger } from './errors.js';
|
|
6
6
|
import { type InsulaQuotaOptions } from './insula.js';
|
|
7
7
|
import { type AccountMapper, type QuotaReceipt, type VaultRosterFile } from './roster.js';
|
|
8
|
+
import { type WatchMode } from './watch.js';
|
|
8
9
|
export interface ClaustrumConsumerOptions<Q = QuotaObservation> {
|
|
9
10
|
/**
|
|
10
11
|
* The roster file: one row per vault account (route id, credential id,
|
|
@@ -41,6 +42,12 @@ export interface ClaustrumConsumerOptions<Q = QuotaObservation> {
|
|
|
41
42
|
*/
|
|
42
43
|
onRoster?: (roster: VaultRosterFile) => void;
|
|
43
44
|
onError?: (error: unknown) => void;
|
|
45
|
+
/**
|
|
46
|
+
* How often `start()` ticks. A tick lists the vault when no vault watch is
|
|
47
|
+
* open (before it is accepted, or when the vault or the client cannot
|
|
48
|
+
* watch), and otherwise only every five minutes, as a safety net. A tick
|
|
49
|
+
* polls insula (at most once a minute) only while its watch is not open.
|
|
50
|
+
*/
|
|
44
51
|
pollIntervalMs?: number;
|
|
45
52
|
setTimeoutImpl?: typeof setTimeout;
|
|
46
53
|
clearTimeoutImpl?: typeof clearTimeout;
|
|
@@ -49,6 +56,15 @@ export interface ClaustrumConsumerOptions<Q = QuotaObservation> {
|
|
|
49
56
|
/** Optional background quota source. In-band readings still use recordQuota. */
|
|
50
57
|
insula?: InsulaQuotaOptions<Q>;
|
|
51
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* How a started consumer learns about changes, for logs and status displays.
|
|
61
|
+
* `insula` is present when the consumer was given an insula source. Never
|
|
62
|
+
* carries secrets.
|
|
63
|
+
*/
|
|
64
|
+
export interface ClaustrumWatchState {
|
|
65
|
+
readonly vault: WatchMode;
|
|
66
|
+
readonly insula?: WatchMode;
|
|
67
|
+
}
|
|
52
68
|
export interface SendOptions {
|
|
53
69
|
/**
|
|
54
70
|
* What kind of request this is (for example `model`, `quota`, `profile`),
|
|
@@ -71,9 +87,33 @@ export declare class ClaustrumConsumer<Q = QuotaObservation> {
|
|
|
71
87
|
constructor(options: ClaustrumConsumerOptions<Q>);
|
|
72
88
|
/** The last committed roster this instance saw, without any I/O. */
|
|
73
89
|
snapshot(): VaultRosterFile | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* What this consumer is granted, from its latest discovery, without any I/O.
|
|
92
|
+
* It explains an empty roster: `grants === 0` means no grant at all, while
|
|
93
|
+
* `grants > 0` means a grant that reaches no credential (`tuples` names its
|
|
94
|
+
* selector). Undefined means unknown, never zero: the vault or client is too
|
|
95
|
+
* old to send it, nothing was discovered yet, or the roster now in memory is
|
|
96
|
+
* a different view than the one this instance discovered (a peer process
|
|
97
|
+
* held the discovery lease and its newer roster was served instead).
|
|
98
|
+
*/
|
|
99
|
+
grantReach(): VaultGrantReach | undefined;
|
|
100
|
+
/** How this consumer currently learns about changes, without I/O. */
|
|
101
|
+
watchState(): ClaustrumWatchState;
|
|
102
|
+
/** Whether any account is using an in-memory outage receipt, without I/O. */
|
|
103
|
+
keptReceiptState(): KeptReceiptState;
|
|
74
104
|
/** Rows for `/routing`, from the last committed roster. */
|
|
75
105
|
routingRows(): RoutingRow[];
|
|
76
106
|
refresh(): Promise<VaultRosterFile | undefined>;
|
|
107
|
+
/**
|
|
108
|
+
* Poll in the background and open the vault and insula watches. Until the
|
|
109
|
+
* vault watch is accepted, and whenever it is unavailable, every tick lists
|
|
110
|
+
* the vault exactly as before watches existed. While it is open a tick
|
|
111
|
+
* lists only when five minutes have passed since the last discovery, since
|
|
112
|
+
* the watch keeps no history; it still checks `isCustodyActive`, so a switch
|
|
113
|
+
* to local mode closes the connection as quickly as before. Insula is
|
|
114
|
+
* polled on the tick only while its own watch is not open. A host not in
|
|
115
|
+
* vault mode makes no request.
|
|
116
|
+
*/
|
|
77
117
|
start(): void;
|
|
78
118
|
/**
|
|
79
119
|
* Fetch on a background tick, at most once per minute across projects with
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { type ClaustrumClient, type ClaustrumReporterSource, type EnrollmentTokenFile } from '@cortexkit/claustrum-client';
|
|
2
2
|
import { type ClaustrumLogger } from './errors.js';
|
|
3
3
|
import type { InsulaUsageTransport } from './insula.js';
|
|
4
|
+
import { type ScopedWatchSource, type WatchMode } from './watch.js';
|
|
5
|
+
/**
|
|
6
|
+
* `watchScoped` is present from claustrum-client 0.12.0. Hosts detect it at
|
|
7
|
+
* run time and keep polling without it.
|
|
8
|
+
*/
|
|
4
9
|
export type ClaustrumScopedClient = Pick<ClaustrumClient, 'listScoped' | 'getScoped' | 'reportAuthFailureScoped' | 'close'> & {
|
|
5
10
|
insula?: InsulaUsageTransport;
|
|
6
|
-
};
|
|
11
|
+
} & ScopedWatchSource;
|
|
7
12
|
/** The two credential types a plugin's family can hold; routing differs by type. */
|
|
8
13
|
export type VaultCredentialType = 'oauth' | 'api_key';
|
|
9
14
|
/**
|
|
@@ -44,6 +49,30 @@ export interface SkippedVaultRecord {
|
|
|
44
49
|
readonly credentialId?: string;
|
|
45
50
|
readonly reason: SkippedVaultReason;
|
|
46
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* One grant this consumer's enrollment holds, as the vault's list reply names
|
|
54
|
+
* it: for example `{ selectorKind: 'category', selector: 'openai-native',
|
|
55
|
+
* operation: 'read' }`. `selectorKind` is `exact` (the selector is a credential
|
|
56
|
+
* id) or `category` (a bare category name); both open strings.
|
|
57
|
+
*/
|
|
58
|
+
export interface VaultGrantTuple {
|
|
59
|
+
readonly selectorKind: string;
|
|
60
|
+
readonly selector: string;
|
|
61
|
+
readonly operation: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* What this consumer is granted, independent of what those grants currently
|
|
65
|
+
* reach. It is what tells two empty listings apart: no rows with `grants === 0`
|
|
66
|
+
* means no grant at all (the fix is `ck auth grant`), while no rows with
|
|
67
|
+
* `grants > 0` means a grant that reaches no credential, usually logins that
|
|
68
|
+
* lack the category (`ck auth reclassify --from-registry`); `tuples` names the
|
|
69
|
+
* selector that matched nothing. Never written to the roster file.
|
|
70
|
+
*/
|
|
71
|
+
export interface VaultGrantReach {
|
|
72
|
+
/** How many grants the consumer holds, deposit grants excluded. */
|
|
73
|
+
readonly grants: number;
|
|
74
|
+
readonly tuples: readonly VaultGrantTuple[];
|
|
75
|
+
}
|
|
47
76
|
export interface VaultInventory {
|
|
48
77
|
/**
|
|
49
78
|
* The vault's change cursor: a digest over exactly what this consumer can see
|
|
@@ -53,6 +82,11 @@ export interface VaultInventory {
|
|
|
53
82
|
readonly view: string;
|
|
54
83
|
readonly credentials: readonly VaultCredential[];
|
|
55
84
|
readonly skipped: readonly SkippedVaultRecord[];
|
|
85
|
+
/**
|
|
86
|
+
* The consumer's grants, as the vault's list reply named them. Undefined means unknown
|
|
87
|
+
* (the vault or client predates the fields), never zero grants.
|
|
88
|
+
*/
|
|
89
|
+
readonly grantReach?: VaultGrantReach;
|
|
56
90
|
}
|
|
57
91
|
export interface ClaustrumScopedIdentity {
|
|
58
92
|
readonly credentialId: string;
|
|
@@ -103,6 +137,57 @@ export interface ClaustrumScopedAttempt {
|
|
|
103
137
|
readonly accessToken: string;
|
|
104
138
|
readonly recordVersion: number;
|
|
105
139
|
readonly expiresAtMs: number | null;
|
|
140
|
+
/**
|
|
141
|
+
* True when this receipt was not freshly served but is the last token kept
|
|
142
|
+
* from an earlier read, used because the vault could not be reached. A 401
|
|
143
|
+
* with such a receipt must not be retried and is not reported to the vault,
|
|
144
|
+
* which is unreachable.
|
|
145
|
+
*/
|
|
146
|
+
readonly fromKeptReceipt?: true;
|
|
147
|
+
}
|
|
148
|
+
export interface KeptReceiptState {
|
|
149
|
+
readonly active: boolean;
|
|
150
|
+
readonly since: number | null;
|
|
151
|
+
}
|
|
152
|
+
export interface ClaustrumRosterWatchOptions {
|
|
153
|
+
/**
|
|
154
|
+
* Run the host's own discovery (`custody.discover()`, then its roster
|
|
155
|
+
* commit). Called with the newest notice's view when that view differs from
|
|
156
|
+
* the last discovered one, when a notice shows a sequence gap or a new
|
|
157
|
+
* epoch, and on the first notice of a watch re-opened after a close (the
|
|
158
|
+
* time between two watches was not observed). One call runs at a time;
|
|
159
|
+
* notices that arrive meanwhile earn at most one more call, which runs only
|
|
160
|
+
* if the view still differs. A rejection goes to `onError`.
|
|
161
|
+
*/
|
|
162
|
+
onView: (view: string) => unknown;
|
|
163
|
+
/**
|
|
164
|
+
* The view of the roster the host last committed. Defaults to the view of
|
|
165
|
+
* the last successful `custody.discover()` (outage probes excluded). Pass it
|
|
166
|
+
* when the host can commit a roster it did not list itself (a peer process
|
|
167
|
+
* held the discovery lease) or can fail after listing.
|
|
168
|
+
*/
|
|
169
|
+
view?: () => string | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Called whenever the mode changes. The watch starts in `polling`. Keep the
|
|
172
|
+
* host's own list timer at its usual pace unless the mode is `watching`.
|
|
173
|
+
*/
|
|
174
|
+
onMode?: (mode: WatchMode) => void;
|
|
175
|
+
/**
|
|
176
|
+
* The vault refused the watch, and its confirming `list_scoped` read it with
|
|
177
|
+
* this error (a revoked enrollment answers `not_found`). Custody has already
|
|
178
|
+
* applied it as it applies a refused discovery (`listFailed`: every kept
|
|
179
|
+
* receipt is dropped); handle it as the host handles that refusal.
|
|
180
|
+
*/
|
|
181
|
+
onRefused?: (error: unknown) => void;
|
|
182
|
+
/** A failed `onView`, or a watch that could not be opened. */
|
|
183
|
+
onError?: (error: unknown) => void;
|
|
184
|
+
}
|
|
185
|
+
/** A vault watch held by a custody; see `ClaustrumScopedCustody.watchRoster`. */
|
|
186
|
+
export interface ClaustrumRosterWatch {
|
|
187
|
+
/** How the host currently learns about changes. */
|
|
188
|
+
readonly mode: WatchMode;
|
|
189
|
+
/** Stop the watch and its timers. Safe to call more than once. */
|
|
190
|
+
close(): void;
|
|
106
191
|
}
|
|
107
192
|
/** Reads the provider identity a served token executes under, when the plugin can tell. */
|
|
108
193
|
export type IdentityParser = (accessToken: string) => string | undefined;
|
|
@@ -122,13 +207,15 @@ export declare function decideScopedRetryAfter401(site: string, served: Claustru
|
|
|
122
207
|
* so it cannot expire while a long request is still using it.
|
|
123
208
|
*/
|
|
124
209
|
export declare const SERVING_MARGIN_MS = 300000;
|
|
210
|
+
/** A kept receipt needs a minute of headroom, not the vault's refresh demand. */
|
|
211
|
+
export declare const KEPT_RECEIPT_MARGIN_MS = 60000;
|
|
125
212
|
/**
|
|
126
213
|
* Reads this consumer's vault credentials (list, fetch, 401 report), with the
|
|
127
|
-
* enrollment token as authorization. Used by every host of a plugin.
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
214
|
+
* enrollment token as authorization. Used by every host of a plugin. Every
|
|
215
|
+
* physical send asks the vault first while reachable. Only an unreachable vault permits
|
|
216
|
+
* reuse of the last validated receipt, in memory and bounded by its expiry.
|
|
217
|
+
* The enrollment token is re-read per operation so revocation and disconnect
|
|
218
|
+
* still take effect without a process restart.
|
|
132
219
|
*/
|
|
133
220
|
export declare class ClaustrumScopedCustody {
|
|
134
221
|
#private;
|
|
@@ -148,7 +235,13 @@ export declare class ClaustrumScopedCustody {
|
|
|
148
235
|
requireAssertion?: boolean;
|
|
149
236
|
now?: () => number;
|
|
150
237
|
logger?: ClaustrumLogger;
|
|
238
|
+
setTimeoutImpl?: typeof setTimeout;
|
|
239
|
+
clearTimeoutImpl?: typeof clearTimeout;
|
|
151
240
|
});
|
|
241
|
+
/** A watch losing its transport is the same outage as an unreachable read. */
|
|
242
|
+
transportLost(error?: unknown): void;
|
|
243
|
+
/** Any vault answer ends transport fallback, even when it refuses access. */
|
|
244
|
+
reachable(): void;
|
|
152
245
|
/**
|
|
153
246
|
* List this consumer's credentials. A record that cannot be used is skipped
|
|
154
247
|
* and warned about rather than failing the whole list, so one bad record
|
|
@@ -157,15 +250,23 @@ export declare class ClaustrumScopedCustody {
|
|
|
157
250
|
*/
|
|
158
251
|
discover(signal?: AbortSignal): Promise<VaultInventory>;
|
|
159
252
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* refuse; the receipt records what the vault asserted separately from what
|
|
165
|
-
* the roster expected. With it, a reply that does not itself name the
|
|
166
|
-
* credential id and the expected account is refused.
|
|
253
|
+
* Apply a failed account listing: any answer from the vault (a refusal such
|
|
254
|
+
* as a revoked enrollment's `not_found`) drops every kept receipt, while an
|
|
255
|
+
* unreachable vault keeps them. A vault watch that ends with the same
|
|
256
|
+
* refusal calls this too, so both paths treat it alike.
|
|
167
257
|
*/
|
|
168
|
-
|
|
258
|
+
listFailed(error: unknown): void;
|
|
259
|
+
/** Discard bearer material when a route is removed, declined or rebound. */
|
|
260
|
+
forget(credentialId: string): void;
|
|
261
|
+
/** Apply an already-held roster; this never fetches credentials or writes it. */
|
|
262
|
+
retain(identities: readonly (ClaustrumScopedIdentity & {
|
|
263
|
+
enabled: boolean;
|
|
264
|
+
state: string;
|
|
265
|
+
})[]): void;
|
|
266
|
+
/** A frozen, non-secret summary for a plugin's status display. */
|
|
267
|
+
keptReceiptState(): KeptReceiptState;
|
|
268
|
+
/** Ask the vault while reachable; retain one validated receipt in memory only. */
|
|
269
|
+
authorize(identity: ClaustrumScopedIdentity, signal?: AbortSignal, routeId?: string): Promise<ClaustrumScopedAttempt>;
|
|
169
270
|
/**
|
|
170
271
|
* Report that a send this consumer actually made was rejected. Only a 401
|
|
171
272
|
* is reported (the vault refuses 429, 402 and 5xx reports), addressed by
|
|
@@ -174,5 +275,25 @@ export declare class ClaustrumScopedCustody {
|
|
|
174
275
|
* custody did not issue is refused rather than reported.
|
|
175
276
|
*/
|
|
176
277
|
reportFailure(attempt: ClaustrumScopedAttempt, status: number, reporterSource: ClaustrumReporterSource): Promise<void>;
|
|
278
|
+
/**
|
|
279
|
+
* Watch this consumer's visible vault accounts instead of listing them on a
|
|
280
|
+
* short timer. Opens one `watchScoped` on this custody's client and calls
|
|
281
|
+
* `onView` when the host must run its discovery (see
|
|
282
|
+
* `ClaustrumRosterWatchOptions`). A claustrum-client without `watchScoped`
|
|
283
|
+
* (before 0.12.0) or a vault that answers `unknown_method` reports
|
|
284
|
+
* `fallback` and opens nothing. A watch that closes before any notice was
|
|
285
|
+
* never accepted: the client reads once to learn why, and the watch reports
|
|
286
|
+
* `fallback` and tries one new watch after `WATCH_RETRY_MS`, never in a
|
|
287
|
+
* loop. A watch that closes after notices is re-opened after
|
|
288
|
+
* `RESUBSCRIBE_BACKOFF_MS`, doubling per close in a row. A lost transport
|
|
289
|
+
* starts this custody's outage, exactly as an unreachable call does, and
|
|
290
|
+
* the first notice of a watch ends it. Timers are unreferenced; `close()`
|
|
291
|
+
* on the returned watch or on this custody stops it.
|
|
292
|
+
*
|
|
293
|
+
* The watch keeps no history, so the host still lists on a slow timer while
|
|
294
|
+
* the mode is `watching`: every `SAFETY_POLL_MS` (five minutes). In any
|
|
295
|
+
* other mode the host keeps its usual list timer.
|
|
296
|
+
*/
|
|
297
|
+
watchRoster(options: ClaustrumRosterWatchOptions): ClaustrumRosterWatch;
|
|
177
298
|
close(): void;
|
|
178
299
|
}
|
|
@@ -11,6 +11,7 @@ export declare class ClaustrumConsumerError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
/** The structural logger every class here accepts; `/logger`'s `createLogger` satisfies it. */
|
|
13
13
|
export interface ClaustrumLogger {
|
|
14
|
+
info?(message: string, data?: unknown): void;
|
|
14
15
|
warn(message: string, data?: unknown): void;
|
|
15
16
|
debug(message: string, data?: unknown): void;
|
|
16
17
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { type ClaustrumClientOptions } from '@cortexkit/claustrum-client';
|
|
2
2
|
import type { ClaustrumScopedClient } from './custody.js';
|
|
3
3
|
export { ClaustrumCredentialError, type ClaustrumReporterSource, type EnrollmentTokenFile, } from '@cortexkit/claustrum-client';
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
4
|
+
export { ClaustrumUnreachableError, classifyVaultFailure, VAULT_FAILURE_CLASSES, type VaultFailureClass, } from './availability.js';
|
|
5
|
+
export { ClaustrumConsumer, type ClaustrumConsumerOptions, type ClaustrumWatchState, type SendOptions, } from './consumer.js';
|
|
6
|
+
export { type AccountIdentitySource, type ClaustrumFamily, type ClaustrumRosterWatch, type ClaustrumRosterWatchOptions, type ClaustrumScopedAttempt, type ClaustrumScopedClient, ClaustrumScopedCustody, type ClaustrumScopedIdentity, decideScopedRetryAfter401, type IdentityParser, isScopedCredentialRotation, KEPT_RECEIPT_MARGIN_MS, type KeptReceiptState, type ScopedRetryReason, SERVING_MARGIN_MS, type SkippedVaultReason, type SkippedVaultRecord, type VaultCredential, type VaultCredentialType, type VaultGrantReach, type VaultGrantTuple, type VaultInventory, } from './custody.js';
|
|
6
7
|
export { type ClaustrumEnrollmentClient, type ClaustrumEnrollmentConnection, type ClaustrumEnrollmentDisconnectResult, ClaustrumEnrollmentManager, type ClaustrumEnrollmentPaths, type ClaustrumEnrollmentResetResult, type ClaustrumEnrollmentStatus, classifyEnrollmentError, connectClaustrumEnrollmentClient, disconnectClaustrumEnrollment, type EnrollmentDisposition, enrollmentAuthority, enrollmentName, getClaustrumEnrollmentPaths, hostEnrollmentPaths, RETRYABLE_ENROLLMENT_CODES, readClaustrumEnrollmentStatus, readClaustrumEnrollmentToken, resetClaustrumEnrollmentState, TERMINAL_ENROLLMENT_CODES, } from './enrollment.js';
|
|
7
8
|
export { ClaustrumConsumerError, type ClaustrumConsumerFailureKind, type ClaustrumLogger, } from './errors.js';
|
|
8
9
|
export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHOLDER_PREFIX, classifyHostSlot, custodyPlaceholder, custodyPlaceholderKey, type HostSlotContent, isCustodyPlaceholder, isCustodyPlaceholderValue, } from './host-slot.js';
|
|
9
|
-
export type
|
|
10
|
+
export { acquireInsulaQuotaSource, type InsulaQuotaContext, type InsulaQuotaOptions, type InsulaQuotaSource, type InsulaUsageEntry, type InsulaUsageTransport, type InsulaUsageWatch, type InsulaUsageWindow, type InsulaWatchOptions, } from './insula.js';
|
|
10
11
|
export { acceptAccount, type DeclinedAccount, declineAccount, isDeclined, } from './interlock.js';
|
|
11
12
|
export { type AccountMapper, acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, type MutateVaultRosterOptions, mutateVaultRoster, type ProjectionOptions, projectVaultRoster, type QuotaReceipt, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, type VaultPrimary, type VaultPrimaryBinding, type VaultPrimaryUnavailableReason, type VaultRosterFile, type VaultRosterLockContext, type VaultRosterRow, vaultRoutingRows, } from './roster.js';
|
|
13
|
+
export { type ChangeNotice, ChangeNoticeReader, RESUBSCRIBE_BACKOFF_MS, SAFETY_POLL_MS, ScopedRosterWatch, type ScopedRosterWatchOptions, type ScopedWatchEnd, type ScopedWatchHandle, type ScopedWatchNoticeShape, type ScopedWatchSource, WATCH_RETRY_MS, type WatchMode, } from './watch.js';
|
|
12
14
|
/**
|
|
13
15
|
* Connect the client that lists and fetches this consumer's vault credentials
|
|
14
16
|
* on the request path. `connectionFile` is required: this library
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClaustrumClientOptions } from '@cortexkit/claustrum-client';
|
|
2
|
+
import { type WatchMode } from './watch.js';
|
|
2
3
|
/** Insula's window fields are open: plugins choose which limits they understand. */
|
|
3
4
|
export interface InsulaUsageWindow {
|
|
4
5
|
usedPercent: number;
|
|
@@ -43,21 +44,68 @@ export interface InsulaQuotaOptions<Q> {
|
|
|
43
44
|
*/
|
|
44
45
|
normalize: (entry: InsulaUsageEntry, context: InsulaQuotaContext) => Q | undefined;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* A held-open `usage.watch` stream. `closed` resolves when insula ends the
|
|
49
|
+
* stream (STREAM_END at shutdown, or a reply that ends it) or after `stop()`,
|
|
50
|
+
* and rejects with insula's refusal (an error with a `code`, such as
|
|
51
|
+
* `unknown_method` from an insula that predates the method, or
|
|
52
|
+
* `usage_watch_limit` when 64 watches are already open) or with the
|
|
53
|
+
* transport failure.
|
|
54
|
+
*/
|
|
55
|
+
export interface InsulaUsageWatch {
|
|
56
|
+
stop(): void;
|
|
57
|
+
readonly closed: Promise<void>;
|
|
58
|
+
}
|
|
46
59
|
/** Optional transport for custom scoped clients; getUsage must not retry refusals. */
|
|
47
60
|
export interface InsulaUsageTransport {
|
|
48
61
|
connectionFile: string;
|
|
49
62
|
getUsage(): Promise<unknown>;
|
|
63
|
+
/**
|
|
64
|
+
* Open a `usage.watch` stream, passing each notice's raw JSON body to
|
|
65
|
+
* `onNotice`. Optional: a transport without it is polled as before.
|
|
66
|
+
*/
|
|
67
|
+
watchUsage?(onNotice: (body: Uint8Array) => void): InsulaUsageWatch;
|
|
50
68
|
}
|
|
51
69
|
/** Constructing this transport opens no connection; getUsage opens one per call. */
|
|
52
70
|
export declare function insulaUsageTransport(options: ClaustrumClientOptions & {
|
|
53
71
|
connectionFile: string;
|
|
54
72
|
}): InsulaUsageTransport;
|
|
73
|
+
export interface InsulaWatchOptions {
|
|
74
|
+
/**
|
|
75
|
+
* The provider this watcher reads. A notice that names only other
|
|
76
|
+
* providers then needs no read; unset means every notice does.
|
|
77
|
+
*/
|
|
78
|
+
provider?: string;
|
|
79
|
+
/**
|
|
80
|
+
* setTimeout and clearTimeout for the waits before re-subscribing or retrying
|
|
81
|
+
* a watch. Defaults to the global timers; tests inject virtual ones.
|
|
82
|
+
*/
|
|
83
|
+
setTimeoutImpl?: typeof setTimeout;
|
|
84
|
+
clearTimeoutImpl?: typeof clearTimeout;
|
|
85
|
+
}
|
|
55
86
|
export declare class InsulaQuotaSource {
|
|
56
87
|
#private;
|
|
57
88
|
private readonly transport;
|
|
58
89
|
private readonly key;
|
|
59
90
|
constructor(transport: InsulaUsageTransport, key: string);
|
|
91
|
+
/**
|
|
92
|
+
* The background poll: at most one usage.get per minute per daemon and
|
|
93
|
+
* enrollment token. While a watch is open it reads nothing, because insula's
|
|
94
|
+
* notices announce every new reading. If insula stops announcing an account,
|
|
95
|
+
* that account's last entry grows older than the freshness limit `current()`
|
|
96
|
+
* applies, `current()` returns nothing for it, and the plugin falls back to
|
|
97
|
+
* its own provider poll for that account.
|
|
98
|
+
*/
|
|
60
99
|
refresh(now: number): Promise<void>;
|
|
100
|
+
/** How this source currently learns about usage changes, for logs. */
|
|
101
|
+
watchMode(): WatchMode;
|
|
102
|
+
/**
|
|
103
|
+
* Hold a `usage.watch` open while any watcher wants it, and re-read
|
|
104
|
+
* usage.get when a notice says something changed. Watchers of one source
|
|
105
|
+
* share one watch, since insula admits only 64 per process. Returns the
|
|
106
|
+
* release; the last release closes the watch.
|
|
107
|
+
*/
|
|
108
|
+
watch(options?: InsulaWatchOptions): () => void;
|
|
61
109
|
current(provider: string, accountId: string, now: number): {
|
|
62
110
|
entry: InsulaUsageEntry;
|
|
63
111
|
context: InsulaQuotaContext;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { ClaustrumLogger } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* How a host currently learns about changes, for logs and status displays.
|
|
4
|
+
* `watching`: a watch was accepted and is open, so polling is reduced to a
|
|
5
|
+
* slow safety read. `polling`: no watch is open yet (not started, opening, or
|
|
6
|
+
* re-subscribing after a close), so the host polls at its usual interval.
|
|
7
|
+
* `fallback`: the watch is unavailable (an older module or client, a refusal,
|
|
8
|
+
* the watch limit, or repeated failures), so the host polls at its usual
|
|
9
|
+
* interval and may try a watch again later. Never carries secrets.
|
|
10
|
+
*/
|
|
11
|
+
export type WatchMode = 'watching' | 'polling' | 'fallback';
|
|
12
|
+
/**
|
|
13
|
+
* While a vault watch is healthy, the roster is still read this often: the
|
|
14
|
+
* watch carries no history, so a lost notice or a silently dead stream is
|
|
15
|
+
* caught within this period.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SAFETY_POLL_MS: number;
|
|
18
|
+
/**
|
|
19
|
+
* After a watch fails without being accepted, the host polls and tries one
|
|
20
|
+
* new watch this much later, so a refusing or draining module is never asked
|
|
21
|
+
* in a tight loop.
|
|
22
|
+
*/
|
|
23
|
+
export declare const WATCH_RETRY_MS: number;
|
|
24
|
+
/**
|
|
25
|
+
* First wait before re-subscribing a watch that closed after it had been
|
|
26
|
+
* accepted. It doubles for each further close in a row, up to WATCH_RETRY_MS.
|
|
27
|
+
*/
|
|
28
|
+
export declare const RESUBSCRIBE_BACKOFF_MS = 1000;
|
|
29
|
+
/**
|
|
30
|
+
* The fields every change notice carries, from the vault's watchScoped and
|
|
31
|
+
* insula's usage.watch alike.
|
|
32
|
+
*/
|
|
33
|
+
export interface ChangeNotice {
|
|
34
|
+
readonly kind: string;
|
|
35
|
+
readonly epoch: string;
|
|
36
|
+
readonly seq: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Tells whether a notice follows the previous one with nothing lost between
|
|
40
|
+
* them. `seq` restarts at 1 for every watch and `epoch` names the sending
|
|
41
|
+
* module's process lifetime. A sequence gap (notices are sheddable), a
|
|
42
|
+
* different epoch (the module restarted) or a notice of an unknown kind all
|
|
43
|
+
* mean changes may have been missed, so the caller must re-read everything.
|
|
44
|
+
*/
|
|
45
|
+
export declare class ChangeNoticeReader {
|
|
46
|
+
#private;
|
|
47
|
+
/**
|
|
48
|
+
* Begin a new watch, whose first notice is seq 1 again. The epoch is kept,
|
|
49
|
+
* so a module restart between two watches still reads as a break.
|
|
50
|
+
*/
|
|
51
|
+
restart(): void;
|
|
52
|
+
continuous(notice: ChangeNotice): boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Runs `read` with at most one call in flight and at most one more queued
|
|
56
|
+
* behind it, however many requests arrive meanwhile. A request made while a
|
|
57
|
+
* read is running may have arrived after that read took its snapshot, so it
|
|
58
|
+
* earns exactly one more read; any further requests share that one.
|
|
59
|
+
*/
|
|
60
|
+
export declare class CoalescedRead {
|
|
61
|
+
#private;
|
|
62
|
+
constructor(read: () => Promise<void>, onError: (error: unknown) => void);
|
|
63
|
+
/** The read in flight (including its queued follow-up), if any. */
|
|
64
|
+
get running(): Promise<void> | undefined;
|
|
65
|
+
request(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
/** A vault watch notice: `view` is the same digest `list_scoped` returns. */
|
|
68
|
+
export interface ScopedWatchNoticeShape extends ChangeNotice {
|
|
69
|
+
readonly view: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* How a vault watch ended, as claustrum-client 0.12.0 reports it. `refused`
|
|
73
|
+
* and `not_accepting` both mean the watch closed before its first notice; the
|
|
74
|
+
* client then made one `list_scoped` read to learn why, and `refused` carries
|
|
75
|
+
* that read's refusal.
|
|
76
|
+
*/
|
|
77
|
+
export type ScopedWatchEnd = {
|
|
78
|
+
readonly kind: 'refused';
|
|
79
|
+
readonly error: unknown;
|
|
80
|
+
} | {
|
|
81
|
+
readonly kind: 'transport_lost';
|
|
82
|
+
readonly error: unknown;
|
|
83
|
+
} | {
|
|
84
|
+
readonly kind: 'ended' | 'not_accepting' | 'unsupported' | 'stopped';
|
|
85
|
+
};
|
|
86
|
+
export interface ScopedWatchHandle {
|
|
87
|
+
stop(): void;
|
|
88
|
+
readonly closed: Promise<ScopedWatchEnd>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The part of a vault client that can watch. claustrum-client added
|
|
92
|
+
* `watchScoped` in 0.12.0; the peer range still admits 0.6.2, so it is
|
|
93
|
+
* optional and detected at run time.
|
|
94
|
+
*/
|
|
95
|
+
export interface ScopedWatchSource {
|
|
96
|
+
watchScoped?(onNotice: (notice: ScopedWatchNoticeShape) => void, options?: {
|
|
97
|
+
enrollmentToken?: string;
|
|
98
|
+
}): ScopedWatchHandle;
|
|
99
|
+
}
|
|
100
|
+
export interface ScopedRosterWatchOptions {
|
|
101
|
+
client: ScopedWatchSource;
|
|
102
|
+
/** The enrollment token, read again for every watch so a new one is used. */
|
|
103
|
+
enrollmentToken: () => Promise<string>;
|
|
104
|
+
/** The view of the roster this host last discovered; undefined for none. */
|
|
105
|
+
view: () => string | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* The host's existing discovery: `list_scoped`, the roster commit and
|
|
108
|
+
* everything that follows it. The watch never reads the vault list itself.
|
|
109
|
+
* `view` is the view the newest notice carried.
|
|
110
|
+
*/
|
|
111
|
+
discover: (view: string) => Promise<unknown>;
|
|
112
|
+
/**
|
|
113
|
+
* The vault refused the watch, and the confirming `list_scoped` read it
|
|
114
|
+
* with this error (a revoked enrollment ends both with `not_found`). Handle
|
|
115
|
+
* it as discovery handles the same refusal from `list_scoped`.
|
|
116
|
+
*/
|
|
117
|
+
onRefused: (error: unknown) => void;
|
|
118
|
+
onTransportLost?: () => void;
|
|
119
|
+
onReachable?: () => void;
|
|
120
|
+
onError?: (error: unknown) => void;
|
|
121
|
+
onMode?: (mode: WatchMode) => void;
|
|
122
|
+
logger?: ClaustrumLogger;
|
|
123
|
+
now?: () => number;
|
|
124
|
+
setTimeoutImpl?: typeof setTimeout;
|
|
125
|
+
clearTimeoutImpl?: typeof clearTimeout;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Keeps one vault `watchScoped` open for a host and runs the host's discovery
|
|
129
|
+
* when a notice shows the visible account list changed. Discovery runs only
|
|
130
|
+
* when the notice's view differs from the last discovered roster's view, when
|
|
131
|
+
* a notice shows a gap or a new epoch, or on the first notice of a watch that
|
|
132
|
+
* was re-subscribed after a close (the time between two watches was not
|
|
133
|
+
* observed). It never opens a second watch while one is open.
|
|
134
|
+
*/
|
|
135
|
+
export declare class ScopedRosterWatch {
|
|
136
|
+
#private;
|
|
137
|
+
constructor(options: ScopedRosterWatchOptions);
|
|
138
|
+
get mode(): WatchMode;
|
|
139
|
+
start(): void;
|
|
140
|
+
stop(): void;
|
|
141
|
+
}
|