From 4c292e4bef81c647417fcdc146938ec988046372 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 14 Jun 2026 01:11:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(governance):=20=E6=96=B0=E5=A2=9E=20result?= =?UTF-8?q?=20capture=20owner=20promotion=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/api/v1/agents.py | 33 ++ ...t_result_capture_owner_promotion_review.py | 380 ++++++++++++++++ ...t_result_capture_owner_promotion_review.py | 118 +++++ ...sult_capture_owner_promotion_review_api.py | 47 ++ apps/web/messages/en.json | 34 ++ apps/web/messages/zh-TW.json | 34 ++ .../tabs/automation-inventory-tab.tsx | 165 ++++++- apps/web/src/lib/api-client.ts | 174 ++++++++ ...ure_owner_promotion_review_2026-06-14.json | 414 ++++++++++++++++++ ...ture_owner_promotion_review_v1.schema.json | 91 ++++ 10 files changed, 1489 insertions(+), 1 deletion(-) create mode 100644 apps/api/src/services/ai_agent_result_capture_owner_promotion_review.py create mode 100644 apps/api/tests/test_ai_agent_result_capture_owner_promotion_review.py create mode 100644 apps/api/tests/test_ai_agent_result_capture_owner_promotion_review_api.py create mode 100644 docs/evaluations/ai_agent_result_capture_owner_promotion_review_2026-06-14.json create mode 100644 docs/schemas/ai_agent_result_capture_owner_promotion_review_v1.schema.json diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 3ed2b0174..a7c48f33b 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -121,6 +121,9 @@ from src.services.ai_agent_reviewer_queue_no_write_readback import ( from src.services.ai_agent_result_capture_no_write_readback import ( load_latest_ai_agent_result_capture_no_write_readback, ) +from src.services.ai_agent_result_capture_owner_promotion_review import ( + load_latest_ai_agent_result_capture_owner_promotion_review, +) from src.services.ai_agent_result_capture_promotion_approval_gate import ( load_latest_ai_agent_result_capture_promotion_approval_gate, ) @@ -1818,6 +1821,36 @@ async def get_agent_result_capture_writer_dry_run_readback() -> dict[str, Any]: ) from exc +@router.get( + "/agent-result-capture-owner-promotion-review", + response_model=dict[str, Any], + summary="取得 AI Agent result capture owner promotion review", + description=( + "讀取最新已提交的 P2-125 result capture owner promotion review;" + "此端點只回傳 owner promotion packet、execution gate、rollback owner review、" + "blocked execution write 與 operator handoff,不套用 writer、不執行 live apply、" + "不寫 receipt、不寫 result capture、learning、PlayBook trust、reviewer queue、" + "Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。" + ), +) +async def get_agent_result_capture_owner_promotion_review() -> dict[str, Any]: + """Return the latest read-only result capture owner promotion review package.""" + try: + payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_owner_promotion_review) + 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_result_capture_owner_promotion_review_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent result capture owner promotion review 無效", + ) from exc + + @router.get( "/agent-owner-approved-fixture-dry-run", response_model=dict[str, Any], diff --git a/apps/api/src/services/ai_agent_result_capture_owner_promotion_review.py b/apps/api/src/services/ai_agent_result_capture_owner_promotion_review.py new file mode 100644 index 000000000..398904ebb --- /dev/null +++ b/apps/api/src/services/ai_agent_result_capture_owner_promotion_review.py @@ -0,0 +1,380 @@ +""" +AI Agent result capture owner promotion review snapshot. + +Loads the latest committed P2-125 owner promotion review / execution gate package. +This module validates committed evidence only; it never applies writers, executes +live applies, writes receipts, writes result captures, writes learning records, +updates PlayBook trust, writes reviewer / Gateway queues, sends Telegram +messages, reads canonical runtime targets, 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_result_capture_owner_promotion_review_*.json" +_SCHEMA_VERSION = "ai_agent_result_capture_owner_promotion_review_v1" +_RUNTIME_AUTHORITY = "result_capture_owner_promotion_review_only_no_live_write" + + +def load_latest_ai_agent_result_capture_owner_promotion_review( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed result capture owner promotion review package.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError(f"no AI Agent result capture owner promotion review 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_packets(payload, label) + _require_execution_gates(payload, label) + _require_rollback_reviews(payload, label) + _require_blocked_writes(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-125", + "next_task_id": "P2-126", + "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_result_capture_writer_dry_run_readback") or {} + expected = { + "schema_version": "ai_agent_result_capture_writer_dry_run_readback_v1", + "dry_run_readback_card_count": 5, + "receipt_verifier_check_count": 5, + "promotion_readiness_gate_count": 5, + "blocked_promotion_write_count": 6, + "operator_action_count": 5, + "writer_apply_count": 0, + "dry_run_execution_count": 0, + "receipt_write_count": 0, + "promotion_apply_count": 0, + "result_capture_write_count": 0, + "learning_write_count": 0, + "playbook_trust_write_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, + } + mismatches = _mismatches(prior, expected) + if mismatches: + raise ValueError(f"{label}: prior_result_capture_writer_dry_run_readback mismatch: {mismatches}") + if not prior.get("readiness_note"): + raise ValueError(f"{label}: prior_result_capture_writer_dry_run_readback.readiness_note is required") + + +def _require_truth(payload: dict[str, Any], label: str) -> None: + truth = payload.get("owner_promotion_truth") or {} + required_true = { + "p2_124_readback_loaded", + "owner_promotion_review_ready", + "execution_gate_ready", + "rollback_owner_review_ready", + "dual_approval_required", + "post_write_verifier_required", + "idempotency_key_required", + "redaction_review_required", + } + missing = sorted(field for field in required_true if truth.get(field) is not True) + if missing: + raise ValueError(f"{label}: owner promotion ready flags must remain true: {missing}") + required_false = { + "owner_promotion_approved", + "execution_apply_enabled", + "writer_apply_enabled", + "receipt_write_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 execution/send/write flags must remain false: {unsafe}") + zero_counts = { + "owner_promotion_approved_count", + "execution_gate_pass_count", + "rollback_owner_confirmed_count", + "post_write_verifier_ready_count", + "writer_apply_count", + "execution_apply_count", + "receipt_write_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_counts if truth.get(field) != 0) + if non_zero: + raise ValueError(f"{label}: owner promotion live counters must remain zero: {non_zero}") + if not truth.get("truth_note"): + raise ValueError(f"{label}: owner_promotion_truth.truth_note is required") + + +def _require_packets(payload: dict[str, Any], label: str) -> None: + packets = payload.get("owner_promotion_packets") or [] + required = { + "packet_result_capture_writer", + "packet_learning_writer", + "packet_playbook_trust_writer", + "packet_reviewer_queue_writer", + "packet_gateway_queue_writer", + } + ids = {packet.get("packet_id") for packet in packets} + if ids != required: + raise ValueError(f"{label}: owner promotion packets must match {sorted(required)}") + for packet in packets: + packet_id = packet.get("packet_id") + if packet.get("promotion_review_only") is not True: + raise ValueError(f"{label}: packet {packet_id} must remain review-only") + if packet.get("execution_apply_enabled") is not False: + raise ValueError(f"{label}: packet {packet_id} must not enable execution apply") + if packet.get("status") not in {"ready_for_owner_review", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: packet {packet_id} status is invalid") + if not packet.get("required_owner") or not packet.get("operator_note"): + raise ValueError(f"{label}: packet {packet_id} must include owner and operator note") + if len(packet.get("required_decision_fields") or []) < 5: + raise ValueError(f"{label}: packet {packet_id} must include decision fields") + if not _is_redacted_sha256(packet.get("evidence_hash")): + raise ValueError(f"{label}: packet {packet_id} must expose redacted evidence_hash") + + +def _require_execution_gates(payload: dict[str, Any], label: str) -> None: + gates = payload.get("execution_gate_checks") or [] + required = { + "gate_owner_promotion_approval", + "gate_dual_approval", + "gate_dry_run_hash_lock", + "gate_post_write_verifier_plan", + "gate_production_write_block", + } + ids = {gate.get("gate_id") for gate in gates} + if ids != required: + raise ValueError(f"{label}: execution gate checks must match {sorted(required)}") + for gate in gates: + gate_id = gate.get("gate_id") + if gate.get("gate_mode") != "no_write_execution_gate": + raise ValueError(f"{label}: gate {gate_id} must remain no-write") + if gate.get("execution_enabled") is not False: + raise ValueError(f"{label}: gate {gate_id} must not enable execution") + if gate.get("required_before_execution") is not True: + raise ValueError(f"{label}: gate {gate_id} must be required before execution") + if gate.get("status") not in {"ready_for_owner_review", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: gate {gate_id} status is invalid") + if not gate.get("failure_if_missing"): + raise ValueError(f"{label}: gate {gate_id} failure_if_missing is required") + + +def _require_rollback_reviews(payload: dict[str, Any], label: str) -> None: + reviews = payload.get("rollback_owner_reviews") or [] + required = { + "rollback_result_capture_writer", + "rollback_learning_writer", + "rollback_playbook_trust_writer", + "rollback_reviewer_queue_writer", + "rollback_gateway_queue_writer", + } + ids = {review.get("rollback_id") for review in reviews} + if ids != required: + raise ValueError(f"{label}: rollback owner reviews must match {sorted(required)}") + for review in reviews: + rollback_id = review.get("rollback_id") + if review.get("rollback_owner_required") is not True: + raise ValueError(f"{label}: rollback {rollback_id} must require owner") + if review.get("rollback_confirmed") is not False: + raise ValueError(f"{label}: rollback {rollback_id} must not be confirmed") + if review.get("status") not in {"ready_for_owner_review", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: rollback {rollback_id} status is invalid") + if not review.get("rollback_note"): + raise ValueError(f"{label}: rollback {rollback_id} rollback_note is required") + + +def _require_blocked_writes(payload: dict[str, Any], label: str) -> None: + blockers = payload.get("blocked_execution_writes") or [] + required = { + "blocked_writer_apply", + "blocked_execution_apply", + "blocked_receipt_write", + "blocked_result_capture_write", + "blocked_gateway_queue_write", + "blocked_telegram_send", + } + ids = {blocker.get("blocker_id") for blocker in blockers} + if ids != required: + raise ValueError(f"{label}: blocked execution writes must match {sorted(required)}") + for blocker in blockers: + blocker_id = blocker.get("blocker_id") + if blocker.get("status") not in {"approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: blocker {blocker_id} status is invalid") + if blocker.get("severity") not in {"high", "critical"}: + raise ValueError(f"{label}: blocker {blocker_id} severity is invalid") + if not blocker.get("blocked_action") or not blocker.get("blocked_until"): + raise ValueError(f"{label}: blocker {blocker_id} must include blocked_action and blocked_until") + 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_owner_promotion_packets", + "verify_execution_gates", + "confirm_rollback_owners", + "prepare_p2_126_execution_rehearsal", + "hold_live_writer_gate", + } + ids = {action.get("action_id") for action in actions} + if 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_write_allowed") is not False: + raise ValueError(f"{label}: action {action_id} must not allow runtime write") + 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 {} + expected = { + "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, + } + mismatches = _mismatches(contract, expected) + if mismatches: + raise ValueError(f"{label}: display_redaction_contract mismatch: {mismatches}") + if not contract.get("frontend_display_policy"): + raise ValueError(f"{label}: display_redaction_contract.frontend_display_policy is required") + + +def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None: + rollups = payload.get("rollups") or {} + packets = payload.get("owner_promotion_packets") or [] + gates = payload.get("execution_gate_checks") or [] + rollbacks = payload.get("rollback_owner_reviews") or [] + blockers = payload.get("blocked_execution_writes") or [] + actions = payload.get("operator_actions") or [] + expected = { + "owner_promotion_packet_count": len(packets), + "execution_gate_check_count": len(gates), + "rollback_owner_review_count": len(rollbacks), + "blocked_execution_write_count": len(blockers), + "operator_action_count": len(actions), + "approval_required_packet_count": sum(1 for item in packets if item.get("status") == "approval_required"), + "blocked_packet_count": sum(1 for item in packets if item.get("status") == "blocked_by_policy"), + "approval_required_gate_count": sum(1 for item in gates if item.get("status") == "approval_required"), + "blocked_gate_count": sum(1 for item in gates if item.get("status") == "blocked_by_policy"), + "approval_required_rollback_count": sum(1 for item in rollbacks if item.get("status") == "approval_required"), + "blocked_rollback_count": sum(1 for item in rollbacks if item.get("status") == "blocked_by_policy"), + "critical_blocker_count": sum(1 for item in blockers if item.get("severity") == "critical"), + "owner_promotion_approved_count": 0, + "execution_gate_pass_count": 0, + "rollback_owner_confirmed_count": 0, + "post_write_verifier_ready_count": 0, + "writer_apply_count": 0, + "execution_apply_count": 0, + "receipt_write_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, + } + mismatches = _mismatches(rollups, expected) + if mismatches: + raise ValueError(f"{label}: rollup counts mismatch: {mismatches}") + + +def _require_no_forbidden_display_terms(payload: dict[str, Any], label: str) -> None: + serialized = json.dumps(payload, ensure_ascii=False) + 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 present: {hits}") + + +def _is_redacted_sha256(value: Any) -> bool: + if not isinstance(value, str) or not value.startswith("sha256:"): + return False + digest = value.removeprefix("sha256:") + return len(digest) == 64 and all(char in "0123456789abcdef" for char in digest) + + +def _mismatches(payload: dict[str, Any], expected: dict[str, Any]) -> dict[str, dict[str, Any]]: + return { + key: {"expected": value, "actual": payload.get(key)} + for key, value in expected.items() + if payload.get(key) != value + } diff --git a/apps/api/tests/test_ai_agent_result_capture_owner_promotion_review.py b/apps/api/tests/test_ai_agent_result_capture_owner_promotion_review.py new file mode 100644 index 000000000..997d548ec --- /dev/null +++ b/apps/api/tests/test_ai_agent_result_capture_owner_promotion_review.py @@ -0,0 +1,118 @@ +import copy +import json +import os +from pathlib import Path + +import pytest + +os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test") + +from src.services.ai_agent_result_capture_owner_promotion_review import ( + load_latest_ai_agent_result_capture_owner_promotion_review, +) + + +REPO_ROOT = Path(__file__).resolve().parents[3] +FIXTURE = REPO_ROOT / "docs/evaluations/ai_agent_result_capture_owner_promotion_review_2026-06-14.json" + + +def test_load_latest_ai_agent_result_capture_owner_promotion_review_snapshot() -> None: + data = load_latest_ai_agent_result_capture_owner_promotion_review() + + assert data["schema_version"] == "ai_agent_result_capture_owner_promotion_review_v1" + assert data["program_status"]["current_task_id"] == "P2-125" + assert data["program_status"]["next_task_id"] == "P2-126" + assert data["program_status"]["overall_completion_percent"] == 100 + assert data["program_status"]["read_only_mode"] is True + + rollups = data["rollups"] + assert rollups["owner_promotion_packet_count"] == 5 + assert rollups["execution_gate_check_count"] == 5 + assert rollups["rollback_owner_review_count"] == 5 + assert rollups["blocked_execution_write_count"] == 6 + assert rollups["operator_action_count"] == 5 + assert rollups["approval_required_packet_count"] == 2 + assert rollups["blocked_packet_count"] == 1 + assert rollups["approval_required_gate_count"] == 2 + assert rollups["blocked_gate_count"] == 1 + assert rollups["approval_required_rollback_count"] == 2 + assert rollups["blocked_rollback_count"] == 1 + assert rollups["critical_blocker_count"] == 5 + + assert {packet["promotion_review_only"] for packet in data["owner_promotion_packets"]} == {True} + assert {packet["execution_apply_enabled"] for packet in data["owner_promotion_packets"]} == {False} + assert {gate["execution_enabled"] for gate in data["execution_gate_checks"]} == {False} + assert {review["rollback_confirmed"] for review in data["rollback_owner_reviews"]} == {False} + + zero_fields = [ + "owner_promotion_approved_count", + "execution_gate_pass_count", + "rollback_owner_confirmed_count", + "post_write_verifier_ready_count", + "writer_apply_count", + "execution_apply_count", + "receipt_write_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_result_capture_owner_promotion_review_rejects_execution_enabled(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["execution_gate_checks"][0]["execution_enabled"] = True + target = tmp_path / "ai_agent_result_capture_owner_promotion_review_2026-06-14.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="must not enable execution"): + load_latest_ai_agent_result_capture_owner_promotion_review(tmp_path) + + +def test_result_capture_owner_promotion_review_rejects_truth_write_flag(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["owner_promotion_truth"]["result_capture_write_enabled"] = True + target = tmp_path / "ai_agent_result_capture_owner_promotion_review_2026-06-14.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="live execution/send/write flags"): + load_latest_ai_agent_result_capture_owner_promotion_review(tmp_path) + + +def test_result_capture_owner_promotion_review_rejects_packet_apply_enabled(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["owner_promotion_packets"][0]["execution_apply_enabled"] = True + target = tmp_path / "ai_agent_result_capture_owner_promotion_review_2026-06-14.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="must not enable execution apply"): + load_latest_ai_agent_result_capture_owner_promotion_review(tmp_path) + + +def test_result_capture_owner_promotion_review_rejects_rollup_drift(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["rollups"]["owner_promotion_packet_count"] = 4 + target = tmp_path / "ai_agent_result_capture_owner_promotion_review_2026-06-14.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="rollup counts mismatch"): + load_latest_ai_agent_result_capture_owner_promotion_review(tmp_path) + + +def test_result_capture_owner_promotion_review_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 session_id" + target = tmp_path / "ai_agent_result_capture_owner_promotion_review_2026-06-14.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="forbidden display terms"): + load_latest_ai_agent_result_capture_owner_promotion_review(tmp_path) diff --git a/apps/api/tests/test_ai_agent_result_capture_owner_promotion_review_api.py b/apps/api/tests/test_ai_agent_result_capture_owner_promotion_review_api.py new file mode 100644 index 000000000..97f8a2b8e --- /dev/null +++ b/apps/api/tests/test_ai_agent_result_capture_owner_promotion_review_api.py @@ -0,0 +1,47 @@ +import os + +import pytest +from httpx import ASGITransport, AsyncClient + +os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test") + +from src.main import app + + +@pytest.mark.asyncio +async def test_get_agent_result_capture_owner_promotion_review_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-result-capture-owner-promotion-review") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_result_capture_owner_promotion_review_v1" + assert data["program_status"]["current_task_id"] == "P2-125" + assert data["program_status"]["next_task_id"] == "P2-126" + assert data["program_status"]["overall_completion_percent"] == 100 + + rollups = data["rollups"] + assert rollups["owner_promotion_packet_count"] == 5 + assert rollups["execution_gate_check_count"] == 5 + assert rollups["rollback_owner_review_count"] == 5 + assert rollups["blocked_execution_write_count"] == 6 + assert rollups["operator_action_count"] == 5 + assert rollups["critical_blocker_count"] == 5 + assert rollups["owner_promotion_approved_count"] == 0 + assert rollups["execution_gate_pass_count"] == 0 + assert rollups["rollback_owner_confirmed_count"] == 0 + assert rollups["writer_apply_count"] == 0 + assert rollups["execution_apply_count"] == 0 + assert rollups["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["gateway_queue_write_count"] == 0 + assert rollups["telegram_send_count"] == 0 + assert rollups["production_write_count"] == 0 + + assert {packet["promotion_review_only"] for packet in data["owner_promotion_packets"]} == {True} + assert {packet["execution_apply_enabled"] for packet in data["owner_promotion_packets"]} == {False} + assert {gate["execution_enabled"] for gate in data["execution_gate_checks"]} == {False} + assert {review["rollback_confirmed"] for review in data["rollback_owner_reviews"]} == {False} diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index b9ea5ffb9..cd3dc7846 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5812,6 +5812,40 @@ "promotionAllowed": "允許提升={value}" } }, + "resultCaptureOwnerPromotionReview": { + "title": "P2-125 owner promotion review / execution gate", + "source": "產生 {generated};目前 {current};下一步 {next}", + "priorTitle": "前一關 dry-run readback", + "truthTitle": "owner promotion 真相", + "metrics": { + "overall": "完成度", + "packets": "Promotion packets", + "gates": "Execution gates", + "rollbackReviews": "Rollback reviews", + "blockers": "已阻擋執行", + "actions": "操作選項", + "approvalRequired": "需批准", + "blocked": "阻擋", + "executionPass": "Execution 通過", + "liveWrites": "正式寫入 / 發送" + }, + "flags": { + "readbackLoaded": "已載入 readback={value}", + "dualApproval": "需要雙重批准={value}", + "postWriteVerifier": "需要 post-write verifier={value}" + }, + "labels": { + "writerApply": "writer 套用={value}", + "executionApply": "execution 套用={value}", + "receiptWrite": "receipt 寫入={value}", + "gatewayWrites": "Gateway 寫入={value}", + "requiredOwner": "owner:{value}", + "promotionReviewOnly": "promotion review-only={value}", + "executionEnabled": "正式執行={value}", + "rollbackRequired": "需要 rollback owner={value}", + "rollbackConfirmed": "rollback 已確認={value}" + } + }, "resultCaptureWriteGateReview": { "title": "P2-121 result capture write gate review", "source": "產生 {generated};目前 {current};下一步 {next}", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index b9ea5ffb9..cd3dc7846 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5812,6 +5812,40 @@ "promotionAllowed": "允許提升={value}" } }, + "resultCaptureOwnerPromotionReview": { + "title": "P2-125 owner promotion review / execution gate", + "source": "產生 {generated};目前 {current};下一步 {next}", + "priorTitle": "前一關 dry-run readback", + "truthTitle": "owner promotion 真相", + "metrics": { + "overall": "完成度", + "packets": "Promotion packets", + "gates": "Execution gates", + "rollbackReviews": "Rollback reviews", + "blockers": "已阻擋執行", + "actions": "操作選項", + "approvalRequired": "需批准", + "blocked": "阻擋", + "executionPass": "Execution 通過", + "liveWrites": "正式寫入 / 發送" + }, + "flags": { + "readbackLoaded": "已載入 readback={value}", + "dualApproval": "需要雙重批准={value}", + "postWriteVerifier": "需要 post-write verifier={value}" + }, + "labels": { + "writerApply": "writer 套用={value}", + "executionApply": "execution 套用={value}", + "receiptWrite": "receipt 寫入={value}", + "gatewayWrites": "Gateway 寫入={value}", + "requiredOwner": "owner:{value}", + "promotionReviewOnly": "promotion review-only={value}", + "executionEnabled": "正式執行={value}", + "rollbackRequired": "需要 rollback owner={value}", + "rollbackConfirmed": "rollback 已確認={value}" + } + }, "resultCaptureWriteGateReview": { "title": "P2-121 result capture write gate review", "source": "產生 {generated};目前 {current};下一步 {next}", 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 d4110e845..e84b9d69f 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 @@ -72,6 +72,7 @@ import { type AiAgentResultCaptureWriteGateReviewSnapshot, type AiAgentResultCaptureWriterDryRunFixtureSnapshot, type AiAgentResultCaptureWriterDryRunReadbackSnapshot, + type AiAgentResultCaptureOwnerPromotionReviewSnapshot, type AiAgentResultCaptureWriterImplementationReviewSnapshot, type AiAgentOwnerApprovedFixturePromotionGateSnapshot, type AiAgentRuntimeWorkerShadowGateSnapshot, @@ -467,6 +468,7 @@ export function AutomationInventoryTab() { const [resultCaptureWriterImplementationReview, setResultCaptureWriterImplementationReview] = useState(null) const [resultCaptureWriterDryRunFixture, setResultCaptureWriterDryRunFixture] = useState(null) const [resultCaptureWriterDryRunReadback, setResultCaptureWriterDryRunReadback] = useState(null) + const [resultCaptureOwnerPromotionReview, setResultCaptureOwnerPromotionReview] = useState(null) const [reportTruthActionabilityReview, setReportTruthActionabilityReview] = useState(null) const [ownerDryRunPackage, setOwnerDryRunPackage] = useState(null) const [hostStatefulInventory, setHostStatefulInventory] = useState(null) @@ -528,6 +530,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentResultCaptureWriterImplementationReview(), apiClient.getAiAgentResultCaptureWriterDryRunFixture(), apiClient.getAiAgentResultCaptureWriterDryRunReadback(), + apiClient.getAiAgentResultCaptureOwnerPromotionReview(), apiClient.getAiAgentReportTruthActionabilityReview(), apiClient.getAiAgentOwnerApprovedFixtureDryRun(), apiClient.getAiAgentHostStatefulVersionInventory(), @@ -588,6 +591,7 @@ export function AutomationInventoryTab() { resultCaptureWriterImplementationReviewResult, resultCaptureWriterDryRunFixtureResult, resultCaptureWriterDryRunReadbackResult, + resultCaptureOwnerPromotionReviewResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -645,6 +649,7 @@ export function AutomationInventoryTab() { setResultCaptureWriterImplementationReview(resultCaptureWriterImplementationReviewResult.status === 'fulfilled' ? resultCaptureWriterImplementationReviewResult.value : null) setResultCaptureWriterDryRunFixture(resultCaptureWriterDryRunFixtureResult.status === 'fulfilled' ? resultCaptureWriterDryRunFixtureResult.value : null) setResultCaptureWriterDryRunReadback(resultCaptureWriterDryRunReadbackResult.status === 'fulfilled' ? resultCaptureWriterDryRunReadbackResult.value : null) + setResultCaptureOwnerPromotionReview(resultCaptureOwnerPromotionReviewResult.status === 'fulfilled' ? resultCaptureOwnerPromotionReviewResult.value : null) setReportTruthActionabilityReview(reportTruthActionabilityReviewResult.status === 'fulfilled' ? reportTruthActionabilityReviewResult.value : null) setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null) setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null) @@ -700,6 +705,7 @@ export function AutomationInventoryTab() { resultCaptureWriterImplementationReviewResult, resultCaptureWriterDryRunFixtureResult, resultCaptureWriterDryRunReadbackResult, + resultCaptureOwnerPromotionReviewResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -1942,7 +1948,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 || !runtimeReadbackPromotionGate || !ownerApprovedFixturePromotionGate || !canonicalRuntimeReadbackOwnerAcceptance || !failureReceiptNoSendReplay || !reviewerQueueNoWriteReadback || !resultCaptureNoWriteReadback || !resultCapturePromotionApprovalGate || !ownerApprovedResultCapturePromotionDryRun || !resultCaptureWriteGateReview || !resultCaptureWriterImplementationReview || !resultCaptureWriterDryRunFixture || !resultCaptureWriterDryRunReadback || !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 || !ownerApprovedFixturePromotionGate || !canonicalRuntimeReadbackOwnerAcceptance || !failureReceiptNoSendReplay || !reviewerQueueNoWriteReadback || !resultCaptureNoWriteReadback || !resultCapturePromotionApprovalGate || !ownerApprovedResultCapturePromotionDryRun || !resultCaptureWriteGateReview || !resultCaptureWriterImplementationReview || !resultCaptureWriterDryRunFixture || !resultCaptureWriterDryRunReadback || !resultCaptureOwnerPromotionReview || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -2785,6 +2791,37 @@ export function AutomationInventoryTab() { + resultCaptureWriterDryRunReadback.rollups.playbook_trust_write_count + resultCaptureWriterDryRunReadback.rollups.production_write_count ) + const resultCaptureOwnerPromotionOverall = resultCaptureOwnerPromotionReview.program_status.overall_completion_percent + const resultCaptureOwnerPromotionPackets = resultCaptureOwnerPromotionReview.rollups.owner_promotion_packet_count + const resultCaptureOwnerPromotionGates = resultCaptureOwnerPromotionReview.rollups.execution_gate_check_count + const resultCaptureOwnerPromotionRollbacks = resultCaptureOwnerPromotionReview.rollups.rollback_owner_review_count + const resultCaptureOwnerPromotionBlockers = resultCaptureOwnerPromotionReview.rollups.blocked_execution_write_count + const resultCaptureOwnerPromotionActions = resultCaptureOwnerPromotionReview.rollups.operator_action_count + const resultCaptureOwnerPromotionApprovalRequired = ( + resultCaptureOwnerPromotionReview.rollups.approval_required_packet_count + + resultCaptureOwnerPromotionReview.rollups.approval_required_gate_count + + resultCaptureOwnerPromotionReview.rollups.approval_required_rollback_count + ) + const resultCaptureOwnerPromotionBlocked = ( + resultCaptureOwnerPromotionReview.rollups.blocked_packet_count + + resultCaptureOwnerPromotionReview.rollups.blocked_gate_count + + resultCaptureOwnerPromotionReview.rollups.blocked_rollback_count + + resultCaptureOwnerPromotionReview.rollups.critical_blocker_count + ) + const resultCaptureOwnerPromotionLiveWrites = ( + resultCaptureOwnerPromotionReview.rollups.writer_apply_count + + resultCaptureOwnerPromotionReview.rollups.execution_apply_count + + resultCaptureOwnerPromotionReview.rollups.receipt_write_count + + resultCaptureOwnerPromotionReview.rollups.reviewer_queue_write_count + + resultCaptureOwnerPromotionReview.rollups.gateway_queue_write_count + + resultCaptureOwnerPromotionReview.rollups.telegram_send_count + + resultCaptureOwnerPromotionReview.rollups.bot_api_call_count + + resultCaptureOwnerPromotionReview.rollups.report_receipt_write_count + + resultCaptureOwnerPromotionReview.rollups.result_capture_write_count + + resultCaptureOwnerPromotionReview.rollups.learning_write_count + + resultCaptureOwnerPromotionReview.rollups.playbook_trust_write_count + + resultCaptureOwnerPromotionReview.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 @@ -7334,6 +7371,132 @@ export function AutomationInventoryTab() {
+
+
+
+ + + {t('resultCaptureOwnerPromotionReview.title')} + +
+ +
+ +
+ } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
+ +
+
+ {t('resultCaptureOwnerPromotionReview.priorTitle')} + + {resultCaptureOwnerPromotionReview.prior_result_capture_writer_dry_run_readback.readiness_note} + +
+ + + +
+
+ +
+ {t('resultCaptureOwnerPromotionReview.truthTitle')} + + {resultCaptureOwnerPromotionReview.owner_promotion_truth.truth_note} + +
+ + + + + + + +
+
+
+ +
+ {resultCaptureOwnerPromotionReview.owner_promotion_packets.slice(0, 5).map(packet => ( +
+
+ + {packet.display_name} + + +
+ + {packet.source_readback} → {packet.operator_note} + +
+ + + + +
+
+ ))} +
+ +
+ {resultCaptureOwnerPromotionReview.execution_gate_checks.slice(0, 5).map(gate => ( +
+
+ + {gate.display_name} + + +
+ + {gate.failure_if_missing} + +
+ + + +
+
+ ))} +
+ +
+ {resultCaptureOwnerPromotionReview.rollback_owner_reviews.slice(0, 5).map(review => ( +
+
+ + {review.display_name} + + +
+ + {review.rollback_note} + +
+ + + +
+
+ ))} +
+
+
diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 946ae5bf7..702a05d8f 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -520,6 +520,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiAgentResultCaptureOwnerPromotionReview() { + const res = await fetch(`${API_BASE_URL}/agents/agent-result-capture-owner-promotion-review`) + return handleResponse(res) + }, + async getAiAgentOwnerApprovedFixtureDryRun() { const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-dry-run`) return handleResponse(res) @@ -6375,6 +6380,175 @@ export interface AiAgentResultCaptureWriterDryRunReadbackSnapshot { } } +export interface AiAgentResultCaptureOwnerPromotionReviewSnapshot { + schema_version: 'ai_agent_result_capture_owner_promotion_review_v1' + generated_at: string + program_status: { + overall_completion_percent: number + current_priority: 'P0' | 'P1' | 'P2' | 'P3' + current_task_id: 'P2-125' + next_task_id: 'P2-126' + read_only_mode: true + runtime_authority: 'result_capture_owner_promotion_review_only_no_live_write' + status_note: string + } + source_refs: string[] + prior_result_capture_writer_dry_run_readback: { + schema_version: string + dry_run_readback_card_count: number + receipt_verifier_check_count: number + promotion_readiness_gate_count: number + blocked_promotion_write_count: number + operator_action_count: number + writer_apply_count: number + dry_run_execution_count: number + receipt_write_count: number + promotion_apply_count: number + result_capture_write_count: number + learning_write_count: number + playbook_trust_write_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 + readiness_note: string + } + owner_promotion_truth: { + p2_124_readback_loaded: boolean + owner_promotion_review_ready: boolean + execution_gate_ready: boolean + rollback_owner_review_ready: boolean + dual_approval_required: boolean + post_write_verifier_required: boolean + idempotency_key_required: boolean + redaction_review_required: boolean + owner_promotion_approved: boolean + execution_apply_enabled: boolean + writer_apply_enabled: boolean + receipt_write_enabled: boolean + reviewer_queue_write_enabled: boolean + gateway_queue_write_enabled: boolean + telegram_send_enabled: boolean + bot_api_call_enabled: boolean + report_receipt_write_enabled: boolean + result_capture_write_enabled: boolean + learning_write_enabled: boolean + playbook_trust_write_enabled: boolean + production_write_enabled: boolean + secret_read_enabled: boolean + destructive_operation_enabled: boolean + owner_promotion_approved_count: number + execution_gate_pass_count: number + rollback_owner_confirmed_count: number + post_write_verifier_ready_count: number + writer_apply_count: number + execution_apply_count: number + receipt_write_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 + truth_note: string + } + owner_promotion_packets: Array<{ + packet_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + source_readback: string + required_owner: string + status: 'ready_for_owner_review' | 'approval_required' | 'blocked_by_policy' + promotion_review_only: true + execution_apply_enabled: false + required_decision_fields: string[] + operator_note: string + evidence_hash: string + }> + execution_gate_checks: Array<{ + gate_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + status: 'ready_for_owner_review' | 'approval_required' | 'blocked_by_policy' + gate_mode: 'no_write_execution_gate' + required_before_execution: true + execution_enabled: false + failure_if_missing: string + }> + rollback_owner_reviews: Array<{ + rollback_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + status: 'ready_for_owner_review' | 'approval_required' | 'blocked_by_policy' + rollback_owner_required: true + rollback_confirmed: false + rollback_note: string + }> + blocked_execution_writes: Array<{ + blocker_id: string + display_name: string + severity: 'high' | 'critical' + status: 'approval_required' | 'blocked_by_policy' + blocked_action: string + blocked_until: string + evidence_hash: string + }> + operator_actions: Array<{ + action_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + operator_instruction: string + runtime_write_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: { + owner_promotion_packet_count: number + execution_gate_check_count: number + rollback_owner_review_count: number + blocked_execution_write_count: number + operator_action_count: number + approval_required_packet_count: number + blocked_packet_count: number + approval_required_gate_count: number + blocked_gate_count: number + approval_required_rollback_count: number + blocked_rollback_count: number + critical_blocker_count: number + owner_promotion_approved_count: number + execution_gate_pass_count: number + rollback_owner_confirmed_count: number + post_write_verifier_ready_count: number + writer_apply_count: number + execution_apply_count: number + receipt_write_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_result_capture_owner_promotion_review_2026-06-14.json b/docs/evaluations/ai_agent_result_capture_owner_promotion_review_2026-06-14.json new file mode 100644 index 000000000..d7a531e0b --- /dev/null +++ b/docs/evaluations/ai_agent_result_capture_owner_promotion_review_2026-06-14.json @@ -0,0 +1,414 @@ +{ + "schema_version": "ai_agent_result_capture_owner_promotion_review_v1", + "generated_at": "2026-06-14T01:20:00+08:00", + "program_status": { + "overall_completion_percent": 100, + "current_priority": "P2", + "current_task_id": "P2-125", + "next_task_id": "P2-126", + "read_only_mode": true, + "runtime_authority": "result_capture_owner_promotion_review_only_no_live_write", + "status_note": "P2-125 只建立 owner promotion review、execution gate 與 rollback owner review;不得套用 writer、執行 live apply、寫 receipt 或寫入 production。" + }, + "source_refs": [ + "docs/evaluations/ai_agent_result_capture_writer_dry_run_readback_2026-06-14.json", + "docs/evaluations/ai_agent_result_capture_writer_dry_run_fixture_2026-06-14.json", + "docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md#45-建立-result-capture-writer-dry-run-readback" + ], + "prior_result_capture_writer_dry_run_readback": { + "schema_version": "ai_agent_result_capture_writer_dry_run_readback_v1", + "dry_run_readback_card_count": 5, + "receipt_verifier_check_count": 5, + "promotion_readiness_gate_count": 5, + "blocked_promotion_write_count": 6, + "operator_action_count": 5, + "writer_apply_count": 0, + "dry_run_execution_count": 0, + "receipt_write_count": 0, + "promotion_apply_count": 0, + "result_capture_write_count": 0, + "learning_write_count": 0, + "playbook_trust_write_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, + "readiness_note": "P2-124 已提供 dry-run readback、receipt verifier 與 promotion readiness;P2-125 只讀回這些輸出並建立 owner promotion review / execution gate。" + }, + "owner_promotion_truth": { + "p2_124_readback_loaded": true, + "owner_promotion_review_ready": true, + "execution_gate_ready": true, + "rollback_owner_review_ready": true, + "dual_approval_required": true, + "post_write_verifier_required": true, + "idempotency_key_required": true, + "redaction_review_required": true, + "owner_promotion_approved": false, + "execution_apply_enabled": false, + "writer_apply_enabled": false, + "receipt_write_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_promotion_approved_count": 0, + "execution_gate_pass_count": 0, + "rollback_owner_confirmed_count": 0, + "post_write_verifier_ready_count": 0, + "writer_apply_count": 0, + "execution_apply_count": 0, + "receipt_write_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, + "truth_note": "P2-125 把 P2-124 readback 讀回成 owner promotion review 與 execution gate。所有 live writer、queue、Telegram、Bot API、receipt 與 production write 仍為 0。" + }, + "owner_promotion_packets": [ + { + "packet_id": "packet_result_capture_writer", + "display_name": "Result capture writer promotion packet", + "owner_agent": "openclaw", + "source_readback": "readback_result_capture_writer", + "required_owner": "incident_owner + SRE reviewer", + "status": "ready_for_owner_review", + "promotion_review_only": true, + "execution_apply_enabled": false, + "required_decision_fields": [ + "owner role", + "decision", + "decision reason", + "affected scope", + "rollback owner", + "post-write verifier" + ], + "operator_note": "先確認 result capture writer promotion packet、雙重批准與 dry-run hash,再進 P2-126 execution rehearsal。", + "evidence_hash": "sha256:d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1" + }, + { + "packet_id": "packet_learning_writer", + "display_name": "Learning writer promotion packet", + "owner_agent": "hermes", + "source_readback": "readback_learning_writer", + "required_owner": "knowledge owner + product owner", + "status": "approval_required", + "promotion_review_only": true, + "execution_apply_enabled": false, + "required_decision_fields": [ + "KM impact", + "source refs", + "stale guard", + "rollback owner", + "review window" + ], + "operator_note": "高影響 KM 寫入必須 owner review 與 stale guard 同時成立。", + "evidence_hash": "sha256:d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2" + }, + { + "packet_id": "packet_playbook_trust_writer", + "display_name": "PlayBook trust promotion packet", + "owner_agent": "nemotron", + "source_readback": "readback_playbook_trust_writer", + "required_owner": "SRE reviewer + policy owner", + "status": "blocked_by_policy", + "promotion_review_only": true, + "execution_apply_enabled": false, + "required_decision_fields": [ + "verifier success", + "negative reinforcement", + "trust delta", + "rollback owner", + "policy note" + ], + "operator_note": "信任分數提升前必須完成 verifier success 與 negative reinforcement review。", + "evidence_hash": "sha256:d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3" + }, + { + "packet_id": "packet_reviewer_queue_writer", + "display_name": "Reviewer queue promotion packet", + "owner_agent": "openclaw", + "source_readback": "readback_reviewer_queue_writer", + "required_owner": "reviewer lane owner", + "status": "approval_required", + "promotion_review_only": true, + "execution_apply_enabled": false, + "required_decision_fields": [ + "owner route", + "dedupe key", + "SLA", + "manual handoff reason", + "rollback owner" + ], + "operator_note": "reviewer queue 寫入前需確認 owner route、dedupe key、SLA 與回滾負責人。", + "evidence_hash": "sha256:d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4" + }, + { + "packet_id": "packet_gateway_queue_writer", + "display_name": "Gateway queue promotion packet", + "owner_agent": "nemotron", + "source_readback": "readback_gateway_queue_writer", + "required_owner": "AwoooI SRE 戰情室 route owner", + "status": "ready_for_owner_review", + "promotion_review_only": true, + "execution_apply_enabled": false, + "required_decision_fields": [ + "route owner", + "route lock", + "redaction policy", + "retry policy", + "dead-letter owner" + ], + "operator_note": "只能顯示 AwoooI SRE 戰情室邏輯路由,不顯示 chat id、token 或 Bot API payload。", + "evidence_hash": "sha256:d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5d5" + } + ], + "execution_gate_checks": [ + { + "gate_id": "gate_owner_promotion_approval", + "display_name": "Owner promotion approval gate", + "owner_agent": "openclaw", + "status": "approval_required", + "gate_mode": "no_write_execution_gate", + "required_before_execution": true, + "execution_enabled": false, + "failure_if_missing": "缺 owner promotion approval 時不得進 writer execution。" + }, + { + "gate_id": "gate_dual_approval", + "display_name": "Dual approval gate", + "owner_agent": "hermes", + "status": "approval_required", + "gate_mode": "no_write_execution_gate", + "required_before_execution": true, + "execution_enabled": false, + "failure_if_missing": "缺雙重批准時不得寫入 result capture 或 Gateway queue。" + }, + { + "gate_id": "gate_dry_run_hash_lock", + "display_name": "Dry-run hash lock gate", + "owner_agent": "nemotron", + "status": "ready_for_owner_review", + "gate_mode": "no_write_execution_gate", + "required_before_execution": true, + "execution_enabled": false, + "failure_if_missing": "缺 dry-run hash lock 時不得 promotion apply。" + }, + { + "gate_id": "gate_post_write_verifier_plan", + "display_name": "Post-write verifier plan gate", + "owner_agent": "openclaw", + "status": "ready_for_owner_review", + "gate_mode": "no_write_execution_gate", + "required_before_execution": true, + "execution_enabled": false, + "failure_if_missing": "缺 post-write verifier plan 時不得執行 writer。" + }, + { + "gate_id": "gate_production_write_block", + "display_name": "Production write block gate", + "owner_agent": "nemotron", + "status": "blocked_by_policy", + "gate_mode": "no_write_execution_gate", + "required_before_execution": true, + "execution_enabled": false, + "failure_if_missing": "未有正式維護窗口與 rollback owner 前不得寫 production。" + } + ], + "rollback_owner_reviews": [ + { + "rollback_id": "rollback_result_capture_writer", + "display_name": "Result capture rollback owner review", + "owner_agent": "openclaw", + "status": "ready_for_owner_review", + "rollback_owner_required": true, + "rollback_confirmed": false, + "rollback_note": "需要 incident owner 與 SRE reviewer 確認 result capture rollback route。" + }, + { + "rollback_id": "rollback_learning_writer", + "display_name": "Learning rollback owner review", + "owner_agent": "hermes", + "status": "approval_required", + "rollback_owner_required": true, + "rollback_confirmed": false, + "rollback_note": "需要 KM owner 確認錯誤學習撤回與 stale guard。" + }, + { + "rollback_id": "rollback_playbook_trust_writer", + "display_name": "PlayBook trust rollback owner review", + "owner_agent": "nemotron", + "status": "blocked_by_policy", + "rollback_owner_required": true, + "rollback_confirmed": false, + "rollback_note": "需要 policy owner 確認 trust delta rollback 與 negative reinforcement。" + }, + { + "rollback_id": "rollback_reviewer_queue_writer", + "display_name": "Reviewer queue rollback owner review", + "owner_agent": "openclaw", + "status": "approval_required", + "rollback_owner_required": true, + "rollback_confirmed": false, + "rollback_note": "需要 reviewer lane owner 確認 queue item 撤回方式。" + }, + { + "rollback_id": "rollback_gateway_queue_writer", + "display_name": "Gateway queue rollback owner review", + "owner_agent": "nemotron", + "status": "ready_for_owner_review", + "rollback_owner_required": true, + "rollback_confirmed": false, + "rollback_note": "需要 AwoooI SRE 戰情室 route owner 確認 retry / dead-letter rollback。" + } + ], + "blocked_execution_writes": [ + { + "blocker_id": "blocked_writer_apply", + "display_name": "Writer apply blocked", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "writer_apply", + "blocked_until": "P2-126 execution rehearsal and explicit owner approval", + "evidence_hash": "sha256:e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1" + }, + { + "blocker_id": "blocked_execution_apply", + "display_name": "Execution apply blocked", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "execution_apply", + "blocked_until": "dual approval + dry-run hash + rollback owner", + "evidence_hash": "sha256:e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2" + }, + { + "blocker_id": "blocked_receipt_write", + "display_name": "Receipt write blocked", + "severity": "high", + "status": "approval_required", + "blocked_action": "receipt_write", + "blocked_until": "post-write verifier plan accepted", + "evidence_hash": "sha256:e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3" + }, + { + "blocker_id": "blocked_result_capture_write", + "display_name": "Result capture write blocked", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "result_capture_write", + "blocked_until": "owner promotion accepted and execution gate passed", + "evidence_hash": "sha256:e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4" + }, + { + "blocker_id": "blocked_gateway_queue_write", + "display_name": "Gateway queue write blocked", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "gateway_queue_write", + "blocked_until": "AwoooI SRE 戰情室 route lock accepted", + "evidence_hash": "sha256:e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5" + }, + { + "blocker_id": "blocked_telegram_send", + "display_name": "Telegram send blocked", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "telegram_send", + "blocked_until": "Gateway queue receipt + no-secret payload verifier accepted", + "evidence_hash": "sha256:e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6" + } + ], + "operator_actions": [ + { + "action_id": "review_owner_promotion_packets", + "display_name": "Review owner promotion packets", + "owner_agent": "openclaw", + "operator_instruction": "審查 5 個 owner promotion packet,確認 owner、decision、reason、scope、rollback owner 與 verifier plan。", + "runtime_write_allowed": false + }, + { + "action_id": "verify_execution_gates", + "display_name": "Verify execution gates", + "owner_agent": "nemotron", + "operator_instruction": "確認 execution gate 仍是 no-write,dry-run hash、dual approval 與 post-write verifier 都未缺漏。", + "runtime_write_allowed": false + }, + { + "action_id": "confirm_rollback_owners", + "display_name": "Confirm rollback owners", + "owner_agent": "hermes", + "operator_instruction": "補齊 rollback owner review 的人工責任與撤回方式,但不得寫入 production。", + "runtime_write_allowed": false + }, + { + "action_id": "prepare_p2_126_execution_rehearsal", + "display_name": "Prepare P2-126 execution rehearsal", + "owner_agent": "openclaw", + "operator_instruction": "只準備下一階段 execution rehearsal 契約,不觸發 writer apply 或 Gateway queue。", + "runtime_write_allowed": false + }, + { + "action_id": "hold_live_writer_gate", + "display_name": "Hold live writer gate", + "owner_agent": "nemotron", + "operator_instruction": "保持 writer、receipt、Gateway、Telegram、learning、PlayBook trust 與 production write gate 全部關閉。", + "runtime_write_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": "只顯示 owner promotion packet、execution gate、rollback owner review 與 blocked action 摘要;不顯示提示詞原文、推理原文、機敏值、chat id、token、原始 payload 或內部協作內容。" + }, + "rollups": { + "owner_promotion_packet_count": 5, + "execution_gate_check_count": 5, + "rollback_owner_review_count": 5, + "blocked_execution_write_count": 6, + "operator_action_count": 5, + "approval_required_packet_count": 2, + "blocked_packet_count": 1, + "approval_required_gate_count": 2, + "blocked_gate_count": 1, + "approval_required_rollback_count": 2, + "blocked_rollback_count": 1, + "critical_blocker_count": 5, + "owner_promotion_approved_count": 0, + "execution_gate_pass_count": 0, + "rollback_owner_confirmed_count": 0, + "post_write_verifier_ready_count": 0, + "writer_apply_count": 0, + "execution_apply_count": 0, + "receipt_write_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_result_capture_owner_promotion_review_v1.schema.json b/docs/schemas/ai_agent_result_capture_owner_promotion_review_v1.schema.json new file mode 100644 index 000000000..177884139 --- /dev/null +++ b/docs/schemas/ai_agent_result_capture_owner_promotion_review_v1.schema.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.wooo.work/schemas/ai_agent_result_capture_owner_promotion_review_v1.schema.json", + "title": "AI Agent result capture owner promotion review v1", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "prior_result_capture_writer_dry_run_readback", + "owner_promotion_truth", + "owner_promotion_packets", + "execution_gate_checks", + "rollback_owner_reviews", + "blocked_execution_writes", + "operator_actions", + "display_redaction_contract", + "rollups" + ], + "properties": { + "schema_version": { + "const": "ai_agent_result_capture_owner_promotion_review_v1" + }, + "generated_at": { + "type": "string" + }, + "program_status": { + "type": "object" + }, + "source_refs": { + "type": "array", + "items": { + "type": "string" + } + }, + "prior_result_capture_writer_dry_run_readback": { + "type": "object" + }, + "owner_promotion_truth": { + "type": "object" + }, + "owner_promotion_packets": { + "type": "array", + "minItems": 5, + "maxItems": 5, + "items": { + "type": "object" + } + }, + "execution_gate_checks": { + "type": "array", + "minItems": 5, + "maxItems": 5, + "items": { + "type": "object" + } + }, + "rollback_owner_reviews": { + "type": "array", + "minItems": 5, + "maxItems": 5, + "items": { + "type": "object" + } + }, + "blocked_execution_writes": { + "type": "array", + "minItems": 6, + "maxItems": 6, + "items": { + "type": "object" + } + }, + "operator_actions": { + "type": "array", + "minItems": 5, + "maxItems": 5, + "items": { + "type": "object" + } + }, + "display_redaction_contract": { + "type": "object" + }, + "rollups": { + "type": "object" + } + }, + "additionalProperties": false +}