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:
- A job named exactly
pagesalways 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 singlepagesjob therefore could not do both, and did the publishing one. - A
changes:rule always evaluates to true in a tag pipeline. The root pipeline'strigger-<artifact>jobs were gated onchanges: ["<artifact>/**"]alone, so taggingpayment-v1.0.0fired 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. - The combined site has no version to display. Every single-repo
pagesjob 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 exactlypages, artifacts inpublic/,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¶
- Split into
docs-build(test) andpages(publish on release tag), with a generated released-version table — chosen. - Keep ADR-015's branch gate and document the publish-on-merge behaviour more prominently.
- Move the existing
pagesjob to a tag rule without adding a build-only tier. - 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 aspecs/directory or anmkdocs.ymlchanged. Keepspublic/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 intosite_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
gitin the mkdocs image. Resolved by baking it in rather than installing it at job runtime (tool_chain.md): the root docs jobs pinmkdocs:1.2.0, the first tag to ship git. The per-artifactpagesjob stays onmkdocs:1.0.0— it builds no version table and needs nothing from the newer tags. docs-buildintroduces the root pipeline's firstmerge_request_eventrule, so GitLab now creates root pipelines for merge requests. Thetrigger-*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.
Links¶
- Supersedes ADR-015's gating decision. ADR-015's other rulings stay in force: the runtime
install of
mkdocs-monorepo-pluginand the root nav structure. Itsmkdocs:1.1.0pin is advanced to1.2.0here, for the git binary the released-version table needs. - ADR-012 — mono-repo CI composition; per-artifact
pagessuppression stays as decided there. - BR-022 — mono-repo children use artifact-scoped CI namespacing.
specs/guidelines/ci_cd.md,specs/guidelines/git.md.