feat(maven): serialize version size via the shared size field
Summary
Step 5 of the Maven version-size accounting plan: the hosted Maven Version resource now serializes maven_versions.size_bytes through the shared size field the npm slice (#549 (closed)) already populated.
versionFromMavenModelmapsSize: new(row.SizeBytes), mirroringversionFromNpmModel— both mappers now always serialize a present value, zero included, and no hosted response sendsnull.mavenVersionColumns()gainsSizeBytesso both version reads (list and find) project the stored counter.- The OpenAPI
Version.sizedescription and the S17 Artifact-resources row state "both Maven and npm hosted versions carry a value"; the type stays[integer, 'null']for contract stability against the historical nullable shape. - The file-delete refold ships with the serialization:
MavenFileDeleter.deleteFileTxnow refoldsmaven_versions.size_bytesin the delete's own transaction via the walk's transaction-taking halves —LockVersionForSizeRefoldTxtakes the version-keyed advisory lock and themaven_versionsrowFOR NO KEY UPDATEas the transaction's first statements, then themaven_filesDELETE runs, thenRefoldVersionSizeTxrefolds from the surviving rows (the same walk as the upload path's recompute), so the value this MR puts on the wire never drifts after a file hard-delete — the npm arm already had this property, and without it a servedsizewould go permanently wrong on any Maven file delete. The plan's "no Maven delete path" premises that deferred this site on #472 (closed)/S28 are corrected in its Delete-and-GC, Decrement-side, and Dependencies passages.
Mechanism-neutral for the maintenance mechanism: the contract describes only the serialized value, not how the counter is maintained (ADR-007 governs that).
Scope
- Field-only serialization on the wire:
sizeon the existing sharedVersionresource for hosted Maven. No newsize_bytesfield is added (the npm slice already shipped the shared field), no route changes, no error-case changes. - The file-delete refold is a datastore-internal fix that keeps the serialized field correct; no management-API behavior change (the delete route already answered 202; only its transaction now also maintains the counter).
- Size-ordered version listing stays out of scope: the S17 sort vocabulary covers
created_at/versiononly; the column and its size-DESC index exist, but the sort key has no tracker.
Spec and plan records in this MR
docs/specs/S17-rest-management-api.md: the Artifact-resourcesVersionrow now reads "the stored per-version bytes for both Maven and npm hosted versions" (the "hosted" qualifier scoping the claim against the remote twins, which declare buffered counters nothing writes yet), plus reconciliation of the stale passages the Step 1/2 landings left behind — the S22 dependency row's and Freshness section's "still absent" clauses, the Follow-ups bullet's three stale sentences (the hosted-Maven carve-out, the "null for Maven" wire claim, the size-sort sentence), the Resolutions "defer to S18/S22" pointer, the Phase 8 read-cost bullet's "no total per-version index" premise (recast to prescribe the per-version bound theindex_maven_files_on_ns_id_ver_idindex enables), and the Freshness sentence on the Maven column's write paths (upload post-commit recompute, plus the delete-transaction refold). Each contradicted a landed Step 1/2 artifact or the spec's own S22 correction.docs/plans/2026-08-10-maven-version-size-accounting.md: Step 5's Files and Acceptance entries record the shared-field routing, the delete-refold wiring, and the reconciliation scope, per the plan's research-correction guardrail; the falsified "no Maven delete path" premises in Delete-and-GC, Decrement-side, and Dependencies are corrected with the landed deleter and its refold.
Rolling-deploy window
The refold changes a file delete's lock order without a schema or an API change, so old and new pods serve the delete route side by side during a rolling deploy with opposite orders:
- old: the
maven_filesDELETE, then the advisory lock, then themaven_versionsrow at the writeback; - new: the advisory lock, then the
maven_versionsrowFOR NO KEY UPDATE, then themaven_filesDELETE.
Two concurrent DELETE /files/{id} calls for the same file id, one per binary, can close a lock cycle, and PostgreSQL aborts one side as 40P01 (one of the two would otherwise answer 404). The window is bounded by the rollout and is narrower than the upload-versus-delete cycle the old order carries, so it argues for finishing the rollout promptly rather than for staging it.
Testing
TestVersionDetailHandler_SerializesSizePerFormat(managementapi): "maven carries the stored size" (654321) and "maven zero is a present zero, not null" (0); npm cases unchanged (123456, 0).TestVersionListHandler_SerializesStoredSize(managementapi, new): each list row'ssizeequals its distinct stored counter, for both Maven and npm arms (the npm list arm previously had no size coverage).- Datastore:
TestMavenVersionStore_RecomputeVersionSizeTx_FiresBeforeAnyDBWorkpins the Tx sibling's guards, andTestMavenVersionStore_SizeRefoldHalves_FiresBeforeAnyDBWorkextends the table toLockVersionForSizeRefoldTxandRefoldVersionSizeTx;TestMavenFileDeleter_DeleteFile_RefoldsVersionSize(integration, new) pins the delete-transaction refold to the surviving rows and its digest-deduplicated shared-digest arm, andTestMavenFileDeleter_DeleteFile_LocksVersionRowBeforeFileRow(integration, new) pins the parent-before-child lock order (holds the version row so the delete parks, then probes the file rowFOR UPDATE NOWAIT);TestLockMavenVersionForRecomputeStmt_SQLpins the lock read'sFOR NO KEY UPDATEclause;TestListMavenVersionsByPackageStmt_Projectionpinsmaven_versions.size_bytesin the SELECT; the list and find integration suites stamp non-zerosize_bytesand assert round-trip (a dropped column scans as zero and fails). - Lint:
golangci-lint run ./internal/managementapi/ ./internal/datastore/clean (pinned 2.12.2);--build-tags=integration ./internal/datastore/gains no new findings beyond the suite's existing contextcheck pattern.
E2e scenario catalogs
No catalog scenario is added or affected. The management-API size display field this MR adds to the Maven version resource is already served by the existing scenario e2e.maven.discover.view-in-ui in docs/testing/e2e/maven.md ("with coordinates and sizes shown"), and the file-delete refold is a datastore-internal correctness fix with no user-flow delta. Stated per the testing-catalogs guardrail; the catalogs target user-level flows and there is no new user flow to cover.
Related to #550