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 ./... and go 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=integration run 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), and TestIntegration_BuildHealthSweepSources_NonNilClients (against real PostgreSQL and Redis: seeds one remote per format, drives an enumerate, resolve, probe round per source, and asserts the row's last_health_checked_at moved: 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)

Edited by Dzmitry (Dima) Meshcharakou

Merge request reports

Loading
Loading