feat(governance): 新增 TG canary owner review gate
This commit is contained in:
@@ -768,7 +768,7 @@ async def get_agent_12_agent_war_room() -> dict[str, Any]:
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 AI Agent 專業任務擴展與 Telegram Runtime Bridge 快照",
|
||||
description=(
|
||||
"讀取最新已提交的 P2-405A AI Agent 專業任務擴展與 Telegram Runtime Bridge 只讀快照;"
|
||||
"讀取最新已提交的 P2-405F AI Agent 專業任務擴展與 Telegram Runtime Bridge 只讀快照;"
|
||||
"此端點只呈現 OpenClaw、Hermes、NemoTron 與專責 Agent 可承接的專業任務、MCP/RAG、"
|
||||
"風險分層、Telegram no-send preview 與後續 canary gate,"
|
||||
"不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production write、"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""
|
||||
AI Agent professional task expansion and Telegram runtime bridge snapshot.
|
||||
|
||||
Loads the latest committed P2-405E read-only contract. The contract expands
|
||||
Loads the latest committed P2-405F read-only contract. The contract expands
|
||||
professional AI Agent work and defines Telegram no-send previews plus canary
|
||||
delivery rehearsal evidence, but it does not write Telegram Gateway queues,
|
||||
send Telegram messages, call the Bot API, read secrets, or execute production
|
||||
changes.
|
||||
delivery rehearsal and owner review gate evidence, but it does not write
|
||||
Telegram Gateway queues, send Telegram messages, call the Bot API, read secrets,
|
||||
or execute production changes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -32,6 +32,7 @@ _EXPECTED_CANARY_PACKAGE_COUNT = 1
|
||||
_EXPECTED_CANARY_APPROVAL_PACKET_COUNT = 1
|
||||
_EXPECTED_CANARY_DELIVERY_GATE_COUNT = 1
|
||||
_EXPECTED_CANARY_DELIVERY_REHEARSAL_COUNT = 1
|
||||
_EXPECTED_CANARY_LIVE_DELIVERY_OWNER_REVIEW_GATE_COUNT = 1
|
||||
_ZERO_ROLLUP_FIELDS = {
|
||||
"current_live_count",
|
||||
"gateway_queue_write_count",
|
||||
@@ -68,6 +69,14 @@ _ZERO_ROLLUP_FIELDS = {
|
||||
"canary_delivery_rehearsal_gateway_queue_write_enabled_count",
|
||||
"canary_delivery_rehearsal_bot_api_call_enabled_count",
|
||||
"canary_delivery_rehearsal_receipt_write_enabled_count",
|
||||
"canary_live_delivery_owner_review_received_count",
|
||||
"canary_live_delivery_owner_review_accepted_count",
|
||||
"canary_live_delivery_approved_count",
|
||||
"canary_live_delivery_attempt_allowed_count",
|
||||
"canary_live_delivery_gateway_queue_write_enabled_count",
|
||||
"canary_live_delivery_bot_api_call_enabled_count",
|
||||
"canary_live_delivery_telegram_send_enabled_count",
|
||||
"canary_live_delivery_receipt_write_enabled_count",
|
||||
}
|
||||
_FORBIDDEN_PUBLIC_TERMS = {
|
||||
"work_window_transcript",
|
||||
@@ -115,11 +124,11 @@ def _require_schema(payload: dict[str, Any], label: str) -> None:
|
||||
status = payload.get("program_status") or {}
|
||||
expected = {
|
||||
"current_priority": "P2",
|
||||
"current_task_id": "P2-405E",
|
||||
"next_task_id": "P2-405F",
|
||||
"current_task_id": "P2-405F",
|
||||
"next_task_id": "P2-406B",
|
||||
"read_only_mode": True,
|
||||
"runtime_authority": _RUNTIME_AUTHORITY,
|
||||
"overall_completion_percent": 98,
|
||||
"overall_completion_percent": 99,
|
||||
}
|
||||
mismatches = _mismatches(status, expected)
|
||||
if mismatches:
|
||||
@@ -162,6 +171,7 @@ def _require_telegram_bridge(payload: dict[str, Any], label: str) -> None:
|
||||
_require_canary_send_approval_packet(bridge, label)
|
||||
_require_canary_delivery_gate(bridge, label)
|
||||
_require_canary_delivery_rehearsal(bridge, label)
|
||||
_require_canary_live_delivery_owner_review_gate(bridge, label)
|
||||
|
||||
|
||||
def _require_no_send_previews(bridge: dict[str, Any], label: str) -> None:
|
||||
@@ -509,6 +519,96 @@ def _require_canary_delivery_rehearsal(bridge: dict[str, Any], label: str) -> No
|
||||
raise ValueError(f"{label}: canary rehearsal execution flags mismatch: {mismatches}")
|
||||
|
||||
|
||||
def _require_canary_live_delivery_owner_review_gate(
|
||||
bridge: dict[str, Any], label: str
|
||||
) -> None:
|
||||
gate = bridge.get("canary_live_delivery_owner_review_gate") or {}
|
||||
expected = {
|
||||
"status": "ready_for_owner_review_no_send",
|
||||
"gate_ready": True,
|
||||
"approval_required": True,
|
||||
"owner_review_received": False,
|
||||
"owner_review_accepted": False,
|
||||
"live_canary_delivery_approved": False,
|
||||
"delivery_attempt_allowed": False,
|
||||
"prior_rehearsal_status": "ready_no_send_rehearsal",
|
||||
"prior_readback_completed_check_count": 8,
|
||||
"prior_readback_failed_check_count": 0,
|
||||
"selected_message_type": "daily_agent_workload_digest",
|
||||
"selected_preview_id": "p2_405b_preview_daily_agent_workload_digest_v1",
|
||||
"selected_receipt_expectation_id": "p2_405b_receipt_daily_agent_workload_digest_v1",
|
||||
"target_room_env": "SRE_GROUP_CHAT_ID",
|
||||
"target_room_value_visible": False,
|
||||
"owner_agent": "telegram_ops_liaison",
|
||||
"receipt_readback_owner": "hermes",
|
||||
"arbiter": "openclaw",
|
||||
}
|
||||
mismatches = _mismatches(gate, expected)
|
||||
if mismatches:
|
||||
raise ValueError(
|
||||
f"{label}: canary_live_delivery_owner_review_gate mismatch: {mismatches}"
|
||||
)
|
||||
if not gate:
|
||||
raise ValueError(
|
||||
f"{label}: expected "
|
||||
f"{_EXPECTED_CANARY_LIVE_DELIVERY_OWNER_REVIEW_GATE_COUNT} "
|
||||
"canary live delivery owner review gate"
|
||||
)
|
||||
|
||||
required_fields = gate.get("required_owner_fields") or []
|
||||
if len(required_fields) != 9:
|
||||
raise ValueError(f"{label}: canary live owner review must define 9 fields")
|
||||
for field in required_fields:
|
||||
if field.get("required") is not True:
|
||||
raise ValueError(f"{label}: canary live owner field must be required")
|
||||
if field.get("current_value_status") != "waiting_owner_response":
|
||||
raise ValueError(
|
||||
f"{label}: canary live owner field must wait for owner response"
|
||||
)
|
||||
if field.get("value_display_allowed") is not False:
|
||||
raise ValueError(f"{label}: canary live owner field values must stay hidden")
|
||||
|
||||
if len(gate.get("acceptance_checks") or []) != 9:
|
||||
raise ValueError(f"{label}: canary live owner review must define 9 checks")
|
||||
if len(gate.get("rejection_reasons") or []) != 8:
|
||||
raise ValueError(f"{label}: canary live owner review must define 8 rejections")
|
||||
if len(gate.get("reviewer_actions") or []) != 6:
|
||||
raise ValueError(f"{label}: canary live owner review must define 6 actions")
|
||||
|
||||
receipt = gate.get("receipt_readback_plan") or {}
|
||||
if receipt.get("owner_agent") != "hermes":
|
||||
raise ValueError(f"{label}: canary live owner receipt owner must be hermes")
|
||||
if len(receipt.get("required_checks") or []) != 8:
|
||||
raise ValueError(f"{label}: canary live owner receipt plan must define 8 checks")
|
||||
if receipt.get("completed_check_count") != 0:
|
||||
raise ValueError(f"{label}: canary live owner receipt checks must not complete yet")
|
||||
if receipt.get("failed_check_count") != 0:
|
||||
raise ValueError(f"{label}: canary live owner receipt failed checks must be zero")
|
||||
if receipt.get("production_receipt_write_enabled") is not False:
|
||||
raise ValueError(f"{label}: canary live owner receipt write must remain false")
|
||||
if receipt.get("live_receipt_readback_enabled") is not False:
|
||||
raise ValueError(f"{label}: canary live owner live readback must remain false")
|
||||
|
||||
execution_flags = gate.get("execution_flags") or {}
|
||||
expected_execution = {
|
||||
"live_delivery_enabled": False,
|
||||
"gateway_queue_write_enabled": False,
|
||||
"bot_api_call_enabled": False,
|
||||
"telegram_send_enabled": False,
|
||||
"delivery_receipt_write_enabled": False,
|
||||
"production_write_enabled": False,
|
||||
"secret_read_enabled": False,
|
||||
"paid_api_enabled": False,
|
||||
}
|
||||
mismatches = _mismatches(execution_flags, expected_execution)
|
||||
if mismatches:
|
||||
raise ValueError(
|
||||
f"{label}: canary live owner review execution flags mismatch: {mismatches}"
|
||||
)
|
||||
if gate.get("owner_decision_log") != []:
|
||||
raise ValueError(f"{label}: canary live owner decision log must remain empty")
|
||||
|
||||
|
||||
def _require_professional_tasks(payload: dict[str, Any], label: str) -> None:
|
||||
domains = payload.get("professional_task_domains") or []
|
||||
if len(domains) != _EXPECTED_DOMAIN_COUNT:
|
||||
@@ -676,6 +776,42 @@ def _require_rollups(payload: dict[str, Any], label: str) -> None:
|
||||
or {}
|
||||
).get("failed_check_count")
|
||||
),
|
||||
"canary_live_delivery_owner_review_gate_count": 1
|
||||
if bridge.get("canary_live_delivery_owner_review_gate")
|
||||
else 0,
|
||||
"canary_live_delivery_owner_review_required_field_count": len(
|
||||
(bridge.get("canary_live_delivery_owner_review_gate") or {}).get(
|
||||
"required_owner_fields"
|
||||
)
|
||||
or []
|
||||
),
|
||||
"canary_live_delivery_owner_review_acceptance_check_count": len(
|
||||
(bridge.get("canary_live_delivery_owner_review_gate") or {}).get(
|
||||
"acceptance_checks"
|
||||
)
|
||||
or []
|
||||
),
|
||||
"canary_live_delivery_owner_review_rejection_reason_count": len(
|
||||
(bridge.get("canary_live_delivery_owner_review_gate") or {}).get(
|
||||
"rejection_reasons"
|
||||
)
|
||||
or []
|
||||
),
|
||||
"canary_live_delivery_owner_review_reviewer_action_count": len(
|
||||
(bridge.get("canary_live_delivery_owner_review_gate") or {}).get(
|
||||
"reviewer_actions"
|
||||
)
|
||||
or []
|
||||
),
|
||||
"canary_live_delivery_owner_review_receipt_check_count": len(
|
||||
(
|
||||
(bridge.get("canary_live_delivery_owner_review_gate") or {}).get(
|
||||
"receipt_readback_plan"
|
||||
)
|
||||
or {}
|
||||
).get("required_checks")
|
||||
or []
|
||||
),
|
||||
}
|
||||
mismatches = _mismatches(rollups, expected)
|
||||
if mismatches:
|
||||
|
||||
@@ -18,9 +18,9 @@ def test_load_latest_ai_agent_professional_task_expansion_snapshot() -> None:
|
||||
snapshot = load_latest_ai_agent_professional_task_expansion()
|
||||
|
||||
assert snapshot["schema_version"] == "ai_agent_professional_task_expansion_v1"
|
||||
assert snapshot["program_status"]["current_task_id"] == "P2-405E"
|
||||
assert snapshot["program_status"]["next_task_id"] == "P2-405F"
|
||||
assert snapshot["program_status"]["overall_completion_percent"] == 98
|
||||
assert snapshot["program_status"]["current_task_id"] == "P2-405F"
|
||||
assert snapshot["program_status"]["next_task_id"] == "P2-406B"
|
||||
assert snapshot["program_status"]["overall_completion_percent"] == 99
|
||||
assert snapshot["program_status"]["runtime_authority"] == (
|
||||
"professional_task_expansion_and_telegram_bridge_read_only_no_send"
|
||||
)
|
||||
@@ -117,6 +117,20 @@ def test_load_latest_ai_agent_professional_task_expansion_snapshot() -> None:
|
||||
assert rollups["canary_delivery_rehearsal_gateway_queue_write_enabled_count"] == 0
|
||||
assert rollups["canary_delivery_rehearsal_bot_api_call_enabled_count"] == 0
|
||||
assert rollups["canary_delivery_rehearsal_receipt_write_enabled_count"] == 0
|
||||
assert rollups["canary_live_delivery_owner_review_gate_count"] == 1
|
||||
assert rollups["canary_live_delivery_owner_review_required_field_count"] == 9
|
||||
assert rollups["canary_live_delivery_owner_review_acceptance_check_count"] == 9
|
||||
assert rollups["canary_live_delivery_owner_review_rejection_reason_count"] == 8
|
||||
assert rollups["canary_live_delivery_owner_review_reviewer_action_count"] == 6
|
||||
assert rollups["canary_live_delivery_owner_review_receipt_check_count"] == 8
|
||||
assert rollups["canary_live_delivery_owner_review_received_count"] == 0
|
||||
assert rollups["canary_live_delivery_owner_review_accepted_count"] == 0
|
||||
assert rollups["canary_live_delivery_approved_count"] == 0
|
||||
assert rollups["canary_live_delivery_attempt_allowed_count"] == 0
|
||||
assert rollups["canary_live_delivery_gateway_queue_write_enabled_count"] == 0
|
||||
assert rollups["canary_live_delivery_bot_api_call_enabled_count"] == 0
|
||||
assert rollups["canary_live_delivery_telegram_send_enabled_count"] == 0
|
||||
assert rollups["canary_live_delivery_receipt_write_enabled_count"] == 0
|
||||
|
||||
|
||||
def test_professional_tasks_cover_required_agents_and_reporting() -> None:
|
||||
@@ -288,6 +302,46 @@ def test_canary_delivery_rehearsal_is_ready_without_live_send() -> None:
|
||||
assert all(value is False for value in rehearsal["execution_flags"].values())
|
||||
|
||||
|
||||
def test_canary_live_delivery_owner_review_gate_waits_for_owner_response() -> None:
|
||||
snapshot = load_latest_ai_agent_professional_task_expansion()
|
||||
gate = snapshot["telegram_runtime_bridge"]["canary_live_delivery_owner_review_gate"]
|
||||
|
||||
assert gate["status"] == "ready_for_owner_review_no_send"
|
||||
assert gate["gate_ready"] is True
|
||||
assert gate["approval_required"] is True
|
||||
assert gate["owner_review_received"] is False
|
||||
assert gate["owner_review_accepted"] is False
|
||||
assert gate["live_canary_delivery_approved"] is False
|
||||
assert gate["delivery_attempt_allowed"] is False
|
||||
assert gate["prior_readback_completed_check_count"] == 8
|
||||
assert gate["prior_readback_failed_check_count"] == 0
|
||||
assert gate["selected_message_type"] == "daily_agent_workload_digest"
|
||||
assert gate["target_room_env"] == "SRE_GROUP_CHAT_ID"
|
||||
assert gate["target_room_value_visible"] is False
|
||||
assert gate["owner_agent"] == "telegram_ops_liaison"
|
||||
assert gate["receipt_readback_owner"] == "hermes"
|
||||
assert gate["arbiter"] == "openclaw"
|
||||
assert len(gate["required_owner_fields"]) == 9
|
||||
assert len(gate["acceptance_checks"]) == 9
|
||||
assert len(gate["rejection_reasons"]) == 8
|
||||
assert len(gate["reviewer_actions"]) == 6
|
||||
assert gate["owner_decision_log"] == []
|
||||
|
||||
for field in gate["required_owner_fields"]:
|
||||
assert field["required"] is True
|
||||
assert field["current_value_status"] == "waiting_owner_response"
|
||||
assert field["value_display_allowed"] is False
|
||||
|
||||
receipt = gate["receipt_readback_plan"]
|
||||
assert receipt["owner_agent"] == "hermes"
|
||||
assert len(receipt["required_checks"]) == 8
|
||||
assert receipt["completed_check_count"] == 0
|
||||
assert receipt["failed_check_count"] == 0
|
||||
assert receipt["production_receipt_write_enabled"] is False
|
||||
assert receipt["live_receipt_readback_enabled"] is False
|
||||
assert all(value is False for value in gate["execution_flags"].values())
|
||||
|
||||
|
||||
def test_rejects_telegram_send_enabled(tmp_path: Path) -> None:
|
||||
snapshot = copy.deepcopy(load_latest_ai_agent_professional_task_expansion())
|
||||
snapshot["telegram_runtime_bridge"]["telegram_send_enabled"] = True
|
||||
@@ -424,6 +478,28 @@ def test_rejects_canary_delivery_rehearsal_receipt_write_enabled(tmp_path: Path)
|
||||
load_latest_ai_agent_professional_task_expansion(tmp_path)
|
||||
|
||||
|
||||
def test_rejects_canary_live_delivery_owner_review_received(tmp_path: Path) -> None:
|
||||
snapshot = copy.deepcopy(load_latest_ai_agent_professional_task_expansion())
|
||||
gate = snapshot["telegram_runtime_bridge"]["canary_live_delivery_owner_review_gate"]
|
||||
gate["owner_review_received"] = True
|
||||
snapshot["rollups"]["canary_live_delivery_owner_review_received_count"] = 1
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="canary_live_delivery_owner_review_gate mismatch"):
|
||||
load_latest_ai_agent_professional_task_expansion(tmp_path)
|
||||
|
||||
|
||||
def test_rejects_canary_live_delivery_telegram_send_enabled(tmp_path: Path) -> None:
|
||||
snapshot = copy.deepcopy(load_latest_ai_agent_professional_task_expansion())
|
||||
gate = snapshot["telegram_runtime_bridge"]["canary_live_delivery_owner_review_gate"]
|
||||
gate["execution_flags"]["telegram_send_enabled"] = True
|
||||
snapshot["rollups"]["canary_live_delivery_telegram_send_enabled_count"] = 1
|
||||
_write_snapshot(tmp_path, snapshot)
|
||||
|
||||
with pytest.raises(ValueError, match="canary live owner review execution flags mismatch"):
|
||||
load_latest_ai_agent_professional_task_expansion(tmp_path)
|
||||
|
||||
|
||||
def test_rejects_high_risk_without_approval(tmp_path: Path) -> None:
|
||||
snapshot = copy.deepcopy(load_latest_ai_agent_professional_task_expansion())
|
||||
high_task = next(task for task in snapshot["professional_tasks"] if task["risk_tier"] == "high")
|
||||
|
||||
@@ -17,9 +17,9 @@ def test_ai_agent_professional_task_expansion_endpoint() -> None:
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert payload["schema_version"] == "ai_agent_professional_task_expansion_v1"
|
||||
assert payload["program_status"]["current_task_id"] == "P2-405E"
|
||||
assert payload["program_status"]["next_task_id"] == "P2-405F"
|
||||
assert payload["program_status"]["overall_completion_percent"] == 98
|
||||
assert payload["program_status"]["current_task_id"] == "P2-405F"
|
||||
assert payload["program_status"]["next_task_id"] == "P2-406B"
|
||||
assert payload["program_status"]["overall_completion_percent"] == 99
|
||||
assert payload["program_status"]["runtime_authority"] == (
|
||||
"professional_task_expansion_and_telegram_bridge_read_only_no_send"
|
||||
)
|
||||
@@ -61,6 +61,12 @@ def test_ai_agent_professional_task_expansion_endpoint() -> None:
|
||||
assert payload["rollups"]["canary_delivery_rehearsal_gateway_queue_write_enabled_count"] == 0
|
||||
assert payload["rollups"]["canary_delivery_rehearsal_bot_api_call_enabled_count"] == 0
|
||||
assert payload["rollups"]["canary_delivery_rehearsal_receipt_write_enabled_count"] == 0
|
||||
assert payload["rollups"]["canary_live_delivery_owner_review_gate_count"] == 1
|
||||
assert payload["rollups"]["canary_live_delivery_owner_review_required_field_count"] == 9
|
||||
assert payload["rollups"]["canary_live_delivery_owner_review_received_count"] == 0
|
||||
assert payload["rollups"]["canary_live_delivery_owner_review_accepted_count"] == 0
|
||||
assert payload["rollups"]["canary_live_delivery_approved_count"] == 0
|
||||
assert payload["rollups"]["canary_live_delivery_telegram_send_enabled_count"] == 0
|
||||
assert payload["telegram_runtime_bridge"]["canary_approval_package"]["live_send_enabled"] is False
|
||||
assert payload["telegram_runtime_bridge"]["canary_send_approval_packet"]["approval_granted"] is False
|
||||
assert (
|
||||
@@ -81,5 +87,11 @@ def test_ai_agent_professional_task_expansion_endpoint() -> None:
|
||||
]
|
||||
is False
|
||||
)
|
||||
owner_gate = payload["telegram_runtime_bridge"]["canary_live_delivery_owner_review_gate"]
|
||||
assert owner_gate["gate_ready"] is True
|
||||
assert owner_gate["owner_review_received"] is False
|
||||
assert owner_gate["owner_review_accepted"] is False
|
||||
assert owner_gate["live_canary_delivery_approved"] is False
|
||||
assert owner_gate["execution_flags"]["telegram_send_enabled"] is False
|
||||
assert len(payload["telegram_runtime_bridge"]["no_send_message_previews"]) == 6
|
||||
assert len(payload["telegram_runtime_bridge"]["receipt_expectations"]) == 6
|
||||
|
||||
Reference in New Issue
Block a user