From b619aa812695b2752e7d920d20cef9eea54107b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 29 Jun 2026 17:03:13 +0900 Subject: [PATCH] Enable central review workflows for thin callers --- .github/workflows/opencode-review.yml | 103 +++++++++++++-------- .github/workflows/strix.yml | 57 ++++++++---- PR_GOVERNANCE_AUDIT.md | 22 +++-- scripts/ci/test_strix_quick_gate.sh | 21 +++-- tests/test_pr_governance_audit_contract.py | 21 ++++- 5 files changed, 144 insertions(+), 80 deletions(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index cf459655c..6cc077af2 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -3,6 +3,29 @@ name: Required OpenCode Review on: pull_request_target: types: [opened, synchronize, reopened, ready_for_review] + workflow_call: + inputs: + pr_number: + description: Pull request number to review + required: true + type: string + pr_base_ref: + description: Pull request base branch + required: true + type: string + pr_base_sha: + description: Pull request base SHA + required: true + type: string + pr_head_sha: + description: Pull request head SHA + required: true + type: string + canonical_ref: + description: Ref of ContextualWisdomLab/.github to use for trusted review scripts + required: false + default: main + type: string workflow_dispatch: inputs: pr_number: @@ -28,7 +51,7 @@ on: type: string concurrency: - group: opencode-review-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.inputs.pr_number || github.run_id }} + group: opencode-review-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number || github.run_id }} cancel-in-progress: true permissions: @@ -54,7 +77,7 @@ jobs: - name: Resolve trusted OpenCode source ref id: trusted_source env: - INPUT_CANONICAL_REF: ${{ github.event.inputs.canonical_ref || '' }} + INPUT_CANONICAL_REF: ${{ inputs.canonical_ref || github.event.inputs.canonical_ref || '' }} WORKFLOW_REF: ${{ github.workflow_ref }} run: | set -euo pipefail @@ -80,7 +103,7 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} fetch-depth: 0 persist-credentials: false - ref: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + ref: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} path: pr-head - name: Install Python coverage measurement tools @@ -89,8 +112,8 @@ jobs: - name: Measure test and docstring coverage at 100 percent id: measure env: - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} COVERAGE_SOURCE_WORKDIR: ${{ github.workspace }}/pr-head run: | set -euo pipefail @@ -513,7 +536,7 @@ jobs: opencode-review-target: name: opencode-review needs: [coverage-evidence] - if: always() && (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target') + if: always() && (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_call') runs-on: ubuntu-latest permissions: actions: read @@ -531,7 +554,7 @@ jobs: - name: Resolve trusted OpenCode source ref id: trusted_source env: - INPUT_CANONICAL_REF: ${{ github.event.inputs.canonical_ref || '' }} + INPUT_CANONICAL_REF: ${{ inputs.canonical_ref || github.event.inputs.canonical_ref || '' }} WORKFLOW_REF: ${{ github.workflow_ref }} run: | set -euo pipefail @@ -555,10 +578,10 @@ jobs: env: GH_TOKEN: ${{ github.token }} GH_REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - PR_BASE_REF: ${{ github.event.pull_request.base.ref || github.event.inputs.pr_base_ref }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + PR_BASE_REF: ${{ github.event.pull_request.base.ref || inputs.pr_base_ref || github.event.inputs.pr_base_ref }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head run: | set -euo pipefail @@ -632,10 +655,10 @@ jobs: env: GH_TOKEN: ${{ github.token }} GH_REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md OPENCODE_FAILED_CHECK_EVIDENCE_FILE: ${{ runner.temp }}/opencode-failed-check-evidence.md @@ -1426,10 +1449,10 @@ jobs: OPENCODE_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-primary.md OPENCODE_REVIEW_WORKDIR: ${{ runner.temp }}/opencode-review-project OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} RUN_ID: ${{ github.run_id }} RUN_ATTEMPT: ${{ github.run_attempt }} run: | @@ -1552,10 +1575,10 @@ jobs: OPENCODE_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-fallback.md OPENCODE_REVIEW_WORKDIR: ${{ runner.temp }}/opencode-review-project OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} RUN_ID: ${{ github.run_id }} RUN_ATTEMPT: ${{ github.run_attempt }} run: | @@ -1679,10 +1702,10 @@ jobs: OPENCODE_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-second-fallback.md OPENCODE_REVIEW_WORKDIR: ${{ runner.temp }}/opencode-review-project OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} RUN_ID: ${{ github.run_id }} RUN_ATTEMPT: ${{ github.run_attempt }} run: | @@ -1807,10 +1830,10 @@ jobs: OPENCODE_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-catalog-fallback.md OPENCODE_REVIEW_WORKDIR: ${{ runner.temp }}/opencode-review-project OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} RUN_ID: ${{ github.run_id }} RUN_ATTEMPT: ${{ github.run_attempt }} run: | @@ -1979,8 +2002,8 @@ jobs: env: GH_TOKEN: ${{ steps.opencode_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || github.token }} GH_REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} RUN_ID: ${{ github.run_id }} RUN_ATTEMPT: ${{ github.run_attempt }} OPENCODE_PRIMARY_OUTCOME: ${{ steps.opencode_review_primary.outputs.review_status }} @@ -1993,8 +2016,8 @@ jobs: OPENCODE_CATALOG_FALLBACK_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-catalog-fallback.md # The publish gate re-runs source-backed validation against PR-head data. OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} run: | set -euo pipefail @@ -2261,8 +2284,8 @@ jobs: USE_GITHUB_TOKEN: "true" NPM_CONFIG_IGNORE_SCRIPTS: "true" NO_COLOR: "1" - PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} RUN_ID: ${{ github.run_id }} RUN_ATTEMPT: ${{ github.run_attempt }} OPENCODE_PRIMARY_OUTCOME: ${{ steps.opencode_review_primary.outputs.review_status }} @@ -2273,8 +2296,8 @@ jobs: OPENCODE_FALLBACK_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-fallback.md OPENCODE_SECOND_FALLBACK_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-second-fallback.md OPENCODE_CATALOG_FALLBACK_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-catalog-fallback.md - PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} APPROVAL_CHECK_WAIT_ATTEMPTS: "81" APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "30" CHECK_LOOKUP_RETRY_ATTEMPTS: "5" @@ -3036,7 +3059,7 @@ jobs: } emit_known_missing_string_finding \ - "github.event.inputs.strix_llm || 'openai/gpt-5'" \ + "inputs.strix_llm || github.event.inputs.strix_llm || 'openai/gpt-5'" \ "Strix PR scans must default to GitHub Models GPT-5" \ ".github/workflows/strix.yml" \ "scripts/ci/test_strix_quick_gate.sh" diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index a63e164fe..7de5283b9 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -4,6 +4,25 @@ on: push: branches: [main, develop, master] pull_request_target: + workflow_call: + inputs: + pr_number: + description: Optional pull request number for trusted PR-scope evidence + required: false + type: string + pr_base_sha: + description: Optional pull request base SHA for trusted PR-scope evidence + required: false + type: string + pr_head_sha: + description: Optional pull request head SHA for trusted PR-scope evidence + required: false + type: string + strix_llm: + description: Optional Strix model override for caller evidence runs + required: false + default: openai/gpt-5 + type: string schedule: # Weekly scan on protected branches (Mondays at 03:00 UTC). - cron: '0 3 * * 1' @@ -31,9 +50,9 @@ concurrency: group: >- strix-${{ github.repository }}-${{ github.event_name == 'pull_request_target' && format('pr-{0}-{1}', github.event.pull_request.number, github.event.pull_request.head.sha) || - github.event.inputs.pr_number != '' && github.event.inputs.pr_head_sha != '' && - format('pr-{0}-{1}', github.event.inputs.pr_number, github.event.inputs.pr_head_sha) || - github.event.inputs.pr_number != '' && format('pr-{0}', github.event.inputs.pr_number) || github.ref }} + (inputs.pr_number || github.event.inputs.pr_number) != '' && (inputs.pr_head_sha || github.event.inputs.pr_head_sha) != '' && + format('pr-{0}-{1}', inputs.pr_number || github.event.inputs.pr_number, inputs.pr_head_sha || github.event.inputs.pr_head_sha) || + (inputs.pr_number || github.event.inputs.pr_number) != '' && format('pr-{0}', inputs.pr_number || github.event.inputs.pr_number) || github.ref }} # cancel-in-progress deliberately disabled: an attacker could force-push # a benign commit to cancel an in-progress scan of a malicious commit. The # head SHA in PR groups prevents stale scans from serializing newer evidence. @@ -152,12 +171,12 @@ jobs: } >> "$GITHUB_ENV" - name: Fetch pull request head for trusted scan - if: github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '' + if: github.event_name == 'pull_request_target' || (inputs.pr_number || github.event.inputs.pr_number) != '' env: GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event.inputs.pr_number }} - PR_BASE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + PR_BASE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} run: | set -euo pipefail if [ -z "$PR_NUMBER" ] || [ -z "$PR_HEAD_SHA" ]; then @@ -222,7 +241,7 @@ jobs: - name: Gate Strix secrets id: gate env: - STRIX_MODEL: ${{ github.event.inputs.strix_llm || 'openai/gpt-5' }} + STRIX_MODEL: ${{ inputs.strix_llm || github.event.inputs.strix_llm || 'openai/gpt-5' }} STRIX_OPENAI_API_KEY: ${{ secrets.STRIX_OPENAI_API_KEY }} STRIX_VERTEX_CREDENTIALS: ${{ secrets.GCP_SA_KEY }} STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} @@ -384,7 +403,7 @@ jobs: - name: Prepare Strix model input file if: steps.gate.outputs.enabled == 'true' env: - STRIX_MODEL: ${{ github.event.inputs.strix_llm || 'openai/gpt-5' }} + STRIX_MODEL: ${{ inputs.strix_llm || github.event.inputs.strix_llm || 'openai/gpt-5' }} run: | umask 077 strix_llm_file="$RUNNER_TEMP/strix_llm.txt" @@ -445,7 +464,7 @@ jobs: CLOUDSDK_PROJECT: ${{ env.CLOUDSDK_PROJECT }} VERTEXAI_LOCATION: ${{ secrets.VERTEX_LOCATION || 'us-central1' }} VERTEX_LOCATION: ${{ secrets.VERTEX_LOCATION || 'us-central1' }} - STRIX_TARGET_PATH: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && '__PR_SCOPE__' || './' }} + STRIX_TARGET_PATH: ${{ (github.event_name == 'pull_request_target' || (inputs.pr_number || github.event.inputs.pr_number) != '') && '__PR_SCOPE__' || './' }} STRIX_SOURCE_DIRS: ". backend frontend" STRIX_REASONING_EFFORT: low STRIX_LLM_MAX_RETRIES: 1 @@ -459,12 +478,12 @@ jobs: YARN_ENABLE_SCRIPTS: "false" BUN_CONFIG_IGNORE_SCRIPTS: "true" STRIX_FAIL_ON_MIN_SEVERITY: MEDIUM - STRIX_DISABLE_PR_SCOPING: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && '0' || '1' }} - GH_TOKEN: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && github.token || '' }} - PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event.inputs.pr_number }} - PR_BASE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} - PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} - IS_PR_EVIDENCE_RUN: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && 'true' || 'false' }} + STRIX_DISABLE_PR_SCOPING: ${{ (github.event_name == 'pull_request_target' || (inputs.pr_number || github.event.inputs.pr_number) != '') && '0' || '1' }} + GH_TOKEN: ${{ (github.event_name == 'pull_request_target' || (inputs.pr_number || github.event.inputs.pr_number) != '') && github.token || '' }} + PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number }} + PR_BASE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || inputs.pr_base_sha || github.event.inputs.pr_base_sha }} + PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} + IS_PR_EVIDENCE_RUN: ${{ (github.event_name == 'pull_request_target' || (inputs.pr_number || github.event.inputs.pr_number) != '') && 'true' || 'false' }} run: | budget_suffix="TIME""OUT" process_budget_seconds="1500" @@ -477,7 +496,7 @@ jobs: - name: Collect Strix reports for artifact upload if: ${{ always() && steps.gate.outputs.enabled == 'true' }} env: - PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} + PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }} run: | set -euo pipefail mkdir -p "$GITHUB_WORKSPACE/strix_runs" @@ -512,7 +531,7 @@ jobs: publish-manual-pr-evidence-status: name: publish-manual-pr-evidence-status needs: strix - if: ${{ always() && !cancelled() && github.event_name == 'workflow_dispatch' && github.event.inputs.pr_head_sha != '' }} + if: ${{ always() && !cancelled() && github.event_name == 'workflow_dispatch' && (inputs.pr_head_sha || github.event.inputs.pr_head_sha) != '' }} runs-on: ubuntu-latest permissions: statuses: write @@ -520,7 +539,7 @@ jobs: - name: Publish same-head manual Strix status env: GH_TOKEN: ${{ github.token }} - PR_HEAD_SHA: ${{ github.event.inputs.pr_head_sha }} + PR_HEAD_SHA: ${{ inputs.pr_head_sha || github.event.inputs.pr_head_sha }} STRIX_RESULT: ${{ needs.strix.result }} run: | set -euo pipefail diff --git a/PR_GOVERNANCE_AUDIT.md b/PR_GOVERNANCE_AUDIT.md index e8f49f779..63b419e58 100644 --- a/PR_GOVERNANCE_AUDIT.md +++ b/PR_GOVERNANCE_AUDIT.md @@ -299,16 +299,18 @@ PR #381: wait: OpenCode review is already in progress ## Remaining Proof Gaps -- 2026-06-29 KST `html4tree` onboarding gap: PR #3 is the lowest open PR and is - cleanly mergeable by GitHub, but current head - `d0c4cbc2bb267aed407e4bf6308f4f3cfd3b504c` has no check runs and no reviews. - The PR title claims an XSS/attribute-injection fix, while the diff only - changes indentation in `src/main/kotlin/html4tree/util.kt`. This proves that - `html4tree` cannot be merged by queue order until central Strix, OpenCode - Review, and scheduler evidence run on the same head and OpenCode either - requests changes or approves real code/test evidence. The required process - repair is to add `html4tree` to the organization required-workflow target set - or add a temporary thin caller that delegates to `.github`; do not bypass the review gate +- 2026-06-29 KST `html4tree` onboarding gap: after obsolete/no-op PRs #3-#19 + were closed with queue evidence, PR #20 is the lowest open PR. It is cleanly + mergeable by GitHub and contains real generated-HTML accessibility changes, + but current head `e5598f21cd6147b5fcc0e281d29866ae70bc77db` has no check runs + and no reviews from the central `.github` process. `html4tree` is a public + fork, and live repository ruleset lookup with inherited rules enabled returns + no applied organization rulesets. This proves that `html4tree` cannot be + merged by queue order until central Strix, OpenCode Review, and scheduler + evidence run on the same head and OpenCode either requests changes or + approves real code/test evidence. The required process repair is to add + `html4tree` to the organization required-workflow target set or add a + temporary thin caller that delegates to `.github`; do not bypass the review gate with a manual or forced merge. - 2026-06-26 17:53 KST continuation snapshot: `.github` PR #68 is merged at merge commit `590b4ecb2ac9eac700019a183081309e28d8f25b`; `bandscope` PR #459 is merged at merge commit `a7173e45304d8681f02fdf43e4de5a6b6540bb44`; `.github` PR #79 and #80 are merged, and organization ruleset `18156473` now requires central Strix, OpenCode, and scheduler workflows from `.github@807254a04efafd5f806e0f70cb067ecf050cfd11`. The live organization target inventory contains 12 public non-fork repositories and confirms `appguardrail` is present while `VibeSec` is not in that set. - PR #80 proved the no-copy required-workflow path after the ruleset update: `scan-pr-queue` ran as a required check in `ContextualWisdomLab/.github`, passed in 7s, used `PULL_REQUEST_NUMBER=80`, and reported `OpenCode review is already in progress` instead of scanning or mutating the entire queue. The same PR passed central Strix in 3m20s and OpenCode in 4m36s on current head `23ee41076b8f3cec21cff3afd3cd5b4380decf12`. diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index b3a700f07..cbfc8be47 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -97,8 +97,8 @@ 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" "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" "format('pr-{0}-{1}', inputs.pr_number || github.event.inputs.pr_number, inputs.pr_head_sha || github.event.inputs.pr_head_sha)" "strix workflow scopes manual or caller PR evidence concurrency to the requested pull request head" + assert_file_contains "$workflow_file" "(inputs.pr_number || github.event.inputs.pr_number) != '' && format('pr-{0}', inputs.pr_number || github.event.inputs.pr_number)" "strix workflow retains a 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" "cancel-in-progress: false" "strix workflow never cancels in-progress security evidence" assert_file_contains "$workflow_file" "head SHA in PR groups prevents stale scans from serializing newer evidence" "strix workflow documents stale scan queue avoidance" @@ -178,7 +178,7 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" 'budget_suffix="TIME""OUT"' "strix workflow builds budget env keys without visible timeout signal text" assert_file_contains "$workflow_file" 'export "STRIX_TOTAL_${budget_suffix}_SECONDS=1800"' "strix workflow caps total Strix budget for PR-scoped quick scans" assert_file_contains "$workflow_file" 'process_budget_seconds="1500"' "strix workflow keeps process budget within the PR quick-scan step timeout" - assert_file_contains "$workflow_file" 'IS_PR_EVIDENCE_RUN: ${{ (github.event_name == '"'"'pull_request_target'"'"' || github.event.inputs.pr_number != '"'"''"'"') && '"'"'true'"'"' || '"'"'false'"'"' }}' "strix workflow passes PR evidence mode through env" + assert_file_contains "$workflow_file" 'IS_PR_EVIDENCE_RUN: ${{ (github.event_name == '"'"'pull_request_target'"'"' || (inputs.pr_number || github.event.inputs.pr_number) != '"'"''"'"') && '"'"'true'"'"' || '"'"'false'"'"' }}' "strix workflow passes PR evidence mode through env" assert_file_not_contains "$workflow_file" 'if [ "${{ (github.event_name == '"'"'pull_request_target'"'"' || github.event.inputs.pr_number != '"'"''"'"') && '"'"'true'"'"' || '"'"'false'"'"' }}" = "true" ]; then' "strix workflow does not interpolate GitHub context inside shell condition" assert_file_not_contains "$workflow_file" "LLM_TIMEOUT:" "strix workflow must not expose LLM timeout env names in GitHub logs" assert_file_not_contains "$workflow_file" "STRIX_MEMORY_COMPRESSOR_TIMEOUT:" "strix workflow must not expose compressor timeout env names in GitHub logs" @@ -186,7 +186,7 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_not_contains "$workflow_file" "STRIX_TOTAL_TIMEOUT_SECONDS:" "strix workflow must not expose total timeout env names in GitHub logs" assert_file_not_contains "$workflow_file" "STRIX_PR_SCOPE_MAX_FILES_PER_BATCH" "strix workflow must not split Strix PR evidence into separate scanner runs" assert_file_not_contains "$workflow_file" "secrets.STRIX_LLM == 'vertex_ai/gemini-3.1-pro-preview-customtools' && 'vertex_ai/gemini-2.5-flash'" "strix workflow must not quarantine the approved Vertex preview model after organization secret visibility is fixed" - assert_file_contains "$workflow_file" "github.event.inputs.strix_llm || 'openai/gpt-5'" "strix workflow defaults PR Strix scans to GitHub Models GPT-5" + assert_file_contains "$workflow_file" "inputs.strix_llm || github.event.inputs.strix_llm || 'openai/gpt-5'" "strix workflow defaults PR Strix scans to GitHub Models GPT-5" assert_file_not_contains "$workflow_file" "secrets.STRIX_LLM ||" "strix workflow must not let the legacy STRIX_LLM secret override PR defaults" assert_file_contains "$workflow_file" "STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, or an approved organization Vertex AI model" "strix workflow rejects unsupported model inputs" assert_file_contains "$workflow_file" "vertex_ai/gemini-3.1-pro-preview-customtools | vertex_ai/gemini-2.5-flash)" "strix workflow accepts only exact approved organization Vertex AI models" @@ -368,10 +368,10 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_not_contains "$workflow_file" "Wait for trusted OpenCode approval review" "opencode pull_request bridge was removed to avoid duplicate required-check resource use" assert_file_not_contains "$workflow_file" "Trusted OpenCode requested changes for head" "opencode pull_request bridge no longer reconsumes stale trusted review state" assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "opencode review workflow must not hard-code repository-specific PR bypasses" - assert_file_contains "$workflow_file" 'group: opencode-review-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.inputs.pr_number || github.run_id }}' "opencode review cancels stale runs per PR instead of preserving older review heads" + assert_file_contains "$workflow_file" 'group: opencode-review-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr_number || github.event.inputs.pr_number || github.run_id }}' "opencode review cancels stale runs per PR instead of preserving older review heads" assert_file_contains "$workflow_file" 'cancel-in-progress: true' "opencode review cancels stale in-progress review attempts when a newer PR event arrives" assert_file_contains "$workflow_file" "github.event.pull_request.head.repo.full_name == github.repository" "opencode pull_request_target coverage execution is limited to same-repository PR heads" - assert_file_contains "$workflow_file" "if: always() && (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target')" "opencode review side effects are limited to manual or required PR events" + assert_file_contains "$workflow_file" "if: always() && (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_call')" "opencode review side effects are limited to manual, required PR, or reusable caller events" assert_file_contains "$workflow_file" "opencode-review-target:" "opencode trusted review job owns the required check surface" assert_file_contains "$workflow_file" "Initialize CodeGraph index for OpenCode" "opencode review workflow initializes CodeGraph before review" assert_file_contains "$workflow_file" "actions: read" "opencode review workflow can read failed Actions logs for GitHub Check diagnosis" @@ -559,8 +559,8 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "OpenCode %s fallback attempt %s/%s failed" "opencode catalog fallback records per-model retry failures" assert_file_contains "$workflow_file" "github-models/openai/o3 github-models/openai/o3-mini github-models/openai/o4-mini" "opencode review includes additional OpenAI reasoning model fallbacks" assert_file_contains "$workflow_file" "coverage-evidence:" "opencode workflow measures coverage before review" - assert_file_contains "$workflow_file" "github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target'" "manual and required OpenCode reviews measure coverage instead of approving skipped coverage evidence" - assert_file_contains "$workflow_file" 'ref: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }}' "coverage evidence checks out the requested PR head SHA as data" + assert_file_contains "$workflow_file" "github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_call'" "manual, required, and reusable OpenCode reviews measure coverage instead of approving skipped coverage evidence" + assert_file_contains "$workflow_file" 'ref: ${{ github.event.pull_request.head.sha || inputs.pr_head_sha || github.event.inputs.pr_head_sha }}' "coverage evidence checks out the requested PR head SHA as data" assert_file_contains "$workflow_file" 'ref: ${{ steps.trusted_source.outputs.ref }}' "OpenCode review checks out central trusted scripts for same-head validation" assert_file_contains "$workflow_file" 'COVERAGE_EVIDENCE_RESULT: ${{ needs.coverage-evidence.result || '\''skipped'\'' }}' "opencode approval receives the coverage-evidence job conclusion" assert_file_contains "$workflow_file" 'build_coverage_evidence_failure_body()' "opencode approval can publish a coverage-evidence blocker" @@ -673,7 +673,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "statusCheckRollup" "opencode review workflow reads current-head GitHub Checks before approval" assert_file_contains "$workflow_file" "OPENCODE_FAILED_CHECK_EVIDENCE_FILE" "opencode review workflow persists failed-check evidence across review and approval steps" 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" 'HEAD_SHA: ${{ github.event.pull_request.head.sha || inputs.pr_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" 'FAILED_CHECK_EVIDENCE_ATTEMPTS: "20"' "opencode review workflow keeps pre-model peer-check waiting bounded for required workflow DX" @@ -875,6 +875,9 @@ assert_pr_review_merge_scheduler_uses_github_actions_bot_token() { local readme_file="$REPO_ROOT/README.md" assert_file_contains "$workflow_file" 'workflow_call:' "scheduler can run as the central reusable workflow contract" + assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'workflow_call:' "strix can run as the central reusable workflow contract" + assert_file_contains "$REPO_ROOT/.github/workflows/opencode-review.yml" 'workflow_call:' "opencode review can run as the central reusable workflow contract" + assert_file_contains "$REPO_ROOT/.github/workflows/opencode-review.yml" "github.event_name == 'workflow_call'" "opencode review job accepts reusable workflow callers" assert_file_contains "$workflow_file" 'pull_request_target:' "scheduler can run as an organization required workflow without repository-local copies" assert_file_contains "$workflow_file" 'workflows: ["Required OpenCode Review"]' "scheduler reruns after required OpenCode Review completion so approvals can trigger merge/update actions" assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "scheduler must not hard-code repository-specific PR bypasses" diff --git a/tests/test_pr_governance_audit_contract.py b/tests/test_pr_governance_audit_contract.py index 7a5cde22f..5f47d5b1c 100644 --- a/tests/test_pr_governance_audit_contract.py +++ b/tests/test_pr_governance_audit_contract.py @@ -11,6 +11,23 @@ def test_html4tree_public_fork_queue_requires_central_review_gate(): assert "temporary thin caller" in audit assert "same-head central review evidence" in audit assert "2026-06-29 KST `html4tree` onboarding gap" in audit - assert "PR #3 is the lowest open PR" in audit - assert "has no check runs and no reviews" in audit + assert "PR #20 is the lowest open PR" in audit + assert "real generated-HTML accessibility changes" in audit + assert "has no check runs" in audit + assert "no reviews from the central `.github` process" in audit assert "do not bypass the review gate" in audit + + +def test_central_review_workflows_are_reusable_for_thin_callers(): + """Temporary repo callers must delegate implementation to .github.""" + strix = Path(".github/workflows/strix.yml").read_text(encoding="utf-8") + opencode = Path(".github/workflows/opencode-review.yml").read_text(encoding="utf-8") + scheduler = Path(".github/workflows/pr-review-merge-scheduler.yml").read_text(encoding="utf-8") + + for workflow in (strix, opencode, scheduler): + assert "workflow_call:" in workflow + + assert "inputs.pr_number || github.event.inputs.pr_number" in strix + assert "inputs.pr_head_sha || github.event.inputs.pr_head_sha" in strix + assert "github.event_name == 'workflow_call'" in opencode + assert "inputs.pr_number || github.event.inputs.pr_number" in opencode