diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2c256e20e..582db3409 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -871,6 +871,18 @@ jobs: fetch - name: merge profiles + threshold gate run: make cover-go-merge && make cover-go-check + - name: per-file coverage of secrets_managed.go (spec 013 G-19) + # The threshold gate checks package totals only. Print this one file's + # statement coverage from the merged profile so coverage-gaps.md can + # cite a measured figure. + if: matrix.module == 'api' && matrix.arch == 'amd64' + run: | + awk '$1 ~ /\/internal\/handlers\/secrets_managed\.go:/ { total += $2; if ($3 > 0) covered += $2; else { split($1, pos, ":"); missed = missed " " pos[2] } } + END { if (total == 0) { print "secrets_managed.go not found in profile"; exit 1 } + printf "G-19 coverage of secrets_managed.go = %d/%d statements (%.1f%%)\n", covered, total, 100 * covered / total + printf "G-19 uncovered blocks (startLine.col,endLine.col):%s\n", missed }' \ + api/coverage/merged.out + (cd api && go tool cover -func=coverage/merged.out | grep -F 'internal/handlers/secrets_managed.go') web: name: web diff --git a/docs/contributing.md b/docs/contributing.md index 406df1f52..edef1368a 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -61,6 +61,17 @@ Every job in every workflow must have: Documented exceptions with longer timeouts are the five e2e jobs (60 minutes each) and the image build in `publish-edge.yaml` (35 minutes). +### Security and drift gates + +These jobs block merging. Each gate with a fixture has a `hack/*_test.sh` self-test that proves it can fail; dependency review is validated by configuration review only. Fix the cause: never add an ignore file, a suppression comment or a looser threshold. + +- **`vuln (go)`** runs `hack/govulncheck-all.sh`, which runs govulncheck over every module in `go.work`. It fails when a module reaches a symbol with a known vulnerability, and also when govulncheck itself hits a build or tool error. For a vulnerability, bump the affected dependency (or the Go toolchain) in that module's `go.mod`, run `make tidy`, and commit both. For any other error, fix the build or tool problem it reports. +- **`vuln (images)`** runs `hack/trivy-scan-images.sh` over the e2e images CI builds (`gameplane-test/:e2e` and `:e2e-postgres`). It fails on any HIGH or CRITICAL vulnerability that has a fix, and on a scan error. For a vulnerability, bump the base-image digest, or bump the Go or npm dependency the finding names in that image's module. For a scan error, fix what Trivy reports and re-run. +- **`workflow lint`** also runs hadolint over every tracked Dockerfile, failing on any warning (`.hadolint.yaml`). Fix the Dockerfile: pin `apk`/`apt` package versions, and set a `SHELL` with `-o pipefail` before any piped `RUN` step. +- **`dependency review`** runs on pull requests only. It fails when the PR adds a dependency with a HIGH or CRITICAL advisory. Pick a version without the advisory, or drop the dependency. +- **`codegen drift`** runs `make generate manifests` and `make tidy`, then fails if `git diff` is not empty. Run the same commands locally and commit what they change (CLAUDE.md rule 7). +- **`submodule freshness`** runs `hack/check-submodule-freshness.sh`. It fails when the `modules` or `website` pointer names a commit that is not on that repo's `main`, when a gitlink is missing, or when the script cannot fetch that repo's `main`. For a pointer that is not on `main`, merge the submodule PR first, then bump the pointer to the merged commit. Otherwise restore the gitlink, or fix the fetch error, and re-run. + ## Testing Run the whole suite: @@ -123,6 +134,20 @@ the dev ingress-nginx manifest pinned in `deploy/kind/up.sh` to finish (its replica available). It only runs when `deploy/kind/**` or the CI workflow file itself changes. +The `optional-components` bucket (`TestOptionalComponents_Lifecycle`) owns its +cluster because it runs `helm upgrade`. It checks that the MCP server, the +audit-syslog bridge and the tunnel are absent by default, then enables them +and checks each one. Its fixtures are `test/e2e/fixtures/frps.yaml` and +`test/e2e/fixtures/syslog-sink.yaml`. + +The `e2e postgres / amd64 (kind)` job runs the API against PostgreSQL. It +boots the cluster with `./deploy/kind/e2e.sh up "$CLUSTER" e2e postgres`, +which applies `deploy/kind/postgres.yaml` and installs the chart with +`api.db.driver=postgres`. It then runs the `api-auth`, `api-roles` and +`api-rbac` buckets, restarting the API between them to reset the in-memory +login rate limiter. The frozen 001–012 upgrade path is covered separately by +`TestPostgres_MigrateFromFrozenBaseline` in the `api (postgres)` job. + ## AI-assisted development Much of this codebase is developed with AI coding assistants (Claude diff --git a/specs/013-expand-test-coverage/coverage-gaps.md b/specs/013-expand-test-coverage/coverage-gaps.md index 441cef81f..99b2a91bd 100644 --- a/specs/013-expand-test-coverage/coverage-gaps.md +++ b/specs/013-expand-test-coverage/coverage-gaps.md @@ -1,7 +1,7 @@ # Coverage Gap Record -**Summary**: 21/24 closed · 1 closed unproven · 1 in progress · 0 open · 1 unit-only by design -**Last updated**: 2026-10-15 +**Summary**: 22/24 closed · 1 closed unproven · 0 in progress · 0 open · 1 unit-only by design +**Last updated**: 2026-10-16 | ID | Category | Gap | Story | FR | Priority | Status | Evidence | Notes | |---|---|---|---|---|---|---|---|---| @@ -23,7 +23,7 @@ | G-16 | e2e-optional | tunnel (tailscale, playit) | US3 | FR-003 | P2 | unit-only-by-design | | Tailscale and playit need external accounts and network reachability that CI cannot provide; covered by unit tests only (research R7). | | G-17 | e2e-optional | telemetry receiver | US3 | FR-003 | P2 | closed | `TestTelemetryLifecycle` in bucket `telemetry` (job `e2e telemetry`), spec 022: https://github.com/GameplanePanel/Gameplane/blob/master/test/e2e/telemetry_e2e_test.go | | | G-18 | e2e-optional | disabled state + no interference | US3 | FR-003 | P2 | closed | `e2e optional-components / amd64 (kind)` and `/ arm64 (kind)` https://github.com/GameplanePanel/Gameplane/actions/runs/38789759814 | `disabled` and `core-unaffected` subtests on #704: no MCP, bridge or tunnel Deployments or Services before enabling; afterward a GameServer reaches Running with a heartbeat and a viewer gets 403 on `/admin/audit`. | -| G-19 | unit | `secrets_managed.go` 100 % | US4 | FR-004 | P2 | in-progress | | `secrets_managed_test.go` added in https://github.com/GameplanePanel/Gameplane/pull/677; CI publishes only package totals, so the per-file figure is not yet measured. The `json.Marshal` error branch in `upsertLabelledSecret` is unreachable. | +| G-19 | unit | `secrets_managed.go` 100 % of reachable statements | US4 | FR-004 | P2 | closed | `go (api / amd64)` step "per-file coverage of secrets_managed.go" https://github.com/GameplanePanel/Gameplane/actions/runs/38951995010/job/117146106880 | Measured on #710: 27/28 statements (96.4 %); `deleteManagedSecret` 100 %, `upsertLabelledSecret` 95.5 %. The one uncovered block is lines 66–68, the `existing.Labels == nil` branch in `upsertLabelledSecret`. It cannot run: when `Labels` is nil, the feature-label check on line 62 has already returned. SC-004 was amended to 100 % of reachable statements (valgul, 2026-10-16). Tests from #677. | | G-20 | unit | `ConsoleShell.test.tsx` ≥ 80 % lines | US4 | FR-004 | P2 | closed | `web` https://github.com/GameplanePanel/Gameplane/actions/runs/38189418314 | `ConsoleShell.tsx` 100 % lines, 95 % branches (lcov from https://github.com/GameplanePanel/Gameplane/pull/677). | | G-21 | unit | `CloneServerDialog` ≥ 80 % lines + cancel/error | US4 | FR-004 | P2 | closed | `web` https://github.com/GameplanePanel/Gameplane/actions/runs/38189418314 | 89.29 % lines before T053; T053 adds API-error (409, 403, other status, non-API) and cancel-discards-changes cases in https://github.com/GameplanePanel/Gameplane/pull/677. | | G-22 | unit | `ServerActionsMenu` ≥ 80 % lines + cancel/error | US4 | FR-004 | P2 | closed | `web` https://github.com/GameplanePanel/Gameplane/actions/runs/38189418314 | 100 % lines, branches and functions. The menu makes no API calls; cancel and API errors live in its dialogs, covered by their own tests (G-21 for clone). | diff --git a/specs/013-expand-test-coverage/plan.md b/specs/013-expand-test-coverage/plan.md index 58d69e472..d324b48ac 100644 --- a/specs/013-expand-test-coverage/plan.md +++ b/specs/013-expand-test-coverage/plan.md @@ -95,7 +95,7 @@ specs/013-expand-test-coverage/ ├── research.md # Phase 0 (R0 baseline + R1–R11 decisions) ├── data-model.md # Phase 1: gate, fixture, gap, record entities ├── quickstart.md # Phase 1: how to validate each story in CI -├── OPEN-DECISIONS.md # OD-1..OD-3, all ruled (rule 10) +├── OPEN-DECISIONS.md # OD-1..OD-5, all ruled (rule 10) ├── contracts/ │ ├── static-gates.md # per-gate trigger / failing condition / proof script contract │ ├── coverage-gap-record.md # coverage-gaps.md format + validator rules diff --git a/specs/013-expand-test-coverage/spec.md b/specs/013-expand-test-coverage/spec.md index 9dc6de4ad..141736a76 100644 --- a/specs/013-expand-test-coverage/spec.md +++ b/specs/013-expand-test-coverage/spec.md @@ -290,7 +290,9 @@ migrations and queries work identically. telemetry-receiver) have at least one E2E test in a CI bucket, and the test verifies the component's primary operation works correctly. - **SC-004**: api/internal/handlers/secrets_managed.go (92 lines, upsertLabelledSecret, - deleteManagedSecret) has 100% test coverage; web components ConsoleShell, + deleteManagedSecret) has 100% test coverage of reachable statements (the + `existing.Labels == nil` branch in upsertLabelledSecret cannot run, because the + feature-label check above it has already returned); web components ConsoleShell, CloneServerDialog, ServerActionsMenu each have a .test.tsx file with ≥ 80% line coverage. - **SC-005**: When the Postgres build tag is enabled, a named e2e bucket runs the same diff --git a/specs/013-expand-test-coverage/tasks.md b/specs/013-expand-test-coverage/tasks.md index 9c2136da6..69dcf3f48 100644 --- a/specs/013-expand-test-coverage/tasks.md +++ b/specs/013-expand-test-coverage/tasks.md @@ -318,9 +318,9 @@ description: "Task list for 013-expand-test-coverage" ## Phase 6: User Story 4 – Unit coverage gaps (Priority: P2) -**Goal**: 100 % of `secrets_managed.go`, and ≥ 80 % lines for ConsoleShell, CloneServerDialog and ServerActionsMenu (research R8, SC-004). +**Goal**: 100 % of the reachable statements in `secrets_managed.go`, and ≥ 80 % lines for ConsoleShell, CloneServerDialog and ServerActionsMenu (research R8, SC-004). -**Independent Test**: the `go (api / amd64)` coverage artifact shows `secrets_managed.go` at 100 %, and the `web` job's lcov shows each of the three components at ≥ 80 % lines. +**Independent Test**: the `go (api / amd64)` step "per-file coverage of secrets_managed.go" shows every statement covered except the uncovered blocks it lists, and each of those is unreachable (on #710: 27/28, the one miss being the `existing.Labels == nil` branch at lines 66–68), and the `web` job's lcov shows each of the three components at ≥ 80 % lines. **PR**: `test: unit coverage for managed secrets and console shell`. Independent of every other phase. @@ -351,7 +351,7 @@ description: "Task list for 013-expand-test-coverage" *Implementation note (2026-10-12)*: lcov from run 38189418314: `CloneServerDialog.tsx` 89.29 % lines / 69.23 % branches, with a Cancel case but no API-error case, so T053 adds the API-error and cancel-discards-changes cases. `ServerActionsMenu.tsx` 100 % lines / branches / functions; it makes no API calls, so cancel and API errors are dialog-level (G-22 note). `ConsoleShell.tsx` 100 % lines. G-19 stays in progress: CI reports only package coverage for Go. -**Checkpoint**: SC-004 stays pending until G-19 has per-file proof for `secrets_managed.go` (G-20 to G-22 are proven). Module thresholds stay unchanged. +**Checkpoint**: G-19 to G-22 are proven. G-19 was measured in CI at 27/28 statements (96.4 %) for `secrets_managed.go`, which is 100 % of its reachable statements: the one miss is the unreachable `existing.Labels == nil` branch (lines 66–68). valgul chose "Measure in CI" and amended SC-004 to match, 2026-10-16. Module thresholds stay unchanged. --- @@ -394,9 +394,11 @@ description: "Task list for 013-expand-test-coverage" ## Phase 8: Polish & cross-cutting -- [ ] T059 [P] Update `docs/contributing.md`, under "CI and workflows", with one short paragraph per new gate: what fails it, and how to fix it without suppressing it. Update "E2E testing" for the `optional-components` bucket and the `e2e-postgres` job. CI's `make check-links` verifies the new anchors. -- [ ] T060 [P] Grep the full feature diff for forbidden directives: `git diff origin/master -- . ':!specs' | grep -nE "nolint|eslint-disable|@ts-ignore|hadolint ignore|trivyignore|allow-ghsas"` must print nothing (SC-006). -- [ ] T061 Confirm `coverage-gaps.md` has no `open` or `in-progress` rows and that `make check-coverage-gaps` passes in CI. Then run `/speckit-analyze` on this folder. +- [X] T059 [P] Update `docs/contributing.md`, under "CI and workflows", with one short paragraph per new gate: what fails it, and how to fix it without suppressing it. Update "E2E testing" for the `optional-components` bucket and the `e2e-postgres` job. CI's `make check-links` verifies the new anchors. +- [X] T060 [P] Grep the full feature diff for forbidden directives: `git diff origin/master -- . ':!specs' ':!*.md' | grep -nE "nolint|eslint-disable|@ts-ignore|hadolint ignore|trivyignore|allow-ghsas"` must print nothing (SC-006). + Result (2026-10-16): over the feature diff, from before #671 to master after #707, the command prints nothing. Markdown is excluded (valgul, 2026-10-16) because its only matches are prose in CLAUDE.md and the agent and skill docs telling contributors not to use these directives; SC-006 covers in-source directives. +- [X] T061 Confirm `coverage-gaps.md` has no `open` or `in-progress` rows and that `make check-coverage-gaps` passes in CI. Then run `/speckit-analyze` on this folder. + Result (2026-10-16): no open or in-progress rows; `make check-coverage-gaps` passed in "docs and repository checks" on #710 (https://github.com/GameplanePanel/Gameplane/actions/runs/38951995010/job/117146106043). `/speckit-analyze` found 0 critical issues; its two medium findings (the tasks.md checkpoint and the plan.md decision count) are fixed on #710, and SC-004 was amended. - [ ] T062 Once every PR above is merged and every task is checked or withdrawn, `git mv specs/013-expand-test-coverage specs/done_013-expand-test-coverage` and update every in-repo reference (including `hack/check-coverage-gaps.sh`'s default path) in one `docs:` commit (Constitution IV). ---