ADR-019: The Maven Source Scan Needs arch-test Instead of Racing It¶
| Field | Value |
|---|---|
| Date | 2026-07-29 |
| Status | Accepted |
| Deciders | Holger Zahnleiter |
| Supersedes | — |
| Superseded by | — |
Context and Problem Statement¶
A generated mono-repo child (hotel-booking) hit the same failure ADR-018 already documents:
FATAL Error remote Maven repository returned 429 Too Many Requests for
https://repo.maven.apache.org/maven2/org/springframework/data/spring-data-bom/2026.0.0/spring-data-bom-2026.0.0.pom.
Retry-After: 1800.
ADR-018 accepted this as a residual risk, scoped to "a brand-new project's first pipeline." This occurrence was neither: an established project, mid-lifecycle, on an ordinary dependency bump. The scoping was wrong, not just optimistic.
The check stage runs scan, arch-test, lint, test and coverage in parallel — that is
what a GitLab stage means absent a needs entry. default: cache: restores .m2/repository at
the start of every job and saves it back at the end of every job; nothing is shared between jobs
while they run. So whichever job resolves a dependency version for the first time, every other job
in the same stage that needs that same version is resolving it too, independently, over the network,
at the same moment — regardless of how many times this exact pipeline has run before. A cold cache
is not a first-pipeline event; it is a first-time-this-version event, and dependency bumps produce
those routinely.
scan and arch-test carry the identical rule set (if: $CI_COMMIT_TAG → never, else
on_success), so arch-test runs in every pipeline scan does. arch-test runs mvn and
resolves the full dependency graph, spring-data-bom included, before scan needs it to.
Decision Drivers¶
- ADR-018's own risk acceptance turned out to be too narrow; the residual exposure recurs on every dependency bump, not only once per project.
- The fix must not touch ADR-018's actual decision (Trivy pointed at the cache,
--skip-dirs, no--offline-scan) — that decision is still correct on its own terms. - Whatever orders
scanafter dependency resolution must not silently stop running if the ordering target changes rules independently ofscan.
Considered Options¶
- Add
needs: [arch-test]toscanin both Java templates (library,service). - Move
scanto a later stage that already runs afterbuild. - Accept the residual risk as documented and change nothing further.
Decision Outcome¶
Option 1.
needs pulls scan out of check's default in-stage parallelism without moving it to another
stage or giving up the pre-check tier's fail-fast property for the other checks in check. Because
arch-test shares scan's exact rule set, arch-test is guaranteed to run in every pipeline where
scan does — there is no rule combination where scan runs and waits on a needs target that
never starts. arch-test's own mvn invocation resolves the dependency graph and writes it back to
the cache before scan starts, so scan reads a cache that is warm for this pipeline run, not
only for a previous one.
This was deliberately not built as a defensive check inside the scan job itself (e.g. running
mvn dependency:go-offline in before_script) — that would need a Maven binary in the Trivy image,
which is exactly the "install tools in pipeline jobs" pattern ci_cd.md and tool_chain.md forbid.
Depending on a job that already does the resolution, in an image that already has Maven, is the
one option that adds no tooling anywhere.
Positive Consequences¶
- The scan job now sees a dependency graph resolved in the same pipeline run, not a previous one — closing the gap ADR-018 left open for the common case (a version bump), not only the rare one (a brand-new project).
- No new tool, image, or script — the fix is a single
needsentry.
Negative Consequences / Risks¶
scanno longer starts at the same moment asarch-test/lint; it now waits onarch-test's runtime. Thecheckstage's wall-clock time for the slowest path grows by roughly onearch-testrun.- The very first time
arch-testitself resolves a brand-new dependency version, it can still hit Maven Central and — in principle — still receive a 429. This ADR removes the parallel race; it does not add a retry or backoff around Maven Central itself. A single job hitting the rate limit alone, rather than several jobs hitting it at the same moment, is accepted as sufficiently rare and cheap to retry. - If
arch-testis ever renamed or split,scan'sneedshas to be updated alongside it — this is now an explicit coupling between two jobs that previously had none.
Rejected alternatives¶
- Option 2 (move
scanto a later stage) achieves the same ordering guarantee but gives up the pre-check tier's fail-fast property described inci_cd.md("Pipeline stages in order: Pre-Check → Build → Test → Package") for a scan that, per ADR-018, is meant to gate early. - Option 3 was rejected because the failure this ADR responds to is exactly the scenario ADR-018 said would be rare, recurring instead on an ordinary, expected event (a dependency bump).
Links¶
- ADR-018 — the sibling decision this one refines; that ADR's Option 1 (point Trivy at the cache, exclude it from the walk) stands unchanged.
specs/domain/features/gitlab_ci.feature— "The source scan knows about the dependency cache the pipeline itself creates" (new assertion: the Maven scan needs a job that resolves dependencies first).