fix: pos ai — honor legacy AI_API_KEY as fallback (provider key stays primary)
gates / consistency-and-conventions (push) Successful in 27s

Architect decision C on the API-key contract mismatch: docs claimed
AI_API_KEY was the required primary key, but resolve_key() only read
provider-specific keys (7ae2e77 removed shared-key priority to fix
cross-provider leakage; docs never updated).

- bin/pos-ai resolve_key(): provider key wins, legacy AI_API_KEY honored
  read-only when the provider's own key is empty, llamacpp unchanged;
  cmd_providers() configured check mirrors the same set
- require_key() error messages byte-stable (test-locked)
- AI_API_KEY NOT re-added to the # POS_CONFIG:/# PROVIDER_CONFIG: registry
- Docs reworded: POS.md rows 91/96/98 + precedence sentence, howto/ai.md
  first-run hints, HOWTO.md row, AGENT_Context prose (2 spots), config/ai.env
  legacy comment
- New regression tests/t-ai-key-resolution.sh: 24 checks / 10 cases
  (provider-key-only, AI_API_KEY-only, both -> provider wins, env-wins,
  llamacpp no-key, missing-key message, providers configured status)

Verified: suite 19 files / 440 checks / 0 fail / 0 skip; make gen
byte-idempotent; make check OK; make lint 0 FAIL, 0 WARN; bash -n clean;
git diff --check clean; Reviewer APPROVE_WITH_NOTES (mutation disproof:
inverted precedence -> C3/C6 fail)
This commit is contained in:
Your Name
2026-09-07 13:32:42 -04:00
parent aedc9b5c8f
commit 9ef42c5fd1
9 changed files with 156 additions and 16 deletions
+10 -5
View File
@@ -156,10 +156,14 @@ load_config() {
resolve_key() {
load_config
local p="${PROVIDER:-gemini}"
# Each provider has its own API key — set AI_API_KEY internally for adapters
# Provider-specific key wins (leakage guard); legacy shared AI_API_KEY is a
# read-only fallback when the provider's own key is empty — load_config/env
# already exported it (env-wins), so never overwrite it here.
case "$p" in
gemini) [ -n "${AI_GEMINI_API_KEY:-}" ] && export AI_API_KEY="$AI_GEMINI_API_KEY" && return 0 ;;
openrouter) [ -n "${OPENROUTER_API_KEY:-}" ] && export AI_API_KEY="$OPENROUTER_API_KEY" && return 0 ;;
gemini) [ -n "${AI_GEMINI_API_KEY:-}" ] && export AI_API_KEY="$AI_GEMINI_API_KEY" && return 0
[ -n "${AI_API_KEY:-}" ] && return 0 ;;
openrouter) [ -n "${OPENROUTER_API_KEY:-}" ] && export AI_API_KEY="$OPENROUTER_API_KEY" && return 0
[ -n "${AI_API_KEY:-}" ] && return 0 ;;
llamacpp) return 0 ;; # No API key needed for local server
esac
return 1
@@ -626,9 +630,10 @@ cmd_providers() {
[ -n "$pmodel" ] || pmodel="unknown"
# Check if API key exists for this provider
configured="not configured"
# Same key set as resolve_key(): provider key, or legacy AI_API_KEY fallback
case "$name" in
gemini) [ -n "${AI_GEMINI_API_KEY:-}" ] && configured="configured" ;;
openrouter) [ -n "${OPENROUTER_API_KEY:-}" ] && configured="configured" ;;
gemini) { [ -n "${AI_GEMINI_API_KEY:-}" ] || [ -n "${AI_API_KEY:-}" ]; } && configured="configured" ;;
openrouter) { [ -n "${OPENROUTER_API_KEY:-}" ] || [ -n "${AI_API_KEY:-}" ]; } && configured="configured" ;;
llamacpp) configured="configured" ;; # Local server — always configured
esac
current=""