feat(managementapi): enforce the repository-scope arm part 2/4 (S09 Enforcement plan: 17/20)
📚 Stacked MRs
This step is split into 4 stacked MRs to keep each within the review size limit (≈800 ideal). Each part targets the previous one (part 1 targets main) and the stack is reviewed and merged bottom-up. Part 2 of 4 of the S09 enforcement plan — Step 17: Management enforcement at the slugMiddleware seam, per S09 (authorization).
🔐 What this MR does
Replaces part 1's temporary repository-scope allow-through stub with the real arm: a scopeRepository binding resolves the named repository ahead of the decision and checks it with the namespace and organization ancestors.
- An unresolved repository name is masked as the existence-hiding 404 before any evaluation, and increments
denials_total{denial_reason="unresolved_repository"}through a narrow exported counter, so the management surface shares the decision layer's observability. The WARN-logged fail-closed arms stay uncounted, the same carve-out class as the decision core's zero-object-id guard. - An authorization-path store failure maps to 503 (matching
DecisionEvaluator's closed status set), where the handler-path resolve keeps its 500. - Every pre-existing management suite the arm's behavior change touches is adapted in this same MR (detail, create, update, delete, settings, statistics, connection test, and the bulk, container, npm, package, and file suites): the authorization-time resolve adds one
FindByNamecall ahead of each handler, an authorization-path store failure is 503 where the handler-path failure keeps its 500 (renames follow the split —_AuthorizationReaderFailure_Returns503beside a re-scoped_Returns500), and the existence-oracle assertions count both resolves. These adaptations cannot land in a separate MR in either direction: the old assertions fail once the arm lands, and the new ones fail before it does. api/openapi/v1.yamlnow declares the authorization503(sharedAuthorizationServiceUnavailableresponse, with itsRetry-AfterandCache-Controlheaders) on the 31 wired operations that can produce it, and the four pre-existing route-specific503components carry an authorization-time clause. The five*UpstreamRepository*operations are declared ahead of their routes and gain it when those routes register. No operation was added, renamed, or removed, soapi/bruno/**needs no sync. The contract suites validate the new 503s against the document.validatedRepositoryRowis extracted so the authorization-time resolve and the handler-path resolve share the nil-row and cross-namespace contract-violation guards.
⚖️ Accepted trade-offs
- Repository double-resolve (TOCTOU). A repository-scoped request resolves
{repository_name}twice: once here for the check object, once in the handler. Beyond the extra indexed query, the two reads race a delete-and-recreate under the same name: authorization approves the row the first read saw, the handler acts on the row the second read finds. This is the accepted resolution-duplication debt tracked in work item 392 ("solve once authorization is fully in place"); the management surface joins the same class rather than growing its own holder.
📏 Size
Roughly 1,200 reviewable LOC, above the stack's 1,000 target and the 500-LOC guideline deliberately — about two thirds of it mechanical test adaptation. The arm, the OpenAPI 503 declarations its status set requires, and the suites that assert the new statuses are one behavior change: landing the arm without the contract declarations fails the contract suites, and landing it without the suite adaptations fails the adapted suites, so any smaller cut leaves a part with failing tests.
🧪 e2e scenarios
No catalog under docs/testing/ covers the management API, so no scenario is added or affected; the enforcement contract is pinned by this stack's handler-level suites.
Related to #859 (closed)
Stacked MRs (review/merge bottom-up)
- feat(managementapi): enforce route bindings par... (!2139 - merged) • David Fernandez • 19.4
- feat(managementapi): enforce the repository-sco... (!2140 - merged) • David Fernandez • 19.4
👈 - test(managementapi): binding enforcement suite ... (!2141 - merged) • David Fernandez • 19.4
- feat(managementapi): gate the evaluator on IAM/... (!2142 - merged) • David Fernandez • 19.4