Loading
feat(npm): dist-tag PUT/DELETE handler (step 21, 2/2)
Part 2 of 2 of the npm hosted plan — Step 21: dist-tag PUT / DELETE.
🗂️ Stacked MRs
Split into 2 stacked MRs to keep each within the review size limit (~600 ideal reviewable LoC). Each part targets the previous one (part 1 → main); review and merge bottom-up.
- feat(npm): dist-tag datastore write layer (step... (!771 - merged) • Dzmitry (Dima) Meshcharakou • 19.2
- feat(npm): dist-tag PUT/DELETE handler (step 21... (!772 - merged) • Dzmitry (Dima) Meshcharakou • 19.2
👈
📦 What this part adds
Implements the dist-tag write endpoints on DistTagsHandler, consuming the datastore writer/adjusters from part 1/2 (!771 (merged)):
PUTbinds a tag to a version: validates the bare-JSON-string body, rejects semver-shaped tag names, enforces the per-package tag cap on new names before version resolution, then upserts and force-expires the cache in one transaction.DELETEremoves the tag: 204; 404 on a missing or empty tag; 404 on a missing package.- After commit: exactly one packument rebuild is enqueued and the buffered
tags_countis adjusted, both off the request path. - Adds the
tag_name_invalid/tag_not_founderror codes.
ℹ️ Known gap — handler not yet wired into the route table. The route mount stays deferred to Step 23: the handler ships built and tested, but is not yet in the production mux.
✅ Spec coverage
| Acceptance criterion | Test |
|---|---|
| AC 21 — PUT writes the tag, fires one rebuild → 204 | TestDistTagsIntegration_WritePutSetsTag |
| AC 22 — DELETE removes the tag, fires one rebuild → 204 | TestDistTagsIntegration_WriteDelete |
AC 23 — semver-shaped tag → 422 tag_name_invalid |
TestDistTagsIntegration_WritePutTagNameInvalid |
| AC 27 — cache invalidated (one rebuild enqueued post-commit) | TestDistTagsIntegration_WritePutSetsTag, …WriteDelete |
AC 31 — new tag at cap → 422 tag_count_exceeded (before version check) |
TestDistTagsIntegration_WritePutTagCountExceeded |
AC 34 — missing/soft-deleted package → 404 package_not_found |
TestDistTagsIntegration_WritePackageNotFound |
AC 35 — missing/soft-deleted version → 404 version_not_found |
TestDistTagsIntegration_WritePutVersionNotFound |
AC 36 — tag missing on DELETE → 404 tag_not_found |
TestDistTagsIntegration_WriteDelete |
| AC 58 — write refused without a Resolution | TestDistTagsIntegration_WriteMissingResolution |
Malformed PUT body → 400 bad_request |
TestDistTagsIntegration_WritePutBodyInvalid |
| Writer error → 500, no post-commit events | TestDistTagsIntegration_WriteWriterError |
| Unwired write deps → 500 (not a nil panic) | TestDistTagsIntegration_WriteNotWired |
Empty tag on DELETE → 404 tag_not_found (no transaction) |
TestDistTagsIntegration_WriteDeleteEmptyTag |
📚 References
- Plan: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/plans/2026-05-11-npm-hosted.md
- Spec: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/specs/S11-npm-hosted.md
Related to #139 (closed)
Edited by Dzmitry (Dima) Meshcharakou