feat(datastore): S04-B rotation-tooling store operations
What
S04-B Plan Step 1 — the rotation-tooling concrete-store operations the operator commands drive, plus the ErrKeyVersionActive sentinel. All compose the existing store helpers (withTxVoid, lockAndCheckShredded, the SQLSTATE mapping, the column projection) rather than adding new machinery:
RetireKeyVersion— the ordered 5-step guarded delete (barrier + namespace-wide shredded check → active-version resolve withErrKeyVersionActive→ row-localshredded_at IS NULLDELETE → unconditional FK-refusal mapping (SQLSTATE 23001 on PostgreSQL 18, 23503 on 17 and earlier) →ErrKeyStillReferenced).ActiveKeyAge— the active key's age by database time (single statement, no host clock).ListLiveKeysNotUnderRootKeyURI— the root-rotation verification read (inequality, page-ceiling clamp, namespace-wide tombstone exclusion).SuccessorKeyCreatedAt— the retirement pre-flight's successor lookup.
Tests
Integration matrix (//go:build integration) covering the full retirement sentinel matrix (shredded/mixed-state, active/above-active, missing/empty, real composite-FK ON DELETE RESTRICT refusal), age against a backdated created_at, the tombstone-excluded verification read + ceiling clamp, and the successor lookup incl. tombstone-skip. Adds testutil.CreateCredentialFKTable.
Covers spec AC 8, 9 and the retire rows of the Error Cases table; the age/verification/successor reads are consumed by AC 3/5/6 through Steps 4 and 8.
E2E: none — operator store ops have no format-client catalog scenario (per the plan's testing strategy).
Related to https://gitlab.com/gitlab-org/ops/artifact-registry/-/work_items/496
Size
The diff exceeds the 500-LOC guideline (~990 changed lines, ~480 of them integration tests). Splitting was considered and declined for the reason the plan records: the four store operations share one FOR UPDATE barrier, error envelope, SQLSTATE mapping switch, and integration fixture set, so extracting the two reads would leave a consumer-less step while duplicating the shared foundation across both MRs.