fix(awooop): fail soft readback list endpoints
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m27s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m27s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -108,16 +108,29 @@ async def get_governance_events(
|
||||
page=page,
|
||||
size=size,
|
||||
)
|
||||
return await query_governance_events(
|
||||
event_ids=event_id,
|
||||
event_types=event_type,
|
||||
from_dt=from_,
|
||||
to_dt=to,
|
||||
status=status,
|
||||
severity=severity,
|
||||
page=page,
|
||||
size=size,
|
||||
)
|
||||
try:
|
||||
return await query_governance_events(
|
||||
event_ids=event_id,
|
||||
event_types=event_type,
|
||||
from_dt=from_,
|
||||
to_dt=to,
|
||||
status=status,
|
||||
severity=severity,
|
||||
page=page,
|
||||
size=size,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"governance_events_readback_degraded",
|
||||
event_ids=event_id,
|
||||
event_types=event_type,
|
||||
status=status,
|
||||
severity=severity,
|
||||
page=page,
|
||||
size=size,
|
||||
error_type=type(exc).__name__,
|
||||
)
|
||||
return GovernanceEventsResponse(items=[], total=0, page=page, size=size)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -12,12 +12,15 @@ from decimal import Decimal
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
import structlog
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
|
||||
from src.services.platform_operator_service import list_tenants as list_tenants_svc
|
||||
from src.utils.timezone import now_taipei
|
||||
|
||||
router = APIRouter()
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class TenantItem(BaseModel):
|
||||
@@ -114,6 +117,48 @@ class ListTenantsResponse(BaseModel):
|
||||
asset_inventory: TenantAssetInventory
|
||||
|
||||
|
||||
def _tenant_readback_fallback() -> dict[str, Any]:
|
||||
created_at = now_taipei()
|
||||
return {
|
||||
"tenants": [
|
||||
{
|
||||
"project_id": "awoooi",
|
||||
"display_name": "AWOOOI",
|
||||
"migration_mode": "readback_degraded",
|
||||
"budget_limit_usd": None,
|
||||
"is_active": True,
|
||||
"created_at": created_at,
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"asset_inventory": {
|
||||
"schema_version": "tenant_asset_inventory_v1",
|
||||
"mode": "readback_degraded_ai_controlled_repair",
|
||||
"evidence_refs": [],
|
||||
"summary": {
|
||||
"tenant_table_count": 0,
|
||||
"product_surface_count": 0,
|
||||
"public_route_count": 0,
|
||||
"public_gateway_snapshot_route_count": 0,
|
||||
"source_candidate_repo_count": 0,
|
||||
"source_in_scope_repo_count": 0,
|
||||
"source_primary_ready_count": 0,
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"runtime_gate_count": 0,
|
||||
"action_button_count": 0,
|
||||
},
|
||||
"products": [],
|
||||
"public_routes": [],
|
||||
"source_repos": [],
|
||||
"boundaries": [
|
||||
"tenant readback degraded; AI controlled retry required",
|
||||
"fallback is read-only and does not modify tenants, routes, repos, workflows, or runtime",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/tenants",
|
||||
response_model=ListTenantsResponse,
|
||||
@@ -124,4 +169,11 @@ class ListTenantsResponse(BaseModel):
|
||||
),
|
||||
)
|
||||
async def list_tenants() -> dict[str, Any]:
|
||||
return await list_tenants_svc()
|
||||
try:
|
||||
return await list_tenants_svc()
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"platform_tenants_readback_degraded",
|
||||
error_type=type(exc).__name__,
|
||||
)
|
||||
return _tenant_readback_fallback()
|
||||
|
||||
@@ -233,6 +233,19 @@ class TestEventsEndpoint:
|
||||
assert captured["event_ids"] == ["evt-001", "evt-002"]
|
||||
assert captured["status"] == "unresolved"
|
||||
|
||||
def test_events_readback_failure_returns_empty_page(self, client):
|
||||
"""readback exception 應降級成 empty page,避免治理頁看到 500."""
|
||||
|
||||
async def mock_query(**_kwargs):
|
||||
raise RuntimeError("simulated governance readback failure")
|
||||
|
||||
with patch("src.api.v1.ai_governance.query_governance_events", new=mock_query):
|
||||
r = client.get("/api/v1/ai/governance/events?page=2&size=10")
|
||||
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data == {"items": [], "total": 0, "page": 2, "size": 10}
|
||||
|
||||
def test_invalid_severity_rejected(self, client):
|
||||
"""非法 severity 值應被拒絕(422)."""
|
||||
r = client.get("/api/v1/ai/governance/events?severity=bad_value")
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
|
||||
from src.api.v1.platform.tenants import ListTenantsResponse
|
||||
from src.api.v1.platform.tenants import ListTenantsResponse, _tenant_readback_fallback
|
||||
from src.services.platform_operator_service import build_tenant_asset_inventory
|
||||
|
||||
|
||||
@@ -145,3 +145,16 @@ def test_tenant_response_model_keeps_asset_inventory_contract() -> None:
|
||||
assert "nexu-io" not in response_payload
|
||||
assert all(marker not in response_payload for marker in FORBIDDEN_PUBLIC_MARKERS)
|
||||
assert response.asset_inventory.source_repos[0].source_namespace_redacted is True
|
||||
|
||||
|
||||
def test_tenant_readback_fallback_keeps_response_model_no_write_contract() -> None:
|
||||
response = ListTenantsResponse.model_validate(_tenant_readback_fallback())
|
||||
|
||||
assert response.tenants[0].project_id == "awoooi"
|
||||
assert response.tenants[0].migration_mode == "readback_degraded"
|
||||
assert response.asset_inventory.mode == "readback_degraded_ai_controlled_repair"
|
||||
assert response.asset_inventory.summary.tenant_table_count == 0
|
||||
assert response.asset_inventory.products == []
|
||||
assert response.asset_inventory.public_routes == []
|
||||
assert response.asset_inventory.source_repos == []
|
||||
assert "does not modify" in " ".join(response.asset_inventory.boundaries)
|
||||
|
||||
Reference in New Issue
Block a user