Loading
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-closed503with no row, logged once) → store uniqueness. - Responses:
201+Locationon create,200on exact anchor replay (unaltered resource),409on mismatched replay or taken slug,413when the body outgrows the request size cap,422on validation,503fail-closed, declared500on store faults. An exact replay outranks a slug refusal: an anchor that already holds a matching namespace answers200even when the slug would now be refused, and a disagreeing body answers the mismatch409. 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
provisionNamespaceoperation inapi/openapi/gitlab-v1.yaml; the package contract rig upgraded from schema-levelVisitJSONto operation-levelopenapi3filter.ValidateResponse(the management-API precedent), sweeping every declared response class. - The
401stays bodiless with aWWW-Authenticate: Bearerchallenge (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