Artifact-level accounting — Container/OCI: lock in manifest size and settle the per-image and remote gaps
Context
docs/adr/007_database_schema.md defines artifact-level storage accounting in full — sections
#### Artifact-level storage accounting and #### Artifact-level storage accounting reconciliation.
S22 excludes this layer and hands it to the format slices; #441 (closed) defers the per-format work and asks
for one issue per format. This is the Container/OCI issue.
OCI is the format ADR-007 treats differently, and the only one whose existing column is already
correct: container_manifests.size is pre-computed at push and immutable, is the source of truth,
and needs no reconciliation. The work here is therefore smaller and mostly about the gaps around it.
What exists today
| Column | ADR-007 | Schema | Writer |
|---|---|---|---|
container_manifests.size |
pre-computed at push, immutable, source of truth, no reconciliation | exists (internal/datastore/migrations/sql/20260526120300_oci_container_manifests.sql:24, with a >= 0 CHECK) |
computeTreeSize (internal/format/oci/manifest.go:919) → internal/datastore/container_manifest.go:317, in-transaction, INSERT-only under ON CONFLICT DO NOTHING |
container_remote_manifests.size |
mirrors the hosted column with progressive semantics as children are cached | table does not exist yet | — |
per-image size / manifests_count |
not declared | none | — |
container_manifests.size is the manifest tree size — this manifest's payload plus every blob and
child manifest reachable from it. ADR-007 is explicit that it may double-count blobs shared across
manifests, so it is not a substitute for storage usage at any higher scope.
Work
- Pin the immutability contract with a test.
ON CONFLICT DO NOTHINGmeans an idempotent re-push never rewritessize(persistExisting,internal/datastore/container_manifest_persister.go:329, only moves tags and inserts relationships). ADR-007 rests "no reconciliation is needed" on that property, and nothing currently asserts it. - Settle
container_remote_manifests.sizesemantics before the table lands. ADR-007 says the value "converges to the full tree footprint progressively as children are fetched"; S16 says the MVP leaves an index or manifest list at its own payload size and writes zero relationship rows, and has an acceptance criterion asserting exactly that. One of the two documents is wrong, and the answer decides whether the column needs a maintenance path at all. - Take the per-image schema decision S17 flags: a per-image
manifests_countbuffered counter (the npmversions_countpattern, counting referrers too) and image-level size have no columns today. ADR-009 promises image-, tag-, and version-level statistics endpoints that need them. - Guard the double-counting rule at the consumers: whatever statistics endpoint eventually reads
container_manifests.sizemust not sum it as a repository or namespace footprint.
Out of scope
container_images.last_downloaded_atandcontainer_manifests.last_downloaded_at— S12 assigns both to the counter subsystem and S22 hands them to S18. Neither has a writer today.- Repository- and namespace-scoped counters for OCI — S22's, wired at the OCI write and delete sites.
Decisions required
container_remote_manifests.size: progressive convergence (ADR-007) or payload-only for the MVP (S16). Whichever wins, the loser's text gets corrected.- Per-image aggregates: add
manifests_countand an image-level size column, or serve those displays by aggregation at read time.
Done when
- A test asserts a re-push does not rewrite
container_manifests.size. - The remote-manifest size semantics are settled in one document and contradicted in none.
- The per-image schema decision is recorded, with a follow-up issue if columns are added.
Refines #441 (closed).