ci: tighten lint coverage, make security scans blocking, enforce coverage threshold
Symptom
The CI pipeline (.github/workflows/ci.yml, security.yml) has three discipline gaps that allow regressions to merge silently:
- 5 of the 9 linters listed in
CLAUDE.md are not executed in CI — they only run locally via make lint, which requires the Docker stack. YAML, Markdown, shell, Dockerfile, and GitHub Actions files can land with errors that local contributors do not see and CI does not catch.
bandit and pip-audit in security.yml use continue-on-error: true — they run, they produce findings, but they do not fail the workflow. Weekly security scans become noise instead of signal.
- No coverage threshold enforced on the
pytest job — CLAUDE.md targets ≥70% coverage but nothing blocks a PR that lowers it from 75% to 40%.
All three are CI-level issues that ship in one PR because they share the same review surface (.github/workflows/) and the same risk profile (low-risk YAML edits, dry-run before merge).
Technical Analysis
CICD-02 — Missing linters in CI
CLAUDE.md enumerates 9 linters in make lint. The current CI runs 4:
| Linter |
Local (make lint) |
CI (ci.yml) |
| ruff (check + format) |
✅ |
✅ |
| mypy |
✅ |
✅ |
| ESLint |
✅ |
✅ |
| Prettier |
✅ |
✅ |
| Stylelint |
✅ |
✅ |
| yamllint |
✅ |
❌ |
| markdownlint-cli2 |
✅ |
❌ |
| shellcheck |
✅ |
❌ |
| hadolint |
✅ |
❌ |
| actionlint |
✅ |
❌ |
The five missing linters target: YAML config, Markdown documentation pages, shell scripts in scripts/, Dockerfiles, and the GitHub Actions workflows themselves (a meta-lint). All have well-maintained Docker images and run in seconds.
CICD-03 — Bandit and pip-audit not blocking
# .github/workflows/security.yml (approximate, verify exact lines at implementation)
- name: Run Bandit
run: bandit -r src/
continue-on-error: true # ← findings ignored
- name: Run pip-audit
run: pip-audit
continue-on-error: true # ← findings ignored
continue-on-error: true means the step is never red on the workflow badge regardless of findings. A weekly scan that finds nothing and a weekly scan that finds 10 HIGH-severity findings look identical to the operator. This is the same anti-pattern as CICD-01 / Issue #33, applied to filesystem-level scans rather than image scans.
CICD-04 — No coverage threshold
# .github/workflows/ci.yml — current pytest job
- name: Run tests
run: pytest tests/
No --cov-fail-under=70. CLAUDE.md aspires to 70% coverage; nothing enforces it. A refactor that lowers coverage from 75% to 40% would pass CI green.
Proposed Fix
Fix CICD-02 — Add the 5 missing linters as parallel CI jobs
In .github/workflows/ci.yml, add five jobs (or one matrix job — choose based on local style; matrix is cleaner). Each uses the official Docker image, no setup steps required.
yamllint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<pinned>
- run: docker run --rm -v $(pwd):/work -w /work cytopia/yamllint:1.35 -c .yamllint.yml .
markdownlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<pinned>
- run: docker run --rm -v $(pwd):/work -w /work davidanson/markdownlint-cli2:v0.13 "**/*.md"
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<pinned>
- run: docker run --rm -v $(pwd):/work -w /work koalaman/shellcheck-alpine:v0.10 sh -c 'shellcheck scripts/*.sh'
hadolint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<pinned>
- run: docker run --rm -v $(pwd):/work -w /work hadolint/hadolint:v2.12-alpine hadolint --config .hadolint.yaml frontend/Dockerfile src/Dockerfile
actionlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<pinned>
- run: docker run --rm -v $(pwd):/work -w /work rhysd/actionlint:1.7 -color
Each job is independent; failures of one do not stop the others. Pin every image version (no :latest).
Image versions cited above are illustrative — at implementation time, confirm the latest stable tag and reflect it in the workflow file.
Fix CICD-03 — Remove continue-on-error on bandit and pip-audit
- name: Run Bandit
run: bandit -r src/
- continue-on-error: true
- name: Run pip-audit
run: pip-audit
- continue-on-error: true
Pre-merge dry-run is required: run security.yml on the candidate branch and verify it passes. If it fails:
- Patch the finding (upgrade dependency, suppress with comment + rationale)
- Or add an exception with explicit comment in the appropriate ignore file (
.bandit, pip-audit allowlist)
The PR description must include the dry-run result. This is the same workflow as Issue #33 for Trivy.
Fix CICD-04 — Enforce coverage threshold
- name: Run tests
- run: pytest tests/
+ run: pytest tests/ --cov=src/rackscope --cov-report=xml --cov-report=term --cov-fail-under=70
Choose 70% (matches CLAUDE.md). Verify the current coverage is ≥70% before merging — if it's currently lower, this PR cannot land without first either raising coverage or temporarily lowering the threshold (and filing an issue to raise it back).
Pre-merge dry-run is again required. Document the current coverage figure in the PR description so reviewers can see the baseline.
Optional: upload coverage.xml to Codecov or GitHub artifact for downstream consumption. Not required for this PR.
Test Checklist
Impact and Severity
- Audience affected: contributors and the project maintenance posture. Today: silent regressions on YAML / Markdown / shell / Dockerfile / GH Actions; silent CVE accumulation; silent coverage drift.
- Severity: medium. Not user-facing, but each finding represents a real "guard not loaded" situation.
- Priority: Sprint 3. Independent of all other open issues, ships when convenient. Good batch fix.
Breaking Changes
None for users. Internal CI policy change:
- PRs that introduce new lint issues now block at CI instead of slipping through.
- The first PR to land after this one may need extra cleanup if pre-existing lint debt is uncovered — track in the PR description and fix in scope.
Related
Out of Scope
- Adding any new linter not already in
make lint — only enforce parity with the local pipeline
- Adding mutation testing or fuzzing — possible future addition
- Raising the coverage threshold above 70% — separate work, file a follow-up if coverage grows organically past 80%+
- Migrating from Docker-image-based lint runners to native GitHub Actions — current Docker approach is intentional (matches local
make lint exactly)
ci: tighten lint coverage, make security scans blocking, enforce coverage threshold
Symptom
The CI pipeline (
.github/workflows/ci.yml,security.yml) has three discipline gaps that allow regressions to merge silently:CLAUDE.mdare not executed in CI — they only run locally viamake lint, which requires the Docker stack. YAML, Markdown, shell, Dockerfile, and GitHub Actions files can land with errors that local contributors do not see and CI does not catch.banditandpip-auditinsecurity.ymlusecontinue-on-error: true— they run, they produce findings, but they do not fail the workflow. Weekly security scans become noise instead of signal.pytestjob —CLAUDE.mdtargets ≥70% coverage but nothing blocks a PR that lowers it from 75% to 40%.All three are CI-level issues that ship in one PR because they share the same review surface (
.github/workflows/) and the same risk profile (low-risk YAML edits, dry-run before merge).Technical Analysis
CICD-02 — Missing linters in CI
CLAUDE.mdenumerates 9 linters inmake lint. The current CI runs 4:make lint)ci.yml)The five missing linters target: YAML config, Markdown documentation pages, shell scripts in
scripts/, Dockerfiles, and the GitHub Actions workflows themselves (a meta-lint). All have well-maintained Docker images and run in seconds.CICD-03 — Bandit and pip-audit not blocking
continue-on-error: truemeans the step is never red on the workflow badge regardless of findings. A weekly scan that finds nothing and a weekly scan that finds 10 HIGH-severity findings look identical to the operator. This is the same anti-pattern as CICD-01 / Issue #33, applied to filesystem-level scans rather than image scans.CICD-04 — No coverage threshold
No
--cov-fail-under=70.CLAUDE.mdaspires to 70% coverage; nothing enforces it. A refactor that lowers coverage from 75% to 40% would pass CI green.Proposed Fix
Fix CICD-02 — Add the 5 missing linters as parallel CI jobs
In
.github/workflows/ci.yml, add five jobs (or one matrix job — choose based on local style; matrix is cleaner). Each uses the official Docker image, no setup steps required.Each job is independent; failures of one do not stop the others. Pin every image version (no
:latest).Image versions cited above are illustrative — at implementation time, confirm the latest stable tag and reflect it in the workflow file.
Fix CICD-03 — Remove
continue-on-erroron bandit and pip-auditPre-merge dry-run is required: run
security.ymlon the candidate branch and verify it passes. If it fails:.bandit,pip-auditallowlist)The PR description must include the dry-run result. This is the same workflow as Issue #33 for Trivy.
Fix CICD-04 — Enforce coverage threshold
Choose 70% (matches
CLAUDE.md). Verify the current coverage is ≥70% before merging — if it's currently lower, this PR cannot land without first either raising coverage or temporarily lowering the threshold (and filing an issue to raise it back).Pre-merge dry-run is again required. Document the current coverage figure in the PR description so reviewers can see the baseline.
Optional: upload
coverage.xmlto Codecov or GitHub artifact for downstream consumption. Not required for this PR.Test Checklist
security.ymlruns green with bandit and pip-audit blocking, or surfaced findings are addressed before merge--cov-fail-under=70, current coverage reported in PR descriptionlatesttags)CLAUDE.mdnow run in CI (count match)Impact and Severity
Breaking Changes
None for users. Internal CI policy change:
Related
AUDIT_ARCHITECTURAL.mdOut of Scope
make lint— only enforce parity with the local pipelinemake lintexactly)