Skip to content

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 scan after dependency resolution must not silently stop running if the ordering target changes rules independently of scan.

Considered Options

  1. Add needs: [arch-test] to scan in both Java templates (library, service).
  2. Move scan to a later stage that already runs after build.
  3. Accept the residual risk as documented and change nothing further.

Decision Outcome

Option 1.

scan:
  stage: check
  needs: [arch-test]

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 needs entry.

Negative Consequences / Risks

  • scan no longer starts at the same moment as arch-test/lint; it now waits on arch-test's runtime. The check stage's wall-clock time for the slowest path grows by roughly one arch-test run.
  • The very first time arch-test itself 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-test is ever renamed or split, scan's needs has to be updated alongside it — this is now an explicit coupling between two jobs that previously had none.

Rejected alternatives

  • Option 2 (move scan to a later stage) achieves the same ordering guarantee but gives up the pre-check tier's fail-fast property described in ci_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).
  • 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).