Checkpoint Lab — Parent-Child Pipelines, Dynamic Child Pipelines, Generated Configuration, and Monorepo Decomposition
The checkpoint lab decomposes a synthetic monorepo, generates one bounded child configuration from validated metadata, proves parent-to-child traceability, injects and repairs a safe configuration failure, and produces an evidence packet that demonstrates bounded execution without relying on paid infrastructure.
Learning objectives
- Decompose a toy monorepo into bounded child pipelines using explicit subtree ownership and current GitLab trigger semantics.
- Generate one child pipeline from validated allowlisted metadata and preserve the generated YAML as evidence.
- Predict and verify parent/child IDs, source SHA equality, child pipeline source, job presence/absence, and status propagation.
- Inject one safe generation/rule failure, repair the causal edge, and rerun only the smallest scope.
- Produce a production-readiness note covering limits, ownership, forwarding, status, observability, security, and rollback before Chapter 15.
1. Checkpoint mission
Build a toy monorepo with API and web subtrees. Route changed areas into child pipelines, but generate the API child from validated metadata so you can prove generated configuration is bounded and reviewable. The lab must remain safe even if no runner is available: configuration, generated YAML, expected graph, and evidence-plan inspection still demonstrate the architecture.
2. Write predictions before running
| Prediction | How you will verify it |
|---|---|
| API-only change creates exactly one child | Parent expanded config + downstream card/API |
| Child uses exactly the parent source SHA | Compare parent/child CI_COMMIT_SHA |
| Child source is parent_pipeline | Safe child metadata output or pipeline API |
| Generated YAML emits exactly one job from allowed metadata | Inspect/digest generated artifact before trigger |
| With strategy: mirror, trigger status follows child status | Compare trigger and child status over one success/failure drill |
| Web child is absent on API-only change | Parent graph + job list |
3. Disposable project files
.
├── .gitlab-ci.yml
├── ci/
│ ├── children/web.yml
│ ├── generate_child.py
│ └── targets.json
├── services/
│ ├── api/app.txt
│ └── web/app.txt
└── generated/
4. Validated metadata is data, not YAML
{
"area": "api",
"checks": ["unit"]
}
The generator supports only area=api|web, only
checks=["unit"] in this checkpoint, and exactly one
generated job. Any other value fails the generator. This
intentionally small contract makes the security boundary visible.
5. Generator with explicit bounds
from pathlib import Path
import json
meta = json.loads(Path("ci/targets.json").read_text(encoding="utf-8"))
if set(meta) != {"area", "checks"}:
raise SystemExit("unexpected metadata keys")
if meta["area"] not in {"api", "web"}:
raise SystemExit("unsupported area")
if meta["checks"] != ["unit"]:
raise SystemExit("checkpoint allows exactly one unit check")
area = meta["area"]
yaml = f"""{area}-unit:
image: alpine:3.20.3
script:
- test -f services/{area}/app.txt
- mkdir -p evidence
- printf 'area={area}\\nsha=%s\\nchild_pipeline=%s\\nsource=%s\\n' "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_PIPELINE_SOURCE" > evidence/{area}.txt
artifacts:
expire_in: 1 day
paths:
- evidence/{area}.txt
"""
Path("generated").mkdir(exist_ok=True)
Path("generated/child.yml").write_text(yaml, encoding="utf-8")
6. Static web child
web-unit:
image: alpine:3.20.3
script:
- test -f services/web/app.txt
- mkdir -p evidence
- printf 'area=web\nsha=%s\nchild_pipeline=%s\nsource=%s\n' "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_PIPELINE_SOURCE" > evidence/web.txt
artifacts:
expire_in: 1 day
paths: [evidence/web.txt]
7. Parent pipeline: bounded generation + one static route
stages: [generate, children]
generate-api:
stage: generate
image: python:3.13.7-alpine3.22
rules:
- changes:
- services/api/**/*
- ci/targets.json
- ci/generate_child.py
script:
- python ci/generate_child.py
- test "$(grep -c '^[a-z].*-unit:' generated/child.yml)" -eq 1
- sha256sum generated/child.yml | tee generated/child.sha256
- sed -n '1,120p' generated/child.yml
artifacts:
name: "generated-api-$CI_PIPELINE_ID-$CI_JOB_ID"
expire_in: 1 day
paths:
- generated/child.yml
- generated/child.sha256
api-child:
stage: children
rules:
- changes:
- services/api/**/*
- ci/targets.json
- ci/generate_child.py
variables:
PARENT_PIPELINE_ID: "$CI_PIPELINE_ID"
trigger:
include:
- artifact: generated/child.yml
job: generate-api
strategy: mirror
web-child:
stage: children
rules:
- changes:
- services/web/**/*
variables:
PARENT_PIPELINE_ID: "$CI_PIPELINE_ID"
trigger:
include:
- local: ci/children/web.yml
strategy: mirror
8. Validate before execution
Use CI Lint/pipeline simulation where available to validate the
parent. The dynamic child itself cannot exist until
generate-api produces its artifact, so separately run
the generator locally or in the job and validate/inspect the emitted
YAML before the trigger consumes it.
Record the digest from generated/child.sha256. The
digest proves which generated bytes you reviewed; it does not prove
the generator or child job is safe by itself.
9. Run the API-only success path
-
Change only
services/api/app.txton a disposable branch. - Record parent pipeline ID/source/ref/SHA.
-
Confirm
generate-apiandapi-childexist whileweb-childis absent. - Inspect the generated YAML artifact and its SHA-256 before or immediately after child creation.
-
Record child pipeline ID and verify same project/ref/SHA plus
source
parent_pipeline. - If runtime is available, record generated child job ID, runner/executor/version, and non-secret evidence artifact.
10. Inject one safe failure and preserve it
Edit ci/targets.json so "checks" becomes
["unit", "integration"]. The generator should fail
before producing a new allowed child configuration.
Preserve the generator job ID/log and confirm no new child was
created from that failed generator.
Repair the metadata back to ["unit"]. Rerun only the
smallest safe scope (the failed pipeline or relevant jobs according
to your GitLab UI/version) and preserve both before/after evidence.
This demonstrates that generation validation is a
security/correctness boundary, not a formatting nicety.
11. Optional status-mirroring drill
In the disposable generated child only, temporarily add
exit 17 after writing the evidence file. Prediction:
with strategy: mirror, the child fails and the parent
trigger reflects that failure. Preserve IDs/statuses, then revert
the deliberate failure. Do not use allow_failure merely
to make the graph green.
12. Required evidence packet
| Evidence | What to capture |
|---|---|
| Parent identity | Project, ref, SHA, pipeline ID/source |
| Routing | Expanded parent trigger rules and proof web child was absent |
| Generator | Generator job ID, validated metadata, image/tool version, generated YAML artifact + digest |
| Child identity | Child pipeline ID, source=parent_pipeline, same SHA/ref |
| Child jobs | Compiled/generated job names, statuses, runner/executor/version if observed |
| Status contract | Trigger strategy and trigger-vs-child status evidence |
| Outputs | Child artifact/report metadata with producer job/SHA and expiry |
| Failure/repair | Original generator or child failure evidence, causal diagnosis, minimal fix |
| Assumptions | GitLab version/offering; no paid cross-pipeline artifact feature required |
| Limits/governance | Expected child count, max generator output, owner, rollback path |
13. Verification checklist
- Exactly one child is created for the API-only success case.
- Parent and child have distinct pipeline IDs but the same project/ref/SHA.
-
Child jobs see
CI_PIPELINE_SOURCE=parent_pipeline. - Generated configuration was produced from validated allowlisted metadata, retained, inspected, and digested.
- No secrets, production targets, privileged runners, broad tokens, or cloud resources are used.
-
strategy: mirroris documented as a status contract, not as a child-creation mechanism. - Fan-out is bounded to one generated job and one intended child for the checkpoint path.
- Failure evidence was preserved before repair and no unrelated pipeline/artifact was deleted.
14. Cleanup and rollback
Remove only the disposable branch/project resources created for the checkpoint. If using a shared sandbox project, revert the exact checkpoint commit or delete the exact lab branch after exporting evidence. Do not select “latest” pipelines/artifacts for destructive cleanup without matching project/ref/SHA/ID.
15. What Chapter 14 adds to the production operating model
You can now decompose one repository into multiple traceable pipeline records without losing source identity: parent routing is explicit, generated configuration is bounded and reviewable, child source/status semantics are understood, forwarding is narrow, and fan-out has limits.
Chapter 15 changes the trust boundary again. Multi-project pipelines cross repository/project authorization, ref, ownership, and artifact/identity boundaries, so the same evidence discipline must be extended to downstream projects rather than assumed from same-project parent-child behavior.
Knowledge check
What proves the generated child belongs to the intended parent revision?
The parent and child share the same project/ref/SHA, while the generated YAML artifact is tied to the recorded generator job and parent pipeline.
Why should the generator reject an extra check instead of silently emitting more jobs?
The checkpoint contract intentionally bounds fan-out. Rejecting unknown metadata prevents hidden expansion and keeps generated executable configuration reviewable.
If the generator job fails, should you investigate child runners?
No. No valid dynamic child was created, so child runner state is irrelevant to that failure.
Why is a digest of generated/child.yml useful but insufficient?
It identifies the exact generated bytes. You still need generator source/input trust, GitLab validation, child IDs, and runtime evidence.
What is the Chapter 15 boundary that changes next?
The downstream pipeline can be in another project, so authorization, project/ref identity, cross-project tokens/artifacts, and ownership become explicit.
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.
Parent/child pipeline syntax, downstream status behavior, report
visibility, hierarchy limits, inputs, and variable forwarding are
version-sensitive. Re-check the deployed GitLab version before
relying on newer syntax such as strategy: mirror.
- Downstream pipelines — parent-child semantics, dynamic child pipelines, nesting, pipeline source, reports, variables, inputs, and status behavior.
-
CI/CD YAML syntax reference
—
trigger,trigger:include,trigger:strategy,trigger:forward, andneeds:pipeline:job. - Use CI/CD configuration from other files — include rules and dynamic-child configuration limitations.
- Troubleshooting downstream pipelines — empty child pipelines, permission/configuration failures, and variable forwarding issues.
- CI/CD limits — downstream hierarchy sizing and resource-protection rationale.
-
Pipelines API
— listing pipelines and querying child pipelines with
source=parent_pipeline.
CI_PIPELINE_SOURCE=parent_pipeline. Nested child
pipelines are limited to two child levels; the default pipeline
hierarchy limit is 1000 downstream pipelines. One child trigger can
combine up to three child configuration files. Dynamic child
configuration can come from a generated artifact; the artifact path
is interpreted by the GitLab server, and CI/CD variables cannot be
used in an include section inside the dynamic child
configuration. strategy: mirror was introduced in
GitLab 18.2 and is the recommended status-coupling strategy; without
a strategy the trigger job succeeds once the child is created.
YAML-defined trigger variables forward by default, while pipeline
variables do not unless
trigger:forward:pipeline_variables: true is set.
needs:pipeline:job for cross-parent/child artifact
download is currently Premium/Ultimate, so mandatory labs do not
depend on it.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.