Chapter 36Lesson 05~180 minutes

Checkpoint Lab — Compliance Pipelines, Pipeline Execution Policies, Governance, Audit Evidence, and Enterprise Controls

Complete a governance checkpoint with one enforced control, one time-bounded waiver, effective-pipeline proof, and a policy-versioned evidence packet.

Checkpoint labPolicy evidenceExpiring waiverProduction model

Learning objectives

  • Predict governance state changes before running the checkpoint.
  • Prove one centrally enforced control and one narrow time-bounded exception.
  • Verify expiry and job-name collision behavior independently.
  • Assemble an evidence packet tied to source revision and policy version.
  • Explain the production governance model and bridge it to Chapter 37 troubleshooting.

Checkpoint boundary: the required lab is fully local and free. It uses no GitLab Ultimate capability, administrator change, runner registration, cloud credential, Kubernetes cluster, production repository, package/release, or external deployment. The optional real-GitLab mapping is inspection-first and disposable only.

1. Scenario: enforce one control and govern one exception

Acme Labs wants a mandatory governance proof for acme-labs/ch36-app. You will prove the policy-owned job enters the effective model, issue one two-hour waiver, prove an expired waiver is ignored, preserve a deliberate collision failure, and assemble an evidence packet tied to ch36-policy-v1.0.0.

The local model is deliberately honest about its boundary: it teaches policy origin/scope/evidence/waiver causality, not the whole GitLab YAML compiler or runner engine.

2. Tool, tier, runner, image, and API assumptions

  • Mandatory: Git 2.x, Python 3.11+ standard library, POSIX shell/Git Bash; no network required.
  • Optional PEP path: GitLab Ultimate on GitLab.com, Self-Managed, or Dedicated; verify the deployed release before use.
  • Runner: not used locally. Current documentation requires GitLab Runner 18.1+ to show policy name/variables_override details in applicable job logs.
  • Image/tool: no container image is pulled by the local checkpoint; no cloud/registry/Kubernetes tool is needed.
  • API: examples use REST API v4. CI Lint is available across tiers; audit event APIs are Premium/Ultimate and role-sensitive.
python --version
git --version
LAB="${TMPDIR:-/tmp}/gitlab-ch36-checkpoint"
printf 'planned_lab=%s\n' "$LAB"
case "$LAB" in */gitlab-ch36-checkpoint) : ;; *) exit 2 ;; esac
rm -rf "$LAB"
mkdir -p "$LAB"/{project,policy,governance,tools,evidence}
cd "$LAB"

3. Predict state changes before performing them

Prediction Expected change Independent verification
P1 — enforce Effective model gains one policy-origin pre-job; project jobs remain. Inspect origins, input hashes, policy version in run-enforced.json.
P2 — waive One valid exception changes only the control from blocking to waived; policy identity remains. Inspect exception ID/expiry and job enforcement field.
P3 — expire Expired exception is ignored and control returns to blocking. Use an expired copy and assert exception:null.
P4 — collision Project job reusing mandatory name is rejected because suffix strategy is never. Preserve non-zero exit plus reason=job-name-conflict.

4. Create checkpoint inputs and exact simulator

Create the project and policy models, then save the complete Python simulator from Lesson 2, section 5. Using the same simulator prevents the checkpoint from changing its own rules mid-lab.

cat > project/project-model.json <<'JSON'
{
  "project":"acme-labs/ch36-app",
  "source_revision":"pending",
  "jobs":[
    {"name":"build","stage":"build"},
    {"name":"test","stage":"test"}
  ]
}
JSON
cat > policy/policy-model.json <<'JSON'
{
  "name":"ch36 mandatory governance gate",
  "version":"ch36-policy-v1.0.0",
  "control":"governance-proof",
  "strategy":"inject_policy",
  "suffix":"never",
  "scope":{"projects":["acme-labs/ch36-app"]},
  "job":{"name":"policy:governance-proof","stage":".pipeline-policy-pre","control":"governance-proof"}
}
JSON
printf '[]\n' > governance/exceptions.json
# Save Lesson 2 section 5's full Python program here:
# tools/compile_effective_pipeline.py
chmod +x tools/compile_effective_pipeline.py

git init -q
git config user.name "Chapter 36 Learner"
git config user.email "learner@example.invalid"
git add . && git commit -qm "checkpoint: establish policy and project models"
SOURCE_SHA="$(git rev-parse HEAD)"
python - "$SOURCE_SHA" <<'PY'
import json,sys
p='project/project-model.json'; d=json.load(open(p)); d['source_revision']=sys.argv[1]
open(p,'w').write(json.dumps(d,indent=2)+'\n')
PY
git add project/project-model.json && git commit -qm "checkpoint: bind source revision"
printf 'source_sha=%s\npolicy_version=%s\n' "$(git rev-parse HEAD)" 'ch36-policy-v1.0.0'

5. Verify P1 — enforced effective configuration

Compile with no exception. Save both machine-readable output and a digest list.

python tools/compile_effective_pipeline.py \
  --project project/project-model.json --policy policy/policy-model.json \
  --exceptions governance/exceptions.json --out evidence/run-enforced.json \
  | tee evidence/run-enforced.trace
python - <<'PY'
import json
x=json.load(open('evidence/run-enforced.json'))
assert x['status']=='compiled' and x['exception'] is None
p=x['jobs'][0]
assert p['origin']=='policy' and p['enforcement']=='blocking'
assert p['policy_version']=='ch36-policy-v1.0.0'
print('P1 verified:',p)
PY
sha256sum project/project-model.json policy/policy-model.json evidence/run-enforced.json \
  | tee evidence/digests.enforced.txt

6. Verify P2 — one bounded exception

Generate a two-hour waiver scoped to one project and one control. This is synthetic governance metadata; it is not presented as a GitLab-native generic waiver schema.

python - <<'PY'
import json
from datetime import datetime,timedelta,timezone
now=datetime.now(timezone.utc)
e=[{
 'id':'EX-CH36-CHECKPOINT-001',
 'project':'acme-labs/ch36-app',
 'control':'governance-proof',
 'owner':'service-owner@example.invalid',
 'approved_by':'governance@example.invalid',
 'reason':'checkpoint bounded migration waiver',
 'created_at':now.isoformat().replace('+00:00','Z'),
 'expires_at':(now+timedelta(hours=2)).isoformat().replace('+00:00','Z'),
 'policy_version':'ch36-policy-v1.0.0'
}]
open('governance/exceptions.json','w').write(json.dumps(e,indent=2)+'\n')
PY
python tools/compile_effective_pipeline.py \
  --project project/project-model.json --policy policy/policy-model.json \
  --exceptions governance/exceptions.json --out evidence/run-waived.json \
  | tee evidence/run-waived.trace
python - <<'PY'
import json
x=json.load(open('evidence/run-waived.json'))
assert x['exception']['id']=='EX-CH36-CHECKPOINT-001'
assert x['jobs'][0]['origin']=='policy'
assert x['jobs'][0]['enforcement']=='waived'
assert x['jobs'][0]['policy_version']=='ch36-policy-v1.0.0'
print('P2 verified:',x['exception']['id'])
PY

7. Verify P3/P4 — expiry and collision do not silently bypass policy

Use copies so the original evidence remains intact.

python - <<'PY'
import json
x=json.load(open('governance/exceptions.json'))
x[0]['expires_at']='2000-01-01T00:00:00Z'
open('evidence/exceptions-expired.json','w').write(json.dumps(x,indent=2)+'\n')
PY
python tools/compile_effective_pipeline.py \
  --project project/project-model.json --policy policy/policy-model.json \
  --exceptions evidence/exceptions-expired.json --out evidence/run-expired.json >/dev/null
python - <<'PY'
import json
x=json.load(open('evidence/run-expired.json'))
assert x['exception'] is None and x['jobs'][0]['enforcement']=='blocking'
print('P3 verified')
PY

python - <<'PY'
import json
x=json.load(open('project/project-model.json'))
x['jobs'].append({'name':'policy:governance-proof','stage':'test'})
open('evidence/project-collision.json','w').write(json.dumps(x,indent=2)+'\n')
PY
set +e
python tools/compile_effective_pipeline.py \
  --project evidence/project-collision.json --policy policy/policy-model.json \
  --exceptions governance/exceptions.json --out evidence/run-collision.json \
  > evidence/run-collision.trace 2>&1
RC=$?
set -e
printf '%s\n' "$RC" > evidence/run-collision.exit
python - <<'PY'
import json
x=json.load(open('evidence/run-collision.json'))
assert x['status']=='rejected' and x['reason']=='job-name-conflict'
print('P4 verified:',x)
PY

8. Produce the evidence packet

The packet includes source/policy identity, effective results, exception evidence, deliberate failure, hashes, and explicit limitations.

python - <<'PY'
import hashlib,json
from pathlib import Path
files=[
 'project/project-model.json','policy/policy-model.json','governance/exceptions.json',
 'evidence/run-enforced.json','evidence/run-waived.json','evidence/run-expired.json',
 'evidence/run-collision.json','evidence/run-collision.exit'
]
def h(p): return hashlib.sha256(Path(p).read_bytes()).hexdigest()
manifest={
 'schema':1,
 'policy_project_simulated':'acme-labs/security-policy-management',
 'policy_version':'ch36-policy-v1.0.0',
 'target_project':'acme-labs/ch36-app',
 'source_revision':json.load(open('project/project-model.json'))['source_revision'],
 'pipeline_source_simulated':'push',
 'runner_executor_image':'not-applicable-local-simulation',
 'exception_id':'EX-CH36-CHECKPOINT-001',
 'files':[{'path':p,'sha256':h(p)} for p in files],
 'limitations':[
   'Local compiler models governance origin/scope/waiver evidence only.',
   'It does not emulate GitLab YAML compilation, runners, rules, artifacts, or audit APIs.',
   'Real PEP and audit-event paths require documented tiers and authorization.'
 ]
}
Path('evidence/manifest.json').write_text(json.dumps(manifest,indent=2)+'\n')
PY
cat evidence/manifest.json
find evidence -maxdepth 1 -type f -print | sort
Real GitLab packet: substitute target project/ref/SHA, pipeline source/ID, effective configuration/job graph, policy project/ref/content digest, policy-job ID, runner/executor/image/tool identity, reports/artifact IDs or digests, API/audit evidence, any environment/deployment state, and an assumptions/limitations note.

9. Optional real GitLab mapping

In an authorized disposable Ultimate namespace, configure the policy through a reviewed merge request, pin the referenced policy CI configuration, create one target pipeline, and collect the evidence fields above. If you use a temporary scope exclusion as the technical waiver, keep the owner/reason/expiry record separately and reconcile it.

Do not use a production group for this checkpoint. Linking policy projects or changing policy scope can affect descendants. The local path already satisfies the required learning objective.

10. Verification checklist and cleanup

  • P1: policy-origin job appears without being declared by project intent.
  • P2: exception has identity, owner, approver, reason, expiry, and changes only the intended control.
  • P3: expired exception is inactive.
  • P4: collision is preserved and rejected rather than hidden.
  • Policy version and source revision are present in evidence.
  • No real secret or production/external resource was changed.
  • Limitations distinguish local simulation from real GitLab state.
cd /tmp 2>/dev/null || cd /
printf 'about_to_remove=%s\n' "$LAB"
case "$LAB" in
  */gitlab-ch36-checkpoint) rm -rf "$LAB" ;;
  *) echo "Refusing unexpected cleanup path" >&2; exit 2 ;;
esac

11. Knowledge check

The project’s .gitlab-ci.yml lacks the mandatory job, but the actual pipeline contains it. Is that automatically a defect?

A project is excluded from policy scope, but there is no owner or expiry. Which layer failed?

A duplicate job-name conflict occurs with suffix: never. Should you grant a broader token?

Why is “policy updated” in an audit event not a complete evidence packet?

A waiver expired yesterday but the technical scope exclusion remains. What should happen?

Why should new enforcement use pipeline execution policies instead of legacy compliance pipelines?

12. What Chapter 36 adds to the production operating model

Chapter 36 adds a governance chain above individual project CI: mandatory controls have a versioned origin, explicit scope, observable effective configuration, attributable execution, retained audit/evidence, and a bounded exception lifecycle. That makes organization-wide CI/CD reviewable instead of merely centralized.

Chapter 37 can now troubleshoot from a stronger baseline: when a pipeline breaks, identify not only the job failure but also which policy revision and scope shaped the pipeline, what first-failure evidence existed, and which layer owns the correction.

Next chapter

Next: Pipeline Troubleshooting, YAML Debugging, Runner Failures, Network Issues, Flaky Jobs, and Recovery Playbooks

Apply the same evidence-first state model to YAML errors, runner failures, networking faults, flaky jobs, and recovery playbooks.

Knowledge check

What must be recorded before claiming that a policy governed a project pipeline?

A project pipeline is green, but the expected enforced job is absent. Which layer should you inspect first?

Why should an exception have an owner, reason, scope, and expiry?

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

Further reading — current official GitLab sources

Behavior and tier assumptions in this chapter were checked against the current documentation on 2026-09-13. Re-check these pages before applying the optional enterprise path because policy schemas and product tiers evolve.

Current assumptions used in this chapter: Version-sensitive YAML, Runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations must be verified against the exact GitLab, GitLab Runner, tool, and external-system versions used in production. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for reproducible work.

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.