feat(governance): 顯示異地 escrow 準備度
Some checks failed
CD Pipeline / tests (push) Successful in 1m32s
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / build-and-deploy (push) Successful in 5m41s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-05 02:11:44 +08:00
parent d7b5dfd85e
commit 4360628864
16 changed files with 1337 additions and 37 deletions

View File

@@ -56,6 +56,9 @@ from src.services.backup_notification_policy import (
from src.services.backup_restore_drill_approval_package_template import (
load_latest_backup_restore_drill_approval_package_template,
)
from src.services.offsite_escrow_readiness_status import (
load_latest_offsite_escrow_readiness_status,
)
from src.services.package_supply_chain_inventory import (
load_latest_package_supply_chain_inventory,
)
@@ -583,6 +586,34 @@ async def get_backup_restore_drill_approval_package_template() -> dict[str, Any]
) from exc
@router.get(
"/offsite-escrow-readiness-status",
response_model=dict[str, Any],
summary="取得異地 / Escrow 準備度狀態",
description=(
"讀取最新已提交的異地備份、credential escrow 與 K8s resource offsite readiness 狀態;"
"此端點只回傳 read-only status不執行 backup、restore、offsite sync、"
"不寫 credential marker、不讀 credential、不輸出 secret 明文、不改排程、不寫 workflow、"
"不送 Telegram 測試通知、不做破壞性 prune、不改生產路由。"
),
)
async def get_offsite_escrow_readiness_status() -> dict[str, Any]:
"""Return the latest read-only offsite / escrow readiness status."""
try:
return await asyncio.to_thread(load_latest_offsite_escrow_readiness_status)
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("offsite_escrow_readiness_status_invalid", error=str(exc))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="異地 / Escrow 準備度狀態快照無效",
) from exc
@router.get(
"/package-supply-chain-inventory",
response_model=dict[str, Any],

View File

@@ -0,0 +1,160 @@
"""
Offsite / escrow readiness status snapshot.
Loads the latest committed, read-only offsite / escrow readiness status. The
status view never runs backups, restores, offsite sync, credential marker
writes, credential reads, schedule changes, workflow writes, Telegram test
notifications, destructive prune, or production routing changes.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from src.services.snapshot_paths import default_evaluations_dir
_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__))
_SNAPSHOT_PATTERN = "offsite_escrow_readiness_status_*.json"
_SCHEMA_VERSION = "offsite_escrow_readiness_status_v1"
def load_latest_offsite_escrow_readiness_status(
evaluations_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the newest committed offsite / escrow readiness status snapshot."""
directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
if not candidates:
raise FileNotFoundError(f"no offsite / escrow readiness status snapshots found in {directory}")
latest = candidates[-1]
with latest.open(encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict):
raise ValueError(f"{latest}: expected JSON object")
_require_schema(payload, _SCHEMA_VERSION, str(latest))
_require_read_only_boundaries(payload, str(latest))
_require_operation_boundaries(payload, str(latest))
_require_rollup_consistency(payload, str(latest))
_require_redacted_cards(payload, str(latest))
return payload
def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None:
actual = payload.get("schema_version")
if actual != expected:
raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}")
def _require_read_only_boundaries(payload: dict[str, Any], label: str) -> None:
program_status = payload.get("program_status") or {}
if program_status.get("read_only_mode") is not True:
raise ValueError(f"{label}: program_status.read_only_mode must be true")
boundaries = payload.get("approval_boundaries") or {}
blocked_flags = {
"sdk_installation_allowed",
"paid_api_call_allowed",
"shadow_or_canary_allowed",
"production_routing_allowed",
"destructive_operation_allowed",
"restore_execution_allowed",
"offsite_sync_execution_allowed",
"credential_marker_write_allowed",
}
allowed = sorted(flag for flag in blocked_flags if boundaries.get(flag) is not False)
if allowed:
raise ValueError(f"{label}: approval boundaries must remain false: {allowed}")
def _require_operation_boundaries(payload: dict[str, Any], label: str) -> None:
boundaries = payload.get("operation_boundaries") or {}
if boundaries.get("read_only_status_allowed") is not True:
raise ValueError(f"{label}: read_only_status_allowed must be true")
blocked_flags = {
"backup_execution_allowed",
"restore_execution_allowed",
"offsite_sync_execution_allowed",
"credential_marker_write_allowed",
"credential_read_allowed",
"secret_plaintext_allowed",
"schedule_change_allowed",
"workflow_write_allowed",
"telegram_test_notification_allowed",
"destructive_prune_allowed",
"production_routing_allowed",
}
allowed = sorted(flag for flag in blocked_flags if boundaries.get(flag) is not False)
if allowed:
raise ValueError(f"{label}: operation boundaries must remain false: {allowed}")
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
cards = payload.get("readiness_cards") or []
rollups = payload.get("rollups") or {}
if rollups.get("total_cards") != len(cards):
raise ValueError(f"{label}: rollups.total_cards must match readiness_cards")
verified_offsite = {
card.get("card_id")
for card in cards
if card.get("kind") == "offsite_mirror" and card.get("readiness") == "verified"
}
if set(rollups.get("verified_offsite_card_ids") or []) != verified_offsite:
raise ValueError(f"{label}: rollups.verified_offsite_card_ids must match verified offsite cards")
blocked_escrow = {
card.get("card_id")
for card in cards
if card.get("kind") == "credential_escrow" and card.get("readiness") == "blocked"
}
if set(rollups.get("blocked_escrow_card_ids") or []) != blocked_escrow:
raise ValueError(f"{label}: rollups.blocked_escrow_card_ids must match blocked escrow cards")
action_required = {
card.get("card_id")
for card in cards
if card.get("readiness") == "action_required"
}
if set(rollups.get("action_required_card_ids") or []) != action_required:
raise ValueError(f"{label}: rollups.action_required_card_ids must match action_required cards")
execution_blocked = {
card.get("card_id")
for card in cards
if any(operation.endswith("_execution") or operation == "credential_marker_write" for operation in card.get("blocked_operations", []))
}
if set(rollups.get("execution_blocked_card_ids") or []) != execution_blocked:
raise ValueError(f"{label}: rollups.execution_blocked_card_ids must match cards with blocked execution operations")
def _require_redacted_cards(payload: dict[str, Any], label: str) -> None:
cards = payload.get("readiness_cards") or []
forbidden_exposure = {
"plaintext",
"secret_plaintext",
"credential_plaintext",
"token_visible",
}
exposed = sorted(
card.get("card_id")
for card in cards
if card.get("credential_exposure_status") in forbidden_exposure
)
if exposed:
raise ValueError(f"{label}: credential exposure must stay redacted: {exposed}")
contract = payload.get("operator_contract") or {}
must_not_interpret_as = set(contract.get("must_not_interpret_as") or [])
required_denials = {
"復原批准",
"異地同步批准",
"credential marker 寫入批准",
"完整 DR 綠燈",
}
if not required_denials.issubset(must_not_interpret_as):
raise ValueError(f"{label}: operator_contract.must_not_interpret_as is missing required denials")

View File

@@ -18,12 +18,12 @@ def test_ai_agent_automation_backlog_snapshot_endpoint_returns_committed_snapsho
assert data["schema_version"] == "ai_agent_automation_backlog_v1"
assert data["program_status"]["overall_completion_percent"] == 100
assert data["program_status"]["read_only_mode"] is True
assert data["program_status"]["current_task_id"] == "P1-105"
assert data["program_status"]["next_task_id"] == "P1-106"
assert data["rollups"]["total_items"] == len(data["backlog_items"]) == 20
assert data["rollups"]["by_priority"]["P1"] == 18
assert data["rollups"]["by_status"]["done"] == 13
assert data["rollups"]["by_gate_status"]["read_only_allowed"] == 17
assert data["program_status"]["current_task_id"] == "P1-106"
assert data["program_status"]["next_task_id"] == "P1-305"
assert data["rollups"]["total_items"] == len(data["backlog_items"]) == 21
assert data["rollups"]["by_priority"]["P1"] == 19
assert data["rollups"]["by_status"]["done"] == 14
assert data["rollups"]["by_gate_status"]["read_only_allowed"] == 18
assert data["approval_boundaries"]["sdk_installation_allowed"] is False
assert data["approval_boundaries"]["paid_api_call_allowed"] is False
assert data["approval_boundaries"]["production_routing_allowed"] is False
@@ -33,4 +33,5 @@ def test_ai_agent_automation_backlog_snapshot_endpoint_returns_committed_snapsho
assert any(item["item_id"] == "AUTO-P1-103" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-104" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-105" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-106" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P3-001" for item in data["backlog_items"])

View File

@@ -18,8 +18,8 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
assert data["schema_version"] == "ai_agent_automation_inventory_snapshot_v1"
assert data["program_status"]["overall_completion_percent"] == 100
assert data["program_status"]["read_only_mode"] is True
assert data["program_status"]["current_task_id"] == "P1-105"
assert data["program_status"]["next_task_id"] == "P1-106"
assert data["program_status"]["current_task_id"] == "P1-106"
assert data["program_status"]["next_task_id"] == "P1-305"
assert data["approval_boundaries"]["sdk_installation_allowed"] is False
assert data["approval_boundaries"]["paid_api_call_allowed"] is False
assert data["approval_boundaries"]["production_routing_allowed"] is False
@@ -30,6 +30,7 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
assert any(task["task_id"] == "P1-103" for task in data["tasks"])
assert any(task["task_id"] == "P1-104" for task in data["tasks"])
assert any(task["task_id"] == "P1-105" for task in data["tasks"])
assert any(task["task_id"] == "P1-106" for task in data["tasks"])
assert any(evidence["evidence_id"] == "dependency_risk_policy_api" for evidence in data["evidence"])
assert any(evidence["evidence_id"] == "dependency_drift_check_plan_api" for evidence in data["evidence"])
assert any(
@@ -42,3 +43,4 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
evidence["evidence_id"] == "backup_restore_drill_approval_package_template_api"
for evidence in data["evidence"]
)
assert any(evidence["evidence_id"] == "offsite_escrow_readiness_status_api" for evidence in data["evidence"])

View File

@@ -0,0 +1,213 @@
from __future__ import annotations
import json
import pytest
from src.services.offsite_escrow_readiness_status import (
load_latest_offsite_escrow_readiness_status,
)
def test_load_latest_offsite_escrow_readiness_status_reads_newest_file(tmp_path):
older = _snapshot(generated_at="2026-06-04T00:00:00+08:00", completion=40)
newer = _snapshot(generated_at="2026-06-05T00:00:00+08:00", completion=100)
(tmp_path / "offsite_escrow_readiness_status_2026-06-04.json").write_text(
json.dumps(older),
encoding="utf-8",
)
(tmp_path / "offsite_escrow_readiness_status_2026-06-05.json").write_text(
json.dumps(newer),
encoding="utf-8",
)
loaded = load_latest_offsite_escrow_readiness_status(tmp_path)
assert loaded["generated_at"] == "2026-06-05T00:00:00+08:00"
assert loaded["program_status"]["overall_completion_percent"] == 100
assert loaded["rollups"]["total_cards"] == 3
def test_offsite_escrow_readiness_status_requires_read_only_mode(tmp_path):
snapshot = _snapshot()
snapshot["program_status"]["read_only_mode"] = False
(tmp_path / "offsite_escrow_readiness_status_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="read_only_mode"):
load_latest_offsite_escrow_readiness_status(tmp_path)
def test_offsite_escrow_readiness_status_requires_blocked_approval_boundaries(tmp_path):
snapshot = _snapshot()
snapshot["approval_boundaries"]["credential_marker_write_allowed"] = True
(tmp_path / "offsite_escrow_readiness_status_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="approval boundaries"):
load_latest_offsite_escrow_readiness_status(tmp_path)
def test_offsite_escrow_readiness_status_requires_blocked_operation_boundaries(tmp_path):
snapshot = _snapshot()
snapshot["operation_boundaries"]["offsite_sync_execution_allowed"] = True
(tmp_path / "offsite_escrow_readiness_status_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="operation boundaries"):
load_latest_offsite_escrow_readiness_status(tmp_path)
def test_offsite_escrow_readiness_status_requires_total_consistency(tmp_path):
snapshot = _snapshot()
snapshot["rollups"]["total_cards"] = 999
(tmp_path / "offsite_escrow_readiness_status_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="total_cards"):
load_latest_offsite_escrow_readiness_status(tmp_path)
def test_offsite_escrow_readiness_status_requires_escrow_blocked_consistency(tmp_path):
snapshot = _snapshot()
snapshot["rollups"]["blocked_escrow_card_ids"] = []
(tmp_path / "offsite_escrow_readiness_status_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="blocked_escrow_card_ids"):
load_latest_offsite_escrow_readiness_status(tmp_path)
def test_offsite_escrow_readiness_status_rejects_credential_plaintext(tmp_path):
snapshot = _snapshot()
snapshot["readiness_cards"][1]["credential_exposure_status"] = "credential_plaintext"
(tmp_path / "offsite_escrow_readiness_status_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="credential exposure"):
load_latest_offsite_escrow_readiness_status(tmp_path)
def test_offsite_escrow_readiness_status_requires_operator_denials(tmp_path):
snapshot = _snapshot()
snapshot["operator_contract"]["must_not_interpret_as"] = ["復原批准"]
(tmp_path / "offsite_escrow_readiness_status_2026-06-05.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="must_not_interpret_as"):
load_latest_offsite_escrow_readiness_status(tmp_path)
def test_offsite_escrow_readiness_status_fails_when_missing(tmp_path):
with pytest.raises(FileNotFoundError):
load_latest_offsite_escrow_readiness_status(tmp_path)
def _snapshot(
*,
generated_at: str = "2026-06-05T00:00:00+08:00",
completion: int = 100,
) -> dict:
return {
"schema_version": "offsite_escrow_readiness_status_v1",
"generated_at": generated_at,
"source_refs": ["docs/evaluations/backup_dr_readiness_matrix_2026-06-04.json"],
"program_status": {
"overall_completion_percent": completion,
"current_priority": "P1",
"current_task_id": "P1-106",
"next_task_id": "P1-305",
"read_only_mode": True,
},
"rollups": {
"total_cards": 3,
"by_readiness": {"verified": 1, "action_required": 1, "blocked": 1},
"by_kind": {
"offsite_mirror": 1,
"credential_escrow": 1,
"k8s_resource_offsite": 1,
},
"verified_offsite_card_ids": ["offsite_rclone_full_sync"],
"blocked_escrow_card_ids": ["credential_escrow_markers"],
"action_required_card_ids": ["velero_k8s_resources"],
"execution_blocked_card_ids": [
"offsite_rclone_full_sync",
"credential_escrow_markers",
"velero_k8s_resources",
],
},
"readiness_cards": [
_card("offsite_rclone_full_sync", "offsite_mirror", "verified"),
_card("credential_escrow_markers", "credential_escrow", "blocked"),
_card("velero_k8s_resources", "k8s_resource_offsite", "action_required"),
],
"operator_contract": {
"display_mode": "read_only_status",
"success_notification_policy": "成功狀態不得即時通知洗版。",
"failure_notification_policy": "失敗或阻擋維持 action-required。",
"credential_display_policy": "只顯示 redacted metadata。",
"must_not_interpret_as": [
"復原批准",
"異地同步批准",
"credential marker 寫入批准",
"完整 DR 綠燈",
],
},
"operation_boundaries": {
"read_only_status_allowed": True,
"backup_execution_allowed": False,
"restore_execution_allowed": False,
"offsite_sync_execution_allowed": False,
"credential_marker_write_allowed": False,
"credential_read_allowed": False,
"secret_plaintext_allowed": False,
"schedule_change_allowed": False,
"workflow_write_allowed": False,
"telegram_test_notification_allowed": False,
"destructive_prune_allowed": False,
"production_routing_allowed": False,
},
"approval_boundaries": {
"sdk_installation_allowed": False,
"paid_api_call_allowed": False,
"shadow_or_canary_allowed": False,
"production_routing_allowed": False,
"destructive_operation_allowed": False,
"restore_execution_allowed": False,
"offsite_sync_execution_allowed": False,
"credential_marker_write_allowed": False,
},
}
def _card(card_id: str, kind: str, readiness: str) -> dict:
return {
"card_id": card_id,
"target_id": card_id,
"display_name": card_id,
"kind": kind,
"readiness": readiness,
"offsite_status": "verified" if readiness == "verified" else "not_applicable",
"escrow_status": "missing_markers" if kind == "credential_escrow" else "not_applicable",
"restore_drill_status": "approval_required",
"credential_exposure_status": "redacted_only",
"automation_gate_status": "read_only_allowed",
"operator_summary": "只讀狀態。",
"next_action": "維持只讀。",
"evidence_refs": ["docs/evaluations/backup_dr_readiness_matrix_2026-06-04.json"],
"blocked_operations": ["offsite_sync_execution", "credential_marker_write"],
}

View File

@@ -0,0 +1,43 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.agents import router
def test_offsite_escrow_readiness_status_endpoint_returns_committed_snapshot():
app = FastAPI()
app.include_router(router, prefix="/api/v1")
client = TestClient(app)
response = client.get("/api/v1/agents/offsite-escrow-readiness-status")
assert response.status_code == 200
data = response.json()
assert data["schema_version"] == "offsite_escrow_readiness_status_v1"
assert data["program_status"]["overall_completion_percent"] == 100
assert data["program_status"]["read_only_mode"] is True
assert data["program_status"]["current_task_id"] == "P1-106"
assert data["program_status"]["next_task_id"] == "P1-305"
assert data["rollups"]["total_cards"] == len(data["readiness_cards"]) == 3
assert data["rollups"]["verified_offsite_card_ids"] == ["offsite_rclone_full_sync"]
assert data["rollups"]["blocked_escrow_card_ids"] == ["credential_escrow_markers"]
assert data["rollups"]["action_required_card_ids"] == ["velero_k8s_resources"]
assert data["operation_boundaries"]["read_only_status_allowed"] is True
assert data["operation_boundaries"]["restore_execution_allowed"] is False
assert data["operation_boundaries"]["offsite_sync_execution_allowed"] is False
assert data["operation_boundaries"]["credential_marker_write_allowed"] is False
assert data["operation_boundaries"]["credential_read_allowed"] is False
assert data["operation_boundaries"]["secret_plaintext_allowed"] is False
assert data["operation_boundaries"]["telegram_test_notification_allowed"] is False
assert data["approval_boundaries"]["credential_marker_write_allowed"] is False
assert any(
card["card_id"] == "credential_escrow_markers" and card["readiness"] == "blocked"
for card in data["readiness_cards"]
)
assert any(
card["card_id"] == "velero_k8s_resources" and card["readiness"] == "action_required"
for card in data["readiness_cards"]
)
assert "完整 DR 綠燈" in data["operator_contract"]["must_not_interpret_as"]