@indigoai-us/hq-cli 5.255.0 → 5.257.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.
@@ -21,6 +21,7 @@ import { registerOutpostPrunerCommand } from "./outpost-pruner.js";
21
21
  import { registerLaneChecksCommand } from "./lanes-checks.js";
22
22
  import { registerLaneReportCommands } from "./lanes-report.js";
23
23
  import { registerLaneTrackerImportCommand } from "./lanes-import.js";
24
+ import { registerLaneRepoMergeHoldCommand } from "./lanes-hold.js";
24
25
  import { ensureCognitoToken } from "../utils/cognito-session.js";
25
26
  import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
26
27
  import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
@@ -6892,6 +6893,12 @@ export function registerLanesCommand(parent, deps = {}) {
6892
6893
  return body;
6893
6894
  },
6894
6895
  }, wrap);
6896
+ registerLaneRepoMergeHoldCommand(lanes, {
6897
+ hqRoot: ctx.hqRoot,
6898
+ resolveCompany: ctx.resolveCompany,
6899
+ now: ctx.now,
6900
+ env: ctx.env,
6901
+ }, wrap);
6895
6902
  registerOutpostPrunerCommand(lanes);
6896
6903
  const noStoryOption = new Option("--no-story <reason>", "Create a lane without a story and record why");
6897
6904
  noStoryOption.attributeName = () => "noStory";
@@ -1421,7 +1421,9 @@ export async function makeInstallSecretResolver(secretNames, company) {
1421
1421
  return () => null;
1422
1422
  [scopeUid] = scopes;
1423
1423
  }
1424
- const authorized = await loadRevealedSecrets(token, scopeUid, names);
1424
+ const authorized = await loadRevealedSecrets(token, scopeUid, names, undefined, {
1425
+ allowOfflineFallback: false,
1426
+ });
1425
1427
  return (name) => authorized.get(name) ?? null;
1426
1428
  }
1427
1429
  catch {
@@ -2,9 +2,10 @@ import { spawn } from 'node:child_process';
2
2
  import * as path from 'node:path';
3
3
  import * as fs from 'node:fs';
4
4
  import { internal } from 'varlock';
5
- import { ensureCognitoToken } from '../utils/cognito-session.js';
5
+ import { cognitoCachePrincipalUid, ensureCognitoToken } from '../utils/cognito-session.js';
6
6
  import { apiKeyCapabilityGateMessage, peekHqApiKey, } from '../utils/resolve-vault-credential.js';
7
7
  import { computeSha256 } from '../utils/integrity.js';
8
+ import { isUnavailableSecretLoadHttpStatus, SecretLoadUnavailableError, } from '../utils/secret-load-availability.js';
8
9
  import { vaultApiFetch, getCompanyUid } from '../utils/vault-api.js';
9
10
  import { discoverSchemas } from '../run/discover-schemas.js';
10
11
  import { installHqPlugin, prewarmHqSecrets } from '../run/hq-plugin.js';
@@ -82,7 +83,9 @@ export function registerRunCommand(program) {
82
83
  throw new Error('company slug not set. Add # @hqCompany("slug") to your .env.schema or pass --company <slug>.');
83
84
  }
84
85
  const token = await ensureCognitoToken();
85
- const uid = await getCompanyUid(token, slug);
86
+ const uid = await getCompanyUid(token, slug, undefined, {
87
+ allowOfflineScopeFallback: true,
88
+ });
86
89
  const usage = await buildRunUsage(opts.script, opts.scriptId);
87
90
  const fetchBatch = async (companyUid, names, requestUsage) => {
88
91
  const res = await vaultApiFetch({
@@ -99,6 +102,9 @@ export function registerRunCommand(program) {
99
102
  : typeof body.error === 'string'
100
103
  ? body.error
101
104
  : res.statusText;
105
+ if (isUnavailableSecretLoadHttpStatus(res.status)) {
106
+ throw new SecretLoadUnavailableError(`Failed to batch-load secrets: ${message}`);
107
+ }
102
108
  if (res.status >= 400 &&
103
109
  res.status < 500 &&
104
110
  typeof body.code === 'string') {
@@ -110,6 +116,8 @@ export function registerRunCommand(program) {
110
116
  };
111
117
  const pluginOpts = {
112
118
  companyOverride: opts.company,
119
+ principalUid: cognitoCachePrincipalUid(token) ?? undefined,
120
+ allowOfflineFallback: true,
113
121
  resolveCompanyUid: async () => uid,
114
122
  usage,
115
123
  fetchBatch,
@@ -31,6 +31,7 @@ export interface SecretMetadata {
31
31
  requiredAttestation?: string;
32
32
  };
33
33
  cacheTtlMs?: number;
34
+ revokedAt?: string | null;
34
35
  }
35
36
  export interface SecretLoadSuccessRow extends SecretMetadata {
36
37
  name: string;
@@ -59,7 +60,9 @@ export declare function parseDestinationUrl(raw: string): {
59
60
  };
60
61
  export declare function collectSecretNames(value: string, previous?: string[]): string[];
61
62
  export declare function scrubSandboxOutput(text: string, secretNames?: string[]): string;
62
- export declare function loadRevealedSecrets(token: string, companyUid: string, keys: string[], usage?: SecretUsage): Promise<Map<string, string>>;
63
+ export declare function loadRevealedSecrets(token: string, companyUid: string, keys: string[], usage?: SecretUsage, options?: {
64
+ allowOfflineFallback?: boolean;
65
+ }): Promise<Map<string, string>>;
63
66
  /**
64
67
  * Emit `hq secrets env` output and terminate deterministically.
65
68
  *
@@ -3,13 +3,14 @@ import * as readline from "node:readline";
3
3
  import { spawn } from "node:child_process";
4
4
  import { writeSync } from "node:fs";
5
5
  import * as nodePath from "node:path";
6
- import { ensureCognitoToken } from "../utils/cognito-session.js";
6
+ import { cognitoCachePrincipalUid, ensureCognitoToken } from "../utils/cognito-session.js";
7
7
  import { DEFAULT_SECRETS_CACHE_TTL_MS, hasCacheEntry, readCache, writeCache, removeCacheEntry, clearAllCache, } from "../utils/secrets-cache.js";
8
8
  import { computeSha256 } from "../utils/integrity.js";
9
9
  import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN, EMAIL_PATTERN } from "./_patterns.js";
10
10
  import { describeSecretsScope, formatSecretSaved, formatSecretsListEmpty, formatSecretsListHeader, } from "./secrets-scope.js";
11
11
  import { vaultApiFetch, getCompanyUid, getEntityUid, looksLikeCompanyUid, } from "../utils/vault-api.js";
12
12
  import { HQ_API_KEY_PREFIX, formatApiKeyCapabilityDenial, requireApiKeyCapability, resolveVaultCredentialForCapability, } from "../utils/resolve-vault-credential.js";
13
+ import { CACHED_SECRETS_SERVER_UNAVAILABLE_MESSAGE, isSecretLoadUnavailable, isUnavailableSecretLoadHttpStatus, SecretLoadUnavailableError, } from "../utils/secret-load-availability.js";
13
14
  import { SandboxRunnerClient, } from "../utils/sandbox-runner-client.js";
14
15
  export { vaultApiFetch, getCompanyUid, getEntityUid };
15
16
  /**
@@ -533,11 +534,11 @@ function renderPolicyScripts(scripts) {
533
534
  // `env` through it stops those callers from emitting the warning while keeping
534
535
  // identical UX and error text.
535
536
  //
536
- // Every value use through exec/env/reveal is server-authorized, including when
537
- // an encrypted disk-cache entry exists. The cache remains write-through for
538
- // explicitly offline install-time consumers; these commands never trust it as
539
- // an authorization decision. This means a policy, ACL, tier, or script-approval
540
- // change takes effect on their next use even though there is no push revocation.
537
+ // Every value use through exec/env/reveal is server-authorized when online,
538
+ // including when an encrypted disk-cache entry exists. Only explicitly opted-in
539
+ // exec/env callers may use principal-bound values during classified outages;
540
+ // install authorization and reveal remain online-only. This means a policy,
541
+ // ACL, tier, or script-approval change takes effect on the next online use.
541
542
  // Requests are chunked at MAX_BATCH_NAMES and throw on the FIRST unresolved key
542
543
  // with the same `Failed to fetch secret '<k>': <reason>` shape the per-key GET
543
544
  // path used — never swallows a failure.
@@ -585,13 +586,16 @@ async function loadRevealedSecretsViaApiKey(token, keys) {
585
586
  }
586
587
  return resolved;
587
588
  }
588
- export async function loadRevealedSecrets(token, companyUid, keys, usage) {
589
+ export async function loadRevealedSecrets(token, companyUid, keys, usage, options = {}) {
589
590
  if (token.startsWith(HQ_API_KEY_PREFIX)) {
590
591
  return loadRevealedSecretsViaApiKey(token, keys);
591
592
  }
592
593
  const resolved = new Map();
593
594
  const requested = [...new Set(keys)];
594
- const cachedCandidates = new Set(requested.filter((name) => hasCacheEntry(companyUid, name)));
595
+ const principalUid = cognitoCachePrincipalUid(token);
596
+ const cachedCandidates = new Set(principalUid
597
+ ? requested.filter((name) => hasCacheEntry(companyUid, name, principalUid))
598
+ : []);
595
599
  try {
596
600
  // The batch endpoint is the only non-capturing authorization path that also
597
601
  // evaluates the supplied usage metadata. It covers warm and cold names in
@@ -618,6 +622,9 @@ export async function loadRevealedSecrets(token, companyUid, keys, usage) {
618
622
  body.highSecurity === true) {
619
623
  throw new Error(highSecuritySandboxOnlyMessage());
620
624
  }
625
+ if (isUnavailableSecretLoadHttpStatus(res.status)) {
626
+ throw new SecretLoadUnavailableError(`Failed to batch-load secrets: ${message}`);
627
+ }
621
628
  if (res.status >= 400 &&
622
629
  res.status < 500 &&
623
630
  typeof body.code === "string") {
@@ -646,6 +653,14 @@ export async function loadRevealedSecrets(token, companyUid, keys, usage) {
646
653
  throw new Error("Invalid secret load response from vault");
647
654
  }
648
655
  seenNames.add(s.name);
656
+ if (s.revokedAt != null) {
657
+ errorsByName.set(s.name, {
658
+ code: "revoked",
659
+ message: `Secret '${s.name}' has been revoked`,
660
+ });
661
+ removeCacheEntry(companyUid, s.name);
662
+ continue;
663
+ }
649
664
  if (s.value == null) {
650
665
  errorsByName.set(s.name, {
651
666
  code: "not_returned",
@@ -660,7 +675,7 @@ export async function loadRevealedSecrets(token, companyUid, keys, usage) {
660
675
  typeof s.version === "number" &&
661
676
  Number.isSafeInteger(s.version) &&
662
677
  s.version > 0
663
- ? readCache(companyUid, s.name, s.version, cacheTtlMs)
678
+ ? readCache(companyUid, s.name, principalUid, s.version, cacheTtlMs)
664
679
  : null;
665
680
  if (cached !== null) {
666
681
  resolved.set(s.name, cached);
@@ -670,10 +685,12 @@ export async function loadRevealedSecrets(token, companyUid, keys, usage) {
670
685
  if (typeof s.version === "number" &&
671
686
  Number.isSafeInteger(s.version) &&
672
687
  s.version > 0) {
673
- writeCache(companyUid, s.name, s.value, cacheTtlMs, s.version);
688
+ if (principalUid) {
689
+ writeCache(companyUid, s.name, principalUid, s.value, cacheTtlMs, s.version);
690
+ }
674
691
  }
675
- else {
676
- writeCache(companyUid, s.name, s.value, cacheTtlMs);
692
+ else if (principalUid) {
693
+ writeCache(companyUid, s.name, principalUid, s.value, cacheTtlMs);
677
694
  }
678
695
  }
679
696
  else {
@@ -730,9 +747,29 @@ export async function loadRevealedSecrets(token, companyUid, keys, usage) {
730
747
  return resolved;
731
748
  }
732
749
  catch (err) {
750
+ if (isSecretLoadUnavailable(err) && options.allowOfflineFallback === true) {
751
+ const offline = new Map();
752
+ for (const key of requested) {
753
+ const cached = cachedCandidates.has(key)
754
+ ? readCache(companyUid, key, principalUid)
755
+ : null;
756
+ if (cached === null) {
757
+ // Keep the same failure behavior for a cache miss, while leaving any
758
+ // still-valid entries untouched for a later retry.
759
+ throw err;
760
+ }
761
+ offline.set(key, cached);
762
+ }
763
+ console.error(CACHED_SECRETS_SERVER_UNAVAILABLE_MESSAGE);
764
+ return offline;
765
+ }
766
+ if (isSecretLoadUnavailable(err)) {
767
+ throw err;
768
+ }
733
769
  // A transport failure, stale session, malformed response, or a later chunk
734
- // failure must not leave values written by this attempted operation available
735
- // to offline cache readers. Evict the whole request before failing closed.
770
+ // failure is an explicit denial or unusable response. Evict the whole
771
+ // request before failing closed. Transient no-answer/5xx/429 failures above
772
+ // preserve entries and only use values still valid under their stored TTL.
736
773
  for (const key of requested) {
737
774
  removeCacheEntry(companyUid, key);
738
775
  }
@@ -979,7 +1016,7 @@ export function registerSecretsCommand(program) {
979
1016
  let revealError = null;
980
1017
  if (opts.reveal) {
981
1018
  try {
982
- revealedValue = (await loadRevealedSecrets(token, companyUid, [name], await buildSecretUsage("reveal"))).get(name);
1019
+ revealedValue = (await loadRevealedSecrets(token, companyUid, [name], await buildSecretUsage("reveal"), { allowOfflineFallback: false })).get(name);
983
1020
  }
984
1021
  catch (err) {
985
1022
  revealError = err instanceof Error ? err.message : String(err);
@@ -1460,10 +1497,10 @@ export function registerSecretsCommand(program) {
1460
1497
  const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets exec", secretsCredentialOptions());
1461
1498
  const companyUid = cred.kind === "api-key"
1462
1499
  ? "__api_key__"
1463
- : await getEntityUid(cred.token, scopeOpts(secrets.opts()));
1500
+ : await getEntityUid(cred.token, scopeOpts(secrets.opts()), { allowOfflineScopeFallback: true });
1464
1501
  const revealed = await loadRevealedSecrets(cred.token, companyUid, keys, cred.kind === "cognito"
1465
1502
  ? await buildSecretUsage("exec", _opts.script, _opts.scriptId)
1466
- : undefined);
1503
+ : undefined, { allowOfflineFallback: true });
1467
1504
  const secretEnv = {};
1468
1505
  for (const key of keys) {
1469
1506
  const value = revealed.get(key);
@@ -1511,10 +1548,10 @@ export function registerSecretsCommand(program) {
1511
1548
  const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets env", secretsCredentialOptions());
1512
1549
  const companyUid = cred.kind === "api-key"
1513
1550
  ? "__api_key__"
1514
- : await getEntityUid(cred.token, scopeOpts(secrets.opts()));
1551
+ : await getEntityUid(cred.token, scopeOpts(secrets.opts()), { allowOfflineScopeFallback: true });
1515
1552
  const revealed = await loadRevealedSecrets(cred.token, companyUid, keys, cred.kind === "cognito"
1516
1553
  ? await buildSecretUsage("env", opts.script, opts.scriptId)
1517
- : undefined);
1554
+ : undefined, { allowOfflineFallback: true });
1518
1555
  let payload = "";
1519
1556
  for (const key of keys) {
1520
1557
  const value = revealed.get(key);
@@ -17,6 +17,7 @@ export interface ReadLanesFlagOptions {
17
17
  getToken?: () => string | Promise<string>;
18
18
  reportFailure?: (error: unknown) => void;
19
19
  }
20
+ export declare function readLanesFlagLocalOverride(flagKey: string, env?: NodeJS.ProcessEnv): boolean | null;
20
21
  /**
21
22
  * Read a lanes flag once for each company identity in this CLI process. The
22
23
  * cache contains only an in-flight or settled boolean promise; it is never
@@ -3,6 +3,9 @@ import { ensureCognitoIdToken } from "../../utils/cognito-session.js";
3
3
  import { FLAG_REGISTRY_DEFAULT_ENDPOINT, resolveFlagRegistryEndpoint, } from "../flag-registry-endpoint.js";
4
4
  export const LANES_FLAG_API_DEFAULT_ENDPOINT = FLAG_REGISTRY_DEFAULT_ENDPOINT;
5
5
  export const LANES_FLAG_READ_TIMEOUT_MS = 5_000;
6
+ export function readLanesFlagLocalOverride(flagKey, env = process.env) {
7
+ return readLocalOverride(flagKey, env);
8
+ }
6
9
  const registryReads = new Map();
7
10
  class MissingFlagTokenError extends Error {
8
11
  constructor() {
@@ -37,7 +40,7 @@ function cacheKey(flagKey, company, endpoint) {
37
40
  */
38
41
  export async function readLanesRegistryFlag(flagKey, options = {}) {
39
42
  const env = options.env ?? process.env;
40
- const localOverride = readLocalOverride(flagKey, env);
43
+ const localOverride = readLanesFlagLocalOverride(flagKey, env);
41
44
  if (localOverride !== null)
42
45
  return localOverride;
43
46
  const endpoint = resolveFlagRegistryEndpoint(undefined, env);
@@ -1,6 +1,10 @@
1
1
  import type { SecretLoadResponse, SecretUsage } from '../commands/secrets.js';
2
2
  export interface InstallHqPluginOpts {
3
3
  companyOverride?: string;
4
+ /** Local cache identity derived from the current Cognito person/agent token. */
5
+ principalUid?: string;
6
+ /** Only hq run opts into encrypted offline values. */
7
+ allowOfflineFallback?: boolean;
4
8
  resolveCompanyUid: (slug: string) => Promise<string>;
5
9
  usage?: SecretUsage;
6
10
  fetchBatch: (uid: string, names: string[], usage?: SecretUsage) => Promise<SecretLoadResponse>;
@@ -1,5 +1,6 @@
1
1
  import { ResolutionError } from 'varlock/plugin-lib';
2
- import { DEFAULT_SECRETS_CACHE_TTL_MS, writeCache, removeCacheEntry, } from '../utils/secrets-cache.js';
2
+ import { DEFAULT_SECRETS_CACHE_TTL_MS, hasCacheEntry, readCache, writeCache, removeCacheEntry, } from '../utils/secrets-cache.js';
3
+ import { CACHED_SECRETS_SERVER_UNAVAILABLE_MESSAGE, isSecretLoadUnavailable, } from '../utils/secret-load-availability.js';
3
4
  function normalizeCacheTtlMs(secret) {
4
5
  if (secret.tier === 'sensitive' ||
5
6
  secret.tier === 'nuclear' ||
@@ -169,6 +170,14 @@ export async function prewarmHqSecrets(graph /* EnvGraph */, opts, state) {
169
170
  throw new Error('Invalid secret load response from vault');
170
171
  }
171
172
  seenNames.add(s.name);
173
+ if (s.revokedAt != null) {
174
+ errorsByName.set(s.name, {
175
+ code: 'revoked',
176
+ message: `Secret '${s.name}' has been revoked`,
177
+ });
178
+ removeCacheEntry(uid, s.name);
179
+ continue;
180
+ }
172
181
  if (s.value == null) {
173
182
  errorsByName.set(s.name, {
174
183
  code: 'not_returned',
@@ -180,8 +189,8 @@ export async function prewarmHqSecrets(graph /* EnvGraph */, opts, state) {
180
189
  returnedNames.add(s.name);
181
190
  state.loadedSecretsByName.set(s.name, s.value);
182
191
  const cacheTtlMs = normalizeCacheTtlMs(s);
183
- if (cacheTtlMs > 0) {
184
- writeCache(uid, s.name, s.value, cacheTtlMs, s.version);
192
+ if (cacheTtlMs > 0 && opts.principalUid) {
193
+ writeCache(uid, s.name, opts.principalUid, s.value, cacheTtlMs, s.version);
185
194
  }
186
195
  else {
187
196
  removeCacheEntry(uid, s.name);
@@ -219,6 +228,24 @@ export async function prewarmHqSecrets(graph /* EnvGraph */, opts, state) {
219
228
  catch (err) {
220
229
  state.loadedSecretsByName.clear();
221
230
  state.errorsByName = new Map();
231
+ if (isSecretLoadUnavailable(err) && opts.allowOfflineFallback === true && opts.principalUid) {
232
+ const cached = new Map();
233
+ for (const name of uniqueNames) {
234
+ const value = hasCacheEntry(uid, name, opts.principalUid)
235
+ ? readCache(uid, name, opts.principalUid)
236
+ : null;
237
+ if (value === null) {
238
+ throw err;
239
+ }
240
+ cached.set(name, value);
241
+ }
242
+ state.loadedSecretsByName = cached;
243
+ state.uid = uid;
244
+ console.error(CACHED_SECRETS_SERVER_UNAVAILABLE_MESSAGE);
245
+ return;
246
+ }
247
+ if (isSecretLoadUnavailable(err))
248
+ throw err;
222
249
  for (const name of uniqueNames) {
223
250
  removeCacheEntry(uid, name);
224
251
  }
@@ -173,6 +173,8 @@ export declare function cognitoSubjectFromToken(token: string | undefined): {
173
173
  sub: string | null;
174
174
  entityUid: string | null;
175
175
  };
176
+ /** Stable local cache identity for a Cognito person or agent principal. */
177
+ export declare function cognitoCachePrincipalUid(token: string | undefined): string | null;
176
178
  /** Stable subject key. Null when neither sub nor entity uid is present. */
177
179
  export declare function cognitoSubjectKey(tokens: {
178
180
  idToken?: string;
@@ -434,6 +434,11 @@ export function cognitoSubjectFromToken(token) {
434
434
  const entityUid = typeof entity === "string" && entity.length > 0 ? entity : null;
435
435
  return { sub, entityUid };
436
436
  }
437
+ /** Stable local cache identity for a Cognito person or agent principal. */
438
+ export function cognitoCachePrincipalUid(token) {
439
+ const claims = cognitoSubjectFromToken(token);
440
+ return claims.entityUid ?? claims.sub;
441
+ }
437
442
  /** Stable subject key. Null when neither sub nor entity uid is present. */
438
443
  export function cognitoSubjectKey(tokens) {
439
444
  if (!tokens)
@@ -0,0 +1,8 @@
1
+ export declare class SecretLoadUnavailableError extends Error {
2
+ readonly serverUnavailable = true;
3
+ constructor(message: string, options?: ErrorOptions);
4
+ }
5
+ export declare function isUnavailableSecretLoadHttpStatus(status: number): boolean;
6
+ export declare function isSecretLoadUnavailable(err: unknown): boolean;
7
+ export declare const CACHED_SECRETS_SERVER_UNAVAILABLE_MESSAGE = "using cached secrets; server unavailable";
8
+ //# sourceMappingURL=secret-load-availability.d.ts.map
@@ -0,0 +1,54 @@
1
+ export class SecretLoadUnavailableError extends Error {
2
+ serverUnavailable = true;
3
+ constructor(message, options) {
4
+ super(message, options);
5
+ this.name = "SecretLoadUnavailableError";
6
+ }
7
+ }
8
+ const TRANSIENT_NETWORK_CODES = new Set([
9
+ "ECONNABORTED",
10
+ "ECONNREFUSED",
11
+ "ECONNRESET",
12
+ "EHOSTUNREACH",
13
+ "ENETUNREACH",
14
+ "ENOTFOUND",
15
+ "EAI_AGAIN",
16
+ "ETIMEDOUT",
17
+ "EPIPE",
18
+ "UND_ERR_ABORTED",
19
+ "UND_ERR_CONNECT_TIMEOUT",
20
+ "UND_ERR_HEADERS_TIMEOUT",
21
+ "UND_ERR_BODY_TIMEOUT",
22
+ "UND_ERR_SOCKET",
23
+ ]);
24
+ export function isUnavailableSecretLoadHttpStatus(status) {
25
+ return status === 408 || status === 429 || (status >= 500 && status <= 599);
26
+ }
27
+ export function isSecretLoadUnavailable(err) {
28
+ if (err instanceof SecretLoadUnavailableError)
29
+ return true;
30
+ const seen = new Set();
31
+ let current = err;
32
+ for (let depth = 0; depth < 4 && current && !seen.has(current); depth += 1) {
33
+ seen.add(current);
34
+ if (typeof current !== "object")
35
+ break;
36
+ const record = current;
37
+ if (typeof record.code === "string" &&
38
+ TRANSIENT_NETWORK_CODES.has(record.code)) {
39
+ return true;
40
+ }
41
+ if (record.name === "TimeoutError" || record.name === "AbortError") {
42
+ return true;
43
+ }
44
+ if (record instanceof TypeError &&
45
+ typeof record.message === "string" &&
46
+ /fetch failed|network error/i.test(record.message)) {
47
+ return true;
48
+ }
49
+ current = record.cause;
50
+ }
51
+ return false;
52
+ }
53
+ export const CACHED_SECRETS_SERVER_UNAVAILABLE_MESSAGE = "using cached secrets; server unavailable";
54
+ //# sourceMappingURL=secret-load-availability.js.map
@@ -1,12 +1,16 @@
1
1
  export declare const DEFAULT_SECRETS_CACHE_TTL_MS: number;
2
- export declare function readCache(companyUid: string, name: string, expectedVersion?: number, maxAgeMs?: number): string | null;
2
+ export declare function readCache(companyUid: string, name: string, principalUid: string, expectedVersion?: number, maxAgeMs?: number): string | null;
3
3
  /**
4
4
  * Returns whether an unexpired entry exists without decrypting its plaintext.
5
5
  * Callers use this only to decide whether to reauthorize a possible cache hit;
6
6
  * they must complete that authorization before calling {@link readCache}.
7
7
  */
8
- export declare function hasCacheEntry(companyUid: string, name: string): boolean;
9
- export declare function writeCache(companyUid: string, name: string, value: string, ttlMs?: number, version?: number): void;
8
+ export declare function hasCacheEntry(companyUid: string, name: string, principalUid: string): boolean;
9
+ export declare function writeCache(companyUid: string, name: string, principalUid: string, value: string, ttlMs?: number, version?: number): void;
10
+ /** Persist the last successfully resolved scope for a principal, encrypted and authenticated. */
11
+ export declare function writeCachedScopeUid(scopeKey: string, principalUid: string, scopeUid: string): void;
12
+ /** Return a still-valid scope UID only for the principal that wrote it. */
13
+ export declare function readCachedScopeUid(scopeKey: string, principalUid: string): string | null;
10
14
  /**
11
15
  * List the scope UIDs (`cmp_*` / `prs_*` subdirectories) that currently have a
12
16
  * secrets-cache directory on disk. Install-time MCP registration may use an