feat(cmd): wire npm into the scheduled upstream-health sweep
🎯 What this MR does
Wires npm into the scheduled upstream-health sweep. buildHealthSweepSources in cmd/artifact-registry/wire_remote.go now builds an npm remote.HealthSweepSource beside Maven's, so the sweep probes npm remotes and refreshes their durable health columns. Until this lands the aggregate holds no npm entry, so an npm remote's stored status is never refreshed and a remote that went down keeps reporting whatever it last reported.
Last step of the 5-MR stack for Step 9 of the npm remote plan. !1479 (merged), !1481 (merged), !1482 (merged) and !1483 (merged) have all merged, so this now targets main directly.
| Step | MR | What it delivers |
|---|---|---|
| 0 | !1479 (merged) | Shared refsWithUsableURL enumeration helper |
| 1 | !1481 (merged) | npm datastore health seams |
| 2a | !1482 (merged) | npmremote.Target + UpstreamRequestBuilder |
| 2b | !1483 (merged) | npmremote.CacheStore + health-sweep glue |
| 3 · this MR | Composition root: npm's sweep source |
🧩 The pieces
1️⃣ npm's sweep source
buildNpmHealthSweepSource composes the npm remote repository store as all three npm-side seams (the enumerator, the health reader/writer pair behind the monitor, and the per-remote row read behind npmremote's target resolver), plus a remote.HealthMonitor over the shared upstream doer and a cache-purpose Redis failure counter. npmremote.NewHealthSweepSource seals the triple. npm contributes no format-specific health path, so each probe targets the remote's configured base URL.
The store takes the production audit sink (remoteAuditLogSink) for the same reason buildNpmResolver's construction takes it: the constructor requires a sink for the credential-writing methods the same type carries, and a stub here would be the construction a later credential-writing consumer inherits an inert trail from. buildNpmResolver's doc comment claimed to be the store's only production construction, which is no longer true, so it now names both.
2️⃣ Per-format skip when a source fails to build
buildHealthSweepSources used to return a nil aggregate when Maven's source failed, and its comment left open whether "a second source landing here" should empty both. It should not: each format's build failure is logged and skipped on its own, and the other format's source still reaches the worker. Both builders reject the same thing today (a probe policy the counter or the monitor refuses), so a broken policy still skips both, and both skips are now reported instead of maven's alone. An operator told about maven alone would read the npm sweep as idle rather than misconfigured.
3️⃣ Docs
docs/dev/observability.md's remote repository health transition row credited the Maven monitor as the record's only emitter. It now names npm's too, since the second production emitter arrives here.
🔁 Rebase note
The earlier revision of this branch also added a remoteHealthAuditLogSink, a lazily-read source aggregate with a registerRemotePeriodic test seam, and edits to logCredentialAuditSuppression, internal/remote/healthjob.go and docs/dev/background-jobs.md. All of that landed on main in another shape while this MR sat: remoteAuditLogSink already implements remote.HealthAuditSink, Maven's source is already wired, and the aggregate is now built eagerly inside wireRemote. This revision drops those pieces and keeps only what npm still needs, so the diff is 5 files.
✅ Verification
go build ./...andgo test ./...: clean.go vet -tags=integration ./cmd/artifact-registry/: clean.golangci-lint run --max-same-issues=0 --max-issues-per-linter=0 ./cmd/artifact-registry/...: 0 issues. The--build-tags=integrationrun reports 10 findings, all pre-existing in files this MR does not touch (wire_npm_boot_integration_test.go,wire_npm_observability_integration_test.go,wire_oci_boot_integration_test.go,wire_root_dispatcher_boot_integration_test.go).- Tests:
TestBuildHealthSweepSources_ContributesMavenAndNpm(new: both formats contribute, with their own enumerator and resolver types),TestBuildHealthSweepSources_SourceBuildFailureSkipsAndLogs(now asserts both skips are reported), andTestIntegration_BuildHealthSweepSources_NonNilClients(against real PostgreSQL and Redis: seeds one remote per format, drives an enumerate, resolve, probe round per source, and asserts the row'slast_health_checked_atmoved: passing).
🧪 E2E scenario impact
None: the health sweep is an off-request-path scheduled job. No HTTP handler consumes npm remote health status yet.
Related to #346 (closed)