Artur Mukhamadiev 5aad7a0b45 backend: admission queue with reservations
In-memory AdmissionController (idle/reserved/running, FIFO tickets keyed by
the session cookie, reservation and heartbeat expiry, promotion after
cleanup, EMA wait estimate) behind POST /api/v1/queue/join,
GET /api/v1/queue/status and DELETE /api/v1/queue/ticket. POST /api/v1/query
claims the session's reservation first and joins implicitly when idle.
Settings: CONFLUENCE_WEB_QUEUE_{RESERVATION_SECONDS,HEARTBEAT_SECONDS,MAX_LENGTH}.
2026-09-15 13:38:59 +03:00

138 lines
4.6 KiB
Python

"""Sanitized application errors and HTTP/protocol mappings."""
from __future__ import annotations
import re
_BEARER_PATTERN = re.compile(r"Bearer\s+[A-Za-z0-9_\-\.]+", re.IGNORECASE)
_TOKEN_PATTERN = re.compile(r"\b[A-Za-z0-9_\-]{32,}\b")
def sanitize_message(msg: str, max_bytes: int = 1024) -> str:
"""Sanitize and bound an error message to max_bytes UTF-8, stripping secret lookalikes."""
if not msg:
return ""
# Strip potential token/secret lookalikes
cleaned = _BEARER_PATTERN.sub("Bearer [REDACTED]", msg)
cleaned = _TOKEN_PATTERN.sub("[REDACTED]", cleaned)
# Strip newlines and carriage returns
cleaned = cleaned.replace("\r", " ").replace("\n", " ")
encoded = cleaned.encode("utf-8")
if len(encoded) <= max_bytes:
return cleaned
truncated = encoded[:max_bytes]
# Ensure valid UTF-8 boundary
return truncated.decode("utf-8", errors="ignore")
class AppError(Exception):
"""Base application error with sanitized error code and message."""
def __init__(self, code: str, message: str, status_code: int = 500):
super().__init__(message)
self.code = code
self.message = sanitize_message(message)
self.status_code = status_code
def to_error_dict(self) -> dict:
return {
"code": self.code,
"message": self.message,
}
def to_envelope(self) -> dict:
return {
"error": self.to_error_dict(),
}
class InvalidInputError(AppError):
def __init__(self, message: str = "Invalid input"):
super().__init__("invalid_input", message, status_code=400)
class ModelContextExceededError(AppError):
def __init__(self, message: str = "Model context window exceeded"):
super().__init__("model_context_exceeded", message, status_code=400)
class OriginDeniedError(AppError):
def __init__(self, message: str = "Origin denied by policy"):
super().__init__("origin_denied", message, status_code=403)
class DestinationDeniedError(AppError):
def __init__(self, message: str = "Destination denied by policy"):
super().__init__("destination_denied", message, status_code=403)
class ConfluenceAuthFailedError(AppError):
def __init__(self, message: str = "Confluence authentication failed"):
super().__init__("confluence_auth_failed", message, status_code=403)
class ArtifactNotFoundError(AppError):
def __init__(self, message: str = "Artifact not found"):
super().__init__("artifact_not_found", message, status_code=404)
class BusyError(AppError):
def __init__(self, message: str = "Active query already in progress"):
super().__init__("busy", message, status_code=409)
class RequestTooLargeError(AppError):
def __init__(self, message: str = "Request body exceeds maximum allowed size"):
super().__init__("request_too_large", message, status_code=413)
class UpstreamFailedError(AppError):
def __init__(self, message: str = "Upstream request failed"):
super().__init__("upstream_failed", message, status_code=502)
class UpstreamResponseTooLargeError(AppError):
def __init__(self, message: str = "Upstream response exceeds maximum allowed size"):
super().__init__("upstream_response_too_large", message, status_code=502)
class TlsFailedError(AppError):
def __init__(self, message: str = "TLS certificate verification failed"):
super().__init__("tls_failed", message, status_code=502)
class ConnectivityFailedError(AppError):
def __init__(self, message: str = "Failed to connect to upstream server"):
super().__init__("connectivity_failed", message, status_code=502)
class ModelOutputLimitError(AppError):
def __init__(self, message: str = "Model output exceeded limit"):
super().__init__("model_output_limit", message, status_code=502)
class QueryTimeoutError(AppError):
def __init__(self, message: str = "Query execution timed out"):
super().__init__("query_timeout", message, status_code=504)
class ExecutionFailedError(AppError):
def __init__(self, message: str = "Query execution failed"):
super().__init__("execution_failed", message, status_code=500)
class CleanupFailedError(AppError):
def __init__(self, message: str = "Container cleanup could not be confirmed"):
super().__init__("cleanup_failed", message, status_code=500)
class QueueFullError(AppError):
def __init__(self, message: str = "The queue is full"):
super().__init__("queue_full", message, status_code=503)
class TicketNotFoundError(AppError):
def __init__(self, message: str = "Ticket not found"):
super().__init__("ticket_not_found", message, status_code=404)