From 9ffcca737d42fa6cb4b96074a901c92622712a34 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 11 Jun 2026 21:04:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(governance):=20=E6=96=B0=E5=A2=9E=20Redis?= =?UTF-8?q?=20dry-run=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/api/v1/agents.py | 30 ++ .../services/ai_agent_redis_dry_run_gate.py | 183 ++++++++++++ ...est_ai_agent_interaction_learning_proof.py | 14 +- ...ai_agent_interaction_learning_proof_api.py | 8 +- ..._ai_agent_proactive_operations_contract.py | 6 +- ...agent_proactive_operations_contract_api.py | 6 +- .../tests/test_ai_agent_redis_dry_run_gate.py | 177 ++++++++++++ .../test_ai_agent_redis_dry_run_gate_api.py | 32 +++ apps/web/messages/en.json | 38 +++ apps/web/messages/zh-TW.json | 38 +++ .../tabs/automation-inventory-tab.tsx | 155 ++++++++++- apps/web/src/lib/api-client.ts | 98 +++++++ docs/LOGBOOK.md | 30 ++ ...AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md | 9 +- ...T_INTERACTION_LEARNING_PROOF_2026-06-11.md | 13 +- ...I_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md | 13 +- ...interaction_learning_proof_2026-06-11.json | 42 ++- ...active_operations_contract_2026-06-11.json | 263 ++++++++++++++---- ...i_agent_redis_dry_run_gate_2026-06-11.json | 240 ++++++++++++++++ ...ai_agent_redis_dry_run_gate_v1.schema.json | 191 +++++++++++++ ...-04-15-MASTER-ai-autonomous-flywheel-v2.md | 32 ++- 21 files changed, 1506 insertions(+), 112 deletions(-) create mode 100644 apps/api/src/services/ai_agent_redis_dry_run_gate.py create mode 100644 apps/api/tests/test_ai_agent_redis_dry_run_gate.py create mode 100644 apps/api/tests/test_ai_agent_redis_dry_run_gate_api.py create mode 100644 docs/evaluations/ai_agent_redis_dry_run_gate_2026-06-11.json create mode 100644 docs/schemas/ai_agent_redis_dry_run_gate_v1.schema.json diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 26437d136..b8f5f84be 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -70,6 +70,9 @@ from src.services.ai_agent_live_read_model_gate import ( from src.services.ai_agent_proactive_operations_contract import ( load_latest_ai_agent_proactive_operations_contract, ) +from src.services.ai_agent_redis_dry_run_gate import ( + load_latest_ai_agent_redis_dry_run_gate, +) from src.services.ai_agent_telegram_action_required_digest_policy import ( load_latest_ai_agent_telegram_action_required_digest_policy, ) @@ -632,6 +635,33 @@ async def get_agent_live_read_model_gate() -> dict[str, Any]: ) from exc +@router.get( + "/agent-redis-dry-run-gate", + response_model=dict[str, Any], + summary="取得 AI Agent Redis dry-run gate", + description=( + "讀取最新已提交的 Redis Streams consumer group dry-run、handoff envelope、" + "ack / dead-letter / replay gate;此端點不連 Redis、不建立 consumer group、不 XADD、" + "不 XREADGROUP、不 ACK、不寫 dead-letter、不 replay、不送 Telegram、不做 learning writeback。" + ), +) +async def get_agent_redis_dry_run_gate() -> dict[str, Any]: + """Return the latest read-only AI Agent Redis dry-run gate snapshot.""" + try: + return await asyncio.to_thread(load_latest_ai_agent_redis_dry_run_gate) + 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_redis_dry_run_gate_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent Redis dry-run gate 無效", + ) from exc + + @router.get( "/agent-proactive-operations-contract", response_model=dict[str, Any], diff --git a/apps/api/src/services/ai_agent_redis_dry_run_gate.py b/apps/api/src/services/ai_agent_redis_dry_run_gate.py new file mode 100644 index 000000000..7b9347761 --- /dev/null +++ b/apps/api/src/services/ai_agent_redis_dry_run_gate.py @@ -0,0 +1,183 @@ +""" +AI Agent Redis dry-run gate snapshot. + +Loads the latest committed, read-only P2-403C gate for Redis Streams +consumer group dry-run, handoff envelopes, ack/dead-letter decisions, and +replay idempotency. This module never connects to Redis, creates consumer +groups, writes queues, sends Telegram messages, or performs learning writeback. +""" + +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_redis_dry_run_gate_*.json" +_SCHEMA_VERSION = "ai_agent_redis_dry_run_gate_v1" + + +def load_latest_ai_agent_redis_dry_run_gate( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed AI Agent Redis dry-run gate snapshot.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError( + f"no AI Agent Redis dry-run 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") + _require_schema(payload, str(latest)) + _require_read_only_boundaries(payload, str(latest)) + _require_fixture_contract(payload, str(latest)) + _require_handoff_safety(payload, str(latest)) + _require_rollup_consistency(payload, str(latest)) + return payload + + +def _require_schema(payload: dict[str, Any], label: str) -> None: + actual = payload.get("schema_version") + if actual != _SCHEMA_VERSION: + raise ValueError(f"{label}: expected schema_version={_SCHEMA_VERSION}, got {actual!r}") + status = payload.get("program_status") or {} + if status.get("read_only_mode") is not True: + raise ValueError(f"{label}: program_status.read_only_mode must be true") + if status.get("runtime_authority") != "dry_run_contract_only_no_redis_runtime": + raise ValueError(f"{label}: runtime_authority must stay dry_run_contract_only_no_redis_runtime") + + +def _require_read_only_boundaries(payload: dict[str, Any], label: str) -> None: + boundaries = payload.get("approval_boundaries") or {} + enabled = sorted(key for key, value in boundaries.items() if value is not False) + if enabled: + raise ValueError(f"{label}: approval boundaries must remain false: {enabled}") + + truth = payload.get("dry_run_truth") or {} + false_flags = { + "redis_connection_allowed", + "consumer_group_created", + "xadd_allowed", + "xreadgroup_allowed", + "ack_allowed", + "dead_letter_write_allowed", + "replay_runtime_allowed", + "telegram_send_allowed", + "learning_writeback_allowed", + } + unsafe = sorted(flag for flag in false_flags if truth.get(flag) is not False) + if unsafe: + raise ValueError(f"{label}: dry-run truth flags must remain false: {unsafe}") + + zero_counts = { + "live_dry_run_event_count", + "live_ack_count", + "live_dead_letter_count", + "live_replay_count", + } + non_zero = sorted(key for key in zero_counts if truth.get(key) != 0) + if non_zero: + raise ValueError(f"{label}: live dry-run counts must remain zero: {non_zero}") + + +def _require_fixture_contract(payload: dict[str, Any], label: str) -> None: + contract = payload.get("consumer_group_dry_run_contract") or {} + if contract.get("fixture_only") is not True: + raise ValueError(f"{label}: dry-run contract must stay fixture_only") + if contract.get("redis_network_call_allowed") is not False: + raise ValueError(f"{label}: Redis network calls must remain blocked") + if not contract.get("required_fixture_fields"): + raise ValueError(f"{label}: required_fixture_fields must not be empty") + + +def _require_handoff_safety(payload: dict[str, Any], label: str) -> None: + envelope = payload.get("handoff_envelope_contract") or {} + required_fields = set(envelope.get("required_fields") or []) + required_minimum = { + "event_id", + "trace_id", + "session_id", + "incident_id", + "from_agent", + "to_agent", + "handoff_type", + "redacted_evidence_ref", + "idempotency_key", + } + missing = sorted(required_minimum - required_fields) + if missing: + raise ValueError(f"{label}: handoff envelope missing required fields: {missing}") + if envelope.get("redacted_evidence_required") is not True: + raise ValueError(f"{label}: redacted evidence refs must be required") + if envelope.get("idempotency_key_required") is not True: + raise ValueError(f"{label}: idempotency key must be required") + + ack_contract = payload.get("ack_dead_letter_replay_contract") or {} + for flag in ("ack_requires_verifier", "dead_letter_requires_reason", "replay_requires_idempotency"): + if ack_contract.get(flag) is not True: + raise ValueError(f"{label}: {flag} must be true") + if ack_contract.get("runtime_replay_allowed") is not False: + raise ValueError(f"{label}: runtime replay must remain blocked") + + redaction = payload.get("display_redaction_contract") or {} + if redaction.get("redaction_required") is not True: + raise ValueError(f"{label}: frontend redaction must be required") + for flag in ("raw_payload_display_allowed", "private_reasoning_display_allowed", "secret_value_display_allowed"): + if redaction.get(flag) is not False: + raise ValueError(f"{label}: {flag} must remain false") + + +def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None: + rollups = payload.get("rollups") or {} + steps = payload.get("dry_run_steps") or [] + lanes = payload.get("handoff_lanes") or [] + envelope = payload.get("handoff_envelope_contract") or {} + truth = payload.get("dry_run_truth") or {} + expected_counts = { + "source_ref_count": len(payload.get("source_refs") or []), + "dry_run_step_count": len(steps), + "handoff_lane_count": len(lanes), + "blocked_runtime_action_count": len({step.get("blocked_runtime_action") for step in steps}), + "required_handoff_field_count": len(envelope.get("required_fields") or []), + "forbidden_field_count": len(envelope.get("forbidden_fields") or []), + } + mismatched = { + key: {"expected": expected, "actual": rollups.get(key)} + for key, expected in expected_counts.items() + if rollups.get(key) != expected + } + if mismatched: + raise ValueError(f"{label}: rollup counts must match payload sections: {mismatched}") + + contract_ready = sorted( + step.get("step_id") for step in steps if step.get("status") == "contract_ready" + ) + if sorted(rollups.get("contract_ready_step_ids") or []) != contract_ready: + raise ValueError(f"{label}: rollups.contract_ready_step_ids mismatch") + + approval_required = sorted( + step.get("step_id") for step in steps if step.get("status") == "approval_required" + ) + if sorted(rollups.get("approval_required_step_ids") or []) != approval_required: + raise ValueError(f"{label}: rollups.approval_required_step_ids mismatch") + + live_total = sum( + int(truth.get(key) or 0) + for key in ( + "live_dry_run_event_count", + "live_ack_count", + "live_dead_letter_count", + "live_replay_count", + ) + ) + if rollups.get("live_truth_count_total") != live_total: + raise ValueError(f"{label}: rollups.live_truth_count_total mismatch") diff --git a/apps/api/tests/test_ai_agent_interaction_learning_proof.py b/apps/api/tests/test_ai_agent_interaction_learning_proof.py index fe0482089..f01bd2802 100644 --- a/apps/api/tests/test_ai_agent_interaction_learning_proof.py +++ b/apps/api/tests/test_ai_agent_interaction_learning_proof.py @@ -13,9 +13,9 @@ def test_load_latest_ai_agent_interaction_learning_proof_reads_committed_snapsho data = load_latest_ai_agent_interaction_learning_proof() assert data["schema_version"] == "ai_agent_interaction_learning_proof_v1" - assert data["program_status"]["overall_completion_percent"] == 55 - assert data["program_status"]["current_task_id"] == "P2-403B" - assert data["program_status"]["next_task_id"] == "P2-403C" + assert data["program_status"]["overall_completion_percent"] == 60 + assert data["program_status"]["current_task_id"] == "P2-403C" + assert data["program_status"]["next_task_id"] == "P2-403D" assert data["program_status"]["read_only_mode"] is True assert data["program_status"]["runtime_authority"] == "proof_surface_only_no_live_worker" assert data["live_truth"]["runtime_loop_enabled"] is False @@ -32,8 +32,6 @@ def test_load_latest_ai_agent_interaction_learning_proof_reads_committed_snapsho assert data["rollups"]["runtime_gate_count"] == len(data["runtime_gates"]) == 5 assert data["rollups"]["live_signal_count"] == 0 assert set(data["rollups"]["live_pending_level_ids"]) == { - "message_bus_receipts", - "handoff_receipts", "learning_growth", "telegram_receipts", } @@ -97,10 +95,10 @@ def _snapshot() -> dict: "schema_version": "ai_agent_interaction_learning_proof_v1", "generated_at": "2026-06-11T23:20:00+08:00", "program_status": { - "overall_completion_percent": 55, + "overall_completion_percent": 60, "current_priority": "P2", - "current_task_id": "P2-403B", - "next_task_id": "P2-403C", + "current_task_id": "P2-403C", + "next_task_id": "P2-403D", "read_only_mode": True, "runtime_authority": "proof_surface_only_no_live_worker", }, diff --git a/apps/api/tests/test_ai_agent_interaction_learning_proof_api.py b/apps/api/tests/test_ai_agent_interaction_learning_proof_api.py index 4afe03a5c..4e745c795 100644 --- a/apps/api/tests/test_ai_agent_interaction_learning_proof_api.py +++ b/apps/api/tests/test_ai_agent_interaction_learning_proof_api.py @@ -16,9 +16,9 @@ def test_ai_agent_interaction_learning_proof_endpoint_returns_committed_snapshot assert response.status_code == 200 data = response.json() assert data["schema_version"] == "ai_agent_interaction_learning_proof_v1" - assert data["program_status"]["overall_completion_percent"] == 55 - assert data["program_status"]["current_task_id"] == "P2-403B" - assert data["program_status"]["next_task_id"] == "P2-403C" + assert data["program_status"]["overall_completion_percent"] == 60 + assert data["program_status"]["current_task_id"] == "P2-403C" + assert data["program_status"]["next_task_id"] == "P2-403D" assert data["program_status"]["read_only_mode"] is True assert data["live_truth"]["runtime_loop_enabled"] is False assert data["live_truth"]["active_live_agent_sessions"] == 0 @@ -27,7 +27,7 @@ def test_ai_agent_interaction_learning_proof_endpoint_returns_committed_snapshot assert data["approval_boundaries"]["conversation_transcript_display_allowed"] is False assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False assert data["rollups"]["proof_level_count"] == 7 - assert data["rollups"]["contract_ready_level_count"] == 3 + assert data["rollups"]["contract_ready_level_count"] == 5 assert data["rollups"]["signal_count"] == 9 assert data["rollups"]["live_signal_count"] == 0 assert data["rollups"]["operator_surface_count"] == 5 diff --git a/apps/api/tests/test_ai_agent_proactive_operations_contract.py b/apps/api/tests/test_ai_agent_proactive_operations_contract.py index aa35d31a1..8a33422ab 100644 --- a/apps/api/tests/test_ai_agent_proactive_operations_contract.py +++ b/apps/api/tests/test_ai_agent_proactive_operations_contract.py @@ -14,8 +14,8 @@ def test_load_latest_ai_agent_proactive_operations_contract_reads_committed_snap assert data["schema_version"] == "ai_agent_proactive_operations_contract_v1" assert data["program_status"]["overall_completion_percent"] == 100 - assert data["program_status"]["current_task_id"] == "P2-403B" - assert data["program_status"]["next_task_id"] == "P2-403C" + assert data["program_status"]["current_task_id"] == "P2-403C" + assert data["program_status"]["next_task_id"] == "P2-403D" assert data["program_status"]["read_only_mode"] is True assert data["program_status"]["runtime_authority"] == "contract_only_no_version_or_runtime_update" assert data["approval_boundaries"]["runtime_version_update_allowed"] is False @@ -25,7 +25,7 @@ def test_load_latest_ai_agent_proactive_operations_contract_reads_committed_snap assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False assert data["rollups"]["version_domain_count"] == len(data["version_lifecycle_domains"]) == 12 assert data["rollups"]["delegable_capability_count"] == len(data["delegable_capabilities"]) == 24 - assert data["rollups"]["rollout_task_count"] == len(data["rollout_tasks"]) == 9 + assert data["rollups"]["rollout_task_count"] == len(data["rollout_tasks"]) == 10 assert data["rollups"]["auto_execute_allowed_count"] == 0 assert any(domain["domain_id"] == "ai_agents_models" for domain in data["version_lifecycle_domains"]) assert any( diff --git a/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py b/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py index cfd4ae6cc..1325cf146 100644 --- a/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py +++ b/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py @@ -17,8 +17,8 @@ def test_ai_agent_proactive_operations_contract_endpoint_returns_committed_snaps data = response.json() assert data["schema_version"] == "ai_agent_proactive_operations_contract_v1" assert data["program_status"]["overall_completion_percent"] == 100 - assert data["program_status"]["current_task_id"] == "P2-403B" - assert data["program_status"]["next_task_id"] == "P2-403C" + assert data["program_status"]["current_task_id"] == "P2-403C" + assert data["program_status"]["next_task_id"] == "P2-403D" assert data["program_status"]["read_only_mode"] is True assert data["approval_boundaries"]["runtime_version_update_allowed"] is False assert data["approval_boundaries"]["package_upgrade_allowed"] is False @@ -26,7 +26,7 @@ def test_ai_agent_proactive_operations_contract_endpoint_returns_committed_snaps assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False assert data["rollups"]["version_domain_count"] == 12 assert data["rollups"]["delegable_capability_count"] == 24 - assert data["rollups"]["rollout_task_count"] == 9 + assert data["rollups"]["rollout_task_count"] == 10 assert data["rollups"]["auto_execute_allowed_count"] == 0 assert any(domain["domain_id"] == "host_os_packages" for domain in data["version_lifecycle_domains"]) assert any( diff --git a/apps/api/tests/test_ai_agent_redis_dry_run_gate.py b/apps/api/tests/test_ai_agent_redis_dry_run_gate.py new file mode 100644 index 000000000..56f349be5 --- /dev/null +++ b/apps/api/tests/test_ai_agent_redis_dry_run_gate.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +import json + +import pytest + +from src.services.ai_agent_redis_dry_run_gate import ( + load_latest_ai_agent_redis_dry_run_gate, +) + + +def test_load_latest_ai_agent_redis_dry_run_gate_reads_committed_snapshot(): + data = load_latest_ai_agent_redis_dry_run_gate() + + assert data["schema_version"] == "ai_agent_redis_dry_run_gate_v1" + assert data["program_status"]["overall_completion_percent"] == 60 + assert data["program_status"]["current_task_id"] == "P2-403C" + assert data["program_status"]["next_task_id"] == "P2-403D" + assert data["program_status"]["read_only_mode"] is True + assert data["program_status"]["runtime_authority"] == "dry_run_contract_only_no_redis_runtime" + assert data["dry_run_truth"]["redis_connection_allowed"] is False + assert data["dry_run_truth"]["consumer_group_created"] is False + assert data["dry_run_truth"]["live_dry_run_event_count"] == 0 + assert data["consumer_group_dry_run_contract"]["fixture_only"] is True + assert data["consumer_group_dry_run_contract"]["redis_network_call_allowed"] is False + assert data["handoff_envelope_contract"]["redacted_evidence_required"] is True + assert data["handoff_envelope_contract"]["idempotency_key_required"] is True + assert data["ack_dead_letter_replay_contract"]["runtime_replay_allowed"] is False + assert data["approval_boundaries"]["xadd_allowed"] is False + assert data["display_redaction_contract"]["raw_payload_display_allowed"] is False + assert data["rollups"]["dry_run_step_count"] == len(data["dry_run_steps"]) == 5 + assert data["rollups"]["handoff_lane_count"] == len(data["handoff_lanes"]) == 3 + assert data["rollups"]["live_truth_count_total"] == 0 + + +def test_load_latest_ai_agent_redis_dry_run_gate_rejects_redis_runtime(tmp_path): + snapshot = _snapshot() + snapshot["approval_boundaries"]["xadd_allowed"] = True + (tmp_path / "ai_agent_redis_dry_run_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="approval boundaries"): + load_latest_ai_agent_redis_dry_run_gate(tmp_path) + + +def test_load_latest_ai_agent_redis_dry_run_gate_rejects_network_call(tmp_path): + snapshot = _snapshot() + snapshot["consumer_group_dry_run_contract"]["redis_network_call_allowed"] = True + (tmp_path / "ai_agent_redis_dry_run_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="Redis network calls"): + load_latest_ai_agent_redis_dry_run_gate(tmp_path) + + +def test_load_latest_ai_agent_redis_dry_run_gate_rejects_missing_idempotency(tmp_path): + snapshot = _snapshot() + snapshot["handoff_envelope_contract"]["required_fields"].remove("idempotency_key") + (tmp_path / "ai_agent_redis_dry_run_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="missing required fields"): + load_latest_ai_agent_redis_dry_run_gate(tmp_path) + + +def test_load_latest_ai_agent_redis_dry_run_gate_rejects_rollup_mismatch(tmp_path): + snapshot = _snapshot() + snapshot["rollups"]["dry_run_step_count"] = 99 + (tmp_path / "ai_agent_redis_dry_run_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="rollup counts"): + load_latest_ai_agent_redis_dry_run_gate(tmp_path) + + +def _snapshot() -> dict: + return { + "schema_version": "ai_agent_redis_dry_run_gate_v1", + "generated_at": "2026-06-11T23:58:00+08:00", + "program_status": { + "overall_completion_percent": 60, + "current_task_id": "P2-403C", + "next_task_id": "P2-403D", + "read_only_mode": True, + "runtime_authority": "dry_run_contract_only_no_redis_runtime", + }, + "source_refs": ["test"], + "dry_run_truth": { + "redis_connection_allowed": False, + "consumer_group_created": False, + "xadd_allowed": False, + "xreadgroup_allowed": False, + "ack_allowed": False, + "dead_letter_write_allowed": False, + "replay_runtime_allowed": False, + "telegram_send_allowed": False, + "learning_writeback_allowed": False, + "live_dry_run_event_count": 0, + "live_ack_count": 0, + "live_dead_letter_count": 0, + "live_replay_count": 0, + }, + "consumer_group_dry_run_contract": { + "fixture_only": True, + "redis_network_call_allowed": False, + "required_fixture_fields": ["event_id", "idempotency_key"], + }, + "handoff_envelope_contract": { + "required_fields": [ + "event_id", + "trace_id", + "session_id", + "incident_id", + "from_agent", + "to_agent", + "handoff_type", + "redacted_evidence_ref", + "idempotency_key", + ], + "forbidden_fields": ["secret_value", "raw_prompt"], + "redacted_evidence_required": True, + "idempotency_key_required": True, + }, + "ack_dead_letter_replay_contract": { + "ack_requires_verifier": True, + "dead_letter_requires_reason": True, + "replay_requires_idempotency": True, + "runtime_replay_allowed": False, + }, + "dry_run_steps": [ + { + "step_id": "fixture_envelope_validation", + "display_name": "Fixture", + "owner_agent": "hermes", + "status": "contract_ready", + "operator_signal": "測試。", + "blocked_runtime_action": "redis_xadd", + } + ], + "handoff_lanes": [ + { + "lane_id": "diagnosis_to_rule_review", + "display_name": "診斷", + "from_agent": "openclaw", + "to_agent": "hermes", + "status": "contract_ready", + "required_evidence": "測試。", + } + ], + "approval_boundaries": {"xadd_allowed": False}, + "display_redaction_contract": { + "redaction_required": True, + "raw_payload_display_allowed": False, + "private_reasoning_display_allowed": False, + "secret_value_display_allowed": False, + "forbidden_frontend_content": ["未脫敏 payload"], + }, + "rollups": { + "source_ref_count": 1, + "dry_run_step_count": 1, + "handoff_lane_count": 1, + "contract_ready_step_ids": ["fixture_envelope_validation"], + "approval_required_step_ids": [], + "blocked_runtime_action_count": 1, + "required_handoff_field_count": 9, + "forbidden_field_count": 2, + "live_truth_count_total": 0, + }, + } diff --git a/apps/api/tests/test_ai_agent_redis_dry_run_gate_api.py b/apps/api/tests/test_ai_agent_redis_dry_run_gate_api.py new file mode 100644 index 000000000..1f4f4a63d --- /dev/null +++ b/apps/api/tests/test_ai_agent_redis_dry_run_gate_api.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1.agents import router + + +def test_ai_agent_redis_dry_run_gate_endpoint_returns_committed_snapshot(): + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get("/api/v1/agents/agent-redis-dry-run-gate") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_redis_dry_run_gate_v1" + assert data["program_status"]["overall_completion_percent"] == 60 + assert data["program_status"]["current_task_id"] == "P2-403C" + assert data["program_status"]["next_task_id"] == "P2-403D" + assert data["dry_run_truth"]["redis_connection_allowed"] is False + assert data["dry_run_truth"]["consumer_group_created"] is False + assert data["dry_run_truth"]["live_dry_run_event_count"] == 0 + assert data["consumer_group_dry_run_contract"]["fixture_only"] is True + assert data["consumer_group_dry_run_contract"]["redis_network_call_allowed"] is False + assert data["ack_dead_letter_replay_contract"]["runtime_replay_allowed"] is False + assert data["approval_boundaries"]["consumer_group_creation_allowed"] is False + assert data["approval_boundaries"]["telegram_send_allowed"] is False + assert data["rollups"]["dry_run_step_count"] == 5 + assert data["rollups"]["handoff_lane_count"] == 3 + assert data["rollups"]["live_truth_count_total"] == 0 diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 86ac2094e..f5d90d92f 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -3126,6 +3126,44 @@ "ready": "就緒" } }, + "redisDryRunGate": { + "title": "P2-403C Redis Dry-run Gate", + "source": "{generated} · {current} → {next}", + "consumerTitle": "Consumer group 乾跑契約", + "consumerDetail": "{group} · {namespace} · fixture fields {fields}", + "envelopeTitle": "Handoff envelope", + "redactionTitle": "Dry-run 顯示紅線", + "metrics": { + "overall": "P2-403C 進度", + "steps": "Dry-run steps", + "lanes": "Handoff lanes", + "approval": "需批准 steps", + "blocked": "阻擋 runtime", + "live": "Live 筆數" + }, + "flags": { + "fixture": "Fixture only: {value}", + "network": "Redis network: {value}", + "groupCreated": "Consumer group created: {value}" + }, + "labels": { + "requiredFields": "必填欄位 {count}", + "forbiddenFields": "禁止欄位 {count}", + "idempotency": "idempotency key: {value}" + }, + "agents": { + "openclaw": "OpenClaw", + "hermes": "Hermes", + "nemotron": "NemoTron" + }, + "statuses": { + "contract_ready": "契約已定義", + "approval_required": "需批准", + "blocked_by_runtime_gate": "被 runtime gate 阻擋", + "blocked": "阻擋", + "ready": "就緒" + } + }, "proactiveOperations": { "title": "可委派能力與版本治理", "source": "{generated} · {current} → {next}", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 86ac2094e..f5d90d92f 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -3126,6 +3126,44 @@ "ready": "就緒" } }, + "redisDryRunGate": { + "title": "P2-403C Redis Dry-run Gate", + "source": "{generated} · {current} → {next}", + "consumerTitle": "Consumer group 乾跑契約", + "consumerDetail": "{group} · {namespace} · fixture fields {fields}", + "envelopeTitle": "Handoff envelope", + "redactionTitle": "Dry-run 顯示紅線", + "metrics": { + "overall": "P2-403C 進度", + "steps": "Dry-run steps", + "lanes": "Handoff lanes", + "approval": "需批准 steps", + "blocked": "阻擋 runtime", + "live": "Live 筆數" + }, + "flags": { + "fixture": "Fixture only: {value}", + "network": "Redis network: {value}", + "groupCreated": "Consumer group created: {value}" + }, + "labels": { + "requiredFields": "必填欄位 {count}", + "forbiddenFields": "禁止欄位 {count}", + "idempotency": "idempotency key: {value}" + }, + "agents": { + "openclaw": "OpenClaw", + "hermes": "Hermes", + "nemotron": "NemoTron" + }, + "statuses": { + "contract_ready": "契約已定義", + "approval_required": "需批准", + "blocked_by_runtime_gate": "被 runtime gate 阻擋", + "blocked": "阻擋", + "ready": "就緒" + } + }, "proactiveOperations": { "title": "可委派能力與版本治理", "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 bbd5bf9c1..d17b8a72e 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 @@ -40,6 +40,7 @@ import { type AiAgentInteractionLearningProofSnapshot, type AiAgentLiveReadModelGateSnapshot, type AiAgentProactiveOperationsContractSnapshot, + type AiAgentRedisDryRunGateSnapshot, type AiProviderRouteMatrixSnapshot, type AiAgentAutomationBacklogSnapshot, type AiAgentAutomationInventorySnapshot, @@ -320,6 +321,7 @@ export function AutomationInventoryTab() { const [proactiveOperations, setProactiveOperations] = useState(null) const [interactionLearningProof, setInteractionLearningProof] = useState(null) const [liveReadModelGate, setLiveReadModelGate] = useState(null) + const [redisDryRunGate, setRedisDryRunGate] = useState(null) const [hostStatefulInventory, setHostStatefulInventory] = useState(null) const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState(null) const [serviceHealthNotificationPolicy, setServiceHealthNotificationPolicy] = useState(null) @@ -343,6 +345,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentProactiveOperationsContract(), apiClient.getAiAgentInteractionLearningProof(), apiClient.getAiAgentLiveReadModelGate(), + apiClient.getAiAgentRedisDryRunGate(), apiClient.getAiAgentHostStatefulVersionInventory(), apiClient.getServiceHealthGapMatrix(), apiClient.getServiceHealthFailureNotificationPolicy(), @@ -365,6 +368,7 @@ export function AutomationInventoryTab() { proactiveOperationsResult, interactionLearningProofResult, liveReadModelGateResult, + redisDryRunGateResult, hostStatefulInventoryResult, serviceHealthGapMatrixResult, serviceHealthNotificationPolicyResult, @@ -384,6 +388,7 @@ export function AutomationInventoryTab() { setProactiveOperations(proactiveOperationsResult.status === 'fulfilled' ? proactiveOperationsResult.value : null) setInteractionLearningProof(interactionLearningProofResult.status === 'fulfilled' ? interactionLearningProofResult.value : null) setLiveReadModelGate(liveReadModelGateResult.status === 'fulfilled' ? liveReadModelGateResult.value : null) + setRedisDryRunGate(redisDryRunGateResult.status === 'fulfilled' ? redisDryRunGateResult.value : null) setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null) setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null) setServiceHealthNotificationPolicy(serviceHealthNotificationPolicyResult.status === 'fulfilled' ? serviceHealthNotificationPolicyResult.value : null) @@ -401,6 +406,7 @@ export function AutomationInventoryTab() { proactiveOperationsResult, interactionLearningProofResult, liveReadModelGateResult, + redisDryRunGateResult, hostStatefulInventoryResult, serviceHealthGapMatrixResult, serviceHealthNotificationPolicyResult, @@ -594,6 +600,30 @@ export function AutomationInventoryTab() { }) }, [liveReadModelGate]) + const visibleRedisDryRunSteps = useMemo(() => { + if (!redisDryRunGate) return [] + const priority = { approval_required: 0, blocked_by_runtime_gate: 1, contract_ready: 2 } as Record + return [...redisDryRunGate.dry_run_steps] + .sort((a, b) => { + const left = priority[a.status] ?? 3 + const right = priority[b.status] ?? 3 + if (left !== right) return left - right + return a.step_id.localeCompare(b.step_id) + }) + }, [redisDryRunGate]) + + const visibleRedisHandoffLanes = useMemo(() => { + if (!redisDryRunGate) return [] + const priority = { blocked_by_runtime_gate: 0, approval_required: 1, contract_ready: 2 } as Record + return [...redisDryRunGate.handoff_lanes] + .sort((a, b) => { + const left = priority[a.status] ?? 3 + const right = priority[b.status] ?? 3 + if (left !== right) return left - right + return a.lane_id.localeCompare(b.lane_id) + }) + }, [redisDryRunGate]) + const visibleReadinessRows = useMemo(() => { if (!backupReadiness) return [] const priority = { blocked: 0, action_required: 1, deferred: 2, ready: 3 } as Record @@ -737,7 +767,7 @@ export function AutomationInventoryTab() { ) } - if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { + if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -831,6 +861,12 @@ export function AutomationInventoryTab() { const liveReadModelNoWriteSmokes = liveReadModelGate.rollups.no_write_smoke_count const liveReadModelLiveTotal = liveReadModelGate.rollups.live_truth_count_total const liveReadModelForbiddenContent = liveReadModelGate.rollups.forbidden_frontend_content_count + const redisDryRunOverall = redisDryRunGate.program_status.overall_completion_percent + const redisDryRunSteps = redisDryRunGate.rollups.dry_run_step_count + const redisHandoffLanes = redisDryRunGate.rollups.handoff_lane_count + const redisApprovalSteps = redisDryRunGate.rollups.approval_required_step_ids.length + const redisBlockedActions = redisDryRunGate.rollups.blocked_runtime_action_count + const redisLiveTotal = redisDryRunGate.rollups.live_truth_count_total const hostReadonlyDeniedCount = ( hostStatefulInventory.rollups.ssh_login_allowed_count + hostStatefulInventory.rollups.kubectl_command_execution_allowed_count @@ -1032,6 +1068,14 @@ export function AutomationInventoryTab() { } } + const redisDryRunValueLabel = (group: string, value: string) => { + try { + return t(`redisDryRunGate.${group}.${value}` as never) + } catch { + return value + } + } + const hostStatefulValueLabel = (group: string, value: string) => { try { return t(`hostStateful.${group}.${value}` as never) @@ -1498,6 +1542,115 @@ export function AutomationInventoryTab() {
+
+
+
+ + + {t('redisDryRunGate.title')} + +
+ +
+ +
+ } /> + } /> + } /> + 0 ? 'danger' : 'ok'} icon={} /> + 0 ? 'danger' : 'ok'} icon={} /> + } /> +
+ +
+
+
+ {t('redisDryRunGate.consumerTitle')} + + {t('redisDryRunGate.consumerDetail', { + group: redisDryRunGate.consumer_group_dry_run_contract.candidate_group_name, + namespace: redisDryRunGate.consumer_group_dry_run_contract.stream_namespace, + fields: redisDryRunGate.consumer_group_dry_run_contract.required_fixture_fields.length, + })} + +
+ + + +
+
+ +
+ {t('redisDryRunGate.envelopeTitle')} + + {redisDryRunGate.handoff_envelope_contract.operator_meaning} + +
+ + + +
+
+ +
+ {t('redisDryRunGate.redactionTitle')} + + {redisDryRunGate.display_redaction_contract.frontend_display_policy} + +
+
+ +
+ {visibleRedisDryRunSteps.map(step => ( +
+
+ + {step.step_id} + + +
+ + {step.display_name} + + + {step.operator_signal} + +
+ + +
+
+ ))} +
+
+ +
+ {visibleRedisHandoffLanes.map(lane => ( +
+
+ + {lane.display_name} + + +
+ + {redisDryRunValueLabel('agents', lane.from_agent)} → {redisDryRunValueLabel('agents', lane.to_agent)} + + + {lane.required_evidence} + +
+ ))} +
+
+
diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index ceb549741..dc660bb51 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -282,6 +282,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiAgentRedisDryRunGate() { + const res = await fetch(`${API_BASE_URL}/agents/agent-redis-dry-run-gate`) + return handleResponse(res) + }, + async getAiAgentHostStatefulVersionInventory() { const res = await fetch(`${API_BASE_URL}/agents/agent-host-stateful-version-inventory`) return handleResponse(res) @@ -1252,6 +1257,99 @@ export interface AiAgentLiveReadModelGateSnapshot { } } +export interface AiAgentRedisDryRunGateSnapshot { + schema_version: 'ai_agent_redis_dry_run_gate_v1' + generated_at: string + program_status: { + overall_completion_percent: number + current_priority: 'P0' | 'P1' | 'P2' | 'P3' + current_task_id: string + next_task_id: string + read_only_mode: true + runtime_authority: 'dry_run_contract_only_no_redis_runtime' + status_note: string + } + source_refs: string[] + dry_run_truth: { + redis_connection_allowed: false + consumer_group_created: false + xadd_allowed: false + xreadgroup_allowed: false + ack_allowed: false + dead_letter_write_allowed: false + replay_runtime_allowed: false + telegram_send_allowed: false + learning_writeback_allowed: false + live_dry_run_event_count: number + live_ack_count: number + live_dead_letter_count: number + live_replay_count: number + truth_note: string + } + consumer_group_dry_run_contract: { + candidate_group_name: string + stream_namespace: string + fixture_only: true + redis_network_call_allowed: false + required_fixture_fields: string[] + forbidden_fixture_fields: string[] + dry_run_assertion: string + } + handoff_envelope_contract: { + required_fields: string[] + forbidden_fields: string[] + redacted_evidence_required: true + idempotency_key_required: true + operator_meaning: string + } + ack_dead_letter_replay_contract: { + ack_requires_verifier: true + dead_letter_requires_reason: true + replay_requires_idempotency: true + runtime_replay_allowed: false + ack_allowed_statuses: string[] + dead_letter_reasons: string[] + replay_preconditions: string[] + } + dry_run_steps: Array<{ + step_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + status: string + operator_signal: string + blocked_runtime_action: string + }> + handoff_lanes: Array<{ + lane_id: string + display_name: string + from_agent: 'openclaw' | 'hermes' | 'nemotron' + to_agent: 'openclaw' | 'hermes' | 'nemotron' + status: string + required_evidence: string + }> + approval_boundaries: Record + display_redaction_contract: { + redaction_required: true + raw_payload_display_allowed: false + private_reasoning_display_allowed: false + secret_value_display_allowed: false + allowed_frontend_content: string[] + forbidden_frontend_content: string[] + frontend_display_policy: string + } + rollups: { + source_ref_count: number + dry_run_step_count: number + handoff_lane_count: number + contract_ready_step_ids: string[] + approval_required_step_ids: string[] + blocked_runtime_action_count: number + required_handoff_field_count: number + forbidden_field_count: number + live_truth_count_total: number + } +} + export interface AiAgentHostStatefulVersionInventorySnapshot { schema_version: 'ai_agent_host_stateful_version_inventory_v1' generated_at: string diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 7ef4df2be..e2f143247 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -37,6 +37,36 @@ **邊界**:本段只做只讀分類、snapshot、schema、guard、文件與前端可視化;未送 owner request、未標記 received / accepted、未修改 Nginx / systemd / Docker / firewall / DNS / TLS / certbot / workflow / secret / runner / K8s / ArgoCD、未收 secret value、未 SSH、未 active scan、未開 `agent-bounty-protocol` runtime、未處理 payout / withdrawal、未建立執行按鈕。 +## 2026-06-11|P2-403C Redis Dry-run Gate 與 Agent 接手契約 + +**背景**:P2-403B 已把 AgentSession / Redis live read model gate 接到治理頁,但 live AgentSession、Redis events、handoff、learning write、Telegram digest receipt 仍全為 `0`。統帥指出批准後仍沒有真正自動化,也沒有可操作的人工處理選項;本段先把 Redis Streams consumer group dry-run、handoff envelope、ack / dead-letter / replay gate 固定成可驗證契約,避免繼續用 UI 文案包裝流程缺口。 + +**完成**: + +- 新增 `ai_agent_redis_dry_run_gate_v1` schema、committed snapshot、只讀 loader、API route 與測試。 +- 新增 `GET /api/v1/agents/agent-redis-dry-run-gate`,只回傳 committed dry-run gate;不連 Redis、不建立 consumer group、不 XADD / XREADGROUP / XACK、不寫 dead-letter、不 replay、不發 Telegram、不 learning writeback。 +- Snapshot 固定 fixture-only consumer group dry-run、handoff envelope 必填欄位、idempotency key、redacted evidence ref、ack / dead-letter / replay 前置條件與 frontend redaction contract。 +- `agent-interaction-learning-proof` 與 `agent-proactive-operations-contract` 已同步 current / next:`P2-403C -> P2-403D`;三 Agent 互動學習證據完成度 `55% -> 60%`。 +- Governance automation inventory 頁新增 Redis dry-run gate 區塊,顯示 dry-run step、handoff lane、approval-required step、blocked runtime action 與 live truth total,仍明確顯示 live truth total 為 `0`。 +- MASTER §3.2.1c / §3.2.1d 與 §8 Living Changelog 已同步 P2-403C;下一步為 `P2-403D` learning writeback approval package。 + +**本地驗證**: + +- JSON parse:P2-403C schema / snapshot、P2-403 interaction snapshot、P2-403 proactive snapshot、`zh-TW.json`、`en.json` 通過。 +- API/service pytest:P2-403C dry-run gate、P2-403B live read model、P2-403 interaction proof、P2-403 proactive contract 目標測試通過。 +- `py_compile`:P2-403C loader 與 `agents.py` 通過。 +- `source-control-owner-response-guard.py`、`security-mirror-progress-guard.py`、`doc-secrets-sanity-check.py docs .gitea`、`git diff --check` 通過。 + +**完成度同步**: + +- P2-403C Redis dry-run gate:本地 `100%`。 +- 三 Agent 主動溝通、學習與成長證據:`60%`。 +- P2-403D learning writeback approval package:`0%`,下一步。 +- live AgentSession / Redis events / handoff / learning write / Telegram digest receipt:仍全為 `0`。 +- IwoooS 整體仍維持 `64%`;active runtime gate 仍 `0`;owner response received / accepted 仍 `0 / 0`。 + +**邊界**:本段只做 dry-run contract / committed snapshot / read-only API / governance UI;未連 Redis、未建立 consumer group、未 XADD、未 XREADGROUP、未 ACK、未 dead-letter write、未 runtime replay、未發 Telegram、未寫 KM / Playbook learning、未 SSH、未 active scan、未開 runtime gate。 + ## 2026-06-11|IwoooS S4.10 Target Owner Response 擴充與正式站驗證 **背景**:`VibeWork` 與 `agent-bounty-protocol` 已納入 IwoooS 資安驗證與控管範圍;S4.10 GitHub target owner response 仍需只讀 owner gate,不得自動建立 repo、同步 refs、切 GitHub primary、修改 workflow、收 secret value 或開 runtime gate。本段把 owner response packet、approval board、primary readiness、rollback ADR、workflow secret-name inventory、rollup 與 IwoooS 前端口徑從舊 `8` 候選 / `7` 需批准 / `22` 回覆範本收斂到目前 `10` 候選 / `9` 需批准 / `24` 回覆範本。 diff --git a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md index 9f6906dd6..35e735a6a 100644 --- a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md +++ b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md @@ -12,7 +12,7 @@ | Nemotron 實際整合應用 | 30% | 完整回放前仍被關卡擋下 | `blocked_needs_evidence`,下一關是 `refresh_source_evidence_then_5_record_smoke_only` | | 工具 / 服務 / 套件 AI 自動化 | 92% | P0 已完成;P1 服務 / runtime / 監控 / provider / service health / 備份 / DR / 套件與供應鏈只讀基線已完成;P1-007 失敗限定通知合約與前端 redaction 合約已完成;下一主線是 P2-004 依賴 / 供應鏈漂移監控 | 狀態分類、盤點 schema、權限矩陣、靜態盤點種子、只讀 API、UI 骨架、驗證、自動化待辦 schema / 快照 / API / 分組 UI、Backup / DR 目標盤點、準備度矩陣、備份通知政策、Backup / DR 證據 UI、復原演練批准包模板、異地 / escrow 準備度狀態、任務批准邊界、確定性進度彙總、Python 套件 / 供應鏈只讀基線、JS pnpm/npm 只讀基線、Docker build surface 只讀基線、CVE / license / drift 嚴重度政策、定期依賴漂移與外部資料來源檢查設計、依賴升級批准包模板、runtime_surface_inventory_v1 schema / snapshot / API / UI、gitea_workflow_runner_health_v1 schema / snapshot / API / UI、observability_contract_matrix_v1 schema / snapshot / API / UI、ai_provider_route_matrix_v1 schema / snapshot / API / UI、service_health_gap_matrix_v1 schema / snapshot / API / UI、service health evidence cards UI、service_health_failure_notification_policy_v1 schema / snapshot / API / UI 已完成 | | OpenClaw / Hermes / NemoTron 佈建布局 | 45% | P1-401 / P1-402 已完成;仍是只讀 layout 與治理頁顯示,不是 runtime deploy | `ai_agent_deployment_layout_v1` schema、`ai_agent_deployment_layout_2026-06-11.json`、`GET /api/v1/agents/agent-deployment-layout`、治理頁自動化盤點 UI、`AI_AGENT_DEPLOYMENT_LAYOUT_2026-06-11.md` | -| OpenClaw / Hermes / NemoTron 主動溝通、學習與成長證據 | 55% | P2-401A 已完成只讀 contract;P2-403A 已完成互動 / 接手 / 學習 / 成長證據面板、API 與治理頁顯示;P2-403B 已完成 AgentSession / Redis Streams live read model gate。runtime worker、DB migration、Redis consumer group、Telegram 實發、learning writeback、SDK / 付費服務仍未開 gate | `ai_agent_communication_learning_contract_v1`、`ai_agent_interaction_learning_proof_v1`、`ai_agent_live_read_model_gate_v1`、`GET /api/v1/agents/agent-communication-learning-contract`、`GET /api/v1/agents/agent-interaction-learning-proof`、`GET /api/v1/agents/agent-live-read-model-gate`、`/zh-TW/governance?tab=automation-inventory`、MASTER §3.2.1b / §3.2.1d / §3.4.3 | +| OpenClaw / Hermes / NemoTron 主動溝通、學習與成長證據 | 60% | P2-401A 已完成只讀 contract;P2-403A 已完成互動 / 接手 / 學習 / 成長證據面板;P2-403B 已完成 AgentSession / Redis Streams live read model gate;P2-403C 已完成 Redis Streams consumer group dry-run、handoff envelope、ack / dead-letter / replay gate。runtime worker、DB migration、production Redis consumer group、Telegram 實發、learning writeback、SDK / 付費服務仍未開 gate | `ai_agent_communication_learning_contract_v1`、`ai_agent_interaction_learning_proof_v1`、`ai_agent_live_read_model_gate_v1`、`ai_agent_redis_dry_run_gate_v1`、`GET /api/v1/agents/agent-communication-learning-contract`、`GET /api/v1/agents/agent-interaction-learning-proof`、`GET /api/v1/agents/agent-live-read-model-gate`、`GET /api/v1/agents/agent-redis-dry-run-gate`、`/zh-TW/governance?tab=automation-inventory`、MASTER §3.2.1b / §3.2.1d / §3.4.3 | | AI Agent 主動營運委派與版本生命週期 | 100% | P2-402A / P2-402B / P2-402C / P2-402D / P2-402E / P2-402F / P2-402G 已完成;已建立 repo-only 版本新鮮度快照、工具採用批准包、Telegram action-required digest policy、Gitea PR 草案 lane、host / K3s / stateful 版本只讀盤點、API 與 governance UI。定期排程、外部版本查詢、工具安裝、CI 變更、套件升級、主機更新、container pull、實際 PR creation、auto merge、Telegram 實發、SSH、kubectl、重啟仍未開 gate | `ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1`、`ai_agent_gitea_pr_draft_lane_v1`、`ai_agent_host_stateful_version_inventory_v1`、`GET /api/v1/agents/agent-proactive-operations-contract`、`GET /api/v1/agents/agent-version-freshness-snapshot`、`GET /api/v1/agents/agent-tool-adoption-approval-package`、`GET /api/v1/agents/agent-telegram-action-required-digest-policy`、`GET /api/v1/agents/agent-gitea-pr-draft-lane`、`GET /api/v1/agents/agent-host-stateful-version-inventory`、`/zh-TW/governance?tab=automation-inventory`、MASTER §3.2.1c | | 本工作清單與分析報告 | 100% | 已完成 | 本 MD 文件 | @@ -20,9 +20,9 @@ AI Agent 自動化工作包目前完成度:**92%**。本工作清單文件本 三 Agent 佈建布局目前完成度:**45%**。第一波已完成只讀 schema / snapshot / API / 測試 / 報告,第二波已接入治理頁自動化盤點 UI;正式 runtime 佈署、Telegram E2E 發送與 AgentSession 工作流仍需逐項 gate。 -三 Agent 主動溝通、學習與成長證據目前完成度:**55%**。已完成只讀契約、互動 / 接手 / 學習 / 成長證據面板、P2-403B live read model gate、API、治理頁顯示、測試與 MASTER 同步;目前 live AgentSession、Agent message、handoff、learning write、Telegram receipt 仍全部為 `0`,下一步依優先順序推 `P2-403C` Redis Streams consumer group dry-run 與 handoff envelope,但在批准前仍不得啟動 runtime loop。 +三 Agent 主動溝通、學習與成長證據目前完成度:**60%**。已完成只讀契約、互動 / 接手 / 學習 / 成長證據面板、P2-403B live read model gate、P2-403C Redis dry-run gate、API、治理頁顯示、測試與 MASTER 同步;目前 live AgentSession、Agent message、handoff、learning write、Telegram receipt 仍全部為 `0`,下一步依優先順序推 `P2-403D` learning writeback approval package,但在批准前仍不得啟動 runtime loop。 -AI Agent 主動營運委派與版本生命週期目前完成度:**100%**。已完成 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory、只讀 API、`P2-402B` repo-only daily version freshness snapshot、`P2-402C` Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包、`P2-402D` Telegram action-required digest policy、`P2-402E` Gitea PR 草案 lane、`P2-402F` host OS / K3s / stateful services 版本只讀盤點,以及 `P2-402G` governance UI 顯示可委派能力;`P2-403A` 與 `P2-403B` 已先補互動、學習證據面與 live read model gate。下一步是 `P2-403C` Redis Streams dry-run,外部 registry / package source / host probe / SSH / kubectl / 工具安裝 / CI 變更 / 實際 PR creation / Telegram 實發仍需 gate。 +AI Agent 主動營運委派與版本生命週期目前完成度:**100%**。已完成 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory、只讀 API、`P2-402B` repo-only daily version freshness snapshot、`P2-402C` Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包、`P2-402D` Telegram action-required digest policy、`P2-402E` Gitea PR 草案 lane、`P2-402F` host OS / K3s / stateful services 版本只讀盤點,以及 `P2-402G` governance UI 顯示可委派能力;`P2-403A`、`P2-403B` 與 `P2-403C` 已先補互動、學習證據面、live read model gate 與 Redis dry-run gate。下一步是 `P2-403D` learning writeback approval package,外部 registry / package source / host probe / SSH / kubectl / 工具安裝 / CI 變更 / 實際 PR creation / Telegram 實發仍需 gate。 完成度計算模型: @@ -76,7 +76,7 @@ AI Agent 主動營運委派與版本生命週期目前完成度:**100%**。已 | AgentSession / Redis Streams runtime loop | 待辦:P2-401,需 migration / worker gate | | 主動溝通與學習契約 | 已完成:P2-401A,`ai_agent_communication_learning_contract_v1` + `GET /api/v1/agents/agent-communication-learning-contract` | | 主動營運委派與版本生命週期契約 | 已完成:P2-402A,`ai_agent_proactive_operations_contract_v1` + `GET /api/v1/agents/agent-proactive-operations-contract` | -| 互動、接手、學習與成長證據面 | 已完成:P2-403A + P2-403B,`ai_agent_interaction_learning_proof_v1`、`ai_agent_live_read_model_gate_v1`、`GET /api/v1/agents/agent-interaction-learning-proof`、`GET /api/v1/agents/agent-live-read-model-gate` + governance UI;live count 仍為 0 | +| 互動、接手、學習與成長證據面 | 已完成:P2-403A + P2-403B + P2-403C,`ai_agent_interaction_learning_proof_v1`、`ai_agent_live_read_model_gate_v1`、`ai_agent_redis_dry_run_gate_v1`、`GET /api/v1/agents/agent-interaction-learning-proof`、`GET /api/v1/agents/agent-live-read-model-gate`、`GET /api/v1/agents/agent-redis-dry-run-gate` + governance UI;live count 仍為 0 | | NemoTron 3 Ultra smoke | 待辦:P3-401,需 source refresh + cost/data approval | ## 4. 工作流總覽 @@ -961,6 +961,7 @@ UI: | P2-402G | 完成 | 100 | Hermes | 接入 governance UI 顯示可委派能力 | 自主等級、gate、owner、Telegram policy、Host / K3s / stateful 只讀關卡與 redaction 狀態 | 只讀;不顯示敏感端點、不發 Telegram、不提供執行按鈕 | | P2-403A | 完成 | 100 | Hermes + OpenClaw | 建立 Agent 互動、接手、學習、成長與 Telegram 收據證據面 | `ai_agent_interaction_learning_proof_v1` / snapshot / 只讀 API / governance UI;證據階梯、live truth、Agent lanes、runtime gates、redaction policy | 只讀;live session / message / handoff / learning write / Telegram receipt 目前全為 0;不啟動 worker、不 DB migration、不 Redis consumer、不發 Telegram、不顯示工作對話 | | P2-403B | 完成 | 100 | OpenClaw + Hermes | AgentSession / Redis Streams live read model 與 worker gate planning | `ai_agent_live_read_model_gate_v1` / snapshot / 只讀 API / governance UI;既有 `agent_sessions` safe fields、Redis envelope、worker gate、rollback plan、無寫入 smoke | DB / Redis approval;live DB query / Redis consumer / worker / Telegram / learning writeback 仍未啟用 | +| P2-403C | 完成 | 100 | Hermes + OpenClaw + Nemotron | Redis Streams consumer group dry-run、handoff envelope、ack / dead-letter / replay gate | `ai_agent_redis_dry_run_gate_v1` / snapshot / 只讀 API / governance UI;fixture-only dry-run、handoff 必填欄位、idempotency、redacted evidence、ack / dead-letter / replay preconditions | 不連 Redis、不建立 consumer group、不 XADD / XREADGROUP / XACK、不寫 dead-letter、不 runtime replay、不發 Telegram、不 learning writeback | | P2-101 | 待辦 | 0 | OpenClaw | 定義操作類別權限模型 | 操作政策 schema | HITL 關卡 | | P2-102 | 待辦 | 0 | OpenClaw | 所有候選操作都要有 dry-run 證據 | dry-run 合約 | 不直接 apply | | P2-103 | 待辦 | 0 | Hermes | 把任務結果接回 KM / LOGBOOK / 稽核軌跡 | 證據寫入器 | 不洩漏 secret | diff --git a/docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md b/docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md index a9e6e9114..ade187d17 100644 --- a/docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md +++ b/docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md @@ -1,12 +1,12 @@ # AI Agent 互動、溝通、學習與成長證據報告 > 日期:2026-06-11(台北時間) -> 文件定位:P2-403A 證據面 + P2-403B AgentSession / Redis Streams live read model gate、API、治理頁 UI 與後續 runtime gate 分析。 +> 文件定位:P2-403A 證據面 + P2-403B AgentSession / Redis Streams live read model gate + P2-403C Redis dry-run gate、API、治理頁 UI 與後續 runtime gate 分析。 > 事實邊界:本波只建立可見證據面與 read model gate,不啟動 runtime worker、不建立 DB migration、不開 Redis consumer group、不發 Telegram、不顯示工作視窗對話內容。 ## 1. 結論 -已完成 P2-403A 與 P2-403B:讓統帥能在治理頁看到 OpenClaw / Hermes / NemoTron 的互動、接手、學習與成長是否真的有證據,並看到 live read model 下一步要通過哪些 DB / Redis / worker gate。 +已完成 P2-403A、P2-403B 與 P2-403C:讓統帥能在治理頁看到 OpenClaw / Hermes / NemoTron 的互動、接手、學習與成長是否真的有證據,並看到 live read model、Redis dry-run、handoff envelope、ack / dead-letter / replay 下一步要通過哪些 gate。 目前真相: @@ -51,21 +51,22 @@ | 產物 | 內容 | |---|---| | `docs/schemas/ai_agent_interaction_learning_proof_v1.schema.json` | 強制 live flags / counts / Telegram / transcript / 私有推理維持安全邊界 | -| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | P2-403A + P2-403B committed snapshot,完成度 `55%`,live count 全為 `0` | +| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | P2-403A + P2-403B + P2-403C committed snapshot,完成度 `60%`,live count 全為 `0` | | `docs/schemas/ai_agent_live_read_model_gate_v1.schema.json` | 強制 DB / Redis / worker / Telegram / learning writeback gate 維持未批准 | | `docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json` | P2-403B committed snapshot,完成度 `55%`,live count 全為 `0` | +| `docs/evaluations/ai_agent_redis_dry_run_gate_2026-06-11.json` | P2-403C committed snapshot,完成度 `60%`,live count 全為 `0` | | `apps/api/src/services/ai_agent_interaction_learning_proof.py` | 只讀 loader 與安全驗證 | | `apps/api/src/services/ai_agent_live_read_model_gate.py` | P2-403B 只讀 loader;拒絕 live DB query、Redis consumer、unsafe fields、Telegram 與 writeback | | `GET /api/v1/agents/agent-interaction-learning-proof` | 只讀 API,不啟動 worker、不碰 Redis / DB runtime、不發 Telegram | | `GET /api/v1/agents/agent-live-read-model-gate` | 只讀 API,不連 DB、不讀寫 Redis、不發 Telegram | -| governance UI | 新增證據階梯、目前真相、P2-403B live read gate、Agent lane、可觀測訊號、runtime gates、前端 redaction | +| governance UI | 新增證據階梯、目前真相、P2-403B live read gate、P2-403C Redis dry-run gate、Agent lane、可觀測訊號、runtime gates、前端 redaction | ## 5. 後續優先順序 | 優先 | ID | 工作 | gate | |---:|---|---|---| -| 1 | P2-403C | Redis Streams consumer group dry-run、handoff envelope、ack / dead letter / replay | runtime dry-run approval | -| 2 | P2-403D | learning writeback approval package、KM / playbook trust / replay score 趨勢 | writeback approval | +| 1 | P2-403D | learning writeback approval package、KM / playbook trust / replay score 趨勢 | writeback approval | +| 2 | P2-403E | learning writeback approval package、KM / playbook trust / replay score 趨勢 | writeback approval | | 3 | P2-403E | Telegram gateway dry-run、E2E 送達與 receipt 顯示 | Telegram E2E approval | | 4 | P2-403F | 成長趨勢週報與 operator feedback applied 指標 | trend evidence | diff --git a/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md b/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md index b93a7fa67..5733879c9 100644 --- a/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md +++ b/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md @@ -1,7 +1,7 @@ # AI Agent 主動營運委派與版本生命週期分析報告 > 日期:2026-06-11(台北時間) -> 文件定位:P2-402A / P2-402B / P2-402C / P2-402D / P2-402E / P2-402F / P2-402G / P2-403A / P2-403B 只讀契約與治理 UI 摘要。權威細節以 MASTER §3.2.1c / §3.2.1d、`ai_agent_proactive_operations_contract_v1`、`ai_agent_interaction_learning_proof_v1`、`ai_agent_live_read_model_gate_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1`、`ai_agent_gitea_pr_draft_lane_v1` 與 `ai_agent_host_stateful_version_inventory_v1` 為準。 +> 文件定位:P2-402A / P2-402B / P2-402C / P2-402D / P2-402E / P2-402F / P2-402G / P2-403A / P2-403B / P2-403C 只讀契約與治理 UI 摘要。權威細節以 MASTER §3.2.1c / §3.2.1d、`ai_agent_proactive_operations_contract_v1`、`ai_agent_interaction_learning_proof_v1`、`ai_agent_live_read_model_gate_v1`、`ai_agent_redis_dry_run_gate_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1`、`ai_agent_gitea_pr_draft_lane_v1` 與 `ai_agent_host_stateful_version_inventory_v1` 為準。 ## 1. 本波完成度 @@ -15,8 +15,8 @@ | Host / K3s / stateful 版本只讀盤點 | 100% | P2-402F 已完成 5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務與 maintenance window 批准包 schema / committed snapshot / API / 測試 | | Governance UI 可委派能力顯示 | 100% | P2-402G 已把可委派能力、版本生命週期、Host / K3s / stateful 只讀盤點、maintenance window 與 Telegram / redaction gate 接入治理頁 | | Agent 互動與學習證據面 | 100% | P2-403A 已把目前真相、證據階梯、三 Agent lane、可觀測訊號、runtime gates 與 redaction policy 接入治理頁;live counts 全為 `0` | -| Live read model gate | 100% | P2-403B 已把 AgentSession safe fields、Redis envelope、worker gate、rollback plan、no-write smoke 與治理頁顯示接入;live counts 全為 `0` | -| 整體主動營運與版本生命週期 | 100% | P2-402A~G、P2-403A 與 P2-403B 只讀契約、snapshot、API、測試與治理 UI 已完成;runtime 排程、工具安裝、CI 變更、實際 PR 建立與更新、host probe、升級、重啟、learning write、Telegram receipt 仍未開 gate | +| Redis dry-run gate | 100% | P2-403C 已把 consumer group dry-run、handoff envelope、ack / dead-letter / replay idempotency 與治理頁顯示接入;live counts 全為 `0` | +| 整體主動營運與版本生命週期 | 100% | P2-402A~G、P2-403A、P2-403B 與 P2-403C 只讀契約、snapshot、API、測試與治理 UI 已完成;runtime 排程、工具安裝、CI 變更、實際 PR 建立與更新、host probe、升級、重啟、learning write、Telegram receipt 仍未開 gate | ## 2. 可交給 AI Agent 的工作分類 @@ -54,7 +54,7 @@ | `docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json` | 5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務、6 個只讀 probe 步驟、maintenance window approval package | | `GET /api/v1/agents/agent-host-stateful-version-inventory` | 只讀 API;不 SSH、不 kubectl、不升級、不 drain、不 reboot、不重啟 stateful、不發 Telegram | | `docs/schemas/ai_agent_interaction_learning_proof_v1.schema.json` | P2-403A Agent 互動、接手、學習、成長與 Telegram receipt 證據面 schema | -| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | 證據階梯、live truth、三 Agent lane、可觀測訊號、runtime gates;P2-403B 後完成度 `55%`,live counts 全部 `0` | +| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | 證據階梯、live truth、三 Agent lane、可觀測訊號、runtime gates;P2-403C 後完成度 `60%`,live counts 全部 `0` | | `GET /api/v1/agents/agent-interaction-learning-proof` | 只讀 API;不啟動 worker、不開 Redis consumer、不 DB migration、不發 Telegram、不顯示工作視窗對話 | | `docs/schemas/ai_agent_live_read_model_gate_v1.schema.json` | P2-403B AgentSession / Redis Streams live read model gate schema | | `docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json` | AgentSession safe fields、Redis envelope、worker gate、rollback plan、no-write smoke、frontend redaction;live counts 全部 `0` | @@ -69,7 +69,7 @@ | Dockerfiles | Hermes | action_required | P2-402C 評估 Trivy / Syft / Grype / Docker Scout 採用,不 build / pull | | Committed evaluation snapshots | Hermes | action_required | 將 2026-06-04~06-05 舊基線列入 stale refs,不假裝是外部最新 | | Agent / model governance snapshots | NemoTron | action_required | 只做離線 freshness note,不進 shadow / canary / production route | -| K8s / Gitea / observability / Ansible / backup / web surfaces | OpenClaw + Hermes | baseline_ready / planned_next | 下一步進入 P2-403C Redis Streams consumer group dry-run;現階段仍只讀 | +| K8s / Gitea / observability / Ansible / backup / web surfaces | OpenClaw + Hermes | baseline_ready / planned_next | 下一步進入 P2-403D learning writeback approval package;現階段仍只讀 | 本波只把「每天要看哪些 repo 內版本來源」定義成可驗證資料面。每日排程、外部 registry 查詢、主機/K3s live probe、Telegram digest 與 Gitea PR lane 都仍是下一階段 gate。 @@ -140,7 +140,8 @@ P2-402G 的重點是把前面六個資料契約接回治理頁,讓統帥可以 | P2-402G | 3 | governance UI 顯示可委派能力 | 已完成,只讀;不顯示敏感端點或工作對話內容 | | P2-403A | 4 | Agent 互動、接手、學習、成長與 Telegram receipt 證據面 | 已完成,只讀;live counts 全為 `0` | | P2-403B | 5 | live AgentSession / Redis read model:safe fields、Redis envelope、worker gate、rollback、no-write smoke | 已完成,只讀;live DB query / Redis consumer / worker 未啟用 | -| P2-403C | 6 | Redis Streams consumer group dry-run、handoff envelope、ack / dead letter / replay | runtime dry-run approval | +| P2-403C | 6 | Redis Streams consumer group dry-run、handoff envelope、ack / dead letter / replay | 已完成,只讀;production Redis consumer / worker / ACK / replay 未啟用 | +| P2-403D | 7 | Learning writeback approval package、資料保留、錯誤補償、人工回饋回滾 | learning writeback approval | ## 11. 仍維持 false 的安全邊界 diff --git a/docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json b/docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json index 74f3364ca..0d5f09356 100644 --- a/docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json +++ b/docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json @@ -2,13 +2,13 @@ "schema_version": "ai_agent_interaction_learning_proof_v1", "generated_at": "2026-06-11T23:20:00+08:00", "program_status": { - "overall_completion_percent": 55, + "overall_completion_percent": 60, "current_priority": "P2", - "current_task_id": "P2-403B", - "next_task_id": "P2-403C", + "current_task_id": "P2-403C", + "next_task_id": "P2-403D", "read_only_mode": true, "runtime_authority": "proof_surface_only_no_live_worker", - "status_note": "P2-403B 已把 AgentSession / Redis Streams live read model gate、唯讀查詢、回滾與無寫入 smoke 接入證據鏈;live AgentSession/Redis/Telegram/learning writeback 尚未開 gate。" + "status_note": "P2-403C 已把 Redis Streams consumer group dry-run、handoff envelope、ack / dead-letter / replay gate 接入證據鏈;live Redis consumer、runtime worker、Telegram receipt 與 learning writeback 仍未開 gate。" }, "live_truth": { "runtime_loop_enabled": false, @@ -40,7 +40,7 @@ "completion_percent": 100, "operator_meaning": "治理頁會顯示哪些證據真的有資料、哪些仍是 0 與原因。", "source_of_truth": "ai_agent_interaction_learning_proof_v1", - "next_gate": "P2-403B live AgentSession / Redis readback。" + "next_gate": "P2-403C Redis dry-run gate 已接入;下一步是 learning writeback approval package。" }, { "level_id": "live_read_model", @@ -49,25 +49,25 @@ "completion_percent": 100, "operator_meaning": "已定義 AgentSession 安全欄位、Redis event envelope、worker gate、回滾與無寫入 smoke;尚未讀 live DB/Redis。", "source_of_truth": "ai_agent_live_read_model_gate_v1", - "next_gate": "P2-403C Redis Streams consumer group dry-run。" + "next_gate": "P2-403C Redis dry-run gate 已接入治理頁。" }, { "level_id": "message_bus_receipts", "display_name": "訊息匯流收據", - "status": "live_pending", - "completion_percent": 0, - "operator_meaning": "啟用後你會看到 Agent 發出、收到、挑戰、審查、決策的脫敏事件收據。", - "source_of_truth": "Redis Streams + agent_sessions", - "next_gate": "P2-403C consumer group dry-run 與重放驗證。" + "status": "contract_ready", + "completion_percent": 100, + "operator_meaning": "已定義 fixture-only Redis dry-run、consumer group plan、ack / dead-letter / replay idempotency;尚未連 production Redis。", + "source_of_truth": "ai_agent_redis_dry_run_gate_v1", + "next_gate": "P2-403D learning writeback approval package。" }, { "level_id": "handoff_receipts", "display_name": "互相接手紀錄", - "status": "live_pending", - "completion_percent": 0, - "operator_meaning": "啟用後你會看到 OpenClaw 要求 Hermes 補證、NemoTron 回傳 replay 分數、再由 OpenClaw 仲裁的接手鏈。", - "source_of_truth": "agent_sessions.parent_turn_id + timeline_events", - "next_gate": "P2-403C handoff envelope schema 與拒收測試。" + "status": "contract_ready", + "completion_percent": 100, + "operator_meaning": "已定義 OpenClaw / Hermes / NemoTron handoff envelope、必填欄位、idempotency key 與 redacted evidence refs;尚未啟動 runtime worker。", + "source_of_truth": "ai_agent_redis_dry_run_gate_v1", + "next_gate": "P2-403D learning writeback approval package。" }, { "level_id": "learning_growth", @@ -278,8 +278,8 @@ "gate_id": "redis_stream_consumer_gate", "display_name": "Redis Streams consumer group dry-run", "status": "approval_required", - "required_before_green": "必須先證明 consumer ack、dead letter、重放與停用開關。", - "next_task_id": "P2-403C" + "required_before_green": "P2-403C 已完成 fixture-only dry-run、handoff envelope、ack / dead-letter / replay 契約;建立 production consumer group 前仍需人工批准。", + "next_task_id": "P2-403D" }, { "gate_id": "learning_writeback_gate", @@ -300,7 +300,7 @@ "display_name": "前端脫敏與內容紅線", "status": "blocked", "required_before_green": "任何面板只能顯示脫敏摘要、狀態、來源與核准欄位,未脫敏內容不得進前端。", - "next_task_id": "P2-403B" + "next_task_id": "P2-403C" } ], "learning_memory_stack": [ @@ -365,11 +365,9 @@ }, "rollups": { "proof_level_count": 7, - "contract_ready_level_count": 3, + "contract_ready_level_count": 5, "live_pending_level_ids": [ - "handoff_receipts", "learning_growth", - "message_bus_receipts", "telegram_receipts" ], "signal_count": 9, diff --git a/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json b/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json index 324450181..920d281fd 100644 --- a/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json +++ b/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json @@ -4,11 +4,11 @@ "program_status": { "overall_completion_percent": 100, "current_priority": "P2", - "current_task_id": "P2-403B", - "next_task_id": "P2-403C", + "current_task_id": "P2-403C", + "next_task_id": "P2-403D", "read_only_mode": true, "runtime_authority": "contract_only_no_version_or_runtime_update", - "status_note": "P2-403B 已把 AgentSession / Redis Streams live read model gate、safe read fields、回滾計畫與無寫入 smoke 接入治理證據;live AgentSession / message / handoff / learning write / Telegram receipt 目前全為 0,下一步是 P2-403C Redis dry-run。" + "status_note": "P2-403C 已把 Redis Streams consumer group dry-run、handoff envelope、ack / dead-letter / replay gate 接入治理證據;live AgentSession / Redis consumer / runtime worker / learning write / Telegram receipt 目前全為 0,下一步是 P2-403D learning writeback approval package。" }, "external_source_evidence": [ { @@ -110,7 +110,14 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "OpenClaw 仲裁 + replay / shadow / canary gate", "approval_gate": "market_scorecard_replay_and_cost_data_approval_required", - "tracked_examples": ["OpenClaw", "Hermes", "NemoTron", "LangGraph", "OpenAI Agents SDK", "Claude Agent SDK"] + "tracked_examples": [ + "OpenClaw", + "Hermes", + "NemoTron", + "LangGraph", + "OpenAI Agents SDK", + "Claude Agent SDK" + ] }, { "domain_id": "python_packages", @@ -120,7 +127,10 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "dependency upgrade approval package", "approval_gate": "dependency_approval_required", - "tracked_examples": ["pyproject.toml", "requirements.txt"] + "tracked_examples": [ + "pyproject.toml", + "requirements.txt" + ] }, { "domain_id": "javascript_packages", @@ -130,7 +140,10 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "dependency upgrade approval package", "approval_gate": "dependency_approval_required", - "tracked_examples": ["package.json", "pnpm-lock.yaml"] + "tracked_examples": [ + "package.json", + "pnpm-lock.yaml" + ] }, { "domain_id": "container_images", @@ -140,7 +153,11 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "image digest pin proposal + smoke plan", "approval_gate": "image_pull_build_push_approval_required", - "tracked_examples": ["Dockerfile", "Harbor image tags", "base image digest"] + "tracked_examples": [ + "Dockerfile", + "Harbor image tags", + "base image digest" + ] }, { "domain_id": "kubernetes_k3s_components", @@ -150,7 +167,13 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "version skew report + maintenance window approval", "approval_gate": "k8s_upgrade_maintenance_window_required", - "tracked_examples": ["kube-apiserver", "kubelet", "kubectl", "CNI", "Ingress"] + "tracked_examples": [ + "kube-apiserver", + "kubelet", + "kubectl", + "CNI", + "Ingress" + ] }, { "domain_id": "host_os_packages", @@ -160,7 +183,12 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "Ansible check-mode / maintenance plan only", "approval_gate": "host_update_approval_required", - "tracked_examples": ["Ubuntu packages", "kernel", "Nginx", "OpenSSH"] + "tracked_examples": [ + "Ubuntu packages", + "kernel", + "Nginx", + "OpenSSH" + ] }, { "domain_id": "observability_stack", @@ -170,7 +198,14 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "observability compatibility report", "approval_gate": "monitoring_route_receiver_write_blocked", - "tracked_examples": ["Prometheus", "Alertmanager", "Grafana", "SigNoz", "OpenTelemetry Collector", "Sentry"] + "tracked_examples": [ + "Prometheus", + "Alertmanager", + "Grafana", + "SigNoz", + "OpenTelemetry Collector", + "Sentry" + ] }, { "domain_id": "stateful_services", @@ -180,7 +215,13 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "backup freshness + rollback + compatibility gate", "approval_gate": "stateful_upgrade_approval_required", - "tracked_examples": ["PostgreSQL", "Redis", "MinIO", "Harbor", "Gitea"] + "tracked_examples": [ + "PostgreSQL", + "Redis", + "MinIO", + "Harbor", + "Gitea" + ] }, { "domain_id": "backup_dr_tooling", @@ -190,7 +231,12 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "restore drill approval package", "approval_gate": "restore_or_prune_approval_required", - "tracked_examples": ["restic", "Velero", "backup scripts", "offsite escrow"] + "tracked_examples": [ + "restic", + "Velero", + "backup scripts", + "offsite escrow" + ] }, { "domain_id": "ci_cd_and_runner_tools", @@ -200,7 +246,11 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "workflow / runner owner packet", "approval_gate": "workflow_modification_approval_required", - "tracked_examples": ["Gitea workflow", "runner labels", "deploy scripts"] + "tracked_examples": [ + "Gitea workflow", + "runner labels", + "deploy scripts" + ] }, { "domain_id": "mcp_tools_integrations", @@ -210,7 +260,12 @@ "current_allowed_autonomy": "L2_approval_package_only", "update_authority": "MCP schema compatibility report", "approval_gate": "new_tool_or_secret_approval_required", - "tracked_examples": ["K8s MCP", "Prometheus MCP", "Sentry MCP", "Telegram Gateway"] + "tracked_examples": [ + "K8s MCP", + "Prometheus MCP", + "Sentry MCP", + "Telegram Gateway" + ] }, { "domain_id": "public_web_admin_surfaces", @@ -220,7 +275,12 @@ "current_allowed_autonomy": "L1_report_only", "update_authority": "UI smoke report only", "approval_gate": "code_change_required_for_fix", - "tracked_examples": ["awoooi.wooo.work", "AwoooP", "IwoooS", "governance tabs"] + "tracked_examples": [ + "awoooi.wooo.work", + "AwoooP", + "IwoooS", + "governance tabs" + ] } ], "delegable_capabilities": [ @@ -230,7 +290,11 @@ "primary_owner": "hermes", "risk_tier": "low", "automation_level": "L1_report_only", - "outputs": ["version_delta_report", "freshness_score", "stale_source_list"], + "outputs": [ + "version_delta_report", + "freshness_score", + "stale_source_list" + ], "approval_gate": "read_only_allowed", "telegram_policy": "failure_or_action_required_only" }, @@ -240,7 +304,12 @@ "primary_owner": "openclaw", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["risk_matrix", "rollback_plan", "smoke_plan", "owner_packet"], + "outputs": [ + "risk_matrix", + "rollback_plan", + "smoke_plan", + "owner_packet" + ], "approval_gate": "human_approval_required", "telegram_policy": "action_required" }, @@ -250,7 +319,11 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L3_draft_change_after_gate", - "outputs": ["pr_plan", "grouping_policy", "automerge_false_policy"], + "outputs": [ + "pr_plan", + "grouping_policy", + "automerge_false_policy" + ], "approval_gate": "workflow_and_bot_approval_required", "telegram_policy": "action_required" }, @@ -260,7 +333,11 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["sbom_plan", "retention_policy", "tool_choice_matrix"], + "outputs": [ + "sbom_plan", + "retention_policy", + "tool_choice_matrix" + ], "approval_gate": "tool_install_or_ci_change_approval_required", "telegram_policy": "failure_only" }, @@ -270,7 +347,11 @@ "primary_owner": "openclaw", "risk_tier": "high", "automation_level": "L2_approval_package_only", - "outputs": ["vulnerability_report", "blast_radius", "patch_priority"], + "outputs": [ + "vulnerability_report", + "blast_radius", + "patch_priority" + ], "approval_gate": "external_scan_and_dependency_approval_required", "telegram_policy": "critical_or_action_required" }, @@ -280,7 +361,10 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L1_report_only", - "outputs": ["license_delta_report", "owner_review_queue"], + "outputs": [ + "license_delta_report", + "owner_review_queue" + ], "approval_gate": "legal_owner_review_required", "telegram_policy": "action_required_only" }, @@ -290,7 +374,11 @@ "primary_owner": "openclaw", "risk_tier": "high", "automation_level": "L2_approval_package_only", - "outputs": ["skew_report", "upgrade_order", "rollback_window"], + "outputs": [ + "skew_report", + "upgrade_order", + "rollback_window" + ], "approval_gate": "maintenance_window_required", "telegram_policy": "action_required" }, @@ -300,7 +388,11 @@ "primary_owner": "openclaw", "risk_tier": "high", "automation_level": "L2_approval_package_only", - "outputs": ["host_patch_plan", "affected_service_map", "reboot_risk"], + "outputs": [ + "host_patch_plan", + "affected_service_map", + "reboot_risk" + ], "approval_gate": "host_update_approval_required", "telegram_policy": "action_required" }, @@ -310,7 +402,11 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["drift_report", "owner_packet", "rollback_refs"], + "outputs": [ + "drift_report", + "owner_packet", + "rollback_refs" + ], "approval_gate": "owner_response_required", "telegram_policy": "action_required" }, @@ -320,7 +416,10 @@ "primary_owner": "openclaw", "risk_tier": "medium", "automation_level": "L1_report_only", - "outputs": ["stale_endpoint_report", "health_gap_list"], + "outputs": [ + "stale_endpoint_report", + "health_gap_list" + ], "approval_gate": "restart_or_endpoint_change_requires_approval", "telegram_policy": "failure_only" }, @@ -330,7 +429,10 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["noise_report", "rule_change_proposal"], + "outputs": [ + "noise_report", + "rule_change_proposal" + ], "approval_gate": "alert_rule_write_approval_required", "telegram_policy": "action_required_only" }, @@ -340,7 +442,11 @@ "primary_owner": "openclaw", "risk_tier": "high", "automation_level": "L1_report_only", - "outputs": ["delivery_report", "silent_route_alert", "fallback_gap"], + "outputs": [ + "delivery_report", + "silent_route_alert", + "fallback_gap" + ], "approval_gate": "telegram_send_or_route_change_requires_approval", "telegram_policy": "failure_or_action_required" }, @@ -350,7 +456,11 @@ "primary_owner": "openclaw", "risk_tier": "high", "automation_level": "L2_approval_package_only", - "outputs": ["readiness_matrix", "restore_drill_package", "offsite_gap"], + "outputs": [ + "readiness_matrix", + "restore_drill_package", + "offsite_gap" + ], "approval_gate": "restore_or_prune_approval_required", "telegram_policy": "failure_or_action_required" }, @@ -360,7 +470,11 @@ "primary_owner": "openclaw", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["capacity_forecast", "cost_delta", "resource_limit_proposal"], + "outputs": [ + "capacity_forecast", + "cost_delta", + "resource_limit_proposal" + ], "approval_gate": "cost_or_runtime_change_approval_required", "telegram_policy": "action_required_only" }, @@ -370,7 +484,11 @@ "primary_owner": "openclaw", "risk_tier": "high", "automation_level": "L2_approval_package_only", - "outputs": ["provider_scorecard", "fallback_gap", "cost_boundary_report"], + "outputs": [ + "provider_scorecard", + "fallback_gap", + "cost_boundary_report" + ], "approval_gate": "cost_data_and_route_approval_required", "telegram_policy": "action_required" }, @@ -380,7 +498,11 @@ "primary_owner": "nemotron", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["sanitized_replay_score", "schema_pass_rate", "tool_call_quality"], + "outputs": [ + "sanitized_replay_score", + "schema_pass_rate", + "tool_call_quality" + ], "approval_gate": "cost_data_and_sanitized_fixture_approval_required", "telegram_policy": "action_required_only" }, @@ -390,7 +512,11 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["stale_km_report", "merge_draft", "owner_review_queue"], + "outputs": [ + "stale_km_report", + "merge_draft", + "owner_review_queue" + ], "approval_gate": "owner_review_required", "telegram_policy": "digest_only" }, @@ -400,7 +526,11 @@ "primary_owner": "hermes", "risk_tier": "low", "automation_level": "L1_report_only", - "outputs": ["browser_smoke_report", "overflow_report", "route_health"], + "outputs": [ + "browser_smoke_report", + "overflow_report", + "route_health" + ], "approval_gate": "code_change_required_for_fix", "telegram_policy": "failure_only" }, @@ -410,7 +540,11 @@ "primary_owner": "openclaw", "risk_tier": "high", "automation_level": "L2_approval_package_only", - "outputs": ["schema_drift_report", "rls_context_gap", "migration_plan"], + "outputs": [ + "schema_drift_report", + "rls_context_gap", + "migration_plan" + ], "approval_gate": "db_migration_approval_required", "telegram_policy": "action_required" }, @@ -420,7 +554,11 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L1_report_only", - "outputs": ["postmortem_draft", "learning_delta", "weekly_digest"], + "outputs": [ + "postmortem_draft", + "learning_delta", + "weekly_digest" + ], "approval_gate": "km_write_owner_review_required", "telegram_policy": "digest_only" }, @@ -430,7 +568,11 @@ "primary_owner": "openclaw", "risk_tier": "high", "automation_level": "L1_report_only", - "outputs": ["secret_name_inventory", "rotation_due_report", "owner_packet"], + "outputs": [ + "secret_name_inventory", + "rotation_due_report", + "owner_packet" + ], "approval_gate": "secret_value_handling_forbidden", "telegram_policy": "action_required_only" }, @@ -440,7 +582,11 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L1_report_only", - "outputs": ["evidence_pack", "missing_owner_response", "audit_gap"], + "outputs": [ + "evidence_pack", + "missing_owner_response", + "audit_gap" + ], "approval_gate": "read_only_allowed", "telegram_policy": "digest_only" }, @@ -450,7 +596,11 @@ "primary_owner": "hermes", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["market_watch_report", "candidate_queue", "integration_review"], + "outputs": [ + "market_watch_report", + "candidate_queue", + "integration_review" + ], "approval_gate": "market_scorecard_and_replay_gate_required", "telegram_policy": "action_required_only" }, @@ -460,7 +610,11 @@ "primary_owner": "openclaw", "risk_tier": "medium", "automation_level": "L2_approval_package_only", - "outputs": ["release_train_plan", "batching_policy", "blast_radius_map"], + "outputs": [ + "release_train_plan", + "batching_policy", + "blast_radius_map" + ], "approval_gate": "operator_release_window_required", "telegram_policy": "action_required" } @@ -612,15 +766,15 @@ "summary": "定義 AI Agent 主動營運委派與版本生命週期契約、schema、snapshot、只讀 API 與文件同步。", "next_gate": "正式部署驗證" }, - { - "task_id": "P2-402B", - "priority": "P2", - "status": "done", - "completion_percent": 100, - "owner_agent": "Hermes", - "summary": "建立 repo-only daily version freshness snapshot schema、committed snapshot、只讀 API 與測試;不查外部 registry、不改 workflow。", - "next_gate": "P2-402C_completed" - }, + { + "task_id": "P2-402B", + "priority": "P2", + "status": "done", + "completion_percent": 100, + "owner_agent": "Hermes", + "summary": "建立 repo-only daily version freshness snapshot schema、committed snapshot、只讀 API 與測試;不查外部 registry、不改 workflow。", + "next_gate": "P2-402C_completed" + }, { "task_id": "P2-402C", "priority": "P2", @@ -682,7 +836,16 @@ "completion_percent": 100, "owner_agent": "OpenClaw + Hermes", "summary": "建立 AgentSession / Redis Streams live read model gate;定義既有表安全欄位、Redis envelope、worker gate、rollback plan、no-write smoke 與 governance UI 顯示。", - "next_gate": "P2-403C_redis_consumer_group_dry_run" + "next_gate": "P2-403C_completed" + }, + { + "task_id": "P2-403C", + "priority": "P2", + "status": "done", + "completion_percent": 100, + "owner_agent": "Hermes + OpenClaw + Nemotron", + "summary": "建立 Redis Streams consumer group dry-run、handoff envelope、ack / dead-letter / replay idempotency gate、只讀 API 與 governance UI 顯示;不連 Redis、不建立 consumer group、不 XADD、不 ACK、不 replay、不發 Telegram。", + "next_gate": "P2-403D_learning_writeback_approval_package" } ], "approval_boundaries": { @@ -703,7 +866,7 @@ "cadence_count": 5, "mcp_tool_count": 8, "rag_memory_count": 4, - "rollout_task_count": 9, + "rollout_task_count": 10, "auto_execute_allowed_count": 0, "approval_required_capability_count": 23, "blocked_update_domain_ids": [ diff --git a/docs/evaluations/ai_agent_redis_dry_run_gate_2026-06-11.json b/docs/evaluations/ai_agent_redis_dry_run_gate_2026-06-11.json new file mode 100644 index 000000000..12568077e --- /dev/null +++ b/docs/evaluations/ai_agent_redis_dry_run_gate_2026-06-11.json @@ -0,0 +1,240 @@ +{ + "schema_version": "ai_agent_redis_dry_run_gate_v1", + "generated_at": "2026-06-11T23:58:00+08:00", + "program_status": { + "overall_completion_percent": 60, + "current_priority": "P2", + "current_task_id": "P2-403C", + "next_task_id": "P2-403D", + "read_only_mode": true, + "runtime_authority": "dry_run_contract_only_no_redis_runtime", + "status_note": "P2-403C 已把 Redis Streams consumer group dry-run、handoff envelope、ack、dead-letter 與 replay gate 固定為只讀契約;尚未連 Redis、未建立 consumer group、未 XADD、未 XREADGROUP、未 ACK、未 replay、未發 Telegram、未做 learning writeback。" + }, + "source_refs": [ + "docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json", + "apps/api/src/services/signal_producer.py", + "apps/api/src/main.py:Signal Worker", + "docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md" + ], + "dry_run_truth": { + "redis_connection_allowed": false, + "consumer_group_created": false, + "xadd_allowed": false, + "xreadgroup_allowed": false, + "ack_allowed": false, + "dead_letter_write_allowed": false, + "replay_runtime_allowed": false, + "telegram_send_allowed": false, + "learning_writeback_allowed": false, + "live_dry_run_event_count": 0, + "live_ack_count": 0, + "live_dead_letter_count": 0, + "live_replay_count": 0, + "truth_note": "目前只允許 fixture / snapshot 層 dry-run 設計;治理頁看得到每一步需要什麼證據,但 production Redis runtime 全部維持關閉。" + }, + "consumer_group_dry_run_contract": { + "candidate_group_name": "awoooi-agent-handoff-dry-run", + "stream_namespace": "awoooi:agent:*", + "fixture_only": true, + "redis_network_call_allowed": false, + "required_fixture_fields": [ + "event_id", + "trace_id", + "session_id", + "incident_id", + "from_agent", + "to_agent", + "handoff_type", + "event_status", + "idempotency_key", + "redacted_evidence_ref", + "created_at" + ], + "forbidden_fixture_fields": [ + "secret_value", + "credential_value", + "raw_prompt", + "conversation_transcript", + "private_reasoning", + "chain_of_thought", + "raw_tool_output" + ], + "dry_run_assertion": "fixture replay 必須能決定 ack / dead-letter / replay lane,但不得連 Redis 或寫任何 runtime queue。" + }, + "handoff_envelope_contract": { + "required_fields": [ + "event_id", + "trace_id", + "session_id", + "incident_id", + "from_agent", + "to_agent", + "handoff_type", + "handoff_reason", + "evidence_summary", + "redacted_evidence_ref", + "required_next_action", + "idempotency_key", + "created_at" + ], + "forbidden_fields": [ + "secret_value", + "credential_value", + "raw_prompt", + "conversation_transcript", + "private_reasoning", + "chain_of_thought", + "raw_tool_output" + ], + "redacted_evidence_required": true, + "idempotency_key_required": true, + "operator_meaning": "每次 Agent 接手都必須能說清楚誰交給誰、為什麼交、下一步是什麼、證據在哪裡;但不顯示未脫敏 payload 或私有推理。" + }, + "ack_dead_letter_replay_contract": { + "ack_requires_verifier": true, + "dead_letter_requires_reason": true, + "replay_requires_idempotency": true, + "runtime_replay_allowed": false, + "ack_allowed_statuses": [ + "validated_no_write", + "duplicate_idempotency_key", + "operator_review_recorded" + ], + "dead_letter_reasons": [ + "missing_required_field", + "forbidden_payload_detected", + "unknown_agent_lane", + "stale_or_replayed_without_idempotency", + "execution_intent_detected" + ], + "replay_preconditions": [ + "fixture_only", + "idempotency_key_present", + "redacted_evidence_ref_present", + "no_runtime_write", + "no_telegram_send" + ] + }, + "dry_run_steps": [ + { + "step_id": "fixture_envelope_validation", + "display_name": "Fixture envelope 驗證", + "owner_agent": "hermes", + "status": "contract_ready", + "operator_signal": "固定 fixture 必須通過必填欄位、idempotency key 與 redacted evidence refs 檢查。", + "blocked_runtime_action": "redis_xadd" + }, + { + "step_id": "consumer_group_plan", + "display_name": "Consumer group 方案", + "owner_agent": "openclaw", + "status": "approval_required", + "operator_signal": "只定義 group name、stream namespace、pending / ack / dead-letter 行為,不建立 Redis group。", + "blocked_runtime_action": "redis_xgroup_create" + }, + { + "step_id": "ack_decision_fixture", + "display_name": "ACK 判定 fixture", + "owner_agent": "openclaw", + "status": "contract_ready", + "operator_signal": "dry-run 可判斷哪些事件應 ack,但不執行 XACK。", + "blocked_runtime_action": "redis_xack" + }, + { + "step_id": "dead_letter_fixture", + "display_name": "Dead-letter 分流 fixture", + "owner_agent": "hermes", + "status": "contract_ready", + "operator_signal": "缺欄位、敏感 payload、未知 lane 或 execution intent 必須進 dead-letter 候選。", + "blocked_runtime_action": "dead_letter_write" + }, + { + "step_id": "replay_idempotency_fixture", + "display_name": "Replay idempotency fixture", + "owner_agent": "nemotron", + "status": "approval_required", + "operator_signal": "replay 必須先證明 idempotency,不得把重放當成 runtime 自動執行。", + "blocked_runtime_action": "runtime_replay" + } + ], + "handoff_lanes": [ + { + "lane_id": "diagnosis_to_rule_review", + "display_name": "診斷 → 規則審核", + "from_agent": "openclaw", + "to_agent": "hermes", + "status": "contract_ready", + "required_evidence": "alert category、matched rule、evidence summary、blocked reason、redacted evidence ref。" + }, + { + "lane_id": "rule_review_to_repair_candidate", + "display_name": "規則審核 → 修復候選", + "from_agent": "hermes", + "to_agent": "openclaw", + "status": "approval_required", + "required_evidence": "PlayBook trust、MCP evidence refs、risk gate、repair candidate safety。" + }, + { + "lane_id": "repair_candidate_to_replay_review", + "display_name": "修復候選 → replay 審核", + "from_agent": "openclaw", + "to_agent": "nemotron", + "status": "blocked_by_runtime_gate", + "required_evidence": "sanitized fixture、idempotency key、no-write replay result、operator review lane。" + } + ], + "approval_boundaries": { + "redis_connection_allowed": false, + "consumer_group_creation_allowed": false, + "xadd_allowed": false, + "xreadgroup_allowed": false, + "xack_allowed": false, + "dead_letter_write_allowed": false, + "runtime_replay_allowed": false, + "worker_start_allowed": false, + "telegram_send_allowed": false, + "learning_writeback_allowed": false, + "secret_plaintext_allowed": false + }, + "display_redaction_contract": { + "redaction_required": true, + "raw_payload_display_allowed": false, + "private_reasoning_display_allowed": false, + "secret_value_display_allowed": false, + "allowed_frontend_content": [ + "dry-run status", + "handoff lane", + "agent role", + "required fields count", + "blocked runtime action", + "redacted evidence ref", + "next gate" + ], + "forbidden_frontend_content": [ + "未脫敏 payload", + "提示內容", + "私有推理", + "機密值", + "raw tool output" + ], + "frontend_display_policy": "治理頁只顯示 dry-run 狀態、handoff lane、欄位數、阻擋動作與下一步;未脫敏 payload、提示內容、私有推理、機密值與 raw tool output 不進前端。" + }, + "rollups": { + "source_ref_count": 4, + "dry_run_step_count": 5, + "handoff_lane_count": 3, + "contract_ready_step_ids": [ + "ack_decision_fixture", + "dead_letter_fixture", + "fixture_envelope_validation" + ], + "approval_required_step_ids": [ + "consumer_group_plan", + "replay_idempotency_fixture" + ], + "blocked_runtime_action_count": 5, + "required_handoff_field_count": 13, + "forbidden_field_count": 7, + "live_truth_count_total": 0 + } +} diff --git a/docs/schemas/ai_agent_redis_dry_run_gate_v1.schema.json b/docs/schemas/ai_agent_redis_dry_run_gate_v1.schema.json new file mode 100644 index 000000000..d70e529e3 --- /dev/null +++ b/docs/schemas/ai_agent_redis_dry_run_gate_v1.schema.json @@ -0,0 +1,191 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.wooo.work/schemas/ai_agent_redis_dry_run_gate_v1.schema.json", + "title": "AI Agent Redis Dry Run Gate v1", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "dry_run_truth", + "consumer_group_dry_run_contract", + "handoff_envelope_contract", + "ack_dead_letter_replay_contract", + "dry_run_steps", + "handoff_lanes", + "approval_boundaries", + "display_redaction_contract", + "rollups" + ], + "properties": { + "schema_version": {"const": "ai_agent_redis_dry_run_gate_v1"}, + "generated_at": {"type": "string"}, + "program_status": { + "type": "object", + "required": [ + "overall_completion_percent", + "current_task_id", + "next_task_id", + "read_only_mode", + "runtime_authority" + ], + "properties": { + "overall_completion_percent": {"type": "integer", "minimum": 0, "maximum": 100}, + "current_task_id": {"const": "P2-403C"}, + "next_task_id": {"type": "string"}, + "read_only_mode": {"const": true}, + "runtime_authority": {"const": "dry_run_contract_only_no_redis_runtime"} + }, + "additionalProperties": true + }, + "source_refs": {"type": "array", "items": {"type": "string"}, "minItems": 1}, + "dry_run_truth": { + "type": "object", + "required": [ + "redis_connection_allowed", + "consumer_group_created", + "xadd_allowed", + "xreadgroup_allowed", + "ack_allowed", + "dead_letter_write_allowed", + "replay_runtime_allowed", + "telegram_send_allowed", + "learning_writeback_allowed", + "live_dry_run_event_count", + "live_ack_count", + "live_dead_letter_count", + "live_replay_count" + ], + "properties": { + "redis_connection_allowed": {"const": false}, + "consumer_group_created": {"const": false}, + "xadd_allowed": {"const": false}, + "xreadgroup_allowed": {"const": false}, + "ack_allowed": {"const": false}, + "dead_letter_write_allowed": {"const": false}, + "replay_runtime_allowed": {"const": false}, + "telegram_send_allowed": {"const": false}, + "learning_writeback_allowed": {"const": false}, + "live_dry_run_event_count": {"const": 0}, + "live_ack_count": {"const": 0}, + "live_dead_letter_count": {"const": 0}, + "live_replay_count": {"const": 0} + }, + "additionalProperties": true + }, + "consumer_group_dry_run_contract": { + "type": "object", + "required": [ + "candidate_group_name", + "stream_namespace", + "fixture_only", + "redis_network_call_allowed", + "required_fixture_fields" + ], + "properties": { + "fixture_only": {"const": true}, + "redis_network_call_allowed": {"const": false}, + "required_fixture_fields": {"type": "array", "items": {"type": "string"}, "minItems": 1} + }, + "additionalProperties": true + }, + "handoff_envelope_contract": { + "type": "object", + "required": [ + "required_fields", + "forbidden_fields", + "redacted_evidence_required", + "idempotency_key_required" + ], + "properties": { + "required_fields": {"type": "array", "items": {"type": "string"}, "minItems": 1}, + "forbidden_fields": {"type": "array", "items": {"type": "string"}, "minItems": 1}, + "redacted_evidence_required": {"const": true}, + "idempotency_key_required": {"const": true} + }, + "additionalProperties": true + }, + "ack_dead_letter_replay_contract": { + "type": "object", + "required": [ + "ack_requires_verifier", + "dead_letter_requires_reason", + "replay_requires_idempotency", + "runtime_replay_allowed" + ], + "properties": { + "ack_requires_verifier": {"const": true}, + "dead_letter_requires_reason": {"const": true}, + "replay_requires_idempotency": {"const": true}, + "runtime_replay_allowed": {"const": false} + }, + "additionalProperties": true + }, + "dry_run_steps": {"type": "array", "items": {"$ref": "#/$defs/dry_run_step"}, "minItems": 1}, + "handoff_lanes": {"type": "array", "items": {"$ref": "#/$defs/handoff_lane"}, "minItems": 1}, + "approval_boundaries": {"type": "object", "additionalProperties": {"const": false}}, + "display_redaction_contract": { + "type": "object", + "required": [ + "redaction_required", + "raw_payload_display_allowed", + "private_reasoning_display_allowed", + "secret_value_display_allowed", + "forbidden_frontend_content" + ], + "properties": { + "redaction_required": {"const": true}, + "raw_payload_display_allowed": {"const": false}, + "private_reasoning_display_allowed": {"const": false}, + "secret_value_display_allowed": {"const": false}, + "forbidden_frontend_content": {"type": "array", "items": {"type": "string"}, "minItems": 1} + }, + "additionalProperties": true + }, + "rollups": {"type": "object", "additionalProperties": true} + }, + "$defs": { + "dry_run_step": { + "type": "object", + "required": [ + "step_id", + "display_name", + "owner_agent", + "status", + "operator_signal", + "blocked_runtime_action" + ], + "properties": { + "step_id": {"type": "string"}, + "display_name": {"type": "string"}, + "owner_agent": {"type": "string"}, + "status": {"type": "string"}, + "operator_signal": {"type": "string"}, + "blocked_runtime_action": {"type": "string"} + }, + "additionalProperties": true + }, + "handoff_lane": { + "type": "object", + "required": [ + "lane_id", + "display_name", + "from_agent", + "to_agent", + "status", + "required_evidence" + ], + "properties": { + "lane_id": {"type": "string"}, + "display_name": {"type": "string"}, + "from_agent": {"type": "string"}, + "to_agent": {"type": "string"}, + "status": {"type": "string"}, + "required_evidence": {"type": "string"} + }, + "additionalProperties": true + } + }, + "additionalProperties": false +} diff --git a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md index 31cd4287c..38303bcd7 100644 --- a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md +++ b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md @@ -634,7 +634,7 @@ Alert / Sentry / SigNoz / Gitea / Market Watch / Operator | `docs/evaluations/ai_agent_communication_learning_contract_2026-06-11.json` | 2026-06-11 committed snapshot;完成度 `35%`,runtime worker / DB migration / Telegram direct send 全部 false | | `apps/api/src/services/ai_agent_communication_learning_contract.py` | 只讀 loader;強制驗證 runtime / migration / Telegram / SDK / route 權限都未開 | | `GET /api/v1/agents/agent-communication-learning-contract` | 治理 API;只回傳 committed contract,不啟動 worker、不碰 DB/Redis、不呼叫外部服務 | -| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` + `GET /api/v1/agents/agent-interaction-learning-proof` | P2-403A / P2-403B 互動、接手、學習、成長與 read model gate 證據面;目前 live session、message、handoff、learning write、Telegram receipt 全部 `0`,下一步 P2-403C | +| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` + `GET /api/v1/agents/agent-interaction-learning-proof` | P2-403A / P2-403B / P2-403C 互動、接手、學習、成長、read model gate 與 Redis dry-run gate 證據面;目前 live session、message、handoff、learning write、Telegram receipt 全部 `0`,下一步 P2-403D | | `docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json` + `GET /api/v1/agents/agent-live-read-model-gate` | P2-403B AgentSession / Redis Streams live read model gate;定義 safe fields、Redis envelope、worker gate、rollback plan 與 no-write smoke,不連 DB、不讀寫 Redis、不啟動 worker | #### 3.2.1c 2026-06-11 AI Agent 主動營運委派與版本生命週期契約 @@ -693,6 +693,9 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | `docs/schemas/ai_agent_live_read_model_gate_v1.schema.json` | P2-403B AgentSession / Redis Streams live read model gate schema;鎖定 DB migration / live DB query / Redis XADD / consumer group / worker / Telegram / learning writeback 全部 false | | `docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json` | P2-403B committed snapshot:safe selected fields、Redis envelope、worker gate、rollback plan、no-write smoke、frontend redaction;live counts 全部 `0` | | `GET /api/v1/agents/agent-live-read-model-gate` | 只讀 API;只回傳 read model gate,不連 DB、不讀寫 Redis、不啟動 worker、不發 Telegram | +| `docs/schemas/ai_agent_redis_dry_run_gate_v1.schema.json` | P2-403C Redis Streams consumer group dry-run gate schema;鎖定 Redis connection / consumer group / XADD / XREADGROUP / ACK / dead-letter / replay / Telegram / learning writeback 全部 false | +| `docs/evaluations/ai_agent_redis_dry_run_gate_2026-06-11.json` | P2-403C committed snapshot:fixture-only consumer group dry-run、handoff envelope、ack / dead-letter / replay idempotency、frontend redaction;live counts 全部 `0` | +| `GET /api/v1/agents/agent-redis-dry-run-gate` | 只讀 API;只回傳 Redis dry-run gate,不連 Redis、不建立 consumer group、不 XADD / XREADGROUP / ACK、不寫 dead-letter、不 replay、不發 Telegram | | `/zh-TW/governance?tab=automation-inventory` | P2-402G governance UI:顯示可委派能力、版本生命週期、Host / K3s / stateful 只讀關卡與 Telegram / redaction gate;不提供執行按鈕 | **採用順序:** @@ -705,7 +708,8 @@ Repo / registry / release notes / K8s / host / observability / backup evidence 6. 把可委派能力、版本生命週期、Host / K3s / stateful 只讀關卡接入 governance UI。✅ P2-402G 已完成;不顯示敏感端點、不提供執行按鈕。 7. 先建立可被統帥看見的互動 / 接手 / 學習 / 成長證據面。✅ P2-403A 已完成;live count 仍為 `0`,不宣稱 runtime 已運作。 8. 建立 AgentSession / Redis live read model gate。✅ P2-403B 已完成;safe fields、Redis envelope、worker gate、rollback plan、no-write smoke 已接入治理頁,但 live DB query / Redis consumer / worker 仍未授權。 -9. 最後才進人工批准後的 Redis dry-run / smoke / canary / production rollout。下一步 P2-403C Redis Streams consumer group dry-run 與 handoff envelope。 +9. 建立 Redis Streams consumer group dry-run、handoff envelope、ack / dead-letter / replay gate。✅ P2-403C 已完成;fixture-only dry-run、idempotency、redacted evidence 與治理頁已接入,但 Redis runtime consumer / ACK / replay 仍未授權。 +10. 最後才進人工批准後的 learning writeback / smoke / canary / production rollout。下一步 P2-403D learning writeback approval package。 #### 3.2.1d 2026-06-11 Agent 互動、學習與成長證據面 @@ -718,8 +722,8 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | 契約可見 | 三 Agent 分工、MCP/RAG、Telegram/redaction 邊界 | 已完成 | | 證據面板 | 「目前真相」、證據階梯、Agent lane、runtime gate | 已完成 | | Live read model | Agent 心跳、最近狀態、資料時間 | P2-403B gate 已完成;live count 仍為 `0` | -| 訊息收據 | 發話、收話、ack、dead letter、replay | P2-403C 待辦 | -| 接手紀錄 | OpenClaw / Hermes / NemoTron parent turn / handoff chain | P2-403C 待辦 | +| 訊息收據 | 發話、收話、ack、dead letter、replay | P2-403C dry-run gate 已完成;live receipt count 仍為 `0` | +| 接手紀錄 | OpenClaw / Hermes / NemoTron parent turn / handoff chain | P2-403C handoff envelope 已完成;runtime handoff count 仍為 `0` | | 學習回寫 | KM、Playbook trust、learning failure、replay score | P2-403D 待辦 | | Telegram 收據 | action-required / approval / failure digest queued / delivered / acknowledged | P2-403E 待辦 | @@ -728,13 +732,17 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | 檔案 / API | 用途 | |---|---| | `docs/schemas/ai_agent_interaction_learning_proof_v1.schema.json` | 互動、接手、學習、成長、Telegram receipt 與前端 redaction schema | -| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | P2-403A + P2-403B committed snapshot;完成度 `55%`,live truth counts 全部 `0` | +| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | P2-403A + P2-403B + P2-403C committed snapshot;完成度 `60%`,live truth counts 全部 `0` | | `apps/api/src/services/ai_agent_interaction_learning_proof.py` | 只讀 loader;強制 live flags / DB / Redis / Telegram / transcript / 私有推理全部關閉 | | `GET /api/v1/agents/agent-interaction-learning-proof` | 治理 API;只回傳證據面,不啟動 worker、不碰 live DB/Redis、不發 Telegram | | `docs/schemas/ai_agent_live_read_model_gate_v1.schema.json` | P2-403B live read model gate schema;強制 DB / Redis / worker / Telegram / learning writeback 仍需批准 | | `docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json` | P2-403B committed snapshot;safe fields、Redis envelope、worker gate、rollback plan、no-write smoke、frontend redaction | | `apps/api/src/services/ai_agent_live_read_model_gate.py` | 只讀 loader;拒絕 live DB query、Redis consumer、unsafe field projection、Telegram send 與 writeback | | `GET /api/v1/agents/agent-live-read-model-gate` | 治理 API;只回傳 read model gate,不連 DB、不讀寫 Redis、不啟動 worker、不發 Telegram | +| `docs/schemas/ai_agent_redis_dry_run_gate_v1.schema.json` | P2-403C Redis dry-run gate schema;強制 Redis connection / consumer group / ACK / dead-letter / replay / Telegram / learning writeback 仍需批准 | +| `docs/evaluations/ai_agent_redis_dry_run_gate_2026-06-11.json` | P2-403C committed snapshot;fixture-only dry-run、handoff envelope、ack / dead-letter / replay idempotency、frontend redaction | +| `apps/api/src/services/ai_agent_redis_dry_run_gate.py` | 只讀 loader;拒絕 Redis network call、consumer group creation、runtime replay、Telegram send 與 writeback | +| `GET /api/v1/agents/agent-redis-dry-run-gate` | 治理 API;只回傳 Redis dry-run gate,不連 Redis、不建立 consumer group、不 XADD / XREADGROUP / ACK、不 replay、不發 Telegram | | `/zh-TW/governance?tab=automation-inventory` | 顯示證據階梯、目前真相、三 Agent lane、可觀測訊號、runtime gates 與 redaction policy | **硬性紅線:** @@ -1796,6 +1804,20 @@ Phase 6 完成後 - `apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx` 接入 `GET /api/v1/agents/agent-interaction-learning-proof`,顯示「目前真相」、證據階梯、三 Agent lane、可觀測訊號、runtime gates 與前端 redaction。 - 本波仍不啟動 worker、不建立 DB migration、不開 Redis consumer group、不發 Telegram、不寫 learning、不讀取或輸出 secret、不回傳工作視窗對話內容;下一步 P2-403B 才規劃 AgentSession / Redis live read model。 +### 2026-06-11 23:59 (台北) — §3.2 / §5 — 完成 P2-403B AgentSession / Redis live read model gate — 回應統帥要求看見「目前真的跑到哪」 + +- 新增 `ai_agent_live_read_model_gate_v1` schema / committed snapshot / loader / API / 測試,定義 AgentSession safe fields、Redis envelope、worker gate、rollback plan、no-write smoke 與前端 redaction。 +- `apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx` 接入 `GET /api/v1/agents/agent-live-read-model-gate`,顯示 read model gate、live count、禁止項、查詢契約與 no-write smoke。 +- 更新 `ai_agent_interaction_learning_proof_2026-06-11.json`:整體完成度 `55%`,current task `P2-403B`,next task `P2-403C`;live AgentSession / Redis events / handoff / learning write / Telegram digest receipt 全部仍為 `0`。 +- 本波仍不建立 DB migration、不查 live DB、不開 Redis consumer group、不 XADD、不啟動 worker、不發 Telegram、不寫 learning、不讀取或輸出 secret;下一步 P2-403C 才做 Redis dry-run gate。 + +### 2026-06-11 23:59 (台北) — §3.2 / §5 — 完成 P2-403C Redis dry-run gate — 回應統帥要求把 Agent 接手與 ACK / replay 流程先打成可驗證契約 + +- 新增 `ai_agent_redis_dry_run_gate_v1` schema / committed snapshot / loader / API / 測試,定義 fixture-only consumer group dry-run、handoff envelope、ack / dead-letter / replay idempotency、redacted evidence 與 frontend display policy。 +- `apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx` 接入 `GET /api/v1/agents/agent-redis-dry-run-gate`,顯示 Redis dry-run KPI、consumer group 草案、handoff envelope、redaction、dry-run steps 與 handoff lanes。 +- 更新 `ai_agent_interaction_learning_proof_2026-06-11.json`:整體完成度 `60%`,current task `P2-403C`,next task `P2-403D`;live AgentSession / Redis events / handoff / learning write / Telegram digest receipt 全部仍為 `0`。 +- 本波仍不連 Redis、不建立 consumer group、不 XADD / XREADGROUP / XACK、不寫 dead-letter、不 runtime replay、不發 Telegram、不做 learning writeback、不讀取或輸出 secret;下一步 P2-403D 才做 learning writeback approval package。 + ### 2026-04-15 (台北) — 全檔 — 建立 v2 骨架,§0/§1 完成 — 統帥批准「單 MASTER + 4 道閘門」結構 - 從 v1(plans/2026-04-15-MASTER-ai-autonomous-flywheel.md)繼承核心發現