From cc7809fb3a771a313a00e785c6fde1590bdcbc38 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Jun 2026 23:57:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(governance):=20=E6=96=B0=E5=A2=9E=20result?= =?UTF-8?q?=20capture=20writer=20dry-run=20fixture?= 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_writer_dry_run_fixture.py | 410 ++++++++++++++++ ...t_result_capture_writer_dry_run_fixture.py | 122 +++++ ...sult_capture_writer_dry_run_fixture_api.py | 49 ++ apps/web/messages/en.json | 35 ++ apps/web/messages/zh-TW.json | 35 ++ .../tabs/automation-inventory-tab.tsx | 166 ++++++- apps/web/src/lib/api-client.ts | 194 ++++++++ ...ure_writer_dry_run_fixture_2026-06-14.json | 453 ++++++++++++++++++ ...ture_writer_dry_run_fixture_v1.schema.json | 231 +++++++++ 10 files changed, 1727 insertions(+), 1 deletion(-) create mode 100644 apps/api/src/services/ai_agent_result_capture_writer_dry_run_fixture.py create mode 100644 apps/api/tests/test_ai_agent_result_capture_writer_dry_run_fixture.py create mode 100644 apps/api/tests/test_ai_agent_result_capture_writer_dry_run_fixture_api.py create mode 100644 docs/evaluations/ai_agent_result_capture_writer_dry_run_fixture_2026-06-14.json create mode 100644 docs/schemas/ai_agent_result_capture_writer_dry_run_fixture_v1.schema.json diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 645efbe5f..7d1be9646 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -130,6 +130,9 @@ from src.services.ai_agent_result_capture_write_gate_review import ( from src.services.ai_agent_result_capture_writer_implementation_review import ( load_latest_ai_agent_result_capture_writer_implementation_review, ) +from src.services.ai_agent_result_capture_writer_dry_run_fixture import ( + load_latest_ai_agent_result_capture_writer_dry_run_fixture, +) from src.services.ai_agent_runtime_readback_approval_package import ( load_latest_ai_agent_runtime_readback_approval_package, ) @@ -1752,6 +1755,36 @@ async def get_agent_result_capture_writer_implementation_review() -> dict[str, A ) from exc +@router.get( + "/agent-result-capture-writer-dry-run-fixture", + response_model=dict[str, Any], + summary="取得 AI Agent result capture writer dry-run fixture", + description=( + "讀取最新已提交的 P2-123 result capture writer dry-run fixture;" + "此端點只回傳 writer dry-run fixture、receipt preview、idempotency replay、" + "rollback rehearsal、blocked runtime write 與 operator handoff,不套用 writer、" + "不寫 result capture、learning、PlayBook trust、reviewer queue、Gateway queue," + "不送 Telegram、不呼叫 Bot API、不讀 secret。" + ), +) +async def get_agent_result_capture_writer_dry_run_fixture() -> dict[str, Any]: + """Return the latest read-only result capture writer dry-run fixture package.""" + try: + payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_writer_dry_run_fixture) + 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_writer_dry_run_fixture_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent result capture writer dry-run fixture 無效", + ) 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_writer_dry_run_fixture.py b/apps/api/src/services/ai_agent_result_capture_writer_dry_run_fixture.py new file mode 100644 index 000000000..0bd0dbf91 --- /dev/null +++ b/apps/api/src/services/ai_agent_result_capture_writer_dry_run_fixture.py @@ -0,0 +1,410 @@ +""" +AI Agent result capture writer dry-run fixture snapshot. + +Loads the latest committed P2-123 writer dry-run fixture package. +This module validates committed evidence only; it never applies writers, 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_writer_dry_run_fixture_*.json" +_SCHEMA_VERSION = "ai_agent_result_capture_writer_dry_run_fixture_v1" +_RUNTIME_AUTHORITY = "result_capture_writer_dry_run_fixture_only_no_live_write" +_TARGET_DRY_RUN = "result_capture_writer_dry_run_fixture" + + +def load_latest_ai_agent_result_capture_writer_dry_run_fixture( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed result capture writer dry-run fixture 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 writer dry-run fixture 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_writer_dry_run_fixtures(payload, label) + _require_receipt_previews(payload, label) + _require_idempotency_replay(payload, label) + _require_rollback_rehearsals(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-123", + "next_task_id": "P2-124", + "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_implementation_review") or {} + expected = { + "schema_version": "ai_agent_result_capture_writer_implementation_review_v1", + "writer_implementation_review_count": 5, + "implementation_gate_count": 5, + "post_implementation_verifier_plan_count": 5, + "blocked_runtime_change_count": 6, + "operator_action_count": 5, + "writer_apply_count": 0, + "implementation_applied_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_implementation_review mismatch: {mismatches}") + if not prior.get("readiness_note"): + raise ValueError(f"{label}: prior_result_capture_writer_implementation_review.readiness_note is required") + + +def _require_truth(payload: dict[str, Any], label: str) -> None: + truth = payload.get("dry_run_truth") or {} + required_true = { + "p2_122_implementation_review_loaded", + "result_capture_writer_dry_run_fixture_ready", + "receipt_preview_ready", + "idempotency_replay_ready", + "rollback_rehearsal_ready", + "redaction_preview_ready", + "fixture_only_mode", + "owner_approval_required", + "dual_approval_required", + "dry_run_hash_required", + "idempotency_key_required", + "post_dry_run_verifier_required", + "rollback_required", + } + missing = sorted(field for field in required_true if truth.get(field) is not True) + if missing: + raise ValueError(f"{label}: dry-run ready flags must remain true: {missing}") + required_false = { + "writer_apply_enabled", + "dry_run_execution_enabled", + "receipt_write_enabled", + "canonical_runtime_target_read_enabled", + "live_query_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", + "dual_approval_received_count", + "dry_run_hash_verified_count", + "writer_apply_count", + "dry_run_execution_count", + "receipt_write_count", + "canonical_runtime_target_read_count", + "live_query_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}: dry-run live counters must remain zero: {non_zero}") + if not truth.get("truth_note"): + raise ValueError(f"{label}: dry_run_truth.truth_note is required") + + +def _require_writer_dry_run_fixtures(payload: dict[str, Any], label: str) -> None: + fixtures = payload.get("writer_dry_run_fixtures") or [] + required = { + "fixture_result_capture_writer", + "fixture_learning_writer", + "fixture_playbook_trust_writer", + "fixture_reviewer_queue_writer", + "fixture_gateway_queue_writer", + } + fixture_ids = {fixture.get("fixture_id") for fixture in fixtures} + if fixture_ids != required: + raise ValueError(f"{label}: writer dry-run fixtures must match {sorted(required)}") + for fixture in fixtures: + fixture_id = fixture.get("fixture_id") + if fixture.get("target_dry_run") != _TARGET_DRY_RUN: + raise ValueError(f"{label}: fixture {fixture_id} must target {_TARGET_DRY_RUN}") + if fixture.get("fixture_only") is not True: + raise ValueError(f"{label}: fixture {fixture_id} must remain fixture-only") + if fixture.get("runtime_write_enabled") is not False: + raise ValueError(f"{label}: fixture {fixture_id} must not enable runtime write") + if fixture.get("status") not in {"ready_for_owner_review", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: fixture {fixture_id} status is invalid") + for field in {"input_fixture", "expected_receipt_preview", "idempotency_key", "blocked_live_action"}: + if not fixture.get(field): + raise ValueError(f"{label}: fixture {fixture_id} missing {field}") + if not _is_redacted_sha256(fixture.get("evidence_hash")): + raise ValueError(f"{label}: fixture {fixture_id} must expose redacted evidence_hash") + + +def _require_receipt_previews(payload: dict[str, Any], label: str) -> None: + previews = payload.get("receipt_previews") or [] + required = { + "receipt_result_capture_preview", + "receipt_learning_preview", + "receipt_playbook_trust_preview", + "receipt_reviewer_queue_preview", + "receipt_gateway_queue_preview", + } + preview_ids = {preview.get("receipt_id") for preview in previews} + if preview_ids != required: + raise ValueError(f"{label}: receipt previews must match {sorted(required)}") + for preview in previews: + receipt_id = preview.get("receipt_id") + if preview.get("live_receipt_enabled") is not False: + raise ValueError(f"{label}: receipt preview {receipt_id} must not enable live receipt") + if preview.get("status") not in {"ready", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: receipt preview {receipt_id} status is invalid") + for field in {"preview_payload", "redaction_policy", "failure_if_missing"}: + if not preview.get(field): + raise ValueError(f"{label}: receipt preview {receipt_id} missing {field}") + + +def _require_idempotency_replay(payload: dict[str, Any], label: str) -> None: + checks = payload.get("idempotency_replay_checks") or [] + required = { + "replay_duplicate_noop", + "replay_conflict_reject", + "replay_retry_same_receipt", + "replay_rollback_rehearsal", + "replay_gateway_queue_noop", + } + check_ids = {check.get("check_id") for check in checks} + if check_ids != required: + raise ValueError(f"{label}: idempotency replay checks must match {sorted(required)}") + for check in checks: + check_id = check.get("check_id") + if check.get("live_execution_enabled") is not False: + raise ValueError(f"{label}: replay check {check_id} must not enable live execution") + if check.get("status") not in {"ready", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: replay check {check_id} status is invalid") + if not check.get("expected_outcome") or not check.get("failure_if_missing"): + raise ValueError(f"{label}: replay check {check_id} must include expected outcome and failure") + if not _is_redacted_sha256(check.get("evidence_hash")): + raise ValueError(f"{label}: replay check {check_id} must expose redacted evidence_hash") + + +def _require_rollback_rehearsals(payload: dict[str, Any], label: str) -> None: + rehearsals = payload.get("rollback_rehearsals") or [] + required = { + "rollback_result_capture_preview", + "rollback_learning_preview", + "rollback_playbook_trust_preview", + "rollback_reviewer_queue_preview", + "rollback_gateway_queue_preview", + } + rehearsal_ids = {rehearsal.get("rehearsal_id") for rehearsal in rehearsals} + if rehearsal_ids != required: + raise ValueError(f"{label}: rollback rehearsals must match {sorted(required)}") + for rehearsal in rehearsals: + rehearsal_id = rehearsal.get("rehearsal_id") + if rehearsal.get("live_execution_enabled") is not False: + raise ValueError(f"{label}: rollback rehearsal {rehearsal_id} must not enable live execution") + if rehearsal.get("status") not in {"ready", "approval_required", "blocked_by_policy"}: + raise ValueError(f"{label}: rollback rehearsal {rehearsal_id} status is invalid") + if not rehearsal.get("rollback_preview") or not rehearsal.get("blocked_live_action"): + raise ValueError(f"{label}: rollback rehearsal {rehearsal_id} must include preview and blocked action") + + +def _require_blocked_writes(payload: dict[str, Any], label: str) -> None: + blockers = payload.get("blocked_runtime_writes") or [] + required = { + "blocked_result_capture_writer_apply", + "blocked_learning_writer_apply", + "blocked_playbook_trust_writer_apply", + "blocked_reviewer_queue_apply", + "blocked_gateway_queue_apply", + "blocked_telegram_send_apply", + } + blocker_ids = {blocker.get("blocker_id") for blocker in blockers} + if blocker_ids != required: + raise ValueError(f"{label}: blocked runtime 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_writer_dry_run_fixtures", + "verify_receipt_preview_contract", + "replay_idempotency_fixture", + "rehearse_rollback_preview", + "promote_to_p2_124", + } + 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_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 {} + fixtures = payload.get("writer_dry_run_fixtures") or [] + previews = payload.get("receipt_previews") or [] + replays = payload.get("idempotency_replay_checks") or [] + rehearsals = payload.get("rollback_rehearsals") or [] + blockers = payload.get("blocked_runtime_writes") or [] + actions = payload.get("operator_actions") or [] + expected = { + "writer_dry_run_fixture_count": len(fixtures), + "receipt_preview_count": len(previews), + "idempotency_replay_check_count": len(replays), + "rollback_rehearsal_count": len(rehearsals), + "blocked_runtime_write_count": len(blockers), + "operator_action_count": len(actions), + "approval_required_fixture_count": sum(1 for item in fixtures if item.get("status") == "approval_required"), + "blocked_fixture_count": sum(1 for item in fixtures if item.get("status") == "blocked_by_policy"), + "approval_required_receipt_count": sum(1 for item in previews if item.get("status") == "approval_required"), + "blocked_receipt_count": sum(1 for item in previews if item.get("status") == "blocked_by_policy"), + "approval_required_replay_count": sum(1 for item in replays if item.get("status") == "approval_required"), + "blocked_replay_count": sum(1 for item in replays if item.get("status") == "blocked_by_policy"), + "approval_required_rollback_count": sum(1 for item in rehearsals if item.get("status") == "approval_required"), + "critical_blocker_count": sum(1 for item in blockers if item.get("severity") == "critical"), + "owner_approval_received_count": 0, + "dual_approval_received_count": 0, + "dry_run_hash_verified_count": 0, + "writer_apply_count": 0, + "dry_run_execution_count": 0, + "receipt_write_count": 0, + "canonical_runtime_target_read_count": 0, + "live_query_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_writer_dry_run_fixture.py b/apps/api/tests/test_ai_agent_result_capture_writer_dry_run_fixture.py new file mode 100644 index 000000000..b29803337 --- /dev/null +++ b/apps/api/tests/test_ai_agent_result_capture_writer_dry_run_fixture.py @@ -0,0 +1,122 @@ +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_writer_dry_run_fixture import ( + load_latest_ai_agent_result_capture_writer_dry_run_fixture, +) + + +REPO_ROOT = Path(__file__).resolve().parents[3] +FIXTURE = REPO_ROOT / "docs/evaluations/ai_agent_result_capture_writer_dry_run_fixture_2026-06-14.json" + + +def test_load_latest_ai_agent_result_capture_writer_dry_run_fixture_snapshot() -> None: + data = load_latest_ai_agent_result_capture_writer_dry_run_fixture() + + assert data["schema_version"] == "ai_agent_result_capture_writer_dry_run_fixture_v1" + assert data["program_status"]["current_task_id"] == "P2-123" + assert data["program_status"]["next_task_id"] == "P2-124" + assert data["program_status"]["overall_completion_percent"] == 100 + assert data["program_status"]["read_only_mode"] is True + + rollups = data["rollups"] + assert rollups["writer_dry_run_fixture_count"] == 5 + assert rollups["receipt_preview_count"] == 5 + assert rollups["idempotency_replay_check_count"] == 5 + assert rollups["rollback_rehearsal_count"] == 5 + assert rollups["blocked_runtime_write_count"] == 6 + assert rollups["operator_action_count"] == 5 + assert rollups["approval_required_fixture_count"] == 2 + assert rollups["blocked_fixture_count"] == 1 + assert rollups["approval_required_receipt_count"] == 2 + assert rollups["blocked_receipt_count"] == 1 + assert rollups["approval_required_replay_count"] == 2 + assert rollups["blocked_replay_count"] == 1 + assert rollups["approval_required_rollback_count"] == 1 + assert rollups["critical_blocker_count"] == 3 + + assert {fixture["target_dry_run"] for fixture in data["writer_dry_run_fixtures"]} == { + "result_capture_writer_dry_run_fixture" + } + assert {fixture["fixture_only"] for fixture in data["writer_dry_run_fixtures"]} == {True} + assert {fixture["runtime_write_enabled"] for fixture in data["writer_dry_run_fixtures"]} == {False} + + zero_fields = [ + "owner_approval_received_count", + "dual_approval_received_count", + "dry_run_hash_verified_count", + "writer_apply_count", + "dry_run_execution_count", + "receipt_write_count", + "canonical_runtime_target_read_count", + "live_query_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_writer_dry_run_fixture_rejects_runtime_write_enabled(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["writer_dry_run_fixtures"][0]["runtime_write_enabled"] = True + target = tmp_path / "ai_agent_result_capture_writer_dry_run_fixture_2026-06-14.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="must not enable runtime write"): + load_latest_ai_agent_result_capture_writer_dry_run_fixture(tmp_path) + + +def test_result_capture_writer_dry_run_fixture_rejects_truth_write_flag(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["dry_run_truth"]["result_capture_write_enabled"] = True + target = tmp_path / "ai_agent_result_capture_writer_dry_run_fixture_2026-06-14.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="live read/send/write flags"): + load_latest_ai_agent_result_capture_writer_dry_run_fixture(tmp_path) + + +def test_result_capture_writer_dry_run_fixture_rejects_target_drift(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["writer_dry_run_fixtures"][0]["target_dry_run"] = "live_result_capture_writer" + target = tmp_path / "ai_agent_result_capture_writer_dry_run_fixture_2026-06-14.json" + target.write_text(json.dumps(source), encoding="utf-8") + + with pytest.raises(ValueError, match="must target result_capture_writer_dry_run_fixture"): + load_latest_ai_agent_result_capture_writer_dry_run_fixture(tmp_path) + + +def test_result_capture_writer_dry_run_fixture_rejects_rollup_drift(tmp_path: Path) -> None: + source = json.loads(FIXTURE.read_text(encoding="utf-8")) + source["rollups"]["writer_dry_run_fixture_count"] = 4 + target = tmp_path / "ai_agent_result_capture_writer_dry_run_fixture_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_writer_dry_run_fixture(tmp_path) + + +def test_result_capture_writer_dry_run_fixture_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_writer_dry_run_fixture_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_writer_dry_run_fixture(tmp_path) diff --git a/apps/api/tests/test_ai_agent_result_capture_writer_dry_run_fixture_api.py b/apps/api/tests/test_ai_agent_result_capture_writer_dry_run_fixture_api.py new file mode 100644 index 000000000..f0936b619 --- /dev/null +++ b/apps/api/tests/test_ai_agent_result_capture_writer_dry_run_fixture_api.py @@ -0,0 +1,49 @@ +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_writer_dry_run_fixture_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-writer-dry-run-fixture") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_result_capture_writer_dry_run_fixture_v1" + assert data["program_status"]["current_task_id"] == "P2-123" + assert data["program_status"]["next_task_id"] == "P2-124" + assert data["program_status"]["overall_completion_percent"] == 100 + + rollups = data["rollups"] + assert rollups["writer_dry_run_fixture_count"] == 5 + assert rollups["receipt_preview_count"] == 5 + assert rollups["idempotency_replay_check_count"] == 5 + assert rollups["rollback_rehearsal_count"] == 5 + assert rollups["blocked_runtime_write_count"] == 6 + assert rollups["operator_action_count"] == 5 + assert rollups["critical_blocker_count"] == 3 + assert rollups["owner_approval_received_count"] == 0 + assert rollups["dual_approval_received_count"] == 0 + assert rollups["dry_run_hash_verified_count"] == 0 + assert rollups["writer_apply_count"] == 0 + assert rollups["dry_run_execution_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 {fixture["target_dry_run"] for fixture in data["writer_dry_run_fixtures"]} == { + "result_capture_writer_dry_run_fixture" + } + assert {fixture["fixture_only"] for fixture in data["writer_dry_run_fixtures"]} == {True} + assert {fixture["runtime_write_enabled"] for fixture in data["writer_dry_run_fixtures"]} == {False} diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 5179c0724..64fb233f8 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5744,6 +5744,41 @@ "runtimeWriteAllowed": "正式寫入={value}" } }, + "resultCaptureWriterDryRunFixture": { + "title": "P2-123 結果寫入器 dry-run fixture", + "source": "產生 {generated};目前 {current};下一步 {next}", + "priorTitle": "前一關結果寫入器實作審查", + "truthTitle": "dry-run fixture 真相", + "metrics": { + "overall": "完成度", + "fixtures": "Dry-run fixtures", + "receipts": "Receipt previews", + "replays": "Idempotency replay", + "rollbacks": "Rollback rehearsal", + "blockers": "已阻擋寫入", + "actions": "操作選項", + "approvalRequired": "需批准", + "blocked": "阻擋", + "liveWrites": "正式寫入 / 發送" + }, + "flags": { + "implementationLoaded": "已載入實作審查={value}", + "fixtureOnly": "fixture-only={value}", + "rollback": "需要回滾演練={value}" + }, + "labels": { + "targetDryRun": "目標 dry-run:{value}", + "writerApply": "writer 套用={value}", + "dryRunExecution": "dry-run 執行={value}", + "resultWrites": "結果寫入={value}", + "gatewayWrites": "Gateway 寫入={value}", + "writer": "writer:{value}", + "fixtureOnly": "fixture-only={value}", + "runtimeWriteEnabled": "正式寫入={value}", + "liveReceiptEnabled": "正式 receipt={value}", + "liveExecutionEnabled": "正式執行={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 5179c0724..64fb233f8 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5744,6 +5744,41 @@ "runtimeWriteAllowed": "正式寫入={value}" } }, + "resultCaptureWriterDryRunFixture": { + "title": "P2-123 結果寫入器 dry-run fixture", + "source": "產生 {generated};目前 {current};下一步 {next}", + "priorTitle": "前一關結果寫入器實作審查", + "truthTitle": "dry-run fixture 真相", + "metrics": { + "overall": "完成度", + "fixtures": "Dry-run fixtures", + "receipts": "Receipt previews", + "replays": "Idempotency replay", + "rollbacks": "Rollback rehearsal", + "blockers": "已阻擋寫入", + "actions": "操作選項", + "approvalRequired": "需批准", + "blocked": "阻擋", + "liveWrites": "正式寫入 / 發送" + }, + "flags": { + "implementationLoaded": "已載入實作審查={value}", + "fixtureOnly": "fixture-only={value}", + "rollback": "需要回滾演練={value}" + }, + "labels": { + "targetDryRun": "目標 dry-run:{value}", + "writerApply": "writer 套用={value}", + "dryRunExecution": "dry-run 執行={value}", + "resultWrites": "結果寫入={value}", + "gatewayWrites": "Gateway 寫入={value}", + "writer": "writer:{value}", + "fixtureOnly": "fixture-only={value}", + "runtimeWriteEnabled": "正式寫入={value}", + "liveReceiptEnabled": "正式 receipt={value}", + "liveExecutionEnabled": "正式執行={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 6a2e3089f..714f6e44a 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 @@ -29,6 +29,7 @@ import { ShieldAlert, ShieldCheck, Target, + Undo2, } from 'lucide-react' import { useTranslations } from 'next-intl' import { GlassCard } from '@/components/ui/glass-card' @@ -69,6 +70,7 @@ import { type AiAgentOwnerApprovedResultCapturePromotionDryRunSnapshot, type AiAgentResultCapturePromotionApprovalGateSnapshot, type AiAgentResultCaptureWriteGateReviewSnapshot, + type AiAgentResultCaptureWriterDryRunFixtureSnapshot, type AiAgentResultCaptureWriterImplementationReviewSnapshot, type AiAgentOwnerApprovedFixturePromotionGateSnapshot, type AiAgentRuntimeWorkerShadowGateSnapshot, @@ -462,6 +464,7 @@ export function AutomationInventoryTab() { const [ownerApprovedResultCapturePromotionDryRun, setOwnerApprovedResultCapturePromotionDryRun] = useState(null) const [resultCaptureWriteGateReview, setResultCaptureWriteGateReview] = useState(null) const [resultCaptureWriterImplementationReview, setResultCaptureWriterImplementationReview] = useState(null) + const [resultCaptureWriterDryRunFixture, setResultCaptureWriterDryRunFixture] = useState(null) const [reportTruthActionabilityReview, setReportTruthActionabilityReview] = useState(null) const [ownerDryRunPackage, setOwnerDryRunPackage] = useState(null) const [hostStatefulInventory, setHostStatefulInventory] = useState(null) @@ -521,6 +524,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentOwnerApprovedResultCapturePromotionDryRun(), apiClient.getAiAgentResultCaptureWriteGateReview(), apiClient.getAiAgentResultCaptureWriterImplementationReview(), + apiClient.getAiAgentResultCaptureWriterDryRunFixture(), apiClient.getAiAgentReportTruthActionabilityReview(), apiClient.getAiAgentOwnerApprovedFixtureDryRun(), apiClient.getAiAgentHostStatefulVersionInventory(), @@ -579,6 +583,7 @@ export function AutomationInventoryTab() { ownerApprovedResultCapturePromotionDryRunResult, resultCaptureWriteGateReviewResult, resultCaptureWriterImplementationReviewResult, + resultCaptureWriterDryRunFixtureResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -634,6 +639,7 @@ export function AutomationInventoryTab() { setOwnerApprovedResultCapturePromotionDryRun(ownerApprovedResultCapturePromotionDryRunResult.status === 'fulfilled' ? ownerApprovedResultCapturePromotionDryRunResult.value : null) setResultCaptureWriteGateReview(resultCaptureWriteGateReviewResult.status === 'fulfilled' ? resultCaptureWriteGateReviewResult.value : null) setResultCaptureWriterImplementationReview(resultCaptureWriterImplementationReviewResult.status === 'fulfilled' ? resultCaptureWriterImplementationReviewResult.value : null) + setResultCaptureWriterDryRunFixture(resultCaptureWriterDryRunFixtureResult.status === 'fulfilled' ? resultCaptureWriterDryRunFixtureResult.value : null) setReportTruthActionabilityReview(reportTruthActionabilityReviewResult.status === 'fulfilled' ? reportTruthActionabilityReviewResult.value : null) setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null) setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null) @@ -687,6 +693,7 @@ export function AutomationInventoryTab() { ownerApprovedResultCapturePromotionDryRunResult, resultCaptureWriteGateReviewResult, resultCaptureWriterImplementationReviewResult, + resultCaptureWriterDryRunFixtureResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -1929,7 +1936,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 || !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 || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -2703,6 +2710,41 @@ export function AutomationInventoryTab() { + resultCaptureWriterImplementationReview.rollups.playbook_trust_write_count + resultCaptureWriterImplementationReview.rollups.production_write_count ) + const resultCaptureWriterDryRunOverall = resultCaptureWriterDryRunFixture.program_status.overall_completion_percent + const resultCaptureWriterDryRunFixtures = resultCaptureWriterDryRunFixture.rollups.writer_dry_run_fixture_count + const resultCaptureWriterDryRunReceipts = resultCaptureWriterDryRunFixture.rollups.receipt_preview_count + const resultCaptureWriterDryRunReplays = resultCaptureWriterDryRunFixture.rollups.idempotency_replay_check_count + const resultCaptureWriterDryRunRollbacks = resultCaptureWriterDryRunFixture.rollups.rollback_rehearsal_count + const resultCaptureWriterDryRunBlockers = resultCaptureWriterDryRunFixture.rollups.blocked_runtime_write_count + const resultCaptureWriterDryRunActions = resultCaptureWriterDryRunFixture.rollups.operator_action_count + const resultCaptureWriterDryRunApprovalRequired = ( + resultCaptureWriterDryRunFixture.rollups.approval_required_fixture_count + + resultCaptureWriterDryRunFixture.rollups.approval_required_receipt_count + + resultCaptureWriterDryRunFixture.rollups.approval_required_replay_count + + resultCaptureWriterDryRunFixture.rollups.approval_required_rollback_count + ) + const resultCaptureWriterDryRunBlocked = ( + resultCaptureWriterDryRunFixture.rollups.blocked_fixture_count + + resultCaptureWriterDryRunFixture.rollups.blocked_receipt_count + + resultCaptureWriterDryRunFixture.rollups.blocked_replay_count + + resultCaptureWriterDryRunFixture.rollups.critical_blocker_count + ) + const resultCaptureWriterDryRunLiveWrites = ( + resultCaptureWriterDryRunFixture.rollups.writer_apply_count + + resultCaptureWriterDryRunFixture.rollups.dry_run_execution_count + + resultCaptureWriterDryRunFixture.rollups.receipt_write_count + + resultCaptureWriterDryRunFixture.rollups.canonical_runtime_target_read_count + + resultCaptureWriterDryRunFixture.rollups.live_query_count + + resultCaptureWriterDryRunFixture.rollups.reviewer_queue_write_count + + resultCaptureWriterDryRunFixture.rollups.gateway_queue_write_count + + resultCaptureWriterDryRunFixture.rollups.telegram_send_count + + resultCaptureWriterDryRunFixture.rollups.bot_api_call_count + + resultCaptureWriterDryRunFixture.rollups.report_receipt_write_count + + resultCaptureWriterDryRunFixture.rollups.result_capture_write_count + + resultCaptureWriterDryRunFixture.rollups.learning_write_count + + resultCaptureWriterDryRunFixture.rollups.playbook_trust_write_count + + resultCaptureWriterDryRunFixture.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 @@ -7130,6 +7172,128 @@ export function AutomationInventoryTab() {
+
+
+
+ + + {t('resultCaptureWriterDryRunFixture.title')} + +
+ +
+ +
+ } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
+ +
+
+ {t('resultCaptureWriterDryRunFixture.priorTitle')} + + {resultCaptureWriterDryRunFixture.prior_result_capture_writer_implementation_review.readiness_note} + +
+ + + +
+
+ +
+ {t('resultCaptureWriterDryRunFixture.truthTitle')} + + {resultCaptureWriterDryRunFixture.dry_run_truth.truth_note} + +
+ + + + + + + + +
+
+
+ +
+ {resultCaptureWriterDryRunFixture.writer_dry_run_fixtures.slice(0, 5).map(fixture => ( +
+
+ + {fixture.display_name} + + +
+ + {fixture.input_fixture} → {fixture.expected_receipt_preview} + +
+ + + + +
+
+ ))} +
+ +
+ {resultCaptureWriterDryRunFixture.receipt_previews.slice(0, 5).map(receipt => ( +
+
+ + {receipt.display_name} + + +
+ + {receipt.preview_payload} + +
+ + +
+
+ ))} +
+ +
+ {resultCaptureWriterDryRunFixture.idempotency_replay_checks.slice(0, 3).map(check => ( +
+
+ + {check.display_name} + + +
+ + {check.expected_outcome} + + +
+ ))} +
+
+
diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 2802d7a29..ce3a49f5f 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -510,6 +510,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiAgentResultCaptureWriterDryRunFixture() { + const res = await fetch(`${API_BASE_URL}/agents/agent-result-capture-writer-dry-run-fixture`) + return handleResponse(res) + }, + async getAiAgentOwnerApprovedFixtureDryRun() { const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-dry-run`) return handleResponse(res) @@ -5996,6 +6001,195 @@ export interface AiAgentResultCaptureWriterImplementationReviewSnapshot { } } +export interface AiAgentResultCaptureWriterDryRunFixtureSnapshot { + schema_version: 'ai_agent_result_capture_writer_dry_run_fixture_v1' + generated_at: string + program_status: { + overall_completion_percent: number + current_priority: 'P0' | 'P1' | 'P2' | 'P3' + current_task_id: 'P2-123' + next_task_id: 'P2-124' + read_only_mode: true + runtime_authority: 'result_capture_writer_dry_run_fixture_only_no_live_write' + status_note: string + } + source_refs: string[] + prior_result_capture_writer_implementation_review: { + schema_version: string + writer_implementation_review_count: number + implementation_gate_count: number + post_implementation_verifier_plan_count: number + blocked_runtime_change_count: number + operator_action_count: number + writer_apply_count: number + implementation_applied_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 + } + dry_run_truth: { + p2_122_implementation_review_loaded: boolean + result_capture_writer_dry_run_fixture_ready: boolean + receipt_preview_ready: boolean + idempotency_replay_ready: boolean + rollback_rehearsal_ready: boolean + redaction_preview_ready: boolean + fixture_only_mode: boolean + owner_approval_required: boolean + dual_approval_required: boolean + dry_run_hash_required: boolean + idempotency_key_required: boolean + post_dry_run_verifier_required: boolean + rollback_required: boolean + writer_apply_enabled: boolean + dry_run_execution_enabled: boolean + receipt_write_enabled: boolean + canonical_runtime_target_read_enabled: boolean + live_query_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_approval_received_count: number + dual_approval_received_count: number + dry_run_hash_verified_count: number + writer_apply_count: number + dry_run_execution_count: number + receipt_write_count: number + canonical_runtime_target_read_count: number + live_query_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 + } + writer_dry_run_fixtures: Array<{ + fixture_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + target_writer: string + target_dry_run: string + status: 'ready_for_owner_review' | 'approval_required' | 'blocked_by_policy' + input_fixture: string + expected_receipt_preview: string + idempotency_key: string + blocked_live_action: string + fixture_only: true + runtime_write_enabled: false + evidence_hash: string + }> + receipt_previews: Array<{ + receipt_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + status: 'ready' | 'approval_required' | 'blocked_by_policy' + preview_payload: string + redaction_policy: string + failure_if_missing: string + live_receipt_enabled: false + }> + idempotency_replay_checks: Array<{ + check_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + status: 'ready' | 'approval_required' | 'blocked_by_policy' + expected_outcome: string + failure_if_missing: string + live_execution_enabled: false + evidence_hash: string + }> + rollback_rehearsals: Array<{ + rehearsal_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + status: 'ready' | 'approval_required' | 'blocked_by_policy' + rollback_preview: string + blocked_live_action: string + live_execution_enabled: false + }> + blocked_runtime_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: { + writer_dry_run_fixture_count: number + receipt_preview_count: number + idempotency_replay_check_count: number + rollback_rehearsal_count: number + blocked_runtime_write_count: number + operator_action_count: number + approval_required_fixture_count: number + blocked_fixture_count: number + approval_required_receipt_count: number + blocked_receipt_count: number + approval_required_replay_count: number + blocked_replay_count: number + approval_required_rollback_count: number + critical_blocker_count: number + owner_approval_received_count: number + dual_approval_received_count: number + dry_run_hash_verified_count: number + writer_apply_count: number + dry_run_execution_count: number + receipt_write_count: number + canonical_runtime_target_read_count: number + live_query_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_writer_dry_run_fixture_2026-06-14.json b/docs/evaluations/ai_agent_result_capture_writer_dry_run_fixture_2026-06-14.json new file mode 100644 index 000000000..3eb13e229 --- /dev/null +++ b/docs/evaluations/ai_agent_result_capture_writer_dry_run_fixture_2026-06-14.json @@ -0,0 +1,453 @@ +{ + "schema_version": "ai_agent_result_capture_writer_dry_run_fixture_v1", + "generated_at": "2026-06-14T00:20:00+08:00", + "program_status": { + "overall_completion_percent": 100, + "current_priority": "P2", + "current_task_id": "P2-123", + "next_task_id": "P2-124", + "read_only_mode": true, + "runtime_authority": "result_capture_writer_dry_run_fixture_only_no_live_write", + "status_note": "P2-123 只建立 result capture writer dry-run fixture、receipt preview、idempotency replay 與 rollback rehearsal;不得套用 writer 或寫入 production。" + }, + "source_refs": [ + "docs/evaluations/ai_agent_result_capture_writer_implementation_review_2026-06-13.json", + "docs/evaluations/ai_agent_result_capture_write_gate_review_2026-06-13.json", + "docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md#43-建立-result-capture-writer-implementation-review" + ], + "dry_run_truth": { + "p2_122_implementation_review_loaded": true, + "result_capture_writer_dry_run_fixture_ready": true, + "receipt_preview_ready": true, + "idempotency_replay_ready": true, + "rollback_rehearsal_ready": true, + "redaction_preview_ready": true, + "fixture_only_mode": true, + "owner_approval_required": true, + "dual_approval_required": true, + "dry_run_hash_required": true, + "idempotency_key_required": true, + "post_dry_run_verifier_required": true, + "rollback_required": true, + "writer_apply_enabled": false, + "dry_run_execution_enabled": false, + "receipt_write_enabled": false, + "canonical_runtime_target_read_enabled": false, + "live_query_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, + "dual_approval_received_count": 0, + "dry_run_hash_verified_count": 0, + "writer_apply_count": 0, + "dry_run_execution_count": 0, + "receipt_write_count": 0, + "canonical_runtime_target_read_count": 0, + "live_query_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-123 把 P2-122 的 writer implementation review 轉成 fixture-only dry-run。所有 live writer、queue、Telegram、Bot API 與 production write 仍為 0。" + }, + "writer_dry_run_fixtures": [ + { + "fixture_id": "fixture_result_capture_writer", + "display_name": "Result capture writer dry-run fixture", + "owner_agent": "openclaw", + "target_writer": "result_capture_writer", + "target_dry_run": "result_capture_writer_dry_run_fixture", + "status": "ready_for_owner_review", + "input_fixture": "redacted_incident_result_capture_input_v1", + "expected_receipt_preview": "result_capture_receipt_preview_v1", + "idempotency_key": "fixture:result_capture:incident:hash", + "blocked_live_action": "result_capture_writer_apply", + "fixture_only": true, + "runtime_write_enabled": false, + "evidence_hash": "sha256:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" + }, + { + "fixture_id": "fixture_learning_writer", + "display_name": "Learning writer dry-run fixture", + "owner_agent": "hermes", + "target_writer": "learning_writer", + "target_dry_run": "result_capture_writer_dry_run_fixture", + "status": "approval_required", + "input_fixture": "redacted_learning_draft_input_v1", + "expected_receipt_preview": "learning_receipt_preview_v1", + "idempotency_key": "fixture:learning:incident:owner", + "blocked_live_action": "learning_writer_apply", + "fixture_only": true, + "runtime_write_enabled": false, + "evidence_hash": "sha256:a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2" + }, + { + "fixture_id": "fixture_playbook_trust_writer", + "display_name": "PlayBook trust writer dry-run fixture", + "owner_agent": "nemotron", + "target_writer": "playbook_trust_writer", + "target_dry_run": "result_capture_writer_dry_run_fixture", + "status": "blocked_by_policy", + "input_fixture": "redacted_playbook_trust_delta_input_v1", + "expected_receipt_preview": "playbook_trust_receipt_preview_v1", + "idempotency_key": "fixture:playbook_trust:playbook:version", + "blocked_live_action": "playbook_trust_writer_apply", + "fixture_only": true, + "runtime_write_enabled": false, + "evidence_hash": "sha256:a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3" + }, + { + "fixture_id": "fixture_reviewer_queue_writer", + "display_name": "Reviewer queue dry-run fixture", + "owner_agent": "openclaw", + "target_writer": "reviewer_queue_writer", + "target_dry_run": "result_capture_writer_dry_run_fixture", + "status": "approval_required", + "input_fixture": "redacted_reviewer_queue_item_input_v1", + "expected_receipt_preview": "reviewer_queue_receipt_preview_v1", + "idempotency_key": "fixture:reviewer_queue:incident:owner", + "blocked_live_action": "reviewer_queue_apply", + "fixture_only": true, + "runtime_write_enabled": false, + "evidence_hash": "sha256:a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4" + }, + { + "fixture_id": "fixture_gateway_queue_writer", + "display_name": "Gateway queue dry-run fixture", + "owner_agent": "nemotron", + "target_writer": "gateway_queue_writer", + "target_dry_run": "result_capture_writer_dry_run_fixture", + "status": "ready_for_owner_review", + "input_fixture": "redacted_gateway_queue_envelope_input_v1", + "expected_receipt_preview": "gateway_queue_receipt_preview_v1", + "idempotency_key": "fixture:gateway_queue:sre:route", + "blocked_live_action": "gateway_queue_apply", + "fixture_only": true, + "runtime_write_enabled": false, + "evidence_hash": "sha256:a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5" + } + ], + "receipt_previews": [ + { + "receipt_id": "receipt_result_capture_preview", + "display_name": "Result capture receipt preview", + "owner_agent": "openclaw", + "status": "ready", + "preview_payload": "incident、decision、verifier、idempotency key、redacted evidence refs", + "redaction_policy": "只顯示紅線後欄位,不顯示原始 payload 或 secret。", + "failure_if_missing": "沒有 receipt preview 時不得進入 writer dry-run。", + "live_receipt_enabled": false + }, + { + "receipt_id": "receipt_learning_preview", + "display_name": "Learning receipt preview", + "owner_agent": "hermes", + "status": "approval_required", + "preview_payload": "KM draft、owner review、source refs、stale guard、rollback owner", + "redaction_policy": "不顯示高影響 KM 原文或內部協作內容。", + "failure_if_missing": "沒有 owner-reviewed learning receipt 時不得學習寫入。", + "live_receipt_enabled": false + }, + { + "receipt_id": "receipt_playbook_trust_preview", + "display_name": "PlayBook trust receipt preview", + "owner_agent": "nemotron", + "status": "blocked_by_policy", + "preview_payload": "trust delta、success/failure evidence、negative reinforcement、rollback criteria", + "redaction_policy": "只顯示 trust delta 摘要與 hash,不顯示 raw run payload。", + "failure_if_missing": "沒有 verifier success 與 rollback criteria 時不得更新信任分數。", + "live_receipt_enabled": false + }, + { + "receipt_id": "receipt_reviewer_queue_preview", + "display_name": "Reviewer queue receipt preview", + "owner_agent": "openclaw", + "status": "approval_required", + "preview_payload": "reviewer lane、owner route、SLA、dedupe key、manual handoff reason", + "redaction_policy": "不顯示 reviewer 內部註記與未核准欄位。", + "failure_if_missing": "沒有 reviewer queue preview 時不得寫 reviewer queue。", + "live_receipt_enabled": false + }, + { + "receipt_id": "receipt_gateway_queue_preview", + "display_name": "Gateway queue receipt preview", + "owner_agent": "nemotron", + "status": "ready", + "preview_payload": "AwoooI SRE 戰情室 route、dedupe、retry、dead-letter、no-secret policy", + "redaction_policy": "只顯示群組邏輯名稱,不顯示 chat id、token 或 Bot API payload。", + "failure_if_missing": "沒有 Gateway queue receipt preview 時不得送 Telegram。", + "live_receipt_enabled": false + } + ], + "idempotency_replay_checks": [ + { + "check_id": "replay_duplicate_noop", + "display_name": "Duplicate replay no-op", + "owner_agent": "openclaw", + "status": "ready", + "expected_outcome": "同一 idempotency key 重播必須回傳 duplicate-safe receipt preview。", + "failure_if_missing": "缺 duplicate no-op 證據時不得 dry-run writer。", + "live_execution_enabled": false, + "evidence_hash": "sha256:b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1" + }, + { + "check_id": "replay_conflict_reject", + "display_name": "Conflict replay reject", + "owner_agent": "hermes", + "status": "approval_required", + "expected_outcome": "同 key 不同 payload hash 必須被 reject,並產生人工審查原因。", + "failure_if_missing": "缺 conflict reject 證據時不得打開 writer gate。", + "live_execution_enabled": false, + "evidence_hash": "sha256:b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2" + }, + { + "check_id": "replay_retry_same_receipt", + "display_name": "Retry same receipt", + "owner_agent": "openclaw", + "status": "ready", + "expected_outcome": "retry 必須回傳同一 receipt preview,不新增 queue item。", + "failure_if_missing": "retry 行為不穩定時不得接 Gateway queue。", + "live_execution_enabled": false, + "evidence_hash": "sha256:b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3" + }, + { + "check_id": "replay_rollback_rehearsal", + "display_name": "Rollback replay rehearsal", + "owner_agent": "nemotron", + "status": "approval_required", + "expected_outcome": "rollback rehearsal 必須能指出可撤回欄位、owner 與驗證步驟。", + "failure_if_missing": "缺 rollback rehearsal 時不得允許 PlayBook trust delta。", + "live_execution_enabled": false, + "evidence_hash": "sha256:b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4" + }, + { + "check_id": "replay_gateway_queue_noop", + "display_name": "Gateway queue no-op replay", + "owner_agent": "nemotron", + "status": "blocked_by_policy", + "expected_outcome": "Gateway queue dry-run 必須維持 no-op,不呼叫 Bot API。", + "failure_if_missing": "queue no-op 不成立時不得接 Telegram 實發。", + "live_execution_enabled": false, + "evidence_hash": "sha256:b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5" + } + ], + "rollback_rehearsals": [ + { + "rehearsal_id": "rollback_result_capture_preview", + "display_name": "Result capture rollback preview", + "owner_agent": "openclaw", + "status": "ready", + "rollback_preview": "撤回 result capture preview、保留 incident link、標記 duplicate-safe rollback receipt。", + "blocked_live_action": "result_capture_writer_apply", + "live_execution_enabled": false + }, + { + "rehearsal_id": "rollback_learning_preview", + "display_name": "Learning rollback preview", + "owner_agent": "hermes", + "status": "approval_required", + "rollback_preview": "撤回 KM draft 與 stale guard,回到 owner review lane。", + "blocked_live_action": "learning_writer_apply", + "live_execution_enabled": false + }, + { + "rehearsal_id": "rollback_playbook_trust_preview", + "display_name": "PlayBook trust rollback preview", + "owner_agent": "nemotron", + "status": "blocked_by_policy", + "rollback_preview": "撤回 trust delta,要求 verifier success 與 failure evidence 重算。", + "blocked_live_action": "playbook_trust_writer_apply", + "live_execution_enabled": false + }, + { + "rehearsal_id": "rollback_reviewer_queue_preview", + "display_name": "Reviewer queue rollback preview", + "owner_agent": "openclaw", + "status": "ready", + "rollback_preview": "撤回 reviewer queue preview,保留人工接手原因與 dedupe key。", + "blocked_live_action": "reviewer_queue_apply", + "live_execution_enabled": false + }, + { + "rehearsal_id": "rollback_gateway_queue_preview", + "display_name": "Gateway queue rollback preview", + "owner_agent": "nemotron", + "status": "ready", + "rollback_preview": "撤回 Gateway queue envelope preview,不呼叫 Telegram Bot API。", + "blocked_live_action": "gateway_queue_apply", + "live_execution_enabled": false + } + ], + "blocked_runtime_writes": [ + { + "blocker_id": "blocked_result_capture_writer_apply", + "display_name": "Result capture writer apply blocked", + "severity": "high", + "status": "approval_required", + "blocked_action": "result_capture_writer_apply", + "blocked_until": "dry-run fixture, receipt preview, idempotency replay and rollback rehearsal pass", + "evidence_hash": "sha256:c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1" + }, + { + "blocker_id": "blocked_learning_writer_apply", + "display_name": "Learning writer apply blocked", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "learning_writer_apply", + "blocked_until": "KM owner accepts scoped learning draft and rollback rehearsal", + "evidence_hash": "sha256:c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2" + }, + { + "blocker_id": "blocked_playbook_trust_writer_apply", + "display_name": "PlayBook trust writer apply blocked", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "playbook_trust_writer_apply", + "blocked_until": "trust delta rollback and verifier success are approved", + "evidence_hash": "sha256:c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3" + }, + { + "blocker_id": "blocked_reviewer_queue_apply", + "display_name": "Reviewer queue apply blocked", + "severity": "high", + "status": "approval_required", + "blocked_action": "reviewer_queue_apply", + "blocked_until": "reviewer route, dedupe and TTL are approved", + "evidence_hash": "sha256:c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4" + }, + { + "blocker_id": "blocked_gateway_queue_apply", + "display_name": "Gateway queue apply blocked", + "severity": "high", + "status": "approval_required", + "blocked_action": "gateway_queue_apply", + "blocked_until": "AwoooI SRE route, no-secret envelope and no-op replay are approved", + "evidence_hash": "sha256:c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5" + }, + { + "blocker_id": "blocked_telegram_send_apply", + "display_name": "Telegram send apply blocked", + "severity": "critical", + "status": "blocked_by_policy", + "blocked_action": "telegram_send_apply", + "blocked_until": "delivery receipt gate, Bot API redaction and SRE war room route are approved", + "evidence_hash": "sha256:c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6" + } + ], + "operator_actions": [ + { + "action_id": "review_writer_dry_run_fixtures", + "display_name": "Review writer dry-run fixtures", + "owner_agent": "openclaw", + "operator_instruction": "審查 5 個 writer dry-run fixture 是否具備輸入、receipt preview、idempotency key 與 blocked live action。", + "runtime_write_allowed": false + }, + { + "action_id": "verify_receipt_preview_contract", + "display_name": "Verify receipt preview contract", + "owner_agent": "hermes", + "operator_instruction": "確認 receipt preview 不含 secret、內部協作內容或 raw runtime payload;未通過不得進入 live writer。", + "runtime_write_allowed": false + }, + { + "action_id": "replay_idempotency_fixture", + "display_name": "Replay idempotency fixture", + "owner_agent": "nemotron", + "operator_instruction": "重放 duplicate、conflict、retry 與 rollback fixture;不一致即退回 P2-122。", + "runtime_write_allowed": false + }, + { + "action_id": "rehearse_rollback_preview", + "display_name": "Rehearse rollback preview", + "owner_agent": "nemotron", + "operator_instruction": "確認 rollback preview 指出可撤回欄位、owner、驗證方式與人工接手路徑。", + "runtime_write_allowed": false + }, + { + "action_id": "promote_to_p2_124", + "display_name": "Promote to P2-124", + "owner_agent": "openclaw", + "operator_instruction": "只在 P2-123 dry-run fixture 全部通過後,推進到 P2-124;本步不執行 live write。", + "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": "前端只顯示 dry-run fixture、receipt preview、idempotency replay、rollback rehearsal、owner、狀態與紅線後 evidence hash;不得顯示內部協作內容、secret、原始 payload 或私有推理。" + }, + "rollups": { + "writer_dry_run_fixture_count": 5, + "receipt_preview_count": 5, + "idempotency_replay_check_count": 5, + "rollback_rehearsal_count": 5, + "blocked_runtime_write_count": 6, + "operator_action_count": 5, + "approval_required_fixture_count": 2, + "blocked_fixture_count": 1, + "approval_required_receipt_count": 2, + "blocked_receipt_count": 1, + "approval_required_replay_count": 2, + "blocked_replay_count": 1, + "approval_required_rollback_count": 1, + "critical_blocker_count": 3, + "owner_approval_received_count": 0, + "dual_approval_received_count": 0, + "dry_run_hash_verified_count": 0, + "writer_apply_count": 0, + "dry_run_execution_count": 0, + "receipt_write_count": 0, + "canonical_runtime_target_read_count": 0, + "live_query_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 + }, + "prior_result_capture_writer_implementation_review": { + "schema_version": "ai_agent_result_capture_writer_implementation_review_v1", + "writer_implementation_review_count": 5, + "implementation_gate_count": 5, + "post_implementation_verifier_plan_count": 5, + "blocked_runtime_change_count": 6, + "operator_action_count": 5, + "writer_apply_count": 0, + "implementation_applied_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-122 已正式驗證 writer implementation review;P2-123 只建立 fixture-only dry-run、receipt preview、idempotency replay 與 rollback rehearsal,仍不套用 writer。" + } +} diff --git a/docs/schemas/ai_agent_result_capture_writer_dry_run_fixture_v1.schema.json b/docs/schemas/ai_agent_result_capture_writer_dry_run_fixture_v1.schema.json new file mode 100644 index 000000000..22c755059 --- /dev/null +++ b/docs/schemas/ai_agent_result_capture_writer_dry_run_fixture_v1.schema.json @@ -0,0 +1,231 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.local/schemas/ai_agent_result_capture_writer_dry_run_fixture_v1.schema.json", + "title": "AI Agent Result Capture Writer Dry Run Fixture", + "type": "object", + "additionalProperties": true, + "required": [ + "schema_version", + "generated_at", + "program_status", + "prior_result_capture_writer_implementation_review", + "dry_run_truth", + "writer_dry_run_fixtures", + "receipt_previews", + "idempotency_replay_checks", + "rollback_rehearsals", + "blocked_runtime_writes", + "operator_actions", + "display_redaction_contract", + "rollups" + ], + "properties": { + "schema_version": { + "const": "ai_agent_result_capture_writer_dry_run_fixture_v1" + }, + "program_status": { + "type": "object", + "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-123" + }, + "next_task_id": { + "const": "P2-124" + }, + "read_only_mode": { + "const": true + }, + "runtime_authority": { + "const": "result_capture_writer_dry_run_fixture_only_no_live_write" + } + } + }, + "dry_run_truth": { + "type": "object", + "required": [ + "p2_122_implementation_review_loaded", + "fixture_only_mode", + "receipt_preview_ready", + "idempotency_replay_ready", + "rollback_rehearsal_ready", + "writer_apply_enabled", + "dry_run_execution_enabled", + "receipt_write_enabled", + "result_capture_write_enabled", + "learning_write_enabled", + "playbook_trust_write_enabled", + "gateway_queue_write_enabled", + "telegram_send_enabled" + ], + "properties": { + "p2_122_implementation_review_loaded": { + "const": true + }, + "fixture_only_mode": { + "const": true + }, + "receipt_preview_ready": { + "const": true + }, + "idempotency_replay_ready": { + "const": true + }, + "rollback_rehearsal_ready": { + "const": true + }, + "writer_apply_enabled": { + "const": false + }, + "dry_run_execution_enabled": { + "const": false + }, + "receipt_write_enabled": { + "const": false + }, + "result_capture_write_enabled": { + "const": false + }, + "learning_write_enabled": { + "const": false + }, + "playbook_trust_write_enabled": { + "const": false + }, + "gateway_queue_write_enabled": { + "const": false + }, + "telegram_send_enabled": { + "const": false + }, + "production_write_enabled": { + "const": false + } + } + }, + "writer_dry_run_fixtures": { + "type": "array", + "minItems": 5, + "maxItems": 5 + }, + "receipt_previews": { + "type": "array", + "minItems": 5, + "maxItems": 5 + }, + "idempotency_replay_checks": { + "type": "array", + "minItems": 5, + "maxItems": 5 + }, + "rollback_rehearsals": { + "type": "array", + "minItems": 5, + "maxItems": 5 + }, + "blocked_runtime_writes": { + "type": "array", + "minItems": 6, + "maxItems": 6 + }, + "operator_actions": { + "type": "array", + "minItems": 5, + "maxItems": 5 + }, + "display_redaction_contract": { + "type": "object", + "properties": { + "redaction_required": { + "const": true + }, + "secret_value_display_allowed": { + "const": false + }, + "internal_collaboration_content_display_allowed": { + "const": false + } + } + }, + "rollups": { + "type": "object", + "required": [ + "writer_dry_run_fixture_count", + "receipt_preview_count", + "idempotency_replay_check_count", + "rollback_rehearsal_count", + "blocked_runtime_write_count", + "operator_action_count", + "writer_apply_count", + "dry_run_execution_count", + "receipt_write_count", + "result_capture_write_count", + "learning_write_count", + "playbook_trust_write_count", + "gateway_queue_write_count", + "telegram_send_count" + ], + "properties": { + "writer_dry_run_fixture_count": { + "const": 5 + }, + "receipt_preview_count": { + "const": 5 + }, + "idempotency_replay_check_count": { + "const": 5 + }, + "rollback_rehearsal_count": { + "const": 5 + }, + "blocked_runtime_write_count": { + "const": 6 + }, + "operator_action_count": { + "const": 5 + }, + "writer_apply_count": { + "const": 0 + }, + "dry_run_execution_count": { + "const": 0 + }, + "receipt_write_count": { + "const": 0 + }, + "result_capture_write_count": { + "const": 0 + }, + "learning_write_count": { + "const": 0 + }, + "playbook_trust_write_count": { + "const": 0 + }, + "gateway_queue_write_count": { + "const": 0 + }, + "telegram_send_count": { + "const": 0 + }, + "production_write_count": { + "const": 0 + } + } + } + } +}