Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
eaacc50
test(strix): require consolidated metadata admission
seonghobae Sep 6, 2026
c5d4686
ci(strix): consolidate metadata admission
seonghobae Sep 6, 2026
726b601
fix(strix): preserve admission shell bytes
seonghobae Sep 6, 2026
3ae7c80
chore(ci): 최신 main을 Strix 통합 브랜치에 반영
seonghobae Sep 6, 2026
c31aa02
merge: preserve Strix metadata consolidation on current main
seonghobae Sep 6, 2026
ee5567f
test(actions): pin concurrency group keys against comment leakage (#1…
seonghobae Sep 6, 2026
6e014c9
fix(actions): coalesce superseded agent mentions while they are queue…
seonghobae Sep 6, 2026
9aad23c
test(actions): parse the concurrency group instead of slicing the blo…
seonghobae Sep 6, 2026
ad0779b
fix(scheduler): skip review dispatch for a head whose merge tree cann…
seonghobae Sep 6, 2026
49eb9e7
test(actions): anchor cancel-in-progress contracts so a flipped flag …
seonghobae Sep 6, 2026
5c60b5d
test(concurrency): pin the cancel flag as a value for the PR-keyed sc…
seonghobae Sep 6, 2026
74224b2
fix(scheduler): match the run name GitHub actually sends for central …
seonghobae Sep 6, 2026
2396ddc
Revert "fix(scheduler): skip review dispatch for a head whose merge t…
seonghobae Sep 6, 2026
bf0bf0a
fix(audit): stop a ruleset drift from disabling the CodeQL coverage d…
seonghobae Sep 6, 2026
c9052e6
fix(audit): count the repositories examined, not the ones supplied (#…
seonghobae Sep 6, 2026
402aca1
Merge origin/main into the strix metadata consolidation
seonghobae Sep 6, 2026
f162c6b
fix(strix): reject live draft model admission
seonghobae Sep 7, 2026
5a476db
fix(ci): skip backoff after final file-list attempt
seonghobae Sep 7, 2026
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
108 changes: 50 additions & 58 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,74 +104,20 @@ jobs:
# Fails OPEN: an unreadable, empty, or truncated file list scans everything.
if: github.event_name != 'pull_request_target' || (github.event.action != 'closed' && github.event.action != 'converted_to_draft')
runs-on: ubuntu-24.04
timeout-minutes: 5
timeout-minutes: 10
permissions:
contents: read
pull-requests: read
outputs:
code: ${{ steps.scope.outputs.code }}
deps: ${{ steps.scope.outputs.deps }}
steps:
- name: Classify changed paths
id: scope
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
PR: ${{ github.event.pull_request.number }}
EXPECTED_FILES: ${{ github.event.pull_request.changed_files }}
shell: bash
run: |
set -uo pipefail
code=true
deps=true
if [ -n "${PR}" ] && [ -n "${EXPECTED_FILES}" ]; then
changed=""
for attempt in 1 2 3; do
if changed="$(gh api --paginate "repos/${REPO}/pulls/${PR}/files?per_page=100" --jq '.[].filename')" && [ -n "$changed" ]; then
break
fi
changed=""
sleep $((attempt * 3))
done
# GitHub caps /pulls/N/files at 3000 entries; a short list would hide
# source files behind a doc-only verdict, so require an exact count.
if [ -n "$changed" ] && [ "$(printf '%s\n' "$changed" | wc -l | tr -d ' ')" = "${EXPECTED_FILES}" ]; then
code=false
deps=false
while IFS= read -r changed_path; do
case "$changed_path" in
*.md|*.markdown|*.rst|*.png|*.jpg|*.jpeg|*.gif|*.webp|*.bmp|*.ico|LICENSE|LICENSE.txt|COPYING|COPYING.txt|NOTICE|NOTICE.txt|.github/ISSUE_TEMPLATE/*) ;;
*) code=true ;;
esac
case "$changed_path" in
requirements*.txt|*/requirements*.txt|pyproject.toml|*/pyproject.toml|uv.lock|*/uv.lock|pylock.*.toml|*/pylock.*.toml|package.json|*/package.json|package-lock.json|*/package-lock.json|pnpm-lock.yaml|*/pnpm-lock.yaml|yarn.lock|*/yarn.lock|Cargo.toml|*/Cargo.toml|Cargo.lock|*/Cargo.lock|go.mod|*/go.mod|go.sum|*/go.sum|pom.xml|*/pom.xml|build.gradle|*/build.gradle|build.gradle.kts|*/build.gradle.kts|DESCRIPTION|*/DESCRIPTION) deps=true ;;
esac
done <<<"$changed"
else
echo "::notice::changed-scope could not read a complete PR file list; scanning everything."
fi
fi
echo "code=${code}" >> "$GITHUB_OUTPUT"
echo "deps=${deps}" >> "$GITHUB_OUTPUT"
echo "changed-scope code=${code} deps=${deps}"

admit-current-head:
name: Admit current pull request head
if: >-
github.event_name != 'pull_request_target' ||
(github.event.action != 'closed' && github.event.action != 'converted_to_draft')
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
outputs:
admitted: ${{ steps.admission.outputs.admitted }}
target_repository: ${{ steps.admission.outputs.target_repository }}
pr_number: ${{ steps.admission.outputs.pr_number }}
steps:
- name: Verify event metadata against the live pull request
id: admission
timeout-minutes: 5
env:
GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}
EVENT_NAME: ${{ github.event_name }}
Expand Down Expand Up @@ -213,6 +159,52 @@ jobs:
echo "pr_number=${TARGET_PR_NUMBER}"
} >> "$GITHUB_OUTPUT"


- name: Classify changed paths
id: scope
if: steps.admission.outputs.admitted == 'true'
timeout-minutes: 5
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
PR: ${{ github.event.pull_request.number }}
EXPECTED_FILES: ${{ github.event.pull_request.changed_files }}
shell: bash
run: |
set -uo pipefail
code=true
deps=true
if [ -n "${PR}" ] && [ -n "${EXPECTED_FILES}" ]; then
changed=""
for attempt in 1 2 3; do
if changed="$(gh api --paginate "repos/${REPO}/pulls/${PR}/files?per_page=100" --jq '.[].filename')" && [ -n "$changed" ]; then
break
fi
changed=""
sleep $((attempt * 3))
done
# GitHub caps /pulls/N/files at 3000 entries; a short list would hide
# source files behind a doc-only verdict, so require an exact count.
if [ -n "$changed" ] && [ "$(printf '%s\n' "$changed" | wc -l | tr -d ' ')" = "${EXPECTED_FILES}" ]; then
code=false
deps=false
while IFS= read -r changed_path; do
case "$changed_path" in
*.md|*.markdown|*.rst|*.png|*.jpg|*.jpeg|*.gif|*.webp|*.bmp|*.ico|LICENSE|LICENSE.txt|COPYING|COPYING.txt|NOTICE|NOTICE.txt|.github/ISSUE_TEMPLATE/*) ;;
*) code=true ;;
esac
case "$changed_path" in
requirements*.txt|*/requirements*.txt|pyproject.toml|*/pyproject.toml|uv.lock|*/uv.lock|pylock.*.toml|*/pylock.*.toml|package.json|*/package.json|package-lock.json|*/package-lock.json|pnpm-lock.yaml|*/pnpm-lock.yaml|yarn.lock|*/yarn.lock|Cargo.toml|*/Cargo.toml|Cargo.lock|*/Cargo.lock|go.mod|*/go.mod|go.sum|*/go.sum|pom.xml|*/pom.xml|build.gradle|*/build.gradle|build.gradle.kts|*/build.gradle.kts|DESCRIPTION|*/DESCRIPTION) deps=true ;;
esac
done <<<"$changed"
else
echo "::notice::changed-scope could not read a complete PR file list; scanning everything."
fi
fi
echo "code=${code}" >> "$GITHUB_OUTPUT"
echo "deps=${deps}" >> "$GITHUB_OUTPUT"
echo "changed-scope code=${code} deps=${deps}"

cancel-superseded-pr-runs:
if: >-
github.event_name == 'pull_request_target' &&
Expand Down Expand Up @@ -340,8 +332,8 @@ jobs:
done

strix:
needs: [changed-scope, admit-current-head]
if: needs.changed-scope.outputs.code == 'true' && needs.admit-current-head.outputs.admitted == 'true'
needs: [changed-scope]
if: needs.changed-scope.outputs.code == 'true' && needs.changed-scope.outputs.admitted == 'true'
# Large, actively-growing repositories (e.g. contextual-orchestrator) can
# legitimately require well over two hours to scan -- this org's own
# standing operating directive accepts that central OpenCode/Strix/Noema
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
- Raised `hourly-review-repair.yml`'s discovery ceiling from 50 to 200 while rotating deterministic 50-PR deep-inspection windows by hourly run number. The scheduler hydrates only the selected window and stops immediately after its single dispatch, preserving access to newer PRs without quadrupling expensive review/check/comment work. See `docs/doctoring/hourly-review-repair-single-file-consolidation.md`'s 2026-09-03 follow-up.

## [Unreleased]
- Consolidate Strix live-head admission and changed-scope classification into
one bounded read-only metadata job before the security scan.
- Include merge-scheduler entrypoint, core, and regression-test changes in
the existing runtime-quality workflow's trigger and suite selector. Scheduler
workflow edits retain queue checks and also select the full review-repair
Expand Down
76 changes: 76 additions & 0 deletions docs/doctoring/strix-metadata-admission-consolidation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Strix metadata admission consolidation

Status: **Proposed and locally verified**, based on central `.github` commit
`5ea1cc47ec040fa4f6417136f059be637666c2a2`. This is not hosted-run evidence.

## Change and metric

The required Strix workflow previously allocated two read-only metadata jobs:
`changed-scope` and `admit-current-head`. A valid code-changing pull request then
allocated three jobs before completion: those two metadata jobs plus `strix`.
This change moves the byte-identical admission shell ahead of the byte-identical
path classifier in `changed-scope`, publishes `code`, `deps`, `admitted`,
`target_repository`, and `pr_number` from that one job, and makes `strix` depend
only on `changed-scope` while requiring both `code == 'true'` and
`admitted == 'true'`.

The verified source-structure metric is therefore metadata jobs **2 -> 1**.
For `opened`, `reopened`, and `ready_for_review` code-changing PR runs, where
cleanup does not run, total jobs fall **3 -> 2**. A `synchronize` run includes
cleanup and falls **4 -> 3**; an exact dispatch includes the status publisher
and also falls **4 -> 3**. These counts do not claim a hosted queue-time
improvement. A
current exact PR is admitted before its file list is classified; a stale PR
ends successfully with `admitted=false` and never runs the classifier. Direct
push and schedule events make no PR API call and retain fail-open `code=true`.
Exact dispatch validates the live PR but has no native PR changed-file fields,
so the classifier makes no files API call and retains `code=true`. Malformed or
unreadable admission fails the job; empty, unreadable, or count-mismatched file
lists retain the existing fail-open full-scan result.

GitHub documents that a failed or skipped dependency normally propagates to
dependent jobs, which is why admission and classification remain in the same
successful metadata job and the scan reads both outputs. GitHub also requires
step `timeout-minutes` to be a positive number. The metadata job is bounded at
10 minutes and each of its two steps at 5 minutes. See [workflow syntax for
`jobs.<job_id>.needs`](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idneeds)
and [step `timeout-minutes`](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstepstimeout-minutes).

## Permission and latency boundary

Both former jobs had only `contents: read` and `pull-requests: read`; the merged
job keeps exactly that job permission set. The stronger admission credential is
still scoped to the admission step's environment and the classifier receives
only `github.token`. The `actions: write` cleanup job and the scan job's
`id-token: write`/`statuses: write` permissions remain separate. Moazen,
Ahmadian, and Balliu's *Granite: Granular Runtime Enforcement for GitHub Actions
Permissions* explains the general risk of steps sharing job permissions; it
supports keeping unlike privileged work in separate jobs, but it does **not**
establish this change's queue metric. The redistributed v1 PDF is preserved
unchanged at [`docs/papers/granite-granular-runtime-enforcement-github-actions-permissions-v1.pdf`](../papers/granite-granular-runtime-enforcement-github-actions-permissions-v1.pdf),
SHA-256 `5d1dd7b26176de6d5347885867156759928641c8e7415a09869cbfb00cbeb07d`,
under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Citation:
Moazen, M., Ahmadian, A. M., & Balliu, M. (2025). *Granite: Granular Runtime
Enforcement for GitHub Actions Permissions* (v1). arXiv.
https://doi.org/10.48550/arXiv.2512.11602.

The tradeoff is serial latency: admission must finish before classification,
where the old metadata jobs could run in parallel. That delay is bounded by the
5-minute admission step and prevents stale events from spending a second runner.
No model, scan, cleanup, or concurrency behavior is changed.

## Evidence boundary and follow-up

Local regression shells and actionlint verify source wiring and syntax only;
they do not prove hosted output propagation, required-check publication, or
queue latency. Live reads found no separate admission context among the 12
required contexts on `.github` `main` or the 17 required contexts on Naruon
`develop`; organization ruleset
`18156473` could not be read without `admin:org`, so no organization-wide claim
is made. Project linkage is also unverified in this turn because the project was
unavailable and the Mac UI was locked.

The pre-existing workflow-header statement that Strix does not cancel in
progress conflicts with the current `cancel-in-progress: true` expression, but
that text belongs to the independent #1938 concurrency hunk and is intentionally
not mixed into this repair. Track it as a follow-up documentation correction.
Binary file not shown.
14 changes: 14 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -3353,3 +3353,17 @@ queries the check-runs API at its own time, order-independently. The implementin
their change was safe because they had scoped it narrowly, not because they had checked for the name
collision — which is the more useful lesson: **a job name is unique only within one workflow file, and the
same name in another file can carry the opposite safety property.**

### Proposed: consolidate Strix read-only metadata admission

- **G-03 / objective 16:** the Strix required workflow can preserve its scanner,
cleanup, and fail-closed boundaries while folding the separate live-head
admission runner into the changed-scope runner. The source-verified structural
metric is metadata jobs 2 -> 1; total jobs are 3 -> 2 for code-changing
opened/reopened/ready-for-review PR runs, and 4 -> 3 for synchronize or exact
dispatch runs. No old queued-run snapshot is reused as live-effect evidence.
- **G-04 / objective 17:** this is one bounded workflow consolidation, not an
organization-wide completion claim. Hosted queue effect and Project linkage
remain unverified. Design, permission, latency, test, and evidence boundaries
are recorded in
[`docs/doctoring/strix-metadata-admission-consolidation.md`](doctoring/strix-metadata-admission-consolidation.md).
7 changes: 4 additions & 3 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,11 @@ 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" "admit-current-head:" "strix workflow admits the live pull request head before provider execution"
assert_file_contains "$workflow_file" "needs: [changed-scope, admit-current-head]" "strix provider queue waits for live-head admission"
assert_file_not_contains "$workflow_file" " admit-current-head:" "strix workflow consolidates metadata admission into changed-scope"
assert_file_contains "$workflow_file" "needs: [changed-scope]" "strix provider queue waits for consolidated live-head admission"
assert_file_contains "$workflow_file" "needs.changed-scope.outputs.admitted == 'true'" "strix provider scan requires the consolidated admission output"
assert_file_contains "$workflow_file" 'strix-security-scan-${{' "strix workflow coalesces by repository and PR before job admission"
assert_file_not_contains "$workflow_file" 'strix-security-scan-${{ needs.admit-current-head.outputs.target_repository }}-${{' "strix concurrency is not delayed until job admission"
assert_file_not_contains "$workflow_file" 'strix-security-scan-${{ needs.changed-scope.outputs.target_repository }}-${{' "strix concurrency is not delayed until job admission"
assert_file_contains "$workflow_file" "cancel-superseded-pr-runs:" "strix workflow runs superseded-head cleanup outside the provider scan queue"
assert_file_not_contains "$workflow_file" "format('closed-pr-{0}-{1}'" "strix cleanup does not need a second concurrency queue"
assert_file_contains "$workflow_file" 'echo "pr_number=${GITHUB_RUN_ID}"' "strix workflow preserves independent push and schedule evidence"
Expand Down
Loading
Loading