feat(maven): remote repository store finder + health seams (S14 Step 6)

What

Adds the Maven remote-repository store — the row store over maven_remote_repositories — as Step 6 of the S14 Maven remote vertical slice. Two capabilities in one store type, in its own file alongside the hosted Store per the package's SQL-ownership convention:

  1. FindRemoteRepository resolves the remote row bound to the parent repositories.id a kind=2 Resolution already carries, returning the row id, url, both validity-hour columns, snapshot_metadata_always_revalidate, and the health columns. This is the finder the read-path kind-dispatch seam (Step 10) attaches to Resolution, so no handler needs a second round-trip.
  2. HealthStatus / SetHealthStatus implement S13's remote.HealthStatusReader and remote.HealthStatusWriter over last_health_status / last_health_checked_at, keyed by parent repositories.id, so S13's on-demand and scheduled probes drive Maven health without knowing the table.

No HTTP surface and no behavior change reachable by a client yet: nothing calls the finder until Step 10 wires the kind-dispatch seam, and no probe calls the health seams until S13's probers land.

The store and the seams

  • Jet with explicit columns, LIMIT(1), qrm.ErrNoRowsdatastore.ErrNotFound; every other DB error stays itself, so a transient failure is never misreported as a miss.
  • The finder pins namespace_id on both partitioned tables (maven_remote_repositories and repositories, each PARTITION BY HASH(namespace_id) × 64) and gates on format, kind=2, and repositories.soft_deleted_at IS NULL, resolving through the table's UNIQUE (namespace_id, repository_id) index.
  • The credential columns stay unselected, so the row carrier can never surface them; the credential store is Step 7's seam.
  • Compile-time assertions anchor the store against both S13 health seams, so signature drift fails at the impl site rather than at probe wire-up.
  • Argument-guard sentinels (errMavenRemoteRepositoryNilContext / ZeroNamespace / ZeroRepository) reject a nil ctx or zero-UUID key before any query, per database-query-patterns.md and the mirrored npm sibling.

Tests

internal/datastore/maven_remote_repositories_integration_test.go (integration tag; the datastore package's testcontainers database). Every acceptance clause has a named asserting test:

Acceptance clause Test
Finder returns the row and every remote field for a kind=2 repository TestMavenRemoteRepositoryStore_FindRemoteRepository/resolves the remote row and returns every remote field for a kind=2 repository (non-default value in every column, whole-struct diff, microsecond timestamp round-trip)
ErrNotFound for a hosted, virtual, missing, or soft-deleted parent .../returns ErrNotFound when no live remote row matches the parent — seven isolation subtests: hosted parent, virtual parent with a remote binding, wrong-format parent, missing remote row, missing parent, soft-deleted parent, parent in a different namespace
Health write round-trips through the reader with status and timestamp TestMavenRemoteRepositoryStore_SetHealthStatus/a health write round-trips the status and timestamp through the reader (all three taxonomy values), plus overwrites a previous status and timestamp; the timestamp half is pinned by direct column read since the seam returns only the status
Unprobed row reads back remote.HealthUnknown TestMavenRemoteRepositoryStore_HealthStatus/an unprobed row reads back HealthUnknown, plus the finder's reads back an unprobed row with the create-time defaults
Compile-time assertions anchor both health seams var _ remote.HealthStatusReader / HealthStatusWriter = (*MavenRemoteRepositoryStore)(nil) in internal/datastore/maven_remote_repositories.go

Guarding the matrix: a transient-DB-failure subtest on each method asserts the error is never misreported as ErrNotFound; the writer's no-silent-success rule is asserted (ErrNotFound when the locked SELECT ... FOR UPDATE finds no row; a lost race returns applied=false with a nil error); cross-remote isolation is asserted across the hash partition boundary; and TestMavenRemoteRepositoryStore_FindRemoteRepository_PrunesToOnePartition EXPLAINs the exact statement builder and asserts both tables prune to one partition. The suite was diffed subtest-by-subtest against the npm remote finder suite for dropped coverage: every npm subtest scoped to the repositories table has a counterpart here (the two credential subtests are Step 7-owned), and every enumerated kind value appears as a positive or negative hit.

MR size

961 reviewable lines, over the 500 LOC ceiling in the development model, which asks for a split or a justification here.

Justification: the production store is 299 lines — within the plan's ~300 estimate. The remaining 662 are the integration suite the plan template requires to ship in the same step as the code it covers, so no split is available that does not separate a store from its tests. The suite's size is the enumerated matrices the guardrails demand: seven not-found isolations (one per gate value), all three health-taxonomy round-trips, the transient-error and isolation guards, and the EXPLAIN pruning harness.

End-to-end scenario catalogs

No scenario added or invalidated. docs/testing/ holds no Maven catalog yet — authoring the first one is a separate docs concern — and this step ships no request path, so there is no observable behavior to cover. Step 18's hermetic proxy harness is the automated coverage for the S14 read paths.

Conformance

Not applicable: this step implements no Maven protocol behavior — it is a row store with no HTTP surface. Conformance runs against the read paths landing in Steps 11 and 14 through 16.

Notes for reviewers

  • The health seams key on namespace_id plus the parent repositories.id: the S13 seam contract widened on main (!1220 (merged)) to carry namespace_id, so both health queries pin the partition key and prune to one partition through the existing UNIQUE (namespace_id, repository_id) index. The earlier 64-partition Append deviation and its (repository_id) index escape hatch are gone; the EXPLAIN evidence below shows every statement pruning to a single partition.
  • SetHealthStatus is a compare-and-set on the probe timestamp, per the updated HealthStatusWriter contract: it locks the row (SELECT ... FOR UPDATE inside RunInTx), applies only when the stored last_health_checked_at is NULL or older than the incoming checkedAt, and reports (previous, applied, error). A probe that loses the race neither overwrites a newer status nor moves the timestamp backwards — atomically, without an orchestrator serializing probes.
  • The finder reads snapshot_metadata_always_revalidate, which is in the spec and Step 1's merged migration but pending the same ADR-007 amendment as the tmp_plaintext_* columns — the divergence Step 1's MR (!1120 (merged)) already records.
  • database/sql appears only in the test file (fixture seeding), which the no-direct-db depguard rule exempts; production code imports none.

Related to #286 (closed)

Database Review Evidence

Queries

Note

Plans are from EXPLAIN (ANALYZE, BUFFERS) against an ephemeral PostgreSQL 17 container (matching GL_PG_CURR_VERSION from .gitlab-ci-other-versions.yml), with synthesized seed data rolled back per query and the container torn down at the end of the run. Numbers reflect moderate cardinality and do not capture production-scale effects. See Database review evidence for seed sizing, methodology, and the anomalies the skill flags. Expand each row's details for the seed shape, rendered SQL, bound args, and raw plan.

Method Plan node Index Rows (plan / actual) Cost Time Buffers (hit / read) Partitions
datastore.MavenRemoteRepositoryStore.FindRemoteRepository Limit over Nested Loop maven_remote_repositories_p62_namespace_id_repository_id_idx, repositories_p62_pkey 1 / 1 16.62 0.022ms 6 / 0 2 (1 per table)
datastore.MavenRemoteRepositoryStore.HealthStatus Limit over Index Scan maven_remote_repositories_p62_namespace_id_repository_id_idx 1 / 1 8.30 0.009ms 3 / 0 1
datastore.MavenRemoteRepositoryStore.compareAndSetHealthStatus.Select LockRows over Index Scan maven_remote_repositories_p62_namespace_id_repository_id_idx 1 / 1 8.31 0.011ms 4 / 0 1
datastore.MavenRemoteRepositoryStore.compareAndSetHealthStatus.Update Update over Index Scan maven_remote_repositories_p62_namespace_id_repository_id_idx 1 / 1 8.30 0.496ms 24 / 2 1

Refresh after 368c96f6 (carry namespace_id through health seams): the health seam signatures now carry namespace_id, and every statement — finder, health reads, and the compare-and-set UPDATE — pins the partition key, so the planner prunes to a single partition and drives the unique (namespace_id, repository_id) index. This supersedes the earlier evidence in which HealthStatus / SetHealthStatus planned as an Append over 64 partitions; the deviation paragraphs and the (repository_id) escape hatch no longer apply. No anomalies: bounded reads (LIMIT 1), single-partition scans, plan/actual rows match, index hits dominate buffers.

datastore.MavenRemoteRepositoryStore.FindRemoteRepository

Summary: Plan matches the method's intent: the namespace_id literal prunes both hash-partitioned tables to one partition each (p62), and the join resolves as a Nested Loop over two unique-index probes — the same shape TestMavenRemoteRepositoryStore_FindRemoteRepository_PrunesToOnePartition pins in-suite. The format / kind / soft_deleted_at IS NULL predicates ride as a cheap filter on the single-row pkey probe. Actual rows match the estimate (1 / 1) and execution stays at 0.022ms with 5000 seeded rows per table. No anomalies.

Seed shape: namespaces=1, repositories=5000 (one partition), maven_remote_repositories=5000 (one partition, 1:1 with repositories)

Rendered SQL:

SELECT maven_remote_repositories.namespace_id AS "maven_remote_repositories.namespace_id",
     maven_remote_repositories.id AS "maven_remote_repositories.id",
     maven_remote_repositories.repository_id AS "maven_remote_repositories.repository_id",
     maven_remote_repositories.url AS "maven_remote_repositories.url",
     maven_remote_repositories.cache_validity_hours AS "maven_remote_repositories.cache_validity_hours",
     maven_remote_repositories.metadata_cache_validity_hours AS "maven_remote_repositories.metadata_cache_validity_hours",
     maven_remote_repositories.snapshot_metadata_always_revalidate AS "maven_remote_repositories.snapshot_metadata_always_revalidate",
     maven_remote_repositories.last_health_status AS "maven_remote_repositories.last_health_status",
     maven_remote_repositories.last_health_checked_at AS "maven_remote_repositories.last_health_checked_at"
FROM public.maven_remote_repositories
     INNER JOIN public.repositories ON ((repositories.id = maven_remote_repositories.repository_id) AND (repositories.namespace_id = maven_remote_repositories.namespace_id))
WHERE (((((maven_remote_repositories.namespace_id = $1::uuid) AND (repositories.namespace_id = $2::uuid)) AND (maven_remote_repositories.repository_id = $3::uuid)) AND (repositories.format = $4)) AND (repositories.kind = $5)) AND (repositories.soft_deleted_at IS NULL)
LIMIT $6;

Bound args: [5a8c4bac-2083-4410-989a-1100966f71e6, 5a8c4bac-2083-4410-989a-1100966f71e6, 8972c2ca-9f03-4137-98d9-dd21dd77bb8e, 1, 2, 1]

Plan (EXPLAIN (ANALYZE, BUFFERS) output):

 Limit  (cost=0.56..16.62 rows=1 width=94) (actual time=0.012..0.012 rows=1 loops=1)
   Buffers: shared hit=6
   ->  Nested Loop  (cost=0.56..16.62 rows=1 width=94) (actual time=0.011..0.012 rows=1 loops=1)
         Buffers: shared hit=6
         ->  Index Scan using maven_remote_repositories_p62_namespace_id_repository_id_idx on maven_remote_repositories_p62 maven_remote_repositories  (cost=0.28..8.30 rows=1 width=94) (actual time=0.005..0.005 rows=1 loops=1)
               Index Cond: ((namespace_id = '5a8c4bac-2083-4410-989a-1100966f71e6'::uuid) AND (repository_id = '8972c2ca-9f03-4137-98d9-dd21dd77bb8e'::uuid))
               Buffers: shared hit=3
         ->  Index Scan using repositories_p62_pkey on repositories_p62 repositories  (cost=0.28..8.31 rows=1 width=32) (actual time=0.006..0.006 rows=1 loops=1)
               Index Cond: ((id = '8972c2ca-9f03-4137-98d9-dd21dd77bb8e'::uuid) AND (namespace_id = '5a8c4bac-2083-4410-989a-1100966f71e6'::uuid))
               Filter: ((soft_deleted_at IS NULL) AND (format = '1'::smallint) AND (kind = '2'::smallint))
               Buffers: shared hit=3
 Planning:
   Buffers: shared hit=673
 Planning Time: 1.195 ms
 Execution Time: 0.022 ms

Timings: planning 1.195ms, execution 0.022ms, total 1.217ms.

datastore.MavenRemoteRepositoryStore.HealthStatus

Summary: Single-row read of one remote's health status, keyed by namespace_id + repository_id. Both predicates are bound, so the planner prunes to one hash partition and Index Scans the unique (namespace_id, repository_id) index; LIMIT 1 caps the result. Actual rows match the estimate (1 / 1) and execution is ~9µs with 5000 rows in the target partition. Previously an Append over 64 partitions when the seam carried no namespace; no anomalies now.

Seed shape: namespaces=1, repositories=5000 (one partition), maven_remote_repositories=5000 (one partition)

Rendered SQL:

SELECT maven_remote_repositories.last_health_status AS "maven_remote_repositories.last_health_status"
FROM public.maven_remote_repositories
WHERE (maven_remote_repositories.namespace_id = $1::uuid) AND (maven_remote_repositories.repository_id = $2::uuid)
LIMIT $3;

Bound args: [5a8c4bac-2083-4410-989a-1100966f71e6, 8972c2ca-9f03-4137-98d9-dd21dd77bb8e, 1]

Plan (EXPLAIN (ANALYZE, BUFFERS) output):

 Limit  (cost=0.28..8.30 rows=1 width=2) (actual time=0.004..0.004 rows=1 loops=1)
   Buffers: shared hit=3
   ->  Index Scan using maven_remote_repositories_p62_namespace_id_repository_id_idx on maven_remote_repositories_p62 maven_remote_repositories  (cost=0.28..8.30 rows=1 width=2) (actual time=0.004..0.004 rows=1 loops=1)
         Index Cond: ((namespace_id = '5a8c4bac-2083-4410-989a-1100966f71e6'::uuid) AND (repository_id = '8972c2ca-9f03-4137-98d9-dd21dd77bb8e'::uuid))
         Buffers: shared hit=3
 Planning Time: 0.033 ms
 Execution Time: 0.009 ms

Timings: planning 0.033ms, execution 0.009ms, total 0.042ms.

datastore.MavenRemoteRepositoryStore.compareAndSetHealthStatus.Select

Summary: The compare-and-set transaction's lock step: reads last_health_status + last_health_checked_at FOR UPDATE, keyed by namespace_id + repository_id. Prunes to one partition and Index Scans the unique index before locking the single row (LockRows). Actual rows match the estimate (1 / 1) and execution is ~11µs. Previously fanned out over 64 partitions; no anomalies now.

Seed shape: namespaces=1, repositories=5000 (one partition), maven_remote_repositories=5000 (one partition)

Rendered SQL:

SELECT maven_remote_repositories.last_health_status AS "maven_remote_repositories.last_health_status",
     maven_remote_repositories.last_health_checked_at AS "maven_remote_repositories.last_health_checked_at"
FROM public.maven_remote_repositories
WHERE (maven_remote_repositories.namespace_id = $1::uuid) AND (maven_remote_repositories.repository_id = $2::uuid)
FOR UPDATE;

Bound args: [5a8c4bac-2083-4410-989a-1100966f71e6, 8972c2ca-9f03-4137-98d9-dd21dd77bb8e]

Plan (EXPLAIN (ANALYZE, BUFFERS) output):

 LockRows  (cost=0.28..8.31 rows=1 width=20) (actual time=0.006..0.007 rows=1 loops=1)
   Buffers: shared hit=4
   ->  Index Scan using maven_remote_repositories_p62_namespace_id_repository_id_idx on maven_remote_repositories_p62 maven_remote_repositories  (cost=0.28..8.30 rows=1 width=20) (actual time=0.004..0.005 rows=1 loops=1)
         Index Cond: ((namespace_id = '5a8c4bac-2083-4410-989a-1100966f71e6'::uuid) AND (repository_id = '8972c2ca-9f03-4137-98d9-dd21dd77bb8e'::uuid))
         Buffers: shared hit=3
 Planning Time: 0.027 ms
 Execution Time: 0.011 ms

Timings: planning 0.027ms, execution 0.011ms, total 0.038ms.

datastore.MavenRemoteRepositoryStore.compareAndSetHealthStatus.Update

Summary: The compare-and-set transaction's write step: single-row UPDATE of last_health_status + last_health_checked_at, keyed by namespace_id + repository_id. Prunes to one partition and Index Scans the unique index to locate the row; both FK triggers (namespace, repository) fire as the row is checked. Bound values bind as $1 (smallint) / $2::timestamp with time zone. No anomalies; execution includes trigger overhead.

Seed shape: namespaces=1, repositories=5000 (one partition), maven_remote_repositories=5000 (one partition)

Rendered SQL:

UPDATE public.maven_remote_repositories
SET (last_health_status, last_health_checked_at) = ($1, $2::timestamp with time zone)
WHERE (maven_remote_repositories.namespace_id = $3::uuid) AND (maven_remote_repositories.repository_id = $4::uuid);

Bound args: [1, 2026-08-02 07:23:…+03, 5a8c4bac-2083-4410-989a-1100966f71e6, 8972c2ca-9f03-4137-98d9-dd21dd77bb8e]

Plan (EXPLAIN (ANALYZE, BUFFERS) output):

 Update on maven_remote_repositories  (cost=0.28..8.30 rows=0 width=0) (actual time=0.122..0.122 rows=0 loops=1)
   Update on maven_remote_repositories_p62 maven_remote_repositories_1
   Buffers: shared hit=24 read=2
   ->  Index Scan using maven_remote_repositories_p62_namespace_id_repository_id_idx on maven_remote_repositories_p62 maven_remote_repositories_1  (cost=0.28..8.30 rows=1 width=20) (actual time=0.004..0.004 rows=1 loops=1)
         Index Cond: ((namespace_id = '5a8c4bac-2083-4410-989a-1100966f71e6'::uuid) AND (repository_id = '8972c2ca-9f03-4137-98d9-dd21dd77bb8e'::uuid))
         Buffers: shared hit=3
 Planning:
   Buffers: shared hit=3
 Planning Time: 0.034 ms
 Trigger for constraint fk_maven_remote_repositories_namespace_id_namespaces on maven_remote_repositories_p62: time=0.190 calls=1
 Trigger for constraint fk_maven_remote_repositories_repository_id_repositories on maven_remote_repositories_p62: time=0.093 calls=1
 Execution Time: 0.496 ms

Timings: planning 0.034ms, execution 0.496ms, total 0.530ms.

Edited by Moaz Khalifa

Merge request reports

Loading
Loading