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:
| Job | What it does |
|---|---|
unit-test | go test -count=3 ./internal/... -cover + coverage-diff vs base |
lint | golangci-lint via reviewdog (inline PR comments) |
build-test | go build -o backend ./cmd/server/ — does it compile? |
proto-check | buf lint + buf breaking vs base (API compat) |
dbschema-test / gen-dags-test | codegen drift guards (below) |
gitleaks-scan | secret-leak scan |
conclusion | aggregates the required checks into one pass/fail |
-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
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.
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
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.)
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…
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…
git diff --exit-code — a
mismatch means someone forgot to regenerate.
Q3. go test -count=3 is used to catch…
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).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.