Checkpoint Lab — Monorepos, Path Filters, Changed-File Detection, and Selective Pipelines
Prove selective CI for three change sets, deliberately break shared-dependency fan-out, and use a full-suite contract check to catch the selector defect before repair.
Learning objectives
- Implement a checkpoint that predicts selection for API, Web and shared-library changes.
- Prove a deliberately broken dependency selector under-selects a shared change.
- Use an independent full selector-contract suite to fail on that defect and preserve the first failure.
- Repair the selector and verify both selective and full modes with one stable aggregate check.
- Produce an evidence packet that distinguishes event/revision, selector, matrix and governance state.
1. Checkpoint scenario: prove optimization and its safety net
The checkpoint avoids live forks, cloud accounts and production
repositories. Three synthetic change sets model API-only, Web-only
and shared-library changes. The selector has
correct and intentionally broken variants;
broken forgets that Web depends on libs/shared.
Run selective mode, then an independent
full-contract mode that enumerates authoritative
component expectations. The broken selector must fail the full
contract. Preserve that run and repair the graph rather than
normalizing the failure.
2. Preflight and safety boundary
- Use a disposable repository; same-repository branches are sufficient.
-
Use
ubuntu-24.04, checkout v7.0.13d3c42e5aac5ba805825da76410c181273ba90b1, setup-python v7.0.05fda3b95a4ea91299a34e894583c3862153e4b97, Python 3.13, upload-artifact v7.0.1043fb46d1a93c77aae656e7c1c64a875d1fc6a0a. -
Keep permissions at
{}exceptcontents: readfor checkout. - No secret, OIDC token, release/package permission, environment, cloud target or self-hosted runner is needed.
- If branch protection is not changed in the disposable repo, record the required-check configuration as simulated rather than claiming it was enforced.
3. Selector contract fixture
The authoritative component inventory and expected change-set
mapping are separate from the generated matrix. The important case
is the shared path, which must select both applications. The
broken switch exists only to create controlled
under-selection evidence.
COMPONENTS = {"api", "web"}
def select(paths, broken=False):
chosen=set(); full=False
for path in paths:
if path.startswith("apps/api/"): chosen.add("api")
elif path.startswith("apps/web/"): chosen.add("web")
elif path.startswith("libs/shared/"):
chosen.add("api")
if not broken: chosen.add("web")
elif path.startswith("docs/") or path == "README.md": pass
else: full=True
return sorted(COMPONENTS if full else chosen)
CASES = [
(["apps/api/app.py"], ["api"]),
(["apps/web/app.py"], ["web"]),
(["libs/shared/contract.txt"], ["api", "web"]),
]
4. Predict state changes before running
| Run | Expected selector output | Expected jobs | Expected required result |
|---|---|---|---|
| correct + selective + api | [api] | API only | success |
| correct + selective + web | [web] | Web only | success |
| correct + selective + shared | [api, web] | API + Web | success |
| broken + selective + shared | [api] | API only — intentionally incomplete | may be green; evidence proves under-selection |
| broken + full-contract + shared | contract expected api+web, actual api | full contract | failure; preserve first-failure run |
| correct + full-contract + shared | all fixtures verified | full contract | success after repair |
5. Exact checkpoint workflow
name: Monorepo selector checkpoint
on:
workflow_dispatch:
inputs:
selector_variant:
type: choice
options: [correct, broken]
default: correct
suite:
type: choice
options: [selective, full-contract]
default: selective
change_set:
type: choice
options: [api, web, shared]
default: shared
permissions: {}
jobs:
select:
name: Selection evidence
runs-on: ubuntu-24.04
permissions:
contents: read
outputs:
components: ${{ steps.select.outputs.components }}
count: ${{ steps.select.outputs.count }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
- id: select
shell: bash
env:
VARIANT: ${{ inputs.selector_variant }}
CHANGE_SET: ${{ inputs.change_set }}
run: |
set -euo pipefail
python .github/scripts/checkpoint_selector.py "$VARIANT" "$CHANGE_SET" | tee selection.json
COMPONENTS="$(python -c 'import json; print(json.dumps(json.load(open("selection.json"))["components"],separators=(",",":")))')"
COUNT="$(python -c 'import json; print(len(json.load(open("selection.json"))["components"]))')"
printf 'components=%s\ncount=%s\n' "$COMPONENTS" "$COUNT" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ always() }}
with:
name: checkpoint-selection-${{ github.run_id }}-${{ github.run_attempt }}
path: selection.json
retention-days: 7
selected-tests:
name: Selected ${{ matrix.component }}
needs: select
if: ${{ inputs.suite == 'selective' && needs.select.outputs.count != '0' }}
runs-on: ubuntu-24.04
permissions:
contents: read
strategy:
fail-fast: false
matrix:
component: ${{ fromJSON(needs.select.outputs.components) }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
- shell: bash
env:
COMPONENT: ${{ matrix.component }}
run: python "apps/$COMPONENT/test_component.py"
full-contract:
name: Full selector contract
if: ${{ inputs.suite == 'full-contract' }}
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
- name: Enumerate authoritative inventory and assert every selector fixture
shell: bash
env:
VARIANT: ${{ inputs.selector_variant }}
run: python .github/scripts/test_selector_contract.py "$VARIANT"
required:
name: Monorepo CI / required
needs: [select, selected-tests, full-contract]
if: ${{ always() }}
runs-on: ubuntu-24.04
permissions: {}
steps:
- name: Map run mode to required outcome
shell: bash
env:
SUITE: ${{ inputs.suite }}
SELECT_RESULT: ${{ needs.select.result }}
SELECTED_RESULT: ${{ needs.selected-tests.result }}
FULL_RESULT: ${{ needs.full-contract.result }}
COUNT: ${{ needs.select.outputs.count }}
run: |
set -euo pipefail
test "$SELECT_RESULT" = success
if [ "$SUITE" = selective ]; then
if [ "$COUNT" = 0 ]; then test "$SELECTED_RESULT" = skipped; else test "$SELECTED_RESULT" = success; fi
test "$FULL_RESULT" = skipped
else
test "$FULL_RESULT" = success
test "$SELECTED_RESULT" = skipped
fi
The full-contract job does not call the incremental selector to decide which cases to test. It knows the authoritative inventory and expected fixtures independently, so the safety net does not share the same omission.
6. Execute and preserve the six-run sequence
-
Run
correct/selective/api; retain selection artifact and aggregate result. - Run
correct/selective/web. -
Run
correct/selective/shared; verify two matrix cells. -
Run
broken/selective/shared; observe only API was selected and do not call this proof of correctness. -
Run
broken/full-contract/shared; the selector contract must fail. Preserve this first-failure run ID/attempt and logs. -
Repair the Web dependency edge, commit, then run
correct/full-contract/shared. -
Rerun
correct/selective/sharedto prove the original change set now creates API + Web cells.
7. Required evidence packet
| Field | What to retain |
|---|---|
| Event/run | workflow_dispatch inputs, source/workflow SHA, run ID/attempt. |
| Runner/tool | ubuntu-24.04, action SHAs, Python version. |
| Selector revision | exact commit containing selector + dependency graph. |
| Change set | api/web/shared synthetic path list or real base/head/merge-base in PR mode. |
| Selection | selection.json, component array, reasons/fallback. |
| Job graph | selected matrix cells or full-contract job, plus skipped jobs. |
| Failure evidence | broken/full-contract first-failure log showing expected api+web versus actual api. |
| Governance |
stable Monorepo CI / required result and
whether actually required or simulated.
|
| Fallback policy | full-contract/manual/scheduled cadence and authoritative inventory source. |
| Limitations | synthetic checkpoint does not exercise a production-scale dependency graph or native 3,000-file boundary. |
8. Map the checkpoint back to real pull requests
The synthetic change_set isolates selector correctness.
In a real PR, replace it with Lesson 2’s chain: event base/head SHA
→ proven merge base → NUL-safe changed paths → ownership/dependency
closure. Keep the same selector contract tests and aggregate check.
9. Why the full suite is a different control
The full-contract lane is not “select all using the selector.” It enumerates authoritative components and fixture expectations directly. In a larger monorepo, the independent control might be a nightly build-system command that knows every workspace/package, a release qualification lane, or a graph-consistency test.
10. Cleanup and rollback
- Keep the intentionally failed run/artifact until evidence is reviewed.
-
Remove the
brokenteaching switch before adapting to production. - Delete the disposable repository/branch only after exporting desired evidence.
- If you temporarily configured branch protection, remove only the exact disposable rule/check after verification.
- No cloud/package/environment cleanup is needed because the lab creates none.
11. Production hardening checklist
- Event revision pair and merge base are explicit and verifiable.
- Changed paths are NUL-safe data; renames/deletions are conservative.
- Ownership/dependency graph is versioned and regression-tested.
- Unknown/global changes fan out broadly.
- Matrix JSON and empty selection are explicit.
- Stable required aggregate exists for every applicable PR.
- Independent full coverage does not depend on incremental selection.
- First-failure selector evidence survives reruns and merge investigations.
12. What Chapter 28 adds — and the bridge to Chapter 29
Chapter 28 adds evidence-backed selective execution to the operating model. A repository can reduce monorepo CI work while still proving base/head identity, complete path handling, dependency closure, selected job results and one stable governance check. Chapter 29 turns these patterns into reusable platform pipelines and golden paths so multiple repositories can consume versioned organization-wide delivery contracts.
13. Checkpoint summary
You have proved both sides of selective CI: the fast path can choose only affected components, and an independent full-contract path can expose a deliberately incomplete dependency selector. That is the difference between an optimization and an unverified skip.
Knowledge check
Why is the broken selective shared run allowed to appear green?
To demonstrate the real risk: an omitted component cannot fail a job that never existed. The independent full-contract lane must expose the defect.
What evidence must be preserved before repairing the selector?
Run ID/attempt, source/workflow SHA, change set, selection output, job graph and first failing full-contract log.
Why must the full-contract lane not ask the incremental selector what to test?
That would share the same blind spot and could validate the selector using its own incomplete output.
After repairing shared fan-out, what is the smallest equivalent proof?
Rerun the shared selective change set and verify both API and Web matrix cells exist and pass.
What does Chapter 29 add next?
Versioned reusable platform/golden-path contracts that package CI, policy, runner and governance patterns for broader reuse.
Official references and version notes
- Workflow syntax: path filters — Current paths/paths-ignore matching, two-dot versus three-dot diff rules, pattern ordering and GitHub.com diff limits.
- Troubleshooting workflows — Current path-filter diff limits and workflow-run troubleshooting.
- Troubleshooting required status checks — Why a path-filtered workflow can leave a required check Pending and how skipped jobs differ.
- Matrix jobs — Dynamic matrix concepts and per-cell jobs.
- Passing information between jobs — Selector job outputs used by downstream matrix and aggregate jobs.
- Events that trigger workflows — Event-specific base/head/ref semantics.
- actions/checkout — Current checkout behavior; v7.0.1 is pinned by full commit SHA in executable examples.
- Git diff documentation — Two-dot/three-dot revision comparison and NUL-safe path output options.
- Git merge-base — Computing the common ancestor for dependency-aware pull-request change detection.
- actions/checkout v7.0.1 — Pinned checkout used by the checkpoint.
- actions/setup-python v7.0.0 — Pinned Python setup used by the checkpoint.
- actions/upload-artifact v7.0.1 — Pinned evidence upload used by the checkpoint.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.