feat(managementapi): declare the artifact delete operations

Why

The Phase 4 artifact write surface needs its contract declared before any handler lands, so consumers generate a typed client and mock against the schema first. This declares the seven single-artifact deletes, the shared plumbing the remaining contract steps consume (the 202 component, Error.details, service_unavailable), and the pending-operations mechanism that keeps the contract sweep green while the routes are unserved.

Nothing serves these routes yet, by design. Until the write-route scaffold registers them, the six that share a Phase 3 GET path answer an enveloped 405 and the new .../manifests/{digest} path answers 404. Step 1 of the merged S17 Phase 4 plan, whose Approach slices the phase into four contract MRs rather than one.

At 915 reviewable LOC this is over the 500 ceiling and irreducibly so within the step: the Bruno collection must ship in the same MR as the OpenAPI change (CLAUDE.md guardrail 15), which accounts for 234 lines, and 381 of the remainder is the test floor.

What

  • ArtifactDeleteAccepted is named for its subject. The 202 promise, that the artifact stops being readable, holds for one named artifact. A batch operation promises less: an entry resolving to nothing is a no-op and an unappliable entry is skipped, so the bulk steps describe their own 202 instead of referencing this one.
  • The manifest delete is the only refusal. Its 409 carries the blocking parent digests in error.details.parents, non-empty and unique, with no upper bound declared: nothing caps how many manifests index one manifest, so a cap would truncate the list the caller has to act on rather than bound anything the caller sent.
  • The digest path parameter declares no pattern. A malformed digest has to stay expressible so it reaches the handler's 404 instead of failing request validation with a 400, which would expose a digest-syntax oracle.
  • The pending set skips conditionally. An arm is exempt only while it answers 501 or a text/plain 404/405. Anything else, a JSON envelope included, fails it as stale, so a handler that serves its route without dropping its entry reddens its own MR. Draining is per operation rather than per format arm, because the shared repository resolve precedes the format dispatch.

Spec coverage

Spec rule Test
The document defines every Phase 4 endpoint and declares each operation's failure responses (the seven-delete slice, AC #30) TestContract_ArtifactDeletes_DeclareTheirResponses
Every delete answers 202 with an empty body (AC #31, contract half) same, per-row 202-declares-no-content assertion
No delete returns 409 except a manifest another manifest indexes (AC #39, contract half) same, closed per-row status set
The manifest 409 carries the parent digests in error.details.parents (AC #41) same, manifest row, plus TestContract_ErrorSchema_DeclaresOptionalDetails
A path segment that is not a canonical digest is missing-or-invalid, so 404 (Error Cases) manifestDigest declares no pattern; asserted by the same row's path lookup
Bulk enqueue unavailability answers 503 service_unavailable (Error Cases) TestContract_ErrorCodeEnum_MatchesTransportCodes (enum entry only; the operations declare it later)
A declared operation must reach its own handler, and a pending arm must stop skipping once it serves TestContractOperations_AreRegisteredAndServed, both directions plus its orphan loop
The skip-or-fail decision itself TestAnswersUnserved_ClassifiesTheRecordedResponse (12 rows over status and content type)

No docs/testing/ e2e scenario is added or affected. This MR stages contract operations only and nothing serves them, and the plan's Testing Strategy assigns the management-delete journeys in docs/testing/e2e/docker.md and docs/testing/e2e/oci.md to the container image DELETE handler step.

Noted for follow-up, outside this step's declared file list

  • docs/dev/api-style.md's status table lists 204 for a successful DELETE. Nothing conflicts, since its 202 row already describes these deletes and deleteRepository stays 204, but the artifact-delete case is worth naming there. The spec settles the choice in Delete semantics: a 204 would assert completion, which the soft-delete end state does not satisfy.
  • internal/transport/httperror.go's Details doc attests the value carries "only values the caller already holds", while the manifest 409's parent digests are server-derived. The disclosure is authorization-scoped, since a manifest's parents live in the image the caller already resolved, so the attestation is what needs amending, at the step landing the first caller. CodeServiceUnavailable's doc comment names only gitlab-v1.yaml and now applies to this document too.

The refactor commit under-describes itself: that pass also normalized the digest parameter's prose and three collection docs blocks. Rewriting the message was not available in this environment, and the squash-merge takes the MR title regardless.

Context for LLM agents

Rationale

  • Generalizing the 202 component to cover bulk. Rejected because the spec's Bulk delete Outcome makes an unknown entry a no-op and leaves a blocked manifest listable, so the single-artifact promise is false there, and narrowing a published promise later is client-visible. The plan's Steps 3 and 4 name this component as a dependency, so the component's own description now says who must not reuse it.
  • Keying the pending set by operation alone. Rejected because the per-format key lets the closing loop reject an entry naming a format arm the document dropped. The coarser draining granularity is stated on the record instead.
  • Treating a JSON 404 as unserved, so a registered route that resolves before answering stays exempt. Rejected because it would retire the staleness check that makes a forgotten drain fail its own MR.

Non-goals

  • Registering routes or serving any of these operations. The write-route scaffold and the eleven handler steps own that, and this MR deliberately leaves the surface unserved.
  • Declaring maxItems on the blocking-parents array. Left unbounded deliberately, with the reason in the schema description.
  • The tag upsert, the six bulk operations, limit_exceeded, and the marker schema. Separate steps of the same plan.

Related to #313 (closed)

Merge request reports

Loading
Loading