fix: enhance encryption key validation and add trigger parity check for Codecov workflows
This commit is contained in:
71
.github/workflows/codecov-upload.yml
vendored
71
.github/workflows/codecov-upload.yml
vendored
@@ -50,11 +50,80 @@ jobs:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
# SECURITY: Keep pull_request (not pull_request_target) for secret-bearing backend tests.
|
||||
# Untrusted code (fork PRs and Dependabot PRs) gets ephemeral workflow-only keys.
|
||||
- name: Resolve encryption key for backend coverage
|
||||
shell: bash
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
PR_HEAD_FORK: ${{ github.event.pull_request.head.repo.fork }}
|
||||
WORKFLOW_SECRET_KEY: ${{ secrets.CHARON_ENCRYPTION_KEY_TEST }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
is_same_repo_pr=false
|
||||
if [[ "$EVENT_NAME" == "pull_request" && -n "${PR_HEAD_REPO:-}" && "$PR_HEAD_REPO" == "$REPO" ]]; then
|
||||
is_same_repo_pr=true
|
||||
fi
|
||||
|
||||
is_workflow_dispatch=false
|
||||
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
|
||||
is_workflow_dispatch=true
|
||||
fi
|
||||
|
||||
is_dependabot_pr=false
|
||||
if [[ "$EVENT_NAME" == "pull_request" && "$ACTOR" == "dependabot[bot]" ]]; then
|
||||
is_dependabot_pr=true
|
||||
fi
|
||||
|
||||
is_fork_pr=false
|
||||
if [[ "$EVENT_NAME" == "pull_request" && "${PR_HEAD_FORK:-false}" == "true" ]]; then
|
||||
is_fork_pr=true
|
||||
fi
|
||||
|
||||
is_untrusted=false
|
||||
if [[ "$is_fork_pr" == "true" || "$is_dependabot_pr" == "true" ]]; then
|
||||
is_untrusted=true
|
||||
fi
|
||||
|
||||
is_trusted=false
|
||||
if [[ "$is_untrusted" == "false" && ( "$is_same_repo_pr" == "true" || "$is_workflow_dispatch" == "true" ) ]]; then
|
||||
is_trusted=true
|
||||
fi
|
||||
|
||||
resolved_key=""
|
||||
if [[ "$is_trusted" == "true" ]]; then
|
||||
if [[ -z "${WORKFLOW_SECRET_KEY:-}" ]]; then
|
||||
echo "::error title=Missing required secret::Trusted backend CI context requires CHARON_ENCRYPTION_KEY_TEST. Add repository secret CHARON_ENCRYPTION_KEY_TEST."
|
||||
exit 1
|
||||
fi
|
||||
resolved_key="$WORKFLOW_SECRET_KEY"
|
||||
elif [[ "$is_untrusted" == "true" ]]; then
|
||||
resolved_key="$(openssl rand -base64 32)"
|
||||
else
|
||||
echo "::error title=Unsupported event context::Unable to classify trust for backend key resolution (event=${EVENT_NAME})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$resolved_key" ]]; then
|
||||
echo "::error title=Key resolution failure::Resolved encryption key is empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "::add-mask::$resolved_key"
|
||||
{
|
||||
echo "CHARON_ENCRYPTION_KEY<<__CHARON_EOF__"
|
||||
echo "$resolved_key"
|
||||
echo "__CHARON_EOF__"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Go tests with coverage
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
CHARON_ENCRYPTION_KEY: ${{ secrets.CHARON_ENCRYPTION_KEY_TEST }}
|
||||
run: |
|
||||
bash scripts/go-test-coverage.sh 2>&1 | tee backend/test-output.txt
|
||||
exit "${PIPESTATUS[0]}"
|
||||
|
||||
86
.github/workflows/quality-checks.yml
vendored
86
.github/workflows/quality-checks.yml
vendored
@@ -18,12 +18,92 @@ env:
|
||||
GOTOOLCHAIN: auto
|
||||
|
||||
jobs:
|
||||
codecov-trigger-parity-guard:
|
||||
name: Codecov Trigger/Comment Parity Guard
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Enforce Codecov trigger and comment parity
|
||||
run: |
|
||||
bash scripts/ci/check-codecov-trigger-parity.sh
|
||||
|
||||
backend-quality:
|
||||
name: Backend (Go)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
# SECURITY: Do not switch this workflow to pull_request_target for backend tests.
|
||||
# Untrusted code paths (fork PRs and Dependabot PRs) must never receive repository secrets.
|
||||
- name: Resolve encryption key for backend tests
|
||||
shell: bash
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
REPO: ${{ github.repository }}
|
||||
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
PR_HEAD_FORK: ${{ github.event.pull_request.head.repo.fork }}
|
||||
WORKFLOW_SECRET_KEY: ${{ secrets.CHARON_ENCRYPTION_KEY_TEST }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
is_same_repo_pr=false
|
||||
if [[ "$EVENT_NAME" == "pull_request" && -n "${PR_HEAD_REPO:-}" && "$PR_HEAD_REPO" == "$REPO" ]]; then
|
||||
is_same_repo_pr=true
|
||||
fi
|
||||
|
||||
is_workflow_dispatch=false
|
||||
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
|
||||
is_workflow_dispatch=true
|
||||
fi
|
||||
|
||||
is_dependabot_pr=false
|
||||
if [[ "$EVENT_NAME" == "pull_request" && "$ACTOR" == "dependabot[bot]" ]]; then
|
||||
is_dependabot_pr=true
|
||||
fi
|
||||
|
||||
is_fork_pr=false
|
||||
if [[ "$EVENT_NAME" == "pull_request" && "${PR_HEAD_FORK:-false}" == "true" ]]; then
|
||||
is_fork_pr=true
|
||||
fi
|
||||
|
||||
is_untrusted=false
|
||||
if [[ "$is_fork_pr" == "true" || "$is_dependabot_pr" == "true" ]]; then
|
||||
is_untrusted=true
|
||||
fi
|
||||
|
||||
is_trusted=false
|
||||
if [[ "$is_untrusted" == "false" && ( "$is_same_repo_pr" == "true" || "$is_workflow_dispatch" == "true" ) ]]; then
|
||||
is_trusted=true
|
||||
fi
|
||||
|
||||
resolved_key=""
|
||||
if [[ "$is_trusted" == "true" ]]; then
|
||||
if [[ -z "${WORKFLOW_SECRET_KEY:-}" ]]; then
|
||||
echo "::error title=Missing required secret::Trusted backend CI context requires CHARON_ENCRYPTION_KEY_TEST. Add repository secret CHARON_ENCRYPTION_KEY_TEST."
|
||||
exit 1
|
||||
fi
|
||||
resolved_key="$WORKFLOW_SECRET_KEY"
|
||||
elif [[ "$is_untrusted" == "true" ]]; then
|
||||
resolved_key="$(openssl rand -base64 32)"
|
||||
else
|
||||
echo "::error title=Unsupported event context::Unable to classify trust for backend key resolution (event=${EVENT_NAME})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$resolved_key" ]]; then
|
||||
echo "::error title=Key resolution failure::Resolved encryption key is empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "::add-mask::$resolved_key"
|
||||
{
|
||||
echo "CHARON_ENCRYPTION_KEY<<__CHARON_EOF__"
|
||||
echo "$resolved_key"
|
||||
echo "__CHARON_EOF__"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
@@ -39,7 +119,6 @@ jobs:
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
CHARON_ENCRYPTION_KEY: ${{ secrets.CHARON_ENCRYPTION_KEY_TEST }}
|
||||
run: |
|
||||
bash "scripts/go-test-coverage.sh" 2>&1 | tee backend/test-output.txt
|
||||
exit "${PIPESTATUS[0]}"
|
||||
@@ -64,7 +143,7 @@ jobs:
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# Codecov upload moved to `codecov-upload.yml` which is push-only.
|
||||
# Codecov upload moved to `codecov-upload.yml` (pull_request + workflow_dispatch).
|
||||
|
||||
|
||||
- name: Run golangci-lint
|
||||
@@ -118,7 +197,6 @@ jobs:
|
||||
PERF_MAX_MS_GETSTATUS_P95: 500ms
|
||||
PERF_MAX_MS_GETSTATUS_P95_PARALLEL: 1500ms
|
||||
PERF_MAX_MS_LISTDECISIONS_P95: 2000ms
|
||||
CHARON_ENCRYPTION_KEY: ${{ secrets.CHARON_ENCRYPTION_KEY_TEST }}
|
||||
run: |
|
||||
{
|
||||
echo "## 🔍 Running performance assertions (TestPerf)"
|
||||
@@ -212,7 +290,7 @@ jobs:
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# Codecov upload moved to `codecov-upload.yml` which is push-only.
|
||||
# Codecov upload moved to `codecov-upload.yml` (pull_request + workflow_dispatch).
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,198 +1,145 @@
|
||||
# PR #666 Patch Coverage Recovery Spec (Approval-Ready)
|
||||
# CI Encryption-Key Investigation and Remediation Plan
|
||||
|
||||
Date: 2026-02-16
|
||||
Owner: Planning Agent
|
||||
Status: Draft for Supervisor approval (single coherent plan)
|
||||
## Context
|
||||
- Date: 2026-02-17
|
||||
- Scope: CI failures where backend jobs report encryption key not picked up.
|
||||
- In-scope files:
|
||||
- `.github/workflows/quality-checks.yml`
|
||||
- `.github/workflows/codecov-upload.yml`
|
||||
- `scripts/go-test-coverage.sh`
|
||||
- `backend/internal/crypto/rotation_service.go`
|
||||
- `backend/internal/services/dns_provider_service.go`
|
||||
- `backend/internal/services/credential_service.go`
|
||||
|
||||
## 1) Scope Decision (Unified)
|
||||
## Problem Statement
|
||||
CI backend tests can fail late and ambiguously when `CHARON_ENCRYPTION_KEY` is missing or malformed. The root causes are context-dependent secret availability, missing preflight validation, and drift between workflow intent and implementation.
|
||||
|
||||
### In Scope
|
||||
- Backend unit-test additions only, targeting changed patch lines in backend handlers/services/utils.
|
||||
- Minimum-risk posture: prioritize test-only additions in files already touched by PR #666 before opening any new test surface.
|
||||
- Coverage validation using current backend coverage task/script.
|
||||
## Research Findings
|
||||
|
||||
### Out of Scope
|
||||
- E2E/Playwright, integration, frontend, Docker, and security scan remediation.
|
||||
### Workflow Surface and Risks
|
||||
| Workflow | Job | Key-sensitive step | Current key source | Main risk |
|
||||
|---|---|---|---|---|
|
||||
| `.github/workflows/quality-checks.yml` | `backend-quality` | `Run Go tests`, `Run Perf Asserts` | `${{ secrets.CHARON_ENCRYPTION_KEY_TEST }}` | Empty/malformed input not preflighted |
|
||||
| `.github/workflows/codecov-upload.yml` | `backend-codecov` | `Run Go tests with coverage` | `${{ secrets.CHARON_ENCRYPTION_KEY_TEST }}` | Same key-risk as above |
|
||||
|
||||
### E2E Decision for this task
|
||||
- E2E is explicitly **out-of-scope** for this patch-coverage remediation.
|
||||
- Rationale: target metric is Codecov patch lines on backend changes; E2E adds runtime risk/cycle time without direct line-level patch closure.
|
||||
### Backend Failure Surface
|
||||
- `backend/internal/crypto/rotation_service.go`
|
||||
- `NewRotationService(db *gorm.DB)` hard-fails if `CHARON_ENCRYPTION_KEY` is empty.
|
||||
- `backend/internal/services/dns_provider_service.go`
|
||||
- `NewDNSProviderService(...)` depends on `NewRotationService(...)` and can degrade to warning-based behavior when key input is bad.
|
||||
- `backend/internal/services/credential_service.go`
|
||||
- `NewCredentialService(...)` has the same dependency pattern.
|
||||
|
||||
### Scope Reconciliation with Current Implementation (PR #666)
|
||||
Confirmed already touched backend test files:
|
||||
- `backend/internal/api/handlers/import_handler_test.go`
|
||||
- `backend/internal/api/handlers/settings_handler_helpers_test.go`
|
||||
- `backend/internal/api/handlers/emergency_handler_test.go`
|
||||
- `backend/internal/services/backup_service_test.go`
|
||||
- `backend/internal/api/handlers/backup_handler_test.go`
|
||||
- `backend/internal/api/handlers/system_permissions_handler_test.go`
|
||||
- `backend/internal/api/handlers/notification_provider_handler_validation_test.go`
|
||||
### Script Failure Mode
|
||||
- `scripts/go-test-coverage.sh` currently uses `set -euo pipefail` but does not pre-validate key shape before `go test`.
|
||||
- Empty secret expressions become late runtime failures instead of deterministic preflight failures.
|
||||
|
||||
Optional/deferred (not yet touched in current remediation pass):
|
||||
- `backend/internal/util/permissions_test.go`
|
||||
- `backend/internal/services/notification_service_json_test.go`
|
||||
- `backend/internal/services/backup_service_rehydrate_test.go`
|
||||
- `backend/internal/api/handlers/security_handler_coverage_test.go`
|
||||
## Supervisor-Required Constraints (Preserved)
|
||||
1. `pull_request_target` SHALL NOT be used for secret-bearing backend test execution on untrusted code (fork PRs and Dependabot PRs).
|
||||
2. Same-repo `pull_request` and `workflow_dispatch` SHALL require `CHARON_ENCRYPTION_KEY_TEST`; missing secret SHALL fail fast (no fallback).
|
||||
3. Fork PRs and Dependabot PRs SHALL use workflow-only ephemeral key fallback for backend test execution.
|
||||
4. Key material SHALL NEVER be logged.
|
||||
5. Resolved key SHALL be masked before any potential output path.
|
||||
6. `GITHUB_ENV` propagation SHALL use safe delimiter write pattern.
|
||||
7. Workflow layer SHALL own key resolution/fallback.
|
||||
8. Script layer SHALL only validate and fail fast; it SHALL NOT generate fallback keys.
|
||||
9. Anti-drift guard SHALL be added so trigger comments and trigger blocks remain aligned.
|
||||
10. Known drift SHALL be corrected: comment in `quality-checks.yml` about `codecov-upload.yml` trigger behavior must match actual triggers.
|
||||
|
||||
## 2) Single Source of Truth for Success
|
||||
## EARS Requirements
|
||||
|
||||
Authoritative success metric:
|
||||
- **Codecov PR patch status (`lines`)** is the source of truth for this task.
|
||||
### Ubiquitous
|
||||
- THE SYSTEM SHALL fail fast with explicit diagnostics when encryption-key input is required and unavailable or malformed.
|
||||
- THE SYSTEM SHALL prevent secret-value exposure in logs, summaries, and artifacts.
|
||||
|
||||
Relationship to `codecov.yml`:
|
||||
- `coverage.status.patch.default.target: 100%` and `required: false` means patch status is advisory in CI.
|
||||
- For this plan, we set an internal quality gate: **patch lines >= 85%** (minimum), preferred **>= 87%** buffer.
|
||||
- Local script output (`go tool cover`) remains diagnostic; pass/fail is decided by Codecov patch `lines` after upload.
|
||||
### Event-driven
|
||||
- WHEN workflow context is trusted (same-repo `pull_request` or `workflow_dispatch`), THE SYSTEM SHALL require `secrets.CHARON_ENCRYPTION_KEY_TEST`.
|
||||
- WHEN workflow context is untrusted (fork PR or Dependabot PR), THE SYSTEM SHALL generate ephemeral key material in workflow preflight only.
|
||||
- WHEN workflow context is untrusted, THE SYSTEM SHALL NOT use `pull_request_target` for secret-bearing backend tests.
|
||||
|
||||
## 3) Feasibility Math and Coverage-Line Budget
|
||||
### Unwanted behavior
|
||||
- IF `CHARON_ENCRYPTION_KEY` is empty, non-base64, or decoded length is not 32 bytes, THEN THE SYSTEM SHALL stop before running tests.
|
||||
- IF trigger comments diverge from workflow triggers, THEN THE SYSTEM SHALL fail anti-drift validation.
|
||||
|
||||
Given baseline:
|
||||
- Patch coverage = `60.84011%`
|
||||
- Missing patch lines = `578`
|
||||
## Technical Design
|
||||
|
||||
Derived totals:
|
||||
- Let total patch lines = `T`
|
||||
- `578 = T * (1 - 0.6084011)` => `T ≈ 1476`
|
||||
- Currently covered lines `C0 = 1476 - 578 = 898`
|
||||
### Workflow Contract
|
||||
Both backend jobs (`backend-quality`, `backend-codecov`) implement the same preflight sequence:
|
||||
1. `Resolve encryption key for backend tests`
|
||||
2. `Fail fast when required encryption secret is missing`
|
||||
3. `Validate encryption key format`
|
||||
|
||||
Required for >=85%:
|
||||
- `C85 = ceil(0.85 * 1476) = 1255`
|
||||
- Additional covered lines required: `1255 - 898 = 357`
|
||||
### Preflight Resolution Algorithm
|
||||
1. Detect fork PR context via `github.event.pull_request.head.repo.fork`.
|
||||
2. Detect Dependabot PR context (actor/repo metadata check).
|
||||
3. Trusted context: require `secrets.CHARON_ENCRYPTION_KEY_TEST`; fail immediately if empty.
|
||||
4. Untrusted context: generate ephemeral key (`openssl rand -base64 32`) in workflow only.
|
||||
5. Mask resolved key via `::add-mask::`.
|
||||
6. Export via delimiter-based `GITHUB_ENV` write:
|
||||
- `CHARON_ENCRYPTION_KEY<<EOF`
|
||||
- `<value>`
|
||||
- `EOF`
|
||||
|
||||
Budget by phase (line-coverage gain target):
|
||||
### Script Validation Contract
|
||||
`scripts/go-test-coverage.sh` adds strict preflight validation:
|
||||
- Present and non-empty.
|
||||
- Base64 decodable.
|
||||
- Decoded length exactly 32 bytes.
|
||||
|
||||
| Phase | Target line gain | Cumulative gain target | Stop/Go threshold |
|
||||
|---|---:|---:|---|
|
||||
| Phase 1 | +220 | +220 | Stop if <+170; re-scope before Phase 2 |
|
||||
| Phase 2 | +100 | +320 | Stop if <+70; activate residual plan |
|
||||
| Phase 3 (residual closure) | +45 | +365 | Must reach >=+357 total |
|
||||
Script constraints:
|
||||
- SHALL NOT generate keys.
|
||||
- SHALL NOT select key source.
|
||||
- SHALL only validate and fail fast with deterministic error messages.
|
||||
|
||||
Notes:
|
||||
- Planned total gain `+365` gives `+8` lines safety over minimum `+357`.
|
||||
- If patch denominator changes due to rebase/new touched lines, recompute budget before continuing.
|
||||
### Error Handling Matrix
|
||||
| Condition | Detection layer | Outcome |
|
||||
|---|---|---|
|
||||
| Trusted context + missing secret | Workflow preflight | Immediate failure with explicit message |
|
||||
| Untrusted context + no secret access | Workflow preflight | Ephemeral key path (masked) |
|
||||
| Malformed key | Script preflight | Immediate failure before `go test` |
|
||||
| Trigger/comment drift | Workflow consistency guard | CI failure until synchronized |
|
||||
|
||||
## 4) Target Files/Functions (Concise, Specific)
|
||||
## Implementation Plan
|
||||
|
||||
Primary hotspots (Phase 1 focus, aligned to touched tests first):
|
||||
- `backend/internal/api/handlers/system_permissions_handler.go`
|
||||
- `RepairPermissions`, `repairPath`, `normalizePath`, `pathHasSymlink`, `isWithinAllowlist`, `mapRepairErrorCode`
|
||||
- `backend/internal/services/backup_service.go`
|
||||
- `RestoreBackup`, `extractDatabaseFromBackup`, `unzipWithSkip`, `RehydrateLiveDatabase`, `GetAvailableSpace`
|
||||
- `backend/internal/api/handlers/settings_handler.go`
|
||||
- `UpdateSetting`, `PatchConfig`, `validateAdminWhitelist`, `syncAdminWhitelistWithDB`
|
||||
- `backend/internal/api/handlers/import_handler.go`
|
||||
- `GetStatus`, `Upload`, `Commit`, `Cancel`, `safeJoin`
|
||||
- `backend/internal/api/handlers/backup_handler.go`
|
||||
- `Restore`, `isSQLiteTransientRehydrateError`
|
||||
- `backend/internal/api/handlers/emergency_handler.go`
|
||||
- `SecurityReset`, `disableAllSecurityModules`, `upsertSettingWithRetry`
|
||||
- `backend/internal/api/handlers/notification_provider_handler.go`
|
||||
- `isProviderValidationError`, provider validation branches
|
||||
### Phase 1: Workflow Hardening
|
||||
- Update `.github/workflows/quality-checks.yml` and `.github/workflows/codecov-upload.yml` with identical key-resolution and key-validation steps.
|
||||
- Enforce trusted-context fail-fast and untrusted-context fallback boundaries.
|
||||
- Add explicit prohibition notes and controls preventing `pull_request_target` migration for secret-bearing tests.
|
||||
|
||||
Secondary hotspots (Phase 2 focus, optional/deferred expansion):
|
||||
- `backend/internal/api/handlers/security_handler.go` (`GetStatus`, `latestConfigApplyState`)
|
||||
- `backend/internal/util/permissions.go` (`CheckPathPermissions`, `MapSaveErrorCode`, `MapDiagnosticErrorCode`)
|
||||
- `backend/internal/services/notification_service.go` (`sendJSONPayload`, `TestProvider`, `RenderTemplate`)
|
||||
### Phase 2: Script Preflight Hardening
|
||||
- Update `scripts/go-test-coverage.sh` to validate key presence/format/length before tests.
|
||||
- Preserve existing coverage behavior; only harden pre-test guard path.
|
||||
|
||||
## 5) Execution Phases with Strict Stop/Go and De-Scoping Rules
|
||||
### Phase 3: Anti-Drift Enforcement
|
||||
- Define one canonical backend-key-bootstrap contract path.
|
||||
- Add consistency check that enforces trigger/comment parity between `quality-checks.yml` and `codecov-upload.yml`.
|
||||
- Fix known push-only comment mismatch in `quality-checks.yml`.
|
||||
|
||||
### Phase 0 - Baseline Lock
|
||||
Actions:
|
||||
- Run `Test: Backend with Coverage` task (`.github/skills/scripts/skill-runner.sh test-backend-coverage`).
|
||||
- Record baseline patch lines from Codecov PR view and local artifact `backend/coverage.txt`.
|
||||
## Validation Plan
|
||||
Run these scenarios:
|
||||
1. Same-repo PR with valid secret.
|
||||
2. Same-repo PR with missing secret (must fail fast).
|
||||
3. Same-repo PR with malformed secret (must fail fast before tests).
|
||||
4. Fork PR with no secret access (must use ephemeral fallback).
|
||||
5. Dependabot PR with no secret access (must use ephemeral fallback, no `pull_request_target`).
|
||||
6. `workflow_dispatch` with valid secret.
|
||||
|
||||
Go gate:
|
||||
- Baseline captured and denominator confirmed.
|
||||
Expected results:
|
||||
- No late ambiguous key-init failures.
|
||||
- No secret material logged.
|
||||
- Deterministic and attributable failure messages.
|
||||
- Trigger docs and trigger config remain synchronized.
|
||||
|
||||
Stop gate:
|
||||
- If patch denominator changed by >5% from 1476, pause and recompute budgets before coding.
|
||||
## Acceptance Criteria
|
||||
- Backend jobs in `quality-checks.yml` and `codecov-upload.yml` no longer fail ambiguously on encryption-key pickup.
|
||||
- Trusted contexts fail fast if `CHARON_ENCRYPTION_KEY_TEST` is missing.
|
||||
- Untrusted contexts use workflow-only ephemeral fallback.
|
||||
- `scripts/go-test-coverage.sh` enforces deterministic key preflight checks.
|
||||
- `pull_request_target` is explicitly prohibited for secret-bearing backend tests on untrusted code.
|
||||
- Never-log-key-material and safe `GITHUB_ENV` propagation are implemented.
|
||||
- Workflow/script responsibility boundary is enforced.
|
||||
- Anti-drift guard is present and known trigger-comment mismatch is resolved.
|
||||
|
||||
### Phase 1 - High-yield branch closure
|
||||
Actions:
|
||||
- Extend existing tests only in:
|
||||
- `backend/internal/api/handlers/system_permissions_handler_test.go`
|
||||
- `backend/internal/services/backup_service_test.go`
|
||||
- `backend/internal/api/handlers/backup_handler_test.go`
|
||||
- `backend/internal/api/handlers/emergency_handler_test.go`
|
||||
- `backend/internal/api/handlers/settings_handler_helpers_test.go`
|
||||
- `backend/internal/api/handlers/import_handler_test.go`
|
||||
- `backend/internal/api/handlers/notification_provider_handler_validation_test.go`
|
||||
|
||||
Go gate:
|
||||
- Achieve >= `+170` covered patch lines and no failing backend tests.
|
||||
|
||||
Stop gate:
|
||||
- If < `+170`, do not proceed; re-scope to only highest delta-per-test functions.
|
||||
|
||||
### Phase 2 - Secondary branch fill
|
||||
Actions:
|
||||
- Extend tests in:
|
||||
- `backend/internal/api/handlers/security_handler_coverage_test.go`
|
||||
- `backend/internal/util/permissions_test.go`
|
||||
- `backend/internal/services/backup_service_rehydrate_test.go`
|
||||
- `backend/internal/services/notification_service_json_test.go`
|
||||
|
||||
Go gate:
|
||||
- Additional >= `+70` covered patch lines in this phase.
|
||||
|
||||
Stop gate:
|
||||
- If < `+70`, skip low-yield areas and move directly to residual-line closure.
|
||||
|
||||
### Phase 3 - Residual-line closure (minimum-risk)
|
||||
Actions:
|
||||
- Work only uncovered/partial lines still shown in Codecov patch details.
|
||||
- Add narrow table-driven tests to existing files; no new harness/framework.
|
||||
|
||||
Go gate:
|
||||
- Reach total >= `+357` covered lines and patch >=85%.
|
||||
|
||||
Stop gate:
|
||||
- If a residual branch requires production refactor, de-scope it and log as follow-up.
|
||||
|
||||
### Global de-scope rules (all phases)
|
||||
- No production code changes unless a test proves a correctness bug.
|
||||
- No new test framework, no integration/E2E expansion, no unrelated cleanup.
|
||||
- No edits outside targeted backend test and directly related helper files.
|
||||
|
||||
## 6) Current Tasks/Scripts (Deprecated references removed)
|
||||
|
||||
Use these current commands/tasks only:
|
||||
- Backend coverage (preferred): `Test: Backend with Coverage`
|
||||
- command: `.github/skills/scripts/skill-runner.sh test-backend-coverage`
|
||||
- Equivalent direct script: `bash scripts/go-test-coverage.sh`
|
||||
- Optional backend unit quick check: `Test: Backend Unit Tests`
|
||||
- command: `.github/skills/scripts/skill-runner.sh test-backend-unit`
|
||||
|
||||
Deprecated tasks are explicitly out-of-plan (for this work):
|
||||
- `Security: CodeQL Go Scan (DEPRECATED)`
|
||||
- `Security: CodeQL JS Scan (DEPRECATED)`
|
||||
|
||||
## 7) Residual Uncovered Lines Handling (Beyond hotspot table)
|
||||
|
||||
After each phase, run a residual triage loop:
|
||||
1. Export remaining uncovered/partial patch lines from Codecov patch detail.
|
||||
2. Classify each residual line into one of:
|
||||
- `validation/error mapping`
|
||||
- `permission/role guard`
|
||||
- `fallback/retry`
|
||||
- `low-value defensive log/telemetry`
|
||||
3. Apply closure rule:
|
||||
- First three classes: add targeted tests in existing suite.
|
||||
- Last class: close only if deterministic and cheap; otherwise de-scope with rationale.
|
||||
4. Maintain a residual ledger in the PR description:
|
||||
- line(s), owning function, planned test, status (`closed`/`de-scoped`), reason.
|
||||
|
||||
Exit condition:
|
||||
- No unclassified residual lines remain.
|
||||
- Any de-scoped residual lines have explicit follow-up items.
|
||||
|
||||
## 8) Acceptance Criteria (Unified)
|
||||
|
||||
1. One coherent plan only (this document), no conflicting statuses.
|
||||
2. E2E explicitly out-of-scope for this patch-coverage task.
|
||||
3. Success is measured by Codecov patch `lines`; local statement output is diagnostic only.
|
||||
4. Feasibility math and phase budgets remain explicit and tracked against actual deltas.
|
||||
5. All phase stop/go gates enforced; de-scope rules followed.
|
||||
6. Only current tasks/scripts are referenced.
|
||||
7. Residual uncovered lines are either closed with tests or formally de-scoped with follow-up.
|
||||
8. Scope remains reconciled with touched files first; deferred files are only pulled in if phase gates require expansion.
|
||||
## Handoff to Supervisor
|
||||
- This document is intentionally single-scope and restricted to CI encryption-key investigation/remediation.
|
||||
- Legacy multi-topic coverage planning content has been removed from this file to maintain coherence.
|
||||
|
||||
35
scripts/ci/check-codecov-trigger-parity.sh
Normal file
35
scripts/ci/check-codecov-trigger-parity.sh
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
QUALITY_WORKFLOW=".github/workflows/quality-checks.yml"
|
||||
CODECOV_WORKFLOW=".github/workflows/codecov-upload.yml"
|
||||
EXPECTED_COMMENT='Codecov upload moved to `codecov-upload.yml` (pull_request + workflow_dispatch).'
|
||||
|
||||
fail() {
|
||||
local message="$1"
|
||||
echo "::error title=Codecov trigger/comment drift::${message}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ -f "$QUALITY_WORKFLOW" ]] || fail "Missing workflow file: $QUALITY_WORKFLOW"
|
||||
[[ -f "$CODECOV_WORKFLOW" ]] || fail "Missing workflow file: $CODECOV_WORKFLOW"
|
||||
|
||||
grep -qE '^on:' "$QUALITY_WORKFLOW" || fail "quality-checks workflow is missing an 'on:' block"
|
||||
grep -qE '^on:' "$CODECOV_WORKFLOW" || fail "codecov-upload workflow is missing an 'on:' block"
|
||||
|
||||
grep -qE '^ pull_request:' "$QUALITY_WORKFLOW" || fail "quality-checks must run on pull_request"
|
||||
if grep -qE '^ workflow_dispatch:' "$QUALITY_WORKFLOW"; then
|
||||
fail "quality-checks unexpectedly includes workflow_dispatch; keep Codecov manual trigger scoped to codecov-upload workflow"
|
||||
fi
|
||||
|
||||
grep -qE '^ pull_request:' "$CODECOV_WORKFLOW" || fail "codecov-upload must run on pull_request"
|
||||
grep -qE '^ workflow_dispatch:' "$CODECOV_WORKFLOW" || fail "codecov-upload must run on workflow_dispatch"
|
||||
if grep -qE '^ pull_request_target:' "$CODECOV_WORKFLOW"; then
|
||||
fail "codecov-upload must not use pull_request_target"
|
||||
fi
|
||||
|
||||
if ! grep -Fq "$EXPECTED_COMMENT" "$QUALITY_WORKFLOW"; then
|
||||
fail "quality-checks Codecov handoff comment is missing or changed; expected: $EXPECTED_COMMENT"
|
||||
fi
|
||||
|
||||
echo "Codecov trigger/comment parity check passed"
|
||||
@@ -13,6 +13,26 @@ BACKEND_DIR="$ROOT_DIR/backend"
|
||||
COVERAGE_FILE="$BACKEND_DIR/coverage.txt"
|
||||
MIN_COVERAGE="${CHARON_MIN_COVERAGE:-${CPM_MIN_COVERAGE:-85}}"
|
||||
|
||||
if [[ -z "${CHARON_ENCRYPTION_KEY:-}" ]]; then
|
||||
echo "Error: CHARON_ENCRYPTION_KEY is required for backend tests."
|
||||
echo "Set CHARON_ENCRYPTION_KEY to a base64-encoded 32-byte key before running this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DECODED_KEY_HEX=""
|
||||
if ! DECODED_KEY_HEX=$(printf '%s' "$CHARON_ENCRYPTION_KEY" | base64 --decode 2>/dev/null | od -An -tx1 -v | tr -d ' \n'); then
|
||||
echo "Error: CHARON_ENCRYPTION_KEY is not valid base64."
|
||||
echo "Provide a base64-encoded key whose decoded length is exactly 32 bytes."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DECODED_KEY_BYTES=$(( ${#DECODED_KEY_HEX} / 2 ))
|
||||
if [[ "$DECODED_KEY_BYTES" -ne 32 ]]; then
|
||||
echo "Error: CHARON_ENCRYPTION_KEY decoded length is ${DECODED_KEY_BYTES} bytes; expected 32 bytes."
|
||||
echo "Regenerate key (example): openssl rand -base64 32"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Perf asserts are sensitive to -race overhead; loosen defaults for hook runs
|
||||
export PERF_MAX_MS_GETSTATUS_P95="${PERF_MAX_MS_GETSTATUS_P95:-25ms}"
|
||||
export PERF_MAX_MS_GETSTATUS_P95_PARALLEL="${PERF_MAX_MS_GETSTATUS_P95_PARALLEL:-50ms}"
|
||||
|
||||
Reference in New Issue
Block a user