Repository navigation
Conversation
This was referenced Oct 9, 2026
Open
doublewhy
marked this pull request as ready for review
October 9, 2026 08:54
9 of 11 tasks
…or published advisories Upgrade only these four uv.lock entries, within the existing pyproject.toml constraints: - cryptography 49.0.0 -> 50.0.2 (PYSEC-2026-3552) - django 5.2.16 -> 5.2.18 (PYSEC-2026-3717, PYSEC-2026-4035) - pyjwt 2.13.0 -> 2.15.1 (PYSEC-2026-4140 to PYSEC-2026-4152, PYSEC-2026-4183) - sqlparse 0.5.5 -> 0.6.0 (PYSEC-2026-3696 to PYSEC-2026-3699, PYSEC-2026-3923) CI's audit job runs pip-audit --strict over the locked runtime dependencies and fails on dev with 27 known vulnerabilities in these packages (22 distinct advisories: pip-audit lists PYSEC-2026-3552 and PYSEC-2026-3696 to PYSEC-2026-3699 twice); with this lock it reports none. No other package is added, removed or moved. Closes #76
Move ruff check and ruff format --check out of the test job into a lint job that starts with audit and test. A lint failure no longer prevents the tests from running or SonarCloud from analysing them, and sonar now waits only for the coverage report it reads. Run the suite with pytest-xdist (-n auto) in the single test job. Every collected test still runs exactly once, and pytest-cov combines the workers' data into the same coverage.xml, so the quality-gate inputs are unchanged. pytest-xdist joins the dev dependency group; the lock adds pytest-xdist 3.8.0 and execnet 2.1.2 and changes nothing else. Document the CI jobs, their dependencies, test ownership and the local reproduction commands in CONTRIBUTING.md.
sonar runs only when test passes, and Dependabot pull requests skip its scan step. A failing test still stops sonar, so a single push does not report every failing check.
Pushes trigger ci.yml only on dev and main, and sonar runs only when test passes, because it needs test. Say so in the comment above the jobs and in the fast-feedback paragraph of CONTRIBUTING.md, instead of saying that every job runs on every push and that sonar waits only for the coverage report. CONTRIBUTING.md no longer calls ci.yml the full gate: dev and main have no required status checks, and the PR title guard is a separate workflow. It now says what holds for every change: no job is skipped by path, by change scope or by the size of a change. The reproduce command in the audit row writes audit-requirements.txt into the working tree. Ignore that file so that running the command leaves the tree clean.
The lint job only runs ruff, so it never needs the token that actions/checkout writes into .git/config by default. Setting persist-credentials: false removes zizmor's artipacked finding for the new job, so ci.yml is back to dev's three artipacked findings (audit, test and sonar).
doublewhy
force-pushed
the
61-ci-critical-path
branch
from
October 12, 2026 02:35
01643af to
8f90950
Compare
This was referenced Oct 12, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Shortens CI's critical path without removing or weakening a check.
ci.ymlstill runs on every pull request and every push todevandmain, with no path or change-size filter.ruff checkandruff format --checkmove out oftestinto a newlintjob, which starts withauditandtest. A lint failure no longer stops the tests or the Sonar analysis.sonarstarts whentestpasses and analyses itscoverage.xml.testruns the same 148 tests across the runner's 4 CPUs with pytest-xdist. Itscoverage.xmlmatched a serial run's line for line in all six measured runs.Stacked on #74, so merge it after #74. The branch now carries #74's
uv.lockfix (commit6ee2564; #74 is the PR for #76) under this PR's four own commits. That clears CI'sauditjob, which failed here only ondev's lock. The newlintjob's checkout also setspersist-credentials: false.Coverage is ready (the
testverdict, and the earliestsonarstart) at a median of 43 s after run start, against 59 s before. The pytest step drops from a 46 s median to 30 s.Related Issues
Refs #61
Remaining for #61: the after-change median and p95 of final completion and first failure. Both need a full
sonarscan, and every run stops before one (HTTP 403) until the maintainer replaces the repository token (#75); no code change can produce them sooner. This PR adds those timings and completes #61 oncesonarruns, so it usesRefs #61until then. #61's first acceptance criterion (required verification before protected-branch integration) also depends on a repository setting:devandmainrequire no status checks, tracked in #78.Changes
.github/workflows/ci.yml: a newlintjob holds the two ruff steps fromtest, with the same commands and action references as the other jobs. Its checkout setspersist-credentials: false, because the job only runs ruff and never uses the checkout token.testrunsuv run pytest -n auto. The coverage upload,auditandsonarare unchanged, and so are the namesaudit,testandsonar. A comment abovejobs:gives the triggers and the job dependencies.pyproject.toml:pytest-xdist>=3.6joins thedevgroup.uv.lockadds only pytest-xdist 3.8.0 and execnet 2.1.2 (+24/−0).auditchecks runtime dependencies only (--no-dev), so these two additions leave its input unchanged.CONTRIBUTING.md: a new "Continuous integration" section covers each job, what it verifies, when it starts and how to reproduce it locally, plus the fast-feedback path and test ownership (one job, no shards)..gitignore: ignoresaudit-requirements.txt, which theauditreproduce command inCONTRIBUTING.mdwrites, so running that command leaves the working tree clean.uv.lock: the branch carries chore(deps): update locked cryptography, django, pyjwt and sqlparse for published advisories #74's commit6ee2564, so the runtime part of the lock is chore(deps): update locked cryptography, django, pyjwt and sqlparse for published advisories #74's. The rebase merged the two lock diffs without a conflict (Test Plan).astral-sh/setup-uvv9.0.0 → v10.2.0): it conflicts with this PR inci.ymland does not cover the newlintjob. If this PR lands first, recreating chore: bump astral-sh/setup-uv from 9.0.0 to 10.2.0 #69 moves all threesetup-uvreferences inci.yml; if chore: bump astral-sh/setup-uv from 9.0.0 to 10.2.0 #69 lands first, rebasing this branch includes movinglintto v10.2.0.Measurements
Method: job and step times come from
gh run view <id> --json jobs,startedAt, test counts from eachtestjob's log. The clock starts at the run'sstartedAt, so runner pickup is included. A verdict is the job's completion; first failure is the earliest completion of a failed job. Cancelled runs are excluded. p95 is nearest-rank, so with n < 20 it is the maximum.Before, for the two measures #61 names, over the last 40 PR runs (2026-07-12 → 2026-10-04): final completion median 89.5 s, p95 132 s; first failure median 68 s, p95 132 s, over the 19 failed runs.
Like-for-like segments. Before: the 33 CI runs (23 PR, 10 push) from 2026-07-15, when the suite reached 148 tests, to 2026-10-04. After: five
workflow_dispatchruns of this branch at 84968a6 and this PR's run at 775cdeb.auditverdict (job unchanged)testverdict)sonarstart aftertestendssonarjob with a scan (13 non-Dependabot runs)After first failure: median 24 s, p95 58 s. In five of six after-runs it is
audit; in 37901543628 it is the pre-existingsonar403 (58 s). Compare the medians: the before p95s of 115–257 s come from two push runs that queued behind another push, and six runs on one day is a small sample.Per-run detail, check names and evaluated alternatives
gh run list -R OpenRAE/workbench --workflow ci.yml --limit 200 --json databaseId,event,headBranch,conclusion,createdAt,startedAt,attempt. Logs:gh api repos/OpenRAE/workbench/actions/jobs/<job>/logs. The lint verdict before is the end of the "Format check" step insidetest; after, it is the end of thelintjob.auditfailures on Dependabot PRs (2026-08-09 → 10-04); 7 July Dependabot runs whose Sonar scan ran without a token, before the Dependabot skip step existed; and 5 Sonar quality-gate failures on maintainer PRs ("QUALITY GATE STATUS: FAILED"): 29181741353, 29181887912, 29282821131, 29283061862 and 29299763491. 24 of the 40 runs are Dependabot PRs; 17 of them skip the scan step.dev;ci.yml's concurrency group serializes pushes to one ref. None of the six after-runs queued.lintjob finishes. Before, a failing ruff step endedtestwithin a second or two. What changes is that a lint failure no longer suppresses the pytest result or the Sonar analysis.sonarstops at the 403 about 12 s in instead of running a 70 s scan.lintis new.devandmainhave no required status checks (gh api repos/OpenRAE/workbench/branches/<branch>/protection/required_status_checksreturns 404 "Required status checks not enabled" for both), so no protection setting depends on the job names. Requiring checks on both branches is tracked in Dev and main require no status checks before merge #78.sonar. In-job xdist uses the runner's other three CPUs with no extra runner and no artifact merge.pyproject.tomlanduv.lock. A pull request that changes neither file restoresdev's cache, as ci: add an OpenSSF Scorecard workflow and README badge #71's run 37899310319 did. It misses when one of them changes, as in this PR's run 37902221136, or whendev's cache has expired after more than seven days unused: PR run 30737366583 missed seven days after 30192386828, the previous run to use that key, and pull requests kept missing until push run 34800634673 saved it again on 2026-09-14. Dependency download plus install takes about 1–2 s per job. The Sonar scanner download and JRE provisioning take about 6.5 s per scan (29382669364: download 01:52:43.6 → 01:52:46.6, JRE 01:52:50.2 → 01:52:53.6). A cache restore would recover only part of that, and caches written by one PR's runs are not visible to other PRs.Test Plan
uv lock --check("Resolved 95 packages"; the lock diff is additions only),uv run ruff check .("All checks passed!"),uv run ruff format --check .("47 files already formatted"), actionlint 1.7.12 over all workflows (exit 0), anduvx pre-commit run --files .github/workflows/ci.yml CONTRIBUTING.md pyproject.toml uv.lock(pre-commit 4.6.2, exit 0).ci.yml, wording inCONTRIBUTING.mdand one.gitignoreline): actionlint 1.7.12 over all workflows (exit 0); zizmor 1.30.1 reports the same findings onci.ymlas at 775cdeb;uvx pre-commit run --files .github/workflows/ci.yml CONTRIBUTING.md .gitignore(exit 0); ruff 0.15.21, the locked version, throughuvx:check .("All checks passed!") andformat --check .("47 files already formatted");uv lock --check("Resolved 95 packages"). Before the.gitignoreline, theauditexport command fromCONTRIBUTING.mdleft?? audit-requirements.txtingit statusandgit check-ignoreexited 1. With it,git statusstays clean andgit check-ignore -vnames.gitignore:24.uvx zizmor --offline --format plain(1.30.1) onci.yml,devagainst8f90950: unpinned-uses 7 → 9 and artipacked 3 → 3. The two new unpinned-uses are thelintjob's copies of the existingactions/checkout@v7.0.1andastral-sh/setup-uv@v9.0.0references. At01643af, before thepersist-credentials: falsecommit, artipacked was 4. Pinning every action to a commit SHA, and the persisted-credential, template-injection and cache-poisoning findings in the other workflows, are tracked in Workflow lint reports unpinned actions and persisted checkout credentials #77.test_hardening,test_cli,test_collab,test_accounts; 41 tests;-n 4): 41 passed both ways,coverage.xmlhas identical per-file measured and hit lines, and JUnit has the same 41 unique cases as--collect-only. I did not run the full suite locally.6ee2564):git rebase --onto origin/uv-lock-vulnerability-fixes origin/devreplayed the three commits without a conflict, andgit range-diffreports each unchanged (84968a6 = 7923846,775cdeb = 7077137,01643af = 3d6f91c). The rebased tree equalsgit merge-tree --write-treeof the old head01643afand6ee2564(both2b35326).uv lock --checkresolves 95 packages (exit 0).8f90950: actionlint over all workflows (exit 0);uvx pre-commit run --files .github/workflows/ci.yml CONTRIBUTING.md .gitignore pyproject.toml uv.lock(exit 0);uvx ruff@0.15.21 check .("All checks passed!") andformat --check .("47 files already formatted");uv run --frozen pytest -n 4on the same four modules as above, with the merged lock (Django 5.2.18, cryptography 50.0.2, pytest-xdist 3.8.0): 41 passed.uv export --frozen --no-dev --all-extras --no-emit-project --no-hashes --format requirements-txt, thenuvx pip-audit --strict --no-depson the export printed "No known vulnerabilities found" (exit 0). Locally I added--disable-pip, because pip-audit's temporary virtual environment aborts inensurepipon this machine; with--no-depsit audits the same pins. The export's pins equal chore(deps): update locked cryptography, django, pyjwt and sqlparse for published advisories #74's. Ondev's lock the same commands print "Found 27 known vulnerabilities in 4 packages" (exit 1).8f90950: with chore: bump astral-sh/setup-uv from 9.0.0 to 10.2.0 #69's head (6448554)git merge-treereports a content conflict inci.yml; chore: update django requirement from <6,>=5.1 to >=5.1,<7 #63, chore: bump SonarSource/sonarqube-scan-action from 8.2.1 to 8.3.0 #70, ci: add an OpenSSF Scorecard workflow and README badge #71 and chore: use one pre-push Codex review and disable automatic review disposition #72 merge cleanly.lintandtestpassed in all six runs above, each with "148 passed" and TOTAL 1958 statements, 83 missed, 96%. Each run'scoverage-reportartifact matches the serial run 37899310319 from ci: add an OpenSSF Scorecard workflow and README badge #71, which has the samesrc/andtests/asdev: identical per-file measured and hit lines, 1,875 of 1,958 lines hit across 28 files. At 01643af, run 38076112589:lintandtestpass ("4 workers [148 items]", "148 passed", TOTAL 1958 statements, 83 missed, 96%). That run is not in the timing sample, which stays the six runs above.8f90950, run 38196396994:auditpasses ("No known vulnerabilities found"), andlintandtestpass ("4 workers [148 items]", "148 passed", TOTAL 1958 statements, 83 missed, 96%).PR title guard(38196396916), CodeQL actions, javascript-typescript and python (38196393933) and GitGuardian also pass. That run is not in the timing sample.sonaris the only red check, and no code change can fix it: it stops before analysis because SonarCloud rejects the repositorySONAR_TOKEN("HTTP 403 Forbidden" in run 38196396994, as in the earlier run 38076112589). Only the maintainer can replace the secret; tracked in ci: SonarCloud rejects the repository SONAR_TOKEN #75.