From 8fcf767aad8d3e6d8476fd30d796a90f2ab8a462 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Jun 2026 18:36:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(governance):=20=E6=96=B0=E5=A2=9E=20owner-?= =?UTF-8?q?approved=20fixture=20promotion=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/api/v1/agents.py | 33 ++ ...t_owner_approved_fixture_promotion_gate.py | 399 +++++++++++++++ ...t_owner_approved_fixture_promotion_gate.py | 96 ++++ ...ner_approved_fixture_promotion_gate_api.py | 41 ++ apps/web/messages/en.json | 101 ++++ apps/web/messages/zh-TW.json | 101 ++++ .../tabs/automation-inventory-tab.tsx | 264 +++++++++- apps/web/src/lib/api-client.ts | 176 +++++++ ...ved_fixture_promotion_gate_2026-06-13.json | 458 ++++++++++++++++++ ...oved_fixture_promotion_gate_v1.schema.json | 392 +++++++++++++++ 10 files changed, 2060 insertions(+), 1 deletion(-) create mode 100644 apps/api/src/services/ai_agent_owner_approved_fixture_promotion_gate.py create mode 100644 apps/api/tests/test_ai_agent_owner_approved_fixture_promotion_gate.py create mode 100644 apps/api/tests/test_ai_agent_owner_approved_fixture_promotion_gate_api.py create mode 100644 docs/evaluations/ai_agent_owner_approved_fixture_promotion_gate_2026-06-13.json create mode 100644 docs/schemas/ai_agent_owner_approved_fixture_promotion_gate_v1.schema.json diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 187b480a..11a52066 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -82,6 +82,9 @@ from src.services.ai_agent_matched_playbook_learning_gap import ( from src.services.ai_agent_owner_approved_fixture_dry_run import ( load_latest_ai_agent_owner_approved_fixture_dry_run, ) +from src.services.ai_agent_owner_approved_fixture_promotion_gate import ( + load_latest_ai_agent_owner_approved_fixture_promotion_gate, +) from src.services.ai_agent_owner_approved_learning_dry_run import ( load_latest_ai_agent_owner_approved_learning_dry_run, ) @@ -1458,6 +1461,36 @@ async def get_agent_runtime_readback_promotion_gate() -> dict[str, Any]: ) from exc +@router.get( + "/agent-owner-approved-fixture-promotion-gate", + response_model=dict[str, Any], + summary="取得 AI Agent owner-approved fixture promotion gate", + description=( + "讀取最新已提交的 P2-114 owner-approved fixture promotion gate;" + "此端點只回傳 owner approval packet、acceptance template、fixture review、" + "no-write verifier 與 blocked promotion,不讀 canonical runtime target、不做 live query、" + "不寫 reviewer queue、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、" + "不寫 report receipt、不寫 result capture、不寫 learning / PlayBook trust、不讀 secret。" + ), +) +async def get_agent_owner_approved_fixture_promotion_gate() -> dict[str, Any]: + """Return the latest read-only owner-approved fixture promotion gate.""" + try: + payload = await asyncio.to_thread(load_latest_ai_agent_owner_approved_fixture_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_owner_approved_fixture_promotion_gate_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent owner-approved fixture 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_owner_approved_fixture_promotion_gate.py b/apps/api/src/services/ai_agent_owner_approved_fixture_promotion_gate.py new file mode 100644 index 00000000..d882c9d3 --- /dev/null +++ b/apps/api/src/services/ai_agent_owner_approved_fixture_promotion_gate.py @@ -0,0 +1,399 @@ +""" +AI Agent owner-approved fixture promotion gate snapshot. + +Loads the latest committed P2-114 owner approval package. 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_owner_approved_fixture_promotion_gate_*.json" +_SCHEMA_VERSION = "ai_agent_owner_approved_fixture_promotion_gate_v1" +_RUNTIME_AUTHORITY = "owner_approved_fixture_promotion_gate_only_no_live_read_or_write" + + +def load_latest_ai_agent_owner_approved_fixture_promotion_gate( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed owner-approved fixture promotion gate.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError(f"no AI Agent owner-approved fixture 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_packets(payload, label) + _require_acceptance_templates(payload, label) + _require_fixture_reviews(payload, label) + _require_verifier_plans(payload, label) + _require_blocked_promotions(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-114", + "next_task_id": "P2-115", + "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_promotion_gate") or {} + expected = { + "schema_version": "ai_agent_runtime_readback_promotion_gate_v1", + "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, + "owner_approval_received_count": 0, + "promotion_execution_count": 0, + "canonical_runtime_target_read_count": 0, + "live_query_count": 0, + "production_write_count": 0, + } + mismatches = _mismatches(prior, expected) + if mismatches: + raise ValueError(f"{label}: prior_promotion_gate mismatch: {mismatches}") + if not prior.get("readiness_note"): + raise ValueError(f"{label}: prior_promotion_gate.readiness_note is required") + + +def _require_truth(payload: dict[str, Any], label: str) -> None: + truth = payload.get("owner_gate_truth") or {} + required_true = { + "p2_113_promotion_gate_loaded", + "owner_promotion_package_ready", + "acceptance_record_template_ready", + "reviewer_queue_fixture_ready", + "result_capture_fixture_ready", + "rollback_owner_required", + "verifier_plan_required", + } + missing = sorted(field for field in required_true if truth.get(field) is not True) + if missing: + raise ValueError(f"{label}: owner gate ready flags must remain true: {missing}") + if truth.get("owner_approval_received") is not False: + raise ValueError(f"{label}: owner approval must remain false before acceptance") + + 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", + "owner_acceptance_record_write_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}: owner promotion live counters must remain zero: {non_zero}") + if not truth.get("truth_note"): + raise ValueError(f"{label}: owner_gate_truth.truth_note is required") + + +def _require_packets(payload: dict[str, Any], label: str) -> None: + packets = payload.get("owner_approval_packets") or [] + required = { + "failure_receipt_owner_packet", + "reviewer_queue_owner_packet", + "result_capture_owner_packet", + "report_receipt_owner_packet", + "p2_115_scope_owner_packet", + } + packet_ids = {packet.get("packet_id") for packet in packets} + if packet_ids != required: + raise ValueError(f"{label}: owner approval packets must match {sorted(required)}") + for packet in packets: + packet_id = packet.get("packet_id") + if packet.get("owner_acceptance_required") is not True: + raise ValueError(f"{label}: packet {packet_id} must require owner acceptance") + 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 packet.get("risk_tier") not in {"high", "critical"}: + raise ValueError(f"{label}: packet {packet_id} risk_tier is invalid") + if not packet.get("required_owner_fields") or not packet.get("blocked_runtime_actions"): + raise ValueError(f"{label}: packet {packet_id} must list owner fields and blocked actions") + if not _is_redacted_sha256(packet.get("evidence_hash")): + raise ValueError(f"{label}: packet {packet_id} must expose redacted evidence_hash") + + +def _require_acceptance_templates(payload: dict[str, Any], label: str) -> None: + templates = payload.get("acceptance_record_templates") or [] + if len(templates) != 4: + raise ValueError(f"{label}: acceptance_record_templates must contain 4 items") + for template in templates: + template_id = template.get("template_id") + if template.get("accepted") is not False or template.get("record_write_enabled") is not False: + raise ValueError(f"{label}: template {template_id} must not be accepted or write-enabled") + if not template.get("required_fields"): + raise ValueError(f"{label}: template {template_id} required_fields is required") + if not _is_redacted_sha256(template.get("evidence_hash")): + raise ValueError(f"{label}: template {template_id} must expose redacted evidence_hash") + + +def _require_fixture_reviews(payload: dict[str, Any], label: str) -> None: + reviews = payload.get("fixture_promotion_reviews") or [] + if len(reviews) != 4: + raise ValueError(f"{label}: fixture_promotion_reviews must contain 4 items") + for review in reviews: + review_id = review.get("review_id") + if review.get("runtime_write_enabled") is not False: + raise ValueError(f"{label}: review {review_id} must not enable runtime write") + if not review.get("source_packet_id") or not review.get("review_outcome"): + raise ValueError(f"{label}: review {review_id} source/outcome is required") + if not _is_redacted_sha256(review.get("evidence_hash")): + raise ValueError(f"{label}: review {review_id} must expose redacted evidence_hash") + + +def _require_verifier_plans(payload: dict[str, Any], label: str) -> None: + plans = payload.get("no_write_verifier_plans") or [] + required = { + "no_telegram_send_verifier", + "no_reviewer_queue_write_verifier", + "no_result_capture_write_verifier", + "no_live_readback_verifier", + "no_secret_payload_verifier", + } + plan_ids = {plan.get("plan_id") for plan in plans} + if plan_ids != required: + raise ValueError(f"{label}: no-write verifier plans must match {sorted(required)}") + for plan in plans: + plan_id = plan.get("plan_id") + if plan.get("live_verifier_enabled") is not False: + raise ValueError(f"{label}: verifier plan {plan_id} must not enable live verifier") + if not plan.get("required_fixture") or not plan.get("failure_if_missing"): + raise ValueError(f"{label}: verifier plan {plan_id} must include fixture and failure text") + if not _is_redacted_sha256(plan.get("evidence_hash")): + raise ValueError(f"{label}: verifier plan {plan_id} must expose redacted evidence_hash") + + +def _require_blocked_promotions(payload: dict[str, Any], label: str) -> None: + blockers = payload.get("blocked_promotions") or [] + required = { + "owner_acceptance_not_received", + "rollback_owner_missing", + "maintenance_window_missing", + "canonical_readback_scope_missing", + "secret_boundary_not_verified", + } + blocker_ids = {blocker.get("blocker_id") for blocker in blockers} + if blocker_ids != required: + raise ValueError(f"{label}: blocked promotions must match {sorted(required)}") + for blocker in blockers: + blocker_id = blocker.get("blocker_id") + if blocker.get("severity") not in {"high", "critical"}: + raise ValueError(f"{label}: blocker {blocker_id} severity is invalid") + if blocker.get("status") not in {"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_owner_packets", + "verify_acceptance_templates", + "confirm_verifier_plans", + "lock_blocked_promotions", + "promote_to_p2_115", + } + 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 = { + "owner_approval_packet_count": len(payload.get("owner_approval_packets") or []), + "acceptance_record_template_count": len(payload.get("acceptance_record_templates") or []), + "fixture_promotion_review_count": len(payload.get("fixture_promotion_reviews") or []), + "no_write_verifier_plan_count": len(payload.get("no_write_verifier_plans") or []), + "blocked_promotion_count": len(payload.get("blocked_promotions") or []), + "operator_action_count": len(payload.get("operator_actions") or []), + "approval_required_packet_count": sum( + 1 for packet in payload.get("owner_approval_packets") or [] if packet.get("status") == "approval_required" + ), + "blocked_packet_count": sum( + 1 for packet in payload.get("owner_approval_packets") or [] if packet.get("status") == "blocked_by_policy" + ), + "approval_required_template_count": sum( + 1 + for template in payload.get("acceptance_record_templates") or [] + if template.get("status") == "approval_required" + ), + "blocked_template_count": sum( + 1 + for template in payload.get("acceptance_record_templates") or [] + if template.get("status") == "blocked_by_policy" + ), + "approval_required_review_count": sum( + 1 for review in payload.get("fixture_promotion_reviews") or [] if review.get("status") == "approval_required" + ), + "blocked_review_count": sum( + 1 for review in payload.get("fixture_promotion_reviews") or [] if review.get("status") == "blocked_by_policy" + ), + "approval_required_verifier_count": sum( + 1 for plan in payload.get("no_write_verifier_plans") or [] if plan.get("status") == "approval_required" + ), + "blocked_verifier_count": sum( + 1 for plan in payload.get("no_write_verifier_plans") or [] if plan.get("status") == "blocked_by_policy" + ), + "critical_blocker_count": sum( + 1 for blocker in payload.get("blocked_promotions") or [] if blocker.get("severity") == "critical" + ), + } + mismatches = _mismatches(rollups, expected_counts) + if mismatches: + raise ValueError(f"{label}: rollup counts mismatch: {mismatches}") + + zero_rollups = { + "owner_approval_received_count", + "owner_acceptance_record_write_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_owner_approved_fixture_promotion_gate.py b/apps/api/tests/test_ai_agent_owner_approved_fixture_promotion_gate.py new file mode 100644 index 00000000..99a5d0ce --- /dev/null +++ b/apps/api/tests/test_ai_agent_owner_approved_fixture_promotion_gate.py @@ -0,0 +1,96 @@ +import copy +import json +from pathlib import Path + +import pytest + +from src.services.ai_agent_owner_approved_fixture_promotion_gate import ( + load_latest_ai_agent_owner_approved_fixture_promotion_gate, +) + + +REPO_ROOT = Path(__file__).resolve().parents[3] +FIXTURE = REPO_ROOT / "docs/evaluations/ai_agent_owner_approved_fixture_promotion_gate_2026-06-13.json" + + +def test_load_latest_ai_agent_owner_approved_fixture_promotion_gate_snapshot() -> None: + data = load_latest_ai_agent_owner_approved_fixture_promotion_gate() + + assert data["schema_version"] == "ai_agent_owner_approved_fixture_promotion_gate_v1" + assert data["program_status"]["current_task_id"] == "P2-114" + assert data["program_status"]["next_task_id"] == "P2-115" + assert data["program_status"]["overall_completion_percent"] == 100 + assert data["program_status"]["read_only_mode"] is True + + rollups = data["rollups"] + assert rollups["owner_approval_packet_count"] == 5 + assert rollups["acceptance_record_template_count"] == 4 + assert rollups["fixture_promotion_review_count"] == 4 + assert rollups["no_write_verifier_plan_count"] == 5 + assert rollups["blocked_promotion_count"] == 5 + assert rollups["operator_action_count"] == 5 + assert rollups["approval_required_packet_count"] == 2 + assert rollups["blocked_packet_count"] == 2 + assert rollups["critical_blocker_count"] == 3 + + zero_fields = [ + "owner_approval_received_count", + "owner_acceptance_record_write_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_owner_approved_fixture_promotion_gate_rejects_owner_approval(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["owner_gate_truth"]["owner_approval_received"] = True + target = tmp_path / "ai_agent_owner_approved_fixture_promotion_gate_2026-06-13.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="owner approval must remain false"): + load_latest_ai_agent_owner_approved_fixture_promotion_gate(tmp_path) + + +def test_owner_approved_fixture_promotion_gate_rejects_result_write(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["owner_gate_truth"]["result_capture_write_enabled"] = True + target = tmp_path / "ai_agent_owner_approved_fixture_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_owner_approved_fixture_promotion_gate(tmp_path) + + +def test_owner_approved_fixture_promotion_gate_rejects_rollup_drift(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["rollups"]["owner_approval_packet_count"] = 4 + target = tmp_path / "ai_agent_owner_approved_fixture_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_owner_approved_fixture_promotion_gate(tmp_path) + + +def test_owner_approved_fixture_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 session_id" + target = tmp_path / "ai_agent_owner_approved_fixture_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_owner_approved_fixture_promotion_gate(tmp_path) diff --git a/apps/api/tests/test_ai_agent_owner_approved_fixture_promotion_gate_api.py b/apps/api/tests/test_ai_agent_owner_approved_fixture_promotion_gate_api.py new file mode 100644 index 00000000..c0266b3b --- /dev/null +++ b/apps/api/tests/test_ai_agent_owner_approved_fixture_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_owner_approved_fixture_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-owner-approved-fixture-promotion-gate") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_owner_approved_fixture_promotion_gate_v1" + assert data["program_status"]["current_task_id"] == "P2-114" + assert data["program_status"]["next_task_id"] == "P2-115" + assert data["program_status"]["overall_completion_percent"] == 100 + + rollups = data["rollups"] + assert rollups["owner_approval_packet_count"] == 5 + assert rollups["acceptance_record_template_count"] == 4 + assert rollups["fixture_promotion_review_count"] == 4 + assert rollups["no_write_verifier_plan_count"] == 5 + assert rollups["blocked_promotion_count"] == 5 + assert rollups["operator_action_count"] == 5 + assert rollups["owner_approval_received_count"] == 0 + assert rollups["owner_acceptance_record_write_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 e50f227c..5507d10b 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5438,6 +5438,107 @@ "compare_result_capture_preview": "比對 result capture preview", "promote_to_p2_114": "推進 P2-114" } + }, + "ownerApprovedFixturePromotionGate": { + "title": "P2-114 owner-approved fixture promotion gate", + "source": "產生 {generated};目前 {current};下一步 {next}", + "priorGateTitle": "前一關 promotion gate", + "truthTitle": "Owner approval truth", + "redactionTitle": "前端遮蔽契約", + "metrics": { + "overall": "完成度", + "packets": "owner packet", + "templates": "acceptance template", + "reviews": "fixture review", + "verifiers": "no-write verifier", + "blockers": "blocked promotion", + "actions": "操作選項", + "approvalRequired": "需批准", + "blocked": "阻擋", + "critical": "critical blocker", + "ownerApprovals": "owner 批准", + "acceptanceWrites": "acceptance 寫入", + "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", + "productionWrites": "production 寫入", + "liveWrites": "live writes" + }, + "flags": { + "promotionGateLoaded": "P2-113 loaded={value}", + "packageReady": "package ready={value}", + "acceptanceReady": "acceptance ready={value}", + "ownerApproval": "owner approval={value}", + "telegramSend": "Telegram send={value}", + "resultWrite": "result write={value}", + "redactionRequired": "redaction={value}", + "promptDisplay": "prompt display={value}", + "secretDisplay": "secret display={value}", + "runtimePayloadDisplay": "runtime payload display={value}" + }, + "labels": { + "ownerFields": "owner fields {count}", + "allowedFixture": "fixture:{value}", + "blockedActions": "blocked actions {count}", + "requiredFields": "fields {count}", + "accepted": "accepted={value}", + "recordWrite": "record write={value}", + "reviewOutcome": "outcome:{value}", + "runtimeWrite": "runtime write={value}", + "liveVerifier": "live verifier={value}", + "blockedAction": "blocked:{value}", + "blockedUntil": "until:{value}", + "runtimePromotionAllowed": "runtime promotion={value}" + }, + "packetStatuses": { + "ready_for_owner_review": "待 owner review", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "templateStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "reviewStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "verifierStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "blockerStatuses": { + "mapped": "已映射", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "riskTiers": { + "high": "高風險", + "critical": "關鍵風險" + }, + "severities": { + "high": "高", + "critical": "關鍵" + }, + "actionTypes": { + "review_owner_packet": "審查 owner packet", + "verify_acceptance_template": "驗證 acceptance template", + "confirm_verifier_plan": "確認 verifier plan", + "lock_blocked_promotion": "鎖定 blocked promotion", + "promote_to_p2_115": "推進 P2-115" + } } } }, diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index e50f227c..5507d10b 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5438,6 +5438,107 @@ "compare_result_capture_preview": "比對 result capture preview", "promote_to_p2_114": "推進 P2-114" } + }, + "ownerApprovedFixturePromotionGate": { + "title": "P2-114 owner-approved fixture promotion gate", + "source": "產生 {generated};目前 {current};下一步 {next}", + "priorGateTitle": "前一關 promotion gate", + "truthTitle": "Owner approval truth", + "redactionTitle": "前端遮蔽契約", + "metrics": { + "overall": "完成度", + "packets": "owner packet", + "templates": "acceptance template", + "reviews": "fixture review", + "verifiers": "no-write verifier", + "blockers": "blocked promotion", + "actions": "操作選項", + "approvalRequired": "需批准", + "blocked": "阻擋", + "critical": "critical blocker", + "ownerApprovals": "owner 批准", + "acceptanceWrites": "acceptance 寫入", + "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", + "productionWrites": "production 寫入", + "liveWrites": "live writes" + }, + "flags": { + "promotionGateLoaded": "P2-113 loaded={value}", + "packageReady": "package ready={value}", + "acceptanceReady": "acceptance ready={value}", + "ownerApproval": "owner approval={value}", + "telegramSend": "Telegram send={value}", + "resultWrite": "result write={value}", + "redactionRequired": "redaction={value}", + "promptDisplay": "prompt display={value}", + "secretDisplay": "secret display={value}", + "runtimePayloadDisplay": "runtime payload display={value}" + }, + "labels": { + "ownerFields": "owner fields {count}", + "allowedFixture": "fixture:{value}", + "blockedActions": "blocked actions {count}", + "requiredFields": "fields {count}", + "accepted": "accepted={value}", + "recordWrite": "record write={value}", + "reviewOutcome": "outcome:{value}", + "runtimeWrite": "runtime write={value}", + "liveVerifier": "live verifier={value}", + "blockedAction": "blocked:{value}", + "blockedUntil": "until:{value}", + "runtimePromotionAllowed": "runtime promotion={value}" + }, + "packetStatuses": { + "ready_for_owner_review": "待 owner review", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "templateStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "reviewStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "verifierStatuses": { + "ready": "已準備", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "blockerStatuses": { + "mapped": "已映射", + "approval_required": "需批准", + "blocked_by_policy": "政策阻擋" + }, + "riskTiers": { + "high": "高風險", + "critical": "關鍵風險" + }, + "severities": { + "high": "高", + "critical": "關鍵" + }, + "actionTypes": { + "review_owner_packet": "審查 owner packet", + "verify_acceptance_template": "驗證 acceptance template", + "confirm_verifier_plan": "確認 verifier plan", + "lock_blocked_promotion": "鎖定 blocked promotion", + "promote_to_p2_115": "推進 P2-115" + } } } }, 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 3498ca4c..e9b00cc6 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 @@ -62,6 +62,7 @@ import { type AiAgentRuntimeReadbackFixtureApprovalSnapshot, type AiAgentRuntimeReadbackImplementationReviewSnapshot, type AiAgentRuntimeReadbackPromotionGateSnapshot, + type AiAgentOwnerApprovedFixturePromotionGateSnapshot, type AiAgentRuntimeWorkerShadowGateSnapshot, type AiAgentRuntimeVerifierEvidenceReviewSnapshot, type AiAgentRuntimeWriteGateReviewSnapshot, @@ -444,6 +445,7 @@ export function AutomationInventoryTab() { const [reportLiveDeliveryApprovalPackage, setReportLiveDeliveryApprovalPackage] = useState(null) const [runtimeReadbackFixtureApproval, setRuntimeReadbackFixtureApproval] = useState(null) const [runtimeReadbackPromotionGate, setRuntimeReadbackPromotionGate] = useState(null) + const [ownerApprovedFixturePromotionGate, setOwnerApprovedFixturePromotionGate] = useState(null) const [reportTruthActionabilityReview, setReportTruthActionabilityReview] = useState(null) const [ownerDryRunPackage, setOwnerDryRunPackage] = useState(null) const [hostStatefulInventory, setHostStatefulInventory] = useState(null) @@ -494,6 +496,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentReportLiveDeliveryApprovalPackage(), apiClient.getAiAgentRuntimeReadbackFixtureApproval(), apiClient.getAiAgentRuntimeReadbackPromotionGate(), + apiClient.getAiAgentOwnerApprovedFixturePromotionGate(), apiClient.getAiAgentReportTruthActionabilityReview(), apiClient.getAiAgentOwnerApprovedFixtureDryRun(), apiClient.getAiAgentHostStatefulVersionInventory(), @@ -543,6 +546,7 @@ export function AutomationInventoryTab() { reportLiveDeliveryApprovalPackageResult, runtimeReadbackFixtureApprovalResult, runtimeReadbackPromotionGateResult, + ownerApprovedFixturePromotionGateResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -589,6 +593,7 @@ export function AutomationInventoryTab() { setReportLiveDeliveryApprovalPackage(reportLiveDeliveryApprovalPackageResult.status === 'fulfilled' ? reportLiveDeliveryApprovalPackageResult.value : null) setRuntimeReadbackFixtureApproval(runtimeReadbackFixtureApprovalResult.status === 'fulfilled' ? runtimeReadbackFixtureApprovalResult.value : null) setRuntimeReadbackPromotionGate(runtimeReadbackPromotionGateResult.status === 'fulfilled' ? runtimeReadbackPromotionGateResult.value : null) + setOwnerApprovedFixturePromotionGate(ownerApprovedFixturePromotionGateResult.status === 'fulfilled' ? ownerApprovedFixturePromotionGateResult.value : null) setReportTruthActionabilityReview(reportTruthActionabilityReviewResult.status === 'fulfilled' ? reportTruthActionabilityReviewResult.value : null) setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null) setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null) @@ -633,6 +638,7 @@ export function AutomationInventoryTab() { reportLiveDeliveryApprovalPackageResult, runtimeReadbackFixtureApprovalResult, runtimeReadbackPromotionGateResult, + ownerApprovedFixturePromotionGateResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -1875,7 +1881,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 || !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 || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -2357,6 +2363,57 @@ export function AutomationInventoryTab() { + runtimeReadbackPromotionGate.rollups.playbook_trust_write_count + runtimeReadbackPromotionGate.rollups.production_write_count ) + const ownerPromotionOverall = ownerApprovedFixturePromotionGate.program_status.overall_completion_percent + const ownerPromotionPackets = ownerApprovedFixturePromotionGate.rollups.owner_approval_packet_count + const ownerPromotionTemplates = ownerApprovedFixturePromotionGate.rollups.acceptance_record_template_count + const ownerPromotionReviews = ownerApprovedFixturePromotionGate.rollups.fixture_promotion_review_count + const ownerPromotionVerifiers = ownerApprovedFixturePromotionGate.rollups.no_write_verifier_plan_count + const ownerPromotionBlockers = ownerApprovedFixturePromotionGate.rollups.blocked_promotion_count + const ownerPromotionActions = ownerApprovedFixturePromotionGate.rollups.operator_action_count + const ownerPromotionApprovalRequired = ( + ownerApprovedFixturePromotionGate.rollups.approval_required_packet_count + + ownerApprovedFixturePromotionGate.rollups.approval_required_template_count + + ownerApprovedFixturePromotionGate.rollups.approval_required_review_count + + ownerApprovedFixturePromotionGate.rollups.approval_required_verifier_count + ) + const ownerPromotionBlocked = ( + ownerApprovedFixturePromotionGate.rollups.blocked_packet_count + + ownerApprovedFixturePromotionGate.rollups.blocked_template_count + + ownerApprovedFixturePromotionGate.rollups.blocked_review_count + + ownerApprovedFixturePromotionGate.rollups.blocked_verifier_count + ) + const ownerPromotionCritical = ownerApprovedFixturePromotionGate.rollups.critical_blocker_count + const ownerPromotionOwnerApprovals = ownerApprovedFixturePromotionGate.rollups.owner_approval_received_count + const ownerPromotionAcceptanceWrites = ownerApprovedFixturePromotionGate.rollups.owner_acceptance_record_write_count + const ownerPromotionExecutions = ownerApprovedFixturePromotionGate.rollups.promotion_execution_count + const ownerPromotionCanonicalReads = ownerApprovedFixturePromotionGate.rollups.canonical_runtime_target_read_count + const ownerPromotionLiveQueries = ownerApprovedFixturePromotionGate.rollups.live_query_count + const ownerPromotionFailureReceiptSends = ownerApprovedFixturePromotionGate.rollups.failure_receipt_send_count + const ownerPromotionReviewerQueueWrites = ownerApprovedFixturePromotionGate.rollups.reviewer_queue_write_count + const ownerPromotionGatewayWrites = ownerApprovedFixturePromotionGate.rollups.gateway_queue_write_count + const ownerPromotionTelegramSends = ownerApprovedFixturePromotionGate.rollups.telegram_send_count + const ownerPromotionBotCalls = ownerApprovedFixturePromotionGate.rollups.bot_api_call_count + const ownerPromotionReceiptWrites = ownerApprovedFixturePromotionGate.rollups.report_receipt_write_count + const ownerPromotionResultWrites = ownerApprovedFixturePromotionGate.rollups.result_capture_write_count + const ownerPromotionLearningWrites = ownerApprovedFixturePromotionGate.rollups.learning_write_count + const ownerPromotionTrustWrites = ownerApprovedFixturePromotionGate.rollups.playbook_trust_write_count + const ownerPromotionProductionWrites = ownerApprovedFixturePromotionGate.rollups.production_write_count + const ownerPromotionLiveWrites = ( + ownerApprovedFixturePromotionGate.rollups.owner_acceptance_record_write_count + + ownerApprovedFixturePromotionGate.rollups.promotion_execution_count + + ownerApprovedFixturePromotionGate.rollups.canonical_runtime_target_read_count + + ownerApprovedFixturePromotionGate.rollups.live_query_count + + ownerApprovedFixturePromotionGate.rollups.failure_receipt_send_count + + ownerApprovedFixturePromotionGate.rollups.reviewer_queue_write_count + + ownerApprovedFixturePromotionGate.rollups.gateway_queue_write_count + + ownerApprovedFixturePromotionGate.rollups.telegram_send_count + + ownerApprovedFixturePromotionGate.rollups.bot_api_call_count + + ownerApprovedFixturePromotionGate.rollups.report_receipt_write_count + + ownerApprovedFixturePromotionGate.rollups.result_capture_write_count + + ownerApprovedFixturePromotionGate.rollups.learning_write_count + + ownerApprovedFixturePromotionGate.rollups.playbook_trust_write_count + + ownerApprovedFixturePromotionGate.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 @@ -6059,6 +6116,211 @@ export function AutomationInventoryTab() {
+
+
+
+ + + {t('ownerApprovedFixturePromotionGate.title')} + +
+ +
+ +
+ } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
+ +
+
+ {t('ownerApprovedFixturePromotionGate.priorGateTitle')} + + {ownerApprovedFixturePromotionGate.prior_promotion_gate.readiness_note} + +
+ + + + +
+
+ +
+ {t('ownerApprovedFixturePromotionGate.truthTitle')} + + {ownerApprovedFixturePromotionGate.owner_gate_truth.truth_note} + +
+ + + + + + +
+
+ +
+ {t('ownerApprovedFixturePromotionGate.redactionTitle')} + + {ownerApprovedFixturePromotionGate.display_redaction_contract.frontend_display_policy} + +
+ + + + +
+
+
+ +
+ {ownerApprovedFixturePromotionGate.owner_approval_packets.map(packet => ( +
+
+ + {packet.display_name} + + +
+
+ + + + + +
+
+ ))} +
+ +
+ {ownerApprovedFixturePromotionGate.acceptance_record_templates.map(template => ( +
+
+ + {template.display_name} + + +
+
+ + + +
+
+ ))} +
+ +
+ {ownerApprovedFixturePromotionGate.fixture_promotion_reviews.map(review => ( +
+
+ + {review.display_name} + + +
+
+ + + +
+
+ ))} +
+ +
+ {ownerApprovedFixturePromotionGate.no_write_verifier_plans.map(plan => ( +
+
+ + {plan.display_name} + + +
+ + {plan.failure_if_missing} + +
+ + +
+
+ ))} +
+ +
+ {ownerApprovedFixturePromotionGate.blocked_promotions.map(blocker => ( +
+
+ + {blocker.display_name} + + +
+
+ + + +
+
+ ))} +
+ +
+ {ownerApprovedFixturePromotionGate.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 c66ca5e7..1a85daea 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -465,6 +465,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiAgentOwnerApprovedFixturePromotionGate() { + const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-promotion-gate`) + return handleResponse(res) + }, + async getAiAgentOwnerApprovedFixtureDryRun() { const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-dry-run`) return handleResponse(res) @@ -4638,6 +4643,177 @@ export interface AiAgentRuntimeReadbackPromotionGateSnapshot { } } +export interface AiAgentOwnerApprovedFixturePromotionGateSnapshot { + schema_version: 'ai_agent_owner_approved_fixture_promotion_gate_v1' + generated_at: string + program_status: { + overall_completion_percent: number + current_priority: 'P0' | 'P1' | 'P2' | 'P3' + current_task_id: 'P2-114' + next_task_id: 'P2-115' + read_only_mode: true + runtime_authority: 'owner_approved_fixture_promotion_gate_only_no_live_read_or_write' + status_note: string + } + source_refs: string[] + prior_promotion_gate: { + schema_version: 'ai_agent_runtime_readback_promotion_gate_v1' + 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 + owner_approval_received_count: number + promotion_execution_count: number + canonical_runtime_target_read_count: number + live_query_count: number + production_write_count: number + readiness_note: string + } + owner_gate_truth: { + p2_113_promotion_gate_loaded: true + owner_promotion_package_ready: true + acceptance_record_template_ready: true + reviewer_queue_fixture_ready: true + result_capture_fixture_ready: true + rollback_owner_required: true + verifier_plan_required: true + owner_approval_received: false + 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 + owner_acceptance_record_write_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 + } + owner_approval_packets: Array<{ + packet_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + source_lane_id: string + status: 'ready_for_owner_review' | 'approval_required' | 'blocked_by_policy' + risk_tier: 'high' | 'critical' + required_owner_fields: string[] + allowed_fixture: string + blocked_runtime_actions: string[] + owner_acceptance_required: true + evidence_hash: string + }> + acceptance_record_templates: Array<{ + template_id: string + display_name: string + status: 'ready' | 'approval_required' | 'blocked_by_policy' + required_fields: string[] + accepted: false + record_write_enabled: false + evidence_hash: string + }> + fixture_promotion_reviews: Array<{ + review_id: string + display_name: string + status: 'ready' | 'approval_required' | 'blocked_by_policy' + source_packet_id: string + review_outcome: string + runtime_write_enabled: false + evidence_hash: string + }> + no_write_verifier_plans: Array<{ + plan_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 + }> + blocked_promotions: 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' + action_type: 'review_owner_packet' | 'verify_acceptance_template' | 'confirm_verifier_plan' | 'lock_blocked_promotion' | 'promote_to_p2_115' + 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: { + owner_approval_packet_count: number + acceptance_record_template_count: number + fixture_promotion_review_count: number + no_write_verifier_plan_count: number + blocked_promotion_count: number + operator_action_count: number + approval_required_packet_count: number + blocked_packet_count: number + approval_required_template_count: number + blocked_template_count: number + approval_required_review_count: number + blocked_review_count: number + approval_required_verifier_count: number + blocked_verifier_count: number + critical_blocker_count: number + owner_approval_received_count: number + owner_acceptance_record_write_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_owner_approved_fixture_promotion_gate_2026-06-13.json b/docs/evaluations/ai_agent_owner_approved_fixture_promotion_gate_2026-06-13.json new file mode 100644 index 00000000..ae8af922 --- /dev/null +++ b/docs/evaluations/ai_agent_owner_approved_fixture_promotion_gate_2026-06-13.json @@ -0,0 +1,458 @@ +{ + "schema_version": "ai_agent_owner_approved_fixture_promotion_gate_v1", + "generated_at": "2026-06-13T18:45:00+08:00", + "program_status": { + "overall_completion_percent": 100, + "current_priority": "P2", + "current_task_id": "P2-114", + "next_task_id": "P2-115", + "read_only_mode": true, + "runtime_authority": "owner_approved_fixture_promotion_gate_only_no_live_read_or_write", + "status_note": "P2-114 只把 P2-113 promotion gate 整理成 owner-approved fixture promotion package;不得 live read、不得寫 reviewer queue / result capture / Gateway / Telegram / production。" + }, + "source_refs": [ + "docs/evaluations/ai_agent_runtime_readback_promotion_gate_2026-06-13.json", + "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/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md#34-建立-runtime-readback-promotion-gate" + ], + "prior_promotion_gate": { + "schema_version": "ai_agent_runtime_readback_promotion_gate_v1", + "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, + "owner_approval_received_count": 0, + "promotion_execution_count": 0, + "canonical_runtime_target_read_count": 0, + "live_query_count": 0, + "production_write_count": 0, + "readiness_note": "P2-113 已正式驗證 no-write promotion gate;P2-114 只整理 owner approval packet、acceptance record template、rollback owner 與 verifier plan。" + }, + "owner_gate_truth": { + "p2_113_promotion_gate_loaded": true, + "owner_promotion_package_ready": true, + "acceptance_record_template_ready": true, + "reviewer_queue_fixture_ready": true, + "result_capture_fixture_ready": true, + "rollback_owner_required": true, + "verifier_plan_required": true, + "owner_approval_received": false, + "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, + "owner_acceptance_record_write_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": "owner-approved fixture promotion package 已可審查;真正 owner acceptance、live read、queue write、Telegram send、result capture 與 production write 仍全部為 0。" + }, + "owner_approval_packets": [ + { + "packet_id": "failure_receipt_owner_packet", + "display_name": "Failure receipt owner approval packet", + "owner_agent": "hermes", + "source_lane_id": "telegram_failure_receipt_promotion", + "status": "ready_for_owner_review", + "risk_tier": "high", + "required_owner_fields": [ + "owner_role", + "decision", + "decision_reason", + "sre_route_lock" + ], + "allowed_fixture": "no_send_failure_receipt_fixture", + "blocked_runtime_actions": [ + "telegram_send", + "bot_api_call", + "report_receipt_write" + ], + "owner_acceptance_required": true, + "evidence_hash": "sha256:2424242424242424242424242424242424242424242424242424242424242424" + }, + { + "packet_id": "reviewer_queue_owner_packet", + "display_name": "Reviewer queue owner approval packet", + "owner_agent": "openclaw", + "source_lane_id": "reviewer_queue_preview_promotion", + "status": "approval_required", + "risk_tier": "high", + "required_owner_fields": [ + "reviewer_owner", + "queue_scope", + "decision_template", + "audit_boundary" + ], + "allowed_fixture": "reviewer_queue_preview_digest", + "blocked_runtime_actions": [ + "reviewer_queue_write", + "timeline_write", + "audit_db_write" + ], + "owner_acceptance_required": true, + "evidence_hash": "sha256:2525252525252525252525252525252525252525252525252525252525252525" + }, + { + "packet_id": "result_capture_owner_packet", + "display_name": "Result capture owner approval packet", + "owner_agent": "nemotron", + "source_lane_id": "result_capture_no_write_promotion", + "status": "blocked_by_policy", + "risk_tier": "critical", + "required_owner_fields": [ + "score_owner", + "capture_scope", + "playbook_trust_owner", + "rollback_owner" + ], + "allowed_fixture": "result_capture_preview_digest", + "blocked_runtime_actions": [ + "score_write", + "result_capture_write", + "playbook_trust_write" + ], + "owner_acceptance_required": true, + "evidence_hash": "sha256:2626262626262626262626262626262626262626262626262626262626262626" + }, + { + "packet_id": "report_receipt_owner_packet", + "display_name": "Report receipt owner approval packet", + "owner_agent": "hermes", + "source_lane_id": "report_delivery_receipt_link", + "status": "approval_required", + "risk_tier": "high", + "required_owner_fields": [ + "report_owner", + "receipt_scope", + "ai_analysis_boundary", + "maintenance_window" + ], + "allowed_fixture": "report_delivery_receipt_preview", + "blocked_runtime_actions": [ + "gateway_queue_write", + "report_receipt_write", + "ai_analysis_run" + ], + "owner_acceptance_required": true, + "evidence_hash": "sha256:2727272727272727272727272727272727272727272727272727272727272727" + }, + { + "packet_id": "p2_115_scope_owner_packet", + "display_name": "P2-115 canonical readback scope packet", + "owner_agent": "openclaw", + "source_lane_id": "p2_114_handoff_scope", + "status": "blocked_by_policy", + "risk_tier": "critical", + "required_owner_fields": [ + "canonical_target_scope", + "readback_owner", + "rollback_owner", + "verifier_owner" + ], + "allowed_fixture": "canonical_readback_scope_preview", + "blocked_runtime_actions": [ + "canonical_runtime_target_read", + "live_query", + "production_write" + ], + "owner_acceptance_required": true, + "evidence_hash": "sha256:2828282828282828282828282828282828282828282828282828282828282828" + } + ], + "acceptance_record_templates": [ + { + "template_id": "owner_role_decision_record", + "display_name": "Owner role and decision record", + "status": "ready", + "required_fields": [ + "owner_role", + "decision", + "decision_reason" + ], + "accepted": false, + "record_write_enabled": false, + "evidence_hash": "sha256:2929292929292929292929292929292929292929292929292929292929292929" + }, + { + "template_id": "affected_scope_record", + "display_name": "Affected scope record", + "status": "ready", + "required_fields": [ + "affected_scope", + "redacted_evidence_refs", + "followup_owner" + ], + "accepted": false, + "record_write_enabled": false, + "evidence_hash": "sha256:3030303030303030303030303030303030303030303030303030303030303030" + }, + { + "template_id": "rollback_owner_record", + "display_name": "Rollback owner record", + "status": "approval_required", + "required_fields": [ + "rollback_owner", + "rollback_plan", + "stop_condition" + ], + "accepted": false, + "record_write_enabled": false, + "evidence_hash": "sha256:3131313131313131313131313131313131313131313131313131313131313131" + }, + { + "template_id": "maintenance_window_record", + "display_name": "Maintenance window record", + "status": "blocked_by_policy", + "required_fields": [ + "maintenance_window", + "validation_plan", + "post_action_verifier" + ], + "accepted": false, + "record_write_enabled": false, + "evidence_hash": "sha256:3232323232323232323232323232323232323232323232323232323232323232" + } + ], + "fixture_promotion_reviews": [ + { + "review_id": "no_send_receipt_review", + "display_name": "No-send receipt fixture review", + "status": "ready", + "source_packet_id": "failure_receipt_owner_packet", + "review_outcome": "fixture_ready_no_send", + "runtime_write_enabled": false, + "evidence_hash": "sha256:3333333333333333333333333333333333333333333333333333333333333333" + }, + { + "review_id": "no_write_queue_review", + "display_name": "No-write queue fixture review", + "status": "approval_required", + "source_packet_id": "reviewer_queue_owner_packet", + "review_outcome": "queue_preview_ready_no_write", + "runtime_write_enabled": false, + "evidence_hash": "sha256:3434343434343434343434343434343434343434343434343434343434343434" + }, + { + "review_id": "no_write_result_review", + "display_name": "No-write result fixture review", + "status": "blocked_by_policy", + "source_packet_id": "result_capture_owner_packet", + "review_outcome": "result_preview_blocked_until_owner_acceptance", + "runtime_write_enabled": false, + "evidence_hash": "sha256:3535353535353535353535353535353535353535353535353535353535353535" + }, + { + "review_id": "no_live_readback_review", + "display_name": "No-live readback fixture review", + "status": "blocked_by_policy", + "source_packet_id": "p2_115_scope_owner_packet", + "review_outcome": "canonical_readback_blocked", + "runtime_write_enabled": false, + "evidence_hash": "sha256:3636363636363636363636363636363636363636363636363636363636363636" + } + ], + "no_write_verifier_plans": [ + { + "plan_id": "no_telegram_send_verifier", + "display_name": "No Telegram send verifier", + "status": "ready", + "required_fixture": "telegram_send_count remains 0", + "failure_if_missing": "任何 Telegram send 或 Bot API call 非 0 都不得 promotion。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:3737373737373737373737373737373737373737373737373737373737373737" + }, + { + "plan_id": "no_reviewer_queue_write_verifier", + "display_name": "No reviewer queue write verifier", + "status": "approval_required", + "required_fixture": "reviewer queue write count remains 0", + "failure_if_missing": "缺 no-write queue fixture 時不得建立 reviewer queue item。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:3838383838383838383838383838383838383838383838383838383838383838" + }, + { + "plan_id": "no_result_capture_write_verifier", + "display_name": "No result capture write verifier", + "status": "blocked_by_policy", + "required_fixture": "result capture write count remains 0", + "failure_if_missing": "result capture / learning / trust 任何寫入非 0 都必須退回。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:3939393939393939393939393939393939393939393939393939393939393939" + }, + { + "plan_id": "no_live_readback_verifier", + "display_name": "No live readback verifier", + "status": "blocked_by_policy", + "required_fixture": "canonical runtime target read count remains 0", + "failure_if_missing": "未完成 P2-115 前不得讀 canonical runtime target。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:4040404040404040404040404040404040404040404040404040404040404040" + }, + { + "plan_id": "no_secret_payload_verifier", + "display_name": "No secret payload verifier", + "status": "ready", + "required_fixture": "secret read count remains 0", + "failure_if_missing": "任何 secret payload 讀取都違反 P2-114。", + "live_verifier_enabled": false, + "evidence_hash": "sha256:4141414141414141414141414141414141414141414141414141414141414141" + } + ], + "blocked_promotions": [ + { + "blocker_id": "owner_acceptance_not_received", + "display_name": "Owner acceptance not received", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "execute_owner_promotion", + "blocked_until": "owner_acceptance_record_accepted", + "evidence_hash": "sha256:4242424242424242424242424242424242424242424242424242424242424242" + }, + { + "blocker_id": "rollback_owner_missing", + "display_name": "Rollback owner missing", + "severity": "high", + "status": "approval_required", + "blocked_action": "result_capture_write", + "blocked_until": "rollback_owner_record_accepted", + "evidence_hash": "sha256:4343434343434343434343434343434343434343434343434343434343434343" + }, + { + "blocker_id": "maintenance_window_missing", + "display_name": "Maintenance window missing", + "severity": "high", + "status": "approval_required", + "blocked_action": "telegram_send_or_live_query", + "blocked_until": "maintenance_window_accepted", + "evidence_hash": "sha256:4444444444444444444444444444444444444444444444444444444444444444" + }, + { + "blocker_id": "canonical_readback_scope_missing", + "display_name": "Canonical readback scope missing", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "canonical_runtime_target_read", + "blocked_until": "p2_115_owner_scope_accepted", + "evidence_hash": "sha256:4545454545454545454545454545454545454545454545454545454545454545" + }, + { + "blocker_id": "secret_boundary_not_verified", + "display_name": "Secret boundary not verified", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "read_secret_or_raw_payload", + "blocked_until": "redaction_and_secret_boundary_verified", + "evidence_hash": "sha256:4646464646464646464646464646464646464646464646464646464646464646" + } + ], + "operator_actions": [ + { + "action_id": "review_owner_packets", + "display_name": "審查 owner approval packets", + "owner_agent": "openclaw", + "action_type": "review_owner_packet", + "operator_instruction": "確認每個 packet 都有 owner、decision、reason、scope 與 blocked runtime action;缺一項即退回。", + "runtime_promotion_allowed": false + }, + { + "action_id": "verify_acceptance_templates", + "display_name": "驗證 acceptance templates", + "owner_agent": "hermes", + "action_type": "verify_acceptance_template", + "operator_instruction": "只驗證 acceptance record 欄位,不寫入 record、不建立 queue、不送 Telegram。", + "runtime_promotion_allowed": false + }, + { + "action_id": "confirm_verifier_plans", + "display_name": "確認 no-write verifier plans", + "owner_agent": "nemotron", + "action_type": "confirm_verifier_plan", + "operator_instruction": "確認 no-send、no-write、no-live-read、no-secret 五項 verifier 都有 fixture;不得執行 live verifier。", + "runtime_promotion_allowed": false + }, + { + "action_id": "lock_blocked_promotions", + "display_name": "鎖定 blocked promotions", + "owner_agent": "openclaw", + "action_type": "lock_blocked_promotion", + "operator_instruction": "把 owner、rollback、maintenance、canonical scope、secret boundary 缺口全部列為 blocked,避免誤按批准。", + "runtime_promotion_allowed": false + }, + { + "action_id": "promote_to_p2_115", + "display_name": "推進 P2-115", + "owner_agent": "openclaw", + "action_type": "promote_to_p2_115", + "operator_instruction": "只有在 owner acceptance package 完整後,才能進 P2-115 評估 canonical runtime readback;不是 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": "只顯示 owner packet、acceptance template、fixture review、blocked promotion 與 no-write verifier;不顯示內部協作逐字稿、提示詞、私密推理內容、secret 或原始 runtime payload。" + }, + "rollups": { + "owner_approval_packet_count": 5, + "acceptance_record_template_count": 4, + "fixture_promotion_review_count": 4, + "no_write_verifier_plan_count": 5, + "blocked_promotion_count": 5, + "operator_action_count": 5, + "approval_required_packet_count": 2, + "blocked_packet_count": 2, + "approval_required_template_count": 1, + "blocked_template_count": 1, + "approval_required_review_count": 1, + "blocked_review_count": 2, + "approval_required_verifier_count": 1, + "blocked_verifier_count": 2, + "critical_blocker_count": 3, + "owner_approval_received_count": 0, + "owner_acceptance_record_write_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_owner_approved_fixture_promotion_gate_v1.schema.json b/docs/schemas/ai_agent_owner_approved_fixture_promotion_gate_v1.schema.json new file mode 100644 index 00000000..5b9980fc --- /dev/null +++ b/docs/schemas/ai_agent_owner_approved_fixture_promotion_gate_v1.schema.json @@ -0,0 +1,392 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.wooo.work/schemas/ai_agent_owner_approved_fixture_promotion_gate_v1.schema.json", + "title": "AI Agent Owner Approved Fixture Promotion Gate v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "prior_promotion_gate", + "owner_gate_truth", + "owner_approval_packets", + "acceptance_record_templates", + "fixture_promotion_reviews", + "no_write_verifier_plans", + "blocked_promotions", + "operator_actions", + "display_redaction_contract", + "rollups" + ], + "properties": { + "schema_version": { + "const": "ai_agent_owner_approved_fixture_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-114" + }, + "next_task_id": { + "const": "P2-115" + }, + "read_only_mode": { + "const": true + }, + "runtime_authority": { + "const": "owner_approved_fixture_promotion_gate_only_no_live_read_or_write" + }, + "status_note": { + "type": "string", + "minLength": 1 + } + } + }, + "source_refs": { + "$ref": "#/$defs/string_array" + }, + "prior_promotion_gate": { + "type": "object" + }, + "owner_gate_truth": { + "type": "object" + }, + "owner_approval_packets": { + "type": "array", + "minItems": 5, + "items": { + "$ref": "#/$defs/owner_packet" + } + }, + "acceptance_record_templates": { + "type": "array", + "minItems": 4, + "items": { + "$ref": "#/$defs/acceptance_template" + } + }, + "fixture_promotion_reviews": { + "type": "array", + "minItems": 4, + "items": { + "$ref": "#/$defs/fixture_review" + } + }, + "no_write_verifier_plans": { + "type": "array", + "minItems": 5, + "items": { + "$ref": "#/$defs/verifier_plan" + } + }, + "blocked_promotions": { + "type": "array", + "minItems": 5, + "items": { + "$ref": "#/$defs/blocked_promotion" + } + }, + "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": [ + "high", + "critical" + ] + }, + "owner_packet": { + "type": "object", + "additionalProperties": false, + "required": [ + "packet_id", + "display_name", + "owner_agent", + "source_lane_id", + "status", + "risk_tier", + "required_owner_fields", + "allowed_fixture", + "blocked_runtime_actions", + "owner_acceptance_required", + "evidence_hash" + ], + "properties": { + "packet_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "owner_agent": { + "$ref": "#/$defs/owner_agent" + }, + "source_lane_id": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/status" + }, + "risk_tier": { + "$ref": "#/$defs/risk_tier" + }, + "required_owner_fields": { + "$ref": "#/$defs/string_array" + }, + "allowed_fixture": { + "type": "string" + }, + "blocked_runtime_actions": { + "$ref": "#/$defs/string_array" + }, + "owner_acceptance_required": { + "const": true + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + } + }, + "acceptance_template": { + "type": "object", + "additionalProperties": false, + "required": [ + "template_id", + "display_name", + "status", + "required_fields", + "accepted", + "record_write_enabled", + "evidence_hash" + ], + "properties": { + "template_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/status" + }, + "required_fields": { + "$ref": "#/$defs/string_array" + }, + "accepted": { + "const": false + }, + "record_write_enabled": { + "const": false + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + } + }, + "fixture_review": { + "type": "object", + "additionalProperties": false, + "required": [ + "review_id", + "display_name", + "status", + "source_packet_id", + "review_outcome", + "runtime_write_enabled", + "evidence_hash" + ], + "properties": { + "review_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "status": { + "$ref": "#/$defs/status" + }, + "source_packet_id": { + "type": "string" + }, + "review_outcome": { + "type": "string" + }, + "runtime_write_enabled": { + "const": false + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + } + }, + "verifier_plan": { + "type": "object", + "additionalProperties": false, + "required": [ + "plan_id", + "display_name", + "status", + "required_fixture", + "failure_if_missing", + "live_verifier_enabled", + "evidence_hash" + ], + "properties": { + "plan_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" + } + } + }, + "blocked_promotion": { + "type": "object", + "additionalProperties": false, + "required": [ + "blocker_id", + "display_name", + "severity", + "status", + "blocked_action", + "blocked_until", + "evidence_hash" + ], + "properties": { + "blocker_id": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "severity": { + "enum": [ + "high", + "critical" + ] + }, + "status": { + "$ref": "#/$defs/status" + }, + "blocked_action": { + "type": "string" + }, + "blocked_until": { + "type": "string" + }, + "evidence_hash": { + "$ref": "#/$defs/hash" + } + } + }, + "operator_action": { + "type": "object", + "additionalProperties": false, + "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 + } + } + } + } +}