feat(npm): wire route table to real handlers (S11 Step 26)
Part 2 of 2 of the npm hosted plan — Step 26: wire the format route table to the real handlers.
🗂️ Stacked MRs
Split into 2 stacked MRs to reduce review load. This part targets part 1's branch (dm/npm-step26-dispatcher), so its diff is only the wiring slice — retarget to main once part 1 merges. Review and merge bottom-up.
- feat(npm): dispatcher + prerequisites for real ... (!835 - merged) • Dzmitry (Dima) Meshcharakou • 19.2
- feat(npm): wire route table to real handlers (S... (!836 - merged) • Dzmitry (Dima) Meshcharakou • 19.2
👈
📦 What this part adds
Flips the npm mount from the interim 501 base dispatcher to the real per-action handlers — the Step 26 behavior change.
wireNPM/buildNpmDispatcher—wireNPMnow takes the sharedBlobStoreand the npm config;buildNpmDispatcherconstructs the download, packument, dist-tags, publish, deprecate, and unpublish handlers from the production datastore stores and threadsNewDispatchHandlerWithHandlers.- Composition-root adapters — the dual-store
LastDownloadedBumper(bumps both the version and the package off the request path) and thePublishPrechecker(package, version, and tag consumer interfaces) answering the publish pre-check's three reads, keyset-iterating the tag list for the new-tags-only cap check.public_registry_urlis threaded into the packument handler and the cache rebuilder. - Metrics and pairing guard —
npm.RegisterMetricsis called besideoci's at the composition root, so thegitlab_artifact_registry_npm_*series register at boot regardless of handler wiring;NewPublishHandlernow panics when a blob store is wired without a committer, so the interim-200 fake-success becomes a boot-time error rather than a silent runtime one. - Godoc — the deprecate, dist-tags, and unpublish handlers now document that they are wired, and the deprecate body-cap comment attributes the cap to the production dispatcher.
Test coverage: the production entry-point signature, nil-dependency boot guards, the bumper and prechecker adapter delegation, the publish/deprecate split through BodySizeMiddleware, and a boot smoke test driving every route against a real database.
⚠️ Known gap
Publish is wired with QuotaChecker left nil (admits every publish) because the per-request npm quota subsystem is not built yet. This is a known, tracked gap - not a permanent decision - followed up in #260. Surfaced by the automated AppSec review.
📚 References
- Plan: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/plans/2026-05-11-npm-hosted.md (Step 26)
- Spec: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/specs/S11-npm-hosted.md
Related to #238 (closed)