Merge remote-tracking branch 'origin/main' into codex/mcp-federation-runtime-20260715
This commit is contained in:
@@ -454,6 +454,9 @@ from src.services.host_runaway_aiops_loop_readiness import (
|
||||
from src.services.javascript_package_inventory import (
|
||||
load_latest_javascript_package_inventory,
|
||||
)
|
||||
from src.services.notification_matrix import (
|
||||
build_canonical_telegram_routing_runtime_readback,
|
||||
)
|
||||
from src.services.observability_contract_matrix import (
|
||||
load_latest_observability_contract_matrix,
|
||||
)
|
||||
@@ -4977,6 +4980,38 @@ async def get_telegram_alert_ai_automation_matrix() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/telegram-canonical-routing-runtime-readback",
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 Telegram 產品告警 canonical routing runtime readback",
|
||||
description=(
|
||||
"以 production gateway 使用的 fail-closed resolver 逐條解析 11 個產品與 20 條 "
|
||||
"symbolic route,分開顯示歷史 source snapshot 與目前 runtime enforcement。"
|
||||
"此端點不讀 Telegram ID/token、不呼叫 Bot API、不送訊息、不改 runtime route。"
|
||||
),
|
||||
)
|
||||
async def get_telegram_canonical_routing_runtime_readback() -> dict[str, Any]:
|
||||
"""Return the no-secret, no-send canonical Telegram runtime projection."""
|
||||
try:
|
||||
return await asyncio.to_thread(
|
||||
build_canonical_telegram_routing_runtime_readback
|
||||
)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error(
|
||||
"telegram_canonical_routing_runtime_readback_invalid",
|
||||
error=str(exc),
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Telegram canonical routing runtime readback 無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-gitea-pr-draft-lane",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -13,6 +13,7 @@ import json
|
||||
import re
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -337,6 +338,92 @@ def load_canonical_telegram_routing_registry(
|
||||
return payload
|
||||
|
||||
|
||||
def build_canonical_telegram_routing_runtime_readback(
|
||||
security_dir: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Project the committed registry through the live fail-closed resolver.
|
||||
|
||||
The source snapshot deliberately records the no-send boundaries of the run
|
||||
that created it. Once the gateway guard is deployed, that historical
|
||||
``source_status`` must not be presented as the current runtime state. This
|
||||
readback resolves every route with the same code path used before Telegram
|
||||
provider calls and returns symbolic aliases only.
|
||||
"""
|
||||
|
||||
registry = load_canonical_telegram_routing_registry(security_dir)
|
||||
resolved_routes: list[dict[str, Any]] = []
|
||||
for route in registry["routes"]:
|
||||
decision = resolve_canonical_telegram_route(
|
||||
route["product_id"],
|
||||
route["signal_family"],
|
||||
route["severity"][0],
|
||||
requested_destination=route["allowed_destination"],
|
||||
registry=registry,
|
||||
)
|
||||
resolved_routes.append(
|
||||
{
|
||||
**route,
|
||||
"decision": decision["decision"],
|
||||
"decision_reason": decision["reason"],
|
||||
}
|
||||
)
|
||||
|
||||
allowed_route_count = sum(
|
||||
route["decision"] == "allow" for route in resolved_routes
|
||||
)
|
||||
blocked_route_count = len(resolved_routes) - allowed_route_count
|
||||
unknown_probe = resolve_canonical_telegram_route(
|
||||
"unknown-product",
|
||||
"unknown-signal",
|
||||
"P1",
|
||||
registry=registry,
|
||||
)
|
||||
|
||||
return {
|
||||
"schema_version": "telegram_canonical_routing_runtime_readback_v1",
|
||||
"status": "runtime_policy_enforced_product_delivery_receipts_partial",
|
||||
"source_status": registry["status"],
|
||||
"source_mode": registry["mode"],
|
||||
"source_generated_at": registry["generated_at"],
|
||||
"runtime_generated_at": datetime.now(UTC).isoformat(),
|
||||
"runtime_enforcement": {
|
||||
"canonical_resolver_active": True,
|
||||
"gateway_pre_send_gate_active": True,
|
||||
"trusted_ingress_context_required": True,
|
||||
"unknown_route_decision": unknown_probe["decision"],
|
||||
"unknown_route_reason": unknown_probe["reason"],
|
||||
"raw_numeric_destination_count": registry["rollups"][
|
||||
"raw_numeric_destination_count"
|
||||
],
|
||||
"destination_identity_exposed": False,
|
||||
"all_product_delivery_receipts_ready": blocked_route_count == 0,
|
||||
},
|
||||
"rollups": {
|
||||
**registry["rollups"],
|
||||
"runtime_allowed_route_count": allowed_route_count,
|
||||
"runtime_blocked_route_count": blocked_route_count,
|
||||
},
|
||||
"policy": registry["policy"],
|
||||
"destination_roles": registry["destination_roles"],
|
||||
"products": registry["products"],
|
||||
"routes": resolved_routes,
|
||||
"operation_boundaries": {
|
||||
"read_only": True,
|
||||
"telegram_send_performed": False,
|
||||
"bot_api_call_performed": False,
|
||||
"runtime_route_changed": False,
|
||||
"secret_value_read": False,
|
||||
"raw_identifier_included": False,
|
||||
},
|
||||
"source_refs": {
|
||||
"registry": "docs/security/telegram-canonical-routing-registry.snapshot.json",
|
||||
"resolver": "apps/api/src/services/notification_matrix.py",
|
||||
"gateway_gate": "apps/api/src/services/telegram_gateway.py",
|
||||
"verifier": "apps/api/tests/test_telegram_canonical_routing_registry.py",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def resolve_canonical_telegram_route(
|
||||
product_id: str,
|
||||
signal_family: str,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1 import agents
|
||||
from src.api.v1.agents import router
|
||||
|
||||
|
||||
def _client() -> TestClient:
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def test_canonical_routing_runtime_readback_endpoint(monkeypatch) -> None:
|
||||
payload = {
|
||||
"schema_version": "telegram_canonical_routing_runtime_readback_v1",
|
||||
"status": "runtime_policy_enforced_product_delivery_receipts_partial",
|
||||
"operation_boundaries": {
|
||||
"telegram_send_performed": False,
|
||||
"secret_value_read": False,
|
||||
},
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
agents,
|
||||
"build_canonical_telegram_routing_runtime_readback",
|
||||
lambda: payload,
|
||||
)
|
||||
|
||||
response = _client().get(
|
||||
"/api/v1/agents/telegram-canonical-routing-runtime-readback"
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == payload
|
||||
|
||||
|
||||
def test_canonical_routing_runtime_readback_rejects_invalid_source(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
def invalid_source() -> dict:
|
||||
raise ValueError("invalid registry")
|
||||
|
||||
monkeypatch.setattr(
|
||||
agents,
|
||||
"build_canonical_telegram_routing_runtime_readback",
|
||||
invalid_source,
|
||||
)
|
||||
|
||||
response = _client().get(
|
||||
"/api/v1/agents/telegram-canonical-routing-runtime-readback"
|
||||
)
|
||||
|
||||
assert response.status_code == 500
|
||||
assert response.json() == {
|
||||
"detail": "Telegram canonical routing runtime readback 無效"
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
import yaml
|
||||
|
||||
from src.services.notification_matrix import (
|
||||
build_canonical_telegram_routing_runtime_readback,
|
||||
get_trusted_alert_canonical_route_context,
|
||||
load_canonical_telegram_routing_registry,
|
||||
resolve_canonical_telegram_route,
|
||||
@@ -30,6 +31,42 @@ EXPECTED_PRODUCTS = {
|
||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
|
||||
|
||||
def test_runtime_readback_separates_historical_source_state_from_live_gate() -> None:
|
||||
readback = build_canonical_telegram_routing_runtime_readback()
|
||||
|
||||
assert readback["status"] == (
|
||||
"runtime_policy_enforced_product_delivery_receipts_partial"
|
||||
)
|
||||
assert readback["source_status"] == "source_policy_ready_runtime_not_applied"
|
||||
assert readback["source_generated_at"] == "2026-07-14T15:50:04+08:00"
|
||||
assert readback["runtime_generated_at"].endswith("+00:00")
|
||||
assert readback["runtime_enforcement"] == {
|
||||
"canonical_resolver_active": True,
|
||||
"gateway_pre_send_gate_active": True,
|
||||
"trusted_ingress_context_required": True,
|
||||
"unknown_route_decision": "block",
|
||||
"unknown_route_reason": "unknown_product_signal_or_severity",
|
||||
"raw_numeric_destination_count": 0,
|
||||
"destination_identity_exposed": False,
|
||||
"all_product_delivery_receipts_ready": False,
|
||||
}
|
||||
assert readback["rollups"]["runtime_allowed_route_count"] == 4
|
||||
assert readback["rollups"]["runtime_blocked_route_count"] == 16
|
||||
assert len(readback["products"]) == 11
|
||||
assert len(readback["routes"]) == 20
|
||||
assert readback["operation_boundaries"] == {
|
||||
"read_only": True,
|
||||
"telegram_send_performed": False,
|
||||
"bot_api_call_performed": False,
|
||||
"runtime_route_changed": False,
|
||||
"secret_value_read": False,
|
||||
"raw_identifier_included": False,
|
||||
}
|
||||
serialized = json.dumps(readback, ensure_ascii=False)
|
||||
assert '"chat_id"' not in serialized
|
||||
assert '"bot_token"' not in serialized
|
||||
|
||||
|
||||
def test_namespaced_product_ingress_route_is_explicit_and_policy_blocked() -> None:
|
||||
context = get_trusted_alert_canonical_route_context(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user