Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3366,7 +3366,7 @@ jobs:
CHECK_LOOKUP_RETRY_SLEEP_SECONDS: "5"
REVIEW_PUBLISH_RETRY_ATTEMPTS: "3"
REVIEW_PUBLISH_RETRY_SLEEP_SECONDS: "20"
OPENCODE_MODEL_CANDIDATES: "github-models/deepseek/deepseek-v3-0324 openai/gpt-5 github-models/openai/gpt-5 github-models/openai/o3 github-models/deepseek/deepseek-r1-0528"
OPENCODE_MODEL_CANDIDATES: "github-models/deepseek/deepseek-v3-0324 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"
OPENCODE_MODEL_ATTEMPTS: "1"
OPENCODE_RUN_TIMEOUT_SECONDS: "5400"
OPENCODE_EXPORT_TIMEOUT_SECONDS: "120"
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 @@ -338,7 +338,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
15 changes: 8 additions & 7 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,8 +118,8 @@ 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
Expand Down Expand Up @@ -739,6 +736,7 @@ jobs:
GITHUB_STATUS_TOKEN: ${{ (github.event.inputs.target_repository == '' || github.event.inputs.target_repository == github.repository) && github.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 @@ -804,6 +802,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
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
22 changes: 21 additions & 1 deletion scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ GATE_SCRIPT="$REPO_ROOT/scripts/ci/strix_quick_gate.sh"

FAILURES=0

# Keep local developer/provider secrets from changing fake Strix model routing.
unset STRIX_LLM
unset LLM_API_KEY
unset LLM_API_BASE
unset OPENAI_API_KEY
unset STRIX_GITHUB_MODELS_TOKEN
unset LITELLM_API_KEY
unset LITELLM_MASTER_KEY
unset GEMINI_API_KEY
unset GOOGLE_APPLICATION_CREDENTIALS
if ! python3 -c 'import pathlib' >/dev/null 2>&1; then
export PATH="/opt/homebrew/bin:/usr/bin:/bin:$PATH"
fi

record_failure() {
echo "FAIL: $1" >&2
FAILURES=$((FAILURES + 1))
Expand Down Expand Up @@ -429,7 +443,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 @@ -542,6 +556,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'timeout-minutes: 420' "opencode review target keeps a bounded runner budget so deep reviews can finish and stalled reviews still release queue capacity"
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: 350' "opencode model pool gives deep tool-using reviews more than 30 minutes while capping stalled model attempts"
assert_file_contains "$workflow_file" 'timeout-minutes: 45' "opencode approval step keeps a bounded post-review publication budget"
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 gives each model 90 minutes 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"
Expand Down Expand Up @@ -920,6 +935,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }}' "opencode evidence step passes HEAD_SHA to failed-check evidence collection"
assert_file_contains "$workflow_file" "FAILED_CHECK_EVIDENCE_ATTEMPTS" "opencode review workflow bounds waiting for peer check failures before model review"
assert_file_contains "$workflow_file" 'timeout-minutes: 350' "opencode model stage has a bounded but deep-review-capable timeout"
assert_file_contains "$workflow_file" 'timeout-minutes: 40' "opencode evidence preparation has a bounded peer-check wait timeout"
assert_file_contains "$workflow_file" 'FAILED_CHECK_EVIDENCE_ATTEMPTS: "20"' "opencode review workflow keeps pre-model peer-check waiting bounded for required workflow DX"
assert_file_contains "$workflow_file" 'FAILED_CHECK_EVIDENCE_SLEEP_SECONDS: "15"' "opencode review workflow retries peer-check evidence without stalling the model stage for Strix-scale durations"
assert_file_contains "$workflow_file" "found completed failed peer-check evidence while other peer checks are still running" "opencode evidence preparation retries stale failed checks while peer checks are pending"
Expand Down Expand Up @@ -1091,6 +1107,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 Expand Up @@ -2780,6 +2797,9 @@ run_gate_case() {
if [ -n "${STRIX_TEST_CASE_FILTER:-}" ] && [ "$scenario" != "$STRIX_TEST_CASE_FILTER" ]; then
return
fi
if [ "${STRIX_TEST_TRACE_CASES:-0}" = "1" ]; then
printf 'RUN_GATE_CASE: %s\n' "$scenario" >&2
fi

local tmp_dir
tmp_dir="$(mktemp -d)"
Expand Down
4 changes: 3 additions & 1 deletion tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,10 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
'OPENCODE_MODEL_CANDIDATES: "github-models/deepseek/deepseek-v3-0324 '
"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_review_supports_review_token_pat_fallback() -> None:
Expand Down
Loading