docs(oci): record the per-image decision and complete its size guard

Why

S17 still carried the sentence asking for a per-image schema decision, work item 3 on Artifact-level accounting — Container/OCI: lock... (#551 - closed) • Hayley Swimelar. I took that decision on 2026-08-10 and shipped it the next day in test(datastore): pin container manifest size im... (!1400 - merged) • Hayley Swimelar • 19.3, so for nine days the spec has asked an implementer for a decision already made.

The decision: no per-image aggregate columns now. Per-image manifests_count and image-level size are served by read-time aggregation when their consumers land. This MR records that in S17, and completes the double-counting guard at the two sites between the column and the served JSON.

What

An image, repository, or namespace footprint is never SUM(container_manifests.size), because a shared blob counts once per referencing manifest, and a child manifest counts once for itself and once under each parent index. The canonical statement of that rule lives on datastore.ContainerManifest.Size, and the two new sites point at it rather than restating it, because an obligation's pointer belongs in one place.

container_images.created_at landed on 2026-08-13 for a delete-all acceptance boundary, in chore(datastore): add artifact tombstone marker... (!1454 - merged) • Hayley Swimelar • 19.3. That left a pre-existing rationale comment in internal/managementapi/container_resources.go stale, and the correction rides here, because the spec edit would otherwise turn the comment into a contradiction this MR introduced.

Conflict with an open MR

This MR will conflict with docs(oci): correct two container-manifest doc s... (!1512) • ANBUCHELVAN GANESAN CSE • 19.4. It rewrites the first sentence of the same single physical line this MR edits, so resolving the conflict needs prose reconciliation rather than a mechanical replay. The two edits are independent obligations: theirs corrects the image-config-reader sentence, and ours records the per-image decision. Whichever lands second keeps both, and taking one side would silently reopen this issue's residual.

Coverage

One row per Work item and per "Done when" bullet on Artifact-level accounting — Container/OCI: lock... (#551 - closed) • Hayley Swimelar.

Issue item Where it lands
Work item 1: pin the immutability contract with a test Merged in test(datastore): pin container manifest size im... (!1400 - merged) • Hayley Swimelar • 19.3. TestCreateContainerManifest_IdempotentRePush and the persister re-push subtest both inject a differing size and read the stored row back.
Work item 2: settle container_remote_manifests.size semantics Decision 1 in test(datastore): pin container manifest size im... (!1400 - merged) • Hayley Swimelar • 19.3: payload-only for the MVP, so S16 stands unedited. ADR-007's contradicting text is handbook-synced and routes to ADR-007 follow-up: amendments from S12 OCI loca... (#30) • Unassigned, and relationship population is deferred to Container remote and virtual: deferred follow-u... (#264) • Unassigned.
Work item 3: take the per-image schema decision S17 flags This MR. I took the decision on 2026-08-10 and recorded it in test(datastore): pin container manifest size im... (!1400 - merged) • Hayley Swimelar • 19.3, but the S17 sentence this work item quotes still asked for it. This MR records it there, with both revisit triggers and the referrers rule any future counter inherits.
Work item 4: guard the double-counting rule at the consumers The anchor comment on datastore.ContainerManifest.Size, the OpenAPI contract, and the S17 acceptance criterion already carried the guard. This MR adds the two middle hops, ContainerManifestListRow.Size and the served JSON field, each pointing at the anchor rather than restating it.
Done when: a test asserts a re-push does not rewrite container_manifests.size Merged in test(datastore): pin container manifest size im... (!1400 - merged) • Hayley Swimelar • 19.3, per Work item 1.
Done when: the remote-manifest size semantics are settled in one document and contradicted in none Settled in S16, per Decision 1 in test(datastore): pin container manifest size im... (!1400 - merged) • Hayley Swimelar • 19.3. The one in-repo contradiction left is ADR-007's, out of scope here and tracked at ADR-007 follow-up: amendments from S12 OCI loca... (#30) • Unassigned.
Done when: the per-image schema decision is recorded, with a follow-up issue if columns are added This MR records it in S17. No follow-up issue is owed, because no columns are added and neither revisit trigger has fired.

Test plan

No tests. The change adds no code path, symbol, or behavior, so the tests guardrail is satisfied vacuously. No scenario in docs/testing/ changes for the same reason, since no request path or response shape moves.

Gates run on the branch:

gofmt -l internal/datastore/container_manifest.go internal/managementapi/container_resources.go
go build ./...
golangci-lint run --max-same-issues=0 --max-issues-per-linter=0 ./internal/datastore/ ./internal/managementapi/
npx markdownlint-cli2@0.22.1 docs/specs/S17-rest-management-api.md
vale --minAlertLevel error docs/specs/S17-rest-management-api.md
lychee --offline --include-fragments docs/specs/S17-rest-management-api.md

All clean: golangci-lint 0 issues across both packages, markdownlint 0 errors, Vale 0 errors with the file's warning count down from 242 to 239, and lychee 0 errors over 141 links. The lychee run matters because the edit adds a fragment link.

Related to #551 (closed)

Context for LLM agents

Rationale

  • A differing-size probe test on the remote cache path. Rejected: the remote conflict path's Size.SET(...) is a genuine no-op, because the size is derived by reading the committed payload back, and that is already documented and tested. The remote contract is the inverse of the hosted one, so the test would pin a detail that Container remote and virtual: deferred follow-u... (#264) • Unassigned must later delete.
  • Editing the migration's size column comment. Rejected: it restates S16, and the edit would spend a migration-edit override to duplicate an obligation the sibling migration already carries.
  • Restating the double-counting rule at each site. Rejected in favor of cross-referencing the anchor, so the rule keeps one home to update.

Non-goals

  • docs/specs/S12-container-oci-hosted.md:193 names the repository and image statistics endpoints as consumers of the size column "without recursive traversal at query time", which is the summing reading this guard forbids. The column's Go doc comment, the OpenAPI contract, ADR-007, S17, and S22 all contradict it. It is another author's line in an Implemented spec, so it travels as a reported finding rather than an edit here, tracked on S12 licenses summing container_manifests.size f... (#710) • Unassigned.
  • ADR-007's stale progressive-convergence text. Handbook-synced, and tracked on ADR-007 follow-up: amendments from S12 OCI loca... (#30) • Unassigned.
  • Per-image aggregate columns. The decision declines them, and neither revisit trigger has fired.
  • last_downloaded_at writers for either container table, per the issue's own out-of-scope list.
Edited by Hayley Swimelar

Merge request reports

Loading
Loading