Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .github/workflows/opencode-review-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4342,6 +4342,10 @@ jobs:
OPENCODE_NVIDIA_NIM_RUN_TIMEOUT_SECONDS: "180"
OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS: "900"
OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "3600"
# Anonymous free candidates share fifteen minutes total. A quota-starved
# catalog therefore cannot consume the entire 195-minute retry budget
# before keyed Terra/OpenAI/OpenRouter/GitHub Models fallbacks run.
OPENCODE_FREE_TOTAL_BUDGET_SECONDS: "900"
# This installation currently reports a 4k request-body limit for
# GitHub Models GPT-5 endpoints even though the public catalog is
# larger. Keep the exact runtime failure visible without spending a
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/scheduled-security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ jobs:
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- name: Perform CodeQL Analysis
continue-on-error: true
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
with:
category: "/language:${{ matrix.language }}-scheduled"

Expand Down
341 changes: 171 additions & 170 deletions requirements-strix-ci-hashes.txt

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion requirements-strix-ci.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
strix-agent==1.0.4
google-cloud-aiplatform==1.133.0
protobuf<7.0.0
cryptography==49.0.0
cryptography>=50.0.0 # CVE-2026-69247 (pkcs7 decrypt length disclosure)
python-multipart==0.0.32
pyasn1==0.6.4
aiohttp>=3.14.3 # CVE-2026-59881, CVE-2026-69243 (HTTP request smuggling), CVE-2026-69244
37 changes: 26 additions & 11 deletions scripts/ci/install_base_python_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@
re.IGNORECASE,
),
re.compile(r"requires a different Python", re.IGNORECASE),
# A base lock can pin a version that has since been yanked or that offers no
# wheel for the pinned coverage-image interpreter. pip proves the index was
# reachable by listing the versions it *did* find, so this is an
# interpreter/availability incompatibility (defer to the later coverage run),
# not a registry outage. The "(from versions: none)" shape — an empty or
# unreachable index — is deliberately excluded and stays fatal.
re.compile(
r"Could not find a version that satisfies the requirement[^\n]*"
r"\(from versions:(?! none\))",
re.IGNORECASE,
),
)
Runner = Callable[..., subprocess.CompletedProcess[str]]

Expand Down Expand Up @@ -150,12 +161,16 @@ def _is_deferable_preflight_failure(output: str) -> bool:
"""Return whether a failed candidate may be grouped or safely skipped.

A hash-bearing supplement can fail pip's independent-closure check because a
transitive pin/hash lives in a sibling lock, and a base lock can explicitly
reject the pinned coverage-image interpreter. Those states are safe to
recover through a same-directory group or defer to the later networkless
coverage run. Hash mismatches, resolver crashes, empty diagnostics, and
registry/network failures remain fatal so a broken trusted build cannot be
mistaken for an optional lock.
transitive pin/hash lives in a sibling lock, a base lock can explicitly
reject the pinned coverage-image interpreter, and a base lock can pin a
version the reachable index no longer offers for that interpreter (yanked or
no matching wheel). Those states are safe to recover through a same-directory
group or defer to the later networkless coverage run. Hash mismatches,
resolver crashes, empty diagnostics, and registry/network failures — including
the "(from versions: none)" empty/unreachable-index shape — remain fatal so a
broken trusted build cannot be mistaken for an optional lock. Deferred paths
retain a warning and bounded pip diagnostics so the incompatibility stays
visible without blocking unrelated coverage evidence.
"""
return bool(output.strip()) and any(
pattern.search(output) for pattern in DEFERABLE_PREFLIGHT_FAILURES
Expand All @@ -171,8 +186,9 @@ def _report_fatal_preflight_failure(
"""Publish one bounded, source-aware fatal preflight failure."""
print(
"::error::Trusted base Python lock preflight failed for "
f"{entry_label}; only incomplete hash closures or explicit Python "
"interpreter incompatibility may be deferred.",
f"{entry_label}; only incomplete hash closures, explicit Python "
"interpreter incompatibility, or a reachable-index version that is no "
"longer available for the coverage interpreter may be deferred.",
file=stderr,
)
failure_output = _bounded_failure_output(output)
Expand Down Expand Up @@ -280,9 +296,8 @@ def install_materialized_locks(
skipped += 1
print(
"::warning::Skipping trusted base Python requirement candidate "
f"{entry.source}: hash-bearing content is not an independently "
"installable dependency closure and no same-directory lock group "
"completed it.",
f"{entry.source}: it could not be installed independently for the "
"coverage interpreter and no same-directory lock group completed it.",
file=stderr,
)
failure_output = _bounded_failure_output(
Expand Down
12 changes: 8 additions & 4 deletions scripts/ci/materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@

def _is_candidate_lock_name(name: str) -> bool:
"""Return whether a file name is a possible pip requirements lock."""
return name == "requirements.lock" or (
fnmatch.fnmatch(name, "requirements*.txt")
and not fnmatch.fnmatch(name, "requirements-*-ci-hashes.txt")
return (
name == "requirements.lock"
or fnmatch.fnmatch(name, "requirements-*.lock")
or (
fnmatch.fnmatch(name, "requirements*.txt")
and not fnmatch.fnmatch(name, "requirements-*-ci-hashes.txt")
)
)


Expand All @@ -49,7 +53,7 @@ def _is_hash_pinned(content: bytes) -> bool:
"""Return whether content carries hash pins and is safe to preflight.

Discovery is content-based rather than name-based so hash-pinned locks in any
location (a service subdirectory, ``requirements-dev.txt``,
location (a service subdirectory, ``requirements-dev.lock``,
``requirements-test.txt``) can be considered for offline coverage, while an
unpinned or PR-mutable requirements file is still excluded from the networked
build context. Hash syntax cannot prove that a file includes every transitive
Expand Down
59 changes: 53 additions & 6 deletions scripts/ci/run_opencode_review_model_pool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,13 @@ is_nvidia_nim_candidate() {
esac
}

is_opencode_free_candidate() {
case "$1" in
opencode-free/*) return 0 ;;
*) return 1 ;;
esac
}

is_schema_repair_candidate() {
case "$1" in
nvidia-nim/* | opencode-free/*) return 0 ;;
Expand Down Expand Up @@ -548,7 +555,8 @@ main() {
local changed_file_count small_file_threshold medium_file_threshold
local invalid_control_cap max_total_attempts total_attempts alive_candidates
local nim_budget_seconds nim_elapsed_seconds nim_remaining_seconds
local nim_attempt_started nim_attempt_elapsed non_nim_candidate_count
local free_budget_seconds free_elapsed_seconds free_remaining_seconds
local attempt_started attempt_elapsed non_nim_candidate_count keyed_candidate_count
local -A dead_candidate_reasons invalid_control_counts
local -a model_candidates

Expand Down Expand Up @@ -615,11 +623,18 @@ main() {
fi
nim_budget_seconds="$(env_integer_or_default OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS 900)"
nim_elapsed_seconds=0
free_budget_seconds="$(env_integer_or_default OPENCODE_FREE_TOTAL_BUDGET_SECONDS 900)"
free_elapsed_seconds=0
non_nim_candidate_count=0
keyed_candidate_count=0
for model_candidate in "${model_candidates[@]}"; do
if ! is_nvidia_nim_candidate "$model_candidate"; then
non_nim_candidate_count=$((non_nim_candidate_count + 1))
fi
if ! is_nvidia_nim_candidate "$model_candidate" &&
! is_opencode_free_candidate "$model_candidate"; then
keyed_candidate_count=$((keyed_candidate_count + 1))
fi
done
if [ "$non_nim_candidate_count" -gt 0 ] &&
[ "$budget_seconds" -gt 0 ] &&
Expand All @@ -628,8 +643,15 @@ main() {
printf 'OpenCode NVIDIA NIM combined runtime budget was capped at %ss so %s non-NIM fallback candidate(s) retain retry budget.\n' \
"$nim_budget_seconds" "$non_nim_candidate_count"
fi
printf 'Configured OpenCode model pool: candidates=%s attempts=%s per-model-timeout=%ss retry-budget=%ss max-cycles=%s NVIDIA-NIM-combined-budget=%ss.\n' \
"${#model_candidates[@]}" "$attempts" "$original_run_timeout" "$budget_seconds" "$max_cycles" "$nim_budget_seconds"
if [ "$keyed_candidate_count" -gt 0 ] &&
[ "$budget_seconds" -gt 0 ] &&
[ "$free_budget_seconds" -ge "$budget_seconds" ]; then
free_budget_seconds=$((budget_seconds / 2))
printf 'OpenCode anonymous-free combined runtime budget was capped at %ss so %s keyed fallback candidate(s) retain retry budget.\n' \
"$free_budget_seconds" "$keyed_candidate_count"
fi
printf 'Configured OpenCode model pool: candidates=%s attempts=%s per-model-timeout=%ss retry-budget=%ss max-cycles=%s NVIDIA-NIM-combined-budget=%ss anonymous-free-combined-budget=%ss.\n' \
"${#model_candidates[@]}" "$attempts" "$original_run_timeout" "$budget_seconds" "$max_cycles" "$nim_budget_seconds" "$free_budget_seconds"

cycle=1
while :; do
Expand All @@ -649,6 +671,12 @@ main() {
"$model_candidate" "$nim_budget_seconds"
continue
fi
if is_opencode_free_candidate "$model_candidate" &&
[ "$free_elapsed_seconds" -ge "$free_budget_seconds" ]; then
printf 'Skipping OpenCode %s because the anonymous-free combined runtime budget of %ss is exhausted; preserving the remaining retry budget for keyed fallback candidates.\n' \
"$model_candidate" "$free_budget_seconds"
continue
fi
assert_reasoning_effort_for_candidate "$model_candidate"
safe_model="${model_candidate//[\/:]/-}"
prompt_file="${RUNNER_TEMP}/opencode-review-${safe_model}-prompt.md"
Expand All @@ -673,6 +701,12 @@ main() {
"$model_candidate" "$nim_budget_seconds"
break
fi
if is_opencode_free_candidate "$model_candidate" &&
[ "$free_elapsed_seconds" -ge "$free_budget_seconds" ]; then
printf 'Stopping OpenCode %s retries because the anonymous-free combined runtime budget of %ss is exhausted.\n' \
"$model_candidate" "$free_budget_seconds"
break
fi
if [ "$deadline" -gt 0 ] && [ "$now" -ge "$deadline" ]; then
printf 'OpenCode model pool retry deadline elapsed before %s attempt %s/%s.\n' "$model_candidate" "$attempt" "$effective_attempts"
if finish_pool_without_model; then
Expand Down Expand Up @@ -704,6 +738,14 @@ main() {
OPENCODE_RUN_TIMEOUT_SECONDS="$nim_remaining_seconds"
fi
fi
if is_opencode_free_candidate "$model_candidate"; then
free_remaining_seconds=$((free_budget_seconds - free_elapsed_seconds))
if [ "$OPENCODE_RUN_TIMEOUT_SECONDS" -gt "$free_remaining_seconds" ]; then
printf 'OpenCode %s combined anonymous-free budget cap selected %ss instead of %ss so keyed fallback candidates retain retry budget.\n' \
"$model_candidate" "$free_remaining_seconds" "$OPENCODE_RUN_TIMEOUT_SECONDS"
OPENCODE_RUN_TIMEOUT_SECONDS="$free_remaining_seconds"
fi
fi
uncapped_run_timeout="$OPENCODE_RUN_TIMEOUT_SECONDS"
OPENCODE_RUN_TIMEOUT_SECONDS="$(cap_model_run_timeout "$model_candidate" "$OPENCODE_RUN_TIMEOUT_SECONDS")"
if [ "$OPENCODE_RUN_TIMEOUT_SECONDS" -lt "$uncapped_run_timeout" ]; then
Expand All @@ -717,7 +759,7 @@ main() {
agent="$OPENCODE_FIRST_ATTEMPT_AGENT"
fi
run_status=0
nim_attempt_started="$SECONDS"
attempt_started="$SECONDS"
if run_one_model_attempt "$model_candidate" "$attempt" "$effective_attempts" "$agent" "$prompt_file" "$candidate_output_file" "$opencode_json_file" "$opencode_export_file"; then
cp "$candidate_output_file" "$OPENCODE_OUTPUT_FILE"
record_review_model "$model_candidate"
Expand All @@ -726,12 +768,17 @@ main() {
else
run_status=$?
fi
attempt_elapsed=$((SECONDS - attempt_started))
if is_nvidia_nim_candidate "$model_candidate"; then
nim_attempt_elapsed=$((SECONDS - nim_attempt_started))
nim_elapsed_seconds=$((nim_elapsed_seconds + nim_attempt_elapsed))
nim_elapsed_seconds=$((nim_elapsed_seconds + attempt_elapsed))
printf 'OpenCode NVIDIA NIM combined runtime used %ss/%ss after %s attempt %s/%s.\n' \
"$nim_elapsed_seconds" "$nim_budget_seconds" "$model_candidate" "$attempt" "$effective_attempts"
fi
if is_opencode_free_candidate "$model_candidate"; then
free_elapsed_seconds=$((free_elapsed_seconds + attempt_elapsed))
printf 'OpenCode anonymous-free combined runtime used %ss/%ss after %s attempt %s/%s.\n' \
"$free_elapsed_seconds" "$free_budget_seconds" "$model_candidate" "$attempt" "$effective_attempts"
fi
if [ "$run_status" -ne 3 ] && is_credit_exhausted_failure "$opencode_json_file" "${opencode_json_file}.stderr"; then
dead_candidate_reasons[$model_candidate]="provider credits exhausted (HTTP 402 / payment required)"
printf 'OpenCode %s provider credits are exhausted; marking this candidate failed for the rest of the run so retries cannot accrue further spend.\n' "$model_candidate"
Expand Down
4 changes: 3 additions & 1 deletion scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ assert_strix_workflow_pr_trigger_hardened() {
assert_equals "1" "$status_token_count" "strix workflow defines GITHUB_STATUS_TOKEN once so GitHub can parse repository_dispatch"
assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "strix workflow must not hard-code repository-specific PR bypasses"
assert_file_contains "$workflow_file" "models: read" "strix workflow grants only the GitHub Models read permission needed for Strix"
assert_file_contains "$workflow_file" "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6" "strix workflow pins actions/setup-python"
assert_file_contains "$workflow_file" "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0" "strix workflow pins actions/setup-python"
assert_file_contains "$workflow_file" 'python-version: "3.13"' "strix workflow runs Python steps on Python 3.13"
assert_file_contains "$workflow_file" "Resolve trusted Strix source ref" "strix workflow resolves the central trusted Strix source ref"
assert_file_contains "$workflow_file" "toJSON(job)" "strix workflow derives the trusted source from the job workflow context"
Expand Down Expand Up @@ -734,11 +734,13 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'continue-on-error: true' "opencode approval gate still runs after model-pool failure to publish a reason"
assert_file_contains "$workflow_file" 'OPENCODE_RUN_TIMEOUT_SECONDS: "5400"' "opencode primary review preserves legitimate full-hour provider sessions"
assert_file_contains "$workflow_file" 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "3600"' "opencode free-tier failover timeout is hour-class (~3600s)"
assert_file_contains "$workflow_file" 'OPENCODE_FREE_TOTAL_BUDGET_SECONDS: "900"' "opencode anonymous free candidates share a fifteen-minute combined queue budget"
assert_file_contains "$workflow_file" 'OPENCODE_NVIDIA_NIM_RUN_TIMEOUT_SECONDS: "180"' "opencode NVIDIA NIM candidates have a short per-candidate failover timeout"
assert_file_contains "$workflow_file" 'OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS: "900"' "opencode NVIDIA NIM candidates share a bounded combined runtime budget"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_RUN_TIMEOUT_SECONDS:-3600' "opencode pool defaults primary run timeout to hour-class (~3600s) for large repos"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_DYNAMIC_RUN_TIMEOUT_CAP_SECONDS 3600' "opencode pool dynamic timeout cap defaults to hour-class (~3600s)"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS 3600' "opencode free-tier failover timeout is hour-class (~3600s)"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_FREE_TOTAL_BUDGET_SECONDS 900' "opencode pool defaults anonymous free candidates to a fifteen-minute combined queue budget"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_NVIDIA_NIM_RUN_TIMEOUT_SECONDS 180' "opencode NVIDIA NIM candidate runtime cap defaults to three minutes"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS 900' "opencode NVIDIA NIM combined runtime cap defaults to fifteen minutes"

Expand Down
68 changes: 68 additions & 0 deletions tests/test_install_base_python_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,71 @@ def fake_install(root: pathlib.Path) -> int:

assert installer.main(["--requirements-root", str(tmp_path)]) == 7
assert seen == [tmp_path]


def test_reachable_index_missing_pinned_version_is_visible_and_nonfatal(
tmp_path,
) -> None:
"""A pin the reachable index no longer offers (yanked / no wheel for the
coverage interpreter) defers to coverage execution instead of aborting."""
write_candidate(
tmp_path,
generated_file="requirements-000.txt",
source="fuzz/requirements-atheris.txt",
)

def fake_runner(command: list[str], **kwargs):
return subprocess.CompletedProcess(
command,
1,
stdout=(
"ERROR: Could not find a version that satisfies the requirement "
"atheris==3.0.0 (from versions: 3.1.0)\n"
"ERROR: No matching distribution found for atheris==3.0.0"
),
)

stdout = io.StringIO()
stderr = io.StringIO()
result = installer.install_materialized_locks(
tmp_path,
runner=fake_runner,
stdout=stdout,
stderr=stderr,
)

assert result == 0
assert "candidates=1 installed=0 skipped=1" in stdout.getvalue()
assert "Could not find a version that satisfies the requirement" in stderr.getvalue()

Comment thread
seonghobae marked this conversation as resolved.

def test_unreachable_index_from_versions_none_stays_fatal(tmp_path) -> None:
"""An empty/unreachable index ("(from versions: none)") is not deferrable:
it must remain fatal so a registry outage cannot masquerade as an optional
lock."""
write_candidate(
tmp_path,
generated_file="requirements-000.txt",
source="fuzz/requirements-atheris.txt",
)

def fake_runner(command: list[str], **kwargs):
return subprocess.CompletedProcess(
command,
1,
stdout=(
"ERROR: Could not find a version that satisfies the requirement "
"atheris==3.0.0 (from versions: none)\n"
"ERROR: No matching distribution found for atheris==3.0.0"
),
)

stderr = io.StringIO()
result = installer.install_materialized_locks(
tmp_path,
runner=fake_runner,
stderr=stderr,
)

assert result == 1
assert "preflight failed" in stderr.getvalue()
Loading
Loading