Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8436029
fix(sandbox): separate trusted result evidence from stdout
seonghobae Sep 11, 2026
1ca7658
test(sandbox): require binary-safe trusted evidence bundle
seonghobae Sep 12, 2026
74d0154
fix(sandbox): harden trusted result bundle handoff
seonghobae Sep 12, 2026
cc545fd
test(queue): reject draft required-workflow admission
seonghobae Sep 12, 2026
b57a531
fix(queue): skip heavy required jobs on Draft PRs
seonghobae Sep 12, 2026
a4298cf
fix(sandbox): preserve causal failure status
seonghobae Sep 12, 2026
ca9311a
fix(git-data): restore complete queue contract blobs
seonghobae Sep 12, 2026
e64fb47
test(queue): preserve complete Draft-admission RED
seonghobae Sep 12, 2026
228116d
fix(queue): restore guarded exact-tree GREEN
seonghobae Sep 12, 2026
d960c67
merge main into sandbox result handoff repair
seonghobae Sep 12, 2026
de7c1ca
test(queue): cover Draft gitleaks admission
seonghobae Sep 12, 2026
2df3dbb
fix(queue): skip Draft gitleaks admission
seonghobae Sep 12, 2026
fdf8555
test(queue): prove Runtime Quality Ready re-entry RED
seonghobae Sep 12, 2026
719af93
fix(queue): reenter Runtime Quality on Ready
seonghobae Sep 12, 2026
863c214
docs(queue): record complete Draft admission boundary
seonghobae Sep 12, 2026
f9c7a04
fix(sandbox): preserve forwarded output order
seonghobae Sep 12, 2026
0c0e73d
style(queue): normalize Runtime Ready contract EOF
seonghobae Sep 12, 2026
bb42e0c
test(sandbox): keep envelope hidden until streams complete
seonghobae Sep 12, 2026
5a85d0a
test(sandbox): require atomic envelope publication
seonghobae Sep 12, 2026
eda9319
fix(sandbox): publish completed envelope atomically
seonghobae Sep 12, 2026
f111da9
test(sandbox): cover atomic publication edges
seonghobae Sep 12, 2026
5e768ac
test(runtime): require sandbox evidence path admission
seonghobae Sep 12, 2026
ad6fec5
fix(runtime): admit sandbox evidence changes
seonghobae Sep 12, 2026
3648b84
test(runtime): reject vacuous sandbox path admission
seonghobae Sep 12, 2026
dc4291f
merge: integrate atomic sandbox evidence into runtime owner
seonghobae Sep 12, 2026
d147388
fix(runtime): execute sandbox evidence suite
seonghobae Sep 12, 2026
b671555
test(runtime): reject vacuous trigger slice
seonghobae Sep 12, 2026
2c00900
fix(runtime): slice exact trigger block
seonghobae Sep 12, 2026
cc6e39f
docs(runtime): bind non-vacuous sandbox admission
seonghobae Sep 12, 2026
9781003
merge(main): resolve #2109 conflicts; keep Draft admission and sandbo…
seonghobae Sep 18, 2026
ce8c630
test(runtime): align Semgrep admission contract with Draft guard
seonghobae Sep 19, 2026
813aa75
merge(main): adopt protected GitHub API authority foundation
seonghobae Sep 19, 2026
db84349
test(runtime): bind Semgrep Draft guard to real job
seonghobae Sep 19, 2026
5bd73be
merge: stack Draft admission on canonical Strix owner
seonghobae Sep 19, 2026
6c32c7e
fix(runtime): admit stacked Ready pull requests
seonghobae Sep 19, 2026
0f821ed
test(ci): require Draft transition cancellation generation
seonghobae Sep 19, 2026
4bcbe95
fix(ci): cancel Ready runs on Draft transition
seonghobae Sep 19, 2026
f62172a
merge: integrate canonical Strix fixture head
seonghobae Sep 20, 2026
ef0304b
test(actions): reproduce stale-head required-workflow cancellation
seonghobae Sep 20, 2026
42e3f7a
chore(actions): withdraw authority-conflicting stale-head oracle
seonghobae Sep 20, 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
41 changes: 37 additions & 4 deletions .github/workflows/agent-review-runtime-quality-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ name: Agent Review Runtime Quality CI

on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
paths:
- ".github/workflows/agent-review-runtime-quality-ci.yml"
- "scripts/ci/sandboxed_verify.py"
- "tests/test_sandboxed_verify.py"
- "tests/test_required_workflow_queue_contract.py"
- ".github/workflows/noema-review.yml"
- ".github/actions/noema-review/two_phase.py"
- "tests/test_noema_reviewer_token_lifetime.py"
Expand Down Expand Up @@ -126,6 +129,7 @@ permissions:
jobs:
agent_review_runtime_quality:
name: agent-review-runtime-quality
if: github.event.pull_request.draft == false
runs-on: ubuntu-24.04
timeout-minutes: 25
env:
Expand Down Expand Up @@ -164,6 +168,7 @@ jobs:
opencode_suite=false
strix_suite=false
queue_suite=false
sandbox_suite=false
review_repair_suite=false
commercial_readiness_suite=false
exact_artifact_suite=false
Expand All @@ -175,6 +180,7 @@ jobs:
opencode_suite=true
strix_suite=true
queue_suite=true
sandbox_suite=true
review_repair_suite=true
commercial_readiness_suite=true
exact_artifact_suite=true
Expand Down Expand Up @@ -241,6 +247,13 @@ jobs:
scripts/ci/current_head_run_coalescer.py)
queue_suite=true
;;
tests/test_required_workflow_queue_contract.py)
queue_suite=true
;;
scripts/ci/sandboxed_verify.py|\
tests/test_sandboxed_verify.py)
sandbox_suite=true
;;
.github/workflows/pr-review-fix-scheduler.yml|\
scripts/ci/pr_review_fix_scheduler.py|\
scripts/ci/pr_review_merge_scheduler.py|\
Expand Down Expand Up @@ -321,6 +334,7 @@ jobs:
echo "opencode=$opencode_suite"
echo "strix=$strix_suite"
echo "queue=$queue_suite"
echo "sandbox=$sandbox_suite"
echo "review_repair=$review_repair_suite"
echo "commercial_readiness=$commercial_readiness_suite"
echo "exact_artifact=$exact_artifact_suite"
Expand All @@ -346,7 +360,7 @@ jobs:
-r "${RUNNER_TEMP}/strix-quality-requirements.txt"

- name: Install exact review dependencies
if: steps.affected_suites.outputs.noema == 'true' || steps.affected_suites.outputs.opencode == 'true' || steps.affected_suites.outputs.review_repair == 'true' || steps.affected_suites.outputs.exact_artifact == 'true'
if: steps.affected_suites.outputs.noema == 'true' || steps.affected_suites.outputs.opencode == 'true' || steps.affected_suites.outputs.sandbox == 'true' || steps.affected_suites.outputs.review_repair == 'true' || steps.affected_suites.outputs.exact_artifact == 'true'
run: >-
python -m pip install --disable-pip-version-check --require-hashes
-r requirements-opencode-review-ci-hashes.txt -r requirements-noema-document-ci-hashes.txt
Expand Down Expand Up @@ -420,8 +434,27 @@ jobs:
if: steps.affected_suites.outputs.queue == 'true'
run: |
set -euo pipefail
python -m pytest -q tests/test_current_head_coalescer_self_cancellation.py
python -m compileall -q tests/test_current_head_coalescer_self_cancellation.py
python -m pytest -q \
tests/test_current_head_coalescer_self_cancellation.py \
tests/test_required_workflow_queue_contract.py
python -m compileall -q \
tests/test_current_head_coalescer_self_cancellation.py \
tests/test_required_workflow_queue_contract.py

- name: Verify sandbox evidence contracts
if: steps.affected_suites.outputs.sandbox == 'true'
run: |
set -euo pipefail
python -m coverage erase
python -m coverage run --branch -m pytest -q tests/test_sandboxed_verify.py
python -m coverage report \
--include=scripts/ci/sandboxed_verify.py \
--show-missing \
--fail-under=100
python -m interrogate --fail-under 100 scripts/ci/sandboxed_verify.py
python -m compileall -q \
scripts/ci/sandboxed_verify.py \
tests/test_sandboxed_verify.py

- name: Verify scheduler and contextual-orchestrator review-repair contracts
if: steps.affected_suites.outputs.review_repair == 'true'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ name: CodeQL PR

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, closed]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft, closed]
# Do not restrict the base ref: the org required-workflow ruleset already
# scopes this to each repository's actual default branch via
# ref_name: ["~DEFAULT_BRANCH"], whatever it is named. A hardcoded
Expand Down Expand Up @@ -55,7 +55,7 @@ permissions:
jobs:
detect-languages:
name: Detect CodeQL languages
if: github.event.action != 'closed'
if: github.event.action != 'closed' && github.event.pull_request.draft == false
runs-on: ubuntu-24.04
permissions:
contents: read
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/python-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ name: Python Security

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, closed]
branches: [main, master, develop]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft, closed]
push:
branches: [main, master, develop]
# Periodic full-repo coverage so non-PR drift is caught (the removed local
Expand All @@ -45,7 +44,9 @@ permissions:
jobs:
detect-python:
name: Detect Python
if: github.event.action != 'closed'
if: >-
github.event.action != 'closed' &&
(github.event_name != 'pull_request' || github.event.pull_request.draft == false)
runs-on: ubuntu-24.04
outputs:
has_python: ${{ steps.detect.outputs.has_python }}
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/sast-semgrep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ name: SAST Semgrep

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, closed]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft, closed]
# Scan every PR base ref, including feature branches used by stacked PRs.
push:
branches: [main, master, develop]
Expand Down Expand Up @@ -49,7 +49,8 @@ jobs:
# docs/doctoring/required-workflow-path-filter-boundary.md.
# Fails OPEN: an unreadable, empty, or truncated file list scans everything.
# The gate lives inside this job as a step-level guard (one runner, not two).
if: github.event.action != 'closed'
# Draft PRs skip Semgrep; lifecycle events also retire prior Ready work.
if: github.event.action != 'closed' && (github.event_name != 'pull_request' || github.event.pull_request.draft == false)
runs-on: ubuntu-24.04
permissions:
contents: read
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ name: Security Scan

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, closed]
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft, closed]
# Do not restrict the base ref: stacked PRs must receive the same
# diff-scoped OSV/dependency and repo-wide Trivy gate as default-branch PRs.

Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
# here and consumed through `needs`. See
# docs/doctoring/required-workflow-path-filter-boundary.md.
# Fails OPEN: an unreadable, empty, or truncated file list scans everything.
if: github.event.action != 'closed'
if: github.event.action != 'closed' && github.event.pull_request.draft == false
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
Expand Down Expand Up @@ -435,7 +435,7 @@ jobs:
# push, schedule, and manual backstops remain in secret-scan.yml.
gitleaks:
name: gitleaks (secret scan)
if: github.event.action != 'closed' && github.repository == 'ContextualWisdomLab/.github'
if: github.event.action != 'closed' && github.event.pull_request.draft == false && github.repository == 'ContextualWisdomLab/.github'
runs-on: ubuntu-24.04
permissions:
contents: read
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.d/20260920-draft-transition-cancels-ready-runs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Draft transitions retire Ready workflow runs

- The five heavy required workflows now subscribe to `converted_to_draft`.
Their existing pull-request concurrency groups cancel queued or running
Ready work, while the Draft job guards keep the replacement run runner-free.
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@

- `.github/workflows/codeql-pr.yml`'s `analyze-head` and `dispatch-current-head` jobs called `gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}"` and later `repos/${TARGET_REPOSITORY}/commits/${PR_HEAD_SHA}/statuses` while holding only `contents: read` (plus `id-token: write`, and `actions: read` on the coordinator job) -- reads GitHub's REST contract gates behind the `pull-requests: read` and `statuses: read` fine-grained permissions on a private repository. Public consumers never surfaced this because GET on a public repository needs no such grant, but private consumer ContextualWisdomLab/late-life-anxiety-reanalysis's PR #10 (head `a1cd5bc6783c6510dfcf937f523c733366e82213`, run `34700410434`) failed both required-workflow jobs (`103571590442`, `103571810868`) at their first API call with `gh: Resource not accessible by integration (HTTP 403)`. Both jobs now also hold `pull-requests: read` and `statuses: read`; no write permission is added anywhere, and `actions: write` stays absent, so `tests/test_codeql_pr_workflow_contract.py::test_codeql_required_workflow_does_not_gain_actions_write` needed no change. New regression test `test_codeql_pr_jobs_hold_read_grants_private_consumers_need` pins the exact grant set. See `docs/doctoring/codeql-pr-private-consumer-read-permissions.md`. Refs ContextualWisdomLab/late-life-anxiety-reanalysis#10.

### Sandboxed verification emits a versioned, binary-safe trusted result bundle

- `scripts/ci/sandboxed_verify.py --result-file <path>` now keeps command
stdout, command stderr, and the wrapper-controlled JSON envelope in three
exclusive sibling files. The stream files preserve arbitrary and large
binary bytes exactly; their SHA-256 digests and byte lengths are bound into
the `sandboxed_verify.execution.v1` envelope with argv, exit code, explicit
completed/timeout/copy-rejection/internal-error state, runtime identity,
requested network mode, and allowed environment names. Result-directory
traversal uses directory file descriptors with no-follow semantics for every ancestor, and every bundle
file uses exclusive creation, closing the nested-symlink and substitution
races in the first result-file implementation. A bounded evidence-write
failure returns 125 without a traceback when the command succeeded, preserves
an existing command/timeout/copy-rejection failure code, and cannot skip
temporary sandbox cleanup unless `--keep-sandbox` explicitly requests
retention. The envelope explicitly records that this helper supplies a copied
workspace and scrubbed environment, not OS process isolation or enforced
network policy. Legacy stdout-marker mode remains available for human-only
calls. Refs #2086, #2088.

### Failed-check finding names the Strix sandbox instead of the gateway

- `opencode-review-dispatch.yml`'s `emit_strix_provider_failure_finding` rendered one fixed finding for every `STRIX_PROVIDER_UNAVAILABLE` line, whose Root cause read "The contextual-orchestrator gateway or its discovered provider pool was unavailable for this run". `#1953` had just given the Strix sandbox bootstrap failure its own second verdict token (`STRIX_SANDBOX_UNAVAILABLE`) precisely because that attribution is wrong for it -- the sandbox container never reaches its Caido proxy, so the run dies before the gateway serves anything -- and this consumer re-applied the wrong attribution one step downstream, into the review findings and the failure census. The emitter now branches on the second token: a sandbox verdict gets a finding that names Strix's sandbox, says the verdict does not name the gateway, and tells the reader not to change gateway or provider configuration on its strength. A `STRIX_PROVIDER_UNAVAILABLE` line without the token keeps its existing text verbatim, so the gateway class has no regression surface. No test covered this finding text at all before (`gateway or its discovered provider pool` matched nothing under `tests/`); `tests/test_opencode_dispatch_strix_sandbox_finding.py` now runs the production emitter from the published run block and pins both directions plus the no-signal case. Refs #1953, #1935.
Expand Down Expand Up @@ -197,6 +217,20 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]
- **Stop Draft PR pushes from consuming five required-workflow runner lanes.**
Every independent entry job in Runtime Quality, CodeQL, Security Scan
(including its document-sensitive Gitleaks gate), Python Security, and SAST
now skips while a pull request is Draft. Existing
`ready_for_review` triggers create fresh exact-head evidence after review
admission; Runtime Quality now explicitly subscribes to that event as well.
Runtime Quality and Python Security no longer exclude stacked PRs by base
branch name, matching the base-agnostic Security Scan, SAST, and CodeQL gates.
Runtime Quality's path selector now executes the 100% branch-coverage and
public-doc gate whenever `sandboxed_verify.py` or its contract changes;
the selector contract slices the actual trigger block instead of matching
paths vacuously elsewhere in the workflow.
Push, schedule, and repository-dispatch coverage remains intact.
A contract pins both pull-request-only and mixed-event guards.
- **Pin `opencode-review-dispatch.yml` off the starved floating `ubuntu-latest` image.**
The 2026-09-01 floating-image fix (see that entry below) pinned `strix.yml`,
`opencode-review.yml`, and `noema-review.yml` -- the three required-check
Expand Down
86 changes: 86 additions & 0 deletions docs/doctoring/draft-required-workflow-admission-20260912.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Draft required-workflow admission

## Problem and exact evidence

Pull request #2106 head `24bb6591ab7df23558cb793b4af60c567ff9da97`
generated Security Scan `34688578677`, CodeQL PR `34688578675`, SAST
`34688578674`, Python Security `34688578683`, and Runtime Quality
`34688578679` while the pull request was Draft. Restoring Ready at the same
head generated a second set. The first four runs were cancelled after queued
jobs had already entered admission; Runtime Quality had already consumed a
runner and completed. This is same-head lifecycle queue waste, not stale source
or a test failure.

The first successor canary exposed two independent omissions. Security Scan
skipped its `changed-scope` path but admitted independent Gitleaks job
`103542086113`. After that guard was repaired, Ready restoration generated four
security workflows but no Runtime Quality run because that workflow relied on
the default `pull_request` activity set, which excludes `ready_for_review`.
Current-head review then found that sandbox evidence paths started Runtime
Quality without selecting or executing a sandbox contract suite. RED
`3648b848` requires non-vacuous selection and GREEN `d1473882` adds the
100% branch-coverage/public-doc gate plus queue-contract execution. A second
RED `b6715554` proves the selector test's literal `\\n` split retained the
entire workflow; GREEN `2c00900e` restricts assertions to the actual trigger
block.

The 2026-09-20 stacked canary reproduced a fourth omission. Exact head
`5bd73bef20010ea020ab1912ce9564cea8f4ffb5` targeted the canonical Strix owner
branch and changed all five required workflow paths. Its Ready transition
created Security Scan `35476467884`, SAST `35476467914`, and CodeQL
`35476467908`, but no Runtime Quality or Python Security generation. Both
missing workflows restricted `pull_request.branches` to default-like names;
the three working workflows intentionally accepted every PR base.

## Constraints and selected repair

The workflows must keep `ready_for_review`, `converted_to_draft`, PR-keyed
concurrency, and their existing close-event behavior. Security workflows that
also run on push, schedule, or `repository_dispatch` must not lose those
non-PR paths. Trigger
filters alone are insufficient for organization required workflows, so the
repair uses the existing job-level policy boundary:

- pull-request-only workflows require `pull_request.draft == false` on every
independent entry job, including both Security Scan `changed-scope` and its
document-sensitive `gitleaks` gate;
- mixed-event workflows allow every non-PR event and require non-Draft state
only for pull-request events;
- downstream jobs remain unchanged and naturally skip through `needs` when the
admission job skips.
- Runtime Quality explicitly subscribes to `ready_for_review`, so its Draft
skip cannot strand the exact head when review admission opens.
- All five heavy required workflows subscribe to `converted_to_draft`; their
PR-keyed `cancel-in-progress` run retires queued or running Ready work while
the Draft job guard keeps the replacement generation runner-free.
- Runtime Quality and Python Security do not restrict pull-request base names;
stacked owner branches receive the same Ready admission as default branches.
- Sandbox verifier changes select a dedicated suite with 100% branch coverage,
100% public documentation, compilation, and the queue selector contract.
- Trigger-path assertions exclude the workflow `jobs` block, preventing a
matching path elsewhere from satisfying admission tests.

No new workflow, dependency, scheduler, token, or status context is added.

## Alternatives rejected

- Removing `ready_for_review` would strand Draft-origin PRs without fresh
evidence when they become reviewable.
- Adding head SHA to concurrency would not prevent the same-head lifecycle
duplication and would weaken close-event cancellation.
- Omitting `converted_to_draft` leaves Ready work queued after a PR returns to
Draft because no same-group replacement run exists to cancel it.
- Enumerating current owner branch names would require mutable central policy
for every future stack; omitting the pull-request base filter is the existing
Security Scan, SAST, and CodeQL contract and keeps push branch filters intact.

## Verification and follow-up

`tests/test_required_workflow_queue_contract.py` binds all five workflows and
all independent entry-job guards while preserving the existing close-event
contract, requiring Runtime Quality Ready admission on default and stacked
bases, executing sandbox evidence contracts, and limiting trigger assertions
to the actual trigger block. The proposal is not
complete until exact-head hosted Checks and independent review pass, it merges
through ordinary protection, and a post-merge Draft→Ready canary shows skipped
Draft jobs followed by one fresh Ready generation.
Loading
Loading