Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
110 changes: 70 additions & 40 deletions .github/workflows/opencode-review.yml

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,12 @@ jobs:
exit 1

- name: Self-test Strix gate script
timeout-minutes: 10
working-directory: ${{ runner.temp }}/trusted-workspace
run: bash "$TRUSTED_STRIX_GATE_TEST"
run: |
set -euo pipefail
printf 'Running Strix gate self-test with a 10-minute step timeout.\n'
bash "$TRUSTED_STRIX_GATE_TEST"

- name: Gate Strix secrets
id: gate
Expand Down Expand Up @@ -341,6 +345,7 @@ jobs:

- name: Run Strix (quick)
if: steps.gate.outputs.enabled == 'true'
timeout-minutes: 30
# Security invariant for pull_request_target: execute only from the
# trusted base checkout. The gate copies PR-head blobs into an isolated
# temporary scope with execute bits stripped, then scans that scope as
Expand Down Expand Up @@ -384,11 +389,11 @@ jobs:
IS_PR_EVIDENCE_RUN: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && 'true' || 'false' }}
run: |
budget_suffix="TIME""OUT"
process_budget_seconds="3600"
process_budget_seconds="1500"
export "LLM_${budget_suffix}=120"
export "STRIX_MEMORY_COMPRESSOR_${budget_suffix}=10"
export "STRIX_PROCESS_${budget_suffix}_SECONDS=$process_budget_seconds"
export "STRIX_TOTAL_${budget_suffix}_SECONDS=7200"
export "STRIX_TOTAL_${budget_suffix}_SECONDS=1800"
bash "$TRUSTED_STRIX_GATE"

- name: Collect Strix reports for artifact upload
Expand Down
32 changes: 20 additions & 12 deletions PR_GOVERNANCE_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ OpenCode decides; GitHub Actions mutates.
- Prefer `gh pr merge --auto --merge --match-head-commit <head>` when native auto-merge is enabled.
- Use direct `gh pr merge --merge --match-head-commit <head>` only when the repo policy already allows immediate merge.
- OpenCode app-token merges are deprecated; keep app tokens for review publication, not mechanical branch mutation.
- OpenCode approval publication must be bounded. Peer GitHub Checks can be awaited, but the approval step itself must time out instead of running for hours; the current central limit is a 45 minute approval step with 81 peer-check probes at 30 seconds.
- Tool failures are not source findings. Model failure, API transient, update-branch `422/403`, fork/write-permission failure, conflict, failed checks, and stale review state must be reported as distinct scheduler outcomes.

## Live Repository Inventory

Live generated: 2026-06-23 04:18 KST. PR #28 rechecked: 2026-06-23 11:43 KST.
Live generated: 2026-06-23 04:18 KST. PR #28 post-merge refresh: 2026-06-23 16:05 KST.

| Repo | Flow | Default | Auto | Rulesets | Required checks | Stale dismissal | Merge queue | Workflows | Recent merged actor |
|---|---:|---:|---:|---|---|---:|---:|---|---|
| `ContextualWisdomLab/.github` | GitHub Flow | `main` | on | `Lock default branch` | none | true | no | OpenCode Review; PR Review Merge Scheduler; Strix Security Scan | #18 `seonghobae`; #17 `seonghobae`; #2 `seonghobae` |
| `ContextualWisdomLab/.github` | GitHub Flow | `main` | on | `Lock default branch` | none | true | no | OpenCode Review; PR Review Merge Scheduler; Strix Security Scan | #28 `seonghobae` merge `a025be1`; #18 `seonghobae`; #17 `seonghobae` |
| `ContextualWisdomLab/bandscope` | Git Flow | `develop` | on | `Lock default branch` | `ci / build-and-test`, `dependency-review`, `security-audit`, `CodeQL`, `sbom`, `release-preflight`, `gate / build / windows`, `gate / build / macos`, `trivy-fs-scan` | false | no | OpenCode Review; PR Review Merge Scheduler; Strix Security Scan | #427 `github-actions`; #408 `seonghobae`; #405 `seonghobae` |
| `ContextualWisdomLab/clearfolio` | GitHub Flow | `main` | off | `PR` | none | false | no | OpenCode Review; Strix Security Scan | #9 `seonghobae`; #8 `seonghobae`; #7 `seonghobae` |
| `ContextualWisdomLab/codec-carver` | GitHub Flow | `main` | on | `Lock default branch` | none | true | no | OpenCode Review; Scheduled PR Review Merge; Strix Security Scan | #94 `opencode-agent`; #93 `seonghobae`; #90 `seonghobae` |
Expand All @@ -37,7 +38,7 @@ Live generated: 2026-06-23 04:18 KST. PR #28 rechecked: 2026-06-23 11:43 KST.

| Repo | Gap |
|---|---|
| `.github` | PR #28 head `60c821e` is blocked by current-head OpenCode `CHANGES_REQUESTED` and `strix` status failure. Same-head manual Strix run `27996904501` passed self-test but failed `Run Strix (quick)`, so it is not merge evidence. |
| `.github` | PR #28 is merged at `a025be1` after same-head manual Strix run `28007326148`, same-head OpenCode run `28008174977`, unresolved review threads `0`, and guarded merge against head `811446d`. Remaining open PRs #19-#27 and #29-#36 are still blocked by `CHANGES_REQUESTED` and/or `DIRTY`; the scheduler dry run inspected 17 PRs and made no mutation. |
| `bandscope` | Required checks are repo-specific and broad; keep GitHub native auto-merge as the check interpreter. |
| `clearfolio` | Auto-merge is off and the PR Review Merge Scheduler is missing. |
| `codec-carver` | Latest merged sample #94 still used `opencode-agent`; replace the legacy scheduler with the central GitHub Actions path. |
Expand All @@ -53,7 +54,7 @@ Live generated: 2026-06-23 04:18 KST. PR #28 rechecked: 2026-06-23 11:43 KST.
| Repo | Live evidence | Adopt | Reject |
|---|---|---|---|
| `naruon` | `develop`, strict required checks `opencode-review` and `strix`, stale review dismissal enabled. Open PRs show `BEHIND`, `DIRTY`, and `CHANGES_REQUESTED` cases. | Strict current-head evidence and stale-dismissal awareness. | Treating `BEHIND` as merge-ready. |
| `.github` | PR #28 is `MERGEABLE` but `BLOCKED`; required PR-target Strix failed on trusted-base self-test, and same-head manual Strix run `27996904501` also failed at `Run Strix (quick)` after publishing `strix` status failure. The latest OpenCode review still cited the stale PR-target Strix URL instead of the same-head manual Strix failure. | Same-head manual evidence for self-modifying trusted workflow changes, plus explicit handling for failed manual evidence. | Treating stale PR-target failure logs as the only current-head diagnosis after a same-head manual Strix rerun exists. |
| `.github` | PR #28 head `811446d` reached current-head approval after manual Strix run `28007326148` published a successful `strix` status and manual OpenCode run `28008174977` approved the same head; it was merged by `seonghobae` with merge commit `a025be1`. The earlier PR-target Strix failure remains useful only as the reason same-head manual evidence was required. | Same-head manual evidence for self-modifying trusted workflow changes, current-head OpenCode approval, unresolved thread check, and `--match-head-commit` guarded merge. | Treating stale PR-target failure logs as merge blockers after newer same-head evidence exists. |
| `pg-erd-cloud` | Recent PRs #236, #237, #239 were merged by `app/github-actions`. | GitHub Actions as mechanical merge actor with head guard. | Human-only queue draining. |
| `codec-carver` | Recent PR #94 was merged by `app/opencode-agent`, and the repo still has legacy `Scheduled PR Review Merge`. | Native auto-merge path for current-head approved PRs. | OpenCode app as merge actor. |
| `VibeSec` | PR #108 had native auto-merge enabled; #106 merged by `app/github-actions`; #109 merged by human. | Keep native auto-merge as preferred waiting path. | Repo-by-repo actor inconsistency. |
Expand All @@ -69,7 +70,7 @@ The checked-in scheduler already does the minimal central path:
- blocks current-head failed check runs or status contexts before enabling auto-merge;
- updates `BEHIND` only when OpenCode approved the exact current head, using `expected_head_sha`;
- enables native auto-merge only for current-head OpenCode approval;
- dispatches OpenCode when the current head has no OpenCode decision.
- dispatches same-head Strix evidence and then OpenCode when the current head has no OpenCode decision.

Small proof run:

Expand All @@ -78,25 +79,24 @@ $ python3 scripts/ci/pr_review_merge_scheduler.py --self-test
self-test passed

$ python3 scripts/ci/pr_review_merge_scheduler.py --repo ContextualWisdomLab/.github --base-branch main --project-flow github-flow --dry-run --max-prs 40 --no-trigger-reviews
PR #19: block: current-head OpenCode review requested changes
PR #20: block: current-head OpenCode review requested changes
PR #19: block: merge conflict: DIRTY
PR #20: block: merge conflict: DIRTY
PR #21: block: current-head OpenCode review requested changes
PR #22: block: current-head OpenCode review requested changes
PR #22: block: merge conflict: DIRTY
PR #23: block: merge conflict: DIRTY
PR #24: block: current-head OpenCode review requested changes
PR #25: block: current-head OpenCode review requested changes
PR #26: block: current-head OpenCode review requested changes
PR #27: block: current-head OpenCode review requested changes
PR #28: block: current-head OpenCode review requested changes
PR #29: block: current-head OpenCode review requested changes
PR #30: block: current-head OpenCode review requested changes
PR #31: block: current-head OpenCode review requested changes
PR #32: block: current-head OpenCode review requested changes
PR #33: block: current-head OpenCode review requested changes
PR #34: block: current-head OpenCode review requested changes
PR #35: block: current-head OpenCode review requested changes
PR #36: block: current-head OpenCode review requested changes
{"base_branch": "main", "counts": {"block": 18}, "dry_run": true, "inspected": 18, "project_flow": "github-flow"}
PR #36: block: merge conflict: DIRTY
{"base_branch": "main", "counts": {"block": 17}, "dry_run": true, "inspected": 17, "project_flow": "github-flow"}
```

## Rollout List
Expand All @@ -109,7 +109,15 @@ PR #36: block: current-head OpenCode review requested changes

## Remaining Proof Gaps

- A live current-head review -> same-head manual Strix status bridge -> OpenCode approval -> guarded merge trace has been completed on `.github` PR #28.
- No live outdated -> update-branch -> new-head review -> merge/auto-merge trace has been completed yet.
- `update-branch` `422/403` behavior still needs a safe fixture or a real blocked case before claiming standardized handling.
- Required-check interpretation should stay delegated to GitHub native auto-merge until a repo needs immediate merge.
- PR #28 itself cannot prove adoption until the same-head manual Strix failure is diagnosed and OpenCode stops reusing stale PR-target Strix self-test logs as the only failed-check evidence.
- PR #28 proves the self-modifying trusted workflow bootstrap path after newer same-head evidence exists, but it does not prove update-branch behavior, stale approval dismissal after a head change, or cross-repository rollout.
- PR #37 adds a bounded OpenCode approval publication timeout after manual current-head OpenCode run `28011338113` reached the approval step and was observed waiting on peer checks instead of finishing promptly.
- PR #37 current-head run `28012303665` proved 100% coverage/docstring evidence and OpenCode completion for head `184be63`; Strix run `28012303876` proved same-head manual Strix success. The run also exposed that cancelled PR-target helper check `Strix Security Scan/publish-manual-pr-evidence-status` must be superseded by the newer same-head manual `strix` status, not treated as a source finding.
- PR #37 head `9bbf641` exposed a remaining race: OpenCode can finish before same-head manual Strix publishes the superseding `strix` status, causing stale cancelled PR-target Strix checks to become REQUEST_CHANGES. The evidence preparation step now waits, within a 40 minute bound, whenever peer checks are still running, even if completed failed check evidence is already visible.
- The same race also showed that PR `statusCheckRollup` does not see a manual Strix `workflow_dispatch` run until it publishes a commit status. OpenCode evidence preparation now queries current-head `strix.yml` workflow runs directly and treats in-progress same-head Strix runs as peer checks.
- Strix run `28014156427` also reported sensitive log disclosure risk in failed-check evidence handling. The collector now redacts common token, API key, password, secret, authorization, Slack token, and AWS access-key patterns before any failed logs are summarized or embedded in review evidence.
- Strix run `28015621232` reported `GitHub Actions pull_request_target with PR Code Execution` against `.github/workflows/opencode-review.yml`. OpenCode Review is now `workflow_dispatch`-only, and the scheduler dispatches same-head Strix before same-head OpenCode. This follows GitHub's secure-use guidance to avoid `pull_request_target` with untrusted PR checkout/execution: https://docs.github.com/en/actions/reference/security/secure-use and GitHub Security Lab's "Preventing pwn requests": https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/.
- OpenCode run `28017920517` failed without posting a PR review because every model attempt failed to produce a valid control block; the primary `github-models/openai/gpt-5` error was `Request body too large for gpt-5 model. Max size: 4000 tokens.` The prompt now requires reading `bounded-review-evidence.md` instead of inlining `bounded-review-evidence-excerpt.md`.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Branch updates and merges run through the workflow `GITHUB_TOKEN`, so GitHub
records those mechanical mutations as `github-actions[bot]` rather than an
OpenCode app token or a personal token.

OpenCode review execution is `workflow_dispatch`-only. The scheduler dispatches
same-head Strix evidence first, then dispatches OpenCode for the same PR head.
This avoids running PR-head review, CodeGraph, coverage, or PoC code from a
privileged `pull_request_target` OpenCode workflow.

OpenCode approval is evidence-gated. Before approval, the review summary must
name changed files, CodeGraph or structural MCP evidence, a Change Flow DAG,
100% test coverage evidence, 100% docstring coverage evidence, and a concrete
Expand Down
59 changes: 44 additions & 15 deletions scripts/ci/collect_failed_check_evidence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ strip_ansi() {
perl -pe 's/\x1b\[[0-9;?]*[A-Za-z]//g'
}

redact_sensitive_log() {
perl -pe '
s/\b(gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})/[REDACTED_GITHUB_TOKEN]/g;
s/\b(sk-[A-Za-z0-9_-]{20,})/[REDACTED_API_KEY]/g;
s/\b(xox[baprs]-[A-Za-z0-9-]{20,})/[REDACTED_SLACK_TOKEN]/g;
s/\b(AKIA[0-9A-Z]{16})/[REDACTED_AWS_ACCESS_KEY]/g;
s/((?:api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|client[_-]?secret|password|passwd|secret)\s*[:=]\s*)["'\'']?[^"'\''\s]+["'\'']?/${1}[REDACTED]/ig;
s/((?:authorization|proxy-authorization)\s*:\s*(?:bearer|basic)\s+)[A-Za-z0-9._~+\/=-]+/${1}[REDACTED]/ig;
'
}

emit_bounded_file() {
local file_path="$1"
local max_lines="$2"
Expand Down Expand Up @@ -201,20 +212,38 @@ trap cleanup EXIT

manual_success_for_label() {
local label="$1"
local failed_run_id="${2:-}"
local key
local lower_label
local success_context
local success_url
local success_description
local success_run_id

key="${label##*/}"
key="$(printf '%s' "$key" | tr '[:upper:]' '[:lower:]')"
awk -F '\t' -v key="$key" '
tolower($1) == key {
print
found = 1
exit
}
END {
exit found ? 0 : 1
}
' "$manual_success_contexts"
lower_label="$(printf '%s' "$label" | tr '[:upper:]' '[:lower:]')"
case "$lower_label" in
"strix security scan/"*)
key="strix"
;;
esac

while IFS=$'\t' read -r success_context success_url success_description; do
if [ "$(printf '%s' "$success_context" | tr '[:upper:]' '[:lower:]')" != "$key" ]; then
continue
fi
success_run_id="$(printf '%s' "$success_url" | sed -n 's#.*/actions/runs/\([0-9][0-9]*\).*#\1#p')"
if [ -n "$failed_run_id" ] &&
[ -n "$success_run_id" ] &&
[ "$failed_run_id" -ge "$success_run_id" ]; then
continue
fi
printf '%s\t%s\t%s\n' "$success_context" "$success_url" "$success_description"
return 0
done <"$manual_success_contexts"

return 1
}

# shellcheck disable=SC2016
Expand Down Expand Up @@ -350,7 +379,7 @@ while IFS=$'\t' read -r kind label conclusion details_url run_id check_run_id; d
done <"$workflow_run_contexts"

while IFS=$'\t' read -r kind label conclusion details_url run_id check_run_id; do
if success_line="$(manual_success_for_label "$label")"; then
if success_line="$(manual_success_for_label "$label" "$run_id")"; then
IFS=$'\t' read -r success_context success_url success_description <<<"$success_line"
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$kind" \
Expand Down Expand Up @@ -424,7 +453,7 @@ done <"$failed_contexts"
stripped_log_file="$(mktemp)"
tmp_files+=("$log_file" "$stripped_log_file")
if gh run view "$run_id" --repo "$GH_REPOSITORY" --log-failed >"$log_file" 2>&1; then
strip_ansi <"$log_file" >"$stripped_log_file"
strip_ansi <"$log_file" | redact_sensitive_log >"$stripped_log_file"
if [ -s "$stripped_log_file" ]; then
emit_failure_signal_summary "$stripped_log_file" || true
printf '### Failed workflow run log excerpt\n\n'
Expand All @@ -441,7 +470,7 @@ done <"$failed_contexts"
fi
fi
else
strip_ansi <"$log_file" >"$stripped_log_file"
strip_ansi <"$log_file" | redact_sensitive_log >"$stripped_log_file"
printf 'No GitHub Actions job log is available for this failed workflow run.\n\n'
printf '```text\n'
emit_bounded_file "$stripped_log_file" 60
Expand Down Expand Up @@ -493,7 +522,7 @@ done <"$failed_contexts"
--repo "$GH_REPOSITORY" \
--job "$check_run_id" \
--log-failed >"$log_raw" 2>&1; then
strip_ansi <"$log_raw" >"$log_clean"
strip_ansi <"$log_raw" | redact_sensitive_log >"$log_clean"
if [ -s "$log_clean" ]; then
emit_failure_signal_summary "$log_clean" || true
if emit_strix_vulnerability_evidence "$log_clean"; then
Expand All @@ -509,7 +538,7 @@ done <"$failed_contexts"
printf 'The failed job log could not be collected with `gh run view --log-failed`.\n\n'
if [ -s "$log_raw" ]; then
printf '```text\n'
strip_ansi <"$log_raw" | sed -n '1,40p'
strip_ansi <"$log_raw" | redact_sensitive_log | sed -n '1,40p'
printf '\n```\n\n'
fi
fi
Expand Down
Loading
Loading