@cortexkit/opencode-openai-auth 1.1.0 → 1.3.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.
Files changed (66) hide show
  1. package/dist/bin.js +8 -8
  2. package/dist/{codex-request-BXDXToyt.d.ts → codex-request-B9D0e97Q.d.ts} +177 -28
  3. package/dist/{index-7c6sxtd6.js → index-1mbgsxsr.js} +2 -2
  4. package/dist/{index-4c8yw54k.js → index-2pgkrebq.js} +2 -2
  5. package/dist/index-4v2fs42x.js +65 -0
  6. package/dist/index-8x1aj7q3.js +2 -0
  7. package/dist/{index-f2fmb596.js → index-bc89akak.js} +1 -1
  8. package/dist/index-ex6nx3mq.js +2 -0
  9. package/dist/{index-zpm95mt5.js → index-ftkhhr6d.js} +1 -1
  10. package/dist/{index-msx2m78k.js → index-gn2b097f.js} +2 -2
  11. package/dist/{index-j8v2xwq6.js → index-m3ctrabv.js} +1 -1
  12. package/dist/index-psjyexj3.js +69 -0
  13. package/dist/{index-k6cydkgt.js → index-tddr876f.js} +2 -2
  14. package/dist/index-tn8x5tz8.js +90 -0
  15. package/dist/{index-csky2xpg.js → index-xwvze96g.js} +1 -1
  16. package/dist/index.d.ts +5 -3
  17. package/dist/index.js +1 -1
  18. package/dist/{model-YRg7Uowo.d.ts → model-BdITTmeb.d.ts} +1 -1
  19. package/dist/{port-file-CxEpgSHY.d.ts → port-file-DbHIVC9a.d.ts} +1 -1
  20. package/dist/{protocol-CDV1Pc6h.d.ts → protocol-MysYPAho.d.ts} +1 -1
  21. package/dist/rpc/port-file.d.ts +1 -1
  22. package/dist/rpc/port-file.js +1 -1
  23. package/dist/rpc/protocol.d.ts +1 -1
  24. package/dist/rpc/protocol.js +1 -1
  25. package/dist/rpc/rpc-client.d.ts +3 -3
  26. package/dist/rpc/rpc-client.js +1 -1
  27. package/dist/rpc/rpc-dir.js +1 -1
  28. package/dist/{sidebar-state-DjTWUbmf.d.ts → sidebar-state-CVSok7zv.d.ts} +114 -5
  29. package/dist/sidebar-state.d.ts +2 -2
  30. package/dist/sidebar-state.js +1 -1
  31. package/dist/token-util-cf0nyh7n.js +1 -0
  32. package/dist/{token-rz4t3ay6.js → token-z6qq7a1d.js} +1 -1
  33. package/dist/tui-preferences.js +1 -1
  34. package/dist/tui.d.ts +2 -2
  35. package/dist/v2/server.d.ts +109 -2
  36. package/dist/v2/server.js +3 -3
  37. package/package.json +4 -4
  38. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +203 -18
  39. package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +242 -16
  40. package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +168 -6
  41. package/src/tui-compiled/raw/shared/375d9cca2def037d-private-files.js +10 -2
  42. package/src/tui-compiled/raw/shared/94f53cfd572ba4bb-availability.js +106 -0
  43. package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +286 -21
  44. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +1 -1
  45. package/src/tui-compiled/raw/shared/dc9290c2a7552ba6-watch.js +253 -0
  46. package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +4 -1
  47. package/src/tui-compiled/raw/sidebar-state.js +63 -1
  48. package/src/tui-compiled/raw/tui.tsx +82 -30
  49. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +203 -18
  50. package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +242 -16
  51. package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +168 -6
  52. package/src/tui-compiled/runtime/shared/375d9cca2def037d-private-files.js +10 -2
  53. package/src/tui-compiled/runtime/shared/94f53cfd572ba4bb-availability.js +106 -0
  54. package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +286 -21
  55. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +1 -1
  56. package/src/tui-compiled/runtime/shared/dc9290c2a7552ba6-watch.js +253 -0
  57. package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +4 -1
  58. package/src/tui-compiled/runtime/sidebar-state.js +63 -1
  59. package/src/tui-compiled/runtime/tui.js +245 -208
  60. package/dist/index-0yh85bwb.js +0 -69
  61. package/dist/index-7eb3dw37.js +0 -90
  62. package/dist/index-8re8rprk.js +0 -2
  63. package/dist/index-edbdscy7.js +0 -65
  64. package/dist/index-m5qmvr6c.js +0 -2
  65. package/dist/map-Buq-GW7l.d.ts +0 -56
  66. package/dist/token-util-tpy2c7h6.js +0 -1
@@ -5,6 +5,7 @@ import { readClaustrumEnrollmentToken } from "./b9588ad4725c1d10-enrollment.js";
5
5
  import { ClaustrumConsumerError } from "./ca0fccaba1d87af4-errors.js";
6
6
  import { acquireInsulaQuotaSource } from "./d0f0b6be200311af-insula.js";
7
7
  import { acceptVaultRoute, declineVaultRoute, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
8
+ import { SAFETY_POLL_MS, ScopedRosterWatch } from "./dc9290c2a7552ba6-watch.js";
8
9
  /**
9
10
  * One plugin host's view of its vault accounts. It turns vault credentials
10
11
  * into rows for `/routing`, authorizes each physical send with the vault, and
@@ -18,12 +19,23 @@ export class ClaustrumConsumer {
18
19
  #shutdown = new AbortController();
19
20
  #logger;
20
21
  #custody;
22
+ /** The custody's client, kept so `start()` can open its vault watch. */
23
+ #client;
21
24
  #connecting;
22
25
  #refreshing;
23
26
  #roster;
27
+ /**
28
+ * The grants named by this instance's latest discovery, with the view that
29
+ * discovery committed. Memory only: the roster file never carries it.
30
+ */
31
+ #grantReach;
24
32
  #timer;
25
33
  #started = false;
26
34
  #insula;
35
+ #vaultWatch;
36
+ #releaseInsulaWatch;
37
+ /** When this instance last completed a discovery, by `now`. */
38
+ #discoveredAt;
27
39
  constructor(options) {
28
40
  this.#options = options;
29
41
  this.#logger = options.logger ?? createLogger('claustrum');
@@ -78,6 +90,7 @@ export class ClaustrumConsumer {
78
90
  client.close();
79
91
  this.#assertOpen();
80
92
  }
93
+ this.#client = client;
81
94
  this.#custody = new ClaustrumScopedCustody({
82
95
  client,
83
96
  family: this.#options.family,
@@ -86,6 +99,8 @@ export class ClaustrumConsumer {
86
99
  requireAssertion: this.#options.requireAssertion,
87
100
  now: this.#options.now,
88
101
  logger: this.#logger,
102
+ setTimeoutImpl: this.#options.setTimeoutImpl,
103
+ clearTimeoutImpl: this.#options.clearTimeoutImpl,
89
104
  });
90
105
  if (this.#options.insula && client.insula) {
91
106
  this.#insula = acquireInsulaQuotaSource(client.insula, this.#options.tokenPath);
@@ -102,6 +117,43 @@ export class ClaustrumConsumer {
102
117
  snapshot() {
103
118
  return this.#roster;
104
119
  }
120
+ /**
121
+ * What this consumer is granted, from its latest discovery, without any I/O.
122
+ * It explains an empty roster: `grants === 0` means no grant at all, while
123
+ * `grants > 0` means a grant that reaches no credential (`tuples` names its
124
+ * selector). Undefined means unknown, never zero: the vault or client is too
125
+ * old to send it, nothing was discovered yet, or the roster now in memory is
126
+ * a different view than the one this instance discovered (a peer process
127
+ * held the discovery lease and its newer roster was served instead).
128
+ */
129
+ grantReach() {
130
+ const latest = this.#grantReach;
131
+ return latest && this.#roster?.view === latest.view
132
+ ? latest.reach
133
+ : undefined;
134
+ }
135
+ /** How this consumer currently learns about changes, without I/O. */
136
+ watchState() {
137
+ return Object.freeze({
138
+ vault: this.#vaultWatch?.mode ?? 'polling',
139
+ ...(this.#options.insula && {
140
+ insula: this.#insula?.source.watchMode() ?? 'polling',
141
+ }),
142
+ });
143
+ }
144
+ /** Close the watches with the connection they belong to. */
145
+ #stopWatch() {
146
+ this.#vaultWatch?.stop();
147
+ this.#vaultWatch = undefined;
148
+ this.#client = undefined;
149
+ this.#releaseInsulaWatch?.();
150
+ this.#releaseInsulaWatch = undefined;
151
+ }
152
+ /** Whether any account is using an in-memory outage receipt, without I/O. */
153
+ keptReceiptState() {
154
+ return (this.#custody?.keptReceiptState() ??
155
+ Object.freeze({ active: false, since: null }));
156
+ }
105
157
  /** Rows for `/routing`, from the last committed roster. */
106
158
  routingRows() {
107
159
  return vaultRoutingRows(this.#roster);
@@ -113,6 +165,8 @@ export class ClaustrumConsumer {
113
165
  this.#refreshing = (async () => {
114
166
  if (!(await this.#active())) {
115
167
  this.#roster = undefined;
168
+ this.#grantReach = undefined;
169
+ this.#stopWatch();
116
170
  this.#custody?.close();
117
171
  this.#custody = undefined;
118
172
  this.#insula?.release();
@@ -120,9 +174,17 @@ export class ClaustrumConsumer {
120
174
  return undefined;
121
175
  }
122
176
  const custody = await this.#getCustody();
177
+ // Capture the inventory so its grant fields can be kept beside the roster;
178
+ // the roster file itself never stores them.
179
+ let discovered;
123
180
  const roster = await refreshVaultRoster({
124
181
  path: this.#options.rosterPath,
125
- custody,
182
+ custody: {
183
+ discover: async (signal) => {
184
+ discovered = await custody.discover(signal);
185
+ return discovered;
186
+ },
187
+ },
126
188
  isActive: () => this.#active(),
127
189
  signal: this.#shutdown.signal,
128
190
  projection: () => ({
@@ -135,16 +197,35 @@ export class ClaustrumConsumer {
135
197
  this.#assertOpen();
136
198
  const changed = roster?.view !== this.#roster?.view;
137
199
  this.#roster = roster;
200
+ custody.retain(roster?.rows ?? []);
201
+ // Each discovery replaces the previous value, including with unknown. A
202
+ // refresh that served a peer's roster discovered nothing and leaves it;
203
+ // grantReach() hides it once the roster's view no longer matches.
204
+ if (discovered && roster?.view !== undefined)
205
+ this.#grantReach = discovered.grantReach
206
+ ? { view: roster.view, reach: discovered.grantReach }
207
+ : undefined;
138
208
  // Notify on view changes only: subscribers rewrite shared state on each
139
209
  // notification, and the poll runs every few seconds.
140
210
  if (roster && changed)
141
211
  this.#options.onRoster?.(roster);
212
+ this.#discoveredAt = (this.#options.now ?? Date.now)();
142
213
  return roster;
143
214
  })().finally(() => {
144
215
  this.#refreshing = undefined;
145
216
  });
146
217
  return this.#refreshing;
147
218
  }
219
+ /**
220
+ * Poll in the background and open the vault and insula watches. Until the
221
+ * vault watch is accepted, and whenever it is unavailable, every tick lists
222
+ * the vault exactly as before watches existed. While it is open a tick
223
+ * lists only when five minutes have passed since the last discovery, since
224
+ * the watch keeps no history; it still checks `isCustodyActive`, so a switch
225
+ * to local mode closes the connection as quickly as before. Insula is
226
+ * polled on the tick only while its own watch is not open. A host not in
227
+ * vault mode makes no request.
228
+ */
148
229
  start() {
149
230
  if (this.#started)
150
231
  return;
@@ -152,8 +233,10 @@ export class ClaustrumConsumer {
152
233
  this.#started = true;
153
234
  const tick = async () => {
154
235
  try {
155
- await this.refresh();
236
+ if (await this.#listDue())
237
+ await this.refresh();
156
238
  await this.refreshInsulaQuota();
239
+ this.#openWatch();
157
240
  }
158
241
  catch (error) {
159
242
  if (!this.#shutdown.signal.aborted)
@@ -172,6 +255,65 @@ export class ClaustrumConsumer {
172
255
  };
173
256
  void tick();
174
257
  }
258
+ /** Whether a background tick must list the vault now. */
259
+ async #listDue() {
260
+ if (this.#vaultWatch?.mode !== 'watching')
261
+ return true;
262
+ const now = (this.#options.now ?? Date.now)();
263
+ if (this.#discoveredAt === undefined ||
264
+ now - this.#discoveredAt >= SAFETY_POLL_MS)
265
+ return true;
266
+ // refresh() is what closes the connection when the host left vault mode.
267
+ return !(await this.#active());
268
+ }
269
+ /**
270
+ * Open the vault and insula watches once a vault connection exists. Only
271
+ * reached in vault mode: a host in local mode never connects.
272
+ */
273
+ #openWatch() {
274
+ const client = this.#client;
275
+ if (this.#shutdown.signal.aborted || !client || !this.#custody)
276
+ return;
277
+ if (!this.#vaultWatch) {
278
+ const watch = new ScopedRosterWatch({
279
+ client,
280
+ enrollmentToken: async () => (await readClaustrumEnrollmentToken(this.#options.tokenPath)).token,
281
+ view: () => this.#roster?.view,
282
+ // The existing discovery, so a watched change commits the roster and
283
+ // re-applies it to kept receipts exactly as a poll does. A refresh
284
+ // already in flight may have listed before the change was announced,
285
+ // so wait for it and list again.
286
+ discover: async () => {
287
+ while (this.#refreshing)
288
+ await this.#refreshing.catch(() => { });
289
+ await this.refresh();
290
+ },
291
+ onRefused: (error) => {
292
+ // As a poll that met the same refusal from list_scoped would.
293
+ this.#custody?.listFailed(error);
294
+ this.#options.onError?.(error);
295
+ },
296
+ onTransportLost: () => this.#custody?.transportLost(),
297
+ onReachable: () => this.#custody?.reachable(),
298
+ onError: (error) => {
299
+ if (!this.#shutdown.signal.aborted)
300
+ this.#options.onError?.(error);
301
+ },
302
+ logger: this.#logger,
303
+ now: this.#options.now,
304
+ setTimeoutImpl: this.#options.setTimeoutImpl,
305
+ clearTimeoutImpl: this.#options.clearTimeoutImpl,
306
+ });
307
+ this.#vaultWatch = watch;
308
+ watch.start();
309
+ }
310
+ if (this.#options.insula && this.#insula && !this.#releaseInsulaWatch)
311
+ this.#releaseInsulaWatch = this.#insula.source.watch({
312
+ provider: this.#options.insula.provider,
313
+ setTimeoutImpl: this.#options.setTimeoutImpl,
314
+ clearTimeoutImpl: this.#options.clearTimeoutImpl,
315
+ });
316
+ }
175
317
  /**
176
318
  * Fetch on a background tick, at most once per minute across projects with
177
319
  * the same daemon and enrollment token path. Admission must use the cache
@@ -246,20 +388,33 @@ export class ClaustrumConsumer {
246
388
  async authorize(routeId, signal) {
247
389
  this.#assertOpen(signal);
248
390
  const roster = this.#roster ?? (await this.#wait(this.refresh(), signal));
249
- if (!roster || !(await this.#active()))
391
+ if (!roster || !(await this.#active())) {
392
+ this.#custody?.close();
393
+ this.#custody = undefined;
394
+ this.#stopWatch();
395
+ this.#insula?.release();
396
+ this.#insula = undefined;
250
397
  throw new ClaustrumConsumerError('not-active', 'Claustrum scoped custody is not active');
398
+ }
251
399
  const current = await readVaultRoster(this.#options.rosterPath);
252
400
  this.#assertOpen(signal);
253
401
  const observed = roster.rows.find((row) => row.routeId === routeId);
254
402
  const configured = current?.rows.find((row) => row.routeId === routeId);
255
- if (configured && !configured.enabled)
403
+ if (configured && !configured.enabled) {
404
+ if (observed)
405
+ this.#custody?.forget(observed.credentialId);
256
406
  throw new ClaustrumConsumerError('route-declined', 'Claustrum route is disabled');
407
+ }
257
408
  if (!observed ||
258
409
  !configured ||
259
410
  configured.state !== 'active' ||
260
411
  configured.credentialId !== observed.credentialId ||
261
- configured.accountIdentity !== observed.accountIdentity)
412
+ configured.accountIdentity !== observed.accountIdentity ||
413
+ configured.credentialType !== observed.credentialType) {
414
+ if (observed)
415
+ this.#custody?.forget(observed.credentialId);
262
416
  throw new ClaustrumConsumerError('route-unavailable', 'Claustrum route is disabled, removed or changed');
417
+ }
263
418
  const custody = await this.#wait(this.#getCustody(), signal);
264
419
  const combined = combineAbortSignals([
265
420
  this.#shutdown.signal,
@@ -272,7 +427,7 @@ export class ClaustrumConsumer {
272
427
  ...(configured.accountIdentity !== undefined && {
273
428
  accountIdentity: configured.accountIdentity,
274
429
  }),
275
- }, combined.signal);
430
+ }, combined.signal, routeId);
276
431
  }
277
432
  finally {
278
433
  combined.dispose();
@@ -296,6 +451,10 @@ export class ClaustrumConsumer {
296
451
  const signal = options.signal;
297
452
  let served = await this.authorize(routeId, signal);
298
453
  let response = await dispatch(served, signal);
454
+ if (response.status === 401 && served.fromKeptReceipt) {
455
+ await this.reportFailure(served, 401, options.reporterSource ?? 'direct');
456
+ return response;
457
+ }
299
458
  if (response.status === 401 && !signal?.aborted) {
300
459
  let current;
301
460
  try {
@@ -330,6 +489,7 @@ export class ClaustrumConsumer {
330
489
  this.#assertOpen();
331
490
  await declineVaultRoute(this.#options.rosterPath, routeId);
332
491
  this.#roster = await readVaultRoster(this.#options.rosterPath);
492
+ this.#custody?.retain(this.#roster?.rows ?? []);
333
493
  }
334
494
  async accept(routeId) {
335
495
  this.#assertOpen();
@@ -385,9 +545,11 @@ export class ClaustrumConsumer {
385
545
  if (this.#timer)
386
546
  (this.#options.clearTimeoutImpl ?? clearTimeout)(this.#timer);
387
547
  this.#timer = undefined;
548
+ this.#stopWatch();
388
549
  this.#custody?.close();
389
550
  this.#insula?.release();
390
551
  this.#insula = undefined;
391
552
  this.#roster = undefined;
553
+ this.#grantReach = undefined;
392
554
  }
393
555
  }
@@ -184,19 +184,25 @@ export async function replaceWindowsFile(from, to, options = {}) {
184
184
  to = resolve(to);
185
185
  const now = options.now ?? (() => performance.now());
186
186
  const start = now();
187
- const deadline = Math.min(start + 500, options.deadline ?? Infinity);
187
+ const leaseDeadline = options.deadline ?? Infinity;
188
+ let retryDeadline;
188
189
  const delays = [0, 10, 20, 40, 80, 100];
189
190
  let original;
190
191
  let attempts = 0;
191
192
  for (const delay of delays) {
193
+ const deadline = Math.min(retryDeadline ?? Infinity, leaseDeadline);
192
194
  if (now() >= deadline)
193
195
  break;
194
196
  if (delay)
195
197
  await (options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))))(Math.min(delay, Math.max(0, deadline - now())));
196
198
  if (now() >= deadline)
197
199
  break;
200
+ const fenceStart = now();
198
201
  await options.beforeRename?.();
199
- if (now() >= deadline)
202
+ // Ownership checks fence every attempt but do not spend the sharing-retry budget.
203
+ if (retryDeadline !== undefined)
204
+ retryDeadline += now() - fenceStart;
205
+ if (now() >= leaseDeadline)
200
206
  break;
201
207
  attempts++;
202
208
  try {
@@ -212,6 +218,8 @@ export async function replaceWindowsFile(from, to, options = {}) {
212
218
  !detail.reason;
213
219
  if (!candidate)
214
220
  break;
221
+ // Start the retry window only once a rename has actually failed.
222
+ retryDeadline ??= now() + 500;
215
223
  }
216
224
  }
217
225
  const error = new PrivateFileError(to, 'rename', original ??
@@ -0,0 +1,106 @@
1
+ import { ClaustrumCredentialError } from "@cortexkit/claustrum-client";
2
+ import { SocketClosedError, SocketTimeoutError, SubcCallError, } from "@cortexkit/subc-client";
3
+ import { ClaustrumConsumerError } from "./ca0fccaba1d87af4-errors.js";
4
+ /**
5
+ * Codes from claustrum-client's decoder and its subc transport. A retry action
6
+ * alone is not evidence of an outage: even transient vault refusals must win
7
+ * over a previously served credential. New codes fail closed until reviewed.
8
+ */
9
+ export const VAULT_FAILURE_CLASSES = Object.freeze({
10
+ ECONNREFUSED: 'unreachable',
11
+ ENOENT: 'unreachable',
12
+ ECONNRESET: 'unreachable',
13
+ EPIPE: 'unreachable',
14
+ ETIMEDOUT: 'unreachable',
15
+ transport_error: 'unreachable',
16
+ unavailable: 'unreachable',
17
+ timeout: 'unreachable',
18
+ route_closed: 'unreachable',
19
+ daemon_draining: 'unreachable',
20
+ connection_closed: 'unreachable',
21
+ connection_dropped: 'unreachable',
22
+ request_deadline: 'unreachable',
23
+ deadline_exceeded_no_drop_observed: 'unreachable',
24
+ module_reloading: 'unreachable',
25
+ module_warming: 'unreachable',
26
+ target_unavailable: 'unreachable',
27
+ module_timeout: 'unreachable',
28
+ invalid_response: 'answered',
29
+ invalid_record_version: 'answered',
30
+ invalid_expiry: 'answered',
31
+ invalid_status: 'answered',
32
+ invalid_params: 'answered',
33
+ ttl_unsatisfiable: 'answered',
34
+ not_found: 'answered',
35
+ enrollment_revoked: 'answered',
36
+ enrollment_invalid: 'answered',
37
+ grant_denied: 'answered',
38
+ credential_unavailable: 'answered',
39
+ needs_reauth: 'answered',
40
+ declined: 'answered',
41
+ disabled: 'answered',
42
+ identity_changed: 'answered',
43
+ closed: 'answered',
44
+ client_closed: 'answered',
45
+ pending_exists: 'answered',
46
+ pending_queue_full: 'answered',
47
+ already_consumed: 'answered',
48
+ superseded: 'answered',
49
+ stale_generation: 'answered',
50
+ missing_identity: 'answered',
51
+ invalid_control_body: 'answered',
52
+ unknown_module: 'answered',
53
+ module_removed: 'answered',
54
+ capability_denied: 'answered',
55
+ unknown_channel: 'unreachable',
56
+ stale_route_epoch: 'unreachable',
57
+ capability_forbidden: 'answered',
58
+ capability_unprovided: 'answered',
59
+ capability_ambiguous: 'answered',
60
+ invalid_capability_identifier: 'answered',
61
+ binary_body_required: 'answered',
62
+ binary_call_requires_call_binary: 'answered',
63
+ corr_exhausted: 'answered',
64
+ late_route_cleanup_failed: 'answered',
65
+ reverse_request_capability_not_declared: 'answered',
66
+ stale_route_handle: 'answered',
67
+ reverse_request_unhandled: 'answered',
68
+ handler_error: 'answered',
69
+ health_error: 'answered',
70
+ duplicate_module_id: 'answered',
71
+ route_rejected: 'answered',
72
+ lock_busy: 'answered',
73
+ owner_invalid: 'answered',
74
+ renewal_failed: 'answered',
75
+ unsupported_platform: 'answered',
76
+ EACCES: 'answered',
77
+ EPERM: 'answered',
78
+ ERR_PRIVATE_FILES_SECURITY: 'answered',
79
+ });
80
+ /**
81
+ * An injected adapter may throw this ONLY when its connection/transport is
82
+ * unreachable, including a failed connect before getScoped. Do not wrap an
83
+ * adapter shutdown, cancellation, validation error or vault refusal in it.
84
+ * Its fixed message cannot echo enrollment or credential material, and its
85
+ * kind stays `unavailable` for callers handling ClaustrumConsumerError.
86
+ */
87
+ export class ClaustrumUnreachableError extends ClaustrumConsumerError {
88
+ constructor() {
89
+ super('unavailable', 'Claustrum scoped operation unavailable');
90
+ this.name = 'ClaustrumUnreachableError';
91
+ }
92
+ }
93
+ export function classifyVaultFailure(error) {
94
+ if (error instanceof ClaustrumUnreachableError ||
95
+ error instanceof SocketClosedError ||
96
+ error instanceof SocketTimeoutError)
97
+ return 'unreachable';
98
+ const code = error instanceof ClaustrumCredentialError || error instanceof SubcCallError
99
+ ? error.code
100
+ : error instanceof Error && 'code' in error
101
+ ? error.code
102
+ : undefined;
103
+ if (typeof code !== 'string' || !Object.hasOwn(VAULT_FAILURE_CLASSES, code))
104
+ return 'answered';
105
+ return VAULT_FAILURE_CLASSES[code];
106
+ }