Chapter 24Lesson 01~165 minutes

Continuous Integration Patterns for Build, Test, Lint, and Coverage: Core Concepts and Mental Model

Build a CI mental model that keeps source identity, dependency resolution, lint, build, tests, coverage, artifacts and required-check conclusions independently observable.

CI modelSource SHAEvidenceRequired checksDeterminism

Learning objectives

  • Explain why a green “CI” badge is weaker evidence than a staged, revision-bound CI record.
  • Trace exact source SHA through dependency resolution, lint, build, tests, coverage and the final required check.
  • Separate runner filesystem, cache service, artifact service, check state and branch-protection policy.
  • Recognize where deterministic inputs end and mutable hosted-runner/tool state begins.
  • Design CI so a failure preserves useful evidence instead of erasing the causal layer.

1. The practical problem: one opaque script hides several different questions

A workflow that runs ./ci.sh and reports one red or green result can be useful, but it is weak operational evidence. A red result does not immediately tell a reviewer whether dependency resolution failed, lint rejected the source, the code did not build, a unit test failed, coverage missed policy, or the runner/toolchain itself drifted.

Chapter 24 treats CI as a chain of separately observable claims. Each claim is tied to the same event-selected revision and produces evidence that can survive long enough to diagnose the failure. The final merge decision is then an aggregation of those claims, not a substitute for them.

2. Mental model: exact revision in, independent claims out

Read the model from top to bottom. GitHub selects a workflow revision for an event. The checkout must resolve to the intended source SHA. Toolchain and dependency inputs are then established. Lint, build and test/coverage checks each answer a different question. Their logs and artifacts feed a stable aggregate check that branch protection can require.

CI causality and evidence boundaries
flowchart TD
  A[Event + workflow revision] --> B[Checkout exact GITHUB_SHA]
  B --> C[Toolchain + locked dependency inputs]
  C --> D[Lint evidence]
  C --> E[Build evidence]
  E --> F[Test + coverage evidence]
  D --> G[Stable aggregate CI check]
  F --> G
  E --> G
  G --> H[Branch / ruleset merge decision]
  C -. optional speed .-> I[(Actions cache)]
  D -. retained evidence .-> J[(Artifact service)]
  F -. retained evidence .-> J

The dashed arrows are intentionally not correctness arrows. A cache accelerates dependency work; it does not authorize dependency versions. An artifact preserves evidence; it does not make the check successful.

3. Define the state before CI changes it

Layer Record before/after Why it matters
Event/revision event name, ref, github.sha, PR head SHA when applicable, run ID/attempt Required checks must correspond to the intended/latest commit; PR merge SHA and PR head SHA are different identities.
Workflow/config workflow path/revision, job graph, stable job names, permissions A changed workflow can alter what “CI passed” means even on identical application source.
Runner/toolchain ubuntu-24.04, Python 3.13, action SHAs, tool versions Hosted images and preinstalled tools are mutable unless you establish explicit versions.
Dependencies requirements/lock input hash, resolver exit code, pip check, cache-hit signal The lock/input is correctness state; the cache is only a transport optimization.
Lint/build/test commands, exit codes, test counts, failing names, compile result Each check answers a distinct engineering question.
Coverage producer version, data file/report, threshold and threshold result A raw percentage is evidence; a threshold is policy.
Artifacts name, producing run/attempt/SHA, digest/retention Artifacts preserve reports independently of job color.
Governance required check name/source, branch/ruleset expectation A green non-required job does not prove the merge gate passed.

4. Read-only inspection before execution

Before changing CI, inspect what revision and tools you intend to validate. Locally, the following commands are non-destructive. In a workflow, record the same information into the job summary or a small evidence file.

git status --short
git rev-parse HEAD
git show -s --format='%H %cI %s' HEAD
sha256sum requirements-dev.txt
python --version
python -m pip --version

Inside Actions, compare git rev-parse HEAD with GITHUB_SHA. For pull_request, remember that the default event SHA usually identifies the test merge commit, while github.event.pull_request.head.sha identifies the contributor head. Decide which hypothesis you are testing and record both when the distinction matters.

5. Dependency, lint, build, test and coverage are separate claims

Stage Question Good evidence
Dependency resolution Can the declared dependency set be resolved and is it internally consistent? lock/input hash, installer version, resolver exit code, pip check, frozen environment manifest
Lint/static Does source satisfy the selected static rules? tool version, rule configuration, diagnostics, exit code
Build Can the selected source be transformed/compiled as expected? exact SHA, command, compiler/interpreter version, produced file/digest if any
Tests Does behavior satisfy executable expectations? test count, failures, JUnit/report, exit code
Coverage What executed under the coverage producer and does policy accept it? coverage version, report, percentage, threshold result
Aggregate check Are all required CI claims acceptable for this SHA? stable job/check name plus the upstream job conclusions

6. Cache is a performance hint, never the source of truth

A cache entry is mutable service-side state keyed by workflow-controlled text and cache version. GitHub first looks for an exact key match and can then restore prefix/restore-key matches. An exact hit is observable through cache-hit == 'true'; a miss is not an error if dependency installation can still succeed from authoritative inputs.

- id: pip-cache
  uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-py313-pip-${{ hashFiles('requirements-dev.txt') }}

The cache path contains downloaded package material, not the authoritative dependency declaration. The next step still runs the installer from requirements-dev.txt and checks the resulting environment.

7. A GitHub Actions job is a check; merge policy is another layer

GitHub Actions jobs create Checks. Branch protection or rulesets can require selected check names before merge. Required status checks must be satisfied on the latest relevant commit, and the required name should be intentionally stable. If a workflow uses path filters that skip the entire required workflow, the pull request can wait forever for a check that was never created.

A useful pattern is to keep descriptive internal jobs—Dependencies, Lint, Build, Tests + coverage—and expose one stable final job such as CI required. That final job runs with if: always(), inspects all upstream conclusions, and fails if any required claim failed or was unexpectedly skipped.

8. Determinism has several inputs, not one switch

  • Source: validate the event-selected exact SHA and do not silently fetch a newer branch tip midway.
  • Workflow/actions: preserve workflow revision and pin external actions to full commit SHAs.
  • Toolchain: request Python 3.13 explicitly instead of relying on whatever python the image currently exposes.
  • Dependencies: pin or lock package inputs; for stronger guarantees use ecosystem lock/hash mechanisms.
  • Environment: record runner OS/image metadata because hosted images evolve.
  • Evidence: retain run ID, attempt, reports and artifact identity so a rerun cannot erase the first observation.

Security invariant: CI that executes pull-request code is executing potentially untrusted code. Keep permissions narrow, do not inject repository secrets into ordinary fork checks, and do not move untrusted tests to trusted self-hosted runners just for speed.

9. Common wrong models

Wrong model Correction
“If the cache hit, dependencies are correct.” A cache hit only says matching cached data was restored. The lock/input and installer verification establish correctness.
“Coverage 92% means tests are good.” Coverage measures execution, not assertion quality, boundary quality or defect detection.
“The workflow is green, so the artifact is correct.” Tie build output to exact SHA/digest and preserve stage evidence; green is only a conclusion under that workflow revision.
“continue-on-error keeps CI useful.” It can hide failure if no explicit final gate reasserts the failed outcome.
“Branch protection understands my workflow graph.” Policy requires check names/results, not your design intent. Keep required names stable and observable.

10. What Chapter 24 adds to the operating model

Earlier chapters established revisions, runners, caching, artifacts, permissions and provenance. Chapter 24 composes those primitives into a CI decision system: exact source enters; independent, inspectable claims are produced; a stable aggregate check turns those claims into merge policy.

11. Lesson summary

Deterministic CI is not one command and not one green icon. It is a revision-bound chain whose dependency, lint, build, test and coverage claims remain distinguishable, whose speed optimizations cannot alter correctness, and whose final required check can be traced back to preserved evidence.

Next lesson

Continuous Integration Patterns for Build, Test, Lint, and Coverage: Guided Hands-On Workflow

Continue with the next lesson to build on the current concepts, evidence, security boundaries, and operational practices.

Knowledge check

Why is cache-hit: true insufficient proof of dependency correctness?

For a pull request, why might you record both github.sha and the PR head SHA?

What is the value of one stable aggregate job when branch protection is used?

Does an uploaded coverage artifact make a failed test job successful?

A required workflow is skipped by a path filter and no check appears. What layer is failing?

Official references and version notes

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.