diff --git a/.gitea/workflows/update-badges.yml b/.gitea/workflows/update-badges.yml index 7db0341..34b9871 100644 --- a/.gitea/workflows/update-badges.yml +++ b/.gitea/workflows/update-badges.yml @@ -54,11 +54,23 @@ jobs: echo "percent=$PERCENT" >> $GITHUB_OUTPUT echo "Coverage: $PERCENT%" + - name: Extract core (critical-module) coverage percentage + id: core_coverage + run: | + # Reuses the .coverage data from the previous step. The core list is + # the single source of truth in scripts/critical-modules.txt; every + # core module is unit-tested, so the unit-only run is accurate for it. + INCLUDE=$(grep -vE '^[[:space:]]*(#|$)' scripts/critical-modules.txt | paste -sd, -) + PERCENT=$(python -m coverage report --include="$INCLUDE" 2>/dev/null | grep '^TOTAL' | grep -oP '\d+(?=%)' | tail -1) + echo "percent=$PERCENT" >> $GITHUB_OUTPUT + echo "Core coverage: $PERCENT%" + - name: Update badges in README run: | PYLINT_SCORE="${{ steps.pylint.outputs.score }}" PYRIGHT_ERRORS="${{ steps.pyright.outputs.errors }}" COVERAGE_PERCENT="${{ steps.coverage.outputs.percent }}" + CORE_COVERAGE_PERCENT="${{ steps.core_coverage.outputs.percent }}" PYLINT_SCORE_ENCODED=$(echo "$PYLINT_SCORE" | sed 's|/|%2F|g') @@ -71,9 +83,12 @@ jobs: if [ -n "$COVERAGE_PERCENT" ]; then sed -i "s|/badge/coverage-[^)]*|/badge/coverage-${COVERAGE_PERCENT}%25-brightgreen|" README.md fi + if [ -n "$CORE_COVERAGE_PERCENT" ]; then + sed -i "s|/badge/core%20coverage-[^)]*|/badge/core%20coverage-${CORE_COVERAGE_PERCENT}%25-brightgreen|" README.md + fi echo "Updated badges:" - grep -E "pylint|pyright|coverage" README.md | head -3 + grep -E "pylint|pyright|coverage" README.md | head -4 - name: Commit and push badge updates run: | @@ -86,7 +101,7 @@ jobs: else echo "Badge changes detected, committing..." git add README.md - MSG="chore: update quality badges"$'\n\n'"- Pylint: ${{ steps.pylint.outputs.score }}"$'\n'"- Pyright: ${{ steps.pyright.outputs.errors }} errors"$'\n'"- Coverage: ${{ steps.coverage.outputs.percent }}%"$'\n\n'"[skip ci]" + MSG="chore: update quality badges"$'\n\n'"- Pylint: ${{ steps.pylint.outputs.score }}"$'\n'"- Pyright: ${{ steps.pyright.outputs.errors }} errors"$'\n'"- Coverage: ${{ steps.coverage.outputs.percent }}%"$'\n'"- Core coverage: ${{ steps.core_coverage.outputs.percent }}%"$'\n\n'"[skip ci]" git commit -m "$MSG" git push fi diff --git a/README.md b/README.md index 876a95f..8e8e86d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![pylint](https://img.shields.io/badge/pylint-9.93%2F10-brightgreen)](https://github.com/PyCQA/pylint) [![pyright](https://img.shields.io/badge/pyright-0%20errors-brightgreen)](https://github.com/microsoft/pyright) [![coverage](https://img.shields.io/badge/coverage-79%25-brightgreen)](https://coverage.readthedocs.io/) +[![core coverage](https://img.shields.io/badge/core%20coverage-95%25-brightgreen)](https://gitea.dideric.is/didericis/bot-bottle/src/branch/main/docs/decisions/0004-coverage-policy.md) **Problem:** Developer wants to run a coding agent without supervision, but they don't want a prompt injected or misbehaving agent wrecking their environment or exfiltrating sensitive data. diff --git a/docs/decisions/0004-coverage-policy.md b/docs/decisions/0004-coverage-policy.md index 6141e5b..acaaa4e 100644 --- a/docs/decisions/0004-coverage-policy.md +++ b/docs/decisions/0004-coverage-policy.md @@ -88,3 +88,9 @@ omit list. - PRs #290 (cover the egress adapter), and the coverage-policy PR that introduces this record. - `.coveragerc`, `scripts/coverage.sh`, `scripts/diff_coverage.py`. +- `scripts/critical-modules.txt` — the single source of truth for the + core-module list; read by both `scripts/coverage.sh` and the + `update-badges.yml` "core coverage" badge so they cannot drift. +- The README carries a `core coverage` badge (auto-updated from that + list) — the headline number, distinct from the informational global + `coverage` badge. diff --git a/scripts/coverage.sh b/scripts/coverage.sh index db9556c..0dae61b 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -16,13 +16,10 @@ cd "$(dirname "$0")/.." PY="${PYTHON:-python3}" -# Critical security/logic core held to the high bar by ADR 0004. -CRITICAL="bot_bottle/egress_addon.py,bot_bottle/egress_addon_core.py,\ -bot_bottle/dlp_detectors.py,bot_bottle/egress.py,bot_bottle/manifest.py,\ -bot_bottle/manifest_egress.py,bot_bottle/manifest_agent.py,\ -bot_bottle/manifest_schema.py,bot_bottle/git_gate.py,\ -bot_bottle/git_http_backend.py,bot_bottle/supervise.py,\ -bot_bottle/yaml_subset.py,bot_bottle/bottle_state.py" +# Critical security/logic core held to the high bar by ADR 0004. The list +# lives in one place (scripts/critical-modules.txt) so this report and the +# README "core coverage" badge can't drift; comma-join it for --include. +CRITICAL=$(grep -vE '^[[:space:]]*(#|$)' scripts/critical-modules.txt | paste -sd, -) rm -f .coverage diff --git a/scripts/critical-modules.txt b/scripts/critical-modules.txt new file mode 100644 index 0000000..8f23b04 --- /dev/null +++ b/scripts/critical-modules.txt @@ -0,0 +1,23 @@ +# Critical security/logic core held to the >=90% coverage bar by +# docs/decisions/0004-coverage-policy.md. +# +# SINGLE SOURCE OF TRUTH: scripts/coverage.sh (the `critical` report) and +# .gitea/workflows/update-badges.yml (the "core coverage" badge) both read +# this file. Add a module here when it becomes part of the core; a coverage +# number that silently stops measuring a module is worse than no badge. +# +# One module path per line, relative to the repo root. Blank lines and +# `#` comments are ignored. +bot_bottle/egress_addon.py +bot_bottle/egress_addon_core.py +bot_bottle/dlp_detectors.py +bot_bottle/egress.py +bot_bottle/manifest.py +bot_bottle/manifest_egress.py +bot_bottle/manifest_agent.py +bot_bottle/manifest_schema.py +bot_bottle/git_gate.py +bot_bottle/git_http_backend.py +bot_bottle/supervise.py +bot_bottle/yaml_subset.py +bot_bottle/bottle_state.py