feat(managementapi): repository list envelope (S09 Verdicts plan: 3/10)
What changes for a caller
GET /api/v1/{slug}/repositories used to answer with a bare JSON array. It now answers with an object:
{"repositories": [ { "name": "backend-images", "format": "oci", "...": "..." } ]}repositories is always present and never null. An empty page is {"repositories": []}. Pagination is unchanged: the cursors stay in the Link header, and there is still no total.
The envelope is what later steps hang the permission verdicts off, so the shape does not change again when include_permissions arrives: verdicts add keys, they never switch the top level.
Why this is safe to ship as a breaking change
The only client is the GitLab monolith, and it already reads both shapes: gitlab!253040 merged with workflow::post-deploy-db-production, so the tolerant client is live on GitLab.com before this deploys.
Pact provider suite
Contracts published before the envelope assert the array shape. The service is not live, so no compatibility window is kept: the stale version directories get deleted from the bucket and republished with the envelope when the contract CI job lands. Today no CI job compiles the contract build tag, so nothing exercises this suite — pre-existing, tracked in #673 and #305.
Other consumers
One reader of this body outside Go: scripts/e2e/run-e2e-tests.sh filtered the page with a bare-array jq expression and is updated. .claude/skills/run-artifact-registry/driver.sh checks status codes only, so the run recipe is untouched. No Bruno assertion reads the body.
e2e scenarios (guardrail 12)
No catalog change. The four e2e.<format>.setup.create-repository rows in docs/testing/e2e/{docker,maven,npm,oci}.md say the repository "appears in the namespace repository list", which is shape-agnostic and stays true. The harness behind them is scripts/e2e/run-e2e-tests.sh, whose list assertion this MR updates. That job runs on a schedule, not on this pipeline.
Siblings and merge order
No merge-order requirement against any open MR: every pair below was measured on the merged tree, not just on this branch.
| MR | Shared files | Union verdict |
|---|---|---|
| !2287 (merged) (Step 4) | v1.yaml, pact_provider_test.go |
clean merge; build, vet, managementapi tests green |
| !2261 (merged) (Step 7) | v1.yaml, contract_test.go |
clean merge; gates green |
| !2254 (merged) | list_authz_test.go |
clean merge; gates green |
| !2253 (merged) | list_authz_test.go |
conflicts on three empty-page assertions; resolving to this branch's assert.JSONEq form builds and passes. Whichever lands second takes those three lines. |
| !2207 (merged), !2224 (merged), !2268 (merged), !1924 | v1.yaml, contract_test.go, the e2e script |
clean merge; gates green |
!2266 and a handful of older drafts conflict on go.mod or v1.yaml, but they already conflict with main itself — this branch adds nothing to that.
Step 6 (list verdicts) consumes the RepositoryList block in v1.yaml and the Go struct in list.go; both are left flat and minimal so its permissions key is one addition at one anchor.
Spec coverage
Spec: S09 Authorization — the Repository list envelope paragraph in API Contracts, and the Resolutions entry "Does the list response switch shape on include_permissions?".
| Contract clause | Tests |
|---|---|
| The list 200 is always the envelope, one shape on every call | decodeRepositoryList (every list 200 in the package), TestContract_RepositoryList_IsTheEnvelope, TestReadHandlers_ResponsesMatchOpenAPIContract |
repositories required and non-nullable; an empty page is [] |
TestListHandler_EmptyPage_ReturnsEmptyEnvelopeArray, TestListHandler_Authz_NoAccess_EmptyPageNoBatchCheck, TestContract_RepositoryList_IsTheEnvelope |
Pagination stays in the Link header |
decodeRepositoryList asserts the top-level key set is exactly repositories; TestListHandler_KeysetWalk, TestListHandler_PrevLink, TestReadHandlers_PaginatedResponseMatchesContract |
Criterion 24, envelope clause (the shape holds without include_permissions) |
the rows above; the no-permissions-key clause belongs to Steps 5, 6 and 8 |
Every assertion above was mutation-checked: reverting the envelope write, nilling the slice, marking the field omitempty, adding a top-level key, and each of the five OpenAPI schema properties all turn the suite red.
Criteria 20 through 23 and 25 through 27 belong to other steps of this plan and to the S09 enforcement plan; nothing in this MR touches a denial path.
Notes for the reviewer
The repository list is now the one management collection with an envelope; the artifact lists (ContainerImageList, PackageList, VersionList, FileList) and UpstreamRepositoryAssociationList stay bare arrays. That is the spec's deliberate call, not an oversight.
Related to #670 (closed)