diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 4bb64a5b9..187b480a2 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -112,6 +112,9 @@ from src.services.ai_agent_runtime_readback_implementation_review import ( from src.services.ai_agent_runtime_readback_fixture_approval import ( load_latest_ai_agent_runtime_readback_fixture_approval, ) +from src.services.ai_agent_runtime_readback_promotion_gate import ( + load_latest_ai_agent_runtime_readback_promotion_gate, +) from src.services.ai_agent_report_live_delivery_approval_package import ( load_latest_ai_agent_report_live_delivery_approval_package, ) @@ -1425,6 +1428,36 @@ async def get_agent_runtime_readback_fixture_approval() -> dict[str, Any]: ) from exc +@router.get( + "/agent-runtime-readback-promotion-gate", + response_model=dict[str, Any], + summary="取得 AI Agent runtime readback promotion gate", + description=( + "讀取最新已提交的 P2-113 runtime readback promotion gate;" + "此端點只回傳 failure receipt、reviewer queue、result capture 的 no-write promotion " + "lane、preview、verifier 與 blocker,不讀 canonical runtime target、不做 live query、" + "不寫 reviewer queue、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、" + "不寫 report receipt、不寫 result capture、不寫 learning / PlayBook trust、不讀 secret。" + ), +) +async def get_agent_runtime_readback_promotion_gate() -> dict[str, Any]: + """Return the latest read-only runtime readback promotion gate.""" + try: + payload = await asyncio.to_thread(load_latest_ai_agent_runtime_readback_promotion_gate) + return redact_public_lan_topology(payload) + 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("ai_agent_runtime_readback_promotion_gate_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent runtime readback promotion gate 無效", + ) from exc + + @router.get( "/agent-owner-approved-fixture-dry-run", response_model=dict[str, Any], diff --git a/apps/api/src/services/ai_agent_runtime_readback_promotion_gate.py b/apps/api/src/services/ai_agent_runtime_readback_promotion_gate.py new file mode 100644 index 000000000..cdfae3b5b --- /dev/null +++ b/apps/api/src/services/ai_agent_runtime_readback_promotion_gate.py @@ -0,0 +1,404 @@ +""" +AI Agent runtime readback promotion gate snapshot. + +Loads the latest committed P2-113 no-write promotion gate. This module validates +committed evidence only; it never reads canonical runtime targets, performs live +queries, writes reviewer queues, writes result captures, writes Gateway queues, +sends Telegram messages, calls Bot API, reads secrets, or performs destructive +operations. +""" + +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 = "ai_agent_runtime_readback_promotion_gate_*.json" +_SCHEMA_VERSION = "ai_agent_runtime_readback_promotion_gate_v1" +_RUNTIME_AUTHORITY = "runtime_readback_promotion_gate_only_no_live_queue_or_result_write" + + +def load_latest_ai_agent_runtime_readback_promotion_gate( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed runtime readback promotion gate.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError(f"no AI Agent runtime readback promotion gate 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") + + label = str(latest) + _require_schema(payload, label) + _require_prior(payload, label) + _require_truth(payload, label) + _require_lanes(payload, label) + _require_contracts(payload, label) + _require_queue_previews(payload, label) + _require_result_previews(payload, label) + _require_checks(payload, label) + _require_blockers(payload, label) + _require_actions(payload, label) + _require_display_redaction(payload, label) + _require_no_forbidden_display_terms(payload, label) + _require_rollup_consistency(payload, label) + return payload + + +def _require_schema(payload: dict[str, Any], label: str) -> None: + if payload.get("schema_version") != _SCHEMA_VERSION: + raise ValueError(f"{label}: expected schema_version={_SCHEMA_VERSION}") + status = payload.get("program_status") or {} + expected = { + "current_priority": "P2", + "current_task_id": "P2-113", + "next_task_id": "P2-114", + "read_only_mode": True, + "runtime_authority": _RUNTIME_AUTHORITY, + "overall_completion_percent": 100, + } + mismatches = _mismatches(status, expected) + if mismatches: + raise ValueError(f"{label}: program_status mismatch: {mismatches}") + if not status.get("status_note"): + raise ValueError(f"{label}: program_status.status_note is required") + + +def _require_prior(payload: dict[str, Any], label: str) -> None: + prior = payload.get("prior_fixture_approval") or {} + expected = { + "fixture_approval_schema_version": "ai_agent_runtime_readback_fixture_approval_v1", + "fixture_approval_card_count": 5, + "adapter_contract_count": 4, + "verifier_fixture_check_count": 5, + "blocker_mapping_count": 5, + "operator_action_count": 5, + "owner_approval_received_count": 0, + "fixture_readback_execution_count": 0, + "live_query_count": 0, + "production_write_count": 0, + } + mismatches = _mismatches(prior, expected) + if mismatches: + raise ValueError(f"{label}: prior_fixture_approval mismatch: {mismatches}") + if not prior.get("readiness_note"): + raise ValueError(f"{label}: prior_fixture_approval.readiness_note is required") + + +def _require_truth(payload: dict[str, Any], label: str) -> None: + truth = payload.get("promotion_truth") or {} + required_true = { + "p2_112_fixture_approval_loaded", + "promotion_gate_ready", + "failure_receipt_fixture_ready", + "reviewer_queue_preview_ready", + "result_capture_preview_ready", + "owner_acceptance_required_before_promotion", + } + missing = sorted(field for field in required_true if truth.get(field) is not True) + if missing: + raise ValueError(f"{label}: promotion ready flags must remain true: {missing}") + + required_false = { + "canonical_runtime_target_read_enabled", + "live_query_enabled", + "failure_receipt_send_enabled", + "reviewer_queue_write_enabled", + "gateway_queue_write_enabled", + "telegram_send_enabled", + "bot_api_call_enabled", + "report_receipt_write_enabled", + "result_capture_write_enabled", + "learning_write_enabled", + "playbook_trust_write_enabled", + "production_write_enabled", + "secret_read_enabled", + "destructive_operation_enabled", + } + unsafe = sorted(field for field in required_false if truth.get(field) is not False) + if unsafe: + raise ValueError(f"{label}: live read/send/write flags must remain false: {unsafe}") + + zero_counts = { + "owner_approval_received_count", + "promotion_execution_count", + "canonical_runtime_target_read_count", + "live_query_count", + "failure_receipt_send_count", + "reviewer_queue_write_count", + "gateway_queue_write_count", + "telegram_send_count", + "bot_api_call_count", + "report_receipt_write_count", + "result_capture_write_count", + "learning_write_count", + "playbook_trust_write_count", + "production_write_count", + } + non_zero = sorted(field for field in zero_counts if truth.get(field) != 0) + if non_zero: + raise ValueError(f"{label}: promotion live counters must remain zero: {non_zero}") + if not truth.get("truth_note"): + raise ValueError(f"{label}: promotion_truth.truth_note is required") + + +def _require_lanes(payload: dict[str, Any], label: str) -> None: + lanes = payload.get("promotion_lanes") or [] + required = { + "telegram_failure_receipt_promotion", + "reviewer_queue_preview_promotion", + "result_capture_no_write_promotion", + "report_delivery_receipt_link", + "p2_114_handoff_scope", + } + lane_ids = {lane.get("lane_id") for lane in lanes} + if lane_ids != required: + raise ValueError(f"{label}: promotion lanes must match {sorted(required)}") + for lane in lanes: + lane_id = lane.get("lane_id") + if lane.get("owner_approval_required") is not True or lane.get("promotion_only") is not True: + raise ValueError(f"{label}: lane {lane_id} must require owner approval and promotion_only") + if lane.get("status") not in {"ready_for_owner_review", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: lane {lane_id} status is invalid") + if lane.get("risk_tier") not in {"medium", "high", "critical"}: + raise ValueError(f"{label}: lane {lane_id} risk_tier is invalid") + if not lane.get("allowed_artifact") or not lane.get("blocked_runtime_actions"): + raise ValueError(f"{label}: lane {lane_id} must list artifact and blocked actions") + if not _is_redacted_sha256(lane.get("evidence_hash")): + raise ValueError(f"{label}: lane {lane_id} must expose redacted evidence_hash") + + +def _require_contracts(payload: dict[str, Any], label: str) -> None: + contracts = payload.get("receipt_contracts") or [] + required = { + "no_send_failure_receipt_contract", + "sre_war_room_route_lock_contract", + "redacted_receipt_payload_contract", + "no_bot_api_call_contract", + } + contract_ids = {contract.get("contract_id") for contract in contracts} + if contract_ids != required: + raise ValueError(f"{label}: receipt contracts must match {sorted(required)}") + for contract in contracts: + contract_id = contract.get("contract_id") + if contract.get("status") not in {"ready", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: contract {contract_id} status is invalid") + if contract.get("live_send_enabled") is not False or contract.get("receipt_write_enabled") is not False: + raise ValueError(f"{label}: contract {contract_id} must not enable live send or receipt write") + if not contract.get("required_evidence"): + raise ValueError(f"{label}: contract {contract_id} required_evidence is required") + if not _is_redacted_sha256(contract.get("evidence_hash")): + raise ValueError(f"{label}: contract {contract_id} must expose redacted evidence_hash") + + +def _require_queue_previews(payload: dict[str, Any], label: str) -> None: + previews = payload.get("reviewer_queue_previews") or [] + if len(previews) != 4: + raise ValueError(f"{label}: reviewer_queue_previews must contain 4 items") + for preview in previews: + preview_id = preview.get("preview_id") + if preview.get("queue_write_enabled") is not False or preview.get("audit_write_enabled") is not False: + raise ValueError(f"{label}: reviewer preview {preview_id} must not enable queue/audit writes") + if not preview.get("required_fields"): + raise ValueError(f"{label}: reviewer preview {preview_id} required_fields is required") + if not _is_redacted_sha256(preview.get("evidence_hash")): + raise ValueError(f"{label}: reviewer preview {preview_id} must expose redacted evidence_hash") + + +def _require_result_previews(payload: dict[str, Any], label: str) -> None: + previews = payload.get("result_capture_previews") or [] + if len(previews) != 4: + raise ValueError(f"{label}: result_capture_previews must contain 4 items") + for preview in previews: + preview_id = preview.get("preview_id") + if preview.get("result_capture_write_enabled") is not False or preview.get("learning_write_enabled") is not False: + raise ValueError(f"{label}: result preview {preview_id} must not enable result/learning writes") + if not preview.get("required_fields"): + raise ValueError(f"{label}: result preview {preview_id} required_fields is required") + if not _is_redacted_sha256(preview.get("evidence_hash")): + raise ValueError(f"{label}: result preview {preview_id} must expose redacted evidence_hash") + + +def _require_checks(payload: dict[str, Any], label: str) -> None: + checks = payload.get("no_write_verifier_checks") or [] + required = { + "no_live_query_promotion_check", + "no_gateway_queue_write_check", + "no_telegram_bot_send_check", + "no_reviewer_queue_write_check", + "no_result_capture_write_check", + } + check_ids = {check.get("check_id") for check in checks} + if check_ids != required: + raise ValueError(f"{label}: no-write verifier checks must match {sorted(required)}") + for check in checks: + check_id = check.get("check_id") + if check.get("live_verifier_enabled") is not False: + raise ValueError(f"{label}: verifier check {check_id} must not enable live verifier") + if not check.get("required_fixture") or not check.get("failure_if_missing"): + raise ValueError(f"{label}: verifier check {check_id} must include fixture and failure text") + if not _is_redacted_sha256(check.get("evidence_hash")): + raise ValueError(f"{label}: verifier check {check_id} must expose redacted evidence_hash") + + +def _require_blockers(payload: dict[str, Any], label: str) -> None: + blockers = payload.get("blocker_mappings") or [] + required = { + "owner_acceptance_missing", + "canonical_readback_not_approved", + "telegram_send_not_approved", + "reviewer_queue_write_not_approved", + "result_capture_write_not_approved", + } + blocker_ids = {blocker.get("blocker_id") for blocker in blockers} + if blocker_ids != required: + raise ValueError(f"{label}: blocker mappings must match {sorted(required)}") + for blocker in blockers: + blocker_id = blocker.get("blocker_id") + if blocker.get("severity") not in {"medium", "high", "critical"}: + raise ValueError(f"{label}: blocker {blocker_id} severity is invalid") + if blocker.get("status") not in {"mapped", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: blocker {blocker_id} status is invalid") + if not blocker.get("blocked_action") or not blocker.get("blocked_until"): + raise ValueError(f"{label}: blocker {blocker_id} blocked action/until is required") + if not _is_redacted_sha256(blocker.get("evidence_hash")): + raise ValueError(f"{label}: blocker {blocker_id} must expose redacted evidence_hash") + + +def _require_actions(payload: dict[str, Any], label: str) -> None: + actions = payload.get("operator_actions") or [] + required = { + "review_promotion_lanes", + "verify_failure_receipt_fixture", + "confirm_queue_no_write", + "compare_result_capture_preview", + "promote_to_p2_114", + } + action_ids = {action.get("action_id") for action in actions} + if action_ids != required: + raise ValueError(f"{label}: operator actions must match {sorted(required)}") + for action in actions: + action_id = action.get("action_id") + if action.get("runtime_promotion_allowed") is not False: + raise ValueError(f"{label}: action {action_id} must not allow runtime promotion") + if not action.get("operator_instruction"): + raise ValueError(f"{label}: action {action_id} operator_instruction is required") + + +def _require_display_redaction(payload: dict[str, Any], label: str) -> None: + contract = payload.get("display_redaction_contract") or {} + if contract.get("redaction_required") is not True: + raise ValueError(f"{label}: display redaction must be required") + false_fields = { + "raw_prompt_display_allowed", + "private_reasoning_display_allowed", + "secret_value_display_allowed", + "raw_runtime_payload_display_allowed", + "internal_collaboration_content_display_allowed", + } + unsafe = sorted(field for field in false_fields if contract.get(field) is not False) + if unsafe: + raise ValueError(f"{label}: display redaction flags must remain false: {unsafe}") + if not contract.get("frontend_display_policy"): + raise ValueError(f"{label}: frontend_display_policy is required") + + +def _require_no_forbidden_display_terms(payload: dict[str, Any], label: str) -> None: + serialized = json.dumps(payload, ensure_ascii=False).lower() + forbidden = { + "work_window_transcript", + "session_id", + "browser_context", + "authorization_header", + "raw telegram payload", + "private reasoning", + "raw prompt", + "chain-of-thought", + } + hits = sorted(term for term in forbidden if term in serialized) + if hits: + raise ValueError(f"{label}: forbidden display terms leaked: {hits}") + + +def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None: + rollups = payload.get("rollups") or {} + expected_counts = { + "promotion_lane_count": len(payload.get("promotion_lanes") or []), + "receipt_contract_count": len(payload.get("receipt_contracts") or []), + "reviewer_queue_preview_count": len(payload.get("reviewer_queue_previews") or []), + "result_capture_preview_count": len(payload.get("result_capture_previews") or []), + "no_write_verifier_check_count": len(payload.get("no_write_verifier_checks") or []), + "blocker_mapping_count": len(payload.get("blocker_mappings") or []), + "operator_action_count": len(payload.get("operator_actions") or []), + "approval_required_lane_count": sum( + 1 for lane in payload.get("promotion_lanes") or [] if lane.get("status") == "approval_required" + ), + "blocked_lane_count": sum( + 1 for lane in payload.get("promotion_lanes") or [] if lane.get("status") == "blocked_by_policy" + ), + "blocked_receipt_contract_count": sum( + 1 for contract in payload.get("receipt_contracts") or [] if contract.get("status") == "blocked_by_policy" + ), + "approval_required_reviewer_preview_count": sum( + 1 + for preview in payload.get("reviewer_queue_previews") or [] + if preview.get("status") == "approval_required" + ), + "blocked_result_preview_count": sum( + 1 + for preview in payload.get("result_capture_previews") or [] + if preview.get("status") == "blocked_by_policy" + ), + } + mismatches = _mismatches(rollups, expected_counts) + if mismatches: + raise ValueError(f"{label}: rollup counts mismatch: {mismatches}") + + zero_rollups = { + "owner_approval_received_count", + "promotion_execution_count", + "canonical_runtime_target_read_count", + "live_query_count", + "failure_receipt_send_count", + "reviewer_queue_write_count", + "gateway_queue_write_count", + "telegram_send_count", + "bot_api_call_count", + "report_receipt_write_count", + "result_capture_write_count", + "learning_write_count", + "playbook_trust_write_count", + "production_write_count", + "secret_read_count", + "destructive_operation_count", + } + non_zero = sorted(field for field in zero_rollups if rollups.get(field) != 0) + if non_zero: + raise ValueError(f"{label}: live/send/write rollups must remain zero: {non_zero}") + + +def _mismatches(actual: dict[str, Any], expected: dict[str, Any]) -> dict[str, dict[str, Any]]: + return { + key: {"expected": expected_value, "actual": actual.get(key)} + for key, expected_value in expected.items() + if actual.get(key) != expected_value + } + + +def _is_redacted_sha256(value: Any) -> bool: + if not isinstance(value, str): + return False + if not value.startswith("sha256:") or len(value) != len("sha256:") + 64: + return False + digest = value.split(":", 1)[1] + return all(char in "0123456789abcdef" for char in digest) diff --git a/apps/api/tests/test_ai_agent_runtime_readback_promotion_gate.py b/apps/api/tests/test_ai_agent_runtime_readback_promotion_gate.py new file mode 100644 index 000000000..a0adac791 --- /dev/null +++ b/apps/api/tests/test_ai_agent_runtime_readback_promotion_gate.py @@ -0,0 +1,88 @@ +import copy +import json +from pathlib import Path + +import pytest + +from src.services.ai_agent_runtime_readback_promotion_gate import ( + load_latest_ai_agent_runtime_readback_promotion_gate, +) + + +REPO_ROOT = Path(__file__).resolve().parents[3] +FIXTURE = REPO_ROOT / "docs/evaluations/ai_agent_runtime_readback_promotion_gate_2026-06-13.json" + + +def test_load_latest_ai_agent_runtime_readback_promotion_gate_snapshot() -> None: + data = load_latest_ai_agent_runtime_readback_promotion_gate() + + assert data["schema_version"] == "ai_agent_runtime_readback_promotion_gate_v1" + assert data["program_status"]["current_task_id"] == "P2-113" + assert data["program_status"]["next_task_id"] == "P2-114" + assert data["program_status"]["overall_completion_percent"] == 100 + assert data["program_status"]["read_only_mode"] is True + + rollups = data["rollups"] + assert rollups["promotion_lane_count"] == 5 + assert rollups["receipt_contract_count"] == 4 + assert rollups["reviewer_queue_preview_count"] == 4 + assert rollups["result_capture_preview_count"] == 4 + assert rollups["no_write_verifier_check_count"] == 5 + assert rollups["blocker_mapping_count"] == 5 + assert rollups["operator_action_count"] == 5 + assert rollups["approval_required_lane_count"] == 2 + assert rollups["blocked_lane_count"] == 1 + assert rollups["blocked_receipt_contract_count"] == 1 + assert rollups["approval_required_reviewer_preview_count"] == 1 + assert rollups["blocked_result_preview_count"] == 1 + + zero_fields = [ + "owner_approval_received_count", + "promotion_execution_count", + "canonical_runtime_target_read_count", + "live_query_count", + "failure_receipt_send_count", + "reviewer_queue_write_count", + "gateway_queue_write_count", + "telegram_send_count", + "bot_api_call_count", + "report_receipt_write_count", + "result_capture_write_count", + "learning_write_count", + "playbook_trust_write_count", + "production_write_count", + "secret_read_count", + "destructive_operation_count", + ] + for field in zero_fields: + assert rollups[field] == 0 + + +def test_runtime_readback_promotion_gate_rejects_reviewer_queue_write(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["promotion_truth"]["reviewer_queue_write_enabled"] = True + target = tmp_path / "ai_agent_runtime_readback_promotion_gate_2026-06-13.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="live read/send/write flags"): + load_latest_ai_agent_runtime_readback_promotion_gate(tmp_path) + + +def test_runtime_readback_promotion_gate_rejects_rollup_drift(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["rollups"]["promotion_lane_count"] = 4 + target = tmp_path / "ai_agent_runtime_readback_promotion_gate_2026-06-13.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="rollup counts mismatch"): + load_latest_ai_agent_runtime_readback_promotion_gate(tmp_path) + + +def test_runtime_readback_promotion_gate_rejects_forbidden_display_terms(tmp_path: Path) -> None: + source = copy.deepcopy(json.loads(FIXTURE.read_text(encoding="utf-8"))) + source["operator_actions"][0]["operator_instruction"] = "do not expose browser_context" + target = tmp_path / "ai_agent_runtime_readback_promotion_gate_2026-06-13.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="forbidden display terms"): + load_latest_ai_agent_runtime_readback_promotion_gate(tmp_path) diff --git a/apps/api/tests/test_ai_agent_runtime_readback_promotion_gate_api.py b/apps/api/tests/test_ai_agent_runtime_readback_promotion_gate_api.py new file mode 100644 index 000000000..9aff74db4 --- /dev/null +++ b/apps/api/tests/test_ai_agent_runtime_readback_promotion_gate_api.py @@ -0,0 +1,41 @@ +import pytest +from httpx import ASGITransport, AsyncClient + +from src.main import app + + +@pytest.mark.asyncio +async def test_get_agent_runtime_readback_promotion_gate_api() -> None: + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as client: + response = await client.get("/api/v1/agents/agent-runtime-readback-promotion-gate") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_runtime_readback_promotion_gate_v1" + assert data["program_status"]["current_task_id"] == "P2-113" + assert data["program_status"]["next_task_id"] == "P2-114" + assert data["program_status"]["overall_completion_percent"] == 100 + + rollups = data["rollups"] + assert rollups["promotion_lane_count"] == 5 + assert rollups["receipt_contract_count"] == 4 + assert rollups["reviewer_queue_preview_count"] == 4 + assert rollups["result_capture_preview_count"] == 4 + assert rollups["no_write_verifier_check_count"] == 5 + assert rollups["blocker_mapping_count"] == 5 + assert rollups["operator_action_count"] == 5 + assert rollups["owner_approval_received_count"] == 0 + assert rollups["promotion_execution_count"] == 0 + assert rollups["canonical_runtime_target_read_count"] == 0 + assert rollups["live_query_count"] == 0 + assert rollups["failure_receipt_send_count"] == 0 + assert rollups["reviewer_queue_write_count"] == 0 + assert rollups["gateway_queue_write_count"] == 0 + assert rollups["telegram_send_count"] == 0 + assert rollups["bot_api_call_count"] == 0 + assert rollups["report_receipt_write_count"] == 0 + assert rollups["result_capture_write_count"] == 0 + assert rollups["learning_write_count"] == 0 + assert rollups["playbook_trust_write_count"] == 0 + assert rollups["production_write_count"] == 0 diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index eb528e84e..e50f227ca 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5332,6 +5332,112 @@ "reject_canonical_target": "退回 canonical target", "promote_to_p2_113": "推進 P2-113" } + }, + "runtimeReadbackPromotionGate": { + "title": "P2-113 runtime readback promotion gate", + "source": "產生 {generated};目前 {current};下一步 {next}", + "priorFixtureTitle": "前一關 fixture approval", + "truthTitle": "Promotion truth", + "redactionTitle": "前端遮蔽契約", + "metrics": { + "overall": "完成度", + "lanes": "promotion lane", + "contracts": "receipt contract", + "reviewerPreviews": "reviewer queue", + "resultPreviews": "result capture", + "checks": "no-write verifier", + "blockers": "阻塞映射", + "actions": "操作選項", + "approvalRequired": "需批准", + "blocked": "阻擋", + "ownerApprovals": "owner 批准", + "executions": "promotion 執行", + "canonicalReads": "canonical 讀取", + "liveQueries": "live query", + "failureReceiptSends": "failure receipt", + "reviewerQueueWrites": "reviewer 寫入", + "gatewayWrites": "Gateway queue", + "telegramSends": "Telegram 發送", + "botCalls": "Bot API", + "receiptWrites": "receipt 寫入", + "resultWrites": "結果寫入", + "learningWrites": "learning 寫入", + "trustWrites": "PlayBook trust", + "liveWrites": "live writes" + }, + "flags": { + "fixtureLoaded": "P2-112 loaded={value}", + "gateReady": "gate ready={value}", + "failureReceiptReady": "failure receipt ready={value}", + "reviewerReady": "reviewer preview={value}", + "resultReady": "result preview={value}", + "telegramSend": "Telegram send={value}", + "queueWrite": "queue write={value}", + "resultWrite": "result write={value}", + "redactionRequired": "redaction={value}", + "promptDisplay": "prompt display={value}", + "secretDisplay": "secret display={value}", + "runtimePayloadDisplay": "runtime payload display={value}" + }, + "labels": { + "allowedArtifact": "artifact:{value}", + "blockedActions": "blocked actions {count}", + "promotionOnly": "promotion only={value}", + "requiredEvidence": "evidence {count}", + "liveSend": "live send={value}", + "receiptWrite": "receipt write={value}", + "requiredFields": "fields {count}", + "queueWrite": "queue write={value}", + "auditWrite": "audit write={value}", + "resultWrite": "result write={value}", + "learningWrite": "learning write={value}", + "liveVerifier": "live verifier={value}", + "blockedAction": "blocked:{value}", + "blockedUntil": "until:{value}", + "runtimePromotionAllowed": "runtime promotion={value}" + }, + "laneStatuses": { + "ready_for_owner_review": "待 owner review", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "contractStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "previewStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "checkStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "blockerStatuses": { + "mapped": "已映射", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "riskTiers": { + "medium": "中風險", + "high": "高風險", + "critical": "關鍵風險" + }, + "severities": { + "medium": "中", + "high": "高", + "critical": "關鍵" + }, + "actionTypes": { + "review_promotion_lane": "審查 promotion lane", + "verify_failure_receipt_fixture": "驗證 failure receipt fixture", + "confirm_reviewer_queue_no_write": "確認 reviewer queue no-write", + "compare_result_capture_preview": "比對 result capture preview", + "promote_to_p2_114": "推進 P2-114" + } } } }, diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index eb528e84e..e50f227ca 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5332,6 +5332,112 @@ "reject_canonical_target": "退回 canonical target", "promote_to_p2_113": "推進 P2-113" } + }, + "runtimeReadbackPromotionGate": { + "title": "P2-113 runtime readback promotion gate", + "source": "產生 {generated};目前 {current};下一步 {next}", + "priorFixtureTitle": "前一關 fixture approval", + "truthTitle": "Promotion truth", + "redactionTitle": "前端遮蔽契約", + "metrics": { + "overall": "完成度", + "lanes": "promotion lane", + "contracts": "receipt contract", + "reviewerPreviews": "reviewer queue", + "resultPreviews": "result capture", + "checks": "no-write verifier", + "blockers": "阻塞映射", + "actions": "操作選項", + "approvalRequired": "需批准", + "blocked": "阻擋", + "ownerApprovals": "owner 批准", + "executions": "promotion 執行", + "canonicalReads": "canonical 讀取", + "liveQueries": "live query", + "failureReceiptSends": "failure receipt", + "reviewerQueueWrites": "reviewer 寫入", + "gatewayWrites": "Gateway queue", + "telegramSends": "Telegram 發送", + "botCalls": "Bot API", + "receiptWrites": "receipt 寫入", + "resultWrites": "結果寫入", + "learningWrites": "learning 寫入", + "trustWrites": "PlayBook trust", + "liveWrites": "live writes" + }, + "flags": { + "fixtureLoaded": "P2-112 loaded={value}", + "gateReady": "gate ready={value}", + "failureReceiptReady": "failure receipt ready={value}", + "reviewerReady": "reviewer preview={value}", + "resultReady": "result preview={value}", + "telegramSend": "Telegram send={value}", + "queueWrite": "queue write={value}", + "resultWrite": "result write={value}", + "redactionRequired": "redaction={value}", + "promptDisplay": "prompt display={value}", + "secretDisplay": "secret display={value}", + "runtimePayloadDisplay": "runtime payload display={value}" + }, + "labels": { + "allowedArtifact": "artifact:{value}", + "blockedActions": "blocked actions {count}", + "promotionOnly": "promotion only={value}", + "requiredEvidence": "evidence {count}", + "liveSend": "live send={value}", + "receiptWrite": "receipt write={value}", + "requiredFields": "fields {count}", + "queueWrite": "queue write={value}", + "auditWrite": "audit write={value}", + "resultWrite": "result write={value}", + "learningWrite": "learning write={value}", + "liveVerifier": "live verifier={value}", + "blockedAction": "blocked:{value}", + "blockedUntil": "until:{value}", + "runtimePromotionAllowed": "runtime promotion={value}" + }, + "laneStatuses": { + "ready_for_owner_review": "待 owner review", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "contractStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "previewStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "checkStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "blockerStatuses": { + "mapped": "已映射", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "riskTiers": { + "medium": "中風險", + "high": "高風險", + "critical": "關鍵風險" + }, + "severities": { + "medium": "中", + "high": "高", + "critical": "關鍵" + }, + "actionTypes": { + "review_promotion_lane": "審查 promotion lane", + "verify_failure_receipt_fixture": "驗證 failure receipt fixture", + "confirm_reviewer_queue_no_write": "確認 reviewer queue no-write", + "compare_result_capture_preview": "比對 result capture preview", + "promote_to_p2_114": "推進 P2-114" + } } } }, diff --git a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx index db841315b..3498ca4c6 100644 --- a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx +++ b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx @@ -61,6 +61,7 @@ import { type AiAgentRuntimeReadbackApprovalPackageSnapshot, type AiAgentRuntimeReadbackFixtureApprovalSnapshot, type AiAgentRuntimeReadbackImplementationReviewSnapshot, + type AiAgentRuntimeReadbackPromotionGateSnapshot, type AiAgentRuntimeWorkerShadowGateSnapshot, type AiAgentRuntimeVerifierEvidenceReviewSnapshot, type AiAgentRuntimeWriteGateReviewSnapshot, @@ -442,6 +443,7 @@ export function AutomationInventoryTab() { const [runtimeReadbackImplementationReview, setRuntimeReadbackImplementationReview] = useState(null) const [reportLiveDeliveryApprovalPackage, setReportLiveDeliveryApprovalPackage] = useState(null) const [runtimeReadbackFixtureApproval, setRuntimeReadbackFixtureApproval] = useState(null) + const [runtimeReadbackPromotionGate, setRuntimeReadbackPromotionGate] = useState(null) const [reportTruthActionabilityReview, setReportTruthActionabilityReview] = useState(null) const [ownerDryRunPackage, setOwnerDryRunPackage] = useState(null) const [hostStatefulInventory, setHostStatefulInventory] = useState(null) @@ -491,6 +493,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentRuntimeReadbackImplementationReview(), apiClient.getAiAgentReportLiveDeliveryApprovalPackage(), apiClient.getAiAgentRuntimeReadbackFixtureApproval(), + apiClient.getAiAgentRuntimeReadbackPromotionGate(), apiClient.getAiAgentReportTruthActionabilityReview(), apiClient.getAiAgentOwnerApprovedFixtureDryRun(), apiClient.getAiAgentHostStatefulVersionInventory(), @@ -539,6 +542,7 @@ export function AutomationInventoryTab() { runtimeReadbackImplementationReviewResult, reportLiveDeliveryApprovalPackageResult, runtimeReadbackFixtureApprovalResult, + runtimeReadbackPromotionGateResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -584,6 +588,7 @@ export function AutomationInventoryTab() { setRuntimeReadbackImplementationReview(runtimeReadbackImplementationReviewResult.status === 'fulfilled' ? runtimeReadbackImplementationReviewResult.value : null) setReportLiveDeliveryApprovalPackage(reportLiveDeliveryApprovalPackageResult.status === 'fulfilled' ? reportLiveDeliveryApprovalPackageResult.value : null) setRuntimeReadbackFixtureApproval(runtimeReadbackFixtureApprovalResult.status === 'fulfilled' ? runtimeReadbackFixtureApprovalResult.value : null) + setRuntimeReadbackPromotionGate(runtimeReadbackPromotionGateResult.status === 'fulfilled' ? runtimeReadbackPromotionGateResult.value : null) setReportTruthActionabilityReview(reportTruthActionabilityReviewResult.status === 'fulfilled' ? reportTruthActionabilityReviewResult.value : null) setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null) setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null) @@ -627,6 +632,7 @@ export function AutomationInventoryTab() { runtimeReadbackImplementationReviewResult, reportLiveDeliveryApprovalPackageResult, runtimeReadbackFixtureApprovalResult, + runtimeReadbackPromotionGateResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -1869,7 +1875,7 @@ export function AutomationInventoryTab() { ) } - if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !runtimeReadbackApprovalPackage || !runtimeReadbackImplementationReview || !reportLiveDeliveryApprovalPackage || !runtimeReadbackFixtureApproval || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { + if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !runtimeReadbackApprovalPackage || !runtimeReadbackImplementationReview || !reportLiveDeliveryApprovalPackage || !runtimeReadbackFixtureApproval || !runtimeReadbackPromotionGate || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -2309,6 +2315,48 @@ export function AutomationInventoryTab() { + runtimeReadbackFixtureApproval.rollups.result_capture_write_count + runtimeReadbackFixtureApproval.rollups.production_write_count ) + const promotionGateOverall = runtimeReadbackPromotionGate.program_status.overall_completion_percent + const promotionGateLanes = runtimeReadbackPromotionGate.rollups.promotion_lane_count + const promotionGateContracts = runtimeReadbackPromotionGate.rollups.receipt_contract_count + const promotionGateReviewerPreviews = runtimeReadbackPromotionGate.rollups.reviewer_queue_preview_count + const promotionGateResultPreviews = runtimeReadbackPromotionGate.rollups.result_capture_preview_count + const promotionGateChecks = runtimeReadbackPromotionGate.rollups.no_write_verifier_check_count + const promotionGateBlockers = runtimeReadbackPromotionGate.rollups.blocker_mapping_count + const promotionGateActions = runtimeReadbackPromotionGate.rollups.operator_action_count + const promotionGateApprovalRequired = runtimeReadbackPromotionGate.rollups.approval_required_lane_count + const promotionGateBlocked = ( + runtimeReadbackPromotionGate.rollups.blocked_lane_count + + runtimeReadbackPromotionGate.rollups.blocked_receipt_contract_count + + runtimeReadbackPromotionGate.rollups.blocked_result_preview_count + ) + const promotionGateOwnerApprovals = runtimeReadbackPromotionGate.rollups.owner_approval_received_count + const promotionGateExecutions = runtimeReadbackPromotionGate.rollups.promotion_execution_count + const promotionGateCanonicalReads = runtimeReadbackPromotionGate.rollups.canonical_runtime_target_read_count + const promotionGateLiveQueries = runtimeReadbackPromotionGate.rollups.live_query_count + const promotionGateFailureReceiptSends = runtimeReadbackPromotionGate.rollups.failure_receipt_send_count + const promotionGateReviewerQueueWrites = runtimeReadbackPromotionGate.rollups.reviewer_queue_write_count + const promotionGateGatewayWrites = runtimeReadbackPromotionGate.rollups.gateway_queue_write_count + const promotionGateTelegramSends = runtimeReadbackPromotionGate.rollups.telegram_send_count + const promotionGateBotCalls = runtimeReadbackPromotionGate.rollups.bot_api_call_count + const promotionGateReceiptWrites = runtimeReadbackPromotionGate.rollups.report_receipt_write_count + const promotionGateResultWrites = runtimeReadbackPromotionGate.rollups.result_capture_write_count + const promotionGateLearningWrites = runtimeReadbackPromotionGate.rollups.learning_write_count + const promotionGateTrustWrites = runtimeReadbackPromotionGate.rollups.playbook_trust_write_count + const promotionGateLiveWrites = ( + runtimeReadbackPromotionGate.rollups.promotion_execution_count + + runtimeReadbackPromotionGate.rollups.canonical_runtime_target_read_count + + runtimeReadbackPromotionGate.rollups.live_query_count + + runtimeReadbackPromotionGate.rollups.failure_receipt_send_count + + runtimeReadbackPromotionGate.rollups.reviewer_queue_write_count + + runtimeReadbackPromotionGate.rollups.gateway_queue_write_count + + runtimeReadbackPromotionGate.rollups.telegram_send_count + + runtimeReadbackPromotionGate.rollups.bot_api_call_count + + runtimeReadbackPromotionGate.rollups.report_receipt_write_count + + runtimeReadbackPromotionGate.rollups.result_capture_write_count + + runtimeReadbackPromotionGate.rollups.learning_write_count + + runtimeReadbackPromotionGate.rollups.playbook_trust_write_count + + runtimeReadbackPromotionGate.rollups.production_write_count + ) const reportTruthOverall = reportTruthActionabilityReview.program_status.overall_completion_percent const reportTruthFindings = reportTruthActionabilityReview.rollups.zero_signal_finding_count const reportTruthCritical = reportTruthActionabilityReview.rollups.critical_finding_count @@ -5780,6 +5828,237 @@ export function AutomationInventoryTab() {
+
+
+
+ + + {t('runtimeReadbackPromotionGate.title')} + +
+ +
+ +
+ } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
+ +
+
+ {t('runtimeReadbackPromotionGate.priorFixtureTitle')} + + {runtimeReadbackPromotionGate.prior_fixture_approval.readiness_note} + +
+ + + + +
+
+ +
+ {t('runtimeReadbackPromotionGate.truthTitle')} + + {runtimeReadbackPromotionGate.promotion_truth.truth_note} + +
+ + + + + + + + +
+
+ +
+ {t('runtimeReadbackPromotionGate.redactionTitle')} + + {runtimeReadbackPromotionGate.display_redaction_contract.frontend_display_policy} + +
+ + + + +
+
+
+ +
+ {runtimeReadbackPromotionGate.promotion_lanes.map(lane => ( +
+
+ + {lane.display_name} + + +
+ + {lane.operator_guidance} + +
+ + + + + + +
+
+ ))} +
+ +
+ {runtimeReadbackPromotionGate.receipt_contracts.map(contract => ( +
+
+ + {contract.display_name} + + +
+
+ + + + +
+
+ ))} +
+ +
+ {runtimeReadbackPromotionGate.reviewer_queue_previews.map(preview => ( +
+
+ + {preview.display_name} + + +
+
+ + + +
+
+ ))} +
+ +
+ {runtimeReadbackPromotionGate.result_capture_previews.map(preview => ( +
+
+ + {preview.display_name} + + +
+
+ + + +
+
+ ))} +
+ +
+ {runtimeReadbackPromotionGate.no_write_verifier_checks.map(check => ( +
+
+ + {check.display_name} + + +
+ + {check.failure_if_missing} + +
+ + +
+
+ ))} +
+ +
+ {runtimeReadbackPromotionGate.blocker_mappings.map(blocker => ( +
+
+ + {blocker.display_name} + + +
+ + {blocker.source_blocker} + +
+ + + +
+
+ ))} +
+ +
+ {runtimeReadbackPromotionGate.operator_actions.map(action => ( +
+
+ + {action.display_name} + + +
+ + {action.operator_instruction} + +
+ + +
+
+ ))} +
+
+
diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 3620fb541..c66ca5e79 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -460,6 +460,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiAgentRuntimeReadbackPromotionGate() { + const res = await fetch(`${API_BASE_URL}/agents/agent-runtime-readback-promotion-gate`) + return handleResponse(res) + }, + async getAiAgentOwnerApprovedFixtureDryRun() { const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-dry-run`) return handleResponse(res) @@ -4459,6 +4464,180 @@ export interface AiAgentRuntimeReadbackFixtureApprovalSnapshot { } } +export interface AiAgentRuntimeReadbackPromotionGateSnapshot { + schema_version: 'ai_agent_runtime_readback_promotion_gate_v1' + generated_at: string + program_status: { + overall_completion_percent: number + current_priority: 'P0' | 'P1' | 'P2' | 'P3' + current_task_id: 'P2-113' + next_task_id: 'P2-114' + read_only_mode: true + runtime_authority: 'runtime_readback_promotion_gate_only_no_live_queue_or_result_write' + status_note: string + } + source_refs: string[] + prior_fixture_approval: { + fixture_approval_schema_version: 'ai_agent_runtime_readback_fixture_approval_v1' + fixture_approval_card_count: number + adapter_contract_count: number + verifier_fixture_check_count: number + blocker_mapping_count: number + operator_action_count: number + owner_approval_received_count: number + fixture_readback_execution_count: number + live_query_count: number + production_write_count: number + readiness_note: string + } + promotion_truth: { + p2_112_fixture_approval_loaded: true + promotion_gate_ready: true + failure_receipt_fixture_ready: true + reviewer_queue_preview_ready: true + result_capture_preview_ready: true + owner_acceptance_required_before_promotion: true + canonical_runtime_target_read_enabled: false + live_query_enabled: false + failure_receipt_send_enabled: false + reviewer_queue_write_enabled: false + gateway_queue_write_enabled: false + telegram_send_enabled: false + bot_api_call_enabled: false + report_receipt_write_enabled: false + result_capture_write_enabled: false + learning_write_enabled: false + playbook_trust_write_enabled: false + production_write_enabled: false + secret_read_enabled: false + destructive_operation_enabled: false + owner_approval_received_count: number + promotion_execution_count: number + canonical_runtime_target_read_count: number + live_query_count: number + failure_receipt_send_count: number + reviewer_queue_write_count: number + gateway_queue_write_count: number + telegram_send_count: number + bot_api_call_count: number + report_receipt_write_count: number + result_capture_write_count: number + learning_write_count: number + playbook_trust_write_count: number + production_write_count: number + truth_note: string + } + promotion_lanes: Array<{ + lane_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + source_task_id: 'P2-111' | 'P2-112' | 'P2-113' + status: 'ready_for_owner_review' | 'approval_required' | 'blocked_by_policy' + risk_tier: 'medium' | 'high' | 'critical' + allowed_artifact: string + blocked_runtime_actions: string[] + operator_guidance: string + owner_approval_required: true + promotion_only: true + evidence_hash: string + }> + receipt_contracts: Array<{ + contract_id: string + display_name: string + status: 'ready' | 'approval_required' | 'blocked_by_policy' + source_schema: string + output_artifact: string + required_evidence: string[] + live_send_enabled: false + receipt_write_enabled: false + evidence_hash: string + }> + reviewer_queue_previews: Array<{ + preview_id: string + display_name: string + status: 'ready' | 'approval_required' | 'blocked_by_policy' + required_fields: string[] + queue_write_enabled: false + audit_write_enabled: false + evidence_hash: string + }> + result_capture_previews: Array<{ + preview_id: string + display_name: string + status: 'ready' | 'approval_required' | 'blocked_by_policy' + required_fields: string[] + result_capture_write_enabled: false + learning_write_enabled: false + evidence_hash: string + }> + no_write_verifier_checks: Array<{ + check_id: string + display_name: string + status: 'ready' | 'approval_required' | 'blocked_by_policy' + required_fixture: string + failure_if_missing: string + live_verifier_enabled: false + evidence_hash: string + }> + blocker_mappings: Array<{ + blocker_id: string + display_name: string + severity: 'medium' | 'high' | 'critical' + status: 'mapped' | 'approval_required' | 'blocked_by_policy' + source_blocker: string + blocked_action: string + blocked_until: string + evidence_hash: string + }> + operator_actions: Array<{ + action_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + action_type: 'review_promotion_lane' | 'verify_failure_receipt_fixture' | 'confirm_reviewer_queue_no_write' | 'compare_result_capture_preview' | 'promote_to_p2_114' + operator_instruction: string + runtime_promotion_allowed: false + }> + display_redaction_contract: { + redaction_required: true + raw_prompt_display_allowed: false + private_reasoning_display_allowed: false + secret_value_display_allowed: false + raw_runtime_payload_display_allowed: false + internal_collaboration_content_display_allowed: false + frontend_display_policy: string + } + rollups: { + promotion_lane_count: number + receipt_contract_count: number + reviewer_queue_preview_count: number + result_capture_preview_count: number + no_write_verifier_check_count: number + blocker_mapping_count: number + operator_action_count: number + approval_required_lane_count: number + blocked_lane_count: number + blocked_receipt_contract_count: number + approval_required_reviewer_preview_count: number + blocked_result_preview_count: number + owner_approval_received_count: number + promotion_execution_count: number + canonical_runtime_target_read_count: number + live_query_count: number + failure_receipt_send_count: number + reviewer_queue_write_count: number + gateway_queue_write_count: number + telegram_send_count: number + bot_api_call_count: number + report_receipt_write_count: number + result_capture_write_count: number + learning_write_count: number + playbook_trust_write_count: number + production_write_count: number + secret_read_count: number + destructive_operation_count: number + } +} + export interface AiAgentOwnerApprovedFixtureDryRunSnapshot { schema_version: 'ai_agent_owner_approved_fixture_dry_run_v1' generated_at: string diff --git a/docs/evaluations/ai_agent_runtime_readback_promotion_gate_2026-06-13.json b/docs/evaluations/ai_agent_runtime_readback_promotion_gate_2026-06-13.json new file mode 100644 index 000000000..aecbbf7de --- /dev/null +++ b/docs/evaluations/ai_agent_runtime_readback_promotion_gate_2026-06-13.json @@ -0,0 +1,511 @@ +{ + "schema_version": "ai_agent_runtime_readback_promotion_gate_v1", + "generated_at": "2026-06-13T18:20:00+08:00", + "program_status": { + "overall_completion_percent": 100, + "current_priority": "P2", + "current_task_id": "P2-113", + "next_task_id": "P2-114", + "read_only_mode": true, + "runtime_authority": "runtime_readback_promotion_gate_only_no_live_queue_or_result_write", + "status_note": "P2-113 只建立 failure receipt、reviewer queue、result capture 的 no-write promotion gate;未批准前不得 live query、不得寫 Gateway / Telegram / reviewer queue / result capture / production。" + }, + "source_refs": [ + "docs/evaluations/ai_agent_runtime_readback_fixture_approval_2026-06-13.json", + "docs/evaluations/ai_agent_owner_approved_result_capture_readback_2026-06-13.json", + "docs/evaluations/ai_agent_report_live_delivery_approval_package_2026-06-13.json", + "docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md#33-建立-runtime-readback-fixture-approval" + ], + "prior_fixture_approval": { + "fixture_approval_schema_version": "ai_agent_runtime_readback_fixture_approval_v1", + "fixture_approval_card_count": 5, + "adapter_contract_count": 4, + "verifier_fixture_check_count": 5, + "blocker_mapping_count": 5, + "operator_action_count": 5, + "owner_approval_received_count": 0, + "fixture_readback_execution_count": 0, + "live_query_count": 0, + "production_write_count": 0, + "readiness_note": "P2-112 已正式驗證 fixture-only approval package;P2-113 只把它轉成 no-write promotion gate,不讀 live target、不寫任何 queue 或 result。" + }, + "promotion_truth": { + "p2_112_fixture_approval_loaded": true, + "promotion_gate_ready": true, + "failure_receipt_fixture_ready": true, + "reviewer_queue_preview_ready": true, + "result_capture_preview_ready": true, + "owner_acceptance_required_before_promotion": true, + "canonical_runtime_target_read_enabled": false, + "live_query_enabled": false, + "failure_receipt_send_enabled": false, + "reviewer_queue_write_enabled": false, + "gateway_queue_write_enabled": false, + "telegram_send_enabled": false, + "bot_api_call_enabled": false, + "report_receipt_write_enabled": false, + "result_capture_write_enabled": false, + "learning_write_enabled": false, + "playbook_trust_write_enabled": false, + "production_write_enabled": false, + "secret_read_enabled": false, + "destructive_operation_enabled": false, + "owner_approval_received_count": 0, + "promotion_execution_count": 0, + "canonical_runtime_target_read_count": 0, + "live_query_count": 0, + "failure_receipt_send_count": 0, + "reviewer_queue_write_count": 0, + "gateway_queue_write_count": 0, + "telegram_send_count": 0, + "bot_api_call_count": 0, + "report_receipt_write_count": 0, + "result_capture_write_count": 0, + "learning_write_count": 0, + "playbook_trust_write_count": 0, + "production_write_count": 0, + "truth_note": "promotion gate 已可審查;真正 failure receipt、reviewer queue、result capture 與 live readback 仍全部為 0。" + }, + "promotion_lanes": [ + { + "lane_id": "telegram_failure_receipt_promotion", + "display_name": "Telegram failure receipt no-send promotion", + "owner_agent": "hermes", + "source_task_id": "P2-112", + "status": "ready_for_owner_review", + "risk_tier": "high", + "allowed_artifact": "no_send_failure_receipt_fixture", + "blocked_runtime_actions": [ + "telegram_send", + "bot_api_call", + "report_receipt_write" + ], + "operator_guidance": "只允許檢查 no-send failure receipt fixture;真正送出 Telegram 或寫 receipt 必須等 P2-114 之後另行批准。", + "owner_approval_required": true, + "promotion_only": true, + "evidence_hash": "sha256:1010101010101010101010101010101010101010101010101010101010101010" + }, + { + "lane_id": "reviewer_queue_preview_promotion", + "display_name": "Reviewer queue preview no-write promotion", + "owner_agent": "openclaw", + "source_task_id": "P2-112", + "status": "ready_for_owner_review", + "risk_tier": "medium", + "allowed_artifact": "reviewer_queue_preview_digest", + "blocked_runtime_actions": [ + "reviewer_queue_write", + "timeline_write", + "audit_db_write" + ], + "operator_guidance": "把 reviewer 要看的欄位、決策模板與阻擋原因做成 preview;不得建立實際 queue item。", + "owner_approval_required": true, + "promotion_only": true, + "evidence_hash": "sha256:2020202020202020202020202020202020202020202020202020202020202020" + }, + { + "lane_id": "result_capture_no_write_promotion", + "display_name": "Result capture no-write promotion", + "owner_agent": "nemotron", + "source_task_id": "P2-112", + "status": "approval_required", + "risk_tier": "critical", + "allowed_artifact": "result_capture_preview_digest", + "blocked_runtime_actions": [ + "score_write", + "result_capture_write", + "playbook_trust_write" + ], + "operator_guidance": "只比對 critic / reviewer score fixture 與 no-write result digest;不得寫 score、result capture 或 PlayBook trust。", + "owner_approval_required": true, + "promotion_only": true, + "evidence_hash": "sha256:3030303030303030303030303030303030303030303030303030303030303030" + }, + { + "lane_id": "report_delivery_receipt_link", + "display_name": "Report delivery receipt fixture link", + "owner_agent": "hermes", + "source_task_id": "P2-111", + "status": "approval_required", + "risk_tier": "high", + "allowed_artifact": "report_delivery_receipt_preview", + "blocked_runtime_actions": [ + "gateway_queue_write", + "report_receipt_write", + "ai_analysis_run" + ], + "operator_guidance": "僅把報表 no-send receipt 與 P2-112 fixture 做關聯;不得觸發報表實發或 AI 讀報分析。", + "owner_approval_required": true, + "promotion_only": true, + "evidence_hash": "sha256:4040404040404040404040404040404040404040404040404040404040404040" + }, + { + "lane_id": "p2_114_handoff_scope", + "display_name": "P2-114 handoff scope hold", + "owner_agent": "openclaw", + "source_task_id": "P2-113", + "status": "blocked_by_policy", + "risk_tier": "critical", + "allowed_artifact": "operator_handoff_scope", + "blocked_runtime_actions": [ + "canonical_runtime_target_read", + "live_query", + "production_write" + ], + "operator_guidance": "P2-114 前只能整理 handoff scope;owner acceptance record、rollback owner 與 verifier plan 未齊前不得 live readback。", + "owner_approval_required": true, + "promotion_only": true, + "evidence_hash": "sha256:5050505050505050505050505050505050505050505050505050505050505050" + } + ], + "receipt_contracts": [ + { + "contract_id": "no_send_failure_receipt_contract", + "display_name": "no-send failure receipt contract", + "status": "ready", + "source_schema": "ai_agent_runtime_readback_fixture_approval_v1.fixture_approval_cards", + "output_artifact": "failure_receipt_preview", + "required_evidence": [ + "failure_reason", + "route_lock_id", + "redaction_contract_id" + ], + "live_send_enabled": false, + "receipt_write_enabled": false, + "evidence_hash": "sha256:6060606060606060606060606060606060606060606060606060606060606060" + }, + { + "contract_id": "sre_war_room_route_lock_contract", + "display_name": "SRE 戰情室 route lock contract", + "status": "ready", + "source_schema": "ai_agent_report_live_delivery_approval_package_v1.route_lock_gates", + "output_artifact": "sre_route_lock_preview", + "required_evidence": [ + "sre_group_route", + "legacy_route_suppression", + "dedupe_fingerprint" + ], + "live_send_enabled": false, + "receipt_write_enabled": false, + "evidence_hash": "sha256:7070707070707070707070707070707070707070707070707070707070707070" + }, + { + "contract_id": "redacted_receipt_payload_contract", + "display_name": "redacted receipt payload contract", + "status": "ready", + "source_schema": "ai_agent_report_live_delivery_approval_package_v1.payload_redaction_checks", + "output_artifact": "redacted_receipt_payload_digest", + "required_evidence": [ + "payload_digest", + "redaction_check_id", + "no_secret_marker" + ], + "live_send_enabled": false, + "receipt_write_enabled": false, + "evidence_hash": "sha256:8080808080808080808080808080808080808080808080808080808080808080" + }, + { + "contract_id": "no_bot_api_call_contract", + "display_name": "Bot API call hold contract", + "status": "blocked_by_policy", + "source_schema": "ai_agent_runtime_readback_fixture_approval_v1.blocker_mappings", + "output_artifact": "bot_api_hold_record", + "required_evidence": [ + "owner_acceptance_record", + "telegram_token_injection_e2e", + "rollback_owner" + ], + "live_send_enabled": false, + "receipt_write_enabled": false, + "evidence_hash": "sha256:9090909090909090909090909090909090909090909090909090909090909090" + } + ], + "reviewer_queue_previews": [ + { + "preview_id": "owner_review_task_preview", + "display_name": "Owner review task preview", + "status": "ready", + "required_fields": [ + "owner_role", + "decision", + "decision_reason" + ], + "queue_write_enabled": false, + "audit_write_enabled": false, + "evidence_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + { + "preview_id": "reviewer_decision_template_preview", + "display_name": "Reviewer decision template preview", + "status": "ready", + "required_fields": [ + "risk_tier", + "allowed_artifact", + "blocked_action" + ], + "queue_write_enabled": false, + "audit_write_enabled": false, + "evidence_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + { + "preview_id": "escalation_reason_preview", + "display_name": "Escalation reason preview", + "status": "approval_required", + "required_fields": [ + "blocked_until", + "missing_evidence", + "next_owner" + ], + "queue_write_enabled": false, + "audit_write_enabled": false, + "evidence_hash": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + }, + { + "preview_id": "no_write_queue_digest_preview", + "display_name": "No-write queue digest preview", + "status": "ready", + "required_fields": [ + "preview_digest", + "source_snapshot", + "redaction_status" + ], + "queue_write_enabled": false, + "audit_write_enabled": false, + "evidence_hash": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + } + ], + "result_capture_previews": [ + { + "preview_id": "critic_score_preview", + "display_name": "Critic score no-write preview", + "status": "ready", + "required_fields": [ + "score_fixture", + "reason_digest", + "confidence_bucket" + ], + "result_capture_write_enabled": false, + "learning_write_enabled": false, + "evidence_hash": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + }, + { + "preview_id": "reviewer_score_preview", + "display_name": "Reviewer score no-write preview", + "status": "approval_required", + "required_fields": [ + "reviewer_fixture", + "safety_decision", + "disagreement_hold" + ], + "result_capture_write_enabled": false, + "learning_write_enabled": false, + "evidence_hash": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + { + "preview_id": "playbook_trust_preview", + "display_name": "PlayBook trust no-write preview", + "status": "blocked_by_policy", + "required_fields": [ + "matched_playbook_id", + "trust_delta_fixture", + "owner_acceptance" + ], + "result_capture_write_enabled": false, + "learning_write_enabled": false, + "evidence_hash": "sha256:1212121212121212121212121212121212121212121212121212121212121212" + }, + { + "preview_id": "km_learning_preview", + "display_name": "KM learning no-write preview", + "status": "approval_required", + "required_fields": [ + "km_draft_id", + "source_incident", + "owner_review" + ], + "result_capture_write_enabled": false, + "learning_write_enabled": false, + "evidence_hash": "sha256:1313131313131313131313131313131313131313131313131313131313131313" + } + ], + "no_write_verifier_checks": [ + { + "check_id": "no_live_query_promotion_check", + "display_name": "no live query promotion check", + "status": "ready", + "required_fixture": "promotion lane must not read canonical runtime target", + "failure_if_missing": "缺 no-live-query 證據時不得推進 P2-114。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:1414141414141414141414141414141414141414141414141414141414141414" + }, + { + "check_id": "no_gateway_queue_write_check", + "display_name": "no Gateway queue write check", + "status": "ready", + "required_fixture": "Gateway queue write count remains 0", + "failure_if_missing": "queue write 不是 fixture,任何非 0 都要退回。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:1515151515151515151515151515151515151515151515151515151515151515" + }, + { + "check_id": "no_telegram_bot_send_check", + "display_name": "no Telegram / Bot API send check", + "status": "ready", + "required_fixture": "Telegram send and Bot API call counts remain 0", + "failure_if_missing": "發送或 Bot API call 非 0 時不得宣稱 no-send promotion。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:1616161616161616161616161616161616161616161616161616161616161616" + }, + { + "check_id": "no_reviewer_queue_write_check", + "display_name": "no reviewer queue write check", + "status": "approval_required", + "required_fixture": "reviewer queue preview only; write count remains 0", + "failure_if_missing": "缺 queue preview 或 write count 非 0 時不得建立 reviewer item。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:1717171717171717171717171717171717171717171717171717171717171717" + }, + { + "check_id": "no_result_capture_write_check", + "display_name": "no result capture write check", + "status": "blocked_by_policy", + "required_fixture": "result capture preview only; score/write count remains 0", + "failure_if_missing": "缺 no-write preview 時不得進入 result capture promotion。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:1818181818181818181818181818181818181818181818181818181818181818" + } + ], + "blocker_mappings": [ + { + "blocker_id": "owner_acceptance_missing", + "display_name": "Owner acceptance record missing", + "severity": "critical", + "status": "blocked_by_policy", + "source_blocker": "缺 owner acceptance record;不得 promotion 到 live readback。", + "blocked_action": "promote_to_live_readback", + "blocked_until": "owner_acceptance_record_accepted", + "evidence_hash": "sha256:1919191919191919191919191919191919191919191919191919191919191919" + }, + { + "blocker_id": "canonical_readback_not_approved", + "display_name": "Canonical readback not approved", + "severity": "critical", + "status": "blocked_by_policy", + "source_blocker": "P2-113 不授權 canonical runtime target read。", + "blocked_action": "canonical_runtime_target_read", + "blocked_until": "p2_114_owner_approval", + "evidence_hash": "sha256:2020202020202020202020202020202020202020202020202020202020202020" + }, + { + "blocker_id": "telegram_send_not_approved", + "display_name": "Telegram send not approved", + "severity": "high", + "status": "approval_required", + "source_blocker": "SRE 戰情室 route 可預覽,但不得 send。", + "blocked_action": "telegram_send_or_bot_api_call", + "blocked_until": "telegram_e2e_delivery_gate", + "evidence_hash": "sha256:2121212121212121212121212121212121212121212121212121212121212121" + }, + { + "blocker_id": "reviewer_queue_write_not_approved", + "display_name": "Reviewer queue write not approved", + "severity": "high", + "status": "approval_required", + "source_blocker": "只能顯示 reviewer queue preview;不得寫 queue / audit / timeline。", + "blocked_action": "reviewer_queue_write", + "blocked_until": "queue_write_gate_accepted", + "evidence_hash": "sha256:2222222222222222222222222222222222222222222222222222222222222222" + }, + { + "blocker_id": "result_capture_write_not_approved", + "display_name": "Result capture write not approved", + "severity": "critical", + "status": "blocked_by_policy", + "source_blocker": "score、result capture、learning、PlayBook trust 全部不得寫入。", + "blocked_action": "result_capture_write", + "blocked_until": "result_capture_write_gate_accepted", + "evidence_hash": "sha256:2323232323232323232323232323232323232323232323232323232323232323" + } + ], + "operator_actions": [ + { + "action_id": "review_promotion_lanes", + "display_name": "審查 promotion lanes", + "owner_agent": "openclaw", + "action_type": "review_promotion_lane", + "operator_instruction": "逐 lane 確認 allowed artifact 與 blocked runtime action 是否成對,任何 live write 疑慮都退回。", + "runtime_promotion_allowed": false + }, + { + "action_id": "verify_failure_receipt_fixture", + "display_name": "驗證 failure receipt fixture", + "owner_agent": "hermes", + "action_type": "verify_failure_receipt_fixture", + "operator_instruction": "只看 no-send receipt digest、route lock 與 redaction 結果,不送 Telegram、不呼叫 Bot API。", + "runtime_promotion_allowed": false + }, + { + "action_id": "confirm_queue_no_write", + "display_name": "確認 reviewer queue no-write", + "owner_agent": "openclaw", + "action_type": "confirm_reviewer_queue_no_write", + "operator_instruction": "確認 reviewer queue preview 足夠人工判讀,但 queue write、audit write、timeline write 必須維持 0。", + "runtime_promotion_allowed": false + }, + { + "action_id": "compare_result_capture_preview", + "display_name": "比對 result capture preview", + "owner_agent": "nemotron", + "action_type": "compare_result_capture_preview", + "operator_instruction": "比對 critic/reviewer score fixture 與 no-write digest;不得寫 score、learning 或 PlayBook trust。", + "runtime_promotion_allowed": false + }, + { + "action_id": "promote_to_p2_114", + "display_name": "推進 P2-114", + "owner_agent": "openclaw", + "action_type": "promote_to_p2_114", + "operator_instruction": "只有在 no-send、no-write、no-live-query 與 owner acceptance 缺口都明確後,才能進 P2-114;不是 live approval。", + "runtime_promotion_allowed": false + } + ], + "display_redaction_contract": { + "redaction_required": true, + "raw_prompt_display_allowed": false, + "private_reasoning_display_allowed": false, + "secret_value_display_allowed": false, + "raw_runtime_payload_display_allowed": false, + "internal_collaboration_content_display_allowed": false, + "frontend_display_policy": "只顯示 promotion lane、preview digest、blocked action 與 owner handoff;不顯示內部協作逐字稿、提示詞、私密推理內容、secret 或原始 runtime payload。" + }, + "rollups": { + "promotion_lane_count": 5, + "receipt_contract_count": 4, + "reviewer_queue_preview_count": 4, + "result_capture_preview_count": 4, + "no_write_verifier_check_count": 5, + "blocker_mapping_count": 5, + "operator_action_count": 5, + "approval_required_lane_count": 2, + "blocked_lane_count": 1, + "blocked_receipt_contract_count": 1, + "approval_required_reviewer_preview_count": 1, + "blocked_result_preview_count": 1, + "owner_approval_received_count": 0, + "promotion_execution_count": 0, + "canonical_runtime_target_read_count": 0, + "live_query_count": 0, + "failure_receipt_send_count": 0, + "reviewer_queue_write_count": 0, + "gateway_queue_write_count": 0, + "telegram_send_count": 0, + "bot_api_call_count": 0, + "report_receipt_write_count": 0, + "result_capture_write_count": 0, + "learning_write_count": 0, + "playbook_trust_write_count": 0, + "production_write_count": 0, + "secret_read_count": 0, + "destructive_operation_count": 0 + } +} diff --git a/docs/schemas/ai_agent_runtime_readback_promotion_gate_v1.schema.json b/docs/schemas/ai_agent_runtime_readback_promotion_gate_v1.schema.json new file mode 100644 index 000000000..40ba2f50a --- /dev/null +++ b/docs/schemas/ai_agent_runtime_readback_promotion_gate_v1.schema.json @@ -0,0 +1,450 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.wooo.work/schemas/ai_agent_runtime_readback_promotion_gate_v1.schema.json", + "title": "AI Agent Runtime Readback Promotion Gate v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "prior_fixture_approval", + "promotion_truth", + "promotion_lanes", + "receipt_contracts", + "reviewer_queue_previews", + "result_capture_previews", + "no_write_verifier_checks", + "blocker_mappings", + "operator_actions", + "display_redaction_contract", + "rollups" + ], + "properties": { + "schema_version": { + "const": "ai_agent_runtime_readback_promotion_gate_v1" + }, + "generated_at": { + "type": "string", + "minLength": 1 + }, + "program_status": { + "type": "object", + "additionalProperties": false, + "required": [ + "overall_completion_percent", + "current_priority", + "current_task_id", + "next_task_id", + "read_only_mode", + "runtime_authority", + "status_note" + ], + "properties": { + "overall_completion_percent": { + "const": 100 + }, + "current_priority": { + "const": "P2" + }, + "current_task_id": { + "const": "P2-113" + }, + "next_task_id": { + "const": "P2-114" + }, + "read_only_mode": { + "const": true + }, + "runtime_authority": { + "const": "runtime_readback_promotion_gate_only_no_live_queue_or_result_write" + }, + "status_note": { + "type": "string", + "minLength": 1 + } + } + }, + "source_refs": { + "$ref": "#/$defs/string_array" + }, + "prior_fixture_approval": { + "type": "object" + }, + "promotion_truth": { + "type": "object" + }, + "promotion_lanes": { + "type": "array", + "minItems": 5, + "items": { + "$ref": "#/$defs/promotion_lane" + } + }, + "receipt_contracts": { + "type": "array", + "minItems": 4, + "items": { + "$ref": "#/$defs/receipt_contract" + } + }, + "reviewer_queue_previews": { + "type": "array", + "minItems": 4, + "items": { + "$ref": "#/$defs/queue_preview" + } + }, + "result_capture_previews": { + "type": "array", + "minItems": 4, + "items": { + "$ref": "#/$defs/result_preview" + } + }, + "no_write_verifier_checks": { + "type": "array", + "minItems": 5, + "items": { + "$ref": "#/$defs/verifier_check" + } + }, + "blocker_mappings": { + "type": "array", + "minItems": 5, + "items": { + "$ref": "#/$defs/blocker" + } + }, + "operator_actions": { + "type": "array", + "minItems": 5, + "items": { + "$ref": "#/$defs/operator_action" + } + }, + "display_redaction_contract": { + "type": "object" + }, + "rollups": { + "type": "object" + } + }, + "$defs": { + "string_array": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "hash": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "owner_agent": { + "enum": [ + "openclaw", + "hermes", + "nemotron" + ] + }, + "status": { + "enum": [ + "ready", + "ready_for_owner_review", + "approval_required", + "blocked_by_policy" + ] + }, + "risk_tier": { + "enum": [ + "medium", + "high", + "critical" + ] + }, + "promotion_lane": { + "type": "object", + "required": [ + "lane_id", + "display_name", + "owner_agent", + "source_task_id", + "status", + "risk_tier", + "allowed_artifact", + "blocked_runtime_actions", + "operator_guidance", + "owner_approval_required", + "promotion_only", + "evidence_hash" + ], + "properties": { + "lane_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "owner_agent": { + "$ref": "#/$defs/owner_agent" + }, + "source_task_id": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/status" + }, + "risk_tier": { + "$ref": "#/$defs/risk_tier" + }, + "allowed_artifact": { + "type": "string" + }, + "blocked_runtime_actions": { + "$ref": "#/$defs/string_array" + }, + "operator_guidance": { + "type": "string" + }, + "owner_approval_required": { + "const": true + }, + "promotion_only": { + "const": true + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + }, + "additionalProperties": false + }, + "receipt_contract": { + "type": "object", + "required": [ + "contract_id", + "display_name", + "status", + "source_schema", + "output_artifact", + "required_evidence", + "live_send_enabled", + "receipt_write_enabled", + "evidence_hash" + ], + "properties": { + "contract_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/status" + }, + "source_schema": { + "type": "string" + }, + "output_artifact": { + "type": "string" + }, + "required_evidence": { + "$ref": "#/$defs/string_array" + }, + "live_send_enabled": { + "const": false + }, + "receipt_write_enabled": { + "const": false + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + }, + "additionalProperties": false + }, + "queue_preview": { + "type": "object", + "required": [ + "preview_id", + "display_name", + "status", + "required_fields", + "queue_write_enabled", + "audit_write_enabled", + "evidence_hash" + ], + "properties": { + "preview_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/status" + }, + "required_fields": { + "$ref": "#/$defs/string_array" + }, + "queue_write_enabled": { + "const": false + }, + "audit_write_enabled": { + "const": false + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + }, + "additionalProperties": false + }, + "result_preview": { + "type": "object", + "required": [ + "preview_id", + "display_name", + "status", + "required_fields", + "result_capture_write_enabled", + "learning_write_enabled", + "evidence_hash" + ], + "properties": { + "preview_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/status" + }, + "required_fields": { + "$ref": "#/$defs/string_array" + }, + "result_capture_write_enabled": { + "const": false + }, + "learning_write_enabled": { + "const": false + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + }, + "additionalProperties": false + }, + "verifier_check": { + "type": "object", + "required": [ + "check_id", + "display_name", + "status", + "required_fixture", + "failure_if_missing", + "live_verifier_enabled", + "evidence_hash" + ], + "properties": { + "check_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/status" + }, + "required_fixture": { + "type": "string" + }, + "failure_if_missing": { + "type": "string" + }, + "live_verifier_enabled": { + "const": false + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + }, + "additionalProperties": false + }, + "blocker": { + "type": "object", + "required": [ + "blocker_id", + "display_name", + "severity", + "status", + "source_blocker", + "blocked_action", + "blocked_until", + "evidence_hash" + ], + "properties": { + "blocker_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "severity": { + "$ref": "#/$defs/risk_tier" + }, + "status": { + "$ref": "#/$defs/status" + }, + "source_blocker": { + "type": "string" + }, + "blocked_action": { + "type": "string" + }, + "blocked_until": { + "type": "string" + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + }, + "additionalProperties": false + }, + "operator_action": { + "type": "object", + "required": [ + "action_id", + "display_name", + "owner_agent", + "action_type", + "operator_instruction", + "runtime_promotion_allowed" + ], + "properties": { + "action_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "owner_agent": { + "$ref": "#/$defs/owner_agent" + }, + "action_type": { + "type": "string" + }, + "operator_instruction": { + "type": "string" + }, + "runtime_promotion_allowed": { + "const": false + } + }, + "additionalProperties": false + } + } +}