diff --git a/requirements-opencode-review-ci-hashes.txt b/requirements-opencode-review-ci-hashes.txt index 60ebe967..367ac628 100644 --- a/requirements-opencode-review-ci-hashes.txt +++ b/requirements-opencode-review-ci-hashes.txt @@ -107,6 +107,70 @@ coverage==7.14.3 \ # via # -r requirements-opencode-review-ci.txt # pytest-cov +hypothesis==6.163.0 \ + --hash=sha256:002a9709345892279fb0e81b5a05b72d08cfe81f937339827be0d588607ca9b0 \ + --hash=sha256:00d3091b28de83c5116e0ccd9a4bcb28ef61d2aace5df91093bb22434fd2350c \ + --hash=sha256:0a0c396244c13805edcb73ff467c4c8178ccefc41c4ef5ed00a68e612fd773e9 \ + --hash=sha256:0a933aca9ebf9daf951d07cf01200c94c321b6ee0b42cc7b67675c9686d914c2 \ + --hash=sha256:0cba5202f74e7e4cdb676d86f26e8cc1b4fdc88f7f58ba73c8ac45b6b22f3070 \ + --hash=sha256:213527755f0fc2b1f3721e73fd60023e2752a48f914e3e2df8d35111956ae5c8 \ + --hash=sha256:21e72e8d5818e5ef8cd6a2191c386e3fd1a6d9e3739cf97289b4d9b5dbc8e38d \ + --hash=sha256:2849c23b2e0fe2eef4c1ec336b01eac7ad7397c49fca43c264f59ec1e6046eac \ + --hash=sha256:28a6cc1c25a6cc9b6ec079eaabd32ac769994831ecddd57123ce43c9056dcf34 \ + --hash=sha256:31dc46c48aa53c3ec92d03120978ca7f19b9cf96d195ed3fc93503f1433c94a6 \ + --hash=sha256:320b076bf6436f971f1c73ee651e60001226d1b4e341f2c4a1ca87248261ca03 \ + --hash=sha256:331906cb029b6b360b8ebac3ec00c3cfa720037fe2efb294a503a1979c9a9a8f \ + --hash=sha256:34fc895691a2420595506eb17f3a104f2fa9039f013c0770a6cc2743ccaf6fed \ + --hash=sha256:3b6cee2afe6c67b31a4a64b63a876e0b020befdc61daabea80f7a0e14f19203a \ + --hash=sha256:3f3cceb4720a39127622fbf3bcebe1775b894372c53b5edddfdef10bbdeef9ec \ + --hash=sha256:40dfab6fe6a02a80abef81aebf88e53cd529e3f2f6ba3486b674a67b1f4a3512 \ + --hash=sha256:4159a1c2560e10de51b1c14956e277eb1b37526c9abef9e87c1e531760486448 \ + --hash=sha256:487ab8ec2f01a225d6a1e2ceadc5290cde2c691952bd2e7f76199cf82e06fb25 \ + --hash=sha256:4ab0dadc09c537d4ac57e564039dfe7daf09c98375306d54bfc0fd6c218efcca \ + --hash=sha256:50073f8e63c1e7d3403899755657a990d8bba7b5b5bff66b1c56796d4969bb28 \ + --hash=sha256:520480d4bd3a17557616c25923640953e360332c89d012fffcebd69857e674a9 \ + --hash=sha256:52f16840add2eb02c2416f3b83cec4f527b6c19699f2d31eff4859233c715526 \ + --hash=sha256:56ed585baab75cb98462c57ca88bbdc6a9d935a14118dd572fb476c3ecec2a06 \ + --hash=sha256:58be45d1737bf8c2e10cf29505c0f10f8a23d61bc82e4339182a6c8251cbc2d9 \ + --hash=sha256:59f5fdb8addb44c17520a60d50542d9db6ceba577bbf54efefa9c10ee20be140 \ + --hash=sha256:5a3ac6c62d49f7fe518dfe7fa924fa03aac839993702207802b0e45f9e1b0dab \ + --hash=sha256:67d1593941ede41052b4a35ec25b50d0e280358c7674ef7812d520010e7e8bdf \ + --hash=sha256:6ae63dec6d1d467b7f4737455f81a7a82f14a41c14510937fcfbc726a085b5f8 \ + --hash=sha256:7a3db868a943c814cc557104712d43bf609adfe5ea9f708f38377d366b4855f8 \ + --hash=sha256:7ca7b20bf38d51e15f7808b0239791c4792b1709ce0c63093acaff56a09c31e6 \ + --hash=sha256:7cb3d927360fe73f9a06d646e6082237142ee39c24679c7133d22bf06dd03b45 \ + --hash=sha256:7ef8954e37c80e0c46e6161eef1c72c71059b95250e620a77bd646f6c7a52a2d \ + --hash=sha256:8aac96db8a6c7ee43aba2ee0d3c43893da1fb7c38ed54790c1be2b6d8fd87b96 \ + --hash=sha256:8c5d1e6bad47edf6fb1d7406cf6d67314ac08325c63a49550d782a4596ea302b \ + --hash=sha256:9105c66ea8dbc108adc42058bb7b65bd953f53ee178bf63bf9ebb0cded6c8c96 \ + --hash=sha256:9be37b7ddf0af9e3f9112cd133afc34e78a56da1f96db5f2b4fc289fe1c4d1c3 \ + --hash=sha256:9c084749c115ea7918cf7efa144682783da17eec70d1276689182b871126e715 \ + --hash=sha256:9d23f0f3a14bb6e6f99c793d340196dba4af95ba25bfcab624d1794f540f5e27 \ + --hash=sha256:a16ebce774755a7a652bd44c62101dc914372ed1a98935969624848c9627b4a4 \ + --hash=sha256:a2a20e9835d3c4b293a709ee6ef769bcb18c6ed4ef337a9e251c1a9496d5e8be \ + --hash=sha256:a57352efa938889ea9992667a5014c0fc870d03945de71918574d1cf28276378 \ + --hash=sha256:ab34c61d9249f1a8129cb4276062c04e3e47b5be8de6446e7c7fe11362d6fe43 \ + --hash=sha256:b123b4995a7612f1130e2b2362c9a5d0568df887bf7e7bdb45c23af8cd5423c9 \ + --hash=sha256:b268211e625cd550e361fc387bf1db5deb1e9cae0ce4041116f0a0aafeef7c06 \ + --hash=sha256:b2ddcdaf6691101e06dc4a5add7b8c8fdf1e68daba599255a281f3f3550d3331 \ + --hash=sha256:b4ad2134405d5345434c22dea96bbc12c85abcfc3c253a8063dbc9ff01164555 \ + --hash=sha256:b839dfd1342bb50570cb0c66b80322307cdb468abf14faf5df4dab022bc1b9ce \ + --hash=sha256:b8f22fb8218ba6a452bf9000fc656e1ed57625d17cc8a3871a0fcea3b1b69ebf \ + --hash=sha256:bd312b15044b1c1a0920a5827a830559b2d1fa380851cedf509f8b835309c5b9 \ + --hash=sha256:c0ec3b709508ccd835d8ded1db025b7800618f2289a22a6bfd4927da5f4eb33c \ + --hash=sha256:c4f5be1482189c7b0a1dcac269fffe97a7d18cc04ac9a9a4d6613212dd87f38b \ + --hash=sha256:ca1b48bde68c528a79dec2a2859e05035802e5b1c9c3579f388c9de6ed6d0148 \ + --hash=sha256:d0838a28e9943d5b834ebae59b02adda76e2cd1e65caa808104c72102052057d \ + --hash=sha256:e165f6cc2075059b7c95dac1612bfb25494f72d90f56880e84c288b089f8a896 \ + --hash=sha256:e568a3d766b7ba8df00e0c33efc4c6530cde14fbc72daabe4824eed211ed7596 \ + --hash=sha256:ee47c2cb1be03a052ebd3549dad07f636a98b3ccfd7acbe5e17b3b7da0ab9e37 \ + --hash=sha256:f1fe222f50a1898e87a1e7323ab35f9e956278efabe4dd55a1342808206d05ad \ + --hash=sha256:f28ad27193c1fbcfb52ef2ee63d2b721563525089e80962b4268b306dac45507 \ + --hash=sha256:f2f1b67a48da86d3e41c9445367b49a49f7efdb60fc8b5e3593f05e6afb2efbe \ + --hash=sha256:f7f706df6839dcc53f20833f2933cbcd126fd2fdee7c312e053de49df4b64e44 \ + --hash=sha256:fae7305ae20fddeea09df317b920c45d3e20bfedbdb041f4db6ca5267c458189 \ + --hash=sha256:ffdda3006a383a48f71a23b4f2b3fae3fe1b09af67925d885985f7ec34d66bcb + # via -r requirements-opencode-review-ci.txt iniconfig==2.3.0 \ --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 @@ -143,6 +207,10 @@ pytest-cov==7.1.0 \ --hash=sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2 \ --hash=sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 # via -r requirements-opencode-review-ci.txt +sortedcontainers==2.4.0 \ + --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ + --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 + # via hypothesis tabulate==0.10.0 \ --hash=sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d \ --hash=sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3 diff --git a/requirements-opencode-review-ci.txt b/requirements-opencode-review-ci.txt index b73fe983..0b585231 100644 --- a/requirements-opencode-review-ci.txt +++ b/requirements-opencode-review-ci.txt @@ -1,4 +1,8 @@ coverage==7.14.3 +# hypothesis (MPL-2.0, permissive test tool) so the coverage-evidence sandbox can +# run repos' always-on property tests (tests/fuzz/*) instead of ImportError-ing on +# collection. Matches the >=6.100 floor used by consumer repos (e.g. contextual-orchestrator). +hypothesis>=6.100 interrogate==1.7.0 pytest==9.1.1 pytest-cov==7.1.0 diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 25d853d3..3b001a92 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -1497,6 +1497,15 @@ build_pull_request_head_tree_scope_dir() { [ -n "$metadata" ] || continue # shellcheck disable=SC2086 # metadata is exactly git ls-tree's mode/type/object tuple. read -r mode object_type object_hash <<<"$metadata" + # Git submodule pointers (gitlinks) list as mode 160000 / type commit in + # the recursive tree. They carry no scannable blob content in this + # repository (the submodule's files live in a separate repository), so + # skip them here exactly as the changed-file scope path does, instead of + # failing closed on a legitimately non-blob tree entry. + if [ "$mode" = "160000" ] || [ "$object_type" = "commit" ]; then + echo "INFO: pull request head tree entry is a git submodule pointer; excluding content from PR-scoped Strix input: $relative_path" >&2 + continue + fi if [ "$object_type" != "blob" ]; then echo "ERROR: pull request head tree entry is not a blob; failing closed: $relative_path" >&2 return 2 diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index c0535357..a7d36b41 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -7580,6 +7580,124 @@ EOF rm -rf "$tmp_dir" } +run_full_head_scope_skips_gitlink_case() { + # Regression for the full PR-head blob scope path + # (build_pull_request_head_tree_scope_dir): when a PR triggers full-head + # context (e.g. a Dockerfile change) in a repository that contains a git + # submodule, the gitlink tree entry (mode 160000 / type commit) must be + # skipped during full-tree materialization, not treated as a non-blob + # entry that fails the scope closed. Without the skip, every + # submodule-bearing repository fails Strix on any Dockerfile/compose PR. + local tmp_dir + tmp_dir="$(mktemp -d)" + local bin_dir="$tmp_dir/bin" + local repo_root_dir="$tmp_dir/repo" + mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" + cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" + cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" + + local fake_strix="$bin_dir/strix" + local output_log="$tmp_dir/output.log" + local strix_llm_file="$tmp_dir/strix_llm.txt" + local llm_api_key_file="$tmp_dir/llm_api_key.txt" + # The full-head scope must materialize the changed Dockerfile and the + # unchanged docs context, and must never materialize the gitlink as a path. + cat >"$fake_strix" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +target_path="" +while [ "$#" -gt 0 ]; do + if [ "$1" = "-t" ] && [ "$#" -ge 2 ]; then + target_path="$2" + break + fi + shift +done +dockerfile="$target_path/Dockerfile" +if [ ! -f "$dockerfile" ] || ! grep -Fq -- 'FROM python:3.12-slim AS head' "$dockerfile"; then + echo "Error: changed Dockerfile missing head content" >&2 + exit 61 +fi +context_file="$target_path/docs/full-scope-context.md" +if [ ! -f "$context_file" ] || ! grep -Fq -- 'HEAD_FULL_SCOPE_CONTEXT_SHOULD_BE_SCANNED' "$context_file"; then + echo "Error: full PR head scoped context missing" >&2 + exit 65 +fi +if [ -e "$target_path/vendor/newsdom-api" ]; then + echo "Error: gitlink must not be materialized as a path" >&2 + exit 69 +fi +echo "scan ok with PR head content" +EOF + chmod +x "$fake_strix" + printf '%s' 'gemini/test-model' >"$strix_llm_file" + printf '%s' 'dummy' >"$llm_api_key_file" + + ( + cd "$repo_root_dir" + git init -q + git config user.name 'Strix Test' + git config user.email 'strix-test@example.invalid' + echo 'seed' >README.md + mkdir -p docs + printf '%s\n' 'BASE_FULL_SCOPE_CONTEXT_SHOULD_NOT_BE_SCANNED' >docs/full-scope-context.md + printf '%s\n' 'FROM python:3.12-slim AS base' >Dockerfile + git add . + git commit -qm 'base commit' + ) + local seed_sha + seed_sha="$(git -C "$repo_root_dir" rev-parse HEAD)" + # Add the SAME unchanged gitlink to both base and head, so the regression + # proves an *unchanged* submodule pointer is skipped in the full tree. + git -C "$repo_root_dir" update-index --add --cacheinfo "160000,$seed_sha,vendor/newsdom-api" + git -C "$repo_root_dir" commit -qm 'add gitlink to base' + local base_sha + base_sha="$(git -C "$repo_root_dir" rev-parse HEAD)" + ( + cd "$repo_root_dir" + printf '%s\n' 'HEAD_FULL_SCOPE_CONTEXT_SHOULD_BE_SCANNED' >docs/full-scope-context.md + printf '%s\n' 'FROM python:3.12-slim AS head' >Dockerfile + # Stage only the changed files. `git add .` would stage removal of the + # not-checked-out gitlink and drop it from the head tree, so the full-tree + # materialization would never see the submodule pointer this case exists + # to exercise. + git add docs/full-scope-context.md Dockerfile + git commit -qm 'head commit changes Dockerfile' + ) + local head_sha + head_sha="$(git -C "$repo_root_dir" rev-parse HEAD)" + git -C "$repo_root_dir" checkout -q "$base_sha" + + set +e + ( + cd "$repo_root_dir" + env -u GITHUB_EVENT_PATH \ + PATH="$bin_dir:$PATH" \ + STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_INPUT_FILE_ROOT="$tmp_dir" \ + GITHUB_EVENT_NAME="pull_request_target" \ + PR_NUMBER="123" \ + PR_BASE_SHA="$base_sha" \ + PR_HEAD_SHA="$head_sha" \ + STRIX_TEST_CHANGED_FILES_OVERRIDE="Dockerfile" \ + STRIX_DISABLE_PR_SCOPING="0" \ + STRIX_LLM_FILE="$strix_llm_file" \ + LLM_API_KEY_FILE="$llm_api_key_file" \ + STRIX_TARGET_PATH="." \ + STRIX_REPORTS_DIR="$repo_root_dir/strix_runs" \ + bash "./scripts/ci/strix_quick_gate.sh" >"$output_log" 2>&1 + ) + local rc=$? + set -e + + assert_equals "0" "$rc" "full-head-scope gitlink skip exits successfully" + assert_file_contains "$output_log" "scan ok with PR head content" "full-head-scope gitlink skip scans head content" + assert_file_contains "$output_log" "git submodule pointer; excluding content from PR-scoped Strix input: vendor/newsdom-api" "full-head-scope gitlink skip reason is visible" + + rm -rf "$tmp_dir" +} + run_pull_request_target_rejects_unsafe_changed_path_case() { local case_name="$1" local changed_file="$2" @@ -8809,6 +8927,8 @@ run_pull_request_target_irregular_head_entry_fails_closed_case \ run_pull_request_target_gitlink_is_explicitly_skipped_case +run_full_head_scope_skips_gitlink_case + run_pull_request_target_aborts_on_pr_head_blob_failure_case \ "pull-request-target-modified-file-pr-head-tree-lookup-failure" \ "src/existing.py" \