Chapter 10Lesson 05~205 minutes

Checkpoint Lab — Artifacts, Reports, Retention, Dependencies, needs:artifacts, and Cross-Job Data Flow

The checkpoint implements “build once, verify everywhere”: one producer emits a synthetic release candidate plus SHA-256 evidence, later jobs consume exactly that artifact, one dependency is deliberately broken, and recovery restores the intended bytes without rebuilding the release candidate.

Checkpoint labBuild onceDigestEvidence packetVerification

Learning objectives

  • Predict which bytes and metadata each job should receive before executing the checkpoint.
  • Build once and verify the same artifact digest across multiple jobs and evidence records.
  • Inject a transfer/dependency failure, preserve the failed consumer evidence, and repair only the causal declaration.
  • Demonstrate that recovery does not rebuild or silently replace the intended release candidate.
  • Produce an evidence packet and bridge retained outputs naturally into Chapter 11 cache correctness.

1. Checkpoint scenario and success criteria

You are preparing a synthetic release candidate. One job may build it. Two later jobs must independently consume the same bytes: verify_candidate checks integrity and provenance, while package_manifest records release metadata. You will then break one transfer declaration, preserve the failed evidence, and repair it without rerunning the build step as a substitute.

Success: both consumers verify the producer digest; the deliberate failure is explained by transfer metadata rather than guesswork; the restored pipeline uses the same source commit and build contract; and the evidence packet contains no secret material.

2. Preflight and assumptions

  • Disposable project/branch only: glci/ch10-checkpoint.
  • Current GitLab behavior checked 2026-09-11.
  • Any compatible runner is sufficient; container image uses alpine:3.22.1.
  • No real credentials, package registry, release, environment, cloud, or Kubernetes target.
  • Before the run, record the exact commit SHA containing the checkpoint configuration.
  • If a runner is unavailable, perform CI Lint plus a local directory-copy/digest simulation and mark runtime evidence as not observed.
Prediction 1: verify_candidate should receive producer files as soon as build_candidate succeeds. Prediction 2: package_manifest should receive the exact same candidate digest but should not rebuild it.

3. Exact checkpoint configuration

The producer writes a candidate, digest, and producer identity. A small JUnit job supplies independent typed report evidence. The two consumers use explicit needs artifact transfer.

stages: [build, verify, package]

default:
  image: alpine:3.22.1

build_candidate:
  stage: build
  script:
    - mkdir -p dist evidence
    - printf 'candidate-sha=%s\n' "$CI_COMMIT_SHA" > dist/candidate.txt
    - sha256sum dist/candidate.txt > evidence/candidate.sha256
    - printf 'pipeline=%s\njob=%s\nsha=%s\n' "$CI_PIPELINE_ID" "$CI_JOB_ID" "$CI_COMMIT_SHA" > evidence/producer.txt
  artifacts:
    name: "candidate-$CI_PIPELINE_ID-$CI_JOB_ID"
    paths:
      - dist/candidate.txt
      - evidence/candidate.sha256
      - evidence/producer.txt
    expire_in: 7 days

unit_report:
  stage: verify
  needs: []
  script:
    - mkdir -p test-results
    - printf '%s\n' '<testsuite name="checkpoint" tests="1" failures="0"><testcase classname="artifact" name="contract"/></testsuite>' > test-results/junit.xml
  artifacts:
    when: always
    reports:
      junit: test-results/junit.xml
    paths:
      - test-results/junit.xml
    expire_in: 2 days

verify_candidate:
  stage: verify
  needs:
    - job: build_candidate
      artifacts: true
  script:
    - cat evidence/producer.txt
    - sha256sum -c evidence/candidate.sha256
    - grep -F "candidate-sha=$CI_COMMIT_SHA" dist/candidate.txt

package_manifest:
  stage: package
  needs:
    - job: build_candidate
      artifacts: true
    - job: verify_candidate
      artifacts: false
  script:
    - sha256sum -c evidence/candidate.sha256
    - mkdir -p package
    - cp evidence/candidate.sha256 package/release.sha256
    - printf 'source_sha=%s\nproducer_job=%s\nconsumer_job=%s\n' "$CI_COMMIT_SHA" "$(sed -n 's/^job=//p' evidence/producer.txt)" "$CI_JOB_ID" > package/manifest.txt
  artifacts:
    paths:
      - package/release.sha256
      - package/manifest.txt
    expire_in: 7 days

4. Predict the graph and dataflow before execution

Write down the expected state:

  • build_candidate is the only job that creates dist/candidate.txt.
  • unit_report starts without waiting for build because it has needs: [] and does not consume candidate data.
  • verify_candidate waits for build and downloads its artifacts.
  • package_manifest waits for the build artifact and the verification gate. It downloads artifacts from the build, but not from verify_candidate.
  • The candidate digest in verifier and packager must match the producer manifest exactly.

5. Run the verified baseline and capture evidence

Push the exact checkpoint commit. Record:

  1. pipeline ID/source/ref/SHA,
  2. build, report, verifier, and packager job IDs/statuses,
  3. build artifact name and declared expiry,
  4. JUnit report presence in GitLab’s test view,
  5. the producer candidate.sha256 content,
  6. successful digest checks in both consumers,
  7. final package/manifest.txt artifact identity.
Producer

build job ID + SHA

Candidate

path + SHA-256 digest

Report

unit_report job ID + JUnit type

Verifier

job ID + digest PASS

Packager

job ID + same digest PASS

Retention

7-day candidate/manifest declaration + keep-latest assumption

6. Inject one transfer failure without changing the build script

Change only verify_candidate:

verify_candidate:
  stage: verify
  needs:
    - job: build_candidate
      artifacts: false
  script:
    - sha256sum -c evidence/candidate.sha256

Commit and run. Preserve the failed pipeline ID, failed verifier job ID, and first missing-file message. The producer can still upload its artifact successfully; the failure is in the consumer transfer contract.

Do not add a rebuild command. The purpose of the exercise is to recover access to the intended producer output, not synthesize a replacement.

7. Diagnose by layer

Prove each statement in order:

  1. The pipeline was created for the expected source/ref/SHA.
  2. build_candidate exists and succeeded.
  3. The build job has the expected artifact and provenance manifest.
  4. verify_candidate has a needs edge to the correct producer.
  5. The edge explicitly disables artifact transfer.
  6. The failed trace is therefore explained without blaming the runner, shell, or artifact expiry.

8. Repair only the causal declaration

Restore artifacts: true. Run the corrected pipeline at its new commit and verify the new producer/consumer tuple. In a real release incident where the original intended artifact must be preserved, recovery would target the original retained artifact by exact producer identity rather than rebuild. In this teaching branch, every commit creates a new pipeline identity, so record that fact explicitly instead of pretending the repaired run is the same artifact record.

The lesson’s invariant is unchanged: never hide a missing retained release artifact by rebuilding during promotion/deployment.

9. Required evidence packet

Field Record
Pipeline ID, source, ref, exact SHA for successful baseline, broken run, and repaired run
Compiled config Relevant artifact/needs declarations
Producer Build job ID/name, runner context, artifact name
Artifact Candidate path, SHA-256 digest, expiry/access assumptions
Report JUnit producer job ID/type and GitLab ingestion observation
Consumers Verifier/packager job IDs and digest results
Failure Broken verifier job ID + first missing-file evidence + artifacts:false cause
Limitations No real release/registry/deployment; free disposable lab only
Do not include: CI_JOB_TOKEN values, PATs, runner auth tokens, private keys, environment dumps, or any transformed secret value.

10. Verification checklist

  • Exactly one job in each run creates the candidate bytes.
  • Producer identity includes pipeline/job/SHA.
  • Consumers verify SHA-256 before using the file.
  • Typed JUnit evidence remains separate from the candidate artifact.
  • The deliberate failure is explained by the transfer flag, not by guesswork.
  • No cache is used as the authoritative artifact store.
  • No consumer rebuilds the candidate.
  • Retention is long enough for the checkpoint but intentionally bounded.

11. No-runner local simulation path

If no runner is available, simulate the data contract locally:

rm -rf .ch10-sim && mkdir -p .ch10-sim/producer/dist .ch10-sim/producer/evidence .ch10-sim/consumer
printf 'candidate-sha=SIMULATED_SHA\n' > .ch10-sim/producer/dist/candidate.txt
sha256sum .ch10-sim/producer/dist/candidate.txt > .ch10-sim/producer/evidence/candidate.sha256
cp -R .ch10-sim/producer/dist .ch10-sim/producer/evidence .ch10-sim/consumer/
(cd .ch10-sim/consumer && sha256sum -c evidence/candidate.sha256)

This faithfully demonstrates producer selection, transfer, and digest verification, but it does not prove GitLab artifact retention, UI/API access, report ingestion, or Runner transfer behavior. Mark those fields “not observed.”

12. Cleanup/rollback

  1. Keep the evidence packet only as long as needed for the course exercise.
  2. Delete .ch10-sim if created locally.
  3. Delete only the exact disposable branch/project after verifying its identity.
  4. Do not bulk-delete project artifacts or pipelines as part of this lab.

13. What Chapter 10 adds to the production model

You can now name an artifact producer, retain only intentional outputs, distinguish generic files from typed reports, choose stage-based or DAG transfer deliberately, reason about expiry/access, and prove that consumers received the same bytes by digest. That closes the file-evidence gap in the pipeline state model.

Chapter 11 tackles a neighboring but fundamentally different state system: caches. Caches trade certainty for speed and must be keyed so reuse cannot silently violate correctness. The artifact contract you just built becomes the reference point for understanding why a cache is never the release source of truth.

Next lesson

Next: Caches, Cache Keys, Fallback Keys, Distributed Cache, Dependency Reuse, and Cache Correctness: Concepts, Architecture, and Mental Model

Continue with the next lesson in the course sequence and carry forward the evidence-first GitLab CI/CD operating model.

Knowledge check

What makes “build once” auditable?

Why did package_manifest use artifacts:false for verify_candidate?

What evidence distinguishes a transfer failure from a build failure?

Why must the repaired run be labeled as a new artifact identity?

What is the Chapter 11 bridge?

Version and compatibility note

GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.

Official references and version notes

Documentation verification date: 2026-09-11. Artifact/report retention, access, API behavior, and cross-job transfer semantics are version-sensitive; verify the deployed GitLab version for Self-Managed/Dedicated installations.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.