From adefe783a687182467b36a87f76f668bdc9ab886 Mon Sep 17 00:00:00 2001 From: Aleksandr Barkhatov Date: Mon, 3 Aug 2026 15:01:34 +0800 Subject: [PATCH 1/2] fix(analytics): declare real error codes for metric-definitions `.standard_errors` stamps a fixed {400,401,403,404,409,429,500} on an operation with no way to select statuses, so the listing advertised six codes it cannot answer: no license gate and no per-request authz (403), no request body, no path or query params (400), no lookup (404), no conflict path (409), no rate limiter (429). The handler resolves tenant scope from the session and reaches only `CanonicalError::internal`, so the operation answers 200, 500, or the gateway's 401 when the bearer security requirement is unsatisfied. Refs #1669 Signed-off-by: Aleksandr Barkhatov --- .../components/backend/analytics/openapi.json | 50 ------------------- src/backend/services/analytics/src/api/mod.rs | 3 +- 2 files changed, 2 insertions(+), 51 deletions(-) diff --git a/docs/components/backend/analytics/openapi.json b/docs/components/backend/analytics/openapi.json index 1debe37ac..223eff91f 100644 --- a/docs/components/backend/analytics/openapi.json +++ b/docs/components/backend/analytics/openapi.json @@ -3209,16 +3209,6 @@ }, "description": "Metric definitions" }, - "400": { - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - }, - "description": "Bad Request" - }, "401": { "content": { "application/problem+json": { @@ -3229,46 +3219,6 @@ }, "description": "Unauthorized" }, - "403": { - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - }, - "description": "Forbidden" - }, - "404": { - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - }, - "description": "Not Found" - }, - "409": { - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - }, - "description": "Conflict" - }, - "429": { - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - }, - "description": "Too Many Requests" - }, "500": { "content": { "application/problem+json": { diff --git a/src/backend/services/analytics/src/api/mod.rs b/src/backend/services/analytics/src/api/mod.rs index f158dccfc..e7b596b38 100644 --- a/src/backend/services/analytics/src/api/mod.rs +++ b/src/backend/services/analytics/src/api/mod.rs @@ -480,7 +480,8 @@ fn build_operations(router: Router, openapi: &dyn OpenApiRegistry) -> Router { StatusCode::OK, "Metric definitions", ) - .standard_errors(openapi) + .error_401(openapi) + .error_500(openapi) .handler(metric_definitions::list_metric_definitions) .register(router, openapi); From 15c16f645175882a27d72c78b4b27a4fa672bc18 Mon Sep 17 00:00:00 2001 From: Aleksandr Barkhatov Date: Mon, 3 Aug 2026 15:28:53 +0800 Subject: [PATCH 2/2] fix(analytics): declare real error codes for metric-results/drilldown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both operations carried the same fixed `.standard_errors` set. Neither has a license gate or conflict path, so 403/409 were unanswerable on drilldown and 404/409 on metric-results — an unknown `metric_key` there resolves to a 400 (`unavailable`), pinned by its contract test. Declaring per route also picks up codes the boilerplate omitted: 415 on both (wrong Content-Type at the `Json` extractor), 429 on drilldown (its own concurrency cap), and 404 on drilldown alone, where an unknown `metric_key` is a genuine not-found. Drops the metric-results BLOCKED entry from the coverage gate: it excluded 404/409, neither of which the operation declares now. Refs #1669 Signed-off-by: Aleksandr Barkhatov --- .../components/backend/analytics/openapi.json | 38 ++----------------- src/backend/services/analytics/src/api/mod.rs | 13 ++++++- .../tests/e2e/api/test_metric_results.py | 2 +- src/ingestion/tests/e2e/lib/api_coverage.py | 2 - 4 files changed, 16 insertions(+), 39 deletions(-) diff --git a/docs/components/backend/analytics/openapi.json b/docs/components/backend/analytics/openapi.json index 223eff91f..1bd5f1003 100644 --- a/docs/components/backend/analytics/openapi.json +++ b/docs/components/backend/analytics/openapi.json @@ -3283,16 +3283,6 @@ }, "description": "Unauthorized" }, - "403": { - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - }, - "description": "Forbidden" - }, "404": { "content": { "application/problem+json": { @@ -3303,7 +3293,7 @@ }, "description": "Not Found" }, - "409": { + "415": { "content": { "application/problem+json": { "schema": { @@ -3311,7 +3301,7 @@ } } }, - "description": "Conflict" + "description": "Unsupported Media Type" }, "429": { "content": { @@ -3397,17 +3387,7 @@ }, "description": "Forbidden" }, - "404": { - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - }, - "description": "Not Found" - }, - "409": { + "415": { "content": { "application/problem+json": { "schema": { @@ -3415,17 +3395,7 @@ } } }, - "description": "Conflict" - }, - "429": { - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - }, - "description": "Too Many Requests" + "description": "Unsupported Media Type" }, "500": { "content": { diff --git a/src/backend/services/analytics/src/api/mod.rs b/src/backend/services/analytics/src/api/mod.rs index e7b596b38..6d8bc9b24 100644 --- a/src/backend/services/analytics/src/api/mod.rs +++ b/src/backend/services/analytics/src/api/mod.rs @@ -235,7 +235,11 @@ fn build_operations(router: Router, openapi: &dyn OpenApiRegistry) -> Router { StatusCode::OK, "Metric results", ) - .standard_errors(openapi) + .error_400(openapi) + .error_401(openapi) + .error_403(openapi) + .error_415(openapi) + .error_500(openapi) .handler(metric_results::query_metric_results) .register(router, openapi); @@ -345,7 +349,12 @@ fn build_operations(router: Router, openapi: &dyn OpenApiRegistry) -> Router { StatusCode::OK, "Metric evidence", ) - .standard_errors(openapi) + .error_400(openapi) + .error_401(openapi) + .error_404(openapi) + .error_415(openapi) + .error_429(openapi) + .error_500(openapi) .handler(metric_drilldown::query_metric_drilldown) .register(router, openapi); diff --git a/src/ingestion/tests/e2e/api/test_metric_results.py b/src/ingestion/tests/e2e/api/test_metric_results.py index afaa698c6..30ffa0d89 100644 --- a/src/ingestion/tests/e2e/api/test_metric_results.py +++ b/src/ingestion/tests/e2e/api/test_metric_results.py @@ -64,7 +64,7 @@ def test_metric_results_400_reversed_period(api) -> None: def test_metric_results_400_unknown_metric_key(api) -> None: """An unknown `metric_key` is resolved against the catalog and rejected as a 400 (`unavailable`) — NOT a 404. Pins that the compute endpoint has no - not-found path (the spec's declared 404 is `.standard_errors` boilerplate).""" + not-found path, which is why the spec declares no 404 for it.""" body = _request( metrics=[{"metric_key": "e2e.definitely-not-a-real-metric", "views": [{"view": "period"}]}], ) diff --git a/src/ingestion/tests/e2e/lib/api_coverage.py b/src/ingestion/tests/e2e/lib/api_coverage.py index 3900861eb..d9c240707 100755 --- a/src/ingestion/tests/e2e/lib/api_coverage.py +++ b/src/ingestion/tests/e2e/lib/api_coverage.py @@ -76,8 +76,6 @@ "POST /v1/admin/metric-thresholds": frozenset({404, 409}), # 404 boilerplate; 409=#1664 # persons 200/404 covered via the in-process Identity stub (#1691); rest boilerplate "GET /v1/persons/{email}": frozenset({400, 403, 409}), - # 404/409 boilerplate; 403 IS reachable (person outside the caller's visible set) - "POST /v1/metric-results": frozenset({404, 409}), # saved-query CRUD + run (#1965): 403 (no role gate — cross-tenant is 404 by # opacity) and 409 (no conflict path) are `.standard_errors` boilerplate. "GET /v1/queries": frozenset({400, 403, 404, 409}), # boilerplate: list, no input/lookup/conflict