Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 57 additions & 2 deletions scripts/ci/strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,7 @@ pull_request_scope_context_files() {
local needs_frontend_email_api_context=0
local needs_deployment_context=0
local changed_file normalized_changed_file
local -a sql_migration_dirs=()
for changed_file in "$@"; do
normalized_changed_file="$(normalize_changed_file_path "$changed_file")" || return 2
case "$normalized_changed_file" in
Expand All @@ -1199,6 +1200,28 @@ pull_request_scope_context_files() {
needs_deployment_context=1
;;
esac
# A single SQL migration references schema objects (tables, columns) that its
# sibling migrations create. Diff-scoping one migration in isolation makes the
# scanner report phantom "relation does not exist" / "table does not exist"
# findings — a false positive whose PoC depends only on files excluded from the
# scope. Collect each touched migrations directory so the whole ordered set is
# supplied from the PR head as read-only context. Generic across repositories;
# no project-specific paths are hard-coded.
case "$normalized_changed_file" in
*/migrations/*.sql | migrations/*.sql)
local migration_dir="${normalized_changed_file%/*}"
local seen_migration_dir=0 known_migration_dir
for known_migration_dir in "${sql_migration_dirs[@]}"; do
if [ "$known_migration_dir" = "$migration_dir" ]; then
seen_migration_dir=1
break
fi
done
Comment thread
seonghobae marked this conversation as resolved.
if [ "$seen_migration_dir" -eq 0 ]; then
sql_migration_dirs+=("$migration_dir")
fi
;;
esac
done

if [ "$needs_backend_python" -eq 1 ]; then
Expand Down Expand Up @@ -1278,6 +1301,25 @@ render.yaml
VERSION
EOF
fi

# Enumerate sibling SQL migrations from the PR-head tree so cross-migration
# schema references resolve during the scan. Enumeration is read-only and
# fails open: when the head SHA is unavailable or invalid, no context is added
# and the base changed-file scan is unaffected. Changed migrations are copied
# from PR-head blobs; unchanged siblings come from the trusted materialized
# checkout. Both paths are normalized and supplied only as scanner context.
if [ "${#sql_migration_dirs[@]}" -gt 0 ]; then
local head_sha_for_migration_context migration_context_dir
head_sha_for_migration_context="$(trim_whitespace "${PR_HEAD_SHA:-}")"
if [ -n "$head_sha_for_migration_context" ] &&
is_valid_git_commit_sha "$head_sha_for_migration_context" &&
git rev-parse --verify --quiet "$head_sha_for_migration_context^{commit}" >/dev/null; then
for migration_context_dir in "${sql_migration_dirs[@]}"; do
git -c core.quotepath=false ls-tree -r --name-only "$head_sha_for_migration_context" -- "$migration_context_dir/" 2>/dev/null |
grep -E '\.sql$' || true
done
Comment thread
seonghobae marked this conversation as resolved.
fi
fi
}

changed_file_list_contains() {
Expand Down Expand Up @@ -2750,12 +2792,25 @@ is_vertex_not_found_error() {
}

github_models_api_base_is_active() {
if [ -z "$LLM_API_BASE_FILE" ]; then
local api_base_file="${LLM_API_BASE_FILE:-}"
local api_base_file_label="LLM_API_BASE_FILE"
# Cross-provider fallback: when the primary scan uses direct-OpenAI,
# LLM_API_BASE_FILE is not set, but github_models/* fallback models
# route through the GitHub Models endpoint supplied by
# STRIX_GITHUB_MODELS_API_BASE_FILE. Recognise either source so that
# github_models_rate_limit_should_skip_same_model_retry correctly skips
# same-model retries for rate-limited cross-provider fallback models.
if [ -z "$api_base_file" ] && [ -n "${STRIX_GITHUB_MODELS_API_BASE_FILE:-}" ]; then
api_base_file="$STRIX_GITHUB_MODELS_API_BASE_FILE"
api_base_file_label="STRIX_GITHUB_MODELS_API_BASE_FILE"
fi

if [ -z "$api_base_file" ]; then
return 1
fi

local resolved_llm_api_base_file
if ! resolved_llm_api_base_file="$(resolve_trusted_input_file "LLM_API_BASE_FILE" "$LLM_API_BASE_FILE" 2>/dev/null)"; then
if ! resolved_llm_api_base_file="$(resolve_trusted_input_file "$api_base_file_label" "$api_base_file" 2>/dev/null)"; then
return 1
fi

Expand Down
155 changes: 154 additions & 1 deletion scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,53 @@ assert_strix_pr_scope_includes_deployment_context() {
assert_file_contains "$GATE_SCRIPT" "scripts/ci/test_*.sh" "strix gate excludes large CI self-test harnesses from PR scan targets"
}

assert_strix_pr_scope_includes_sql_migration_context() {
assert_file_contains "$GATE_SCRIPT" "*/migrations/*.sql | migrations/*.sql" "strix gate recognizes SQL migration files for sibling context"
assert_file_contains "$GATE_SCRIPT" "sql_migration_dirs+=(\"\$migration_dir\")" "strix gate collects each touched migrations directory"
assert_file_contains "$GATE_SCRIPT" "git -c core.quotepath=false ls-tree -r --name-only \"\$head_sha_for_migration_context\" -- \"\$migration_context_dir/\"" "strix gate enumerates sibling migrations from the PR head without quoting non-ASCII paths"
assert_file_contains "$GATE_SCRIPT" "fails open" "strix gate migration context enumeration is documented as fail-open"
}

assert_strix_pr_scope_migration_siblings_functional() {
# A migration-only diff must resolve schema references from sibling migrations,
# not report a phantom "relation does not exist" finding. Enumerate the context
# a three-migration directory produces when the second and third files change.
local tmp_dir
tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/strix-migration-context.XXXXXX")"
(
cd "$tmp_dir"
git init -q
git config user.email ci@example.com
git config user.name ci
mkdir -p "server/db with space/migrations"
printf 'CREATE TABLE t (id int);\n' >"server/db with space/migrations/0001_기초.sql"
printf 'ALTER TABLE t ADD COLUMN c text;\n' >"server/db with space/migrations/0002_add_col.sql"
printf 'ALTER TABLE t ADD COLUMN d text;\n' >"server/db with space/migrations/0003_add_second_col.sql"
git add -A
git commit -qm base
head_sha="$(git rev-parse HEAD)"

# shellcheck source=/dev/null
REPO_ROOT="$tmp_dir" PR_HEAD_SHA="$head_sha" \
bash -c '
set -euo pipefail
REPO_ROOT="'"$tmp_dir"'"
trim_whitespace() { printf "%s" "$1"; }
is_valid_git_commit_sha() { [[ "$1" =~ ^[0-9a-fA-F]{40}$ ]]; }
normalize_changed_file_path() { printf "%s" "$1"; }
'"$(sed -n "/^pull_request_scope_context_files()/,/^}/p" "$GATE_SCRIPT")"'
pull_request_scope_context_files \
"server/db with space/migrations/0002_add_col.sql" \
"server/db with space/migrations/0003_add_second_col.sql"
' >"$tmp_dir/out.txt"
)
assert_file_contains "$tmp_dir/out.txt" "server/db with space/migrations/0001_기초.sql" "strix gate preserves spaces and non-ASCII sibling migration paths"
local sibling_count
sibling_count="$(grep -Fc "server/db with space/migrations/0001_기초.sql" "$tmp_dir/out.txt" || true)"
assert_equals "1" "$sibling_count" "strix gate deduplicates a migration directory containing spaces"
rm -rf "$tmp_dir"
}

assert_strix_workflow_pr_trigger_hardened() {
local workflow_file="$REPO_ROOT/.github/workflows/strix.yml"

Expand Down Expand Up @@ -3276,6 +3323,31 @@ REPORT
;;
esac
;;
openai-direct-github-models-fallback-ratelimit-skip)
# Primary openai_direct fails; first github_models fallback hits GitHub
# Models rate limit. The gate must detect the GitHub Models API base via
# STRIX_GITHUB_MODELS_API_BASE_FILE and skip same-model retries of the
# rate-limited fallback, moving directly to the second fallback.
case "${STRIX_LLM:-}" in
openai/gpt-5.6-luna)
echo "Error getting response: Error code: 429 - {'error': {'message': 'You exceeded your current quota, please check your plan and billing details.', 'type': 'insufficient_quota', 'code': 'insufficient_quota'}}"
echo "openai.RateLimitError: Error code: 429"
exit 1
;;
openai/gpt-5-chat)
echo "RateLimitError: Too many requests. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service)."
exit 1
;;
openai/o3)
echo "scan ok with second GitHub Models fallback"
exit 0
;;
*)
echo "unexpected model ${STRIX_LLM:-}" >&2
exit 9
;;
esac
;;
vertex-all-notfound)
echo "Error: litellm.NotFoundError: Vertex_aiException - x"
echo '"status": "NOT_FOUND"'
Expand Down Expand Up @@ -5386,7 +5458,8 @@ PY
FAKE_STRIX_OUTSIDE_REPORT_DIR="$repo_root_dir/outside-strix-report"
)
fi
if [ "$scenario" = "openai-direct-quota-github-models-fallback-success" ]; then
if [ "$scenario" = "openai-direct-quota-github-models-fallback-success" ] ||
[ "$scenario" = "openai-direct-github-models-fallback-ratelimit-skip" ]; then
printf '%s' 'https://models.github.ai/inference' >"$tmp_dir/github_models_api_base.txt"
printf '%s' 'github-models-fallback-token' >"$tmp_dir/github_models_key.txt"
env_cmd+=(STRIX_GITHUB_MODELS_API_BASE_FILE="$tmp_dir/github_models_api_base.txt")
Expand Down Expand Up @@ -5582,6 +5655,17 @@ PY
"scenario=$scenario does not sleep in same-model retry after GitHub Models rate limiting"
fi

if [ "$scenario" = "openai-direct-github-models-fallback-ratelimit-skip" ]; then
assert_file_contains \
"$output_log" \
"GitHub Models rate limit detected for model 'github_models/openai/gpt-5-chat'; skipping same-model retry and moving directly to fallback models or current-head neutral classification." \
"scenario=$scenario logs why same-model retry was skipped for cross-provider fallback"
assert_file_not_contains \
"$output_log" \
"Retrying model 'github_models/openai/gpt-5-chat' due to rate limit" \
"scenario=$scenario does not retry rate-limited cross-provider fallback model"
fi

if [ "$scenario" = "pr-changed-scope-full-set" ]; then
assert_internal_pr_scope_targets "$target_log" "$repo_root_dir" "$expected_calls"
fi
Expand Down Expand Up @@ -5788,6 +5872,36 @@ run_filtered_gate_case_if_requested() {
"" \
"github_models/openai/o3"
;;
openai-direct-github-models-fallback-ratelimit-skip)
run_gate_case "openai-direct-github-models-fallback-ratelimit-skip" \
"openai_direct/gpt-5.6-luna" \
"" \
"0" \
"REGEX:Strix quick scan succeeded with fallback model 'github_models/openai/o3' in [0-9]+s\\." \
"5" \
"openai/gpt-5.6-luna|openai/gpt-5.6-luna|openai/gpt-5.6-luna|openai/gpt-5-chat|openai/o3" \
"<unset>|<unset>|<unset>|https://models.github.ai/inference|https://models.github.ai/inference" \
"vertex_ai" \
"" \
"" \
"2" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"github_models/openai/gpt-5-chat github_models/openai/o3"
;;
gemini-timeout-fallback-success)
run_gate_case_allow_provider_signal "gemini-timeout-fallback-success" \
"gemini/timeout-fallback-primary" \
Expand Down Expand Up @@ -8546,6 +8660,10 @@ assert_strix_workflow_pr_trigger_hardened

assert_strix_pr_scope_includes_deployment_context

assert_strix_pr_scope_includes_sql_migration_context

assert_strix_pr_scope_migration_siblings_functional

assert_strix_gpt54_model_guard_cases

assert_strix_gate_target_scope_separated
Expand Down Expand Up @@ -11666,6 +11784,41 @@ run_gate_case "openai-direct-quota-github-models-fallback-success" \
"" \
"github_models/openai/o3"

# Direct-OpenAI primary hits a quota/rate-limit error (retries 3x, as
# openai_direct is not subject to the GitHub Models skip), then the first
# github_models fallback immediately hits a GitHub Models rate limit.
# The gate must detect GitHub Models is active via STRIX_GITHUB_MODELS_API_BASE_FILE
# (not LLM_API_BASE_FILE, which is unset for openai_direct) and skip same-model retries
# of the rate-limited fallback, moving directly to the second fallback (5 calls total).
run_gate_case "openai-direct-github-models-fallback-ratelimit-skip" \
"openai_direct/gpt-5.6-luna" \
"" \
"0" \
"REGEX:Strix quick scan succeeded with fallback model 'github_models/openai/o3' in [0-9]+s\\." \
"5" \
"openai/gpt-5.6-luna|openai/gpt-5.6-luna|openai/gpt-5.6-luna|openai/gpt-5-chat|openai/o3" \
"<unset>|<unset>|<unset>|https://models.github.ai/inference|https://models.github.ai/inference" \
"vertex_ai" \
"" \
"" \
"2" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"" \
"github_models/openai/gpt-5-chat github_models/openai/o3"

run_gate_case "github-models-fallback-success-deepseek-v3" \
"vertex_ai/missing-primary" \
"github_models/deepseek/deepseek-r1-0528 github_models/deepseek/deepseek-v3-0324" \
Expand Down
Loading