feat(maven): the winner-path virtual metric families (S30 plan: 19a/25)
Summary
Step 19a's winner-path metric families, layer 1 of 2: the four collectors — maven_virtual_resolutions_total (maven_virtual_operation, resolution_outcome), maven_virtual_probes_total (maven_virtual_operation), maven_virtual_upstream_requests_total (leg), and maven_virtual_positions_consulted (count-shaped buckets) — with the label and value vocabularies they audit against, the cardinality label budget, and the catalog rows.
- Bounded low-cardinality labels only. No namespace id, no coordinate, no upstream hostname on a label; the three closed sets (3 operations, 3 legs, 17 outcomes) are pinned with the constants, and the derived label-set tests hold against the budget rows.
positions_consultedcounts every position a resolution consulted — every position it walked, the ones the eligibility gate excluded and the ones health marked unhealthy included — so zero means an empty upstream list rather than an all-excluded one, and a consistently wide list is visible however its gates answer. Count-shaped buckets{0, 1, 2, 5, 10, 20}mirrorpageSizeBuckets(internal/format/oci/metrics.go) anddriftRowBuckets(internal/accounting/metrics.go), because thepositionCHECK caps the list at 20.legis the second cross-slice union seam, shared bare with npm's upstream-request counter,revalidatepinned ahead of its producer (the merge path).probes_totalis not optional: the spec's follow-up accepts the cold-cache probe-amplification risk on the condition that this counter exists.
The wide event, its recorder, wrappers, and install sites land in the event layer stacked on this MR.
Scope
Layer 1 of 2 of Step 19a of the S30 plan (### Step 19a: winner-path observability), stacked on !3003 (merged) until that MR merges; this MR targets its branch.
This MR also carries the test-author agent's comment-caps check step (.claude/agents/test-author.md): the suites this step ships are the first that check gates.
Depends on
- !3003 (merged) — Step 17's handler; merge it first, then this MR, then the event layer.
Plan and spec amendments
Per docs/dev/conventions.md (### Review-driven plan and spec corrections), this MR carries the corrections its own review proved:
- Step 19a's Files entry re-records the install sites the review found: the recording decorators wire in
handler.go'scomposeMavenVirtualResolution,virtual_read.go, andvirtual_assembly.go— notcmd/artifact-registry/wire_maven.go— andvirtual_metrics.md(Create) carries the package page. Thepositions_consultedrange reads 0-to-20 (0 for an empty list). - S30's metric row for
maven_virtual_upstream_requests_totalclarifies the leg vocabulary:probeandfetchare the winner path's Phase 2 and Phase 3 legs, and a collection position booksfetch/revalidate,revalidatepinned ahead of the merge path that first books it. The wide event's field-name correction rides the event layer.
Size
~324 reviewable source lines (+270 test, +113 docs) against the !3003 (merged) tip — under the 500-LOC guide.
Spec coverage
Spec: docs/specs/S30-maven-virtual.md (### Observability). Plan: docs/plans/2026-09-10-s30-maven-virtual.md, ### Step 19a.
Acceptance criteria
| Criterion | Test |
|---|---|
| Families carry bounded low-cardinality labels only; closed sets bounded; fail-closed drop | TestMavenVirtualFamilies_CarryTheirLabelSets, TestMavenVirtualVocabularies_AreClosedAndBounded, TestBookMavenVirtualResolveMetrics_DropsValuesOutsideTheClosedSets |
| Audit passes with every declared value emitted | TestRegisterMetrics_PassesCardinalityAuditWithTheVirtualFamilies |
| Consulted buckets count-shaped | TestMavenVirtualPositionBuckets_AreCountShaped |
| Derived label-set tests | internal/metrics/cardinality_virtual_test.go |
ADR coverage
ADR-001 (namespace id rides the event as a field, never a label), ADR-004 (the 20-upstream cap consumed, no slice-local limiter), ADR-012 (operational metrics sit outside the usage-data decision) — checked against the plan, spec, and cited ADRs by the /validate-step pass; no conflicts.
Tests
go build ./..., go test -short ./internal/format/maven/... ./internal/metrics/..., golangci-lint run, and the comment-caps gate pass at this layer standalone, at the pre-commit hook's full go test -short ./... scope.
User documentation
No docs/user/ surface changes: the families are operator-facing and catalogued in docs/dev/observability.md; the end-of-plan docs pass owns the customer-facing write-up.
No e2e scenario added: the virtual read's end-to-end scenarios are Step 20's, the step that mounts the harness the catalog's virtual rows need.
Related to #289 (closed)