feat(B.13-B.17+B.16): Error-Handling-Infra, Observability, Graceful Shutdown, Cost-Cap, API Versioning

B.13 Error-Handling-Infrastruktur:
- ErrorCategory Enum (TRANSIENT/PERMANENT/PARTIAL), ApiError erweitert
- Einheitliches Error-Response-Format: {code, detail, field, trace_id, retryable, category}
- 3 FastAPI Exception-Handler (ApiError, HTTPException, unhandled)
- classify_exception() Helper, 6 neue Error-Codes
- 28 Tests in test_error_handling.py

B.14 Observability & trace_id-Korrelation:
- trace_id pro Request (UUID4 short) in structlog contextvars
- X-Trace-Id Response-Header
- Sensitive Fields structlog processor
- llm_complete()/llm_embed() akzeptieren trace_id kwarg
- 12 Tests in test_observability.py

B.15 Graceful Shutdown & Connection Draining:
- _shutdown_event + _inflight_requests Tracking in main.py
- drain_all_connections() in ws_helpers.py
- Worker on_shutdown pausiert WorkflowInstances (status=paused)
- 8 Tests in test_graceful_shutdown.py

B.16 API Versioning Strategie:
- Plugin-Dev-Guide Kapitel 30: URL-basiertes Versioning, Breaking Change Prozess

B.17 Cost Overrun Protection:
- llm_monthly_budget_usd + llm_hard_cutoff Settings
- _check_tenant_budget() vor jedem LLM-Call
- _track_tenant_cost() in Redis (INCRBYFLOAT)
- _check_cost_alerts() bei 50%/80%/100% -> post_system_message()
- 20 Tests in test_cost_protection.py

Total: 68 neue Tests, alle grün. Keine Regressionen.
This commit is contained in:
Agent Zero
2026-08-13 21:33:14 +02:00
parent 1baa9481a2
commit b5546ea7bd
13 changed files with 1461 additions and 31 deletions
+40
View File
@@ -51,10 +51,50 @@ arq_jobs_total = Counter(
# ─── Structured Logging (structlog) ───
# Sensitive field names that should be redacted from log output
_SENSITIVE_FIELDS = frozenset({
"password", "passwd", "secret", "api_key", "apikey", "token",
"authorization", "auth", "cookie", "session_id", "session",
"private_key", "privatekey", "credentials", "smtp_password",
"mail_password", "encryption_key", "secret_key",
})
def _sanitize_sensitive_fields(logger, method_name, event_dict):
"""structlog processor that redacts sensitive field values."""
for key in list(event_dict.keys()):
if key.lower() in _SENSITIVE_FIELDS:
event_dict[key] = "***REDACTED***"
return event_dict
def sanitize_dict(data: dict[str, Any], extra_sensitive: set[str] | None = None) -> dict[str, Any]:
"""Sanitize a dictionary by redacting sensitive field values.
Args:
data: The dictionary to sanitize.
extra_sensitive: Additional field names to treat as sensitive.
Returns:
A copy of *data* with sensitive values replaced by ``"***REDACTED***"``.
"""
sensitive = _SENSITIVE_FIELDS | (extra_sensitive or set())
result = {}
for key, value in data.items():
if key.lower() in sensitive:
result[key] = "***REDACTED***"
elif isinstance(value, dict):
result[key] = sanitize_dict(value, extra_sensitive)
else:
result[key] = value
return result
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
_sanitize_sensitive_fields,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],