Skip to content

Commit 78ee765

Browse files
committed
ci: split dependency audit schedule matrix
Assisted-by: Codex (model: GPT-5, autonomous)
1 parent 51105a8 commit 78ee765

2 files changed

Lines changed: 70 additions & 36 deletions

File tree

.github/workflows/security.yml

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,36 @@ on:
1414

1515
jobs:
1616
dependency-audit:
17-
name: Dependency audit (${{ matrix.os }}, Python ${{ matrix.python-version }})
17+
name: Dependency audit
18+
if: ${{ github.event_name != 'schedule' }}
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
23+
with:
24+
fetch-depth: 2
25+
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
28+
29+
- name: Set up Python
30+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
31+
with:
32+
python-version: "3.13"
33+
34+
- name: Check committed audit requirements are current
35+
env:
36+
DEPENDENCY_DIFF_BASE: ${{ github.event.pull_request.base.sha || github.event.before || '' }}
37+
DEPENDENCY_DIFF_HEAD: ${{ github.sha }}
38+
GENERATED_REQUIREMENTS: ${{ runner.temp }}/security-audit-requirements.txt
39+
run: python .github/scripts/check_security_requirements.py
40+
41+
- name: Run pip-audit (committed requirements)
42+
run: uvx --from pip-audit==2.10.0 pip-audit --disable-pip --require-hashes -r .github/security-audit-requirements.txt --progress-spinner off
43+
44+
dependency-audit-scheduled:
45+
name: Dependency audit scheduled (${{ matrix.os }}, Python ${{ matrix.python-version }})
46+
if: ${{ github.event_name == 'schedule' }}
1847
runs-on: ${{ matrix.os }}
1948
strategy:
2049
fail-fast: false
@@ -24,8 +53,6 @@ jobs:
2453
steps:
2554
- name: Checkout
2655
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27-
with:
28-
fetch-depth: 2
2956

3057
- name: Install uv
3158
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
@@ -37,28 +64,15 @@ jobs:
3764

3865
# The committed .github/security-audit-requirements.txt is generated with
3966
# --universal (resolves across all interpreters/platforms) and is what
40-
# push/PR runs audit. The scheduled job instead compiles per matrix
41-
# entry with --python-version so it can surface advisories in wheels that
42-
# only resolve on a specific interpreter (e.g. 3.11-only) — coverage the
43-
# universal file may not exercise. This broadening is intentional; PR runs
44-
# trade that depth for determinism against the committed snapshot.
67+
# push/PR/workflow_dispatch runs audit. The scheduled job instead compiles
68+
# per matrix entry with --python-version so it can surface advisories in
69+
# wheels that only resolve on a specific interpreter (e.g. 3.11-only) —
70+
# coverage the universal file may not exercise. This broadening is
71+
# intentional; non-scheduled runs trade that depth for determinism against
72+
# the committed snapshot.
4573
- name: Compile scheduled audit requirements
46-
if: ${{ github.event_name == 'schedule' }}
4774
run: |
4875
uv pip compile pyproject.toml --extra test --python-version "${{ matrix.python-version }}" --upgrade --generate-hashes --quiet --output-file "${{ runner.temp }}/spec-kit-audit-requirements.txt"
4976
5077
- name: Run pip-audit (scheduled live resolution)
51-
if: ${{ github.event_name == 'schedule' }}
5278
run: uvx --from pip-audit==2.10.0 pip-audit --disable-pip --require-hashes -r "${{ runner.temp }}/spec-kit-audit-requirements.txt" --progress-spinner off
53-
54-
- name: Check committed audit requirements are current
55-
if: ${{ github.event_name != 'schedule' }}
56-
env:
57-
DEPENDENCY_DIFF_BASE: ${{ github.event.pull_request.base.sha || github.event.before || '' }}
58-
DEPENDENCY_DIFF_HEAD: ${{ github.sha }}
59-
GENERATED_REQUIREMENTS: ${{ runner.temp }}/security-audit-requirements.txt
60-
run: python .github/scripts/check_security_requirements.py
61-
62-
- name: Run pip-audit (committed requirements)
63-
if: ${{ github.event_name != 'schedule' }}
64-
run: uvx --from pip-audit==2.10.0 pip-audit --disable-pip --require-hashes -r .github/security-audit-requirements.txt --progress-spinner off

tests/test_security_workflow.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ def _step(job_name: str, step_name: str) -> dict:
6161
raise AssertionError(f"Step {step_name!r} not found in job {job_name!r}.")
6262

6363

64+
def _job_run_text(*job_names: str) -> str:
65+
workflow = _load_security_workflow()
66+
return "\n".join(
67+
step.get("run", "")
68+
for job_name in job_names
69+
for step in workflow["jobs"][job_name]["steps"]
70+
)
71+
72+
6473
def _load_sync_script():
6574
spec = importlib.util.spec_from_file_location(
6675
"check_security_requirements",
@@ -77,27 +86,26 @@ class TestDependencyAuditWorkflow:
7786
"""Guard the dependency-audit security workflow."""
7887

7988
def test_dependency_audit_uses_committed_requirements_for_prs_and_pushes(self):
80-
scheduled_compile = _step("dependency-audit", "Compile scheduled audit requirements")
81-
scheduled_audit = _step("dependency-audit", "Run pip-audit (scheduled live resolution)")
89+
workflow = _load_security_workflow()
90+
job = workflow["jobs"]["dependency-audit"]
8291
committed_audit = _step("dependency-audit", "Run pip-audit (committed requirements)")
8392
sync_check = _step("dependency-audit", "Check committed audit requirements are current")
93+
setup_python = _step("dependency-audit", "Set up Python")
8494

85-
assert scheduled_compile["if"] == "${{ github.event_name == 'schedule' }}"
86-
assert WORKFLOW_COMPILE_SCHEDULED_TEST_EXTRA_DEPS in scheduled_compile["run"]
87-
assert scheduled_audit["if"] == "${{ github.event_name == 'schedule' }}"
88-
assert scheduled_audit["run"] == WORKFLOW_LIVE_PIP_AUDIT
89-
assert sync_check["if"] == "${{ github.event_name != 'schedule' }}"
95+
assert job["if"] == "${{ github.event_name != 'schedule' }}"
96+
assert job["runs-on"] == "ubuntu-latest"
97+
assert "strategy" not in job
98+
assert setup_python["with"]["python-version"] == "3.13"
9099
assert sync_check["env"]["DEPENDENCY_DIFF_BASE"] == (
91100
"${{ github.event.pull_request.base.sha || github.event.before || '' }}"
92101
)
93102
assert sync_check["env"]["DEPENDENCY_DIFF_HEAD"] == "${{ github.sha }}"
94103
assert sync_check["run"] == WORKFLOW_SYNC_SCRIPT
95-
assert committed_audit["if"] == "${{ github.event_name != 'schedule' }}"
96104
assert committed_audit["run"] == LOCAL_PIP_AUDIT
97105

98-
dependency_job_text = "\n".join(
99-
step.get("run", "")
100-
for step in _load_security_workflow()["jobs"]["dependency-audit"]["steps"]
106+
dependency_job_text = _job_run_text(
107+
"dependency-audit",
108+
"dependency-audit-scheduled",
101109
)
102110
protection_text = (
103111
dependency_job_text
@@ -133,13 +141,25 @@ def test_security_workflow_triggers(self):
133141
assert "workflow_dispatch" in triggers
134142
assert triggers["schedule"] == [{"cron": "17 4 * * 1"}]
135143

136-
def test_dependency_audit_runs_supported_python_os_matrix(self):
144+
def test_scheduled_dependency_audit_runs_supported_python_os_matrix(self):
137145
workflow = _load_security_workflow()
138-
matrix = workflow["jobs"]["dependency-audit"]["strategy"]["matrix"]
146+
job = workflow["jobs"]["dependency-audit-scheduled"]
147+
matrix = job["strategy"]["matrix"]
148+
scheduled_compile = _step(
149+
"dependency-audit-scheduled",
150+
"Compile scheduled audit requirements",
151+
)
152+
scheduled_audit = _step(
153+
"dependency-audit-scheduled",
154+
"Run pip-audit (scheduled live resolution)",
155+
)
139156

157+
assert job["if"] == "${{ github.event_name == 'schedule' }}"
140158
assert matrix["os"] == ["ubuntu-latest", "windows-latest"]
141159
assert matrix["python-version"] == ["3.11", "3.12", "3.13"]
142-
assert workflow["jobs"]["dependency-audit"]["runs-on"] == "${{ matrix.os }}"
160+
assert job["runs-on"] == "${{ matrix.os }}"
161+
assert WORKFLOW_COMPILE_SCHEDULED_TEST_EXTRA_DEPS in scheduled_compile["run"]
162+
assert scheduled_audit["run"] == WORKFLOW_LIVE_PIP_AUDIT
143163

144164
def test_pip_audit_is_pinned(self):
145165
workflow_text = SECURITY_WORKFLOW.read_text(encoding="utf-8")

0 commit comments

Comments
 (0)