S22: reconciliation's version-type table list has no exhaustiveness guard

What is missing

The version-type tables that back namespace_statistics.components_count and repositories.artifacts_count are enumerated by hand in three independent places. No test fails when a new format's table is added to one list and missed in another.

The three enumerations:

  1. internal/datastore/reconcile_namespace.go, in RecomputeComponentsCount, sums six tables inline:

    total := namespaceRowCount(table.ContainerManifests, table.ContainerManifests.NamespaceID, ns).
        ADD(namespaceRowCount(table.ContainerRemoteManifests, table.ContainerRemoteManifests.NamespaceID, ns)).
        ADD(namespaceRowCount(table.MavenVersions, table.MavenVersions.NamespaceID, ns)).
        ADD(namespaceRowCount(table.MavenRemoteVersions, table.MavenRemoteVersions.NamespaceID, ns)).
        ADD(namespaceRowCount(table.NpmVersions, table.NpmVersions.NamespaceID, ns)).
        ADD(namespaceRowCount(table.NpmRemoteVersions, table.NpmRemoteVersions.NamespaceID, ns))
  2. internal/datastore/reconcile_repository.go carries a separate per-(format, kind) catalog of countName and countBuild entries over the same tables.

  3. internal/datastore/lifecycle_scan.go and internal/datastore/lifecycle_scan_versions.go carry S20-A's own list over the same tables.

The existing test asserts that the six sum correctly and hardcodes six. TestNamespaceReconcileStore_RecomputeComponentsCount_SumsEveryVersionTypeTable stages all six into one namespace and checks the total, so it detects a table counted twice and not a seventh table counted zero times.

Why this outranks a missed emit site

A missed emit site costs one drain interval. Reconciliation recomputes from source and repairs it.

A missed recompute table is different in kind. Reconciliation computes a value that is low by the omitted table's row count, writes it over the correct one, and confirms the same wrong value on every later pass. The source rows agree with the wrong number, so nothing detects the omission. reconciliation_drift_rows records the correction once, on the pass that introduces the error, and reads zero afterwards.

The S22 spec routes every accepted fast-path race to reconciliation's next pass and calls reconciliation liveness "the single most important operational safety property in the design". This issue is about the case where reconciliation itself is the thing that is wrong.

What to build

A fail-closed source invariant, following the pattern the repository already uses. internal/datastore/maven_invariants_test.go and internal/datastore/container_remote_invariants_test.go both walk the AST, fail when a site is missing its marker, and fail when the scan finds zero sites.

The check needs a mechanical definition of "version-type table". Two candidates, and picking between them is part of the work:

  • Derive the set from the jet catalog by naming rule (*_versions and *_manifests), which needs an explicit exclusion list for tables that are not counted.
  • Declare the set once in one place and assert that all three enumerations match it, which moves the hand-maintained list from three places to one and makes the other two derived.

The second is the stronger shape, because it removes the duplication rather than policing it. It costs more, because lifecycle_scan.go reads these tables for a different purpose and needs its own columns from them.

Acceptance criteria

  1. A version-type table present in the jet catalog and absent from any of the three enumerations fails a test.
  2. A table removed from one enumeration and left in the others fails a test.
  3. The check fails when it finds zero enumerations, so a scan that breaks does not pass silently.
  4. The test names, in its own comment, what a reader has to do when they add a format.

The emit-side counterpart is #834 (closed) and #836 (closed). While those stand, reconciliation is what brings the columns to the correct value. This issue covers the case where that repair is itself wrong.


🤖 Triage note: This issue has been reviewed and classified as type::maintenance (maintenance::test-gap). It addresses a test coverage gap — adding exhaustiveness guards to prevent silent drift between version-type table enumerations — with no new user-facing functionality and no broken behaviour to fix. The labels Category:Artifact Registry, devops::package, and group::package registry have been confirmed as correct.

If this classification doesn't look right to you, please feel free to update the labels and let us know!

Edited by Triage and Label issues