From 51d5799d37c45b9ba7ce661972dcbe8c94c4acd2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 11 Jul 2026 03:02:16 +0900 Subject: [PATCH 1/3] fix(strix): keep scan token status read-only --- .github/workflows/strix.yml | 11 +- scripts/ci/opencode_review_approve_gate.sh | 144 +++++++++++++----- scripts/ci/test_strix_quick_gate.sh | 14 +- .../validate_opencode_failed_check_review.sh | 97 +++++++----- 4 files changed, 177 insertions(+), 89 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index bf1bc7177..a4d66c5ac 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -97,9 +97,9 @@ concurrency: # runs may still cancel the matching PR/head group. cancel-in-progress: ${{ github.event_name == 'pull_request_target' && github.event.action == 'closed' }} -# Scorecard Token-Permissions (alert #43): keep the workflow-level token -# read-only and grant the id-token/statuses writes only on the job that needs -# them (the strix scan job below and the publish-manual-pr-evidence-status job). +# Scorecard Token-Permissions (alert #43): keep GITHUB_TOKEN read-only. Jobs +# that publish manual PR evidence exchange app or central secret tokens instead +# of elevating the workflow token's commit-status scope. permissions: actions: read contents: read @@ -124,14 +124,13 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest # Least-privilege token scoped to this job (Scorecard alert #43): the scan - # exchanges an OIDC token (id-token) and posts a commit status - # (statuses:write); all other scopes stay read-only. + # exchanges an OIDC token (id-token); status publication uses exchanged app + # or central secret tokens so GITHUB_TOKEN keeps commit-status read-only. permissions: actions: read contents: read id-token: write models: read - statuses: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: diff --git a/scripts/ci/opencode_review_approve_gate.sh b/scripts/ci/opencode_review_approve_gate.sh index ca033b115..f04ce05cf 100755 --- a/scripts/ci/opencode_review_approve_gate.sh +++ b/scripts/ci/opencode_review_approve_gate.sh @@ -74,15 +74,95 @@ TMP_JSON="$(mktemp)" trap 'rm -f "$TMP_JSON"' EXIT printf '%s\n' "$CONTROL_JSON" >"$TMP_JSON" -if ! jq -e . "$TMP_JSON" >/dev/null 2>&1; then +if ! control_fields="$( + python3 - "$TMP_JSON" <<'PY' +from __future__ import annotations + +import json +import sys +from pathlib import Path + + +def reject(reason: str) -> None: + print(f"Reason: {reason}", file=sys.stderr) + raise SystemExit(4) + + +control_path = Path(sys.argv[1]) +try: + control = json.loads(control_path.read_text(encoding="utf-8")) +except Exception as exc: # noqa: BLE001 - shell gate reports the parse reason. + reject(f"control JSON is invalid: {exc}") + +if not isinstance(control, dict): + reject("control JSON must be an object") + + +def non_empty_string(field: str) -> str: + value = control.get(field) + if not isinstance(value, str) or not value: + reject(f"{field} must be a non-empty string") + return value + + +head_sha = non_empty_string("head_sha") +run_id = non_empty_string("run_id") +run_attempt = non_empty_string("run_attempt") +result = control.get("result") +if result not in {"APPROVE", "REQUEST_CHANGES"}: + reject("result must be APPROVE or REQUEST_CHANGES") + +non_empty_string("reason") +non_empty_string("summary") + +findings = control.get("findings") +if result == "REQUEST_CHANGES": + if not isinstance(findings, list) or not findings: + reject("REQUEST_CHANGES requires at least one finding") +else: + if findings is not None and (not isinstance(findings, list) or findings): + reject("APPROVE requires findings to be empty") + +for index, finding in enumerate(findings or [], start=1): + if not isinstance(finding, dict): + reject(f"finding {index} must be an object") + for field in ( + "path", + "severity", + "title", + "problem", + "root_cause", + "fix_direction", + "regression_test_direction", + "suggested_diff", + ): + value = finding.get(field) + if not isinstance(value, str) or not value: + reject(f"finding {index} field {field} must be a non-empty string") + path = finding["path"].lower() + if path in {"n/a", "unknown"}: + reject(f"finding {index} path must name a source file") + line = finding.get("line") + if type(line) is not int or line <= 0: + reject(f"finding {index} line must be a positive integer") + suggested_diff = finding["suggested_diff"].lower() + if suggested_diff.startswith("n/a") or suggested_diff.startswith("cannot provide diff"): + reject(f"finding {index} suggested_diff must be concrete") + +print(head_sha) +print(run_id) +print(run_attempt) +print(result) +PY +)"; then echo "NO_CONCLUSION" exit 4 fi -CONTROL_HEAD_SHA="$(jq -r '.head_sha // empty' "$TMP_JSON")" -CONTROL_RUN_ID="$(jq -r '.run_id // empty' "$TMP_JSON")" -CONTROL_RUN_ATTEMPT="$(jq -r '.run_attempt // empty' "$TMP_JSON")" -RESULT="$(jq -r '.result // empty' "$TMP_JSON")" +CONTROL_HEAD_SHA="$(printf '%s\n' "$control_fields" | sed -n '1p')" +CONTROL_RUN_ID="$(printf '%s\n' "$control_fields" | sed -n '2p')" +CONTROL_RUN_ATTEMPT="$(printf '%s\n' "$control_fields" | sed -n '3p')" +RESULT="$(printf '%s\n' "$control_fields" | sed -n '4p')" if [ "$CONTROL_HEAD_SHA" != "$EXPECTED_HEAD_SHA" ]; then echo "SHA_MISMATCH" @@ -99,37 +179,6 @@ if [ "$EXPECTED_RUN_ATTEMPT" != "-" ] && [ "$CONTROL_RUN_ATTEMPT" != "$EXPECTED_ exit 2 fi -if ! jq -e ' - type == "object" - and (.head_sha | type == "string" and length > 0) - and (.run_id | type == "string" and length > 0) - and (.run_attempt | type == "string" and length > 0) - and (.result == "APPROVE" or .result == "REQUEST_CHANGES") - and (.reason | type == "string" and length > 0) - and (.summary | type == "string" and length > 0) - and ( - if .result == "REQUEST_CHANGES" then (.findings | type == "array" and length > 0) - else ((.findings == null) or (.findings | type == "array" and length == 0)) - end - ) - and all((.findings // [])[]; - (.path | type == "string" and length > 0) - and ((.path | ascii_downcase) as $p | ($p != "n/a" and $p != "unknown")) - and (.line | type == "number" and . > 0 and floor == .) - and (.severity | type == "string" and length > 0) - and (.title | type == "string" and length > 0) - and (.problem | type == "string" and length > 0) - and (.root_cause | type == "string" and length > 0) - and (.fix_direction | type == "string" and length > 0) - and (.regression_test_direction | type == "string" and length > 0) - and (.suggested_diff | type == "string" and length > 0) - and ((.suggested_diff | ascii_downcase) as $d | (($d | startswith("n/a")) | not) and (($d | startswith("cannot provide diff")) | not)) - ) -' "$TMP_JSON" >/dev/null; then - echo "NO_CONCLUSION" - exit 4 -fi - if ! python3 "$NORMALIZER" --check-structural-approval "$TMP_JSON" >/dev/null; then echo "NO_CONCLUSION" exit 4 @@ -285,7 +334,28 @@ then fi if [ -n "$NORMALIZED_JSON_FILE" ]; then - jq -c '{head_sha, run_id, run_attempt, result, reason, summary, findings:(.findings // [])}' "$TMP_JSON" >"$NORMALIZED_JSON_FILE" + python3 - "$TMP_JSON" "$NORMALIZED_JSON_FILE" <<'PY' +from __future__ import annotations + +import json +import sys +from pathlib import Path + +control = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +normalized = { + "head_sha": control["head_sha"], + "run_id": control["run_id"], + "run_attempt": control["run_attempt"], + "result": control["result"], + "reason": control["reason"], + "summary": control["summary"], + "findings": control.get("findings") or [], +} +Path(sys.argv[2]).write_text( + json.dumps(normalized, ensure_ascii=False, separators=(",", ":")) + "\n", + encoding="utf-8", +) +PY fi echo "$RESULT" diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index b9b143989..79e980e9d 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -98,10 +98,10 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" "branches: [main, develop, master]" "strix workflow scans GitHub Flow and Git Flow protected branches" assert_file_contains "$workflow_file" "pull_request_target:" "strix workflow uses trusted PR trigger" assert_file_contains "$workflow_file" "format('pr-{0}-{1}', github.event.pull_request.number, github.event.pull_request.head.sha)" "strix workflow scopes pull_request_target concurrency to the active pull request head" - assert_file_contains "$workflow_file" 'strix-${{ github.event.inputs.target_repository || github.event.pull_request.base.repo.full_name || github.repository }}' "strix manual dispatch concurrency scopes to the target repository when provided" + assert_file_contains "$workflow_file" "github.event.inputs.target_repository ||" "strix manual dispatch concurrency scopes to the target repository when provided" assert_file_contains "$workflow_file" "format('pr-{0}-{1}', github.event.inputs.pr_number, github.event.inputs.pr_head_sha)" "strix workflow scopes manual PR evidence concurrency to the requested pull request head" assert_file_contains "$workflow_file" "github.event.inputs.pr_number != '' && format('pr-{0}', github.event.inputs.pr_number)" "strix workflow retains a manual PR fallback group when no head SHA is provided" - assert_file_contains "$workflow_file" "|| github.ref" "strix workflow scopes non-PR concurrency to the current ref" + assert_file_contains "$workflow_file" "github.ref }}" "strix workflow scopes non-PR concurrency to the current ref" assert_file_contains "$workflow_file" "cancel-in-progress: \${{ github.event_name == 'pull_request_target' && github.event.action == 'closed' }}" "strix workflow cancels only closed-PR cleanup runs" assert_file_contains "$workflow_file" "cancel-in-progress stays disabled for normal PR updates" "strix workflow documents normal PR security evidence preservation" assert_file_contains "$workflow_file" "refs/pull//head has already advanced before this queued run starts" "strix workflow documents stale scan queue avoidance" @@ -788,14 +788,14 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'Manual workflow_dispatch Strix evidence passed' "opencode approval requires an explicit manual Strix evidence status description" assert_file_contains "$workflow_file" 'last // empty' "opencode approval checks the latest strix status before accepting manual success evidence" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'publish-manual-pr-evidence-status:' "strix workflow publishes same-head manual PR evidence as a commit status" - assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'statuses: write' "strix manual evidence status job has commit-status write permission" + assert_file_not_contains "$REPO_ROOT/.github/workflows/strix.yml" 'statuses: write' "strix manual evidence status keeps GITHUB_TOKEN commit-status permission read-only" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'TARGET_REPOSITORY: ${{ github.event.inputs.target_repository || github.repository }}' "strix manual evidence status publishes to the requested target repository" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'context="strix"' "strix manual evidence status uses the status context consumed by OpenCode" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'repos/${TARGET_REPOSITORY}/statuses/${PR_HEAD_SHA}' "strix manual evidence status does not post private-target evidence to .github by mistake" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'PR_REVIEW_MERGE_STATUS_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || '"'"''"'"' }}' "strix manual evidence status can publish cross-repo evidence with the central mutation credential" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'post_strix_status "pr-review-merge-token" "$PR_REVIEW_MERGE_STATUS_TOKEN"' "strix manual evidence status retries the central mutation credential when the target app token cannot write statuses" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'post_strix_status "opencode-approve-token" "$OPENCODE_APPROVE_STATUS_TOKEN"' "strix manual evidence status retries the approval credential before declaring status publication unavailable" - assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'post_strix_status "github-token" "$GITHUB_STATUS_TOKEN"' "strix manual evidence status keeps same-repository github-token fallback" + assert_file_not_contains "$REPO_ROOT/.github/workflows/strix.yml" 'post_strix_status "github-token" "$GITHUB_STATUS_TOKEN"' "strix manual evidence status does not depend on a GITHUB_TOKEN commit-status fallback" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'Manual workflow_dispatch Strix evidence failed' "strix manual evidence status records failed reruns so older success cannot mask newer failure" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'Could not publish manual Strix status from scan job' "strix scan evidence does not fail solely because target status publication is unavailable" assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '"workflow_run"' "failed-check evidence includes failed same-head workflow runs outside statusCheckRollup" @@ -812,7 +812,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '((.name // "") | contains("${{"))' "failed-check evidence ignores cancelled matrix-template helper checks without logs" assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '(.name // "") == "noema-review"' "failed-check evidence ignores cancelled Noema queue replacement checks without source logs" assert_file_contains "$workflow_file" 'metadata-only gate evaluation' "opencode approval gate ignores cancelled metadata-only PR Governance helper gates" - assert_file_contains "$workflow_file" '((.name // "") | contains("${{"))' "opencode failed-check collection ignores cancelled matrix-template helper checks without logs" + assert_file_contains "$workflow_file" '((.name // "") | contains("$" + "{{"))' "opencode failed-check collection ignores cancelled matrix-template helper checks without logs" assert_file_contains "$workflow_file" '(.name // "") == "noema-review"' "opencode failed-check collection ignores cancelled Noema queue replacement checks without source logs" assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '"strix security scan/"*' "failed-check evidence maps stale Strix workflow helper checks to the manual strix evidence status" assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '$successful_strix_runs > 0' "failed-check evidence drops cancelled duplicate Strix runs once same-head Strix evidence succeeded" @@ -882,7 +882,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'branch protection cannot observe an approval gate without a matching GitHub pull review' "opencode approval logs why approval publication failure is blocking" assert_file_contains "$workflow_file" 'OpenCode approve review publication failed for head %s; branch protection still lacks the required GitHub review.' "opencode approval explains failed-closed review publication" assert_file_not_contains "$workflow_file" 'OpenCode approve review publication skipped after successful gate; keeping the successful approval gate result' "opencode approval no longer soft-passes rejected APPROVE review writes" - assert_file_contains "$workflow_file" 'Branch protection remains authoritative for required reviews and peer checks.' "opencode approval logs that branch protection remains authoritative after review write failure" + assert_file_contains "$workflow_file" 'Branch protection: remains authoritative for required reviews and peer checks.' "opencode approval logs that branch protection remains authoritative after review write failure" assert_file_not_contains "$workflow_file" 'gh_error_is_rate_limited()' "opencode approval soft-pass is event-scoped rather than rate-limit-specific" assert_file_contains "$workflow_file" 'warn_gh_publication_failure "review overview comment"' "opencode approval soft-fails permission-denied overview publication" assert_file_not_contains "$workflow_file" 'gh api -X DELETE "repos/${GH_REPOSITORY}/issues/comments/${comment_id}"' "opencode review must not delete Review Overview gate evidence" @@ -904,7 +904,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "collect_failed_check_evidence.sh" "opencode review workflow collects failed check logs and annotations" 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: 40' "opencode evidence preparation has a bounded peer-check wait timeout" + assert_file_contains "$workflow_file" 'timeout-minutes: 12' "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" diff --git a/scripts/ci/validate_opencode_failed_check_review.sh b/scripts/ci/validate_opencode_failed_check_review.sh index a500e5fbc..945fc9d26 100755 --- a/scripts/ci/validate_opencode_failed_check_review.sh +++ b/scripts/ci/validate_opencode_failed_check_review.sh @@ -21,28 +21,34 @@ if [ ! -s "$FAILED_CHECKS_FILE" ]; then fi review_text="$( - jq -r ' - [ - (.summary // ""), - (.reason // ""), - ( - .findings[]? - | [ - (.path // ""), - ((.line // "") | tostring), - (.severity // ""), - (.title // ""), - (.problem // ""), - (.root_cause // ""), - (.fix_direction // ""), - (.regression_test_direction // ""), - (.suggested_diff // "") - ] - | join("\n") - ) - ] - | join("\n") - ' "$CONTROL_JSON_FILE" + python3 - "$CONTROL_JSON_FILE" <<'PY' +from __future__ import annotations + +import json +import sys +from pathlib import Path + +control = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +parts = [str(control.get("summary") or ""), str(control.get("reason") or "")] +for finding in control.get("findings") or []: + parts.append( + "\n".join( + str(finding.get(field) or "") + for field in ( + "path", + "line", + "severity", + "title", + "problem", + "root_cause", + "fix_direction", + "regression_test_direction", + "suggested_diff", + ) + ) + ) +print("\n".join(parts)) +PY )" contains_review_text() { @@ -168,23 +174,36 @@ extract_strix_report_model_markers() { } count_strix_review_findings() { - jq -r ' - [ - (.findings // [])[] - | [ - .title, - .problem, - .root_cause, - .fix_direction, - .regression_test_direction, - .suggested_diff - ] - | map(. // "") - | join("\n") - | select(test("strix|github[-_]models/|deepseek/|openai/gpt-|vertex_ai/|Vulnerability Report"; "i")) - ] - | length - ' "$CONTROL_JSON_FILE" + python3 - "$CONTROL_JSON_FILE" <<'PY' +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +control = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +pattern = re.compile( + r"strix|github[-_]models/|deepseek/|openai/gpt-|vertex_ai/|Vulnerability Report", + re.IGNORECASE, +) +count = 0 +for finding in control.get("findings") or []: + text = "\n".join( + str(finding.get(field) or "") + for field in ( + "title", + "problem", + "root_cause", + "fix_direction", + "regression_test_direction", + "suggested_diff", + ) + ) + if pattern.search(text): + count += 1 +print(count) +PY } validate_distinct_strix_report_findings() { From dff332aad9d6e0200df46cbe7347e2a544b1cb1e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 11 Jul 2026 03:16:37 +0900 Subject: [PATCH 2/3] fix(strix): smoke PR head workflow contract --- scripts/ci/strix_required_workflow_smoke.sh | 8 +++++++- scripts/ci/test_strix_quick_gate.sh | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/ci/strix_required_workflow_smoke.sh b/scripts/ci/strix_required_workflow_smoke.sh index a9c8130a1..7b6e9f9d6 100755 --- a/scripts/ci/strix_required_workflow_smoke.sh +++ b/scripts/ci/strix_required_workflow_smoke.sh @@ -11,7 +11,11 @@ repo_root="$( cd -P -- "$script_dir/../.." pwd -P )" -workflow_file="$repo_root/.github/workflows/strix.yml" +workflow_root="${TRUSTED_WORKSPACE:-$repo_root}" +if [ ! -f "$workflow_root/.github/workflows/strix.yml" ]; then + workflow_root="$repo_root" +fi +workflow_file="$workflow_root/.github/workflows/strix.yml" gate_script="$repo_root/scripts/ci/strix_quick_gate.sh" full_gate_test="$repo_root/scripts/ci/test_strix_quick_gate.sh" @@ -117,6 +121,8 @@ if ! bash -n "$gate_script" "$full_gate_test"; then record_failure "Strix gate scripts must pass bash syntax checks" fi +echo "Checking Strix workflow contract in $workflow_file" + checkout_count="$(grep -Fc "uses: actions/checkout@" "$workflow_file" || true)" if [ "$checkout_count" != "1" ]; then record_failure "Strix workflow must use actions/checkout exactly once for central trusted source checkout" diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index aa10fe1e4..cb5a4c3d8 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -134,6 +134,7 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" 'mkdir -p "$TRUSTED_WORKSPACE/scripts/ci"' "strix workflow creates the scheduler policy directory before materializing PR-head scheduler policy" assert_file_contains "$workflow_file" "STRIX_REPO_ROOT:" "strix workflow passes target repository root to the central Strix gate" assert_file_contains "$workflow_file" "bash \"\$TRUSTED_STRIX_REQUIRED_SMOKE\"" "strix workflow self-test executes bounded trusted smoke script" + assert_file_contains "$REPO_ROOT/scripts/ci/strix_required_workflow_smoke.sh" 'TRUSTED_WORKSPACE' "strix required-workflow smoke validates the fetched PR head workflow when available" assert_file_not_contains "$workflow_file" "bash \"\$TRUSTED_STRIX_GATE_TEST\"" "strix required path does not execute the full long-form gate harness" assert_file_contains "$workflow_file" "bash \"\$TRUSTED_STRIX_GATE\"" "strix workflow executes trusted temp gate script" assert_file_contains "$workflow_file" "Collect Strix reports for artifact upload" "strix workflow preserves reports from trusted workspace" From 1873f3d5607798e8edc6908c7188b0b9c476e943 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 11 Jul 2026 03:22:20 +0900 Subject: [PATCH 3/3] fix(strix): materialize PR workflow for smoke --- .github/workflows/strix.yml | 10 ++++++++++ scripts/ci/test_strix_quick_gate.sh | 1 + 2 files changed, 11 insertions(+) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 2857c1cb8..ab6763c61 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -334,6 +334,11 @@ jobs: if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:opencode.jsonc" 2>/dev/null; then git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc" fi + if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:.github/workflows/strix.yml" 2>/dev/null; then + mkdir -p "$TRUSTED_WORKSPACE/.github/workflows" + git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:.github/workflows/strix.yml" > "$TRUSTED_WORKSPACE/.github/workflows/strix.yml" + echo "Materialized PR-head Strix workflow for self-test." + fi if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" 2>/dev/null; then mkdir -p "$TRUSTED_WORKSPACE/scripts/ci" git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py" @@ -349,6 +354,11 @@ jobs: if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:opencode.jsonc" 2>/dev/null; then git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc" fi + if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:.github/workflows/strix.yml" 2>/dev/null; then + mkdir -p "$TRUSTED_WORKSPACE/.github/workflows" + git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:.github/workflows/strix.yml" > "$TRUSTED_WORKSPACE/.github/workflows/strix.yml" + echo "Materialized PR-head Strix workflow for self-test." + fi if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" 2>/dev/null; then mkdir -p "$TRUSTED_WORKSPACE/scripts/ci" git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py" diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index cb5a4c3d8..9aaedb0e8 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -132,6 +132,7 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" "git -C \"\$TRUSTED_WORKSPACE\"" "strix workflow runs git only inside trusted workspace" assert_file_contains "$workflow_file" 'working-directory: ${{ runner.temp }}/trusted-workspace' "strix workflow executes privileged steps from the trusted workspace" assert_file_contains "$workflow_file" 'mkdir -p "$TRUSTED_WORKSPACE/scripts/ci"' "strix workflow creates the scheduler policy directory before materializing PR-head scheduler policy" + assert_file_contains "$workflow_file" 'git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:.github/workflows/strix.yml" > "$TRUSTED_WORKSPACE/.github/workflows/strix.yml"' "strix workflow materializes the PR-head workflow for required-path self-test" assert_file_contains "$workflow_file" "STRIX_REPO_ROOT:" "strix workflow passes target repository root to the central Strix gate" assert_file_contains "$workflow_file" "bash \"\$TRUSTED_STRIX_REQUIRED_SMOKE\"" "strix workflow self-test executes bounded trusted smoke script" assert_file_contains "$REPO_ROOT/scripts/ci/strix_required_workflow_smoke.sh" 'TRUSTED_WORKSPACE' "strix required-workflow smoke validates the fetched PR head workflow when available"