Chapter 15Lesson 05~215 minutes

Checkpoint Lab — Multi-Project Pipelines, Downstream Triggers, Cross-Project Dependencies, and Platform-Oriented Delivery

The checkpoint lab builds a two-project delivery chain with a narrow job-token allowlist, verifies the exact downstream SHA and mirrored status, injects one authorization failure, repairs only the access edge, and produces an evidence packet suitable for platform review.

Multi-project pipelinesCI_JOB_TOKENDownstream triggersAuthorizationPlatform delivery

Learning objectives

  • Build a two-project pipeline using a governed downstream ref and strategy: mirror.
  • Use a narrow CI_JOB_TOKEN allowlist and prove one intended API call succeeds while the unauthorized baseline is preserved.
  • Verify the exact downstream SHA independently rather than trusting the requested ref name alone.
  • Produce a complete cross-project evidence packet including identities, authorization, inputs, statuses, assumptions, and rollback.
  • Bridge to Chapter 16 by separating cross-project orchestration from merge-request and merge-train validation semantics.

1. Checkpoint mission

Build a disposable two-project chain in which ch15-upstream triggers ch15-downstream on a governed lab tag with strategy: mirror. The downstream receives only typed non-secret inputs, verifies the upstream SHA through an allowlisted Free-tier Commit API request, and records its own exact SHA/status. You must preserve one intentional authorization denial before repairing it.

2. Assumptions and preflight

  • GitLab.com, Self-Managed, or Dedicated with current multi-project pipeline/input syntax; docs checked 2026-09-11.
  • Two private disposable projects under a namespace you control.
  • A normal non-privileged runner capable of Alpine 3.20.3 jobs, or equivalent trusted disposable runner.
  • No production secrets, cloud accounts, registries, packages, releases, environments, or customer source.
  • The same lab user has permission to create pipelines in downstream and read upstream commit metadata.
  • Cross-project artifact retrieval is not required; Premium/Ultimate path remains optional.

3. Write predictions before running

Prediction Independent verification
Upstream trigger creates a separate downstream pipeline in another project Pipeline graph + downstream pipeline ID/project path
Downstream runs source=pipeline at the SHA behind lab-v1 Downstream evidence file + repository preflight SHA
Before allowlisting, downstream job-token request to upstream is denied Sanitized HTTP 401/403/404 evidence
After upstream allowlists downstream, exact upstream commit API request succeeds HTTP 200 + JSON id equals upstream input SHA
strategy: mirror makes trigger status track downstream status Trigger job status compared with downstream pipeline status

4. Downstream checkpoint configuration

spec:
  inputs:
    upstream-project-id:
      type: number
    upstream-pipeline-id:
      type: number
    upstream-sha:
      type: string
      regex: '^[0-9a-f]{40}$'
---
stages: [verify]

verify-platform-contract:
  stage: verify
  image: alpine:3.20.3
  rules:
    - if: '$CI_PIPELINE_SOURCE == "pipeline"'
    - when: never
  before_script:
    - apk add --no-cache curl jq
    - mkdir -p evidence
  script:
    - printf 'downstream_project=%s\n' "$CI_PROJECT_ID" > evidence/identity.txt
    - printf 'downstream_pipeline=%s\n' "$CI_PIPELINE_ID" >> evidence/identity.txt
    - printf 'downstream_ref=%s\n' "$CI_COMMIT_REF_NAME" >> evidence/identity.txt
    - printf 'downstream_sha=%s\n' "$CI_COMMIT_SHA" >> evidence/identity.txt
    - printf 'source=%s\n' "$CI_PIPELINE_SOURCE" >> evidence/identity.txt
    - printf 'upstream_project=%s\n' '$[[ inputs.upstream-project-id ]]' >> evidence/identity.txt
    - printf 'upstream_pipeline=%s\n' '$[[ inputs.upstream-pipeline-id ]]' >> evidence/identity.txt
    - printf 'upstream_sha=%s\n' '$[[ inputs.upstream-sha ]]' >> evidence/identity.txt
    - |
      code="$(curl --silent --show-error         --output evidence/upstream-commit.json         --write-out '%{http_code}'         --header "JOB-TOKEN: $CI_JOB_TOKEN"         "$CI_API_V4_URL/projects/$[[ inputs.upstream-project-id ]]/repository/commits/$[[ inputs.upstream-sha ]]" )"
      printf 'upstream_api_http=%s\n' "$code" >> evidence/identity.txt
      test "$code" = "200"
    - jq -e --arg sha '$[[ inputs.upstream-sha ]]' '.id == $sha' evidence/upstream-commit.json >/dev/null
    - jq -r '{id, short_id, committed_date}' evidence/upstream-commit.json > evidence/upstream-summary.json
  artifacts:
    name: "ch15-checkpoint-$CI_PIPELINE_ID-$CI_JOB_ID"
    expire_in: 1 day
    paths:
      - evidence/identity.txt
      - evidence/upstream-summary.json

For the intentional failure run, keep the same job but expect the API step to fail before the allowlist exists. Preserve the first trace and HTTP status.

5. Upstream checkpoint configuration

stages: [record, orchestrate]

record-upstream:
  stage: record
  image: alpine:3.20.3
  script:
    - mkdir -p evidence
    - printf 'project=%s\npipeline=%s\nsource=%s\nref=%s\nsha=%s\n'         "$CI_PROJECT_ID" "$CI_PIPELINE_ID" "$CI_PIPELINE_SOURCE"         "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" > evidence/upstream.txt
  artifacts:
    name: "ch15-upstream-$CI_PIPELINE_ID-$CI_JOB_ID"
    expire_in: 1 day
    paths: [evidence/upstream.txt]

orchestrate-downstream:
  stage: orchestrate
  inherit:
    variables: false
  trigger:
    project: platform-lab/ch15-downstream
    branch: lab-v1
    strategy: mirror
    inputs:
      upstream-project-id: $CI_PROJECT_ID
      upstream-pipeline-id: $CI_PIPELINE_ID
      upstream-sha: $CI_COMMIT_SHA
    forward:
      yaml_variables: false
      pipeline_variables: false

6. Bind and record the downstream ref before triggering

In the disposable downstream repository, review the checkpoint configuration, then create or update lab-v1 only under the lab procedure. Record:

DOWNSTREAM_EXPECTED_SHA="$(git rev-parse HEAD)"
printf 'expected_downstream_sha=%s\n' "$DOWNSTREAM_EXPECTED_SHA"
git tag -f lab-v1 "$DOWNSTREAM_EXPECTED_SHA"
git push --force-with-lease origin refs/tags/lab-v1

Safety: force-moving tags is shown only for the disposable lab. Production release tags should normally be protected/immutable by policy, not rewritten. If your lab can create a fresh unique tag instead, prefer that.

7. First run: intentionally preserve authorization failure

Ensure the upstream project does not yet allowlist downstream for job-token access. Run the upstream pipeline. The multi-project trigger should create downstream, but downstream's Commit API request should fail and, because of strategy: mirror, the trigger/upstream pipeline should reflect that downstream failure.

Capture upstream pipeline ID/SHA, trigger job ID/status, downstream pipeline ID/ref/SHA/source, failed downstream job ID, and sanitized HTTP status. Do not retry yet.

8. Repair exactly one authorization edge

In the disposable upstream project, add platform-lab/ch15-downstream to the upstream CI/CD job token allowlist. Keep project visibility, user roles, refs, YAML, runner configuration, and variables unchanged. This isolates the repaired causal edge.

9. Second run: prove exact source and authorization

Run the upstream pipeline again. Verify:

  • downstream pipeline source is pipeline;
  • downstream ref is lab-v1;
  • downstream SHA equals the independently recorded tag SHA;
  • the Commit API response HTTP status is 200;
  • the returned upstream commit id equals the input upstream SHA;
  • downstream success is mirrored to the trigger job;
  • no secret/token value appears in the trace or artifact.

10. Optional second denial: prove the allowlist is narrow

If you own a third disposable project solely for this check, do not add it to the upstream allowlist. A job from that project should not gain the same API access merely because it shares a namespace. If creating a third project is unnecessary, document this as an architecture prediction rather than adding resources.

11. Optional Premium/Ultimate artifact extension

Only after the core checkpoint passes, optionally test needs:project or CI_JOB_TOKEN artifact API access. Record the producer project/ref/SHA/job and digest. Document that this capability is paid-tier and that needs:project does not wait for an in-progress pipeline on the ref.

Do not rebuild the upstream artifact in downstream to avoid an authorization or retrieval problem; that would change artifact identity and invalidate the delivery evidence.

12. Required evidence packet

Evidence item Required capture
Upstream identity Project path/ID, pipeline ID/source, ref, SHA
Trigger contract Downstream project, requested ref, strategy=mirror, explicit inputs, forwarding disabled
Downstream identity Project path/ID, pipeline ID/source=pipeline, ref, exact SHA
Failure baseline Downstream job ID, first trace, sanitized denied HTTP status, allowlist absent
Authorization repair Target=upstream, source=downstream allowlist entry, user permission assumption
Success proof HTTP 200, returned commit ID equals upstream SHA, downstream job/pipeline success
Runner/tool context Runner/executor/version if observed; Alpine tag and curl/jq versions if material
Artifacts Only synthetic evidence artifacts, producer job IDs, paths, expiry, optional digest
Tier assumptions Core path Free-compatible; cross-project artifact extension Premium/Ultimate
Rollback Remove exact allowlist entry; remove disposable refs/projects; no long-lived token retained

13. Verification checklist

  • Exactly two required disposable projects are involved.
  • Requested downstream ref and actual downstream SHA are both recorded.
  • No branch/default-ref assumption substitutes for SHA evidence.
  • Downstream inputs are explicit and non-secret.
  • Variable forwarding is intentionally disabled in the checkpoint.
  • The initial API denial is preserved before the allowlist repair.
  • The upstream target allowlists only the downstream source needed for the request.
  • Allowlisting is not described as granting user permissions.
  • strategy: mirror behavior is verified from statuses, not assumed.
  • No PAT, trigger-token secret, TLS bypass, privileged runner, cloud credential, or production target is introduced.

14. Cleanup and rollback

Remove the exact downstream entry from the upstream job-token allowlist. Delete only the disposable lab-v1 ref and lab branches/projects created by the exercise. Export the sanitized evidence first. Confirm no project visibility or user-role changes remain and no long-lived token was created.

15. What Chapter 15 adds to the production operating model

You can now orchestrate across repository/project boundaries without losing identity: upstream and downstream refs/SHAs are distinct, pipeline creation and completion are distinct, user permission and job-token allowlisting are distinct, and cross-project artifacts are separate from triggers. Chapter 16 moves to merge-request pipelines, merged-results pipelines, merge trains, and pre-merge validation, where the source revision itself can be synthetic or merge-result-specific and must be reasoned about just as explicitly.

Knowledge check

What single change should separate the failing and successful checkpoint runs?

Why does the first downstream failure make the upstream trigger fail in this checkpoint?

What proves downstream executed the intended revision?

Does the successful Commit API request prove artifact access?

What Chapter 16 identity complication comes next?

Next lesson

Chapter 16 — Merge request pipelines and pre-merge validation

Move from cross-project orchestration to merge-request, merged-results, and merge-train source/revision semantics.

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. Multi-project trigger syntax, downstream inputs/forwarding, CI_JOB_TOKEN permissions, allowlist controls, artifact APIs, and needs:project are version-sensitive. Re-check the GitLab version deployed on Self-Managed or Dedicated instances before relying on newer behavior.

  • Downstream pipelines — multi-project triggers, downstream source values, trigger strategies, inputs, variable forwarding, and cross-project artifact patterns.
  • CI/CD job token — token lifetime, user-derived permissions, cross-project allowlists, security, and current allowlist controls.
  • Fine-grained job-token permissions — optional endpoint-level restrictions for allowlisted projects/groups.
  • Trigger pipelines with the API — trigger tokens, CI_JOB_TOKEN multi-project API triggers, source semantics, and revocation guidance.
  • CI/CD YAML syntax reference — trigger, trigger:strategy, trigger:forward, needs:project, and current tier constraints.
  • Job Artifacts API — artifact download by job/ref and the current tier requirement for cross-project job-token downloads.
Current behavior used by this chapter: Multi-project downstream pipelines are available across GitLab offerings. The user who starts the upstream pipeline must also be authorized to start a pipeline in the downstream project. Jobs in a multi-project downstream pipeline see CI_PIPELINE_SOURCE=pipeline. By default, a trigger job succeeds when GitLab successfully creates the downstream pipeline; strategy: mirror makes the trigger job track downstream status, while strategy: depend is not recommended for new designs. Downstream inputs are preferred over broad variables when defining a configuration contract. CI_JOB_TOKEN exists only while a job runs; target projects restrict cross-project token use with an allowlist, and allowlisting does not grant project membership. Current allowlists support up to 200 group entries and 200 project entries. needs:project and CI_JOB_TOKEN-authenticated Job Artifacts API downloads across projects are Premium/Ultimate features, so this chapter's mandatory path uses Free-tier allowlisted API metadata access and treats cross-project artifact transfer as optional/simulated.

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.