feat(maven): the winner-path wide event (S30 plan: 19a/25)

Summary

Step 19a's wide event, layer 2 of 2: the maven_virtual_resolve record with its positions[] array, over the exclusion-sink, health, and collaborator recording wrappers, emitted from a defer at the resolution entry.

  • The full S30 field set — identity fields, upstream_count, winner fields (absent rather than zero-valued when there is no winner), resolution_outcome, positions, status, maven_code — with upstream_count counting live positions.
  • positions[] attributes a 503 to an upstream when no label can. The four remote collaborators are wrapped as recording adapters, the hosted upstream's miss outcome and the health reader are recorded, and a denied or ineligible position reaches positions[] through the exclusion-audit recording wrapper — the only slice-visible source of those outcomes, since loadAndFilter excludes them before any collaborator call.
  • The deferred emission books every arm an inline emitter would leave silent: the recovered panic, the client cancel, the unclassified disconnect, and the infrastructure arm; the rejections ahead of resolution (the write-verb 405, the malformed-path 400s, the FIPS 422, the rate-limit 429) emit no event.
  • The drain-failure arm records its own verdict on the read's recorder: it resolves after the defer captured the read's verdict, so the record would otherwise report the pre-drain stream.
  • The composition root cannot forget the records: the health reader and the exclusion audit sink each get their recording decorator in composeMavenVirtualResolution (handler.go).

Scope

Layer 2 of 2 of Step 19a of the S30 plan (### Step 19a: winner-path observability), stacked on the families layer until it merges; this MR targets its branch. The merge path's values (Step 19b) are pinned ahead as vocabulary only and land with their producer.

Depends on

  • The families layer (!3014 (merged)) — the recorder's bookings land there; merge that MR first, then this one. Both sit on !3003 (merged).

Plan and spec amendments

Per docs/dev/conventions.md (### Review-driven plan and spec corrections), this MR carries the correction its own review proved:

  • S30's Wide events section renames the event's HTTP status field http_status → status, matching LabKit's fields.HTTPStatusCode standard field the emission keys on.

Size justification

~1,116 reviewable source lines (+2,043 test, +33 docs) against the families layer, past the 500 reviewable-LOC guide. The recorder, its recording wrappers, the emission, and the install sites are one fixture-driven unit — the suites drive every arm through the composed handler (composeMavenVirtualResolution), so cutting the recorder from its emission leaves two MRs whose suites cannot run standalone; the npm mirror's own assembly part shipped over the ceiling for the same reason. Split by file group: virtual_observe.go 920, virtual_read.go 124, handler.go 42, virtual_assembly.go 25, virtual_read_errors.go 5, suites ~2,043, docs ~33.

Spec coverage

Spec: docs/specs/S30-maven-virtual.md (### Wide events). Plan: docs/plans/2026-09-10-s30-maven-virtual.md, ### Step 19a.

Acceptance criteria

Criterion Test
Full spec field set on the event TestServeVirtualRead_EmitsTheFieldSet, TestMavenVirtualWideEvent_HostedWarmHit
503 attributed to an upstream via positions[] TestMavenVirtualWideEvent_UnhealthyRemoteRecordsTheHealthGate
Denied and ineligible positions reach positions[] through the audit-sink wrapper TestMavenVirtualWideEvent_AllDeniedRecordsTheDeniedPosition, TestMavenVirtualWideEvent_DenialAndDataErrorAreDistinct
One read books every family once TestServeVirtualRead_BooksTheFourFamilies
Panic, cancel, disconnect, and abandoned arms all book TestServeVirtualRead_BooksAPanic, TestServeVirtualRead_BooksAnUnclassifiedDisconnect, TestServeVirtualRead_BooksAnAbandonedRead, TestMavenVirtualWideEvent_AbandonedReadStillBooks
The arm's own outcome, not the pre-drain stream TestServeVirtualRead_BooksTheDrainArm, TestServeVirtualRead_BooksTheOutcomeItsArmReached
Rejections ahead of resolution book no event TestServeVirtualRead_RejectionsAheadOfResolutionBookNoEvent
Wrappers forward unchanged; recorder ordering TestMavenVirtualRecorder_WrapperForwardsUnchanged, TestMavenVirtualRecorder_SnapshotOrderIsFirstObservationOrder, TestMavenVirtualExclusionAuditRecorder_ForwardsEveryEvent, TestMavenVirtualHealthStatusRecorder_ForwardsEveryVerdict

ADR coverage

ADR-001 (namespace_id is an event field, never a label), ADR-007 (the emission's composite-FK citation for NamespaceID ownership; positions[].position is the resolver's list index, not the stored column), ADR-008 (every wrapper is a pass-through), ADR-009 (the event is a log record, not an API surface), ADR-012 (operational metrics + log event 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, at the pre-commit hook's full go test -short ./... scope.

User documentation

No docs/user/ surface changes: the event is 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)

Edited by Moaz Khalifa

Merge request reports

Loading
Loading