chore(managementapi): scaffold route mount and slug resolution

What

Phase 1 Step 2 of the S17 REST management API: scaffolds the internal/managementapi package and mounts it on the application mux.

  • NewHandler: a private http.ServeMux registering the five hosted-repository CRUD routes under /api/v1/{slug}/repositories and .../{repository_name} as 501 placeholders (method-scoped, so an unserved method returns 405 with an Allow header automatically).
  • slugMiddleware: resolves {slug} to a namespace via FindBySlug and injects the namespace id into the request context. A missing slug returns 404 not_found (existence-hiding); a finder error (or a (nil, nil) contract violation) returns 500 and logs the cause, never masking an outage as a 404. Routes are wrapped at registration, so Steps 8-10 handlers read the id via namespaceIDFromContext and cannot bypass resolution.
  • wireManagementAPI: mounts the handler behind the existing auth/body-size/logging middleware chain.

CRUD handler bodies stay 501 placeholders; they land in Steps 8-10.

Review

  • /review-branch (APPROVE): added the 500-path error logging (the only diagnostic when the response envelope hides the cause).
  • /plannotator-review: dropped rot-prone spec-section and sibling-package references from comments.
  • MR review (GitLabDuo + AppSec): resolved the slug in middleware so handlers read the namespace id from context instead of a (uuid, bool) return (removes the "forgot to check the bool" footgun); added a defensive nil-namespace guard so a (nil, nil) finder result returns 500 rather than panicking.

Spec coverage

Spec: S17 REST management API. This step owns the scaffold rows (route registration, slug-404 existence-hiding, the 500-not-masked-as-404 seam, mount wiring, the error-envelope shape); per-handler CRUD rows are owned by Steps 8-10 / 5-7.

Acceptance criteria

# Criterion This step
AC-7/8/10/11 Route registration for detail/list/update/delete TestHandler_RouteTable_EveryRouteResolves (each route 501s, not a bare miss)
AC-12 Cross-slug isolation Slug-resolution seam: TestHandler_UnknownSlug_Returns404WithEnvelope, TestSlugMiddleware_InjectsNamespaceID, TestWireManagementAPIWithFinder_UnknownSlugIs404
AC-13 Every error path returns the error envelope with request_id assertErrorEnvelope across the route-table, slug-404, and finder-failure tests
AC-1..6, 9 OpenAPI + per-handler CRUD behavior Owned by Step 1 (!637) and Steps 5-10

Error cases

Endpoint Condition Status This step
All Slug resolves to no namespace 404 not_found TestHandler_UnknownSlug_Returns404WithEnvelope, TestSlugMiddleware_ShortCircuitsOnFailure
All Unexpected datastore failure / (nil, nil) 500 (logged, not masked) TestHandler_FinderFailure_Returns500WithEnvelope, TestHandler_FinderFailure_LogsCause, TestResolveSlug_NilNamespaceWithoutError_Writes500
Registered path, wrong method 405 + Allow TestHandler_WrongMethod_Returns405WithAllow
Per-handler 400/409/422 Owned by Steps 8-10

Security

Concern This step
Existence-hiding Unknown slug 404s identically to a missing repo; a real datastore failure is a 500, never masked as 404
Resolution bypass slugMiddleware is the single resolve site; handlers read the id from context, so a handler cannot run against an unresolved slug
Tenant isolation The slug-resolution seam scoping every request to a namespace_id is scaffolded here; per-query scoping lands in Steps 5-7
Auth/authz, input validation, injection Owned by S08/S09 and the handler/datastore steps

Plan

Step 2 of docs/plans/2026-06-22-s17-phase1-repository-crud.md. Part of epic gitlab-org#22342 (closed).

Related to #171 (closed)

Edited by João Pereira

Merge request reports

Loading
Loading