Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ebe5895
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€
seonghobae Jul 10, 2026
7826d1e
Merge branch 'main' into sentinel-prevent-ssrf-redirects-120892205133โ€ฆ
opencode-agent[bot] Jul 10, 2026
bb8938b
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€ ๋ฐ ์›Œํฌํ”Œ๋กœ์šฐ ๊ถŒโ€ฆ
seonghobae Jul 10, 2026
e554a85
Merge origin/main into SSRF hardening workflow
seonghobae Jul 10, 2026
e93a7bb
Merge origin/main after SSRF hardening landed
seonghobae Jul 10, 2026
fff4666
Merge remote-tracking branch 'origin/main' into codex/github-425-rebase
seonghobae Jul 10, 2026
c125a33
fix(opencode): extend review timing and stub evidence
seonghobae Jul 11, 2026
839d415
Merge branch 'main' into sentinel-prevent-ssrf-redirects-120892205133โ€ฆ
opencode-agent[bot] Jul 11, 2026
af754e9
test(opencode): cover implementation completeness scan
seonghobae Jul 11, 2026
3ec268f
Merge branch 'main' into sentinel-prevent-ssrf-redirects-120892205133โ€ฆ
opencode-agent[bot] Jul 11, 2026
d3f3338
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€
seonghobae Jul 11, 2026
b03fa33
chore: remove unused sandboxed e2e import
seonghobae Jul 11, 2026
2c7c997
fix(opencode): restore deep review budget and stub scan
seonghobae Jul 11, 2026
ef0a265
fix(opencode): keep completeness scan without queue drift
seonghobae Jul 11, 2026
7578214
fix(opencode): restore bounded model pool defaults
seonghobae Jul 11, 2026
8c3c0de
fix(opencode): keep deepseek review budget
seonghobae Jul 11, 2026
19490c5
fix: bound central review model pool stalls
seonghobae Jul 11, 2026
3681a82
fix(strix): restore scan job status write scope
seonghobae Jul 11, 2026
4025dc5
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€ ๋ฐ ์›Œํฌํ”Œ๋กœ์šฐ ๊ถŒโ€ฆ
seonghobae Jul 11, 2026
e7525fb
fix(opencode): restore deep review budget
seonghobae Jul 11, 2026
badbdd4
Merge remote-tracking branch 'origin/sentinel-prevent-ssrf-redirects-โ€ฆ
seonghobae Jul 11, 2026
ca27278
fix(opencode): bound failed-check evidence collection
seonghobae Jul 11, 2026
f22812e
Merge remote-tracking branch 'origin/main' into codex/github-425-rebase
seonghobae Jul 11, 2026
c7f39f7
Merge remote-tracking branch 'origin/sentinel-prevent-ssrf-redirects-โ€ฆ
seonghobae Jul 11, 2026
5a482c7
fix(strix): keep status permission read-only
seonghobae Jul 11, 2026
aa6f4e6
fix(security): avoid privileged workflow checkout patterns
seonghobae Jul 11, 2026
513ff6d
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€
seonghobae Jul 11, 2026
39234fc
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€
seonghobae Jul 11, 2026
17fda8f
Merge remote-tracking branch 'origin/main' into pr-425-evidence-timeout
seonghobae Jul 11, 2026
322b07b
Merge remote-tracking branch 'origin/sentinel-prevent-ssrf-redirects-โ€ฆ
seonghobae Jul 11, 2026
9f76b3b
Merge remote-tracking branch 'origin/main' into pr-425-evidence-timeout
seonghobae Jul 11, 2026
126a851
fix(opencode): restore bounded failed-check evidence collection
seonghobae Jul 11, 2026
7bd590c
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€ ๋ฐ ์ตœ์‹  ๋ธŒ๋žœ์น˜ ๋ฐ˜์˜
seonghobae Jul 11, 2026
0ab0149
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€ ๋ฐ ์ตœ์‹  ๋ธŒ๋žœ์น˜ ๋ฐ˜์˜
seonghobae Jul 11, 2026
1716e89
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€ ๋ฐ ์ตœ์‹  ๋ฉ”์ธ ๋ธŒโ€ฆ
seonghobae Jul 11, 2026
1e03b77
fix(opencode): restore deep review timeouts and implementation scan
seonghobae Jul 11, 2026
40d2734
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€ ๋ฐ ์ตœ์‹  ๋ธŒ๋žœ์น˜ ๋ณ‘ํ•ฉ
seonghobae Jul 11, 2026
93f6629
fix(opencode): restore deep review timeouts and implementation scan
seonghobae Jul 11, 2026
c4347d2
Merge main into OpenCode SSRF hardening
seonghobae Jul 11, 2026
19624ac
Merge review-gate materialization hardening
seonghobae Jul 11, 2026
0d31104
Merge origin/main into PR 425 and restore workflow contracts
seonghobae Jul 11, 2026
0c32797
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€ ๋ฐ ์ตœ์‹  ๋ธŒ๋žœ์น˜ ๋ณ‘ํ•ฉ
seonghobae Jul 11, 2026
369ca66
Merge current-head OpenCode evidence hardening
seonghobae Jul 11, 2026
3375f94
Merge remote-tracking branch 'origin/sentinel-prevent-ssrf-redirects-โ€ฆ
seonghobae Jul 11, 2026
0bacc50
Revert "Merge remote-tracking branch 'origin/sentinel-prevent-ssrf-reโ€ฆ
seonghobae Jul 11, 2026
3d43036
Avoid real-looking PAT fixtures in scheduler tests
seonghobae Jul 11, 2026
570550c
Merge remote PR workflow hardening
seonghobae Jul 11, 2026
8c7989c
Fix scheduler secret-scan fixtures
seonghobae Jul 11, 2026
2e21570
Merge latest main into PR 425
seonghobae Jul 11, 2026
db00c7f
Merge updated PR branch fixtures
seonghobae Jul 11, 2026
081ac9b
Merge latest OpenCode retry hardening
seonghobae Jul 11, 2026
b7ad6a5
Merge current-head Strix queue hardening
seonghobae Jul 11, 2026
32df235
๋ณด์•ˆ: `urllib.request.urlopen`์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™”๋ฅผ ํ†ตํ•œ SSRF ์ทจ์•ฝ์  ๋ฐฉ์ง€ ๋ฐ ์ตœ์‹  ๋ฉ”์ธ ๋ธŒโ€ฆ
seonghobae Jul 11, 2026
9a03b7d
Merge remote-tracking branch 'origin/sentinel-prevent-ssrf-redirects-โ€ฆ
seonghobae Jul 11, 2026
cd827b1
Merge remote-tracking branch 'origin/main' into pr-425-evidence-timeout
seonghobae Jul 11, 2026
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
13 changes: 6 additions & 7 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2957,12 +2957,11 @@ jobs:
# the SAME model 5x let a rate-limited/hung leader consume the whole
# step, so the pool never reached a healthy fallback model.
OPENCODE_MODEL_ATTEMPTS: "1"
# Three minutes per model is enough for healthy providers to emit the
# required control block and short enough to avoid queue pileups when a
# provider stalls silently.
OPENCODE_RUN_TIMEOUT_SECONDS: "180"
OPENCODE_EXPORT_TIMEOUT_SECONDS: "60"
OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "540"
# 90 minutes per model gives deep tool-using reviews room to finish;
# stale providers still yield within the bounded retry budget.
OPENCODE_RUN_TIMEOUT_SECONDS: "5400"
OPENCODE_EXPORT_TIMEOUT_SECONDS: "120"
OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "18000"
# Keep cycling through the high-sensitivity catalog until a model
# emits a valid control block or the bounded retry/step timeout fires.
# A single invalid no-control response is provider/tool state, not a
Expand Down Expand Up @@ -3364,7 +3363,7 @@ jobs:
CHECK_LOOKUP_RETRY_SLEEP_SECONDS: "5"
REVIEW_PUBLISH_RETRY_ATTEMPTS: "3"
REVIEW_PUBLISH_RETRY_SLEEP_SECONDS: "20"
OPENCODE_MODEL_CANDIDATES: "openai/gpt-5 github-models/openai/gpt-5 github-models/openai/o3 github-models/deepseek/deepseek-r1-0528"
OPENCODE_MODEL_CANDIDATES: "openai/gpt-5 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1 github-models/deepseek/deepseek-v3-0324 github-models/mistral-ai/mistral-medium-2505 github-models/meta/llama-4-maverick-17b-128e-instruct-fp8 github-models/meta/llama-4-scout-17b-16e-instruct"
OPENCODE_MODEL_ATTEMPTS: "1"
OPENCODE_RUN_TIMEOUT_SECONDS: "120"
OPENCODE_EXPORT_TIMEOUT_SECONDS: "60"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-review-merge-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ jobs:
SCHEDULER_READ_TOKEN: ${{ github.token }}
SCHEDULER_MUTATION_TOKEN_SOURCE: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' && 'PR_REVIEW_MERGE_TOKEN' || secrets.OPENCODE_APPROVE_TOKEN != '' && 'OPENCODE_APPROVE_TOKEN' || steps.scheduler_app_token.outputs.available == 'true' && 'opencode-app' || 'github-token' }}
SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY: ContextualWisdomLab/.github
SCHEDULER_REQUIRED_WORKFLOW_REF: main
SCHEDULER_REQUIRED_WORKFLOW_REF: ${{ steps.trusted_source.outputs.ref }}
SCHEDULER_ALLOW_CROSS_REPO_WORKFLOW_DISPATCH: ${{ (secrets.PR_REVIEW_MERGE_TOKEN != '' || secrets.OPENCODE_APPROVE_TOKEN != '') && 'true' || 'false' }}
run: |
set -euo pipefail
Expand Down
17 changes: 9 additions & 8 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,15 @@ on:
concurrency:
# Include the event name so manual workflow_dispatch evidence cannot cancel
# the required pull_request_target Strix context that branch protection reads.
# Within each event class, PR-number scope still keeps only the current head.
# PR-number scope keeps the queue on the current HEAD within each event class.
group: >-
strix-${{ github.event_name }}-${{ github.event.inputs.target_repository || github.event.pull_request.base.repo.full_name || github.repository }}-${{
github.event_name == 'pull_request_target' && format('pr-{0}', github.event.pull_request.number) ||
github.event_name == 'workflow_dispatch' && github.event.inputs.pr_number != '' && format('pr-{0}', github.event.inputs.pr_number) || github.ref }}
# PR-number scope keeps the queue on the current HEAD: a synchronize event
# cancels older Strix evidence for the same PR before it burns reviewer time.
cancel-in-progress: true

# Scorecard Token-Permissions (alert #43): keep the workflow-level token
# read-only and grant status publication only through exchanged app/secret
# tokens. GITHUB_TOKEN can read status evidence but must not write it.
# read-only and scope same-repo status publication to the Strix scan job.
permissions:
actions: read
contents: read
Expand All @@ -121,14 +118,14 @@ jobs:
timeout-minutes: 45
runs-on: ubuntu-latest
# Least-privilege token scoped to this job (Scorecard alert #43): the scan
# exchanges an OIDC token (id-token) and reads commit status evidence here;
# publication uses exchanged app/secret tokens below.
# exchanges an OIDC token (id-token) and publishes same-repo status evidence
# from the scan job only.
permissions:
actions: read
contents: read
id-token: write
models: read
statuses: read
statuses: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
Expand Down Expand Up @@ -738,6 +735,7 @@ jobs:
TARGET_APP_STATUS_TOKEN: ${{ steps.target_app_token.outputs.token || '' }}
PR_REVIEW_MERGE_STATUS_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || '' }}
OPENCODE_APPROVE_STATUS_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || '' }}
GITHUB_STATUS_TOKEN: ${{ github.token }}
TARGET_REPOSITORY: ${{ github.event.inputs.target_repository || github.repository }}
PR_HEAD_SHA: ${{ github.event.inputs.pr_head_sha }}
STRIX_RESULT: ${{ job.status }}
Expand Down Expand Up @@ -800,6 +798,9 @@ jobs:
if post_strix_status "opencode-approve-token" "$OPENCODE_APPROVE_STATUS_TOKEN"; then
exit 0
fi
if post_strix_status "github-token" "$GITHUB_STATUS_TOKEN"; then
exit 0
fi
echo "::warning::Could not publish manual Strix status from scan job; keeping scan evidence result authoritative in the workflow run."

publish-manual-pr-evidence-status:
Expand Down
1 change: 1 addition & 0 deletions scripts/ci/sandboxed_web_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import sys
import tempfile
import time
import typing
import urllib.error
import urllib.request
from collections.abc import Sequence
Expand Down
4 changes: 4 additions & 0 deletions scripts/ci/strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3820,6 +3820,10 @@ run_current_target_scan() {
fi
fi

if fail_reported_vulnerabilities_before_fallback_success; then
return 1
fi

case "$PR_FINDINGS_DECISION" in
block_changed | block_unmapped | block_manifest_unverified)
return 1
Expand Down
28 changes: 3 additions & 25 deletions scripts/ci/strix_required_workflow_smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ if any(line.strip() == "statuses: write" for line in top_level_permissions):
print("Strix workflow top-level GITHUB_TOKEN must not grant statuses: write.", file=sys.stderr)
raise SystemExit(1)

status_read_jobs: list[str] = []
status_write_jobs: list[str] = []
current_job = ""
inside_permissions = False
Expand All @@ -99,37 +98,16 @@ for line in lines[jobs_index + 1 :]:
if not inside_permissions:
continue
if line.startswith(" "):
if line.strip() == "statuses: read":
status_read_jobs.append(current_job)
if line.strip() == "statuses: write":
status_write_jobs.append(current_job)
continue
if line.strip():
inside_permissions = False

if status_read_jobs and status_read_jobs != ["strix"]:
if status_write_jobs != ["strix"]:
print(
"Strix workflow must scope statuses: read only to the strix scan job when read is used; found: "
+ ", ".join(status_read_jobs),
file=sys.stderr,
)
raise SystemExit(1)
if status_write_jobs and status_write_jobs != ["strix"]:
print(
"Strix workflow must scope statuses: write only to the strix scan job when write is used; found: "
+ ", ".join(status_write_jobs),
file=sys.stderr,
)
raise SystemExit(1)
if status_read_jobs and status_write_jobs:
print(
"Strix workflow must choose statuses: read or statuses: write for the strix scan job, not both.",
file=sys.stderr,
)
raise SystemExit(1)
if not status_read_jobs and not status_write_jobs:
print(
"Strix workflow must scope statuses: read or statuses: write to the strix scan job; found: none",
"Strix workflow must scope statuses: write only to the strix scan job; found: "
+ (", ".join(status_write_jobs) if status_write_jobs else "none"),
file=sys.stderr,
)
raise SystemExit(1)
Expand Down
14 changes: 9 additions & 5 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'install_deps <- c("Depends", "Imports", "LinkingTo")' "opencode R coverage avoids installing oversized suggested dependencies"
assert_file_contains "$workflow_file" 'read.dcf("DESCRIPTION")' "opencode R coverage installs target package dependencies from DESCRIPTION"
assert_file_contains "$workflow_file" "R package testthat suite" "opencode R package coverage requires package testthat evidence"
assert_file_contains "$workflow_file" "R coverage tooling install unavailable or exceeded the runner time budget; deferring to required peer R CMD check evidence." "opencode R coverage defers runner package-install failures to required peer R checks"
assert_file_contains "$workflow_file" "R coverage tooling install did not complete or exceeded 780 seconds" "opencode R coverage defers runner package-install failures to required peer R checks"
assert_file_contains "$workflow_file" "testthat unavailable in coverage runner; deferring to required peer R CMD check evidence." "opencode R package tests defer only when testthat cannot be installed in the coverage runner"
assert_file_contains "$workflow_file" "covr package_coverage unavailable after package tests; treating missing-line report as advisory." "opencode R package coverage does not block on covr installation reproduction after tests pass"
assert_file_contains "$workflow_file" "R coverage tooling packages unavailable after install" "opencode R coverage verifies covr/testthat are loadable after installation"
Expand Down Expand Up @@ -543,9 +543,10 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'timeout-minutes: 40' "opencode evidence preparation fails closed before it ties up the review queue"
assert_file_contains "$workflow_file" 'timeout-minutes: 45' "opencode model pool leaves approval-gate headroom while capping stalled model attempts"
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: "600"' "opencode primary review has a bounded per-model timeout before trying fallback models"
assert_file_contains "$workflow_file" 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "2400"' "opencode model pool exits before the job timeout so the approval gate can publish a reason"
assert_file_contains "$workflow_file" 'OPENCODE_POOL_MAX_CYCLES: "1"' "opencode model pool stops after one full candidate pass instead of looping to the job timeout"
assert_file_contains "$workflow_file" 'OPENCODE_RUN_TIMEOUT_SECONDS: "5400"' "opencode primary review has enough per-model time for deep tool-using review before trying fallback models"
assert_file_contains "$workflow_file" 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "18000"' "opencode model pool exits before the job timeout so the approval gate can publish a reason"
assert_file_not_contains "$workflow_file" "OPENCODE_POOL_MAX_CYCLES" "opencode model pool retries invalid control output until the bounded retry or step timeout fires"
assert_file_contains "$workflow_file" 'OPENCODE_POOL_CYCLE_SLEEP_SECONDS: "15"' "opencode model pool uses a bounded sleep between invalid-output retry cycles"
assert_file_contains "$workflow_file" "needs.coverage-evidence.result == 'success'" "opencode model pool only runs after coverage evidence passed"
assert_file_contains "$workflow_file" "id: opencode_review_model_pool" "opencode DeepSeek V3 fallback still runs after a primary model timeout or step failure when coverage evidence passed"
assert_file_contains "$workflow_file" "always()" "opencode fallback chain uses always() so failed model steps cannot skip every fallback"
Expand Down Expand Up @@ -657,10 +658,12 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_not_contains "$workflow_file" "no model produced a valid review control block" "opencode model-failure path no longer documents a final exhausted state"
assert_file_contains "$workflow_file" 'OPENCODE_MODEL_ATTEMPTS: "1"' "opencode primary and fallback paths avoid multi-attempt stalls on one model"
assert_file_contains "$workflow_file" 'OPENCODE_MODEL_ATTEMPTS: "1"' "opencode catalog fallback tries each model once before moving on"
assert_file_contains "$workflow_file" 'OPENCODE_RUN_TIMEOUT_SECONDS: "600"' "opencode catalog fallback has a bounded per-model review timeout before step timeout"
assert_file_contains "$workflow_file" 'OPENCODE_RUN_TIMEOUT_SECONDS: "5400"' "opencode catalog fallback has enough per-model time for deep tool-using review before step timeout"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" "OpenCode %s attempt %s/%s failed" "opencode catalog fallback records per-model retry failures"
assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" "exponential backoff" "opencode model retry paths use exponential backoff instead of fixed sleeps"
assert_file_contains "$workflow_file" "openai/gpt-5 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3" "opencode review tries native OpenAI before GitHub Models fallbacks"
assert_file_contains "$workflow_file" "github-models/openai/gpt-5-chat" "opencode review keeps GPT-5 chat fallback coverage after compact reasoning attempts"
assert_file_contains "$workflow_file" "github-models/openai/o3" "opencode review keeps o3 fallback coverage after compact reasoning attempts"
assert_file_contains "$workflow_file" "github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1 github-models/deepseek/deepseek-v3-0324" "opencode review keeps DeepSeek fallback coverage after OpenAI candidates"
assert_file_contains "$workflow_file" "coverage-source-tree:" "opencode workflow materializes coverage source before running PR-head tests"
assert_file_contains "$workflow_file" "coverage-evidence:" "opencode workflow measures coverage before review"
Expand Down Expand Up @@ -1089,6 +1092,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_not_contains "$workflow_file" "MODEL: github-models/gpt-4.1" "opencode review must not fall back to GPT-4.1"
assert_file_contains "$workflow_file" "github-models/openai/gpt-5-chat" "opencode review includes GitHub Models GPT-5 chat as a catalog fallback"
assert_file_not_contains "$workflow_file" "github-models/openai/gpt-4.1-mini" "opencode review does not fall back to GPT-4.1 mini review evidence"
assert_file_contains "$workflow_file" "github-models/openai/gpt-5" "opencode review includes GitHub Models GPT-5 as a catalog fallback"
assert_file_not_contains "$workflow_file" "github-models/openai/gpt-5-mini" "opencode review excludes GitHub Models GPT-5 mini from the high-sensitivity review pool"

assert_file_contains "$opencode_config" '"mcp"' "opencode config declares MCP servers"
Expand Down
10 changes: 6 additions & 4 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,9 +447,9 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
'github-models/deepseek/deepseek-r1-0528"'
) in workflow
assert 'OPENCODE_MODEL_ATTEMPTS: "1"' in workflow
assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "180"' in workflow
assert 'OPENCODE_EXPORT_TIMEOUT_SECONDS: "60"' in workflow
assert 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "540"' in workflow
assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "5400"' in workflow
assert 'OPENCODE_EXPORT_TIMEOUT_SECONDS: "120"' in workflow
assert 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "18000"' in workflow
assert "OPENCODE_POOL_MAX_CYCLES" not in workflow
assert 'OPENCODE_POOL_CYCLE_SLEEP_SECONDS: "15"' in workflow
assert 'OPENCODE_BACKOFF_MAX_SECONDS: "5"' in workflow
Expand Down Expand Up @@ -482,8 +482,10 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
assert (
'OPENCODE_MODEL_CANDIDATES: "openai/gpt-5 '
"github-models/openai/gpt-5 "
"github-models/openai/gpt-5-chat "
"github-models/openai/o3 "
'github-models/deepseek/deepseek-r1-0528"'
"github-models/deepseek/deepseek-r1-0528 "
'github-models/deepseek/deepseek-r1'
) in workflow
assert "${{ runner.temp }}/opencode-review-model-pool.md" in workflow
assert re.search(r'check-runs" \\\n\s+-f per_page=100 \\\n\s+--paginate \\\n\s+--slurp \|\n\s+jq -r "\$jq_filter"', workflow)
Expand Down
4 changes: 3 additions & 1 deletion tests/test_required_workflow_queue_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def test_pull_request_close_events_cancel_superseded_runs_without_heavy_jobs() -

strix_workflow = workflow_text("strix.yml")
assert "cancel-in-progress: true" in strix_workflow
assert "PR-number scope keeps the queue on the current HEAD" in strix_workflow


def test_close_empty_pr_metadata_lookup_retries_and_fails_open() -> None:
Expand All @@ -114,6 +115,7 @@ def test_close_empty_pr_metadata_lookup_retries_and_fails_open() -> None:
assert "exit 0" in workflow



def test_cancelled_review_workflow_runs_do_not_spawn_more_queue_work() -> None:
for filename in ("noema-review.yml", "pr-review-merge-scheduler.yml"):
workflow = workflow_text(filename)
Expand Down Expand Up @@ -175,7 +177,7 @@ def test_noema_workflow_run_without_pull_request_skips_before_token_exchange() -

assert "Noema review skipped: no pull request number is associated with this event." in workflow
assert "if: env.PR_NUMBER == ''" in workflow
assert workflow.count("if: env.PR_NUMBER != ''") >= 3
assert workflow.count("if: env.PR_NUMBER != ''") >= 4


def test_noema_and_scheduler_trusted_checkouts_use_static_main() -> None:
Expand Down
Loading