Chapter 14Lesson 05~215 minutes

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.

Parent-child pipelinesDynamic configurationMonoreposGenerated YAMLPipeline orchestration

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

  1. Change only services/api/app.txt on a disposable branch.
  2. Record parent pipeline ID/source/ref/SHA.
  3. Confirm generate-api and api-child exist while web-child is absent.
  4. Inspect the generated YAML artifact and its SHA-256 before or immediately after child creation.
  5. Record child pipeline ID and verify same project/ref/SHA plus source parent_pipeline.
  6. 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: mirror is 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?

Why should the generator reject an extra check instead of silently emitting more jobs?

If the generator job fails, should you investigate child runners?

Why is a digest of generated/child.yml useful but insufficient?

What is the Chapter 15 boundary that changes next?

Next lesson

Chapter 15 — Multi-project pipelines

Cross the project boundary into downstream triggers, authorization, cross-project dependencies, and platform-oriented delivery.

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.

Current behavior used by this chapter: parent-child pipelines are available on Free/Premium/Ultimate and GitLab.com/Self-Managed/Dedicated. Child pipelines run in the same project, ref, and commit SHA as the parent and report 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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.