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.
MavenVirtualHealthStatusReaderanswers aremote.HealthStatusReadercall from the per-request mapWithMavenVirtualHealthStatusescarries, keyed by parentrepositories.id, and falls back to the store on a miss. The context never aliases a caller's live map: the helper storesmaps.Cloneof what it is handed, and a nil map stays nil so the fallback still answers.MavenVirtualRemoteFetcherresolves a remote position's shared*remote.SingleFlightfrom the registry on the firstFetchand 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_totalcounts health reads the map could not answer; it is label-less, declared beside the adapter it meters, and registered inmavenCollectors.
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: thepackage maven_testsuite reaches the counter and the adapters' sentinels through the test-accessor seam.buffered_test.go:TestRegisterMetrics_ExposedViaGathererpins the series set againstmavenCollectors, so the new collector joins itswantSerieslist.
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.idalone becausefk_mvru_upstream_repository_id_repositoriesconstrains 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)