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
ArtifactDeleteAcceptedis named for its subject. The202promise, 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 own202instead of referencing this one.- The manifest delete is the only refusal. Its
409carries the blocking parent digests inerror.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
404instead of failing request validation with a400, which would expose a digest-syntax oracle. - The pending set skips conditionally. An arm is exempt only while it answers
501or a text/plain404/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 lists204for a successful DELETE. Nothing conflicts, since its202row already describes these deletes anddeleteRepositorystays204, but the artifact-delete case is worth naming there. The spec settles the choice in Delete semantics: a204would assert completion, which the soft-delete end state does not satisfy.internal/transport/httperror.go'sDetailsdoc attests the value carries "only values the caller already holds", while the manifest409'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 onlygitlab-v1.yamland 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
202component 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
404as 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
maxItemson 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)