mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:01:36 +00:00
ci-pipeline-cleanup Phase 2: coverage thresholds → YAML manifest
Bundle: ci-pipeline-cleanup, Phase 2 / frozen decision 0.3. Move 9 hardcoded coverage thresholds from inline bash to a YAML manifest at .github/coverage-thresholds.yml. The load-bearing per-package context (Bundle reference, HEAD measurement, gap rationale) survives in the YAML's `why:` field instead of in inline bash comments. Adding a new gated package: one YAML entry instead of ~30 lines of bash + 50 lines of comment. Coverage check logic extracted to scripts/check-coverage-thresholds.sh so the operator can run the same check locally: bash scripts/check-coverage-thresholds.sh ci.yml dropped 557 → 417 lines (-140, total Phase 1+2: -1071, -72% from baseline 1488). Same 9 floors, same fail-on-miss semantics — pure relocation: internal/service: 70 (was: 70) internal/api/handler: 75 (was: 75) internal/domain: 40 (was: 40) internal/api/middleware: 30 (was: 30) internal/crypto: 88 (was: 88) internal/connector/issuer/local: 86 (was: 86) internal/connector/issuer/acme: 80 (was: 80) internal/connector/issuer/stepca: 80 (was: 80) internal/mcp: 85 (was: 85) Sandbox verification: - ci.yml YAML-parses cleanly - coverage-thresholds.yml YAML-parses cleanly with all 9 entries - scripts/check-coverage-thresholds.sh extracts the (pkg, floor) table correctly from the YAML
This commit is contained in:
+6
-146
@@ -86,152 +86,12 @@ jobs:
|
||||
go test ./internal/service/... ./internal/api/handler/... ./internal/api/middleware/... ./internal/integration/... ./internal/connector/issuer/... ./internal/connector/target/... ./internal/connector/notifier/... ./internal/connector/discovery/... ./internal/crypto/... ./internal/mcp/... ./internal/cli/... ./internal/domain/... ./internal/validation/... ./internal/tlsprobe/... -count=1 -cover -coverprofile=coverage.out
|
||||
|
||||
- name: Check Coverage Thresholds
|
||||
run: |
|
||||
# Extract per-package coverage from test output
|
||||
echo "=== Coverage Report ==="
|
||||
go tool cover -func=coverage.out | tail -1
|
||||
|
||||
# Check service layer coverage (target: 60%+)
|
||||
SERVICE_COV=$(go tool cover -func=coverage.out | grep 'internal/service' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "Service layer coverage: ${SERVICE_COV}%"
|
||||
|
||||
# Check handler layer coverage (target: 60%+)
|
||||
HANDLER_COV=$(go tool cover -func=coverage.out | grep 'internal/api/handler' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "Handler layer coverage: ${HANDLER_COV}%"
|
||||
|
||||
# Check domain layer coverage (target: 40%+)
|
||||
DOMAIN_COV=$(go tool cover -func=coverage.out | grep 'internal/domain' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "Domain layer coverage: ${DOMAIN_COV}%"
|
||||
|
||||
# Check middleware layer coverage (target: 50%+)
|
||||
MIDDLEWARE_COV=$(go tool cover -func=coverage.out | grep 'internal/api/middleware' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "Middleware layer coverage: ${MIDDLEWARE_COV}%"
|
||||
|
||||
# Check crypto package coverage (target: 85%+)
|
||||
# M-8 rationale: encryption primitives are a security-critical gate.
|
||||
# v2 format, key-derivation, fallback, and fail-closed sentinel paths
|
||||
# all need exhaustive coverage to avoid silent regressions (CWE-916 / CWE-329).
|
||||
CRYPTO_COV=$(go tool cover -func=coverage.out | grep 'internal/crypto' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "Crypto package coverage: ${CRYPTO_COV}%"
|
||||
|
||||
# Bundle-7 / Audit H-005 — extended crypto-cluster gates per CLAUDE.md.
|
||||
# internal/pkcs7/ is at 100% at HEAD (encoder-only, exhaustively tested
|
||||
# via Bundle-4 fuzz targets + unit tests). internal/connector/issuer/local/
|
||||
# is at 68.3% at HEAD; H-010 tracks the gap and will lift this floor
|
||||
# to 85% once the missing CSR-validation + CA-cert-loading tests land.
|
||||
PKCS7_COV=$(go tool cover -func=coverage.out | grep 'internal/pkcs7' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "PKCS7 package coverage: ${PKCS7_COV}%"
|
||||
|
||||
LOCAL_ISSUER_COV=$(go tool cover -func=coverage.out | grep 'internal/connector/issuer/local' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "Local-issuer coverage: ${LOCAL_ISSUER_COV}%"
|
||||
|
||||
# Bundle-J / Coverage-Audit C-001 (partial-closed) — ACME failure-mode
|
||||
# batch lifts internal/connector/issuer/acme from 41.8% to ~55.6%
|
||||
# (per-package package-scoped run). The global per-file average can
|
||||
# come in lower because this awk pattern divides by file count
|
||||
# rather than weighting by line count, but with the failure-mode
|
||||
# tests landed every file in the package has at least 50% coverage.
|
||||
# Floor set at 50 to accommodate the global-run arithmetic; bumps
|
||||
# to 85 when Bundle J-extended (Pebble-style mock) lands and the
|
||||
# IssueCertificate / solveAuthorizations* flows are exercisable.
|
||||
ACME_COV=$(go tool cover -func=coverage.out | grep 'internal/connector/issuer/acme' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "ACME issuer coverage: ${ACME_COV}%"
|
||||
|
||||
# Bundle-L.B / Coverage-Audit C-005 — StepCA failure-mode + JWE
|
||||
# round-trip tests lift internal/connector/issuer/stepca from
|
||||
# 52.1% to 90.4% (per-package run). Floor at 80 with margin.
|
||||
STEPCA_COV=$(go tool cover -func=coverage.out | grep 'internal/connector/issuer/stepca' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "StepCA issuer coverage: ${STEPCA_COV}%"
|
||||
|
||||
# Bundle-K / Coverage-Audit C-002 — MCP per-tool dispatch via
|
||||
# in-memory transport lifts internal/mcp from 28.0% to 93.1%
|
||||
# (per-package run). Floor at 85.
|
||||
MCP_COV=$(go tool cover -func=coverage.out | grep 'internal/mcp/' | awk '{print $NF}' | sed 's/%//' | awk '{sum+=$1; n++} END {if(n>0) printf "%.1f", sum/n; else print "0"}')
|
||||
echo "MCP coverage: ${MCP_COV}%"
|
||||
|
||||
# Fail if thresholds not met.
|
||||
# Bundle R-CI-extended raises (post-Bundle-N.C-extended):
|
||||
# service 55 -> 70 (HEAD 73.4%; 3pp margin); handler 60 -> 75
|
||||
# (HEAD 79.8%; 4pp margin). Prescribed Bundle R target was 80;
|
||||
# held lower to avoid false-positives on single low-coverage
|
||||
# files dragging the global per-file-average down.
|
||||
if [ "$(echo "$SERVICE_COV < 70" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::Service layer coverage ${SERVICE_COV}% is below 70% (Bundle R-CI-extended floor — add tests, do not lower the gate)"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(echo "$HANDLER_COV < 75" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::Handler layer coverage ${HANDLER_COV}% is below 75% (Bundle R-CI-extended floor — add tests, do not lower the gate)"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(echo "$DOMAIN_COV < 40" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::Domain layer coverage ${DOMAIN_COV}% is below 40% threshold"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(echo "$MIDDLEWARE_COV < 30" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::Middleware layer coverage ${MIDDLEWARE_COV}% is below 30% threshold"
|
||||
exit 1
|
||||
fi
|
||||
# Bundle R / Coverage Audit Closure — CI threshold raise checkpoint #3.
|
||||
# Crypto package floor lifted 85 → 88. Post-Bundle-Q package-scoped
|
||||
# coverage at HEAD: 88.2% (Bundle Q's gopter property tests don't add
|
||||
# production-code coverage — they exercise the same paths via
|
||||
# generative inputs). The remaining ~12% gap is platform-failure
|
||||
# branches (rand.Reader / aes.NewCipher) that require interface seams
|
||||
# the production code doesn't use; closing them is tracked as
|
||||
# R-CI-extended, not Bundle R scope.
|
||||
if [ "$(echo "$CRYPTO_COV < 88" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::Crypto package coverage ${CRYPTO_COV}% is below 88% (Bundle R closure floor — add tests, do not lower the gate)"
|
||||
exit 1
|
||||
fi
|
||||
# Bundle-7 / H-005: pkcs7 coverage is INFORMATIONAL only in this run.
|
||||
# The global `go test -cover ./...` invocation in CI doesn't exercise
|
||||
# internal/pkcs7's tests (they're primarily Fuzz* targets that
|
||||
# require an explicit `-fuzz` invocation, plus encoder helpers
|
||||
# exercised transitively). The deep-scan workflow runs
|
||||
# `go test -cover ./internal/pkcs7/...` directly and confirmed 100%
|
||||
# at Bundle-7 close — that's the load-bearing measurement. Keeping
|
||||
# the global-run number visible here for trend-watching but not
|
||||
# gating because 0% is a measurement artifact, not a regression.
|
||||
echo "PKCS7 package coverage (global run, informational): ${PKCS7_COV}%"
|
||||
# Bundle-9 / H-010 closure: local-issuer HARD gate at 85%. The
|
||||
# transitional 60% floor (Bundle-7) was an explicit promise in the
|
||||
# CI config that H-010 would raise it once CSR-validation + CA-
|
||||
# cert-loading + key-rotation + key-encoding pin tests landed.
|
||||
# Bundle-9 ships those tests (bundle9_coverage_test.go) and lifts
|
||||
# the package-scoped run to ~86.7%; the global run averages a few
|
||||
# points lower (per-function arithmetic), so the gate is set to 85
|
||||
# with the live `go test -cover` number being the source of truth.
|
||||
# If this gate trips, the fix is to add tests, NOT to lower the
|
||||
# floor — every percentage point under 85 is a regression on the
|
||||
# H-010 closure invariant.
|
||||
# Bundle R / Coverage Audit Closure — CI threshold raise checkpoint #3.
|
||||
# Local-issuer floor lifted 85 → 86. Post-Bundle-Q package-scoped
|
||||
# coverage at HEAD: 86.7%. The prescribed Bundle R target was
|
||||
# 92, but reaching it requires interface seams for crypto/x509
|
||||
# signing-error branches — tracked as R-CI-extended.
|
||||
if [ "$(echo "$LOCAL_ISSUER_COV < 86" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::Local-issuer coverage ${LOCAL_ISSUER_COV}% is below 86% (Bundle R closure floor — add tests, do not lower the gate)"
|
||||
exit 1
|
||||
fi
|
||||
# Bundle R-CI-extended threshold raise (post-Bundle-J-extended):
|
||||
# ACME 50 -> 80. The Pebble-style mock + per-CA failure tests
|
||||
# lift package-scoped ACME to 85.4%; gate at 80 with 5pp margin
|
||||
# to absorb the global-run per-file-average dip. The prescribed
|
||||
# Bundle R target was 85; held at 80 to avoid false-positives
|
||||
# on single low-coverage files.
|
||||
if [ "$(echo "$ACME_COV < 80" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::ACME issuer coverage ${ACME_COV}% is below 80% (Bundle R-CI-extended floor — add tests, do not lower the gate)"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(echo "$STEPCA_COV < 80" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::StepCA issuer coverage ${STEPCA_COV}% is below 80% (Bundle L.B closure floor — add tests, do not lower the gate)"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(echo "$MCP_COV < 85" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::MCP coverage ${MCP_COV}% is below 85% (Bundle K closure floor — add tests, do not lower the gate)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Coverage thresholds passed!"
|
||||
# ci-pipeline-cleanup Phase 2: per-package floors moved to
|
||||
# .github/coverage-thresholds.yml. Each entry has `floor:` +
|
||||
# `why:` (load-bearing context). Logic in
|
||||
# scripts/check-coverage-thresholds.sh — operator runs the same
|
||||
# script locally via `make verify`-equivalent loop.
|
||||
run: bash scripts/check-coverage-thresholds.sh
|
||||
|
||||
- name: Upload Coverage Report
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
Reference in New Issue
Block a user