Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ jobs:
- 'operator/config/**'
- 'charts/gameplane/crds/**'
- 'charts/gameplane/crd-manifests/**'
- 'hack/check-codegen-drift_test.sh'
ci:
- '.github/workflows/ci.yaml'
- '.github/actions/**'
Expand Down Expand Up @@ -517,6 +518,32 @@ jobs:
- name: test up.sh registry/context bootstrap
run: make test-up-registry

# Spec 013 G5: regenerated files (deepcopy, operator/config/{crd,rbac}, chart
# CRDs, go.mod/go.sum) must be committed with the change that needs them. The
# proof step shows the gate goes red on a types edit committed without regenerating.
codegen-drift:
name: codegen drift
needs: [changes]
if: needs.changes.outputs.codegen == 'true' || github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/go-cache
with:
key-suffix: codegen-drift
- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
- name: regenerate deepcopy, CRDs, RBAC and chart CRD copies
run: make generate manifests
- name: go mod tidy across modules
run: make tidy
- name: fail on drift
run: git diff --exit-code
- name: prove the gate fails on a stale types edit
run: ./hack/check-codegen-drift_test.sh

lint:
name: lint (${{ matrix.module }})
needs: [changes]
Expand Down Expand Up @@ -1957,6 +1984,7 @@ jobs:
build-images-arm64,
capture-sidecar-setcap-proof,
docs,
codegen-drift,
lint,
go,
web,
Expand Down Expand Up @@ -2068,7 +2096,7 @@ jobs:

const NEEDS_ORDER = [
'changes', 'build-images', 'build-images-arm64',
'capture-sidecar-setcap-proof', 'docs', 'lint', 'go',
'capture-sidecar-setcap-proof', 'docs', 'codegen-drift', 'lint', 'go',
'web', 'web-e2e-mock', 'helm', 'chart-template', 'workflow-lint', 'go-e2e-unit',
'api-postgres',
'e2e-buckets', 'e2e-go', 'e2e-go-arm64', 'e2e-multicluster', 'e2e-multicluster-arm64', 'e2e-upgrade', 'e2e-upgrade-arm64',
Expand All @@ -2084,6 +2112,7 @@ jobs:
'build-images-arm64': (n) => n === 'build e2e images (arm64)',
'capture-sidecar-setcap-proof': (n) => n === 'capture-sidecar CAP_NET_RAW survival',
docs: (n) => n === 'docs and repository checks',
'codegen-drift': (n) => n === 'codegen drift',
lint: (n) => n.startsWith('lint ('),
go: (n) => n.startsWith('go (') && n !== 'go (test/e2e)',
web: (n) => n === 'web',
Expand Down
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
github.com/prometheus/procfs v0.21.0/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
Expand Down
90 changes: 90 additions & 0 deletions hack/check-codegen-drift_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# check-codegen-drift_test.sh: Proof that the "codegen drift" CI gate fails on a
# types edit committed without regenerating (spec 013 G5,
# specs/013-expand-test-coverage/contracts/static-gates.md).
#
# The gate is: make generate manifests && make tidy && git diff --exit-code
# run inside a checkout. This script runs it in a throwaway detached worktree:
#
# Case 1 (must FAIL): a probe commit edits
# operator/api/v1alpha1/gameserver_types.go (doc comment + json tag of
# Suspend) without regenerating. The gate must exit non-zero.
# Case 2 (must PASS): the probe commit is dropped; the clean checkout must
# pass the gate.
#
# The main working tree is never modified. The worktree and temp dir are
# removed on exit.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/.."
REPO_ROOT="$(pwd)"

TYPES_FILE="operator/api/v1alpha1/gameserver_types.go"
TAG_OLD='json:"suspend,omitempty"'
TAG_NEW='json:"suspendDriftProbe,omitempty"'
DOC_LINE='// DriftProbe: doc comment added by hack/check-codegen-drift_test.sh.'

tmp="$(mktemp -d)"
wt="$tmp/wt"

cleanup() {
git -C "$REPO_ROOT" worktree remove --force "$wt" >/dev/null 2>&1 || true
rm -rf "$tmp"
}
trap cleanup EXIT

# gate DIR: regenerate, tidy, then require a clean tree.
gate() {
local dir="$1"
(cd "$dir" && make generate manifests && make tidy && git diff --exit-code)
}

git worktree add --detach "$wt" HEAD >/dev/null

echo "== Case 1: stale types edit committed without regenerating (expect FAIL)"

# Insert the doc comment above the first `Suspend bool` field and rename its
# json tag, on that same line only.
line_no="$(grep -n -m1 -F " Suspend bool \`$TAG_OLD\`" "$wt/$TYPES_FILE" | cut -d: -f1 || true)"
if [[ -z "$line_no" ]]; then
echo "✗ could not find the Suspend field in $TYPES_FILE" >&2
exit 1
fi
awk -v ln="$line_no" -v old="$TAG_OLD" -v new="$TAG_NEW" -v doc=" $DOC_LINE" '
NR == ln {
print doc
sub(old, new)
}
{ print }
' "$wt/$TYPES_FILE" > "$tmp/types.go"
cat "$tmp/types.go" > "$wt/$TYPES_FILE"

if ! grep -q 'suspendDriftProbe' "$wt/$TYPES_FILE" || ! grep -q 'DriftProbe:' "$wt/$TYPES_FILE"; then
echo "✗ probe edit did not apply to $TYPES_FILE" >&2
exit 1
fi

git -C "$wt" -c user.name=drift-probe -c user.email=drift-probe@invalid \
commit -q --no-gpg-sign -am "drift probe"

# Regeneration itself must succeed, so the failure below is the drift diff
# and not a broken toolchain.
(cd "$wt" && make generate manifests && make tidy)
if git -C "$wt" diff --exit-code; then
echo "✗ gate passed on a stale types edit" >&2
exit 1
fi
echo "✓ gate failed on a stale types edit"

echo
echo "== Case 2: clean checkout (expect PASS)"

git -C "$wt" reset -q --hard HEAD~1
git -C "$wt" clean -fdq

if ! gate "$wt"; then
echo "✗ gate failed on a clean checkout" >&2
exit 1
fi
echo "✓ gate passed on a clean checkout"
6 changes: 3 additions & 3 deletions specs/013-expand-test-coverage/coverage-gaps.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Coverage Gap Record

**Summary**: 2/24 closed · 0 closed unproven · 2 in progress · 19 open · 1 unit-only by design
**Last updated**: 2026-10-11
**Summary**: 3/24 closed · 0 closed unproven · 2 in progress · 18 open · 1 unit-only by design
**Last updated**: 2026-10-12

| ID | Category | Gap | Story | FR | Priority | Status | Evidence | Notes |
|---|---|---|---|---|---|---|---|---|
| G-01 | static | govulncheck gate | US1 | FR-001 | P1 | open | | |
| G-02 | static | container image scan gate | US1 | FR-001 | P1 | open | | |
| G-03 | static | Dockerfile lint gate | US1 | FR-001 | P1 | open | | |
| G-04 | static | dependency-review gate | US1 | FR-001 | P1 | open | | |
| G-05 | static | codegen / tidy drift gate | US1 | FR-001 | P1 | open | | |
| G-05 | static | codegen / tidy drift gate | US1 | FR-001 | P1 | closed | `codegen drift` https://github.com/GameplanePanel/Gameplane/actions/runs/38189397259 | First run on #672 found no drift; the proof shows the job fails on injected drift. |
| G-06 | static | submodule pointer freshness gate | US1 | FR-001 | P1 | open | | |
| G-07 | e2e-dashboard | live: notification webhook config | US2 | FR-002 | P1 | open | | |
| G-08 | e2e-dashboard | live: user create + role assignment | US2 | FR-002 | P1 | open | | |
Expand Down
7 changes: 4 additions & 3 deletions specs/013-expand-test-coverage/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,20 @@ description: "Task list for 013-expand-test-coverage"

### G5 codegen drift: PR `ci: codegen drift gate`

- [ ] T012 [P] [US1] Write `hack/check-codegen-drift_test.sh`. In a `git worktree add` temp checkout it:
- [X] T012 [P] [US1] Write `hack/check-codegen-drift_test.sh`. In a `git worktree add` temp checkout it:
1. Appends a doc comment to a field in `operator/api/v1alpha1/gameserver_types.go`, edits the struct tag on that field, runs `make generate manifests && make tidy && git diff --exit-code`, and expects a non-zero exit.
2. Resets the checkout, reruns the same command, and expects zero.

It removes the worktree on exit.
- [ ] T013 [US1] Add a `codegen-drift` job named `codegen drift` to `.github/workflows/ci.yaml`. Settings:
- [X] T013 [US1] Add a `codegen-drift` job named `codegen drift` to `.github/workflows/ci.yaml`. Settings:
- `needs: [changes]`
- `if: needs.changes.outputs.codegen == 'true' || github.event_name == 'push'`
- setup: `actions/checkout`, `./.github/actions/go-cache`, `azure/setup-helm`
- steps: `make generate manifests`, then `make tidy`, then `git diff --exit-code`, then `./hack/check-codegen-drift_test.sh`

Register the job in `report`. Depends on T007 and T012.
- [ ] T014 [US1] If the first CI run of T013 shows drift, commit the regenerated files the job printed: deepcopy, `operator/config/**`, chart CRDs, `go.mod`/`go.sum`. Flip gap row G-05 with the job name and run link.
- [X] T014 [US1] If the first CI run of T013 shows drift, commit the regenerated files the job printed: deepcopy, `operator/config/**`, chart CRDs, `go.mod`/`go.sum`. Flip gap row G-05 with the job name and run link.
*Implementation note (2026-10-12)*: the first run of `codegen drift` (run 38189397259) found no drift, so there was nothing to commit.

### G1 govulncheck: PR `ci: go vulnerability gate`

Expand Down
Loading