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:
-
internal/datastore/reconcile_namespace.go, inRecomputeComponentsCount, 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)) -
internal/datastore/reconcile_repository.gocarries a separate per-(format, kind)catalog ofcountNameandcountBuildentries over the same tables. -
internal/datastore/lifecycle_scan.goandinternal/datastore/lifecycle_scan_versions.gocarry 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 (
*_versionsand*_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
- A version-type table present in the jet catalog and absent from any of the three enumerations fails a test.
- A table removed from one enumeration and left in the others fails a test.
- The check fails when it finds zero enumerations, so a scan that breaks does not pass silently.
- The test names, in its own comment, what a reader has to do when they add a format.
Related
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 astype::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 labelsCategory:Artifact Registry,devops::package, andgroup::package registryhave been confirmed as correct.If this classification doesn't look right to you, please feel free to update the labels and let us know!