From 851f8919a8040ca2a5c872b644039ff7ece7501c Mon Sep 17 00:00:00 2001 From: Nguyen Minh Phuc Date: Fri, 17 Jul 2026 10:51:59 +0000 Subject: [PATCH] ci: fix coverage target, pip-audit scope, bandit config - --cov pointed at libs/svcforge_core/domain, a path that does not exist (the package nests one level deeper). Coverage measured 0.00% of the code. Use the module form, which is layout-independent. - pip-audit --strict cannot audit our own editable, not-on-PyPI packages. Audit the locked dependency set instead and keep --strict. - bandit re-reports B608/B104, which ruff's S ruleset already enforces with justified per-line noqa it cannot see. Skipped in config, with reasons. - registry host was git.oci-oci; it is gitea.oci-oci. - integration job set SVCFORGE_PG_DSN; conftest reads SVCFORGE_TEST_DSN. --- .gitea/workflows/ci.yaml | 26 +++++++++++++++++++++++--- pyproject.toml | 17 +++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 641b78f..435e33c 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -76,7 +76,13 @@ jobs: # The gate is on domain/ alone, and only domain/. It is pure, has no I/O, and needs # no mocks — there is no excuse for a gap there. Pointing this at the whole repo # would let untested SQL be paid for by well-tested pure functions. - run: uv run pytest tests/unit --cov=libs/svcforge_core/domain --cov-fail-under=90 + # + # `--cov=svcforge_core.domain` — the MODULE, not a path. `--cov=libs/svcforge_core/domain` + # is a path that does not exist (the package nests one level deeper, at + # libs/svcforge_core/svcforge_core/domain), so coverage measured nothing and reported + # 0.00%. A path-based --cov that misses silently reports 0 rather than erroring, so + # without a --cov-fail-under this reads as a passing coverage gate over no code at all. + run: uv run pytest tests/unit --cov=svcforge_core.domain --cov-fail-under=90 # --- stages 4+5: migrate, then integration against that schema ----------------------- integration: @@ -143,7 +149,10 @@ jobs: # `--with`, not a dev dependency: bandit is a CI tool, not something the project # imports, and ruff's S ruleset already runs its checks in the lint stage. This is # the belt to that suspenders — -ll reports medium severity and above only. - run: uv run --with bandit bandit -r libs services -ll + # + # `bandit[toml]` + `-c pyproject.toml`: without the toml extra bandit cannot read + # its own config and silently ignores it, which looks identical to a clean run. + run: uv run --with 'bandit[toml]' bandit -c pyproject.toml -r libs services -ll - name: gitleaks (secret scan) # Pinned by digest and run directly, so the command is the documented one rather @@ -155,7 +164,18 @@ jobs: - name: pip-audit (dependency CVEs) # --strict fails on an audit error rather than shrugging and reporting clean. - run: uv run --with pip-audit pip-audit --strict + # + # Audits the LOCKED dependency set, not the installed environment. Auditing the env + # means auditing `svcforge` and `svcforge-core` too, which are ours, are installed + # editable, and are not on PyPI — under --strict that is a hard error ("distribution + # marked as editable"), so the choice was to drop --strict or to stop asking PyPI + # about packages it has never heard of. This asks about the 56 that actually came + # from PyPI, and keeps --strict. + run: | + uv export --frozen --no-dev \ + --no-emit-project --no-emit-package svcforge-core \ + -o /tmp/requirements-audit.txt + uv run --with pip-audit pip-audit --strict -r /tmp/requirements-audit.txt # --- stage 9: hadolint --------------------------------------------------------------- dockerfile: diff --git a/pyproject.toml b/pyproject.toml index c7c7f38..66d4f86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,23 @@ known-first-party = ["svcforge_core", "services"] # input that reaches a helm release name. "tests/e2e/**" = ["S101", "S603", "S607"] +[tool.bandit] +# bandit is the belt to ruff's suspenders: ruff's `S` ruleset IS flake8-bandit and runs on +# every file in the lint stage. These two skips are the rules ruff already enforces here, +# where each real site carries an individually justified `# noqa` that bandit cannot see — +# so bandit re-reports them as findings that ruff has already adjudicated. +# +# B608 (SQL built by string) == ruff S608. Every occurrence interpolates `_COLUMNS`, a +# module constant. Tenant input goes through psycopg parameters, never the f-string, +# and ruff fails the build if that ever changes. +# B104 (bind 0.0.0.0) == ruff S104. A container must bind all interfaces; the pod's +# network namespace is the boundary, not the listen address. +# +# Nothing else is skipped. If you add a third, justify it here or you are just turning the +# gate off one rule at a time. +skips = ["B608", "B104"] +exclude_dirs = [".venv", "tests"] + [tool.mypy] strict = true python_version = "3.12"