diff --git a/docs/audits/AUDIT_2026-06-10.md b/docs/audits/AUDIT_2026-06-10.md index f8148b01..d4413ec5 100644 --- a/docs/audits/AUDIT_2026-06-10.md +++ b/docs/audits/AUDIT_2026-06-10.md @@ -53,11 +53,11 @@ The remaining structural debt — the three god-files and the test-mock monolith | M4 | Pre-commit hook used deprecated husky v9 bootstrap (hard failure in v10) and ran lint only — type errors reached CI undetected locally | `.husky/pre-commit` | Fixed in PR #521 | | M5 | `codex-multi-auth workspace` (issue #491) shipped undocumented in `docs/reference/commands.md` | `lib/codex-manager/commands/workspace.ts` | Fixed in PR #520 (incl. doc-integrity test) | | M6 | `AGENTS.md` claimed package version 2.2.0 vs actual 2.3.0-beta.1; `docs/README.md` duplicated 11 release rows inside the Reference table | `AGENTS.md:6`, `docs/README.md:101-112` | Fixed in PR #520 | -| M7 | Retry-with-backoff logic re-implemented with divergent semantics (attempt counts 4/5/6, different retryable-code sets, sleep vs no-sleep) across `lib/config.ts:57-86`, `lib/fs-retry.ts:28-40`, `lib/storage.ts` (BACKUP_COPY loops), `lib/quota-cache.ts:260`, `lib/recovery/storage.ts:188` | multiple | Roadmap §4.2 | +| M7 | Retry-with-backoff logic re-implemented with divergent semantics (attempt counts 4/5/6, different retryable-code sets, sleep vs no-sleep) across `lib/config.ts:57-86`, `lib/fs-retry.ts:28-40`, `lib/storage.ts` (BACKUP_COPY loops), `lib/quota-cache.ts:260`, `lib/recovery/storage.ts:188` | multiple | Roadmap §4.2 — first batch landed earlier in the cycle (`withRetry`/`withRetrySync` in `lib/fs-retry.ts` + config/quota-cache/recovery/uninstall call sites); closing batch in PR #585 (storage rename + config ESTALE CAS) | | M8 | `config/schema/config.schema.json` documents 3 fields; `lib/schemas.ts` `PluginConfigSchema` defines 75+ — IDE autocomplete and external validation see almost none of the config surface | `config/schema/config.schema.json` vs `lib/schemas.ts:30-83` | Roadmap §4.5 | | M9 | `@types/node ^25` against `engines >=18.17` — a **present** type-safety gap, not just polish: new code calling a Node 20+-only API (e.g. `fs.glob`) typechecks cleanly today but fails at runtime for Node 18.17 consumers | `package.json:159` | Open — needs a maintainer decision now (pin `@types/node` to `^20`, or raise engines and document the break); mechanics in §4.5 | | M10 | Shipping both `sourceMap` and `declarationMap` to npm: 532 `.map` files ≈ 1.9 MB (~39% of unpacked size) for a CLI-first package | `tsconfig.json:20-21` | Roadmap §4.5 | -| M11 | `lib/errors.ts` defines a clean `CodexError` hierarchy but only a handful of modules throw it; `runtime-rotation-proxy.ts` and `config.ts` mostly throw/log bare `Error`/strings, so callers cannot distinguish auth from network failures programmatically | `lib/errors.ts` (214 lines), few importers | Roadmap §4.3 | +| M11 | `lib/errors.ts` defines a clean `CodexError` hierarchy but only a handful of modules throw it; `runtime-rotation-proxy.ts` and `config.ts` mostly throw/log bare `Error`/strings, so callers cannot distinguish auth from network failures programmatically | `lib/errors.ts` (214 lines), few importers | Roadmap §4.3 — executed in PR #586 (rotation-proxy startup guards → `CodexValidationError` + error-contract doc section) and PR #587 (config-save unreadable aborts → `StorageError`); the earlier §4.1 extractions had already removed the other bare throws | | M12 | `test/codex-manager-cli.test.ts` carries 40+ module-level `vi.fn()` mocks (~12k LOC test file) — refactoring any mocked interface ripples through the whole file; no snapshot coverage for `--help`/`--json` output | `test/codex-manager-cli.test.ts` | Roadmap §4.4 | | M13 | `ci.yml` and `pr-ci.yml` duplicate typecheck/lint/coverage/build/audit step lists (143 + 112 lines) — drift hazard | `.github/workflows/` | Roadmap §4.4 | @@ -177,6 +177,62 @@ Remaining deferred: the mock-factory migration of the giant suites `index.test.ts`/`runtime-rotation-proxy.test.ts` (stacks on #537's helpers; 202 `vi.mock` call sites in `index.test.ts` alone, deferred for size). +### 5.2 Direct-coverage wave (added 2026-06-10, after the roadmap merge) + +The phase-3/phase-4 extractions (§4.1.1) left ~2,500 lines of login machinery, +health-check, and persistence helpers reachable only through the giant CLI +suites. Writing direct suites for extracted modules surfaced real bugs twice +earlier in this cycle (the `isRecord` guard fixed in #544 and the stream-stall +fix in #546), so the method was applied across the remaining gaps. +Every PR below is independent and based on `main`. The test PRs +(#559–#575, plus #580 and #582–#584) each mock only the effectful seams and run the +real identity-matching / retry / parsing logic; the remaining rows are +follow-ups from the same wave — the L3 convention doc (#576), a +version-drift fix the wave's full-suite canary caught (#577), a +dead-code sweep the coverage scan surfaced (#578, later folded into the +pre-existing #554), a dedup idempotence +fix found by a new identity property suite (#579), an unused +devDependency removal (#581), the closing batch of the §4.2 retry +consolidation (#585), the two §4.3 typed-error slices (#586, #587), and +the knowledge-base refreshes (#588 for test/, #589 for lib/), and a +spy-cascade isolation fix in the storage suite (#590). +(The number sequence skips #558 and #562, which are issues — the knip CI +tracking issue and a maintainer MCP-auth test — not PRs; it also skips +#568, which is the PR delivering this very section.) + +| PR | Suite | Pins | +|----|-------|------| +| [#559](https://github.com/ndycode/codex-multi-auth/pull/559) | `test/login-oauth-selection.test.ts` | login-oauth `resolveAccountSelection` through the real candidate extraction (only `decodeJWT` mocked): #491/#512 workspaces persistence incl. the explicit `--org` path, org-override precedence, cancellation/abort predicates | +| [#560](https://github.com/ndycode/codex-multi-auth/pull/560) | `test/login-menu-actions.test.ts` | `handleManageAction` with the real `findMatchingAccountIndex`: delete/toggle re-resolve by identity under concurrent reorder, active-index rebalancing, refresh transports (cancel/manual via stubbed select), non-TTY prompt fallbacks | +| [#561](https://github.com/ndycode/codex-multi-auth/pull/561) | `test/login-flow.test.ts` | `runAuthLogin` with the real arg parser and cancellation predicate: explicit transports bypass the dashboard and exit on cancel, `--org` threads as an argument (no env mutation), inserted/updated/rebound messaging, MAX_ACCOUNTS cap, forced re-login on add-another | +| [#563](https://github.com/ndycode/codex-multi-auth/pull/563) | `test/login-menu-data.test.ts` | quota probe targeting (safe-storability gate), the dashboard row view model (status mapping, ready-first ordering, quick-switch numbering), runtime current-selection loading, Codex CLI drift sync incl. writer-false | +| [#564](https://github.com/ndycode/codex-multi-auth/pull/564) | `test/persist-selected-account.test.ts` | the shared switch/best/restore persistence helper: family-index bookkeeping, validation refresh (success/graceful failure), pinning, the #474 affinity bump on max(disk, memory), EBUSY retry | +| [#565](https://github.com/ndycode/codex-multi-auth/pull/565) | `test/health-check.test.ts` | `runHealthCheck` quick + live probe: fresh-session trust, rotated-credential write-back and CLI sync, refresh-then-probe with the rotated token, re-login vs still-works classification, cache-save failure tolerance | +| [#566](https://github.com/ndycode/codex-multi-auth/pull/566) | `test/forecast-report-shared.test.ts` | mock-free: `persistRefreshedAccountPatch` identity re-resolution incl. the patched-credentials fallback, `saveAccountsWithRetry` retry/give-up policy, forecast row serialization | +| [#567](https://github.com/ndycode/codex-multi-auth/pull/567) | `test/settings-write-queue.test.ts` | `withQueuedRetry` only (the helper exports are internal surface): retry schedule with injected sleep, 429 retry-after clamping, strict per-path ordering, failed predecessors not blocking, retries staying inside their queue slot | +| [#569](https://github.com/ndycode/codex-multi-auth/pull/569) | `test/dashboard-settings-data.test.ts` | settings clone/equality contract with the real layout-mode resolver: sparse legacy objects clone to documented defaults, explicit layout mode beats the legacy boolean, equality sweeps every independent field comparison | +| [#570](https://github.com/ndycode/codex-multi-auth/pull/570) | `test/rotation-account-selection.test.ts` | `chooseAccount` through a real AccountManager: #474 pin discipline (wins without cursor commit, never falls back, full skip-reason taxonomy), affinity tier, hybrid + linear fallback cursor rules, #509 sequential mode never moving the drain-first primary on transient failure | +| [#571](https://github.com/ndycode/codex-multi-auth/pull/571) | `test/rotation-token-refresh.test.ts` | `ensureFreshAccessToken` with real cooldown bookkeeping: fresh-token short-circuit, rotate-and-commit, concurrent-commit dedup, the #495 invalidation cooldown + monotonic guard, commit-failure degradation | +| [#572](https://github.com/ndycode/codex-multi-auth/pull/572) | `test/rotation-proxy-state.test.ts` | proxy state init and pool-exhausted stale-state recovery: manager swap with the previous pool kept known, routing-mutex carry-over, 1s reload dedupe incl. concurrent sharing, failed-reload retry | +| [#573](https://github.com/ndycode/codex-multi-auth/pull/573) | `test/auth-menu-builder.test.ts` | the auth dashboard view-model formatters, asserted with ANSI stripped so both UI palettes hold: identity precedence + ANSI/control-character stripping in row titles, status badges/colors, hint field ordering, focus keys on storage position | +| [#574](https://github.com/ndycode/codex-multi-auth/pull/574) | `test/property/model-fallback.property.test.ts` | fast-check invariants for the unsupported-model fallback: any returned fallback is a chain member, never the current or an attempted model (under any prefix/suffix/casing spelling), legacy gpt-5.3 edge toggle respected, exhausted chains give up | +| [#575](https://github.com/ndycode/codex-multi-auth/pull/575) | `test/property/settings-write-queue.property.test.ts` | fast-check invariants for the write queue: per-key invocations stay contiguous and in submission order under any ok/flaky/fatal schedule, fatal tasks never block successors, 429 retry-after hints clamp into 10ms..30s | +| [#576](https://github.com/ndycode/codex-multi-auth/pull/576) | `lib/AGENTS.md` | closes audit L3: documents the class vs module-state convention (classes for multi-instance/injectable state and the error hierarchy; module-level state only for process-global concerns, always with a test reset helper) | +| [#577](https://github.com/ndycode/codex-multi-auth/pull/577) | `.codex-plugin/plugin.json`, `docs/README.md`, `AGENTS.md`, `test/documentation.test.ts` | live-regression fix found by a full-suite canary vs the §7 baseline: the 2.3.0-beta.2 bump left three stale beta.1 references (plugin manifest, docs-portal release table, AGENTS.md header); adds a doc-integrity guard pinning the AGENTS.md version to package.json | +| [#578](https://github.com/ndycode/codex-multi-auth/pull/578) (closed, superseded by [#554](https://github.com/ndycode/codex-multi-auth/pull/554)) | five deleted modules + `lib/AGENTS.md` | dead-code sweep surfaced while verifying coverage convergence; an open-PR sweep then showed #554 already deletes the same five files plus two more (`recovery/index.ts`, `storage/restore.ts`) that this PR's reference search had false-matched as live — #578 was closed and its unique `lib/AGENTS.md` fix moved onto #554 | +| [#579](https://github.com/ndycode/codex-multi-auth/pull/579) | `lib/storage.ts` + `test/property/account-identity.property.test.ts` | third live bug found by the coverage method: fast-check proved `deduplicateAccounts` non-idempotent (a newest-wins merge in one matching tier can install an account that duplicates an earlier survivor through another tier); fixed with a fixpoint loop, pinned by 9 identity-matching properties incl. a deterministic counterexample replay | +| [#580](https://github.com/ndycode/codex-multi-auth/pull/580) | `test/account-rate-limits.test.ts` | the extracted per-account rate-limit helpers, previously reachable only via the `accounts.ts` facade: reason-code taxonomy incl. the generic-429 unknown bucket, family vs model-scoped quota keys, expiry-boundary semantics (`now >= reset` clears), model-key precedence, and the expired-entry pruning side effect | +| [#581](https://github.com/ndycode/codex-multi-auth/pull/581) | `package.json` | drops the unused `@fast-check/vitest` devDependency (the property suites deliberately use plain `fast-check`, so the `test.prop` wrapper has no consumers) | +| [#582](https://github.com/ndycode/codex-multi-auth/pull/582) | `test/usage-redaction.test.ts` | the usage-ledger redaction/normalization contracts (previously reachable only via the `usage/index.js` facade): identifier hashing, account refs storing only hashes, the unknown-outcome -> `failure` fallback (never miscounted as success), token clamping/total recompute, the 100..599 status window, cost fallback to the pricing estimate, and the serialized JSONL line never containing raw account id or email | +| [#583](https://github.com/ndycode/codex-multi-auth/pull/583) | `test/settings-preview.test.ts` | the preview-first settings renderer (previously reachable only via the interactive settings-hub panels), using the real `resolveMenuLayoutMode`: statusline-field normalization incl. the defensive copy, summary composition with the inverted status-badge dependency and both nothing-visible explanations, badge toggles, and TTY-gated ANSI highlighting asserted by stripped equality | +| [#584](https://github.com/ndycode/codex-multi-auth/pull/584) | `test/settings-hub-shared.test.ts` | the settings-hub merge/persist layer (only the disk seams mocked; real queued-retry policy): array-clone copy semantics, key-scoped defaults/merge, the load-merge-save transaction preserving concurrent edits to unrelated keys, EBUSY retry without warning, warn + clone-normalized fallback on persistent failure, backend patch save with defensive clone, and `clampBackendNumber` | +| [#585](https://github.com/ndycode/codex-multi-auth/pull/585) | `lib/storage.ts`, `lib/config.ts` + storage rename-retry tests | closes the §4.2 retry consolidation (M7): migrates the last two hand-rolled loops — the temp→final rename (EPERM/EBUSY, 5 attempts, 10ms-doubling) and the config-save ESTALE mtime CAS (3 attempts, re-read/re-merge per try) — onto `withRetry`, with new tests pinning the rename policy through the real `saveAccounts` path | +| [#586](https://github.com/ndycode/codex-multi-auth/pull/586) | `lib/runtime-rotation-proxy.ts`, `docs/reference/error-contracts.md` + proxy guard test | first §4.3 slice (M11): the rotation-proxy startup guards (loopback-only host refusal, missing clientApiKey) throw `CodexValidationError` with field/expected/context metadata, messages unchanged; adds the Typed Error Classes section to the error-contract reference | +| [#587](https://github.com/ndycode/codex-multi-auth/pull/587) | `lib/config.ts` + config-save typed-abort test | second §4.3 slice (M11), unblocked by #585: the three unreadable-config save aborts in `savePluginConfig` throw `StorageError` (UNREADABLE code, config path, actionable hint, classifier cause) with byte-identical messages; with #586 this clears every bare throw from the files §4.3 names | +| [#588](https://github.com/ndycode/codex-multi-auth/pull/588) | `test/AGENTS.md`, `test/README.md` | refreshes the test knowledge bases the wave left stale: headline counts corrected (3847/257 and 2071/87 → the actual 4909 tests / 317 files) and the curated trees gain the wave's load-bearing new suites plus the three fast-check property suites | +| [#589](https://github.com/ndycode/codex-multi-auth/pull/589) | `lib/AGENTS.md` | companion refresh for the lib knowledge base: the STRUCTURE tree gains the extraction-era clusters (`codex-manager/formatters/`, `request/helpers/` + decision/failover modules, `runtime/rotation-*`, `policy/`, `fs-retry`, `temp-path`, `budget-guard`, `local-bridge`) and WHERE TO LOOK points at the shared retry and temp-path helpers | +| [#590](https://github.com/ndycode/codex-multi-auth/pull/590) | `test/storage.test.ts` | isolation hardening for a contamination class found while writing #585's tests: a test failing before its inline `mockRestore()` leaks its fs spy, and a later `vi.spyOn` returns the same leaked spy so passthrough bindings recurse into the new test's own mock; the suite's top-level afterEach now calls `vi.restoreAllMocks()` | + --- ## 6. Findings investigated and REJECTED (do not re-report)