feat(npm): dist-tags merge, pass-1 validation (S31 Step 13, part 1/3)
Stacked MRs (review/merge bottom-up)
- feat(npm): dist-tags merge, pass-1 validation (... (!2931 - merged) • David Fernandez • 19.5
👈 - feat(npm): packument-merge core (S31 Step 13, p... (!2932 - merged) • David Fernandez • 19.5
- test(npm): merge edge cases and property test (... (!2933 - merged) • David Fernandez • 19.5
Split into 3 stacked MRs to keep each within the review size limit (≈800 ideal). Each part targets the previous (part 1 → main), reviewed and merged bottom-up.
Part 1 of 3 of docs/plans/2026-08-21-s31-npm-virtual.md — Step 13: metadata merge and serve surface
This step's plan text is split into three parts; this stack ships Part 1 only ("the streaming merger") — a pure library merger with no DB, no HTTP, and no context deadline. Parts 2 (collection/fan-out) and 3 (re-point, ETag, HTTP serve) are separate, later steps; Part 2 is currently blocked.
This part's scope:
MergeDistTags, the standalone dist-tags document merge (buffered map, lowest position wins each tag, nolatestsynthesized).- The bounded pass-1 validator shared by both merge entry points: well-formedness, size-cap enforcement, object-shape checks for
versions/dist-tags/time, dist-tag value string checks, and route-awareness between the dist-tags and packument routes. - Fatal-vs-per-position-omission error classification: an
Open()or read failure, or an invalidjsonsafe.Limits, fails the whole call; a malformed or oversized individual input is omitted, letting the read still serve the survivors. - The three
merge_duration_seconds/merge_inputs/integrity_divergence_totalPrometheus vectors end to end (registration,docs/dev/observability.md, the cardinality budget ininternal/metrics/cardinality.go).
Notes:
- This is the first production adopter of
internal/jsonsafe(a boundedjsontextwrapper enforcing depth/key-count limits), which existed unadopted by any format package before this change. docs/specs/S31-npm-virtual.md's metrics table rides in this MR: theoperationlabel is renamed tonpm_virtual_operation(it collided with the OCI proxy's already-pinnedoperationlabel ininternal/metrics/cardinality.go), and a merge-rule row is clarified to state explicitly that descriptive top-level fields are taken as a whole set, not per-field. This is an intentional, deliberate exception to the usual "specs aren't edited by step MRs" convention, made because the collision was only discovered during implementation.- No e2e catalog or
docs/user/change: this is a pure merger with no route wired to it yet (Part 3's job). Conformance tests: n/a, no protocol surface is reachable yet.
Spec: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/specs/S31-npm-virtual.md, "Cross-upstream packument merge" and "Metrics". Plan: https://gitlab.com/gitlab-org/ops/artifact-registry/-/blob/main/docs/plans/2026-08-21-s31-npm-virtual.md, Step 13.
Related to #894 (closed)