chore: document maintenance freeze and pause version-update PRs #596
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI Baseline | |
| # Minimal CI for Phase 0: lint, secret scan, and proto lint (when applicable). | |
| # Build / test workflows are added in Phase 1+ once Bazel targets exist. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # Required for GitHub merge queue. Every context in main-protection's | |
| # required_status_checks list is a job in THIS workflow, so without a | |
| # merge_group trigger a queued PR waits forever on checks that never | |
| # run — the queue deadlocks rather than failing visibly. Adding the | |
| # trigger must therefore land BEFORE the merge_queue rule is enabled. | |
| merge_group: | |
| types: [checks_requested] | |
| # github.ref is the queue's own gh-readonly-queue/... ref on merge_group | |
| # events, so each queued entry gets its own concurrency group and entries | |
| # do not cancel each other. | |
| concurrency: | |
| group: ci-baseline-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Pre-commit: runs all hooks defined in .pre-commit-config.yaml | |
| # This is the primary lint / format gate. | |
| # --------------------------------------------------------------------------- | |
| pre-commit: | |
| name: Pre-commit hooks | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 # needed for buf-breaking against main | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Run pre-commit | |
| uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 | |
| # --------------------------------------------------------------------------- | |
| # Lockfile format guard — cheap, fast-failing check that the repo-root | |
| # pnpm-lock.yaml still declares lockfileVersion 6.0. Root package.json | |
| # pins "packageManager": "pnpm@8.6.7", and ADR-0015 explains why: 6.0 is | |
| # the lockfile format aspect_rules_js's npm_translate_lock expects. Both | |
| # this workflow's "Install frontend deps (hermetic pnpm)" step and | |
| # release-staging-frontend.yml consume this lockfile via Bazel. | |
| # | |
| # If the lockfile is ever regenerated with a pnpm newer than 8.6.7 (e.g. | |
| # a Dependabot npm PR that runs its own pnpm), lockfileVersion drifts to | |
| # 9.0 and the Bazel build breaks, not just frontend tests. No Bazel, no | |
| # pnpm install, no network here — just a checkout and a grep, so this | |
| # fails loud in seconds instead of surfacing later as a confusing Bazel | |
| # error. | |
| # | |
| # The value match is tolerant of quoting: native pnpm@8.6.7 output is | |
| # single-quoted (lockfileVersion: '6.0'), but the repo's prettier | |
| # pre-commit hook rewrites it to double-quoted (lockfileVersion: "6.0") | |
| # once it runs. Both are legitimate depending on whether prettier has | |
| # touched the file yet, so the check accepts bare, single-quoted, or | |
| # double-quoted "6.0" — but the value itself must match exactly | |
| # ("16.0" or "6.01" still fail; this is not a substring match). | |
| # --------------------------------------------------------------------------- | |
| lockfile-format-guard: | |
| name: Lockfile format guard | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Assert pnpm-lock.yaml is lockfileVersion 6.0 | |
| run: | | |
| set -euo pipefail | |
| file="pnpm-lock.yaml" | |
| expected_desc='lockfileVersion 6.0 (bare, single-quoted, or double-quoted)' | |
| if [ ! -f "$file" ]; then | |
| echo "::error::${file} not found at repo root. Expected ${expected_desc}. Regenerate with pnpm@8.6.7 per ADR-0015 (docs/ADR/0015-hermetic-nodejs-via-aspect-rules-js.md)." | |
| exit 1 | |
| fi | |
| found=$(grep -m1 '^lockfileVersion:' "$file" || true) | |
| if [ -z "$found" ]; then | |
| echo "::error::${file} has no lockfileVersion line at all. Expected ${expected_desc}. Regenerate with pnpm@8.6.7 per ADR-0015 (docs/ADR/0015-hermetic-nodejs-via-aspect-rules-js.md)." | |
| exit 1 | |
| fi | |
| # Anchored to the whole line: cannot match a nested key, and | |
| # cannot substring-match "16.0" / "6.01" — the value between | |
| # the optional quotes must be exactly 6.0. | |
| regex="^lockfileVersion:[[:space:]]*[\"']?6\.0[\"']?[[:space:]]*\$" | |
| if ! printf '%s\n' "$found" | grep -Eq "$regex"; then | |
| echo "::error::${file} lockfileVersion mismatch. Expected: ${expected_desc}. Found: ${found}. Regenerate the lockfile with pnpm@8.6.7 (root package.json packageManager pin) per ADR-0015 — a different lockfileVersion (e.g. 9.0 from a newer pnpm) breaks aspect_rules_js's npm_translate_lock and the Bazel build, not just the frontend tests." | |
| exit 1 | |
| fi | |
| echo "OK: ${file} declares ${expected_desc} (found: ${found})" | |
| # --------------------------------------------------------------------------- | |
| # Secret scanning — redundant with GitHub push protection + pre-commit | |
| # gitleaks, but runs in CI for pull requests from forks where push | |
| # protection may not apply. | |
| # --------------------------------------------------------------------------- | |
| secret-scan: | |
| name: Gitleaks secret scan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Run gitleaks | |
| uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # --------------------------------------------------------------------------- | |
| # Proto lint — only runs when proto/ directory contains .proto files. | |
| # In Phase 0 this job is a no-op until the contracts land in Phase 1. | |
| # --------------------------------------------------------------------------- | |
| buf-lint: | |
| name: Proto lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Check for proto files | |
| id: check_protos | |
| run: | | |
| if ls proto/**/*.proto >/dev/null 2>&1; then | |
| echo "has_protos=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_protos=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up buf | |
| if: steps.check_protos.outputs.has_protos == 'true' | |
| uses: bufbuild/buf-setup-action@a47c93e0b1648d5651a065437926377d060baa99 # v1 | |
| - name: Run buf lint | |
| if: steps.check_protos.outputs.has_protos == 'true' | |
| run: buf lint | |
| - name: Run buf breaking | |
| if: steps.check_protos.outputs.has_protos == 'true' && github.event_name == 'pull_request' | |
| # Pass the PR base ref through env, not direct ${{ }} interpolation | |
| # into the run: body — branch names are untrusted github-context | |
| # data, so inlining them is a shell-injection sink (semgrep | |
| # run-shell-injection). The env indirection makes the value a | |
| # quoted shell variable the runner cannot evaluate as code. | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| run: buf breaking --against ".git#branch=${BASE_REF}" | |
| # --------------------------------------------------------------------------- | |
| # Proto codegen drift — ADR-0013. The .pb.go files under | |
| # gateway_go/gen/go/ are checked in for IDE/gopls consumption while | |
| # Bazel remains the authoritative producer. This job re-runs | |
| # `buf generate` and fails if the working tree differs, catching PRs | |
| # that edit .proto without re-running tools/scripts/proto_gen.sh. | |
| # --------------------------------------------------------------------------- | |
| proto-codegen-drift: | |
| name: Proto codegen drift check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Regenerate via tools/scripts/proto_gen.sh | |
| run: ./tools/scripts/proto_gen.sh | |
| - name: Verify no diff | |
| # `frontend_web/src/gen/`, not `frontend_web/gen/`. buf.gen.yaml | |
| # writes the TypeScript tree under src/ so Vite's `@/` alias | |
| # resolves it; the old pathspec pointed at a directory that has | |
| # never existed, which made `git diff` match nothing and let TS | |
| # codegen drift through unnoticed. | |
| run: | | |
| if ! git diff --exit-code -- gateway_go/gen/ frontend_web/src/gen/ 2>/dev/null; then | |
| echo "::error::Generated proto code is out of sync with .proto sources." | |
| echo "::error::Run ./tools/scripts/proto_gen.sh locally and commit the result." | |
| exit 1 | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Bazel unit tests — runs //engine_cpp/tests/unit/... and any future | |
| # //gateway_go/...:test targets. Excludes integration tests tagged | |
| # `requires-model` because CI does not fetch the 75 MB whisper model | |
| # (per ADR-0011's CI integration plan; full WER regression lands in | |
| # Phase 2 with the cron-based workflow). | |
| # | |
| # actions/cache holds .bazel_cache so subsequent CI runs skip the | |
| # cold whisper.cpp + grpc build. Cache key is keyed on MODULE.bazel | |
| # so dep bumps invalidate cleanly. | |
| # --------------------------------------------------------------------------- | |
| bazel-unit-tests: | |
| name: Bazel unit tests | |
| runs-on: ubuntu-latest | |
| # cold ggml builds run 17-28min; 30 was too tight (run 27428960657 hit the wall 2026-06-12) | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Cache Bazel | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 | |
| with: | |
| # Linux CI runners have spaceless paths so our bazelisk wrapper | |
| # uses the in-repo .bazel_cache; on macOS dev machines with | |
| # spaces in the repo path it routes to /tmp/aegis-bazel-* — | |
| # both included so the cache pattern is portable. | |
| # .bazelisk/ holds the downloaded Bazel binary. | |
| # actions/cache stays as the no-internet fallback (ADR-0014 | |
| # α); BuildBuddy layered on top in the next step shortens | |
| # cold runs further by sharing artifacts across PRs. | |
| path: | | |
| ${{ github.workspace }}/.bazel_cache | |
| ${{ github.workspace }}/.bazelisk | |
| /tmp/aegis-bazel-* | |
| key: bazel-unit-${{ runner.os }}-${{ hashFiles('MODULE.bazel', 'MODULE.bazel.lock', '.bazelversion', '.bazelrc') }} | |
| restore-keys: | | |
| bazel-unit-${{ runner.os }}- | |
| # ADR-0014 Option β (Phase A, demo horizon): BuildBuddy Personal | |
| # remote cache. Writes .bazelrc.user (gitignored, runner-local | |
| # only) so every subsequent bazelisk build/test call in this job | |
| # inherits the cache flags without having to repeat them. The | |
| # committed .bazelrc stays cache-free, preserving the "clone it, | |
| # build it, it just works" local posture (README.md Quick Start). | |
| # | |
| # In forks without BUILDBUDDY_API_KEY set, this step no-ops and | |
| # Bazel falls back to local execution — CI still works, just | |
| # colder. See docs/runbooks/buildbuddy-cache-setup.md for how | |
| # upstream operators and fork operators provision the key. | |
| - name: Configure BuildBuddy remote cache | |
| env: | |
| BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} | |
| run: | | |
| if [ -z "${BUILDBUDDY_API_KEY:-}" ]; then | |
| echo "BUILDBUDDY_API_KEY not set — skipping remote cache (normal in forks)" | |
| exit 0 | |
| fi | |
| # Ensure any accidental echo of the key in later steps is masked. | |
| echo "::add-mask::$BUILDBUDDY_API_KEY" | |
| cat > .bazelrc.user <<EOF | |
| build --remote_cache=grpcs://remote.buildbuddy.io | |
| build --remote_header=x-buildbuddy-api-key=$BUILDBUDDY_API_KEY | |
| build --bes_backend=grpcs://remote.buildbuddy.io | |
| build --bes_results_url=https://app.buildbuddy.io/invocation/ | |
| build --remote_timeout=3600 | |
| EOF | |
| echo "BuildBuddy cache configured. Invocation dashboard: https://app.buildbuddy.io/" | |
| - name: Build proto + engine + gateway + gateway OCI image | |
| run: | | |
| # //packaging/gateway:image is the Phase 4a Slice 1 OCI smoke | |
| # target — builds the distroless layered image. ADR-0025. | |
| ./tools/bazelisk/bazelisk build \ | |
| //proto/aegis/v1:aegis_cc_grpc \ | |
| //proto/aegis/v1:aegis_go_proto \ | |
| //engine_cpp/cmd/engine:engine \ | |
| //gateway_go/cmd/gateway:gateway \ | |
| //packaging/gateway:image | |
| # ADR-0025 Camp B / dev-CI split: dev boxes never need Docker | |
| # (host-native binary suffices for development). CI is the SOLE | |
| # gate that proves "image actually boots inside a container", and | |
| # is the leftmost gate in the promotion chain to staging | |
| # (Trivy / Cosign / ECR push / ArgoCD sync land downstream in | |
| # Slices 4a-3 and Phase 4b). Skipping this would make Camp B | |
| # untenable. | |
| # | |
| # Verifies, in order: | |
| # 1. image loads into runner Docker (validates rules_oci output) | |
| # 2. container starts as nonroot under read-only rootfs | |
| # (validates ADR-0025 runtime posture, not just BUILD claims) | |
| # 3. /healthz returns 200 within 10s (validates the static-linked | |
| # gateway binary actually executes inside distroless — catches | |
| # missing /etc/passwd, DNS, certs, etc.) | |
| # 4. container is removed cleanly (no leak into next CI run) | |
| - name: Smoke-test gateway OCI image (boot + healthz + read-only rootfs) | |
| run: | | |
| set -euxo pipefail | |
| # The image_load target's repo_tags are defined in | |
| # packaging/gateway/BUILD.bazel as "aegis-core-gateway:dev-local"; | |
| # `bazel run` does NOT accept a tag override as a positional | |
| # argument (oci_load reads repo_tags from the BUILD attribute, | |
| # not from argv). Aligning the docker tag below. | |
| ./tools/bazelisk/bazelisk run //packaging/gateway:image_load | |
| docker run -d --name gw \ | |
| --read-only \ | |
| --user 65532:65532 \ | |
| -p 18080:8080 \ | |
| aegis-core-gateway:dev-local | |
| # Wait up to 10s for /healthz to return 200; the gateway tries | |
| # to dial the C++ engine on startup, which fails (engine isn't | |
| # in this CI step), but per main.go:440-443 /healthz still | |
| # returns 200 with engine.reachable=false. That's the expected | |
| # boot-up shape and what we're validating here. | |
| for i in 1 2 3 4 5 6 7 8 9 10; do | |
| if curl -sf http://localhost:18080/healthz > /tmp/healthz.json; then | |
| echo "healthz returned 200 after ${i}s" | |
| cat /tmp/healthz.json | |
| break | |
| fi | |
| sleep 1 | |
| if [ "$i" = "10" ]; then | |
| echo "ERROR: healthz never returned 200 within 10s" | |
| docker logs gw | |
| exit 1 | |
| fi | |
| done | |
| docker logs gw | |
| docker stop gw | |
| docker rm gw | |
| # Phase 4a Slice 2 — SBOM (CycloneDX) for the gateway image, per | |
| # ADR-0025 §"Sequencing across slices" and ARCHITECTURE.md §10.1 | |
| # ("every release artifact ... produces a CycloneDX or SPDX SBOM | |
| # via Syft"). Generated against the docker image that the smoke | |
| # step just verified — `aegis-core-gateway:dev-local` is still loaded | |
| # in the runner's docker daemon (only the container was removed, | |
| # the image stays). Pin by commit SHA, not @v0, for reproducibility | |
| # — bumping syft is a 1-line PR with the new SHA. | |
| # | |
| # Output is a workflow artifact `gateway-sbom-cyclonedx` retained | |
| # 90 days; Slice 4a-3 (ECR push) will pull it via download-artifact | |
| # to attach alongside the image; Phase 4b will sign it as a | |
| # Cosign attestation (anchore/sbom-action supports `cosign-*` | |
| # inputs natively at that point). | |
| - name: Generate gateway image SBOM (CycloneDX) | |
| # anchore/sbom-action v0.24.0 (released 2026-03-20) | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 | |
| with: | |
| image: aegis-core-gateway:dev-local | |
| format: cyclonedx-json | |
| artifact-name: gateway.sbom.cdx.json | |
| output-file: gateway.sbom.cdx.json | |
| upload-artifact: true | |
| upload-release-assets: false | |
| # ADR-0021 P3 — shared ggml runtime drift check. | |
| # | |
| # Layer 1: a cheap grep of GGML_VERSION_* from each archive's | |
| # ggml/CMakeLists.txt. Catches the easy case (someone bumped one of | |
| # the triple without the others) and the dangerous-direction case | |
| # (standalone @ggml older than a consumer's bundled ggml, which | |
| # will cause undefined-symbol link failures). | |
| # | |
| # Layer 2: build (not run) the integration test targets. These | |
| # link whisper + llama + shared ggml into a single binary, so the | |
| # link step fails if the three archives' ggml symbol tables don't | |
| # align — catches the "same version string, divergent source" | |
| # drift that Layer 1 by itself cannot detect (incident-10). | |
| - name: ggml version-string drift check | |
| run: ./tools/scripts/check_ggml_versions.sh | |
| - name: Build integration tests (shared ggml link check) | |
| run: | | |
| ./tools/bazelisk/bazelisk build \ | |
| //engine_cpp/tests/integration/... | |
| - name: Run unit tests (excludes requires-model) | |
| run: | | |
| ./tools/bazelisk/bazelisk test \ | |
| //engine_cpp/tests/unit/... \ | |
| --test_tag_filters=-requires-model | |
| # --------------------------------------------------------------------------- | |
| # ADR-0002 Phase 3 Web Frontend compliance — Tauri-wrap compatibility. | |
| # Greps frontend_web/src/ for the six forbidden patterns from ADR-0002's | |
| # "Constraints on the Phase 3 Web Frontend" section. Runs in < 1s; fails | |
| # the PR if a chrome.* / Service Worker / SharedArrayBuffer / out-of- | |
| # provider notification / Blob-download / navigator.mediaDevices slip- | |
| # through appears. Protects the Phase 4+ Tauri shell migration path. | |
| # --------------------------------------------------------------------------- | |
| frontend-tauri-compliance: | |
| name: Frontend Tauri-compatibility check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Run ADR-0002 Phase 3 compliance grep | |
| run: ./tools/scripts/check_frontend_tauri_compliance.sh | |
| # --------------------------------------------------------------------------- | |
| # Live-browser smoke (Playwright) — Phase 3c Slice 6. Runs the frontend | |
| # in real chromium + webkit engines via Playwright so UI-level | |
| # regressions cannot hide behind jsdom approximations. Rooted in | |
| # Incident 09's lesson: loopback/mock tests silently passed while | |
| # every real-browser Opus frame failed to decode. | |
| # | |
| # Browsers are kept out of actions/cache at the global user dir; | |
| # PLAYWRIGHT_BROWSERS_PATH is pinned to a repo-local path matching | |
| # tools/scripts/frontend.sh so the cache key can live in-repo. | |
| # --------------------------------------------------------------------------- | |
| frontend-e2e-smoke: | |
| name: Frontend live-browser smoke (Playwright) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 | |
| with: | |
| path: ${{ github.workspace }}/.playwright-browsers | |
| key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: | | |
| playwright-${{ runner.os }}- | |
| - name: Cache Bazel (Node toolchain) | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 | |
| with: | |
| path: | | |
| ${{ github.workspace }}/.bazel_cache | |
| ${{ github.workspace }}/.bazelisk | |
| key: bazel-e2e-${{ runner.os }}-${{ hashFiles('MODULE.bazel', 'MODULE.bazel.lock', '.bazelversion') }} | |
| restore-keys: | | |
| bazel-e2e-${{ runner.os }}- | |
| - name: Install frontend deps (hermetic pnpm) | |
| run: ./tools/scripts/frontend.sh install | |
| - name: Install Playwright browsers | |
| run: ./tools/scripts/frontend.sh e2e:install | |
| - name: Run Playwright smoke | |
| run: ./tools/scripts/frontend.sh e2e | |
| - name: Upload Playwright report on failure | |
| if: failure() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: playwright-report | |
| path: frontend_web/playwright-report/ | |
| retention-days: 7 | |
| # --------------------------------------------------------------------------- | |
| # Documentation link check — basic sanity for internal cross-references. | |
| # Uses a lightweight action; does not fail the build on external link | |
| # timeouts (those are noisy). | |
| # --------------------------------------------------------------------------- | |
| markdown-link-check: | |
| name: Markdown link check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Check links in markdown | |
| uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1 | |
| with: | |
| use-quiet-mode: "yes" | |
| use-verbose-mode: "no" | |
| check-modified-files-only: "yes" | |
| base-branch: main | |
| # Allowlist for GitHub / BuildBuddy settings URLs that 404 | |
| # anonymously by design (see config file header for rationale). | |
| config-file: .github/workflows/mlc_config.json | |
| # --------------------------------------------------------------------------- | |
| # Phase 4b security scanners. Each is a distinct concern, parallelised: | |
| # | |
| # - govulncheck: Go module + stdlib CVE scan (golang.org/x/vuln) | |
| # - gosec: Go source-level security lint (hardcoded creds, | |
| # SQL injection patterns, weak crypto primitives) | |
| # - semgrep: Polyglot SAST (Go + TS + bash + YAML); curated p/ci | |
| # ruleset covers CWE Top 25 + framework-specific | |
| # patterns ARCHITECTURE.md §10.2 calls for. | |
| # | |
| # The K8s-manifest scanners (Checkov + kube-score) that used to live | |
| # here were removed when the deploy manifests moved out of this repo | |
| # to `aegis-core-deploy` (ADR-0036) — there is no `apps/staging/` left | |
| # for them to scan. Manifest scanning will be re-established in | |
| # `aegis-core-deploy`'s own CI as a follow-up. | |
| # | |
| # All three remaining scanners are advisory on PR-time today | |
| # (non-blocking `|| true` guard) to let us see the baseline signal | |
| # before flipping to hard gates. Graduation criterion: a clean | |
| # main-branch run of each scanner; at that point we remove the guard | |
| # in a follow-up PR and the scanners become blocking. ARCHITECTURE.md | |
| # §10.2 is the anchor. | |
| # --------------------------------------------------------------------------- | |
| go-vulncheck: | |
| name: Go vulnerability scan (govulncheck) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: "1.25" | |
| cache-dependency-path: gateway_go/go.sum | |
| - name: Run govulncheck | |
| working-directory: gateway_go | |
| run: | | |
| go install golang.org/x/vuln/cmd/govulncheck@latest | |
| # Advisory-only on PR time; tighten to hard-gate once main is clean. | |
| govulncheck ./... || true | |
| go-security-scan: | |
| name: Go security scan (gosec) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: "1.25" | |
| cache-dependency-path: gateway_go/go.sum | |
| - name: Run gosec | |
| working-directory: gateway_go | |
| run: | | |
| go install github.com/securego/gosec/v2/cmd/gosec@latest | |
| # Advisory-only on PR time; -exclude-dir suppresses generated | |
| # protobuf code (G402 false positives on gRPC insecure creds | |
| # are inevitable in LOCAL-mode test setup). | |
| gosec -exclude-dir=gen ./... || true | |
| semgrep: | |
| name: Semgrep SAST scan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.x" | |
| - name: Run Semgrep | |
| run: | | |
| # `semgrep` Python package — pinning the major minor leaves | |
| # patch-level fixes auto-pulled while preventing surprise | |
| # major bumps that break the ruleset shape. Update line in | |
| # the same PR as a deliberate ruleset graduation. | |
| pip install --quiet 'semgrep~=1.95' | |
| # `--config p/ci` is Semgrep's curated CI-suitable ruleset | |
| # covering CWE Top 25 + framework-specific patterns across | |
| # Go / TS / Python / bash. Multi-language pass in one run. | |
| # `--metrics off` skips Semgrep Cloud telemetry per the | |
| # Phase 0 governance posture (don't phone home unless we | |
| # made an explicit choice). | |
| # `--error` makes findings exit non-zero; `|| true` keeps | |
| # the job advisory on PR-time per the Phase 4b graduation | |
| # discipline noted in the comment block above. | |
| semgrep scan --config p/ci --error --metrics off |