Checkpoint Lab — Matrix Strategies, Dynamic Matrices, Fail-Fast, and Parallel Test Design
The checkpoint combines the chapter into one auditable compatibility decision. A tiny Python app is tested across supported and experimental cells, one supported cell and one experimental cell are deliberately broken, every cell publishes its own evidence, and an aggregator produces a support verdict from those artifacts.
Learning objectives
- Predict all four matrix cells, their tolerance policy and the expected final support verdict before dispatch.
- Run a deterministic tiny app across supported and experimental compatibility cells with pinned actions/toolchains.
- Preserve a unique evidence artifact for every cell, including failed cells.
- Aggregate artifacts into a decision that blocks on mandatory failures but records experimental failures separately.
- Produce a complete evidence packet and clean up the disposable repository/resources without erasing first-failure evidence.
1. Checkpoint scenario and invariant
You are qualifying a tiny Python utility. Three cells are part of
the support contract; one cell is experimental. The lab deliberately
fails supported-b and experimental-future.
Your invariant is:
every mandatory cell must produce a successful observed test
result; an experimental failure is recorded but does not itself
block support.
Before running, predict: four generated jobs; at most two matrix
cells active simultaneously; one mandatory failure; one tolerated
experimental failure; four per-cell artifacts; and an aggregate
decision of BLOCK.
2. Preflight and current assumptions
| Item | Checkpoint assumption | Verification |
|---|---|---|
| GitHub platform | GitHub.com Actions | Actions enabled in disposable repo |
| Runners | ubuntu-24.04, windows-2025 |
record job runner/image metadata |
| Checkout |
v7.0.1 →
3d3c42e5aac5ba805825da76410c181273ba90b1
|
full SHA in workflow |
| Setup Python |
v7.0.0 →
5fda3b95a4ea91299a34e894583c3862153e4b97
|
full SHA + resolved Python version |
| Upload artifact |
v7.0.1 →
043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
unique artifact per cell |
| Download artifact |
v8.0.1 →
3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
pattern download in aggregator |
| Credentials | none; permissions: {} |
no secret/token use in lab |
| External systems | none | all side effects limited to run/artifact state |
3. Create the tiny app
In the disposable repository, create these two files. They are intentionally dependency-free so a test failure is about the controlled matrix policy, not a package registry.
def normalize(value: str) -> str:
return value.strip().lower()
from app import normalize
assert normalize(" GHA ") == "gha"
print("base test passed")
4. Exact checkpoint workflow
Create .github/workflows/chapter12-checkpoint.yml with
the following workflow. The test step records its own outcome before
a final gate converts the controlled failure into the generated
job’s failure. That lets the evidence-upload step run even for
failed cells.
name: chapter12-compatibility-checkpoint
on:
workflow_dispatch:
permissions: {}
jobs:
test:
name: ${{ matrix.mode }} | ${{ matrix.os }} | py-${{ matrix.python }}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
max-parallel: 2
matrix:
include:
- os: ubuntu-24.04
python: '3.12'
mode: supported-a
experimental: false
inject_failure: false
- os: ubuntu-24.04
python: '3.13'
mode: supported-b
experimental: false
inject_failure: true
- os: windows-2025
python: '3.13'
mode: supported-c
experimental: false
inject_failure: false
- os: ubuntu-24.04
python: '3.13'
mode: experimental-future
experimental: true
inject_failure: true
steps:
- name: Checkout exact workflow source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up pinned runtime action
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python }}
- name: Run base test and controlled failure
id: test
continue-on-error: true
shell: bash
run: |
python test_app.py
if [ '${{ matrix.inject_failure }}' = 'true' ]; then
echo 'controlled matrix failure for checkpoint evidence'
exit 1
fi
- name: Write per-cell evidence
if: ${{ always() }}
shell: bash
env:
CELL_OS: ${{ matrix.os }}
CELL_PYTHON: ${{ matrix.python }}
CELL_MODE: ${{ matrix.mode }}
CELL_EXPERIMENTAL: ${{ matrix.experimental }}
TEST_OUTCOME: ${{ steps.test.outcome }}
run: |
mkdir -p evidence
python - <<'PY_EVIDENCE'
import json, os, platform
data = {
"run_id": os.environ["GITHUB_RUN_ID"],
"run_attempt": os.environ["GITHUB_RUN_ATTEMPT"],
"sha": os.environ["GITHUB_SHA"],
"os": os.environ["CELL_OS"],
"python_requested": os.environ["CELL_PYTHON"],
"python_resolved": platform.python_version(),
"mode": os.environ["CELL_MODE"],
"experimental": os.environ["CELL_EXPERIMENTAL"] == "true",
"test_outcome": os.environ["TEST_OUTCOME"],
}
name = data["mode"] + ".json"
with open("evidence/" + name, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, sort_keys=True)
PY_EVIDENCE
- name: Upload unique cell evidence
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cell-${{ strategy.job-index }}-${{ matrix.mode }}
path: evidence/*.json
if-no-files-found: error
retention-days: 7
- name: Convert recorded test failure into cell failure
if: ${{ steps.test.outcome == 'failure' }}
shell: bash
run: exit 1
aggregate:
name: Aggregate compatibility evidence
if: ${{ always() }}
needs: test
runs-on: ubuntu-24.04
steps:
- name: Download all per-cell evidence
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: cell-*
path: collected
merge-multiple: true
- name: Produce support decision
shell: bash
run: |
python - <<'PY_AGG'
import glob, json, sys
rows = [json.load(open(p, encoding="utf-8")) for p in glob.glob("collected/*.json")]
rows.sort(key=lambda r: r["mode"])
mandatory_failures = [r for r in rows if not r["experimental"] and r["test_outcome"] != "success"]
experimental_failures = [r for r in rows if r["experimental"] and r["test_outcome"] != "success"]
with open("support-decision.txt", "w", encoding="utf-8") as out:
out.write(f"cells_observed={len(rows)}\n")
out.write(f"mandatory_failures={len(mandatory_failures)}\n")
out.write(f"experimental_failures={len(experimental_failures)}\n")
out.write("decision=" + ("BLOCK" if mandatory_failures else "SUPPORTED") + "\n")
print(open("support-decision.txt", encoding="utf-8").read())
# Deliberately fail because supported-b was designed to fail.
sys.exit(1 if mandatory_failures else 0)
PY_AGG
always() is acceptable here
The always() steps only write/upload non-secret
diagnostic evidence. They do not deploy, mutate repository state
or use privileged credentials. This is the narrow
evidence-preservation use established in Chapter 11.
5. Predictions before dispatch
| Prediction | Expected state | Independent proof |
|---|---|---|
| Cell count | 4 | generated job list / strategy.job-total |
| Parallelism | ≤2 matrix cells active | job start/end timestamps |
| supported-b | test outcome failure; mandatory | cell JSON + failed job |
| experimental-future | test outcome failure; tolerated by job policy | cell JSON + experimental=true |
| Artifacts | 4 unique cell-* artifacts |
artifact list/IDs/digests |
| Aggregate decision | BLOCK |
downloaded JSON + support-decision output |
6. Execute and preserve the first run
- Commit the fixture and workflow; record the exact commit SHA.
- Dispatch the workflow once. Do not rerun immediately.
- Record run ID and run attempt.
- Open the generated job graph and confirm all four cell names.
- Capture the first failing supported cell log before changing anything.
- Record which cells completed and which were tolerated.
Because fail-fast:false, the expected outcome is full
evidence rather than sibling cancellation.
7. Verification checklist
-
The checkout step ran the exact
github.shaand did not persist credentials. - Each cell records requested and resolved Python version.
- Four unique artifacts exist; a missing artifact is a failed evidence invariant.
-
supported-b.jsonsaysexperimental:falseandtest_outcome:failure. -
experimental-future.jsonsaysexperimental:trueandtest_outcome:failure. -
The aggregator downloads all cell artifacts and prints
decision=BLOCK. - The workflow’s final red state is explained by a mandatory compatibility failure, not by artifact transport or runner setup.
8. Repair without hiding the original failure
Preserve the original run. Then change only the controlled supported
failure: set inject_failure:false for
supported-b. Leave the experimental failure in place.
Commit the new workflow revision and dispatch again. Predict that
mandatory failures become zero, experimental failures remain one,
and the aggregate decision changes to SUPPORTED.
This second run demonstrates why experimental tolerance and
mandatory support must be separate dimensions. Do not “repair” the
first run by making every cell continue-on-error:true.
9. Required evidence packet
| Evidence | Required content |
|---|---|
| Run identity | run ID, attempt, event, exact source SHA, workflow path |
| Matrix policy | all include entries, fail-fast=false, max-parallel=2, experimental flags |
| Runner/toolchain | runner labels/image metadata, setup-python SHA, resolved versions |
| Per-cell results | job conclusion plus JSON artifact from every generated cell |
| Artifact identity | artifact names and, where available, IDs/digests |
| Aggregation | download action SHA, support-decision output, mandatory vs experimental counts |
| Failure history | original supported-b first-failure log and original BLOCK verdict |
| Repair proof | new SHA/run ID and SUPPORTED verdict while experimental failure remains visible |
| Limitations | GitHub.com artifact-action assumptions; no cloud/deployment/environment state exercised |
10. Cleanup and rollback
After the evidence packet is saved, delete the disposable repository if desired. Do not delete the original run/artifacts before you have copied the evidence you intend to retain. No external infrastructure, credential, package or deployment needs rollback because the mandatory lab never creates one.
11. What Chapter 12 adds to the operating model
You can now turn one job definition into a controlled compatibility experiment: the matrix source is explicit, each generated cell has a defined trust/runtime identity, failure tolerance is policy rather than wishful thinking, cost is bounded, and downstream decisions are based on retained cell evidence. Chapter 13 deepens the artifact side of this model: retention, cross-job transport and workflow-result management.
Knowledge check
Why does the checkpoint upload evidence before the final gate step exits 1?
So a failing test still produces a diagnostic artifact. The recorded step outcome is preserved before the job is intentionally converted into a failure.
Why are artifact names unique per matrix cell?
Current artifact actions use immutable artifacts; unique names avoid cross-cell collisions and preserve one evidence object per compatibility cell.
The experimental cell fails. Should the aggregate decision automatically be BLOCK?
No. The declared policy tolerates that advisory cell. The aggregate decision blocks only on observed mandatory failures while still recording the experimental failure.
If one mandatory cell artifact is missing, may the aggregator classify it as pass?
No. Missing evidence is not success. A mandatory support claim requires an observed successful result and its evidence.
After fixing supported-b, why keep the experimental failure?
It proves the aggregation policy distinguishes mandatory support from advisory experimentation instead of making the entire matrix green by suppressing failures.
Official references and version notes
- GitHub Docs — Running variations of jobs in a workflow — matrix expansion, contexts, include/exclude, dynamic outputs, failure handling and max-parallel.
- GitHub Docs — workflow syntax: strategy.matrix — current matrix limit and strategy semantics.
- GitHub Docs — expressions: fromJSON — converting job-output JSON into arrays/objects used by a later matrix.
- GitHub Docs — strategy context — fail-fast, job-index, job-total and max-parallel for the current generated job.
- GitHub Docs — matrix strategy with reusable workflows — matrix-driven reusable workflow calls and output caveats.
- actions/upload-artifact v7.0.1 and actions/download-artifact v8.0.1 — action releases pinned by full commit SHA in the checkpoint.
Version-sensitive behavior was rechecked against current
GitHub-maintained documentation on 2026-09-09. A
matrix can generate at most
256 jobs per workflow run.
strategy.fail-fast defaults to true;
continue-on-error is evaluated per generated job; and
max-parallel limits simultaneous matrix jobs but does
not redefine the matrix or create a repository-wide concurrency
lock. Current mandatory examples target GitHub.com with versioned
runner labels ubuntu-24.04 and
windows-2025. Official actions are pinned to full
immutable commit SHAs: checkout v7.0.1, setup-python v7.0.0,
upload-artifact v7.0.1 and download-artifact v8.0.1. GitHub
Enterprise Server users must verify artifact-action
backend/major-version compatibility for their appliance before
copying these examples. The checkpoint intentionally introduces
only the artifact behavior needed for matrix evidence; Chapter 13
owns the comprehensive artifact lifecycle.
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.