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
20 changes: 13 additions & 7 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ on:
description: Pull request number to review
required: true
type: string
target_repository:
description: Repository that owns the pull request, in owner/name form
required: false
default: ""
type: string
pr_base_ref:
description: Pull request base branch
required: true
Expand Down Expand Up @@ -77,9 +82,10 @@ jobs:
- name: Checkout pull request head for coverage measurement
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.event.inputs.target_repository || github.repository }}
fetch-depth: 0
persist-credentials: false
token: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}
ref: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }}
path: pr-head

Expand Down Expand Up @@ -553,8 +559,8 @@ jobs:

- name: Materialize pull request head for OpenCode review data
env:
GH_TOKEN: ${{ github.token }}
GH_REPOSITORY: ${{ github.repository }}
GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}
GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_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 }}
Expand Down Expand Up @@ -630,8 +636,8 @@ jobs:
- name: Prepare bounded OpenCode review evidence
timeout-minutes: 40
env:
GH_TOKEN: ${{ github.token }}
GH_REPOSITORY: ${{ github.repository }}
GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}
GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_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 }}
Expand Down Expand Up @@ -1978,7 +1984,7 @@ jobs:
|| steps.opencode_review_catalog_fallback.outputs.review_status == 'success')
env:
GH_TOKEN: ${{ steps.opencode_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || github.token }}
GH_REPOSITORY: ${{ github.repository }}
GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_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 }}
RUN_ID: ${{ github.run_id }}
Expand Down Expand Up @@ -2247,7 +2253,7 @@ jobs:
timeout-minutes: 45
env:
GH_TOKEN: ${{ steps.opencode_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || github.token }}
GH_REPOSITORY: ${{ github.repository }}
GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }}
STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}
OPENCODE_APP_TOKEN: ${{ steps.opencode_app_token.outputs.token }}
OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md
Expand Down
18 changes: 12 additions & 6 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ on:
description: Optional pull request number for trusted PR-scope evidence
required: false
type: string
target_repository:
description: Optional repository that owns the pull request, in owner/name form
required: false
default: ""
type: string
pr_base_sha:
description: Optional pull request base SHA for trusted PR-scope evidence
required: false
Expand All @@ -29,7 +34,7 @@ on:

concurrency:
group: >-
strix-${{ github.repository }}-${{ github.event_name == 'pull_request_target' &&
strix-${{ github.event.inputs.target_repository || 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) ||
Expand Down Expand Up @@ -134,8 +139,8 @@ jobs:

- name: Materialize target workspace
env:
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ github.repository }}
GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}
REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }}
TARGET_WORKSPACE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.sha }}
run: |
set -euo pipefail
Expand All @@ -154,7 +159,7 @@ jobs:
- name: Fetch pull request head for trusted scan
if: github.event_name == 'pull_request_target' || github.event.inputs.pr_number != ''
env:
GH_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_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 }}
Expand Down Expand Up @@ -519,7 +524,8 @@ jobs:
steps:
- name: Publish same-head manual Strix status
env:
GH_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}
TARGET_REPOSITORY: ${{ github.event.inputs.target_repository || github.repository }}
PR_HEAD_SHA: ${{ github.event.inputs.pr_head_sha }}
STRIX_RESULT: ${{ needs.strix.result }}
run: |
Expand All @@ -544,7 +550,7 @@ jobs:
;;
esac

gh api -X POST "repos/${GITHUB_REPOSITORY}/statuses/${PR_HEAD_SHA}" \
gh api -X POST "repos/${TARGET_REPOSITORY}/statuses/${PR_HEAD_SHA}" \
-f state="$state" \
-f context="strix" \
-f description="$description" \
Expand Down
1 change: 1 addition & 0 deletions docs/org-required-workflow-rollout.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The central `.github/workflows/opencode-review.yml` is now part of the active or
- Stable required check job name: `opencode-review`
- Trusted source: `ContextualWisdomLab/.github`
- PR-head handling: checkout or fetch PR head as review data only; trusted scripts come from the central `.github` ref
- Manual target support: OpenCode and Strix `workflow_dispatch` runs can pass `target_repository` for repos such as private `aFIPC` whose PRs do not yet inherit the org required-workflow rule; org ruleset coverage is still the required steady state before draining that queue
- Model token posture: use the organization `STRIX_GITHUB_MODELS_TOKEN` secret for GitHub Models calls, with `github.token` as the fallback; live workflow evidence showed `github.token` alone can return 403 from `models.github.ai/inference`
- Write posture: OpenCode may create review/comment side effects through the OpenCode app token when available; `github.token` remains the last fallback and publication failures are soft-failed
- Coverage execution posture: privileged `pull_request_target` coverage runs only for same-repository PR heads; fork PR heads must be covered by an unprivileged PR-side check or manually trusted dispatch before approval
Expand Down
14 changes: 12 additions & 2 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ 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.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"
Expand All @@ -117,6 +118,9 @@ assert_strix_workflow_pr_trigger_hardened() {
assert_file_contains "$workflow_file" 'TRUSTED_STRIX_SOURCE=$trusted_strix_source' "strix workflow exports the central Strix source path"
assert_file_contains "$workflow_file" 'TRUSTED_STRIX_GATE=$trusted_strix_source/scripts/ci/strix_quick_gate.sh' "strix workflow executes the central Strix gate script"
assert_file_contains "$workflow_file" "Materialize target workspace" "strix workflow materializes target repository data separately from trusted scripts"
assert_file_contains "$workflow_file" "target_repository:" "strix workflow_dispatch can target a repository whose PR does not inherit required workflows"
assert_file_contains "$workflow_file" 'REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }}' "strix manual dispatch fetches target repository data instead of the central .github repo"
assert_file_contains "$workflow_file" 'GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}' "strix manual dispatch can use the cross-repo approval token to read private target repositories"
assert_file_contains "$workflow_file" "TARGET_WORKSPACE_SHA" "strix workflow pins target workspace SHA"
assert_file_contains "$workflow_file" "TRUSTED_WORKSPACE=\$trusted_workspace" "strix workflow exports a trusted workspace path"
assert_file_contains "$workflow_file" "git -C \"\$TRUSTED_WORKSPACE\"" "strix workflow runs git only inside trusted workspace"
Expand Down Expand Up @@ -154,7 +158,7 @@ assert_strix_workflow_pr_trigger_hardened() {
in_block { print }
' "$workflow_file"
)"
if [[ "$pr_head_fetch_block" != *'GH_TOKEN: ${{ github.token }}'* ]]; then
if [[ "$pr_head_fetch_block" != *'GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}'* ]]; then
record_failure "strix workflow passes GH_TOKEN to PR head fetch step"
fi
if [[ "$pr_head_fetch_block" != *"gh auth setup-git"* ]]; then
Expand Down Expand Up @@ -396,7 +400,9 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" "Checkout trusted OpenCode coverage contract" "opencode coverage job uses central trusted coverage tooling instead of target-repo copies"
assert_file_contains "$workflow_file" "repository: ContextualWisdomLab/.github" "opencode required workflow checks out the central source repository"
assert_file_contains "$workflow_file" 'ref: ${{ steps.trusted_source.outputs.ref }}' "opencode required workflow checks out the resolved central ref"
assert_file_contains "$workflow_file" 'repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}' "opencode coverage checks out the PR head repository separately from trusted scripts"
assert_file_contains "$workflow_file" "target_repository:" "opencode workflow_dispatch can target a repository whose PR does not inherit required workflows"
assert_file_contains "$workflow_file" 'repository: ${{ github.event.pull_request.head.repo.full_name || github.event.inputs.target_repository || github.repository }}' "opencode coverage checks out the PR head repository separately from trusted scripts"
assert_file_contains "$workflow_file" 'token: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}' "opencode manual dispatch can use the cross-repo approval token to read private target repositories"
assert_file_contains "$workflow_file" "path: pr-head" "opencode coverage keeps PR-head data outside the trusted workflow root"
assert_file_contains "$workflow_file" 'COVERAGE_SOURCE_WORKDIR: ${{ github.workspace }}/pr-head' "opencode coverage measures the PR-head checkout explicitly"
assert_file_not_contains "$workflow_file" "pr_head_ref:" "opencode workflow_dispatch no longer accepts an unused PR head branch input"
Expand Down Expand Up @@ -617,7 +623,9 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
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_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" '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/scripts/ci/collect_failed_check_evidence.sh" '"workflow_run"' "failed-check evidence includes failed same-head workflow runs outside statusCheckRollup"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" "--json databaseId,workflowName,status,conclusion,url,event,headSha" "failed-check evidence scopes supplemental workflow runs with event and head SHA metadata"
Expand Down Expand Up @@ -664,6 +672,8 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_not_contains "$workflow_file" "opencode github run" "opencode review workflow must not use the oversized GitHub agent prompt path"
assert_file_not_contains "$workflow_file" 'repos/${{ github.repository }}' "opencode review workflow must pass repository expressions through env before shell use"
assert_file_contains "$workflow_file" "GH_REPOSITORY:" "opencode review workflow exports repository context through env"
assert_file_contains "$workflow_file" 'GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }}' "opencode manual dispatch routes API calls and review publication to the requested target repository"
assert_file_contains "$workflow_file" 'GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}' "opencode manual dispatch uses the cross-repo approval token for target PR evidence lookups"
assert_file_contains "$workflow_file" 'repos/${GH_REPOSITORY}' "opencode review workflow uses env-backed repository context in shell commands"
assert_file_contains "$workflow_file" "Run OpenCode PR Review (DeepSeek R1)" "opencode review starts with DeepSeek R1"
assert_file_contains "$workflow_file" "MODEL: github-models/deepseek/deepseek-r1-0528" "opencode review starts with a reachable DeepSeek R1 reasoning model"
Expand Down