Chapter 11Lesson 05~245 minutes

Checkpoint Lab — Job Dependencies, needs, Concurrency, Cancellation, and Deployment Serialization

The checkpoint requires two kinds of proof: live GitHub Actions evidence for job graph and concurrency behavior, and a deterministic local shared-target simulation that makes the final fake deployment state independently inspectable. No cloud, secret, package, release or production system is required.

CheckpointTwo policiesShared targetEvidence packetRollback

Learning objectives

  • Predict every job edge and concurrency outcome before starting overlapping runs.
  • Execute a queue-preserving serialized fake deployment and a separate cancel-old safe-work policy.
  • Use a local deterministic simulator to prove final shared target state under overlapping serialized runs.
  • Preserve a complete evidence packet with run IDs/attempts/SHAs, direct dependency results, group values, timestamps and target state.
  • Document cleanup, limitations and the production controls that cancellation/concurrency cannot replace.

1. Scenario and invariants

A sample service has two validation jobs and one fake deployment. The staging target must accept only one deployment at a time, and every accepted staging request must execute. Separately, a long-running analysis job is disposable: a newer analysis run should cancel an older one.

State the invariants before writing YAML:

  1. Validation jobs may run in parallel.
  2. Deployment is eligible only after both validation jobs succeed.
  3. Staging deployments serialize and queue; they do not cancel active deployments.
  4. Analysis runs may cancel stale analysis work.
  5. Cancellation never counts as rollback of an already committed target state.

2. Preflight and assumptions

  • Disposable GitHub repository under your control.
  • GitHub Actions enabled; standard hosted runner ubuntu-24.04.
  • No secrets, environments, cloud accounts, packages or releases.
  • gh CLI optional; browser UI is sufficient.
  • Python 3 standard library for the local shared-target simulation.
  • Version-sensitive concurrency behavior rechecked on 2026-09-09.
git status --short
git rev-parse HEAD
git branch --show-current
python --version
gh --version 2>/dev/null || true

3. Exact checkpoint workflow

Save as .github/workflows/ch11-checkpoint.yml.

name: ch11-checkpoint
on:
  workflow_dispatch:
    inputs:
      policy:
        description: Which concurrency policy to exercise?
        required: true
        type: choice
        options: [queue-deploy, cancel-analysis]
      target:
        required: true
        type: choice
        options: [staging, qa]
      hold_seconds:
        required: true
        type: number
        default: 35

permissions: {}

jobs:
  lint:
    runs-on: ubuntu-24.04
    steps:
      - run: |
          echo "lint run=$GITHUB_RUN_ID sha=$GITHUB_SHA"
          sleep 2

  test:
    runs-on: ubuntu-24.04
    steps:
      - run: |
          echo "test run=$GITHUB_RUN_ID sha=$GITHUB_SHA"
          sleep 4

  gate:
    if: ${{ always() }}
    needs: [lint, test]
    runs-on: ubuntu-24.04
    outputs:
      ready: ${{ steps.gate.outputs.ready }}
    steps:
      - id: gate
        shell: bash
        run: |
          echo "lint=${{ needs.lint.result }} test=${{ needs.test.result }}"
          if [[ '${{ needs.lint.result }}' == success && '${{ needs.test.result }}' == success ]]; then
            echo 'ready=true' >> "$GITHUB_OUTPUT"
          else
            echo 'ready=false' >> "$GITHUB_OUTPUT"
          fi

  queue_deploy:
    if: ${{ inputs.policy == 'queue-deploy' && needs.gate.outputs.ready == 'true' }}
    needs: [gate]
    runs-on: ubuntu-24.04
    concurrency:
      group: ch11-checkpoint-deploy-${{ inputs.target }}
      queue: max
    steps:
      - shell: bash
        env:
          HOLD: ${{ inputs.hold_seconds }}
          TARGET: ${{ inputs.target }}
        run: |
          echo "LOCK_ENTER run=$GITHUB_RUN_ID target=$TARGET time=$(date -u +%FT%TZ)"
          sleep "$HOLD"
          echo "TARGET_COMMIT run=$GITHUB_RUN_ID sha=$GITHUB_SHA target=$TARGET"
          echo "LOCK_EXIT run=$GITHUB_RUN_ID target=$TARGET time=$(date -u +%FT%TZ)"

  cancel_analysis:
    if: ${{ inputs.policy == 'cancel-analysis' && needs.gate.outputs.ready == 'true' }}
    needs: [gate]
    runs-on: ubuntu-24.04
    concurrency:
      group: ch11-checkpoint-analysis-${{ inputs.target }}
      cancel-in-progress: true
    steps:
      - shell: bash
        env:
          HOLD: ${{ inputs.hold_seconds }}
        run: |
          echo "ANALYSIS_START run=$GITHUB_RUN_ID time=$(date -u +%FT%TZ)"
          sleep "$HOLD"
          echo "ANALYSIS_END run=$GITHUB_RUN_ID time=$(date -u +%FT%TZ)"

4. Prediction sheet

Before dispatching, create predictions.md with at least these entries:

  • lint/test overlap for each run.
  • gate waits for both and reports direct results.
  • queue-deploy A enters staging lock; queue-deploy B waits; neither is canceled.
  • cancel-analysis C begins; D with same target cancels C and eventually runs.
  • No GitHub job writes a real external target.
  • The local simulator’s final target will equal the last serialized simulated commit.

5. Execute the queue-preserving policy

  1. Dispatch run A: policy=queue-deploy, target=staging, hold_seconds=45.
  2. Wait until A’s queue_deploy enters the lock.
  3. Dispatch run B with the same target and hold_seconds=10.
  4. Record both run IDs, attempts and SHAs.
  5. Verify B’s deploy does not enter before A exits.

Then dispatch a qa run while staging is active; it should use a different group and need not wait for staging.

6. Execute the cancel-old policy

  1. Dispatch run C: policy=cancel-analysis, target=staging, hold_seconds=45.
  2. After ANALYSIS_START, dispatch run D with the same policy/target.
  3. Preserve C’s run ID and canceled conclusion.
  4. Verify D reaches completion.
  5. State why this is safe: the analysis section has no external side effect.

7. Mandatory shared-target simulator

GitHub-hosted jobs do not share a local filesystem across runs. To verify a concrete final target without introducing cloud credentials or repository writes, run this faithful local simulator. It models two accepted deployment runs waiting on one lock and writing a shared target.json.

from pathlib import Path
from threading import Thread, Lock
from time import sleep, time
import json

lock = Lock()
evidence = Path("ch11-sim-evidence.jsonl")
target = Path("target.json")
for p in (evidence, target):
    if p.exists(): p.unlink()

def emit(run, state, **data):
    row = {"t": round(time(), 3), "run": run, "state": state, **data}
    with evidence.open("a", encoding="utf-8") as f:
        f.write(json.dumps(row) + "\n")
    print(row)

def pipeline(run, sha, validation_delay, deploy_delay):
    emit(run, "lint_start")
    sleep(validation_delay / 2)
    emit(run, "lint_success")
    emit(run, "test_start")
    sleep(validation_delay / 2)
    emit(run, "test_success")
    emit(run, "gate_ready", lint="success", test="success")
    emit(run, "deploy_wait", group="staging")
    with lock:
        emit(run, "deploy_enter", group="staging")
        sleep(deploy_delay)
        target.write_text(json.dumps({"target": "staging", "run": run, "sha": sha}), encoding="utf-8")
        emit(run, "target_commit", sha=sha)
        emit(run, "deploy_exit", group="staging")

A = Thread(target=pipeline, args=("A", "sha-A", 0.6, 1.5))
B = Thread(target=pipeline, args=("B", "sha-B", 0.6, 0.2))
A.start(); sleep(0.4); B.start()
A.join(); B.join()
print("FINAL_TARGET", target.read_text(encoding="utf-8"))
python ch11_sim.py
cat ch11-sim-evidence.jsonl
cat target.json

Expected evidence: both runs complete validation; B reaches deploy_wait while A owns the lock; A commits and exits; B enters and commits; final target is run B / sha-B. This is a faithful mutual-exclusion and shared-target simulation, not a claim about GitHub’s external deployment API.

8. Cancellation thought experiment against target state

Take the simulator timeline and imagine B cancels A after A writes target.json. Stopping A cannot erase that write. The only valid rollback is another explicit target operation. Record this as a limitation and production requirement.

9. Evidence packet

Create a folder containing:

  • workflow.yml — exact checkpoint workflow revision.
  • predictions.md — predictions written before dispatch.
  • runs.md — A/B/C/D run IDs, attempts, SHAs, statuses and conclusions.
  • job-graph.md — lint/test → gate → policy-specific job.
  • concurrency.md — resolved group keys, queue/cancel policy and observed wait/cancel timestamps.
  • queue-logs.txt — lock enter/exit/target-commit lines.
  • cancel-logs.txt — C cancellation and D completion evidence.
  • ch11-sim-evidence.jsonl and target.json — shared-target proof.
  • assumptions-limitations.md — no environment approval, cloud provider, package, artifact or real deployment was exercised.

10. Verification checklist

  • Every run has exact ID, attempt and SHA.
  • The DAG is drawn from needs, not YAML position.
  • Both direct dependency results are preserved.
  • Staging queue runs never overlap their critical sections.
  • QA can overlap staging because the group differs.
  • Cancel-analysis old run is canceled and newer run completes.
  • No privileged always() job exists.
  • Shared-target simulator ends with the predicted final target.
  • No real credential or production target is used.

11. Cleanup and rollback

Delete the disposable workflows/repository after preserving evidence. Remove target.json and simulator logs from the temporary local lab directory. No GitHub environment, runner, secret, package, release or cloud resource was created, so there is no hidden external cleanup.

12. What Chapter 11 adds to the operating model

Chapter 11 adds a job-graph and critical-section contract: dependencies are explicit, direct results are observable, concurrency keys encode the smallest real exclusion domain, queue/cancel policies are chosen from side-effect semantics, cancellation is never confused with rollback, and evidence correlates GitHub run state with the external target.

Chapter 12 expands the parallel side of this model. Instead of manually declaring a few peer jobs, you will use matrix strategies and dynamic matrices to generate controlled parallel work while preserving failure, cost and evidence semantics.

Knowledge check

In the checkpoint, which jobs are allowed to run in parallel?

Why are queue-deploy and cancel-analysis separate jobs/groups?

What proves the final fake target state?

If run A is canceled after TARGET_COMMIT, what must you assume?

What is the bridge to Chapter 12?

Next chapter concept

Matrix Strategies, Dynamic Matrices, Fail-Fast, and Parallel Test Design

Next, generate parallel work deliberately with static/dynamic matrices, include/exclude rules, fail-fast behavior and bounded evidence.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked against current GitHub-maintained documentation on 2026-09-09. Current documentation states that needs contains only direct dependencies and exposes result as success, failure, cancelled or skipped. A failed or skipped dependency normally skips downstream jobs unless an explicit job condition permits continuation. Concurrency groups are repository-wide and case-insensitive. By default at most one item may be running and one pending in a group; a newer pending item replaces an older pending item. Current GitHub Actions also supports queue: max to allow up to 100 pending items, processed FIFO by time waiting on the group; this mode cannot be combined with cancel-in-progress: true. Cancellation re-evaluates job/step conditions, so always() can keep work running during cancellation and must not be used casually for privileged or irreversible operations. Mandatory labs use ubuntu-24.04, permissions: {}, no Marketplace action and no real credential. The checkpoint deliberately separates GitHub concurrency evidence from a local shared-target simulator so the mandatory path remains credential-free and disposable.

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.