Skip to content

ADR-016: Mono-Repo Docs Published on Release Tags, With a Released-Version Table

Field Value
Date 2026-07-29
Status Accepted
Deciders Holger Zahnleiter
Supersedes ADR-015 (gating decision only)
Superseded by

Context and Problem Statement

ADR-015 gated the mono-repo root's combined documentation site on the default branch, reasoning that the site is not a versioned release artifact of any single service but a living, always- current combined view. In practice this surprised the first user of a generated mono-repo: merging a branch into main published the site immediately, with no release tag anywhere.

Three properties of the setup make the branch gate worse than it first appears:

  1. A job named exactly pages always deploys when it runs. GitLab offers no build-without- publish mode, so "we only build it as a test" and "we publish it" cannot be the same job. ADR-015's single pages job therefore could not do both, and did the publishing one.
  2. A changes: rule always evaluates to true in a tag pipeline. The root pipeline's trigger-<artifact> jobs were gated on changes: ["<artifact>/**"] alone, so tagging payment-v1.0.0 fired every artifact's child pipeline. All but one contained no runnable job and failed with "no jobs in this pipeline". This defect was latent from ADR-012 onward and only surfaced once someone pushed a real release tag.
  3. The combined site has no version to display. Every single-repo pages job stamps the release into the site header (sed -i "s/^site_name: .*/& ($CI_COMMIT_TAG)/"). A mono-repo root has no single version — its artifacts are versioned independently and deliberately drift apart (BR-022) — so there was nothing to stamp, and the root job stamped nothing.

Underlying all three: what is a combined mono-repo docs site for? ADR-015 answered "a living view of the source tree". That answer produced a site that publishes states nobody released.

Decision Drivers

  • Published documentation should describe a state someone can actually consume — a released artifact — not an arbitrary intermediate commit on the default branch.
  • A broken docs build must be caught at review time, not discovered after publication.
  • Tagging one artifact must not fail five unrelated pipelines.
  • ci_cd.md: the Pages job must be named exactly pages, artifacts in public/, expire_in: never.
  • Independent per-artifact versioning (BR-022) must not be undermined by a synthetic repo-wide version invented purely so the docs have something to display.

Considered Options

  1. Split into docs-build (test) and pages (publish on release tag), with a generated released-version table — chosen.
  2. Keep ADR-015's branch gate and document the publish-on-merge behaviour more prominently.
  3. Move the existing pages job to a tag rule without adding a build-only tier.
  4. Introduce a repo-wide umbrella version (a "release train" tag) so the site has one version.

Decision Outcome

Chosen: option 1.

The combined site is reframed as a catalogue, not a release artifact. It answers "as of this date, which versions of these artifacts are released, and what do they document?" That reframing resolves all three problems at once: it justifies the tag gate, it explains why a build date rather than a version belongs in the header, and it gives the root specs/index.md a job to do.

Docs jobs

A shared .docs template holds the image and build steps. Two jobs extend it:

  • docs-build — merge-request and default-branch pipelines, never tags, and only when a specs/ directory or an mkdocs.yml changed. Keeps public/ for 7 days, publishes nothing.
  • pages — release tags only, matching an alternation of the manifest's own artifact names (^(payment|car-rental|…)-vX.Y.Z$), so a stray tag cannot publish. expire_in: never. Stamps the build date into site_name.

Any artifact's release tag republishes the whole site. The site spans all artifacts, so tying it to one designated artifact's tag would be arbitrary; the most recent release of any service is the most current released state of the system as a whole.

Trigger jobs

Each trigger-<artifact> job now matches its own tag namespace explicitly before falling back to changes:, and refuses tag pipelines that are not its own. This fixes problem 2.

Released-version table

The root specs/index.md carries a marker block — the same <!-- BEGIN: … --> idiom this generator already uses for CLAUDE.md's guideline-applicability table — listing each artifact with its latest release tag, that release's date, and its commit. Both docs jobs regenerate the block from git tag --list '<artifact>-v*' before building, which is why they set GIT_DEPTH: 0. The generator writes placeholder rows so a freshly generated repo and a local mkdocs serve work before the first tag exists.

The commit column is the answer to coordinated releases. Artifacts that had to change together are released by tagging the same commit, each keeping its own version number. A shared commit hash in the table is what makes such a set visible, so no umbrella version is needed — which is why option 4 was rejected. A repo-wide version would have to be bumped for every release of every artifact, and would state a compatibility guarantee the pipeline never verifies.

Positive Consequences

  • Published documentation always corresponds to a released state.
  • A docs build that breaks is caught on the merge request, before reaching the default branch.
  • Tagging one artifact builds and releases exactly that artifact.
  • The root index gains a genuinely useful overview that no one has to maintain by hand.
  • Per-artifact independence is preserved and made visible rather than papered over.

Negative Consequences / Risks

  • Documentation-only changes merged to the default branch are not visible on the published site until the next release tag of any artifact. Reviewers see them via docs-build's artifact.
  • The table generation requires git in the mkdocs image. Resolved by baking it in rather than installing it at job runtime (tool_chain.md): the root docs jobs pin mkdocs:1.2.0, the first tag to ship git. The per-artifact pages job stays on mkdocs:1.0.0 — it builds no version table and needs nothing from the newer tags.
  • docs-build introduces the root pipeline's first merge_request_event rule, so GitLab now creates root pipelines for merge requests. The trigger-* jobs explicitly opt out of those, so no child pipelines are created there and today's behaviour is otherwise preserved.

Known limitation, not addressed here

Each artifact's MR tier (test, coverage, build) is gated on $CI_PIPELINE_SOURCE == "merge_request_event" — a condition that never holds in a triggered child pipeline, where the source is parent_pipeline. Unit tests and coverage therefore do not run in any generated mono-repo. The fix is for the trigger job to pass PARENT_PIPELINE_SOURCE: $CI_PIPELINE_SOURCE down via trigger:variables: and for the child templates to gate on that as well. This touches all nine CI templates and deserves its own ADR.

  • Supersedes ADR-015's gating decision. ADR-015's other rulings stay in force: the runtime install of mkdocs-monorepo-plugin and the root nav structure. Its mkdocs:1.1.0 pin is advanced to 1.2.0 here, for the git binary the released-version table needs.
  • ADR-012 — mono-repo CI composition; per-artifact pages suppression stays as decided there.
  • BR-022 — mono-repo children use artifact-scoped CI namespacing.
  • specs/guidelines/ci_cd.md, specs/guidelines/git.md.