Lesson 3 · CI/CD & GitHub Actions fundamentals

The CI pipeline

What actually runs on your PR — lint, tests, drift guards — and the clever bit that skips most of it.

Your win: name the checks that gate a merge in this repo, and understand its standout feature — a "router" job that decides which checks run, so a monorepo PR doesn't rebuild the world.

The job of CI: catch it before merge

The pre-merge pipeline is the safety net — nothing merges until it's green. In this repo the gate is one big workflow, .github/workflows/tiered.pre_merge.yml, triggered when a PR gets the Ok to test label (on: pull_request: types: [labeled]). Its checks:

JobWhat it does
unit-testgo test -count=3 ./internal/... -cover + coverage-diff vs base
lintgolangci-lint via reviewdog (inline PR comments)
build-testgo build -o backend ./cmd/server/ — does it compile?
proto-checkbuf lint + buf breaking vs base (API compat)
dbschema-test / gen-dags-testcodegen drift guards (below)
gitleaks-scansecret-leak scan
conclusionaggregates the required checks into one pass/fail
A detail worth clocking: -count=3 Unit tests run three times (go test -count=3) — a cheap way to catch flaky tests: a test that passes once but fails on a re-run is unreliable, and CI surfaces it before it wastes everyone's time later.

The standout feature: a router that skips work

Monorepo problem, elegant fix In a monorepo, running every check on every PR would be enormously wasteful — a one-line doc change doesn't need the full test suite. This repo solves it with a router job: a requirements job runs a prebuilt diff binary that inspects the PR's changed files (plus squad membership and PR labels) and decides which jobs run — emitting a per-job on/off flag and the runner size each should use.1 Downstream jobs read those outputs. So most PRs skip most jobs. This "compute the plan, then run only what's needed" pattern is exactly what a good monorepo CI looks like.
Anchor — the router in code tiered.pre_merge.yml:82-108 — the requirements job runs the diff binary and outputs the per-job map; downstream jobs gate on it (e.g. a job's if: and its runs-on: ${{ fromJson(needs.requirements.outputs.runners)['<job>'] }}). A statuscheck binary in the conclusion job (:645-679) then enforces the required set (--require=…,unit-test,build-test,dbschema-test,lint,…,gitleaks-scan) so a skipped-but-required check can't sneak a bad merge through.

Drift guards — generated code must match

Anchor — ensure-make-* Two checks defend generated artifacts: dbschema-test runs .github/scripts/ensure-make-gen-db-schema.bash and gen-dags-test runs ensure-make-generate-dags.bash. Each regenerates the artifact and does git diff --exit-code — if the committed output differs from a fresh generation, CI fails. You met this pattern in the Airflow and Helm courses; it's the guarantee that "what's committed matches what the generator produces." (Proto codegen is guarded separately by buf breaking.)
Read this next

GitHub — Workflow syntax (jobs, needs, if) + about CI

Job dependencies, conditional execution, and the CI model these checks are built on.

docs.github.com — Workflow syntax
docs.github.com — Continuous integration

Check yourself (from memory)

Q1. The requirements router job exists to…

A diff binary inspects the PR and turns jobs on/off + picks runner sizes — so most PRs skip most jobs.

Q2. A drift guard (ensure-make-*) fails CI when…

It regenerates then git diff --exit-code — a mismatch means someone forgot to regenerate.

Q3. go test -count=3 is used to catch…

Running three times surfaces non-deterministic tests before they waste time downstream.
This repo's CI gate — the checks and the router.
recall, then click to reveal
PRE-MERGE gate = tiered.pre_merge.yml, triggered on PR label Ok to test. CHECKS: unit-test (go test -count=3 ./internal/... -cover — count=3 catches FLAKY tests + coverage-diff), lint (reviewdog golangci-lint, inline PR comments), build-test (go build -o backend ./cmd/server/), proto-check (buf lint + BREAKING), drift guards dbschema-test/gen-dags-test (regenerate + git diff --exit-code), gitleaks-scan, conclusion (required-checks aggregator). STANDOUT: a requirements ROUTER job runs a diff binary (:82-108) that inspects the PR → decides WHICH jobs run + their runner sizes → most PRs skip most jobs (monorepo efficiency).
Want to see how the diff router maps files → jobs, or why proto breaking-changes block a merge? Ask me.

1. In-repo: .github/workflows/tiered.pre_merge.yml:82-108 (the diff router); GitHub — Workflow syntax.