feat(runner): isolate structured workflows in one-attempt Kubernetes Pods #738
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
| # Quality gates only. No image build, no release. | |
| # | |
| # Triggers: | |
| # - pull_request: fast feedback on every PR | |
| # - push to main: post-merge sanity (catches squash-regressions). Releases | |
| # are cut by merging the release-please Release PR, which these same PR | |
| # checks gate. | |
| # - workflow_call: retained for reuse; release-please.yml relies on the PR | |
| # checks above rather than calling this workflow. | |
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| workflow_call: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint-and-test: | |
| name: Lint & Test | |
| # `head_commit.message` is only present on `push` events. On | |
| # `pull_request` and `workflow_call` it is null and contains() against | |
| # null can behave unexpectedly. Restrict the skip-ci optimization to | |
| # push events so PRs always run CI. | |
| if: ${{ github.event_name != 'push' || | |
| !contains(github.event.head_commit.message, 'skip ci') }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| # Postgres + Valkey are provisioned as service containers so the | |
| # DB-backed integration suites (test/db/migrate.test.ts, | |
| # test/integration/repo-knowledge.test.ts, | |
| # test/integration/telemetry-aggregates.test.ts) actually execute in | |
| # CI instead of `describe.skipIf`-ing themselves to green. Image tags | |
| # and healthcheck cadence mirror docker-compose.dev.yml. | |
| services: | |
| postgres: | |
| image: pgvector/pgvector:pg17 | |
| env: | |
| POSTGRES_USER: bot | |
| POSTGRES_PASSWORD: bot | |
| POSTGRES_DB: github_app | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U bot -d github_app" | |
| --health-interval 5s | |
| --health-timeout 3s | |
| --health-retries 5 | |
| valkey: | |
| image: valkey/valkey:9 | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "valkey-cli ping" | |
| --health-interval 5s | |
| --health-timeout 3s | |
| --health-retries 5 | |
| steps: | |
| - name: Checkout source code | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| # Full history kept so release-please can walk tags/commits for | |
| # accurate version + changelog inference. Secret scanning lives | |
| # in .github/workflows/secrets-scan.yml now. | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: .tool-versions | |
| - name: Cache Bun install dir | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Typecheck | |
| run: bun run typecheck | |
| - name: Lint | |
| run: bun run lint | |
| - name: Format check | |
| run: bun run format | |
| - name: Dockerfile base-sync guard | |
| run: bun run check:dockerfile-base-sync | |
| - name: GitHub Actions SHA-pin guard | |
| # Fails if any third-party `uses:` reference is on a mutable tag | |
| # instead of a 40-char commit SHA. Prevents a hand-edited workflow | |
| # from reintroducing the supply-chain vector closed in issue #137. | |
| run: bun run check:action-pins | |
| - name: Runner pin guard | |
| # Fails if any `runs-on:` is on a `*-latest` rolling alias instead of | |
| # an explicit image (e.g. ubuntu-24.04). Prevents a hand-edited | |
| # workflow from reintroducing the drift closed in issue #173. | |
| run: bun run check:runner-pins | |
| - name: Test-glob drift guard | |
| # Fails if any `*.test.ts` file is outside the canonical `test/` tree | |
| # consumed by scripts/test-isolated.sh. | |
| run: bun run check:test-globs | |
| - name: Destructive-action guard (FR-009) | |
| # Fails if the ship workflow or a daemon scoped executor contains a | |
| # destructive git/merge call (force-push, reset --hard, gh pr merge, | |
| # merge mutations). Previously local-only; wired into CI so the guard | |
| # is actually enforced. See issue #203. | |
| run: bun run check:no-destructive | |
| - name: Env-contract guard | |
| # Fails on a stale env-contract.json (vs src/config.ts's loadConfig), | |
| # an env var undocumented in docs/operate/configuration.md, or a | |
| # secret-shaped name not classified in SECRET_ENV_VARS. chrisleekr/helm-charts | |
| # consumes env-contract.json (at v<appVersion>) for its ConfigMap/Secret | |
| # parity gate, so this keeps config.ts, the contract, and the docs aligned. | |
| run: bun run check:env-contract | |
| - name: Config-schema guard | |
| # Fails when schema/github-app.schema.json drifts from | |
| # src/repo-config/schema.ts. That file is what editors consume via the | |
| # `# yaml-language-server: $schema=` modeline, so a stale copy would | |
| # advertise a config surface the runtime no longer accepts. | |
| run: bun run check:config-schema | |
| - name: Docs-sync guard (bot workflows, FR-019) | |
| if: github.event_name == 'pull_request' | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: bun run check:docs-sync | |
| - name: Dependency audit | |
| # Wraps `bun audit --json` to gate on severity. `bun audit` itself | |
| # exits 1 on ANY advisory regardless of --audit-level, so the | |
| # wrapper restores severity-based gating + an inline GHSA allowlist. | |
| run: bun run audit:ci | |
| - name: Seed test database | |
| # The `services:` Postgres container does NOT execute | |
| # /docker-entrypoint-initdb.d/, so the github_app_test database | |
| # mounted by docker-compose.dev.yml does not exist here. Create | |
| # it explicitly before the test step. `psql` ships with | |
| # postgresql-client, which is preinstalled on ubuntu-24.04 | |
| # runners (apt-get install kept as a fallback in case the image | |
| # changes upstream). | |
| env: | |
| PGPASSWORD: bot | |
| run: | | |
| if ! command -v psql >/dev/null 2>&1; then | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends postgresql-client | |
| fi | |
| psql -h localhost -U bot -d github_app -v ON_ERROR_STOP=1 -f scripts/init-test-db.sql | |
| - name: Test | |
| env: | |
| TEST_DATABASE_URL: postgres://bot:bot@localhost:5432/github_app_test | |
| # `VALKEY_URL` (no `TEST_` prefix) is what the test code actually | |
| # reads: `test/preload.ts` defaults it and `liveness-reaper.test.ts` | |
| # references `process.env["VALKEY_URL"]`. Setting it explicitly here | |
| # makes CI independent of the preload default so a future preload | |
| # tweak (or port-mapping change) cannot silently regress this job. | |
| VALKEY_URL: redis://localhost:6379 | |
| run: bun run test | |
| - name: Build | |
| run: bun run build | |
| - name: MCP bundle smoke check | |
| # Validates that every MCP server name registered in src/mcp/registry.ts | |
| # resolves to an existing dist/mcp/servers/<name>.js when consumed from | |
| # both bundle locations (dist/app.js + dist/daemon/main.js). Catches the | |
| # build/resolve drift class that shipped in 001990d before the daemon | |
| # image hits production. Must run AFTER `bun run build`. | |
| run: bun run check:mcp-bundle | |
| admission-policy: | |
| name: Workflow runner admission | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout source code | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| # This job runs repository-controlled scripts (bun install, the kind | |
| # admission harness). Nothing here talks to git after checkout, so the | |
| # token has no reason to sit in .git/config where those scripts read. | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version-file: .tool-versions | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Validate admission against Kubernetes 1.30 | |
| run: bun run test:admission |