feat(gitlabapi): provision namespaces from the platform anchor

What

The handler half of POST /api/gitlab/v1/namespaces (S33, Namespace provisioning): the endpoint, its validation chain, the OpenAPI operation, and the wiring.

  • Strict decode: unknown fields, trailing data, wrong types, and missing required fields answer 400; all six row values are caller-owned, nothing is derived server-side.
  • Validation chain: anchor-field bounds (non-empty, ≤255 runes) → ADR-015 slug policy (internal/slug) → Brand-List Validator (config-gated; nil = disabled; reserved → 422; any error → fail-closed 503 with no row, logged once) → store uniqueness.
  • Responses: 201 + Location on create, 200 on exact anchor replay (unaltered resource), 409 on mismatched replay or taken slug, 413 when the body outgrows the request size cap, 422 on validation, 503 fail-closed, declared 500 on store faults. An exact replay outranks a slug refusal: an anchor that already holds a matching namespace answers 200 even when the slug would now be refused, and a disagreeing body answers the mismatch 409. Failure writes are skipped on a done request context; the log is suppressed only when the context is done and the cause is that teardown, so a store fault or validator outage racing a client disconnect is still recorded.
  • Contract: the provisionNamespace operation in api/openapi/gitlab-v1.yaml; the package contract rig upgraded from schema-level VisitJSON to operation-level openapi3filter.ValidateResponse (the management-API precedent), sweeping every declared response class.
  • The 401 stays bodiless with a WWW-Authenticate: Bearer challenge (S08 interim, owned by the auth middleware); the spec's Error Cases row is aligned in !1051 (merged).

Stacking

Stacked on the store MR !1053 (merged) (NamespaceStore.Create), which targets main (!1029 (merged) merged). Second half of the split Step 5 of the S33 Phase 1 plan.

Spec coverage

AC Behavior Test
1 Valid slug + unused anchor → 201, row created, status: active TestCreateNamespace_ValidRequest_Returns201
2 Same-anchor replay → 200, unaltered, no second row TestCreateNamespace_ExactReplay_Returns200 (store: TestNamespaceStore_Create_ExactReplay)
3 Replay body disagrees → 409, row unchanged TestCreateNamespace_StoreConflicts_Return409 (store: TestNamespaceStore_Create_ReplayMismatch)
4 BLV enabled + unreachable → 503 on a free anchor, no row; held anchor: exact body replays 200, disagreeing body 409 TestCreateNamespace_BrandListUnreachable_Returns503 (asserts the store is never called), TestCreateNamespace_ExactReplay_OutranksSlugRefusal
5 Syntactic/reserved/brand-rejected slug → 422 on a free anchor, no row; held anchor: exact body replays 200, disagreeing body 409 TestCreateNamespace_InvalidSlug_Returns422, TestCreateNamespace_BrandListReserved_Returns422, TestCreateNamespace_ExactReplay_OutranksSlugRefusal
6 Slug taken → 409 TestCreateNamespace_StoreConflicts_Return409 (store: TestNamespaceStore_Create_SlugTaken)

Every response class is additionally validated against the OpenAPI operation in TestProvisioning_ResponsesMatchOpenAPIContract.

No docs/testing/ catalog update: the GitLab API surface has no e2e scenario catalog yet; e2e coverage for provisioning arrives with the platform-client integration.

Closes #261 (closed)

Edited by João Pereira

Merge request reports

Loading
Loading