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:
FindRemoteRepositoryresolves the remote row bound to the parentrepositories.idakind=2Resolutionalready 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 toResolution, so no handler needs a second round-trip.HealthStatus/SetHealthStatusimplement S13'sremote.HealthStatusReaderandremote.HealthStatusWriteroverlast_health_status/last_health_checked_at, keyed by parentrepositories.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.ErrNoRows→datastore.ErrNotFound; every other DB error stays itself, so a transient failure is never misreported as a miss. - The finder pins
namespace_idon both partitioned tables (maven_remote_repositoriesandrepositories, eachPARTITION BY HASH(namespace_id)× 64) and gates on format,kind=2, andrepositories.soft_deleted_at IS NULL, resolving through the table'sUNIQUE (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, perdatabase-query-patterns.mdand 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_idplus the parentrepositories.id: the S13 seam contract widened on main (!1220 (merged)) to carrynamespace_id, so both health queries pin the partition key and prune to one partition through the existingUNIQUE (namespace_id, repository_id)index. The earlier 64-partitionAppenddeviation and its(repository_id)index escape hatch are gone; theEXPLAINevidence below shows every statement pruning to a single partition. SetHealthStatusis a compare-and-set on the probe timestamp, per the updatedHealthStatusWritercontract: it locks the row (SELECT ... FOR UPDATEinsideRunInTx), applies only when the storedlast_health_checked_atis NULL or older than the incomingcheckedAt, 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 thetmp_plaintext_*columns — the divergence Step 1's MR (!1120 (merged)) already records. database/sqlappears only in the test file (fixture seeding), which theno-direct-dbdepguard 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 msTimings: 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 msTimings: 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 msTimings: 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 msTimings: planning 0.034ms, execution 0.496ms, total 0.530ms.