From 83e5a49f16c2d7d23a7cc9da88e1852576024254 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 21 Jul 2026 14:17:21 +0900 Subject: [PATCH 1/5] fix(strix): supply sibling SQL migrations as PR-scope context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diff-scoped Strix scans copy only the PR's changed files into the scan target. A migration that only ALTERs or references a table created by an earlier, unchanged migration therefore appears — in isolation — to touch a nonexistent relation, and the model reports a phantom CRITICAL "relation/table does not exist" finding whose PoC depends entirely on files excluded from scope. Extend pull_request_scope_context_files with a generic rule (no project-specific paths): for any changed `*/migrations/*.sql`, enumerate the sibling `.sql` files in that migrations directory from the PR head via a read-only `git ls-tree` and emit them as context. Emitted paths flow through the existing trusted PR-head copy path, so no untrusted content or exec bit is introduced. Enumeration fails open — an unavailable/invalid head SHA adds no context and leaves the base changed-file scan unchanged. Reproduced against ContextualWisdomLab/gyeot#11, whose only migration change (0003) was flagged CRITICAL because 0001/0002 (which create the table) were outside the scope. Adds a static assertion and a functional test exercising a two-migration directory with only the second file changed. Co-Authored-By: Claude Fable 5 --- scripts/ci/strix_quick_gate.sh | 42 +++++++++++++++++++++++++++ scripts/ci/test_strix_quick_gate.sh | 45 +++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 8a8f8b31..cf2eec0f 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -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 @@ -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[@]+"${sql_migration_dirs[@]}"}; do + if [ "$known_migration_dir" = "$migration_dir" ]; then + seen_migration_dir=1 + break + fi + done + if [ "$seen_migration_dir" -eq 0 ]; then + sql_migration_dirs+=("$migration_dir") + fi + ;; + esac done if [ "$needs_backend_python" -eq 1 ]; then @@ -1278,6 +1301,25 @@ render.yaml VERSION EOF fi + + # Emit sibling SQL migrations from the PR head so cross-migration schema + # references resolve during the scan. Enumeration is read-only (git ls-tree of + # the migrations directory at PR head) and fails open: when the head SHA is + # unavailable or invalid, no context is added and the base changed-file scan is + # unaffected. Emitted paths flow through the same trusted PR-head copy path as + # every other context file, so no untrusted content or executable bit is added. + 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 ls-tree -r --name-only "$head_sha_for_migration_context" -- "$migration_context_dir/" 2>/dev/null | + grep -E '\.sql$' || true + done + fi + fi } changed_file_list_contains() { diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index aae63c5d..ba09331c 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -164,6 +164,47 @@ 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 ls-tree -r --name-only \"\$head_sha_for_migration_context\" -- \"\$migration_context_dir/\"" "strix gate enumerates sibling migrations from the PR head" + 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 two-migration directory produces when only the second file is changed. + 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/migrations + printf 'CREATE TABLE t (id int);\n' >server/db/migrations/0001_init.sql + printf 'ALTER TABLE t ADD COLUMN c text;\n' >server/db/migrations/0002_add_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/migrations/0002_add_col.sql + ' >"$tmp_dir/out.txt" + ) + assert_file_contains "$tmp_dir/out.txt" "server/db/migrations/0001_init.sql" "strix gate supplies the base migration as context for a later migration diff" + rm -rf "$tmp_dir" +} + assert_strix_workflow_pr_trigger_hardened() { local workflow_file="$REPO_ROOT/.github/workflows/strix.yml" @@ -8546,6 +8587,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 From baf334f6ecca3dbb5f322b78368c9154a0a101ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 08:12:27 +0000 Subject: [PATCH 2/5] fix(strix): skip same-model retry for rate-limited cross-provider github_models fallback When the primary model uses openai_direct mode, LLM_API_BASE_FILE is not set. github_models/* fallback models route through STRIX_GITHUB_MODELS_API_BASE_FILE. The old github_models_api_base_is_active() only checked LLM_API_BASE_FILE, so it returned false for openai_direct primaries, causing the gate to retry a rate-limited github_models fallback 3x (wasting ~3 minutes) instead of skipping directly to the next fallback. Fix: check STRIX_GITHUB_MODELS_API_BASE_FILE as a fallback when LLM_API_BASE_FILE is unset, mirroring the pattern already used in resolved_llm_api_base_for_model(). The retry-skip guard in github_models_rate_limit_should_skip_same_model_retry() already gates on is_github_models_api_compatible_model(), so openai_direct primary models (which don't match github_models/* patterns) are unaffected. Add test scenario openai-direct-github-models-fallback-ratelimit-skip covering the bug: primary retries 3x (openai_direct, not GitHub Models), then the first github_models fallback hits rate limit and correctly skips retry (1 attempt), and the second fallback succeeds (5 total strix calls, not 7). --- scripts/ci/strix_quick_gate.sh | 17 ++++- scripts/ci/test_strix_quick_gate.sh | 110 +++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index cf2eec0f..4bf029be 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -2792,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 diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index ba09331c..e0cd2ffd 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3317,6 +3317,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"' @@ -5427,7 +5452,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") @@ -5623,6 +5649,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 @@ -5829,6 +5866,42 @@ run_filtered_gate_case_if_requested() { "" \ "github_models/openai/o3" ;; + openai-direct-github-models-fallback-ratelimit-skip) + # openai_direct primary quota-fails (retries 3x, as openai_direct is not + # subject to the GitHub Models skip); first github_models fallback + # immediately hits GitHub Models rate limit. The gate must detect GitHub + # Models is active via STRIX_GITHUB_MODELS_API_BASE_FILE and skip + # same-model retries, moving to the second fallback in one step + # (5 strix calls total: 3 primary retries + 1 gpt-5-chat skip + 1 o3). + 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" \ + "|||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" \ @@ -11711,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" \ + "|||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" \ From 50964aeea595b3d8a28cf43f0ea2ba2a0dd7ad9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 08:14:01 +0000 Subject: [PATCH 3/5] style(test): remove duplicate comment from dispatch block The same explanation is already in the standalone invocation below. Removing the redundant multi-line comment from the filter dispatch case. --- scripts/ci/test_strix_quick_gate.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index e0cd2ffd..cfe6d74a 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -5867,12 +5867,6 @@ run_filtered_gate_case_if_requested() { "github_models/openai/o3" ;; openai-direct-github-models-fallback-ratelimit-skip) - # openai_direct primary quota-fails (retries 3x, as openai_direct is not - # subject to the GitHub Models skip); first github_models fallback - # immediately hits GitHub Models rate limit. The gate must detect GitHub - # Models is active via STRIX_GITHUB_MODELS_API_BASE_FILE and skip - # same-model retries, moving to the second fallback in one step - # (5 strix calls total: 3 primary retries + 1 gpt-5-chat skip + 1 o3). run_gate_case "openai-direct-github-models-fallback-ratelimit-skip" \ "openai_direct/gpt-5.6-luna" \ "" \ From 67c13a04964dc7f68582f8e3ed90ac020ce98f5f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 21 Jul 2026 19:50:50 +0900 Subject: [PATCH 4/5] fix(strix): preserve migration context paths --- scripts/ci/strix_quick_gate.sh | 16 ++++++++-------- scripts/ci/test_strix_quick_gate.sh | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 4bf029be..c2c72a62 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -1211,7 +1211,7 @@ pull_request_scope_context_files() { */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[@]+"${sql_migration_dirs[@]}"}; do + for known_migration_dir in "${sql_migration_dirs[@]}"; do if [ "$known_migration_dir" = "$migration_dir" ]; then seen_migration_dir=1 break @@ -1302,12 +1302,12 @@ VERSION EOF fi - # Emit sibling SQL migrations from the PR head so cross-migration schema - # references resolve during the scan. Enumeration is read-only (git ls-tree of - # the migrations directory at PR head) and fails open: when the head SHA is - # unavailable or invalid, no context is added and the base changed-file scan is - # unaffected. Emitted paths flow through the same trusted PR-head copy path as - # every other context file, so no untrusted content or executable bit is added. + # 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:-}")" @@ -1315,7 +1315,7 @@ EOF 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 ls-tree -r --name-only "$head_sha_for_migration_context" -- "$migration_context_dir/" 2>/dev/null | + 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 fi diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index cfe6d74a..9df45866 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -167,7 +167,7 @@ assert_strix_pr_scope_includes_deployment_context() { 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 ls-tree -r --name-only \"\$head_sha_for_migration_context\" -- \"\$migration_context_dir/\"" "strix gate enumerates sibling migrations from the PR head" + 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" } @@ -182,9 +182,10 @@ assert_strix_pr_scope_migration_siblings_functional() { git init -q git config user.email ci@example.com git config user.name ci - mkdir -p server/db/migrations - printf 'CREATE TABLE t (id int);\n' >server/db/migrations/0001_init.sql - printf 'ALTER TABLE t ADD COLUMN c text;\n' >server/db/migrations/0002_add_col.sql + 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)" @@ -198,10 +199,15 @@ assert_strix_pr_scope_migration_siblings_functional() { 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/migrations/0002_add_col.sql + 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/migrations/0001_init.sql" "strix gate supplies the base migration as context for a later migration diff" + 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" } From b6556d3aa0e6e7092a1a50fdbabcf5bea9c77a40 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 21 Jul 2026 20:18:06 +0900 Subject: [PATCH 5/5] test(strix): describe migration context fixture --- scripts/ci/test_strix_quick_gate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 9df45866..ba6a3d40 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -174,7 +174,7 @@ assert_strix_pr_scope_includes_sql_migration_context() { 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 two-migration directory produces when only the second file is changed. + # 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")" (