collectQueueSecrets function
Map<String, String>
collectQueueSecrets(
- List<
ProviderQueueEntry> entries, - SecureKeyCache keys, {
- Map<
String, String> ? env,
Collects the secrets snapshot for the queue: each entry's apiKeyEnv
resolves from the environment, else the secure store (env wins — the
same precedence as the roles snapshot). A chatgpt-codex entry that
named no apiKeyEnv resolves its DEFAULT Keychain slot
(chatGptOAuthCredentialsEnvName) under the same rule, and a
refreshTokenEnv name resolves the same way (issue #1704). Every
resolved base name also sweeps its ROTATION STACK from the environment
(NAME_2, NAME_3, … — the same rule collectRoleSecrets applies,
gh-1781): buildProviderQueueChain's collectKeyStack builds a
multi-credential ApiKeyRing from it for in-entry key rotation.
Rotation-stack names stay env-only (the store holds base names only).
Implementation
Map<String, String> collectQueueSecrets(
List<ProviderQueueEntry> entries,
SecureKeyCache keys, {
Map<String, String>? env,
}) {
final environment = env ?? Platform.environment;
final secrets = <String, String>{};
for (final entry in entries) {
// gh-1781 review r1: one ladder for "which key names does a queue
// entry reference" — [queueEntryKeyNames] is the source of truth.
for (final name in queueEntryKeyNames([entry])) {
if (secrets.containsKey(name)) continue;
final fromEnv = environment[name];
if (fromEnv != null && fromEnv.isNotEmpty) {
secrets[name] = fromEnv;
} else {
final stored = keys.read(name);
if (stored != null) secrets[name] = stored;
}
final suffix = RegExp('^${RegExp.escape(name)}_\\d+\$');
for (final candidate in environment.entries) {
if (suffix.hasMatch(candidate.key) && candidate.value.isNotEmpty) {
secrets[candidate.key] = candidate.value;
}
}
}
}
return secrets;
}