Skip to content

ADR-013: Mono-Repo Root-Level Specs and Cross-Artifact Contract Referencing

Field Value
Date 2026-07-23
Status Accepted
Deciders Holger Zahnleiter
Supersedes
Superseded by

Context and Problem Statement

ADR-011 and ADR-012 generate each .mono-repo.yaml entry exactly as repo init would generate it standalone — full specs/ tree, full CI pipeline — plus one small root-only file (.gitlab-ci.yml, ADR-012). Two questions came up before populating the demo mono-repo that neither ADR answers:

  1. Where does content that spans more than one artifact live? Every generated artifact already gets its own specs/architecture/ADRs/ and specs/domain/business_rules.md — but a decision like "which technology orchestrates a saga across services" or a rule like "payment is always the last step of the saga" doesn't belong to any single artifact. Today there is no place for this at all; it currently lives informally in the (gitignored, not-checked-in) mono_repo_spec.md.
  2. How does one artifact reference another artifact's API contract? Every generated artifact already gets specs/architecture/APIs/{provided,consumed}/README.md (internal/templating/general/files/specs/architecture/APIs/{provided,consumed}/README.md.tmpl — currently placeholder content in every generated repo). The consumed/ convention already distinguishes copying a file from referencing a URL "to avoid drift" — but that convention was designed for APIs that live in a genuinely different repository. A mono-repo sibling is not external: it shares one Git commit with its consumer, so referencing it by URL (or copying it) discards a same-repo guarantee the URL convention was never able to offer in the first place.

Decision Drivers

  • Stay consistent with ADR-011's core philosophy: no shared master that risks silently overwriting or discarding an artifact's own data (the same reasoning that keeps .mono-repo.yaml write-once and each .repo.yaml independently authoritative applies equally to specs — an artifact's own specs/ must stay its own).
  • Don't invent a new kind of generator-managed artifact (a "specs-only" project) just to hold cross-artifact content — reuse the root-file precedent ADR-012 already established (writeMonoRepoRootGitlabCI) instead.
  • Keep the addition small: only scaffold what is genuinely cross-artifact, not a second copy of everything every artifact already gets (guidelines, use cases, API directories of its own).
  • Contract referencing must degrade gracefully to today's behavior for artifacts consuming a genuinely external API — this is an additional convention, not a replacement.

Considered Options

Root-level specs scaffold:

  1. Generator scaffolds a small root specs/ skeleton at repo init time, containing only architecture/ADRs/, domain/business_rules.md, and an index.md tying them together. (chosen)
  2. Leave it to hand-maintained documentation — e.g. a cleaned-up, checked-in version of mono_repo_spec.md, with no generator involvement.

Cross-artifact contract referencing:

  1. Extend consumed/README.md.tmpl with a mono-repo-local *.path.txt convention — a relative path to a sibling artifact's specs/architecture/APIs/provided/... file, alongside the existing *.url.txt convention for genuinely external APIs. (chosen)
  2. Reuse the existing *.url.txt convention unchanged — treat a mono-repo sibling exactly like any external API.
  3. Centralized root-level contracts/ directory holding every artifact's canonical spec, referenced by all consumers.

Decision Outcome

Chosen: Option 1 (generator-scaffolded root specs) + Option 3 (relative-path contract reference), kept deliberately small.

Root-level specs scaffold

repo init, when run from a .mono-repo.yaml manifest, additionally writes:

specs/
├── index.md
├── architecture/
│   └── ADRs/
│       └── README.md
└── domain/
    └── business_rules.md

— alongside, not instead of, each entry's own full specs/ tree in its own subdirectory. New templates live under internal/templating/general/files/mono_repo_root/specs/... and are copied by a new general.WriteMonoRepoRootSpecs function, called once, unconditionally, from cmd/init.go's initFromMonoRepoConfig — unlike the GitLab-only writeMonoRepoRootGitlabCI, this has no CI-provider dependency, so it always runs for any mono-repo.

Deliberately not included in the root skeleton:

  • specs/guidelines/* — each artifact already gets its own language-appropriate guideline set; a root copy would be redundant.
  • specs/architecture/APIs/* — the mono-repo root has no API surface of its own.
  • specs/domain/use_cases/ and specs/domain/features/ — the one genuinely cross-artifact use case in a typical saga demo (e.g. "book a trip") is naturally owned by whichever artifact implements the orchestration, so it lives in that artifact's own specs/domain/use_cases/, not at root. Revisit only if a use case shows up that doesn't fit any single artifact.
  • specs/architecture/system_context.md — each artifact's own system_context already covers its own boundary; a root-level system-of-systems diagram can be added later without breaking anything, but isn't required to make a handful of artifacts understandable.

Rejected Option 2 (hand-maintained only) because the content this scaffold holds — cross-artifact architecture decisions and invariants — degrades the same way undocumented decisions always do without a fixed home; the generator already has a working precedent (ADR-012's root CI file) for writing exactly this kind of root-only content cheaply.

Cross-artifact contract referencing

specs/architecture/APIs/consumed/README.md.tmpl gains a *.path.txt row alongside the existing *.url.txt one: a relative path from the consumer's own consumed/ directory to the producer sibling's specs/architecture/APIs/provided/... file (e.g. ../../../../flight-booking/specs/architecture/APIs/provided/openapi.yaml). Because both files live in the same repository and the same commit, this reference cannot drift the way a URL reference to an external repository can — the whole point of the existing *.url.txt "prefer a URL to avoid drift" guidance was to manage a staleness risk that same-repo co-location removes structurally. specs/guidelines/api_design.md.tmpl gains one line recommending the relative-path form whenever the consumed API's producer is a sibling artifact in the same mono-repo.

This is documentation and convention only — no generator code enforces, resolves, or validates the reference. Neither provided/ nor consumed/ has real content in any artifact today (both remain placeholder-only, per their existing templates); this ADR only makes sure the convention is in place before an artifact's spec actually needs to reference a sibling's.

Rejected Option 4 (reuse the URL convention unchanged) because it forgoes a drift-safety property same-repo co-location provides for free. Rejected Option 5 (centralized contracts/ directory) because it reintroduces the shared-master duplication hazard ADR-011 already rejected for .mono-repo.yaml/.repo.yaml, and because it would require inventing a new artifact type this generator has no other use for.

Positive Consequences

  • Cross-artifact architectural decisions and invariants get a fixed, discoverable home instead of living only in an informal, gitignored planning document.
  • Contract referencing between mono-repo siblings becomes structurally drift-proof, an improvement over what's possible for a genuinely external dependency.
  • No new generator artifact type, no change to how any single entry is generated.

Negative Consequences / Risks

  • A relative-path reference is only valid as long as both artifacts keep their relative positions under the mono-repo root; renaming an artifact's subdirectory would require updating any *.path.txt files that reference it. Accepted: the same is already true of any relative path in a monorepo, and mono-repo entries are already refused-to-rename by construction (BR-021 refuses to regenerate over an existing subdirectory).
  • The root specs/architecture/ADRs/ and domain/business_rules.md start empty; nothing enforces that a genuinely cross-artifact decision actually gets written there instead of duplicated into one artifact's own specs by habit. Same discipline gap that already exists for every other guideline in this generator's output — not new to this ADR.

Pros and Cons of the Options

Root scaffold Option 1 — Generator-scaffolded (chosen)

  • Pro: cheap, reuses the ADR-012 root-file precedent.
  • Pro: guaranteed to exist from the first repo init, not dependent on someone remembering to write it by hand.
  • Con: starts empty — provides a home, not the content itself.

Root scaffold Option 2 — Hand-maintained only

  • Pro: zero generator code.
  • Con: exactly the gap this ADR closes — cross-artifact decisions have no fixed, discoverable home in the generated output.

Contract referencing Option 3 — Relative-path convention (chosen)

  • Pro: structurally drift-proof for same-repo siblings.
  • Pro: additive — existing *.url.txt convention untouched for genuinely external APIs.
  • Con: breaks if a referenced artifact's subdirectory is renamed or removed; no automated validation today.

Contract referencing Option 4 — Reuse URL convention unchanged

  • Pro: no new convention to document.
  • Con: forgoes the drift-safety property same-repo co-location provides for free.

Contract referencing Option 5 — Centralized contracts/ directory

  • Pro: one place to look for every artifact's contract.
  • Con: reintroduces the shared-master duplication hazard ADR-011 rejected for .mono-repo.yaml/.repo.yaml.
  • Con: requires a new artifact type this generator has no other use for.
  • specs/architecture/ADRs/ADR-011-mono_repo_manifest.md — the .mono-repo.yaml/.repo.yaml independence philosophy this decision stays consistent with.
  • specs/architecture/ADRs/ADR-012-mono_repo_ci_composition.md — the root-file precedent (writeMonoRepoRootGitlabCI) this decision reuses for root-level specs.
  • internal/templating/general/files/specs/architecture/APIs/consumed/README.md.tmpl — gains the *.path.txt convention.
  • internal/templating/general/files/specs/guidelines/api_design.md.tmpl — gains the mono-repo-sibling recommendation.
  • internal/templating/general/files/mono_repo_root/specs/... — the new root-only templates.
  • specs/domain/business_rules.md (BR-024) — the root specs scaffold's testable business rule.
  • specs/domain/use_cases/UC005-init_mono_repo.md, specs/domain/features/UC005-init_mono_repo.feature — behavior specification.