feat(maven): memo adapters and the health reader (S30 plan: 10a/25)

Summary

Implements Step 10a of the S30 plan: the two per-request adapters that will keep one virtual Maven resolution's statement count fixed once the composition root wires them, and the counter that meters the health fallback.

  • MavenVirtualHealthStatusReader answers a remote.HealthStatusReader call from the per-request map WithMavenVirtualHealthStatuses carries, keyed by parent repositories.id, and falls back to the store on a miss. The context never aliases a caller's live map: the helper stores maps.Clone of what it is handed, and a nil map stays nil so the fallback still answers.
  • MavenVirtualRemoteFetcher resolves a remote position's shared *remote.SingleFlight from the registry on the first Fetch and holds it for the request, so a denied, unhealthy, or losing position never takes the registry's lock. Maven's fetches are raw, so the adapter carries neither of npm's two base-URL concerns.
  • virtual_health_memo_misses_total counts health reads the map could not answer; it is label-less, declared beside the adapter it meters, and registered in mavenCollectors.

Depends on: Steps 4, 5, and 6, all merged on main.

References: S30 spec · S30 plan (see "Step 10a: memo adapters and the health reader")

Reviewable LOC: 1237 (1228 insertions, 9 deletions) — past the 500 ceiling. By file group: tests 814 (virtual_memo_test.go 792, export_test.go 21, buffered_test.go 1); production Go 202 (virtual_memo.go 199, metrics.go 3); the contract sidecar 212. A split would not help: the two adapters and their counter are one seam pair, and the plan already cut Steps 10a/10b/10c finer than npm's equivalents. The suite mirrors npm's virtual_memo_test.go per the step's Tests entry, and the sidecar carries the analysis the plan books here.

Scope

The diff carries two paths beyond Step 10a's Files entry. Both are required ripples, confirmed by the pre-push coherence check:

  • export_test.go: the package maven_test suite reaches the counter and the adapters' sentinels through the test-accessor seam.
  • buffered_test.go: TestRegisterMetrics_ExposedViaGatherer pins the series set against mavenCollectors, so the new collector joins its wantSeries list.

Counter outside the spec's table

gitlab_artifact_registry_maven_virtual_health_memo_misses_total is a seventh maven_virtual_* metric against the spec's six-row Metrics table. This is intentional and declared by the plan ("mirroring npm's out-of-spec counter treatment"); it mirrors npm_virtual_health_memo_misses_total, which shipped the same way. The observability catalog row is booked for Step 19a and is not edited here.

Governing ADRs

  • ADR-007 (database schema) — the map is keyed on the parent repositories.id alone because fk_mvru_upstream_repository_id_repositories constrains every upstream to the virtual repository's own namespace; the store fallback forwards both keys. No schema or query change.
  • ADR-008 (content-addressable storage) — the fetcher only relays to the shared flight; cache rows and blob sessions stay the flight's, one row per remote binding, so a virtual fill writes the same row the standalone route serves.
  • ADR-012 (usage data) — the new counter is an operational Prometheus metric, outside the usage-data decision; no usage event is minted here.

Testing

  • go test -count=1 ./internal/format/maven/ green.
  • The suite mirrors npm's virtual_memo_test.go: the 20-position no-statement case, the fallback meter, the map-copy and nil-fallback cases, lazy flight acquisition, the shared-flight composition, and the constructor's five wiring refusals (the fifth, an empty upstream URL, added in round 2).
  • A five-category pre-push coherence check ran on this branch: no blockers, advisories only. Two advisory categories are dispositioned under Scope and the counter section above; the third, a false wiring claim in the contract sidecar carried over from npm, is fixed by docs(maven): correct the memo contract's fall-through callers.

e2e scenarios

No scenario added or affected: the adapters are unwired until the composition root (Step 15) constructs them, and no route serves virtual reads yet.

User documentation

No user-facing surface changes: both adapters are internal seams, so docs/user/** is unaffected by this MR.

Related to #289 (closed)

Edited by Moaz Khalifa

Merge request reports

Loading
Loading