Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
301742c
refactor(api): route draft variable APIs through application services
41tair Oct 8, 2026
c2140a4
Merge updated stack base into refactor/api-76-10-3
41tair Oct 8, 2026
e63fe29
fix(api): preserve variable transport contracts and shared error defi…
41tair Oct 9, 2026
91af5de
fix(api): remove agent import cycle and align variable controller tests
41tair Oct 9, 2026
f3f6a81
docs(api): regenerate workflow variable endpoint contracts
41tair Oct 9, 2026
76588a8
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 9, 2026
42f66cd
Merge updated stack base into refactor/api-76-10-3
41tair Oct 9, 2026
36dca30
Merge updated refactor/api-76-10-2
41tair Oct 15, 2026
5bbf2a6
Merge updated refactor/api-76-10-2 into refactor/api-76-10-3
41tair Oct 19, 2026
939373c
Merge updated refactor/api-76-10-2 into refactor/api-76-10-3
41tair Oct 19, 2026
e8c868d
Merge updated refactor/api-76-10-2 into refactor/api-76-10-3
41tair Oct 19, 2026
a6016b9
Merge updated refactor/api-76-10-2 into refactor/api-76-10-3
41tair Oct 19, 2026
366b2ab
Merge updated refactor/api-76-10-2 into refactor/api-76-10-3
41tair Oct 20, 2026
0182371
Merge workflow migration compatibility from updated parent
41tair Oct 20, 2026
8fcc522
Merge updated refactor/api-76-10-2 into refactor/api-76-10-3
41tair Oct 26, 2026
52bae9e
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
6b329c6
Use the relocated RBAC introspection helper in variable transport tests
41tair Oct 26, 2026
11b652f
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
6c28ca3
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
81983a9
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
c5f52e2
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
7dc7f63
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
9e12c5a
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
6888988
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
11f973a
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
f77b6c6
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
3bdd004
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
bbca2a7
Sync validated refactor/api-76-10-2 fixes
41tair Oct 26, 2026
8cd78a2
Sync validated refactor/api-76-10-2 fixes
41tair Oct 27, 2026
b80360b
Sync validated refactor/api-76-10-2 fixes
41tair Oct 27, 2026
7a69622
Keep variable and annotation repositories in composition
41tair Oct 27, 2026
c97280b
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 27, 2026
c215bd6
Merge updated refactor/api-76-10-2 into refactor/api-76-10-3
41tair Oct 31, 2026
d5db09a
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
5ae42ef
Regenerate API error contracts from merged workflow variable handlers
41tair Oct 31, 2026
25a2fc4
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
78fcba5
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
d404138
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
55d5b2b
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
bb5afff
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
00351c3
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
796801c
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
71a2866
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
f164ea5
Sync validated refactor/api-76-10-2 fixes
41tair Oct 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/controllers/common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Shared HTTP adapter helpers."""
97 changes: 96 additions & 1 deletion api/controllers/common/errors.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import logging
from collections import OrderedDict
from http import HTTPStatus
from typing import cast
from typing import NoReturn, cast

from flask import current_app, got_request_exception
from werkzeug.exceptions import HTTPException

from core.logging.context import get_request_id
from libs.exception import BaseHTTPException
from libs.external_api import ExternalApi
from services.annotation.errors import AnnotationResourceNotFoundError
from services.credentials.refresh import CredentialRefreshUnavailableError
from services.errors.base import NoPermissionError
from services.errors.dataset import DatasetNotFoundError
from services.errors.document import DocumentAccessDeniedError, DocumentNotFoundError, DocumentSourceNotFoundError


class FilenameNotExistsError(BaseHTTPException):
Expand Down Expand Up @@ -167,6 +172,96 @@ class InvalidArgumentError(BaseHTTPException):
code = 400


class InvalidRequestError(BaseHTTPException):
error_code = "bad_request"
code = 400
description = "The browser (or proxy) sent a request that this server could not understand."


class AccessDeniedError(BaseHTTPException):
error_code = "forbidden"
code = 403
description = (
"You don't have the permission to access the requested resource. "
"It is either read-protected or not readable by the server."
)


class AuthenticationRequiredError(BaseHTTPException):
error_code = "unauthorized"
code = 401
description = "Authentication is required."


class UnsupportedMediaTypeError(BaseHTTPException):
error_code = "unsupported_media_type"
code = 415
description = "The server does not support the media type transmitted in the request."


class UnprocessableEntityError(BaseHTTPException):
error_code = "unprocessable_entity"
code = 422


class TooManyRequestsError(BaseHTTPException):
error_code = "too_many_requests"
code = 429
description = "This user has exceeded an allotted request count. Try again later."


class RequestBodyTooLargeError(BaseHTTPException):
error_code = "request_entity_too_large"
code = 413


def raise_unexpected_error(error: Exception, *, message: str) -> NoReturn:
"""Keep HTTP failures intact and sanitize unclassified failures at the transport boundary.

Framework request parsing and project HTTP errors may both reach a route's
error translator. Neither should be converted to a generic server error.
"""
if isinstance(error, HTTPException):
raise error
logging.getLogger(__name__).exception(message)
raise InternalServerError() from error


def register_resource_error_handlers(api: ExternalApi) -> None:
"""Translate annotation and document failures at the HTTP boundary."""

def missing_resource(error: Exception) -> tuple[dict[str, str | int], int]:
got_request_exception.send(current_app, exception=error)
return {"code": "not_found", "message": str(error), "status": 404}, 404

def document_access_denied(error: DocumentAccessDeniedError) -> tuple[dict[str, str | int], int]:
got_request_exception.send(current_app, exception=error)
return {"code": "forbidden", "message": str(error), "status": 403}, 403

for error_type in (
AnnotationResourceNotFoundError,
DatasetNotFoundError,
DocumentNotFoundError,
DocumentSourceNotFoundError,
):
api.errorhandler(error_type)(missing_resource)
cast(OrderedDict[type[Exception], object], api.error_handlers).move_to_end(error_type, last=False)
api.errorhandler(DocumentAccessDeniedError)(document_access_denied)
cast(OrderedDict[type[Exception], object], api.error_handlers).move_to_end(DocumentAccessDeniedError, last=False)
for error_type in (
AnnotationResourceNotFoundError,
DatasetNotFoundError,
DocumentNotFoundError,
DocumentSourceNotFoundError,
):
api.document_error_handler(error_type, status=HTTPStatus.NOT_FOUND, code=NotFoundError.error_code)
api.document_error_handler(
DocumentAccessDeniedError,
status=HTTPStatus.FORBIDDEN,
code=ForbiddenError.error_code,
)


def register_permission_error_handler(api: ExternalApi) -> None:
"""Preserve the default permission-error response for Console and Service API."""

Expand Down
23 changes: 23 additions & 0 deletions api/controllers/console/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
UnauthorizedError,
register_credential_refresh_error_handler,
register_permission_error_handler,
register_resource_error_handlers,
)
from controllers.console.error import NotInitValidateError, NotSetupError, UnauthorizedAndForceLogout
from controllers.console.workspace.error import AccountNotInitializedError
Expand All @@ -26,6 +27,7 @@
WorkflowRunArchiveInvalidMonthError,
WorkflowRunArchiveNotFoundError,
)
from services.workflow.variable_contracts import DraftVariableChangedError

bp = Blueprint("console", __name__, url_prefix="/console/api")
register_app_access_error_metadata(bp, surface="console")
Expand All @@ -37,6 +39,7 @@
description="Console management APIs for app configuration, monitoring, and administration",
)
register_permission_error_handler(api)
register_resource_error_handlers(api)
register_credential_refresh_error_handler(api)
# Errors the Console client handles centrally (bootstrap redirects, forced logout, malformed
# input); any operation may answer with them, so no operation lists them. The shared
Expand Down Expand Up @@ -117,6 +120,26 @@ def _handle_workflow_run_archive_download_not_ready_error(
api.document_error_handler(WorkflowRunArchiveDownloadNotReadyError, status=HTTPStatus.CONFLICT, code="conflict")


@api.errorhandler(DraftVariableChangedError)
def _handle_draft_variable_changed_error(error: DraftVariableChangedError):
"""Expose a concurrent variable edit consistently across Console use cases."""
got_request_exception.send(current_app, exception=error)
status = HTTPStatus.CONFLICT
return {
"code": "draft_variable_changed",
"message": "Variable changed; refresh and retry with the current value.",
"status": status.value,
}, status.value


cast(OrderedDict[type[Exception], object], api.error_handlers).move_to_end(DraftVariableChangedError, last=False)
api.document_error_handler(
DraftVariableChangedError,
status=HTTPStatus.CONFLICT,
code="draft_variable_changed",
)


console_ns = Namespace("console", description="Console management API operations", path="/")

RESOURCE_MODULES = (
Expand Down
Loading
Loading