fix(qwen): gadfly round 3 — stop guarding a duplicate, delete it
Twelve findings, all real, and the two that matter are about the pre-flight I added rather than about qwen. The credential check had a false pass in the OTHER direction from round 2's: on the GADFLY_BASE_URL override path, resolveModel builds the client with GADFLY_API_KEY and never reads QWEN_API_KEY/KIMI_API_KEY, so treating the provider's own key as sufficient there let a doomed run proceed. Having now been wrong about these rules in both directions, the check no longer tries to model both paths: it covers the REGISTRY path, whose rules it can state exactly, and says nothing about the override path — which is hand-configured by definition, while the registry path is the one you hit by adding a model id to a var and forgetting the secret. The logic moves to scripts/preflight.sh, sourced by both run.sh and the test. The previous answer to "this test duplicates production logic" was a regex drift-guard, and that guard compared only the provider table — not the decision logic, which is precisely the half that carried the bug. A duplicate you guard is still a duplicate; this deletes it, and the test now runs under `set -u` like production does. Also: the test that pins the shared provider slice held its own copy of the list (now ranges the slice); endpointProviderNames had nothing tying it to the switches it describes, which is how it shipped without "gemini" (a new test asserts every advertised name resolves); two godoc lists had drifted; and the "sanity" line that asserted nothing is gone. And the repo had NO test job — `go test` and the pre-flight table both existed and neither was ever executed by CI, which reads as coverage while providing none. Added one (build/vet/gofmt/test/pre-flight), running alongside the image build rather than gating it, so red is loud without standing between a push and a rebuild. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
+70
-75
@@ -1,93 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
# Exercise the run.sh credential pre-flight in isolation: every provider x
|
||||
# key-present/absent x GADFLY_API_KEY/GADFLY_BASE_URL combination.
|
||||
# Table test for the credential pre-flight in preflight.sh.
|
||||
#
|
||||
# The preflight() below is a COPY of run.sh's logic, which makes it exactly the
|
||||
# kind of duplicated pair this whole PR keeps finding. The drift guard runs
|
||||
# first: it compares the provider→variable arms in both files and aborts if
|
||||
# they differ, so a table edited in run.sh and not here fails loudly instead of
|
||||
# certifying stale logic.
|
||||
# It SOURCES the real implementation rather than copying it. An earlier version
|
||||
# duplicated the logic and reconciled the copies with a regex diff — which only
|
||||
# covered the provider table and not the decision logic, i.e. exactly the half
|
||||
# that had the bug. Sourcing removes the second copy entirely.
|
||||
#
|
||||
# Run: scripts/preflight_test.sh (exit 0 = all cases pass)
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
RUN_SH="${RUN_SH:-$SCRIPT_DIR/run.sh}"
|
||||
|
||||
# Normalized case arms: "provider) KEY_ENV=... KEY_HINT=..." with runs of
|
||||
# whitespace collapsed, so alignment changes don't trip the guard.
|
||||
arms_of() { grep -oE '^[[:space:]]*[a-z|-]+\)[[:space:]]+KEY_ENV="[A-Z_]+";[[:space:]]+KEY_HINT="[A-Z_]+"' "$1" | tr -s ' \t' ' ' | sed 's/^ //'; }
|
||||
|
||||
if [ -r "$RUN_SH" ]; then
|
||||
if ! diff <(arms_of "$RUN_SH") <(arms_of "$0") >/dev/null; then
|
||||
echo "FAIL drift guard: the pre-flight table here no longer matches $RUN_SH"
|
||||
diff <(arms_of "$RUN_SH") <(arms_of "$0") | sed 's/^/ /'
|
||||
exit 1
|
||||
fi
|
||||
echo "ok drift guard: table matches run.sh"
|
||||
else
|
||||
echo "FAIL drift guard: cannot read $RUN_SH — cannot prove this tests the real table"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
preflight() { # $1=provider ; env carries the keys
|
||||
local GADFLY_PROVIDER_EFF="$1" KEY_ENV="" KEY_HINT="" KEY_OK=0
|
||||
case "$GADFLY_PROVIDER_EFF" in
|
||||
ollama-cloud) KEY_ENV="OLLAMA_API_KEY"; KEY_HINT="OLLAMA_CLOUD_API_KEY" ;;
|
||||
qwen) KEY_ENV="QWEN_API_KEY"; KEY_HINT="QWEN_API_KEY" ;;
|
||||
kimi) KEY_ENV="KIMI_API_KEY"; KEY_HINT="KIMI_API_KEY" ;;
|
||||
openai|openai-compatible) KEY_ENV="OPENAI_API_KEY"; KEY_HINT="OPENAI_API_KEY" ;;
|
||||
anthropic) KEY_ENV="ANTHROPIC_API_KEY"; KEY_HINT="ANTHROPIC_API_KEY" ;;
|
||||
esac
|
||||
[ -n "$KEY_ENV" ] && [ -n "${!KEY_ENV:-}" ] && KEY_OK=1
|
||||
[ -n "${GADFLY_BASE_URL:-}" ] && [ -n "${GADFLY_API_KEY:-}" ] && KEY_OK=1
|
||||
if [ -n "$KEY_ENV" ] && [ "$KEY_OK" -eq 0 ]; then
|
||||
echo "SKIP:$KEY_HINT"
|
||||
else
|
||||
echo "RUN"
|
||||
fi
|
||||
}
|
||||
# shellcheck source=preflight.sh
|
||||
. "$SCRIPT_DIR/preflight.sh"
|
||||
|
||||
fail=0
|
||||
check() { # desc, want, got
|
||||
if [ "$2" = "$3" ]; then echo "ok $1"; else echo "FAIL $1 — want $2, got $3"; fail=1; fi
|
||||
check() { # description, want, got
|
||||
if [ "$2" = "$3" ]; then
|
||||
echo "ok $1"
|
||||
else
|
||||
echo "FAIL $1 — want '$2', got '$3'"
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
|
||||
env -i bash -c 'true' >/dev/null 2>&1 # sanity
|
||||
# probe <provider> [VAR=VAL ...] — run the real function in a clean environment
|
||||
# under the same shell options production uses (set -u), so an unset-variable
|
||||
# bug surfaces here instead of in a live review.
|
||||
probe() {
|
||||
local provider="$1"; shift
|
||||
env -i PATH="$PATH" HOME="$HOME" "$@" bash -c "
|
||||
set -u
|
||||
. '$SCRIPT_DIR/preflight.sh'
|
||||
gadfly_preflight_key '$provider'
|
||||
"
|
||||
}
|
||||
|
||||
# --- keyed providers with NO key -> skip, naming the right variable ---
|
||||
check "qwen, no key" "SKIP:QWEN_API_KEY" "$(env -u QWEN_API_KEY -u GADFLY_API_KEY bash -c "$(declare -f preflight); preflight qwen")"
|
||||
check "kimi, no key" "SKIP:KIMI_API_KEY" "$(env -u KIMI_API_KEY -u GADFLY_API_KEY bash -c "$(declare -f preflight); preflight kimi")"
|
||||
check "ollama-cloud, none" "SKIP:OLLAMA_CLOUD_API_KEY" "$(env -u OLLAMA_API_KEY -u GADFLY_API_KEY bash -c "$(declare -f preflight); preflight ollama-cloud")"
|
||||
check "openai, no key" "SKIP:OPENAI_API_KEY" "$(env -u OPENAI_API_KEY -u GADFLY_API_KEY bash -c "$(declare -f preflight); preflight openai")"
|
||||
check "anthropic, no key" "SKIP:ANTHROPIC_API_KEY" "$(env -u ANTHROPIC_API_KEY -u GADFLY_API_KEY bash -c "$(declare -f preflight); preflight anthropic")"
|
||||
echo "== registry path: keyed providers with no key must name their variable =="
|
||||
check "qwen, no key" "QWEN_API_KEY" "$(probe qwen)"
|
||||
check "kimi, no key" "KIMI_API_KEY" "$(probe kimi)"
|
||||
check "ollama-cloud, no key" "OLLAMA_CLOUD_API_KEY" "$(probe ollama-cloud)"
|
||||
check "openai, no key" "OPENAI_API_KEY" "$(probe openai)"
|
||||
check "openai-compatible, none" "OPENAI_API_KEY" "$(probe openai-compatible)"
|
||||
check "anthropic, no key" "ANTHROPIC_API_KEY" "$(probe anthropic)"
|
||||
|
||||
# --- keyed providers WITH their key -> run ---
|
||||
check "qwen, keyed" "RUN" "$(env -u GADFLY_API_KEY -u GADFLY_BASE_URL QWEN_API_KEY=k bash -c "$(declare -f preflight); preflight qwen")"
|
||||
check "ollama-cloud, keyed" "RUN" "$(env -u GADFLY_API_KEY -u GADFLY_BASE_URL OLLAMA_API_KEY=k bash -c "$(declare -f preflight); preflight ollama-cloud")"
|
||||
echo "== registry path: the provider's own key lets it run =="
|
||||
check "qwen, keyed" "" "$(probe qwen QWEN_API_KEY=k)"
|
||||
check "kimi, keyed" "" "$(probe kimi KIMI_API_KEY=k)"
|
||||
check "ollama-cloud, keyed" "" "$(probe ollama-cloud OLLAMA_API_KEY=k)"
|
||||
check "openai-compatible, keyed" "" "$(probe openai-compatible OPENAI_API_KEY=k)"
|
||||
|
||||
# --- the WRONG key must not satisfy a provider (no cross-provider fallback) ---
|
||||
check "qwen w/ only OPENAI key" "SKIP:QWEN_API_KEY" "$(env -u GADFLY_API_KEY -u GADFLY_BASE_URL -u QWEN_API_KEY OPENAI_API_KEY=k bash -c "$(declare -f preflight); preflight qwen")"
|
||||
echo "== a wrong-provider key never satisfies a provider (no cross-fallback) =="
|
||||
check "qwen w/ only OPENAI key" "QWEN_API_KEY" "$(probe qwen OPENAI_API_KEY=k)"
|
||||
check "kimi w/ only QWEN key" "KIMI_API_KEY" "$(probe kimi QWEN_API_KEY=k)"
|
||||
|
||||
# --- GADFLY_API_KEY substitutes ONLY with GADFLY_BASE_URL (the override path) ---
|
||||
check "qwen via GADFLY_API_KEY + BASE_URL" "RUN" \
|
||||
"$(env -u QWEN_API_KEY GADFLY_API_KEY=k GADFLY_BASE_URL=https://x bash -c "$(declare -f preflight); preflight qwen")"
|
||||
# The false-pass this check exists to prevent: on the registry path (no
|
||||
# GADFLY_BASE_URL) the qwen built-in reads QWEN_API_KEY and never consults
|
||||
# GADFLY_API_KEY, so pre-flighting it as sufficient lets a doomed run proceed.
|
||||
check "qwen w/ GADFLY_API_KEY but no BASE_URL" "SKIP:QWEN_API_KEY" \
|
||||
"$(env -u QWEN_API_KEY -u GADFLY_BASE_URL GADFLY_API_KEY=k bash -c "$(declare -f preflight); preflight qwen")"
|
||||
echo "== an empty-string key counts as missing, not present =="
|
||||
check "qwen, empty key" "QWEN_API_KEY" "$(probe qwen QWEN_API_KEY=)"
|
||||
|
||||
# --- openai-compatible is its own spelling and must be pre-flighted too ---
|
||||
check "openai-compatible, no key" "SKIP:OPENAI_API_KEY" \
|
||||
"$(env -u OPENAI_API_KEY -u GADFLY_API_KEY -u GADFLY_BASE_URL bash -c "$(declare -f preflight); preflight openai-compatible")"
|
||||
check "openai-compatible, keyed" "RUN" \
|
||||
"$(env -u GADFLY_API_KEY -u GADFLY_BASE_URL -u GADFLY_BASE_URL OPENAI_API_KEY=k bash -c "$(declare -f preflight); preflight openai-compatible")"
|
||||
echo "== GADFLY_API_KEY does NOT substitute on the registry path =="
|
||||
# resolveModel reads GADFLY_API_KEY only after its `baseURL == ""` early
|
||||
# return, so on this path the built-in reads its own variable and a set
|
||||
# GADFLY_API_KEY changes nothing. Treating it as sufficient was a false pass.
|
||||
check "qwen w/ GADFLY_API_KEY only" "QWEN_API_KEY" "$(probe qwen GADFLY_API_KEY=k)"
|
||||
|
||||
# --- empty-string key counts as missing, not present ---
|
||||
check "qwen, empty key" "SKIP:QWEN_API_KEY" "$(env -u GADFLY_API_KEY -u GADFLY_BASE_URL QWEN_API_KEY= bash -c "$(declare -f preflight); preflight qwen")"
|
||||
echo "== override path (GADFLY_BASE_URL set) is deliberately not pre-flighted =="
|
||||
# The credential there is GADFLY_API_KEY with a client-specific fallback, and
|
||||
# the built-ins' own variables are never read. Checking one path's rules
|
||||
# against the other produced a false pass in BOTH directions, so this path is
|
||||
# left alone rather than guessed at.
|
||||
check "qwen + BASE_URL, no keys" "" "$(probe qwen GADFLY_BASE_URL=https://x)"
|
||||
check "qwen + BASE_URL + own key" "" "$(probe qwen GADFLY_BASE_URL=https://x QWEN_API_KEY=k)"
|
||||
check "qwen + BASE_URL + GADFLY key" "" "$(probe qwen GADFLY_BASE_URL=https://x GADFLY_API_KEY=k)"
|
||||
check "openai + BASE_URL, no keys" "" "$(probe openai GADFLY_BASE_URL=https://x)"
|
||||
|
||||
# --- unkeyed providers are never blocked, even with nothing set ---
|
||||
for p in ollama llama-swap llamaswap foreman google gemini some-dsn-name; do
|
||||
check "unkeyed $p" "RUN" "$(env -i bash -c "$(declare -f preflight); preflight $p")"
|
||||
echo "== providers needing no key are never blocked, with nothing set =="
|
||||
for p in ollama llama-swap llama-swaps llamaswap llamaswaps foreman google gemini some-dsn-name; do
|
||||
check "unkeyed $p" "" "$(probe "$p")"
|
||||
done
|
||||
|
||||
exit $fail
|
||||
# google is absent from the table on purpose: it accepts GOOGLE_API_KEY *or*
|
||||
# GEMINI_API_KEY, so a one-name arm would skip a correctly-configured run.
|
||||
check "google w/ only GEMINI_API_KEY" "" "$(probe google GEMINI_API_KEY=k)"
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "RESULT: preflight table FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "RESULT: all pre-flight cases pass"
|
||||
|
||||
Reference in New Issue
Block a user