Chapter 34Lesson 05~540 minutes

Production Capstone: Build and Govern a Secure GitLab Software Delivery Platform: Final Operational Review and Handoff

Perform the final operational review, prove critical invariants, assemble a production handoff package, document residual risks and tier constraints, and decide whether the platform is ready to operate.

Operational ReviewHandoffRisk RegisterReadinessCourse Complete

Learning objectives

  • Conduct a final operational review spanning architecture, access, merge governance, CI/CD, runners, variables, artifacts/registries, releases, security evidence, APIs/hooks, observability, Self-Managed recovery, and Duo governance.
  • Prove at least five critical invariants using independent evidence and retain the four controlled failure/recovery records.
  • Assemble a handoff package containing diagrams, role matrix, pipeline map, artifact/release evidence, runbooks, recovery objectives, tier constraints, risk register, maintenance checklist, and cleanup instructions.
  • Make a production-readiness decision that distinguishes verified controls from simulated, tier-dependent, infrastructure-dependent, or residual-risk items.
  • Close the GitLab course with an operating model that can be audited, maintained, recovered, and improved rather than a one-time configuration snapshot.
Availability baseline — verified 2026-08-22 against GitLab 19.3. The final checkpoint is mandatory and free-compatible. It can combine evidence created in the previous lessons with local fixtures. Premium/Ultimate enforcement, Dedicated-specific administration, Self-Managed restore, live Kubernetes, and GitLab Duo are reviewed as optional or simulated boundaries unless the learner already has an authorized disposable environment. The readiness decision must label those items accurately.

1. Final checkpoint scenario and acceptance contract

You are handing the Asterline delivery platform to another team. They should be able to answer, without relying on your memory: who can change what, how code reaches main, which pipeline produced the release, which runner executed sensitive work, which artifact bytes were released, which security evidence was considered, how integrations authenticate events, what failed during drills, how recovery works, which controls are simulated/tier-gated, and how to clean the lab safely.

FINAL ACCEPTANCE CONTRACT
Required proof
  [ ] >= 5 critical invariants independently verified
  [ ] >= 4 controlled failures documented and recovered
  [ ] exact commit / tag / artifact identity chain recorded
  [ ] runner and credential trust boundaries documented
  [ ] security evidence scope and limitations documented
  [ ] API/webhook integration verification recorded
  [ ] observability / audit / incident evidence references included
  [ ] recovery objectives and lifecycle boundaries included
  [ ] tier/offering/version assumptions time-stamped
  [ ] complete cleanup / rollback procedure included

Decision values: VERIFIED | SIMULATED | TIER-DEPENDENT | NOT TESTED | FAIL

2. Build a structured handoff package

from pathlib import Path
import json, textwrap, csv
root=Path('gitlab-ch34-handoff')
for name in ['architecture','governance','pipeline','evidence','runbooks','recovery','risk','maintenance','cleanup']:
    (root/name).mkdir(parents=True,exist_ok=True)

(root/'architecture'/'README.md').write_text("""# Architecture evidence\n\n- Namespace/project topology\n- Trust boundaries\n- Delivery flow\n- Evidence flow\n- External Kubernetes/Self-Managed/Duo boundaries marked optional\n""")
(root/'governance'/'roles.csv').write_text("""principal,scope,role_or_authority,reason\napp-dev,project,Developer,normal branch/MR work\nrelease-owner,project,Maintainer,release/ref governance\nplatform-owner,top-level group,Owner,namespace administration only\nrunner-operator,runner fleet,operator,execution isolation and patching\nsecurity-reviewer,security evidence,reviewer,findings/policy decisions\n""")
(root/'pipeline'/'map.md').write_text("""# Pipeline map\n\nMR/default branch -> test -> package -> security evidence\nTag -> release manifest\nSensitive deployment -> separately governed production boundary\n""")
(root/'recovery'/'objectives.json').write_text(json.dumps({
  'RPO':'training example: <= 24h; production value must be business-approved',
  'RTO':'training example: <= 4h; production value must be exercised',
  'restore_rule':'backup is unproven until compatible disposable restore/verification',
  'external_lifecycles':['configuration/secrets','object storage','runner state','external cloud/Kubernetes']
},indent=2))
(root/'maintenance'/'checklist.md').write_text("""# Maintenance checklist\n\n- Review inherited memberships and elevated roles.\n- Review branch/tag rules and paid-tier approval policy if present.\n- Review pipeline-source rules, variables, job-token allowlists, runner trust classes.\n- Review artifact/cache/registry retention and release identity.\n- Review scanner/template/component/image versions and security evidence freshness.\n- Review webhook/API failures, rate-limit behavior, and signing-token rotation.\n- Review audit/log/metric health and backup/restore test status.\n- Re-check GitLab version, deprecations, tier entitlements, and Duo/Agent status.\n""")
(root/'cleanup'/'README.md').write_text("""# Cleanup\n\n1. Export required synthetic evidence.\n2. Revoke/delete any ephemeral lab credential or webhook signing token.\n3. Detach disposable runner/integration resources.\n4. Delete synthetic packages/images/releases if policy allows.\n5. Archive/delete only the verified disposable GitLab project/group.\n6. Delete local fixture directories last.\n""")
print(root.resolve())

3. Import or create the evidence inventory

If you completed Lessons 2–4 in the same workspace, copy their evidence files into the handoff package after reviewing them for sensitive content. If not, use the following synthetic inventory and label it SIMULATED. Never invent live GitLab IDs.

from pathlib import Path
import json, hashlib
root=Path('gitlab-ch34-handoff'); ev=root/'evidence'
items=[
 {'id':'E-001','type':'membership','status':'SIMULATED','identity':'membership-snapshot-v1'},
 {'id':'E-002','type':'merge-governance','status':'SIMULATED','identity':'main-protection+review-record'},
 {'id':'E-003','type':'pipeline','status':'SIMULATED','identity':'commit-c0ffee01/pipeline-local'},
 {'id':'E-004','type':'artifact','status':'VERIFIED','identity':'sha256:aaa111-fixture'},
 {'id':'E-005','type':'security','status':'SIMULATED','identity':'security-report-fixture'},
 {'id':'E-006','type':'release','status':'SIMULATED','identity':'v0.1.0-capstone'},
 {'id':'E-007','type':'webhook','status':'VERIFIED','identity':'evt-capstone-001-signature-fixture'},
 {'id':'E-008','type':'recovery','status':'SIMULATED','identity':'four-drill-recovery-record'},
]
raw=json.dumps(items,indent=2)
(ev/'inventory.json').write_text(raw)
(ev/'inventory.sha256').write_text(hashlib.sha256(raw.encode()).hexdigest()+'\n')
print(raw)

4. Prove at least five critical invariants independently

The verifier below uses explicit status fields so it cannot silently turn a simulation into a production pass. Replace fixture values only with evidence you actually collected.

from pathlib import Path
import json
root=Path('gitlab-ch34-handoff'); out=root/'evidence'/'invariant_results.json'
results=[
 {'invariant':'least_privilege','status':'SIMULATED','proof':'role matrix + inherited-access drill'},
 {'invariant':'reviewed_default_branch','status':'SIMULATED','proof':'branch rule + reviewer evidence; required approvals not enforced on Free'},
 {'invariant':'runner_isolation','status':'SIMULATED','proof':'runner trust-class design + failure drill'},
 {'invariant':'artifact_identity','status':'VERIFIED','proof':'independent checksum comparison'},
 {'invariant':'release_traceability','status':'SIMULATED','proof':'tag/commit/pipeline/digest tuple fixture'},
 {'invariant':'security_evidence','status':'VERIFIED','proof':'report hash + declared scope'},
 {'invariant':'webhook_integrity','status':'VERIFIED','proof':'HMAC fixture + duplicate-event control'},
 {'invariant':'recovery','status':'SIMULATED','proof':'backup lifecycle + recovery fixture; no live Self-Managed restore'},
]
# Acceptance: at least five invariants have evidence, but production readiness later distinguishes VERIFIED from SIMULATED.
assert sum(r['status'] in {'VERIFIED','SIMULATED'} for r in results) >= 5
out.write_text(json.dumps(results,indent=2))
for r in results: print(f"{r['status']:10} {r['invariant']:28} {r['proof']}")
Important: “SIMULATED” is a successful learning outcome, not a production pass. Production readiness requires the real enforcement point and real independent evidence in the target offering/environment.

5. Confirm the four failure domains and prevention owners

Domain Injected failure Recovery evidence Preventive owner
Access/governance unexpected inherited elevated role effective-role correction + immutable baseline group owner
CI/runner/security MR path intersected privileged/networked runner and credential runner isolation + credential unavailability runner/platform owner
Artifact/release release digest disagreed with artifact identity tuple restored after containment release owner
Operations/integration duplicate webhook + incomplete recovery lifecycle idempotent event handling + separate restore inventory integration/Self-Managed operator

6. Perform the final operational review

Area What must be reviewed Evidence / decision
Architecture namespace/project ownership, external boundaries, trust arrows architecture package
Access direct/inherited roles, least privilege, offboarding path role matrix + membership snapshot
Merge controls protected refs, reviewer/approval behavior, CODEOWNERS/tier limits branch rule + MR evidence
CI/CD workflow/rules, DAG/data flow, retry/interruptibility, release gating pipeline map + config
Runners executor/isolation, tag/protection, network, lifecycle runner trust record
Variables/secrets protection/visibility/scope; no real secret in source/logs variable metadata + incident drill
Artifacts/registries artifact/package/image identity, retention, permissions digest/evidence manifest
Releases/deployments tag/commit/release/deployment linkage release manifest
Security scanner scope, report hash, vulnerability/policy limitations security evidence + tier matrix
API/hooks read-only default, pagination/rate limits, signature/idempotency API plan + webhook verification
Observability/audit request/job/time correlation, privacy-safe evidence incident timeline / Chapter 32 practices
Self-Managed backup scope, config/secrets/object storage, compatible restore recovery inventory; simulated unless disposable instance used
Duo/Agentic data classification, tool governance, approval, independent verification, credits Chapter 33 governance handoff; optional

7. Record the known tier/offering/version constraints in the handoff

{
  "reference_date": "2026-08-22",
  "gitlab_reference_release": "19.3",
  "mandatory_path": "GitLab Free + local fixtures",
  "constraints": {
    "protected_branches": "Free/Premium/Ultimate",
    "required_merge_request_approval_rules": "Premium/Ultimate",
    "code_owner_enforcement": "Premium/Ultimate",
    "protected_environments_and_deployment_approvals": "Premium/Ultimate",
    "basic_sast_and_downloadable_report": "Free/Premium/Ultimate with supported runner",
    "pipeline_secret_detection_report": "Free/Premium/Ultimate with supported runner",
    "security_policies_and_dependency_scanning": "Ultimate",
    "releases_and_release_evidence": "Free/Premium/Ultimate",
    "self_managed_backup_restore": "Self-Managed administration",
    "duo_agent_platform": "feature/status/credits/add-on/offering dependent; no mandatory AI use"
  }
}

8. Build the residual risk register

from pathlib import Path
import csv
root=Path('gitlab-ch34-handoff'); p=root/'risk'/'register.csv'
rows=[
 ['R-01','Required approval enforcement not tested on Free path','medium','project maintainer','upgrade/paid pilot or external policy before regulated production'],
 ['R-02','Runner isolation represented by design/fixture','high','runner operator','test real executor/network/ephemeral lifecycle before sensitive jobs'],
 ['R-03','Security fixture does not equal full production scanner coverage','high','security owner','enable supported scanners and verify coverage/report ingestion'],
 ['R-04','No live Self-Managed restore performed','high','Self-Managed operator','schedule compatible disposable restore drill and measure RPO/RTO'],
 ['R-05','Kubernetes boundary not live-tested','medium','platform owner','use disposable cluster/agent before production cluster access'],
 ['R-06','Duo/Agent Platform not required/tested','low','AI governance owner','re-verify status/credits/privacy/tool governance before adoption'],
 ['R-07','GitLab feature/tier semantics can change after 19.3','medium','platform owner','review release/deprecation notes on upgrade cadence'],
]
with p.open('w',newline='') as f:
    w=csv.writer(f); w.writerow(['id','risk','severity','owner','next_action']); w.writerows(rows)
print(p.read_text())

9. Create concise operating runbooks

A handoff should tell the next operator what to do when the system is not healthy. Keep runbooks action-oriented and evidence-first.

RUNBOOK — RELEASE IDENTITY MISMATCH
Trigger: tag/commit/pipeline/artifact digest disagreement
1. Stop promotion/deployment.
2. Preserve release/tag/pipeline/artifact evidence.
3. Recompute artifact digest independently.
4. Resolve tag to commit; map commit to expected pipeline.
5. Identify wrong object; do not rewrite evidence to match.
6. Rebuild/retag/correct metadata through authorized workflow.
7. Re-run security/tests as required.
8. Verify final immutable tuple and record root cause/prevention.

RUNBOOK — POSSIBLE CREDENTIAL EXPOSURE
1. Revoke/rotate/disable credential.
2. Contain affected automation/sessions.
3. Identify scope and exposure window.
4. Review audit/log/API evidence for misuse.
5. Remove source/log/history exposure where required.
6. Verify old credential unusable; issue least-privilege replacement.

RUNBOOK — PIPELINE / RUNNER TRUST FAILURE
1. Identify pipeline source and commit.
2. Freeze sensitive job/deployment path.
3. Inspect runner executor, tags/protection, network and mounts.
4. Inspect variable/job-token availability without printing values.
5. Re-route to appropriate isolated runner / narrow credentials.
6. Re-run from known commit and verify evidence.

10. Maintenance cadence after handoff

Production readiness decays. GitLab ships monthly, runner images and analyzers update, tokens expire, people change teams, storage grows, and external services change. The platform therefore needs a review cadence rather than a one-time sign-off.

Cadence Review
Per merge/release diff, pipeline source, security evidence, artifact digest, release identity, deployment authorization
Weekly failed/retried pipelines, runner queue/capacity, webhook errors, security backlog, storage anomalies
Monthly members/roles, branch/tag rules, variables/tokens, runner versions, registry/artifact retention, API automation health
Per GitLab upgrade release notes, deprecations, CI syntax/templates/components, runner compatibility, feature/tier/status changes
Quarterly or policy-defined access recertification, restore drill, incident runbook exercise, risk register, RPO/RTO review
Before AI adoption/change Duo/Agent feature status, credits, data processing, model/provider boundary, tool governance, human approvals

11. Make the production-readiness decision

Use a four-way distinction. Verified means the control ran at the real intended boundary and independent evidence supports it. Simulated means the design and logic were tested with fixtures but not at the production enforcement point. Tier-dependent means the control needs an entitlement not present in the mandatory path. Not tested means no acceptable evidence exists yet.

PRODUCTION-READINESS DECISION — TRAINING CAPSTONE
VERIFIED IN LAB
  - deterministic application tests
  - local artifact checksum identity
  - local security-evidence hash/scope
  - signed-webhook HMAC verification and duplicate-event logic
  - evidence-package integrity

SIMULATED / REQUIRES REAL ENVIRONMENT PROOF
  - inherited membership governance at target organization scale
  - runner isolation and production network boundary
  - end-to-end release/deployment promotion
  - Self-Managed restore and measured RPO/RTO
  - Kubernetes Agent authorization/deployment path

TIER-DEPENDENT
  - required MR approval rules / Code Owner enforcement
  - protected-environment deployment approvals
  - centralized security policies / advanced vulnerability workflows

OPTIONAL / NOT REQUIRED
  - GitLab Duo or Agent Platform execution

FINAL DECISION
  TRAINING OBJECTIVES: PASS when all evidence and drills are complete.
  PRODUCTION DEPLOYMENT: CONDITIONAL — only after every high-risk SIMULATED/TIER-DEPENDENT item required by the target organization is verified at its real boundary.

12. Verify the handoff package itself

from pathlib import Path
import hashlib, json
root=Path('gitlab-ch34-handoff')
required=[
 'architecture/README.md','governance/roles.csv','pipeline/map.md','evidence/inventory.json',
 'evidence/invariant_results.json','recovery/objectives.json','risk/register.csv',
 'maintenance/checklist.md','cleanup/README.md'
]
missing=[x for x in required if not (root/x).exists()]
if missing: raise SystemExit('missing: '+', '.join(missing))
manifest=[]
for rel in required:
    p=root/rel
    manifest.append({'path':rel,'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'bytes':p.stat().st_size})
(root/'evidence'/'handoff_manifest.json').write_text(json.dumps(manifest,indent=2))
print('HANDOFF_OK')
for x in manifest: print(x['sha256'][:12],x['bytes'],x['path'])

Hashing the handoff files does not make their claims true. It makes the reviewed handoff set identifiable so later changes can be detected. Evidence integrity and evidence correctness are separate concerns.

13. Final cleanup and rollback

Cleanup is part of the capstone, not an afterthought. Before deleting the disposable project, export the synthetic evidence you intend to keep. Revoke any ephemeral lab credential or webhook token, detach disposable runner/integration resources, remove synthetic packages/images/releases if required, then verify the namespace is truly disposable.

# Local cleanup: REVIEW the absolute path first.
pwd
ls -la gitlab-ch34-capstone gitlab-ch34-drills gitlab-ch34-handoff 2>/dev/null || true

# Delete only after the evidence package has been reviewed/exported:
# rm -rf gitlab-ch34-capstone gitlab-ch34-drills gitlab-ch34-handoff
Destructive boundary: do not automate deletion of the live GitLab project/group merely because the local lab completed. Confirm project path, ownership, package/release retention, integrations, and recovery obligations first.

Knowledge check

A GitLab Free project has reviewer approval comments, but merging is still technically possible without them. How should the handoff classify that control?

The pipeline is green and the release exists, but the artifact digest is missing. Is the release production-ready?

A protected variable is configured, but merge-request code runs on a privileged persistent runner with production network access. What is the dangerous assumption?

A secret appears in a real job log. What is the first operational action?

A Self-Managed GitLab backup archive completed successfully. Can the recovery invariant be marked verified?

A webhook signature is valid but the same event ID was already processed. What should the receiver do?

Why are Premium/Ultimate controls included in a Free-compatible capstone?

What makes the final readiness decision trustworthy?

14. Lesson summary and bridge

  • You converted the entire GitLab course into an operating model with explicit resource, identity, trust, evidence, and recovery boundaries.
  • At least five invariants are documented with independent evidence and four failure domains have recovery records.
  • The handoff package contains architecture, roles, pipeline mapping, artifact/release evidence, runbooks, recovery objectives, tier constraints, risks, maintenance, and cleanup.
  • Production readiness is conditional on verifying every high-risk simulated or tier-dependent boundary that the real organization depends on.
  • The durable skill is not memorizing GitLab menus: it is operating delivery as an evidence-driven system that can explain, constrain, diagnose, and recover every consequential change.

Course complete. You have progressed from GitLab platform foundations through collaboration, CI/CD, runners, artifacts, registries, releases, DevSecOps, Kubernetes, APIs, Self-Managed operations, observability, AI governance, and finally an integrated production delivery-platform capstone. Revisit the evidence and failure drills whenever your GitLab version, offering, runner fleet, security posture, or organizational responsibilities change.

Primary sources and version notes

These lessons were finalized against current official GitLab documentation on 2026-08-22 with GitLab 19.3 as the reference release. Availability can vary by GitLab.com, Self-Managed, or Dedicated offering; Free, Premium, or Ultimate tier; namespace settings; administrator policy; runner type; and feature status. Re-check current documentation before applying a production design. This capstone closes the 34-chapter / 170-lesson GitLab curriculum. For future maintenance, re-check GitLab release notes and the linked primary documentation before reusing tier-sensitive or version-sensitive operational assumptions.

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.