Files
Charon/docs/plans/current_spec.md

23 KiB

Local Pre-CI Patch Report (Single Scope)

Date: 2026-02-17 Scope: Add a local pre-CI patch report to Definition of Done (DoD) unit-testing flow for both backend and frontend.

1) Objective

Add one executable local workflow that computes patch coverage from current branch changes and publishes a consolidated report before CI runs.

The report must consume backend and frontend coverage inputs, use origin/main...HEAD as the patch baseline, and produce human-readable and machine-readable artifacts in test-results/.

2) In Scope / Out of Scope

In Scope

  • Local patch report generation.
  • Backend + frontend DoD unit-testing integration.
  • VS Code task wiring for repeatable local execution.
  • Non-blocking warning policy for initial rollout.

Out of Scope

  • CI gate changes.
  • Encryption-key or unrelated reliability/security remediation.
  • Historical Codecov placeholder gates and unrelated patch-closure matrices.

3) Required Inputs and Baseline

Coverage Inputs

  • Backend coverage profile: backend/coverage.txt
  • Frontend coverage profile: frontend/coverage/lcov.info

Diff Baseline

  • Git diff range: origin/main...HEAD

Preconditions

  • origin/main is fetchable locally.
  • Backend and frontend coverage artifacts exist before report generation.

4) Required Output Artifacts

  • Markdown report: test-results/local-patch-report.md
  • JSON report: test-results/local-patch-report.json

Both artifacts are mandatory per run. Missing either artifact is a failed local report run.

5) Initial Policy (Rollout)

Initial Policy (Non-Blocking)

  • Local patch report does not fail DoD on low patch coverage during initial rollout.
  • Local runner emits warnings (stdout + markdown/json status fields) when thresholds are not met.
  • DoD requires the report to run and artifacts to exist, even in warning mode.
  • Execution and final merge checks in this plan follow this same warn-mode policy during rollout.

Threshold Defaults and Source Precedence

  • Coverage thresholds are resolved with this precedence:
    1. Environment variables (highest precedence)
    2. Built-in defaults (fallback)
  • Threshold environment variables:
    • CHARON_OVERALL_PATCH_COVERAGE_MIN
    • CHARON_BACKEND_PATCH_COVERAGE_MIN
    • CHARON_FRONTEND_PATCH_COVERAGE_MIN
  • Built-in defaults for this rollout:
    • Overall patch coverage minimum: 90
    • Backend patch coverage minimum: 85
    • Frontend patch coverage minimum: 85
  • Parsing/validation:
    • Values must be numeric percentages in [0, 100].
    • Invalid env values are ignored with a warning, and the corresponding default is used.

Future Policy (Optional Hard Gate)

  • Optional future switch to hard gate (non-zero exit on threshold breach).
  • Gate behavior is controlled by a dedicated flag/env (to be added during implementation).
  • Hard-gate enablement is explicitly deferred and not part of this rollout.

6) Technical Specification

6.1 Script

Implement a new local report script:

  • Path: scripts/local-patch-report.sh
  • Responsibilities:
    1. Validate required inputs exist (backend/coverage.txt, frontend/coverage/lcov.info).
    2. Resolve patch files/lines from origin/main...HEAD.
    3. Correlate changed lines with backend/frontend coverage data.
    4. Compute patch summary by component and overall.
    5. Resolve thresholds using env-var-first precedence, then defaults (90/85/85).
    6. Evaluate statuses against resolved thresholds:
      • overall.status=pass when overall.patch_coverage_pct >= overall_threshold, else warn.
      • backend.status=pass when backend.patch_coverage_pct >= backend_threshold, else warn.
      • frontend.status=pass when frontend.patch_coverage_pct >= frontend_threshold, else warn.
    7. Emit warning status when any scope is below its resolved threshold.
    8. Write required outputs:
    • test-results/local-patch-report.md
    • test-results/local-patch-report.json

6.2 Report Contract

Minimum JSON fields:

  • baseline: origin/main...HEAD
  • generated_at
  • mode: warn (initial rollout)
  • thresholds:
    • overall_patch_coverage_min
    • backend_patch_coverage_min
    • frontend_patch_coverage_min
  • threshold_sources:
    • overall (env | default)
    • backend (env | default)
    • frontend (env | default)
  • overall:
    • changed_lines
    • covered_lines
    • patch_coverage_pct
    • status (pass | warn)
  • backend and frontend objects with same coverage counters and status
  • files_needing_coverage (required array for execution baselines), where each item includes at minimum:
    • path
    • uncovered_changed_lines
    • patch_coverage_pct
  • artifacts with emitted file paths

Minimum Markdown sections:

  • Run metadata (timestamp, baseline)
  • Input paths used
  • Resolved thresholds and their sources (env/default)
  • Coverage summary table (overall/backend/frontend)
  • Warning section (if any)
  • Artifact paths

6.3 Task Wiring

Add VS Code task entries in .vscode/tasks.json:

  1. Test: Local Patch Report
    • Runs report generation script only.
  2. Test: Backend DoD + Local Patch Report
    • Runs backend unit test coverage flow, then local patch report.
  3. Test: Frontend DoD + Local Patch Report
    • Runs frontend unit test coverage flow, then local patch report.
  4. Test: Full DoD Unit + Local Patch Report
    • Runs backend + frontend unit coverage flows, then local patch report.

Task behavior:

  • Reuse existing coverage scripts/tasks where available.
  • Keep command order deterministic: coverage generation first, patch report second.

7) Implementation Tasks

Phase 1 — Script Foundation

  • Create scripts/local-patch-report.sh.
  • Add input validation + clear error messages.
  • Add diff parsing for origin/main...HEAD.

Phase 2 — Coverage Correlation

  • Parse backend coverage.txt and map covered lines.
  • Parse frontend coverage/lcov.info and map covered lines.
  • Compute per-scope and overall patch coverage counters.

Phase 3 — Artifact Emission

  • Generate test-results/local-patch-report.json with required schema.
  • Generate test-results/local-patch-report.md with summary + warnings.
  • Ensure test-results/ creation if missing.

Phase 4 — Task Wiring

  • Add Test: Local Patch Report to .vscode/tasks.json.
  • Add backend/frontend/full DoD task variants with report execution.
  • Verify tasks run successfully from workspace root.

Phase 5 — Documentation Alignment

  • Update DoD references in applicable docs/instructions only where this local report is now required.
  • Remove stale references to unrelated placeholder gates in active plan context.

8) Validation Commands

Run from repository root unless noted.

  1. Generate backend coverage input:
cd backend && go test ./... -coverprofile=coverage.txt
  1. Generate frontend coverage input:
cd frontend && npm run test:coverage
  1. Generate local patch report directly:
./scripts/local-patch-report.sh
  1. Generate local patch report via task:
# VS Code task: Test: Local Patch Report
  1. Validate artifacts exist:
test -f test-results/local-patch-report.md
test -f test-results/local-patch-report.json
  1. Validate baseline recorded in JSON:
jq -r '.baseline' test-results/local-patch-report.json
# expected: origin/main...HEAD

9) Acceptance Criteria

  • Plan remains single-scope: local pre-CI patch report for DoD unit testing only.
  • Inputs are explicit and used:
    • backend/coverage.txt
    • frontend/coverage/lcov.info
    • origin/main...HEAD
  • Outputs are generated on every successful run:
    • test-results/local-patch-report.md
    • test-results/local-patch-report.json
  • Initial policy is non-blocking warning mode.
  • Default thresholds are explicit:
    • Overall patch coverage: 90
    • Backend patch coverage: 85
    • Frontend patch coverage: 85
  • Threshold source precedence is explicit: env vars first, then defaults.
  • Future hard-gate mode is documented as optional and deferred.
  • Concrete script + task wiring tasks are present and executable.
  • Validation commands are present and reproducible.
  • Stale unrelated placeholder gates are removed from this active spec.

10) Concrete Execution Plan — Patch Gap Closure (PR Merge Objective)

Single-scope objective: close current patch gaps for this PR merge by adding targeted tests and iterating local patch reports until changed-line coverage is merge-ready under DoD.

Authoritative Gap Baseline (2026-02-17)

Use this list as the only planning baseline for this execution cycle:

  • backend/cmd/localpatchreport/main.go: 0%, 200 uncovered changed lines, ranges 46-59, 61-73, 75-79, 81-85, 87-96, 98-123, 125-156, 158-165, 167-172, 175-179, 182-187, 190-198, 201-207, 210-219, 222-254, 257-264, 267-269
  • frontend/src/pages/UsersPage.tsx: 30.8%, 9 uncovered (152-160)
  • frontend/src/pages/CrowdSecConfig.tsx: 36.8%, 12 uncovered (975-977, 1220, 1248-1249, 1281-1282, 1316, 1324-1325, 1335)
  • frontend/src/pages/DNSProviders.tsx: 70.6%, 10 uncovered
  • frontend/src/pages/AuditLogs.tsx: 75.0%, 1 uncovered
  • frontend/src/components/ProxyHostForm.tsx: 75.5%, 12 uncovered
  • backend/internal/api/middleware/auth.go: 86.4%, 3 uncovered
  • frontend/src/pages/Notifications.tsx: 88.9%, 3 uncovered
  • backend/internal/cerberus/rate_limit.go: 91.9%, 12 uncovered

DoD Entry Gate (Mandatory Before Phase 1)

All execution phases are blocked until this gate is completed in order:

  1. E2E first:
cd /projects/Charon && npx playwright test --project=firefox
  1. Local patch preflight (baseline refresh trigger):
cd /projects/Charon && bash scripts/local-patch-report.sh
  1. Baseline refresh checkpoint (must pass before phase execution):
cd /projects/Charon && jq -r '.files_needing_coverage[].path' test-results/local-patch-report.json | sort > /tmp/charon-baseline-files.txt
cd /projects/Charon && while read -r f; do git diff --name-only origin/main...HEAD -- "$f" | grep -qx "$f" || echo "baseline file missing from current diff: $f"; done < /tmp/charon-baseline-files.txt
  1. If checkpoint output is non-empty, refresh this baseline list to match the latest test-results/local-patch-report.json before starting Phase 1.

Ordered Phases (Highest Impact First)

Phase 1 — Backend Local Patch Report CLI (Highest Delta)

Targets:

  • backend/cmd/localpatchreport/main.go (all listed uncovered ranges)

Suggested test file:

  • backend/cmd/localpatchreport/main_test.go

Test focus:

  • argument parsing and mode selection
  • coverage input validation paths
  • baseline/diff resolution flow
  • report generation branches (markdown/json)
  • warning/error branches for missing inputs and malformed coverage

Pass criteria:

  • maximize reduction of uncovered changed lines in backend/cmd/localpatchreport/main.go from the 200 baseline, with priority on highest-impact uncovered ranges and no new uncovered changed lines introduced
  • backend targeted test command passes

Targeted test command:

cd /projects/Charon/backend && go test ./cmd/localpatchreport -coverprofile=coverage.txt

Phase 2 — Frontend Lowest-Coverage, Highest-Uncovered Pages

Targets:

  • frontend/src/pages/CrowdSecConfig.tsx (975-977, 1220, 1248-1249, 1281-1282, 1316, 1324-1325, 1335)
  • frontend/src/pages/UsersPage.tsx (152-160)
  • frontend/src/pages/DNSProviders.tsx (10 uncovered changed lines)

Suggested test files:

  • frontend/src/pages/__tests__/CrowdSecConfig.patch-gap.test.tsx
  • frontend/src/pages/__tests__/UsersPage.patch-gap.test.tsx
  • frontend/src/pages/__tests__/DNSProviders.patch-gap.test.tsx

Test focus:

  • branch/error-state rendering tied to uncovered lines
  • conditional action handlers and callback guards
  • edge-case interaction states not hit by existing tests

Pass criteria:

  • maximize reduction of changed-line gaps for the three targets, prioritize highest-impact uncovered lines first, and avoid introducing new uncovered changed lines
  • frontend targeted test command passes

Targeted test command:

cd /projects/Charon/frontend && npm run test:coverage -- src/pages/__tests__/CrowdSecConfig.patch-gap.test.tsx src/pages/__tests__/UsersPage.patch-gap.test.tsx src/pages/__tests__/DNSProviders.patch-gap.test.tsx

Phase 3 — Backend Residual Middleware/Security Gaps

Targets:

  • backend/internal/api/middleware/auth.go (3 uncovered changed lines)
  • backend/internal/cerberus/rate_limit.go (12 uncovered changed lines)

Suggested test targets/files:

  • extend backend/internal/api/middleware/auth_test.go
  • extend backend/internal/cerberus/rate_limit_test.go

Test focus:

  • auth middleware edge branches (token/context failure paths)
  • rate-limit boundary and deny/allow branch coverage

Pass criteria:

  • maximize reduction of changed-line gaps for both backend files, prioritize highest-impact uncovered lines first, and avoid introducing new uncovered changed lines
  • backend targeted test command passes

Targeted test command:

cd /projects/Charon/backend && go test ./internal/api/middleware ./internal/cerberus -coverprofile=coverage.txt

Phase 4 — Frontend Component + Residual Page Gaps

Targets:

  • frontend/src/components/ProxyHostForm.tsx (12 uncovered changed lines)
  • frontend/src/pages/AuditLogs.tsx (1 uncovered changed line)
  • frontend/src/pages/Notifications.tsx (3 uncovered changed lines)

Suggested test files:

  • frontend/src/components/__tests__/ProxyHostForm.patch-gap.test.tsx
  • frontend/src/pages/__tests__/AuditLogs.patch-gap.test.tsx
  • frontend/src/pages/__tests__/Notifications.patch-gap.test.tsx

Test focus:

  • form branch paths and validation fallbacks
  • single-line residual branch in audit logs
  • notification branch handling for low-frequency states

Pass criteria:

  • maximize reduction of changed-line gaps for all three targets, prioritize highest-impact uncovered lines first, and avoid introducing new uncovered changed lines
  • frontend targeted test command passes

Targeted test command:

cd /projects/Charon/frontend && npm run test:coverage -- src/components/__tests__/ProxyHostForm.patch-gap.test.tsx src/pages/__tests__/AuditLogs.patch-gap.test.tsx src/pages/__tests__/Notifications.patch-gap.test.tsx

Execution Commands

Run from repository root unless stated otherwise.

  1. Backend coverage:
cd backend && go test ./... -coverprofile=coverage.txt
  1. Frontend coverage:
cd frontend && npm run test:coverage
  1. Local patch report iteration:
bash scripts/local-patch-report.sh
  1. Iteration loop (repeat until all target gaps are closed):
cd backend && go test ./... -coverprofile=coverage.txt
cd /projects/Charon/frontend && npm run test:coverage
cd /projects/Charon && bash scripts/local-patch-report.sh

Phase Completion Checks

  • After each phase, rerun bash scripts/local-patch-report.sh and confirm that only the next planned target set remains uncovered.
  • Do not advance phases when a phase target still shows uncovered changed lines.

Final Merge-Ready Gate (DoD-Aligned, Warn-Mode Rollout)

This PR is merge-ready only when all conditions are true:

  • local patch report runs in warn mode and required artifacts are generated
  • practical merge objective: drive a significant reduction in authoritative baseline uncovered changed lines in this PR, prioritizing highest-impact files; 0 remains aspirational and is not a warn-mode merge blocker
  • required artifacts exist and are current:
    • test-results/local-patch-report.md
    • test-results/local-patch-report.json
  • backend and frontend coverage commands complete successfully
  • DoD checks remain satisfied (E2E first, local patch report preflight, required security/coverage/type/build validations)

Flaky Test Stabilization Plan: TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath (2026-02-17)

1) Scope and Objective

Stabilize backend flake in backend/internal/api/handlers/settings_wave4_test.go for:

  • TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath

Scope is limited to this flaky path and directly adjacent test/lifecycle hardening required to make behavior deterministic across CI contexts.

2) Investigation Findings (Root Cause)

Evidence from CI and local repro (go test -race -count=20 -run 'TestSettingsHandlerWave4_UpdateSetting_ACLPathsPermissionErrors|TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath' ./internal/api/handlers):

  • Race is reported by Go race detector during execution of TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath.
  • Conflicting operations:
    • Read path: background goroutine from services.NewSecurityService() performing db.Create() in persistAuditWithRetry() / processAuditEvents().
    • Write path: test cleanup removing GORM create callback (db.Callback().Create().Remove(...)) in registerCreatePermissionDeniedHook cleanup.
  • This race is triggered by preceding test TestSettingsHandlerWave4_UpdateSetting_ACLPathsPermissionErrors, which creates a SecurityService (spawns goroutine) and does not shut it down before callback cleanup mutates callback registry.

Primary cause is shared mutable callback registry + still-running background audit goroutine (order-dependent teardown), not business logic in PatchConfig itself.

3) Dependency Map (Files and Symbols)

Test path

  • backend/internal/api/handlers/settings_wave4_test.go
    • TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath
    • TestSettingsHandlerWave4_UpdateSetting_ACLPathsPermissionErrors
    • registerCreatePermissionDeniedHook
    • setupSettingsWave3DB

Handler/runtime path

  • backend/internal/api/handlers/settings_handler.go
    • PatchConfig
    • UpdateSetting
  • backend/internal/api/handlers/permission_helpers.go
    • respondPermissionError
    • logPermissionAudit
  • backend/internal/services/security_service.go
    • NewSecurityService
    • LogAudit
    • processAuditEvents
    • Close
    • Flush

CI execution context

  • scripts/go-test-coverage.sh (always runs backend tests with -race)
  • .github/workflows/codecov-upload.yml (uses scripts/go-test-coverage.sh for both push and PR)

4) Flake Vector Assessment

  • Timing/Goroutines: High confidence root cause. Background audit goroutine outlives test branch and races with callback registry mutation.
  • Shared state/global hooks: High confidence root cause. GORM callback registry is mutable shared state per DB instance.
  • Order dependence: High confidence root cause. Preceding wave4 permission-error test influences subsequent test via asynchronous cleanup timing.
  • DB locking/no-such-table noise: Secondary contributor (observed security_audits missing logs), but not primary failure trigger.
  • Env vars (PR vs push): Low confidence as root cause for this test; same script and -race path are used in both contexts.
  • Log buffering: Not a primary root cause; race detector output indicates memory race in callback internals.

5) Stabilization Strategy (Minimal and Deterministic)

  1. Deterministic lifecycle shutdown for SecurityService in wave4 permission-error test

    • In TestSettingsHandlerWave4_UpdateSetting_ACLPathsPermissionErrors, explicitly manage the service used for h.SecuritySvc and register teardown to flush/close it before callback removal side effects complete.
    • Ensure cleanup order prevents callback registry mutation while audit goroutine is still active.
  2. Reduce unnecessary async audit side effects in this wave4 path

    • For tests that only assert HTTP permission error response (not audit persistence), avoid creating live async service when not required by assertion semantics.
    • Keep behavior coverage for response contract while eliminating unnecessary goroutine work in this flaky sequence.
  3. Harden test DB schema for adjacent audit paths

    • In setupSettingsWave3DB, include models.SecurityAudit migration to remove noisy no such table: security_audits writes from concurrent worker paths.
    • This reduces background retry/noise and improves determinism under race mode.
  4. Guard callback hook helper usage

    • Keep callback registration/removal confined to narrow tests and avoid overlap with asynchronous writers on same DB handle.
    • Maintain unique callback naming per test branch to prevent accidental collisions when future subtests are added.

6) EARS Requirements

  • WHEN wave4 permission-error tests register temporary GORM callbacks, THE SYSTEM SHALL ensure all asynchronous SecurityService audit workers are fully stopped before callback removal occurs.
  • WHEN TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath runs with -race, THE SYSTEM SHALL complete without data race reports.
  • IF a test path uses SecurityService.LogAudit, THEN the test DB setup SHALL include required audit schema to avoid asynchronous write failures due to missing tables.
  • WHILE running backend coverage in CI contexts (push and PR), THE SYSTEM SHALL produce deterministic pass/fail outcomes for this test sequence.

7) Implementation Tasks (Single-Scope)

  1. Update backend/internal/api/handlers/settings_wave4_test.go

    • Add explicit SecurityService lifecycle management in TestSettingsHandlerWave4_UpdateSetting_ACLPathsPermissionErrors.
    • Ensure teardown ordering is deterministic relative to callback cleanup.
    • Keep TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath assertions unchanged (status + reload/cache call counts).
  2. Update backend/internal/api/handlers/settings_wave3_test.go

    • Extend setupSettingsWave3DB migrations to include models.SecurityAudit.
  3. Validation

    • Targeted race test loop:
      • cd backend && CHARON_ENCRYPTION_KEY="$(openssl rand -base64 32)" go test -race -count=50 -run 'TestSettingsHandlerWave4_UpdateSetting_ACLPathsPermissionErrors|TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath' ./internal/api/handlers
    • Targeted package race pass:
      • cd backend && CHARON_ENCRYPTION_KEY="$(openssl rand -base64 32)" go test -race -run 'TestSettingsHandlerWave4_' ./internal/api/handlers
    • Standard backend CI-equivalent coverage command:
      • bash scripts/go-test-coverage.sh

8) PR Slicing Strategy

  • Decision: Single PR (small, isolated, low blast radius).
  • Trigger rationale: Changes are constrained to wave4 settings tests and adjacent test helper DB schema.
  • Slice PR-1:
    • Scope: lifecycle/order hardening + helper schema migration only.
    • Files:
      • backend/internal/api/handlers/settings_wave4_test.go
      • backend/internal/api/handlers/settings_wave3_test.go
    • Validation gate: no race detector output in targeted loop; package tests stable under -race; no assertion behavior drift in target flaky test.
  • Rollback: Revert PR-1 if unintended changes appear in broader handlers suite; no production code path changes expected.

9) Acceptance Criteria

  • TestSettingsHandlerWave4_PatchConfig_SecurityReloadSuccessLogsPath is stable under repeated -race runs.
  • No race detector warnings involving GORM callback compile/remove and SecurityService audit goroutine in this test sequence.
  • Test remains behaviorally equivalent (same API contract and assertions).
  • Scope remains limited to this flaky test sequence and adjacent stabilization only.