feat(ms-ai-architect): C3.1 — keychain + Learn Platform API-klient (TDD) [skip-docs]
C3 kurs-deteksjon, fase C3.1 (spec docs/c3-course-detection-plan.md §6/§7). Claude-frie, fil-frie infrastruktur-lag for kurs-detektoren (C3.4): - lib/keychain.mjs: readSecret() leser én Keychain-item via `security`-CLI, fail-soft (manglende item / ikke-macOS → null). execImpl-DI for test. - lib/learn-api.mjs: getToken (Entra client-credentials), paginate (async-gen, følger nextLink), buildApiUrl (pinner api-version), buildUpdatedAtGt (kaster på dato-only, canon Z — gotcha #1). Robusthetskontrakt §6: response.ok-sjekk (fetch kaster ikke på 4xx/5xx), AbortSignal.timeout, 429/5xx-retry som respekterer Retry-After, 4xx≠429 ikke-retry, fail-closed. fetchImpl/sleep-DI. Tester (24, alle grønne): test-learn-api (token-body, buildUpdatedAtGt, nextLink-paginering, produkt-param, alle robusthets-asserts), test-keychain, test-courses-invariant (Claude-fri + skriver-ingen-filer). Live-probe (efemer) grønn mot ekte Platform API m/ ekte creds: token + full-enum + inkrementell. Intern infrastruktur (ingen brukervendt kommando/hook/atferd endret ennå — surfacing + docs lander i C3.6). kb-update 213→237 · validate 239 · null regresjon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
215437cb5d
commit
0390cc10ca
5 changed files with 488 additions and 0 deletions
28
scripts/kb-update/lib/keychain.mjs
Normal file
28
scripts/kb-update/lib/keychain.mjs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// keychain.mjs — Read a single secret from the macOS Keychain at runtime.
|
||||
// Zero dependencies. The C3 course detector reads its Platform API credentials
|
||||
// straight from the Keychain (never repo/env) — see c3-course-detection-plan.md §3.
|
||||
//
|
||||
// Fail-soft: a missing item (or a non-macOS host where `security` is absent)
|
||||
// returns null so the caller can skip cleanly rather than crash the pipeline.
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
/**
|
||||
* Read a generic-password secret from the macOS Keychain.
|
||||
* @param {string} service — the keychain item's service name (-s)
|
||||
* @param {string} [account='ktg'] — the account name (-a)
|
||||
* @param {{ execImpl?: typeof execFileSync }} [opts] — inject exec for tests
|
||||
* @returns {string|null} the secret (trimmed) or null if the item is missing
|
||||
*/
|
||||
export function readSecret(service, account = 'ktg', { execImpl = execFileSync } = {}) {
|
||||
try {
|
||||
return execImpl(
|
||||
'security',
|
||||
['find-generic-password', '-s', service, '-a', account, '-w'],
|
||||
{ encoding: 'utf8' },
|
||||
).trim();
|
||||
} catch {
|
||||
// item missing / not macOS → caller fail-softs
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue