From c44f4515a6d2855d67c837492f34801e37eb5a34 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 11 Jun 2026 19:51:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(governance):=20=E6=8E=A5=E5=85=A5=20Agent?= =?UTF-8?q?=20live=20read=20model=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_live_read_model_gate.py | 217 ++++++++++ ...est_ai_agent_interaction_learning_proof.py | 19 +- ...ai_agent_interaction_learning_proof_api.py | 8 +- .../test_ai_agent_live_read_model_gate.py | 225 ++++++++++ .../test_ai_agent_live_read_model_gate_api.py | 36 ++ ..._ai_agent_proactive_operations_contract.py | 5 +- ...agent_proactive_operations_contract_api.py | 5 +- apps/web/messages/en.json | 42 ++ apps/web/messages/zh-TW.json | 42 ++ .../tabs/automation-inventory-tab.tsx | 171 +++++++- apps/web/src/lib/api-client.ts | 109 +++++ docs/LOGBOOK.md | 38 ++ ...AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md | 10 +- ...T_INTERACTION_LEARNING_PROOF_2026-06-11.md | 31 +- ...I_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md | 15 +- ...interaction_learning_proof_2026-06-11.json | 30 +- ...agent_live_read_model_gate_2026-06-11.json | 306 ++++++++++++++ ...active_operations_contract_2026-06-11.json | 19 +- ..._agent_live_read_model_gate_v1.schema.json | 385 ++++++++++++++++++ ...-04-15-MASTER-ai-autonomous-flywheel-v2.md | 17 +- 21 files changed, 1694 insertions(+), 66 deletions(-) create mode 100644 apps/api/src/services/ai_agent_live_read_model_gate.py create mode 100644 apps/api/tests/test_ai_agent_live_read_model_gate.py create mode 100644 apps/api/tests/test_ai_agent_live_read_model_gate_api.py create mode 100644 docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json create mode 100644 docs/schemas/ai_agent_live_read_model_gate_v1.schema.json diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 2f55da0d8..073c598e7 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -64,6 +64,9 @@ from src.services.ai_agent_host_stateful_version_inventory import ( from src.services.ai_agent_interaction_learning_proof import ( load_latest_ai_agent_interaction_learning_proof, ) +from src.services.ai_agent_live_read_model_gate import ( + load_latest_ai_agent_live_read_model_gate, +) from src.services.ai_agent_proactive_operations_contract import ( load_latest_ai_agent_proactive_operations_contract, ) @@ -602,6 +605,33 @@ async def get_agent_interaction_learning_proof() -> dict[str, Any]: ) from exc +@router.get( + "/agent-live-read-model-gate", + response_model=dict[str, Any], + summary="取得 AI Agent live read model gate", + description=( + "讀取最新已提交的 AgentSession / Redis Streams live read model gate;" + "此端點不連 DB、不讀寫 Redis、不啟動 worker、不建立 DB migration、不送 Telegram、" + "不顯示工作視窗內容、Agent 原始輸出、提示詞、私有推理或機密值。" + ), +) +async def get_agent_live_read_model_gate() -> dict[str, Any]: + """Return the latest read-only AI Agent live read model gate snapshot.""" + try: + return await asyncio.to_thread(load_latest_ai_agent_live_read_model_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_live_read_model_gate_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent live read model gate 無效", + ) from exc + + @router.get( "/agent-proactive-operations-contract", response_model=dict[str, Any], diff --git a/apps/api/src/services/ai_agent_live_read_model_gate.py b/apps/api/src/services/ai_agent_live_read_model_gate.py new file mode 100644 index 000000000..c01b072b3 --- /dev/null +++ b/apps/api/src/services/ai_agent_live_read_model_gate.py @@ -0,0 +1,217 @@ +""" +AI Agent live read model gate snapshot. + +Loads the latest committed, read-only P2-403B gate for the AgentSession / +Redis Streams live read model. This module only validates the approval package; +it never opens a database session, starts workers, creates migrations, reads +Redis consumer groups, sends Telegram messages, or exposes raw Agent outputs. +""" + +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_live_read_model_gate_*.json" +_SCHEMA_VERSION = "ai_agent_live_read_model_gate_v1" + + +def load_latest_ai_agent_live_read_model_gate( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed AI Agent live read model gate snapshot.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError( + f"no AI Agent live read model 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, _SCHEMA_VERSION, str(latest)) + _require_read_only_authority(payload, str(latest)) + _require_storage_safety(payload, str(latest)) + _require_redis_safety(payload, str(latest)) + _require_no_write_smoke(payload, str(latest)) + _require_rollup_consistency(payload, str(latest)) + return payload + + +def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None: + actual = payload.get("schema_version") + if actual != expected: + raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}") + + +def _require_read_only_authority(payload: dict[str, Any], label: str) -> None: + program_status = payload.get("program_status") or {} + if program_status.get("read_only_mode") is not True: + raise ValueError(f"{label}: program_status.read_only_mode must be true") + if program_status.get("runtime_authority") != "gate_plan_only_no_live_worker": + raise ValueError(f"{label}: runtime_authority must stay gate_plan_only_no_live_worker") + + boundaries = payload.get("approval_boundaries") or {} + blocked_flags = { + "db_migration_allowed", + "live_db_query_allowed", + "redis_xadd_allowed", + "redis_consumer_group_allowed", + "runtime_worker_allowed", + "telegram_direct_send_allowed", + "learning_writeback_allowed", + "secret_plaintext_allowed", + "conversation_transcript_display_allowed", + "private_reasoning_display_allowed", + "agent_raw_output_display_allowed", + } + allowed = sorted(flag for flag in blocked_flags if boundaries.get(flag) is not False) + if allowed: + raise ValueError(f"{label}: approval boundaries must remain false: {allowed}") + + live_truth = payload.get("live_truth") or {} + false_flags = { + "live_agent_session_readback_enabled", + "live_redis_stream_read_enabled", + "runtime_worker_enabled", + "telegram_receipt_send_enabled", + "learning_writeback_enabled", + } + enabled = sorted(flag for flag in false_flags if live_truth.get(flag) is not False) + if enabled: + raise ValueError(f"{label}: live truth flags must remain false: {enabled}") + + zero_counts = { + "active_live_agent_sessions", + "live_redis_events_24h", + "live_handoffs_24h", + "live_learning_writes_24h", + "telegram_digest_receipts_24h", + } + non_zero = sorted(key for key in zero_counts if live_truth.get(key) != 0) + if non_zero: + raise ValueError(f"{label}: live truth counts must remain zero: {non_zero}") + + +def _require_storage_safety(payload: dict[str, Any], label: str) -> None: + storage = payload.get("existing_storage_contract") or {} + if storage.get("db_table") != "agent_sessions": + raise ValueError(f"{label}: existing_storage_contract.db_table must be agent_sessions") + if storage.get("approved_for_live_query") is not False: + raise ValueError(f"{label}: live DB query must remain unapproved") + if storage.get("migration_delta_required") is not False: + raise ValueError(f"{label}: migration delta must remain false for this gate") + if storage.get("safe_read_query_defined") is not True: + raise ValueError(f"{label}: safe read query contract must be defined") + + selected_fields = set(storage.get("safe_selected_fields") or []) + forbidden_selected = selected_fields.intersection( + { + "output_json", + "prompt", + "raw_prompt", + "conversation_transcript", + "private_reasoning", + "chain_of_thought", + "secret_plaintext", + "credential_value", + } + ) + if forbidden_selected: + raise ValueError(f"{label}: safe read query selects forbidden fields: {sorted(forbidden_selected)}") + + +def _require_redis_safety(payload: dict[str, Any], label: str) -> None: + redis_contract = payload.get("redis_stream_contract") or {} + if redis_contract.get("consumer_group_allowed") is not False: + raise ValueError(f"{label}: Redis consumer group must remain unapproved") + if redis_contract.get("xadd_allowed") is not False: + raise ValueError(f"{label}: Redis XADD must remain unapproved") + if redis_contract.get("xreadgroup_allowed") is not False: + raise ValueError(f"{label}: Redis XREADGROUP must remain unapproved") + if not redis_contract.get("event_envelope_required_fields"): + raise ValueError(f"{label}: Redis event envelope required fields must be defined") + + +def _require_no_write_smoke(payload: dict[str, Any], label: str) -> None: + smoke_steps = payload.get("no_write_smoke_plan") or [] + if not smoke_steps: + raise ValueError(f"{label}: no_write_smoke_plan must not be empty") + + unsafe_steps = [ + step.get("smoke_id") + for step in smoke_steps + if step.get("writes_allowed") is not False or step.get("status") != "defined" + ] + if unsafe_steps: + raise ValueError(f"{label}: no-write smoke steps must be defined and write-blocked: {unsafe_steps}") + + 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 ( + "work_window_conversation_display_allowed", + "agent_raw_output_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 {} + expected_counts = { + "source_ref_count": len(payload.get("source_refs") or []), + "read_model_card_count": len(payload.get("read_model_cards") or []), + "gate_count": len(payload.get("worker_gate_plan") or []), + "rollback_step_count": len(payload.get("rollback_plan") or []), + "no_write_smoke_count": len(payload.get("no_write_smoke_plan") or []), + "forbidden_frontend_content_count": len( + (payload.get("display_redaction_contract") or {}).get("forbidden_frontend_content") 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}") + + approval_required_gate_ids = sorted( + gate.get("gate_id") + for gate in payload.get("worker_gate_plan") or [] + if gate.get("status") in {"approval_required", "blocked"} + ) + if sorted(rollups.get("approval_required_gate_ids") or []) != approval_required_gate_ids: + raise ValueError(f"{label}: rollups.approval_required_gate_ids mismatch") + + ready_cards = sorted( + card.get("card_id") + for card in payload.get("read_model_cards") or [] + if card.get("readiness_status") == "query_contract_ready" + ) + if sorted(rollups.get("query_contract_ready_card_ids") or []) != ready_cards: + raise ValueError(f"{label}: rollups.query_contract_ready_card_ids mismatch") + + live_truth = payload.get("live_truth") or {} + live_count_total = sum( + int(live_truth.get(key) or 0) + for key in ( + "active_live_agent_sessions", + "live_redis_events_24h", + "live_handoffs_24h", + "live_learning_writes_24h", + "telegram_digest_receipts_24h", + ) + ) + if rollups.get("live_truth_count_total") != live_count_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 510ba7a23..fe0482089 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"] == 45 - assert data["program_status"]["current_task_id"] == "P2-403A" - assert data["program_status"]["next_task_id"] == "P2-403B" + 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"]["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,7 +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"]) == { - "live_read_model", "message_bus_receipts", "handoff_receipts", "learning_growth", @@ -98,10 +97,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": 45, + "overall_completion_percent": 55, "current_priority": "P2", - "current_task_id": "P2-403A", - "next_task_id": "P2-403B", + "current_task_id": "P2-403B", + "next_task_id": "P2-403C", "read_only_mode": True, "runtime_authority": "proof_surface_only_no_live_worker", }, @@ -120,7 +119,7 @@ def _snapshot() -> dict: "proof_ladder": [ _proof_level("contract_surface", "contract_ready"), _proof_level("proof_dashboard", "proof_surface_ready"), - _proof_level("live_read_model", "live_pending"), + _proof_level("live_read_model", "contract_ready"), ], "agent_lanes": [ _agent_lane("openclaw"), @@ -189,8 +188,8 @@ def _snapshot() -> dict: }, "rollups": { "proof_level_count": 3, - "contract_ready_level_count": 2, - "live_pending_level_ids": ["live_read_model"], + "contract_ready_level_count": 3, + "live_pending_level_ids": [], "signal_count": 1, "live_signal_count": 0, "operator_surface_count": 1, 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 c19af159a..4afe03a5c 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"] == 45 - assert data["program_status"]["current_task_id"] == "P2-403A" - assert data["program_status"]["next_task_id"] == "P2-403B" + 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"]["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"] == 2 + assert data["rollups"]["contract_ready_level_count"] == 3 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_live_read_model_gate.py b/apps/api/tests/test_ai_agent_live_read_model_gate.py new file mode 100644 index 000000000..5b3dfc808 --- /dev/null +++ b/apps/api/tests/test_ai_agent_live_read_model_gate.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +import json + +import pytest + +from src.services.ai_agent_live_read_model_gate import ( + load_latest_ai_agent_live_read_model_gate, +) + + +def test_load_latest_ai_agent_live_read_model_gate_reads_committed_snapshot(): + data = load_latest_ai_agent_live_read_model_gate() + + assert data["schema_version"] == "ai_agent_live_read_model_gate_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"]["read_only_mode"] is True + assert data["program_status"]["runtime_authority"] == "gate_plan_only_no_live_worker" + assert data["existing_storage_contract"]["db_table"] == "agent_sessions" + assert data["existing_storage_contract"]["approved_for_live_query"] is False + assert data["existing_storage_contract"]["migration_delta_required"] is False + assert "output_json" not in data["existing_storage_contract"]["safe_selected_fields"] + assert data["redis_stream_contract"]["consumer_group_allowed"] is False + assert data["redis_stream_contract"]["xadd_allowed"] is False + assert data["live_truth"]["active_live_agent_sessions"] == 0 + assert data["live_truth"]["live_redis_events_24h"] == 0 + assert data["approval_boundaries"]["runtime_worker_allowed"] is False + assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False + assert data["display_redaction_contract"]["work_window_conversation_display_allowed"] is False + assert data["display_redaction_contract"]["agent_raw_output_display_allowed"] is False + assert data["rollups"]["read_model_card_count"] == len(data["read_model_cards"]) == 5 + assert data["rollups"]["gate_count"] == len(data["worker_gate_plan"]) == 5 + assert data["rollups"]["rollback_step_count"] == len(data["rollback_plan"]) == 4 + assert data["rollups"]["no_write_smoke_count"] == len(data["no_write_smoke_plan"]) == 5 + assert data["rollups"]["live_truth_count_total"] == 0 + assert set(data["rollups"]["query_contract_ready_card_ids"]) == { + "agent_heartbeat_readback", + "agent_vote_quality", + "agent_role_turns", + } + + +def test_load_latest_ai_agent_live_read_model_gate_rejects_live_db_query(tmp_path): + snapshot = _snapshot() + snapshot["approval_boundaries"]["live_db_query_allowed"] = True + (tmp_path / "ai_agent_live_read_model_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="approval boundaries"): + load_latest_ai_agent_live_read_model_gate(tmp_path) + + +def test_load_latest_ai_agent_live_read_model_gate_rejects_unsafe_field_projection(tmp_path): + snapshot = _snapshot() + snapshot["existing_storage_contract"]["safe_selected_fields"].append("output_json") + (tmp_path / "ai_agent_live_read_model_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="forbidden fields"): + load_latest_ai_agent_live_read_model_gate(tmp_path) + + +def test_load_latest_ai_agent_live_read_model_gate_rejects_redis_consumer_group(tmp_path): + snapshot = _snapshot() + snapshot["redis_stream_contract"]["consumer_group_allowed"] = True + (tmp_path / "ai_agent_live_read_model_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="Redis consumer group"): + load_latest_ai_agent_live_read_model_gate(tmp_path) + + +def test_load_latest_ai_agent_live_read_model_gate_rejects_write_smoke(tmp_path): + snapshot = _snapshot() + snapshot["no_write_smoke_plan"][0]["writes_allowed"] = True + (tmp_path / "ai_agent_live_read_model_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="no-write smoke"): + load_latest_ai_agent_live_read_model_gate(tmp_path) + + +def test_load_latest_ai_agent_live_read_model_gate_rejects_rollup_mismatch(tmp_path): + snapshot = _snapshot() + snapshot["rollups"]["gate_count"] = 99 + (tmp_path / "ai_agent_live_read_model_gate_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="rollup counts"): + load_latest_ai_agent_live_read_model_gate(tmp_path) + + +def _snapshot() -> dict: + return { + "schema_version": "ai_agent_live_read_model_gate_v1", + "generated_at": "2026-06-11T23:50:00+08:00", + "program_status": { + "overall_completion_percent": 55, + "current_priority": "P2", + "current_task_id": "P2-403B", + "next_task_id": "P2-403C", + "read_only_mode": True, + "runtime_authority": "gate_plan_only_no_live_worker", + }, + "source_refs": ["test"], + "live_truth": { + "live_agent_session_readback_enabled": False, + "live_redis_stream_read_enabled": False, + "runtime_worker_enabled": False, + "telegram_receipt_send_enabled": False, + "learning_writeback_enabled": False, + "active_live_agent_sessions": 0, + "live_redis_events_24h": 0, + "live_handoffs_24h": 0, + "live_learning_writes_24h": 0, + "telegram_digest_receipts_24h": 0, + }, + "existing_storage_contract": { + "db_table": "agent_sessions", + "schema_status": "existing", + "migration_delta_required": False, + "approved_for_live_query": False, + "safe_read_query_defined": True, + "safe_selected_fields": ["id", "session_id", "agent_role", "created_at"], + "forbidden_selected_fields": ["output_json", "raw_prompt", "secret_plaintext"], + "required_indexes": ["ix_agent_sessions_created_at"], + "read_query_contract": "SELECT id FROM agent_sessions LIMIT :safe_limit", + }, + "redis_stream_contract": { + "stream_namespace": "awoooi:agent:*", + "candidate_streams": ["awoooi:agent:events"], + "consumer_group_allowed": False, + "xadd_allowed": False, + "xreadgroup_allowed": False, + "event_envelope_required_fields": ["event_id", "session_id", "agent_id"], + }, + "read_model_cards": [ + _card("agent_heartbeat_readback", "query_contract_ready"), + _card("redis_message_receipts", "approval_required"), + ], + "worker_gate_plan": [ + _gate("db_read_model_no_write_smoke", "approval_required"), + _gate("worker_kill_switch", "blocked"), + ], + "rollback_plan": [ + {"rollback_id": "disable", "step": "停用。", "owner_agent": "openclaw"}, + ], + "no_write_smoke_plan": [ + { + "smoke_id": "loader", + "status": "defined", + "writes_allowed": False, + "assertion": "測試。", + }, + ], + "display_redaction_contract": { + "redaction_required": True, + "work_window_conversation_display_allowed": False, + "agent_raw_output_display_allowed": False, + "secret_value_display_allowed": False, + "forbidden_frontend_content": ["工作視窗內容"], + }, + "approval_boundaries": { + "db_migration_allowed": False, + "live_db_query_allowed": False, + "redis_xadd_allowed": False, + "redis_consumer_group_allowed": False, + "runtime_worker_allowed": False, + "telegram_direct_send_allowed": False, + "learning_writeback_allowed": False, + "secret_plaintext_allowed": False, + "conversation_transcript_display_allowed": False, + "private_reasoning_display_allowed": False, + "agent_raw_output_display_allowed": False, + }, + "rollups": { + "source_ref_count": 1, + "read_model_card_count": 2, + "gate_count": 2, + "approval_required_gate_ids": [ + "db_read_model_no_write_smoke", + "worker_kill_switch", + ], + "query_contract_ready_card_ids": ["agent_heartbeat_readback"], + "rollback_step_count": 1, + "no_write_smoke_count": 1, + "forbidden_frontend_content_count": 1, + "live_truth_count_total": 0, + }, + } + + +def _card(card_id: str, status: str) -> dict: + return { + "card_id": card_id, + "display_name": card_id, + "owner_agent": "openclaw", + "source_of_truth": "agent_sessions", + "readiness_status": status, + "operator_signal": "測試。", + "next_gate": "測試。", + } + + +def _gate(gate_id: str, status: str) -> dict: + return { + "gate_id": gate_id, + "display_name": gate_id, + "owner_agent": "openclaw", + "status": status, + "required_evidence": "測試。", + "blocked_action": "test", + } diff --git a/apps/api/tests/test_ai_agent_live_read_model_gate_api.py b/apps/api/tests/test_ai_agent_live_read_model_gate_api.py new file mode 100644 index 000000000..b3d784822 --- /dev/null +++ b/apps/api/tests/test_ai_agent_live_read_model_gate_api.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1.agents import router + + +def test_ai_agent_live_read_model_gate_endpoint_returns_committed_snapshot(): + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get("/api/v1/agents/agent-live-read-model-gate") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_live_read_model_gate_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"]["read_only_mode"] is True + assert data["program_status"]["runtime_authority"] == "gate_plan_only_no_live_worker" + assert data["existing_storage_contract"]["db_table"] == "agent_sessions" + assert data["existing_storage_contract"]["approved_for_live_query"] is False + assert data["redis_stream_contract"]["consumer_group_allowed"] is False + assert data["live_truth"]["active_live_agent_sessions"] == 0 + assert data["live_truth"]["live_redis_events_24h"] == 0 + assert data["approval_boundaries"]["live_db_query_allowed"] is False + assert data["approval_boundaries"]["redis_consumer_group_allowed"] is False + assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False + assert data["display_redaction_contract"]["work_window_conversation_display_allowed"] is False + assert data["rollups"]["read_model_card_count"] == 5 + assert data["rollups"]["gate_count"] == 5 + assert data["rollups"]["no_write_smoke_count"] == 5 + assert data["rollups"]["live_truth_count_total"] == 0 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 4060731cc..aa35d31a1 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-403A" - assert data["program_status"]["next_task_id"] == "P2-403B" + assert data["program_status"]["current_task_id"] == "P2-403B" + assert data["program_status"]["next_task_id"] == "P2-403C" 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,6 +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"]["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 a3205149c..cfd4ae6cc 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-403A" - assert data["program_status"]["next_task_id"] == "P2-403B" + assert data["program_status"]["current_task_id"] == "P2-403B" + assert data["program_status"]["next_task_id"] == "P2-403C" 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,6 +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"]["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/web/messages/en.json b/apps/web/messages/en.json index 1b2feb389..7879b0377 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -3084,6 +3084,48 @@ "blocked_by_gate": "被 gate 阻擋" } }, + "liveReadModelGate": { + "title": "P2-403B Live Read Model Gate", + "source": "{generated} · {current} → {next}", + "storageTitle": "AgentSession 唯讀查詢", + "storageDetail": "{table} · safe fields {fields} · index {indexes} · limit {limit}", + "redisTitle": "Redis Streams Gate", + "redisDetail": "{namespace} · candidate streams {streams} · envelope fields {fields}", + "redactionTitle": "Readback 顯示紅線", + "gatesTitle": "Worker Gate", + "metrics": { + "overall": "P2-403B 進度", + "readyCards": "查詢契約", + "approvalGates": "需批准 gate", + "noWriteSmokes": "無寫入 smoke", + "liveTotal": "Live 筆數", + "forbidden": "前端禁止項" + }, + "flags": { + "liveQuery": "Live DB query: {value}", + "migration": "Migration delta: {value}", + "consumer": "Consumer group: {value}", + "xadd": "XADD: {value}", + "xreadgroup": "XREADGROUP: {value}" + }, + "labels": { + "safeFields": "Safe fields {count}", + "redactionRequired": "redaction required: {value}", + "forbiddenCount": "禁止項 {count}" + }, + "agents": { + "openclaw": "OpenClaw", + "hermes": "Hermes", + "nemotron": "NemoTron" + }, + "statuses": { + "query_contract_ready": "查詢契約就緒", + "approval_required": "需批准", + "blocked_by_writeback_gate": "被 writeback 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 1b2feb389..7879b0377 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -3084,6 +3084,48 @@ "blocked_by_gate": "被 gate 阻擋" } }, + "liveReadModelGate": { + "title": "P2-403B Live Read Model Gate", + "source": "{generated} · {current} → {next}", + "storageTitle": "AgentSession 唯讀查詢", + "storageDetail": "{table} · safe fields {fields} · index {indexes} · limit {limit}", + "redisTitle": "Redis Streams Gate", + "redisDetail": "{namespace} · candidate streams {streams} · envelope fields {fields}", + "redactionTitle": "Readback 顯示紅線", + "gatesTitle": "Worker Gate", + "metrics": { + "overall": "P2-403B 進度", + "readyCards": "查詢契約", + "approvalGates": "需批准 gate", + "noWriteSmokes": "無寫入 smoke", + "liveTotal": "Live 筆數", + "forbidden": "前端禁止項" + }, + "flags": { + "liveQuery": "Live DB query: {value}", + "migration": "Migration delta: {value}", + "consumer": "Consumer group: {value}", + "xadd": "XADD: {value}", + "xreadgroup": "XREADGROUP: {value}" + }, + "labels": { + "safeFields": "Safe fields {count}", + "redactionRequired": "redaction required: {value}", + "forbiddenCount": "禁止項 {count}" + }, + "agents": { + "openclaw": "OpenClaw", + "hermes": "Hermes", + "nemotron": "NemoTron" + }, + "statuses": { + "query_contract_ready": "查詢契約就緒", + "approval_required": "需批准", + "blocked_by_writeback_gate": "被 writeback 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 7e08c9bc1..bbd5bf9c1 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 @@ -38,6 +38,7 @@ import { type AiAgentDeploymentLayoutSnapshot, type AiAgentHostStatefulVersionInventorySnapshot, type AiAgentInteractionLearningProofSnapshot, + type AiAgentLiveReadModelGateSnapshot, type AiAgentProactiveOperationsContractSnapshot, type AiProviderRouteMatrixSnapshot, type AiAgentAutomationBacklogSnapshot, @@ -318,6 +319,7 @@ export function AutomationInventoryTab() { const [deploymentLayout, setDeploymentLayout] = useState(null) const [proactiveOperations, setProactiveOperations] = useState(null) const [interactionLearningProof, setInteractionLearningProof] = useState(null) + const [liveReadModelGate, setLiveReadModelGate] = useState(null) const [hostStatefulInventory, setHostStatefulInventory] = useState(null) const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState(null) const [serviceHealthNotificationPolicy, setServiceHealthNotificationPolicy] = useState(null) @@ -340,6 +342,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentDeploymentLayout(), apiClient.getAiAgentProactiveOperationsContract(), apiClient.getAiAgentInteractionLearningProof(), + apiClient.getAiAgentLiveReadModelGate(), apiClient.getAiAgentHostStatefulVersionInventory(), apiClient.getServiceHealthGapMatrix(), apiClient.getServiceHealthFailureNotificationPolicy(), @@ -361,6 +364,7 @@ export function AutomationInventoryTab() { deploymentLayoutResult, proactiveOperationsResult, interactionLearningProofResult, + liveReadModelGateResult, hostStatefulInventoryResult, serviceHealthGapMatrixResult, serviceHealthNotificationPolicyResult, @@ -379,6 +383,7 @@ export function AutomationInventoryTab() { setDeploymentLayout(deploymentLayoutResult.status === 'fulfilled' ? deploymentLayoutResult.value : null) setProactiveOperations(proactiveOperationsResult.status === 'fulfilled' ? proactiveOperationsResult.value : null) setInteractionLearningProof(interactionLearningProofResult.status === 'fulfilled' ? interactionLearningProofResult.value : null) + setLiveReadModelGate(liveReadModelGateResult.status === 'fulfilled' ? liveReadModelGateResult.value : null) setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null) setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null) setServiceHealthNotificationPolicy(serviceHealthNotificationPolicyResult.status === 'fulfilled' ? serviceHealthNotificationPolicyResult.value : null) @@ -395,6 +400,7 @@ export function AutomationInventoryTab() { deploymentLayoutResult, proactiveOperationsResult, interactionLearningProofResult, + liveReadModelGateResult, hostStatefulInventoryResult, serviceHealthGapMatrixResult, serviceHealthNotificationPolicyResult, @@ -560,6 +566,34 @@ export function AutomationInventoryTab() { .slice(0, 5) }, [interactionLearningProof]) + const visibleLiveReadModelCards = useMemo(() => { + if (!liveReadModelGate) return [] + const priority = { + blocked_by_writeback_gate: 0, + approval_required: 1, + query_contract_ready: 2, + } as Record + return [...liveReadModelGate.read_model_cards] + .sort((a, b) => { + const left = priority[a.readiness_status] ?? 3 + const right = priority[b.readiness_status] ?? 3 + if (left !== right) return left - right + return a.card_id.localeCompare(b.card_id) + }) + }, [liveReadModelGate]) + + const visibleLiveReadModelGates = useMemo(() => { + if (!liveReadModelGate) return [] + const priority = { blocked: 0, approval_required: 1, ready: 2 } as Record + return [...liveReadModelGate.worker_gate_plan] + .sort((a, b) => { + const left = priority[a.status] ?? 3 + const right = priority[b.status] ?? 3 + if (left !== right) return left - right + return a.gate_id.localeCompare(b.gate_id) + }) + }, [liveReadModelGate]) + const visibleReadinessRows = useMemo(() => { if (!backupReadiness) return [] const priority = { blocked: 0, action_required: 1, deferred: 2, ready: 3 } as Record @@ -703,7 +737,7 @@ export function AutomationInventoryTab() { ) } - if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { + if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -791,6 +825,12 @@ export function AutomationInventoryTab() { const proofVisibleSurfaces = interactionLearningProof.rollups.operator_surface_count const proofSignalCount = interactionLearningProof.rollups.signal_count const proofBlockedGates = interactionLearningProof.rollups.blocked_gate_ids.length + const liveReadModelOverall = liveReadModelGate.program_status.overall_completion_percent + const liveReadModelReadyCards = liveReadModelGate.rollups.query_contract_ready_card_ids.length + const liveReadModelApprovalGates = liveReadModelGate.rollups.approval_required_gate_ids.length + 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 hostReadonlyDeniedCount = ( hostStatefulInventory.rollups.ssh_login_allowed_count + hostStatefulInventory.rollups.kubectl_command_execution_allowed_count @@ -984,6 +1024,14 @@ export function AutomationInventoryTab() { } } + const liveReadModelValueLabel = (group: string, value: string) => { + try { + return t(`liveReadModelGate.${group}.${value}` as never) + } catch { + return value + } + } + const hostStatefulValueLabel = (group: string, value: string) => { try { return t(`hostStateful.${group}.${value}` as never) @@ -1332,6 +1380,124 @@ export function AutomationInventoryTab() {
+
+
+
+ + + {t('liveReadModelGate.title')} + +
+ +
+ +
+ } /> + } /> + 0 ? 'danger' : 'ok'} icon={} /> + } /> + } /> + } /> +
+ +
+
+
+ {t('liveReadModelGate.storageTitle')} + + {t('liveReadModelGate.storageDetail', { + table: liveReadModelGate.existing_storage_contract.db_table, + fields: liveReadModelGate.existing_storage_contract.safe_selected_fields.length, + indexes: liveReadModelGate.existing_storage_contract.required_indexes.length, + limit: liveReadModelGate.existing_storage_contract.query_limits.max_limit, + })} + +
+ + + +
+
+ +
+ {t('liveReadModelGate.redisTitle')} + + {t('liveReadModelGate.redisDetail', { + namespace: liveReadModelGate.redis_stream_contract.stream_namespace, + streams: liveReadModelGate.redis_stream_contract.candidate_streams.length, + fields: liveReadModelGate.redis_stream_contract.event_envelope_required_fields.length, + })} + +
+ + + +
+
+ +
+ {t('liveReadModelGate.redactionTitle')} + + {liveReadModelGate.display_redaction_contract.frontend_display_policy} + +
+ + +
+
+
+ +
+
+ {visibleLiveReadModelCards.map(card => ( +
+
+ + {card.card_id} + + +
+ + {card.display_name} + + + {card.operator_signal} + +
+ + +
+
+ ))} +
+ +
+ {t('liveReadModelGate.gatesTitle')} + {visibleLiveReadModelGates.map(gate => ( +
+
+ + {gate.gate_id} + + +
+ + {gate.required_evidence} + +
+ ))} +
+
+
+
+
@@ -3066,6 +3232,9 @@ export function AutomationInventoryTab() { .automation-inventory-proof-grid, .automation-inventory-proof-ladder-grid, .automation-inventory-proof-signal-grid, + .automation-inventory-live-read-kpi-grid, + .automation-inventory-live-read-grid, + .automation-inventory-live-read-card-grid, .automation-inventory-service-health-kpi-grid, .automation-inventory-service-health-map-grid, .automation-inventory-service-health-evidence-grid, diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 32c826239..ceb549741 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -277,6 +277,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiAgentLiveReadModelGate() { + const res = await fetch(`${API_BASE_URL}/agents/agent-live-read-model-gate`) + return handleResponse(res) + }, + async getAiAgentHostStatefulVersionInventory() { const res = await fetch(`${API_BASE_URL}/agents/agent-host-stateful-version-inventory`) return handleResponse(res) @@ -1143,6 +1148,110 @@ export interface AiAgentInteractionLearningProofSnapshot { } } +export interface AiAgentLiveReadModelGateSnapshot { + schema_version: 'ai_agent_live_read_model_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: 'gate_plan_only_no_live_worker' + status_note: string + } + source_refs: string[] + live_truth: { + live_agent_session_readback_enabled: false + live_redis_stream_read_enabled: false + runtime_worker_enabled: false + telegram_receipt_send_enabled: false + learning_writeback_enabled: false + active_live_agent_sessions: number + live_redis_events_24h: number + live_handoffs_24h: number + live_learning_writes_24h: number + telegram_digest_receipts_24h: number + truth_note: string + } + existing_storage_contract: { + db_table: string + schema_status: string + migration_delta_required: false + approved_for_live_query: false + safe_read_query_defined: true + safe_selected_fields: string[] + forbidden_selected_fields: string[] + required_indexes: string[] + read_query_contract: string + query_limits: { + default_window_hours: number + max_limit: number + order_by: string + } + } + redis_stream_contract: { + stream_namespace: string + candidate_streams: string[] + consumer_group_allowed: false + xadd_allowed: false + xreadgroup_allowed: false + dead_letter_required: boolean + replay_required_before_worker: boolean + event_envelope_required_fields: string[] + forbidden_event_fields: string[] + } + read_model_cards: Array<{ + card_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + source_of_truth: string + readiness_status: string + operator_signal: string + next_gate: string + }> + worker_gate_plan: Array<{ + gate_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + status: string + required_evidence: string + blocked_action: string + }> + rollback_plan: Array<{ + rollback_id: string + step: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + }> + no_write_smoke_plan: Array<{ + smoke_id: string + status: 'defined' + writes_allowed: false + assertion: string + }> + display_redaction_contract: { + redaction_required: true + work_window_conversation_display_allowed: false + agent_raw_output_display_allowed: false + secret_value_display_allowed: false + allowed_frontend_content: string[] + forbidden_frontend_content: string[] + frontend_display_policy: string + } + approval_boundaries: Record + rollups: { + source_ref_count: number + read_model_card_count: number + gate_count: number + approval_required_gate_ids: string[] + query_contract_ready_card_ids: string[] + rollback_step_count: number + no_write_smoke_count: number + forbidden_frontend_content_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 912023bf9..b1da9ce6d 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -8,6 +8,44 @@ **完成度與邊界**:P0-9 MCP evidence / PlayBook 修復候選:D5 `88%`;Telegram 監控治理長期項 `96% -> 97%`。D5 讓缺候選路徑產出可追蹤的服務 coverage gap,但仍不代表 runtime execution、PlayBook acceptance、verifier、KM 回寫或 live-fire 自動修復已完成。active runtime gate 仍 `0`;不 SSH、不 active scan、不讀 secret payload、不重啟服務 / 主機、不發 Telegram 測試通知、不直接執行修復。 +## 2026-06-11|P2-403B AI Agent Live Read Model Gate + +**背景**:P2-403A 已讓統帥在治理頁看到 OpenClaw / Hermes / NemoTron 的互動、接手、學習與成長證據面,但 live AgentSession / Redis / Telegram / learning writeback 仍全為 `0`。本階段接續建立 AgentSession / Redis Streams live read model gate,讓下一步要如何證明 Agent 真的互相溝通、接手、學習與成長變成可審查、可回滾、可測試的只讀批准包。 + +**本輪完成**: + +- 新增 `docs/schemas/ai_agent_live_read_model_gate_v1.schema.json` 與 `docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json`,定義既有 `agent_sessions` safe selected fields、Redis Streams event envelope、worker gate plan、rollback plan、no-write smoke 與 frontend redaction contract。 +- 新增 `apps/api/src/services/ai_agent_live_read_model_gate.py` 與 `GET /api/v1/agents/agent-live-read-model-gate`。此 endpoint 只回 committed snapshot,不連 DB、不讀寫 Redis、不啟動 worker、不建立 migration、不發 Telegram。 +- 更新 `ai_agent_interaction_learning_proof_v1` snapshot:目前任務改為 `P2-403B`,下一步 `P2-403C`,整體三 Agent 互動學習證據完成度 `55%`;live count 全部仍為 `0`。 +- 更新 `ai_agent_proactive_operations_contract_v1` snapshot:rollout task 增加 `P2-403B`,下一步為 Redis Streams consumer group dry-run。 +- 更新 governance UI:`/zh-TW/governance?tab=automation-inventory` 新增「P2-403B Live Read Model Gate」區塊,顯示 safe fields、Redis gate、read model cards、worker gates、rollback / no-write smoke 與 redaction 狀態。 +- 更新 `docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md`、`docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md`、`docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md` 與 MASTER §3.2.1b / §3.2.1c / §3.2.1d。 + +**本地驗證**: + +- `python -m json.tool`:P2-403B snapshot / schema、P2-403A interaction snapshot、proactive snapshot 通過。 +- `DATABASE_URL=postgresql://test:test@localhost:5432/test PYTHONPATH=apps/api pytest -q apps/api/tests/test_ai_agent_live_read_model_gate.py apps/api/tests/test_ai_agent_live_read_model_gate_api.py apps/api/tests/test_ai_agent_interaction_learning_proof.py apps/api/tests/test_ai_agent_interaction_learning_proof_api.py`:`13 passed`。 +- `DATABASE_URL=postgresql://test:test@localhost:5432/test PYTHONPATH=apps/api pytest -q apps/api/tests/test_ai_agent_proactive_operations_contract.py apps/api/tests/test_ai_agent_proactive_operations_contract_api.py`:`5 passed`。 +- `python -m py_compile apps/api/src/services/ai_agent_live_read_model_gate.py apps/api/src/api/v1/agents.py`:通過。 +- `pnpm --filter @awoooi/web typecheck`:通過。 +- `NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --filter @awoooi/web build`:通過,`/zh-TW/governance` First Load JS `393 kB`。 +- `python3 scripts/ops/doc-secrets-sanity-check.py docs .gitea`:`DOC_SECRET_SANITY_OK scanned_files=671`。 +- `python` API readback:`agent-live-read-model-gate` 回 `ai_agent_live_read_model_gate_v1 / P2-403B -> P2-403C / completion 55 / live_total 0 / approval gates 5`;`agent-interaction-learning-proof` 回 `P2-403B -> P2-403C / completion 55 / live sessions 0`。 +- `git diff --check`、i18n JSON parse、zh-TW / en key diff:通過。 + +**完成度同步**: + +- P2-403B:`100%`。 +- 三 Agent 主動溝通、學習與成長證據:`55%`。 +- live AgentSession:`0`。 +- live Redis events:`0`。 +- live handoff:`0`。 +- live learning write:`0`。 +- Telegram digest receipt:`0`。 +- Next task:`P2-403C` Redis Streams consumer group dry-run、handoff envelope、ack / dead letter / replay。 + +**邊界**:本波仍不啟動 worker、不建立 DB migration、不讀 live DB、不開 Redis consumer group、不 XADD、不發 Telegram、不寫 learning、不 SSH、不 kubectl、不升級、不重啟、不讀取或輸出 secret、不顯示工作視窗內容、不提供前端執行按鈕。 + ## 2026-06-11|P0 MCP evidence / PlayBook 修復候選 D4 **背景**:D3 已把 Telegram 的「修復候選草案」連到 AwoooP Work Items,但 operator 點進工作項後仍缺明確處置板,無法直接看到 PlayBook 草案必填欄位、禁止誤讀原因、下一步與 Runs / 審批關聯。本段補 Work Items 詳細呈現,回應「批准後沒有自動化,也沒有人工操作選項」的實際痛點;這仍是 read-only 接手面,不是 runtime execution。 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 0c12d036b..9f6906dd6 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 主動溝通、學習與成長證據 | 45% | P2-401A 已完成只讀 contract;P2-403A 已完成互動 / 接手 / 學習 / 成長證據面板、API 與治理頁顯示。runtime worker、DB migration、Redis consumer group、Telegram 實發、learning writeback、SDK / 付費服務仍未開 gate | `ai_agent_communication_learning_contract_v1`、`ai_agent_interaction_learning_proof_v1`、`GET /api/v1/agents/agent-communication-learning-contract`、`GET /api/v1/agents/agent-interaction-learning-proof`、`/zh-TW/governance?tab=automation-inventory`、MASTER §3.2.1b / §3.2.1d / §3.4.3 | +| 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 | | 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 主動溝通、學習與成長證據目前完成度:**45%**。已完成只讀契約、互動 / 接手 / 學習 / 成長證據面板、API、治理頁顯示、測試與 MASTER 同步;目前 live AgentSession、Agent message、handoff、learning write、Telegram receipt 仍全部為 `0`,下一步依優先順序推 `P2-403B` AgentSession / Redis Streams read model 與 worker gate,但在批准前仍不得啟動 runtime loop。 +三 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。 -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 AgentSession / Redis read model,外部 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` 已先補互動、學習證據面與 live read model gate。下一步是 `P2-403C` Redis Streams dry-run,外部 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,`ai_agent_interaction_learning_proof_v1` + `GET /api/v1/agents/agent-interaction-learning-proof` + governance UI;live count 仍為 0 | +| 互動、接手、學習與成長證據面 | 已完成: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 | | NemoTron 3 Ultra smoke | 待辦:P3-401,需 source refresh + cost/data approval | ## 4. 工作流總覽 @@ -960,7 +960,7 @@ UI: | P2-402F | 完成 | 100 | OpenClaw | 建立 host OS / K3s / stateful services 版本只讀盤點 | `ai_agent_host_stateful_version_inventory_v1` / snapshot / 只讀 API / 測試;5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務、6 個只讀 probe step、maintenance window approval package | 只讀;SSH、kubectl、apt / K3s upgrade、node drain、reboot、stateful restart、Telegram 全部未啟用 | | 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 | 待辦 | 0 | OpenClaw | AgentSession / Redis Streams live read model 與 worker gate planning | migration proposal、唯讀查詢、回滾計畫、無寫入 smoke | DB / Redis approval | +| 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-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 e1e62f37e..a9e6e9114 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 只讀證據面、API、治理頁 UI 與後續 runtime gate 分析。 -> 事實邊界:本波只建立可見證據面,不啟動 runtime worker、不建立 DB migration、不開 Redis consumer group、不發 Telegram、不顯示工作視窗對話內容。 +> 文件定位:P2-403A 證據面 + P2-403B AgentSession / Redis Streams live read model gate、API、治理頁 UI 與後續 runtime gate 分析。 +> 事實邊界:本波只建立可見證據面與 read model gate,不啟動 runtime worker、不建立 DB migration、不開 Redis consumer group、不發 Telegram、不顯示工作視窗對話內容。 ## 1. 結論 -已完成 P2-403A:讓統帥能在治理頁看到 OpenClaw / Hermes / NemoTron 的互動、接手、學習與成長是否真的有證據。 +已完成 P2-403A 與 P2-403B:讓統帥能在治理頁看到 OpenClaw / Hermes / NemoTron 的互動、接手、學習與成長是否真的有證據,並看到 live read model 下一步要通過哪些 DB / Redis / worker gate。 目前真相: @@ -14,19 +14,23 @@ |---|---| | 契約與證據面板 | 已完成 | | `GET /api/v1/agents/agent-interaction-learning-proof` | 已完成 | +| `GET /api/v1/agents/agent-live-read-model-gate` | 已完成,只讀 gate | | `/zh-TW/governance?tab=automation-inventory` 顯示 | 已完成 | +| AgentSession safe read fields | 已定義,未查 live DB | +| Redis Streams envelope / consumer gate | 已定義,consumer group 未啟用 | +| rollback plan / no-write smoke | 已定義 | | live AgentSession readback | 未啟用,數量 `0` | | Agent message / handoff receipts | 未啟用,數量 `0` | | learning writeback | 未啟用,數量 `0` | | Telegram digest receipts | 未啟用,數量 `0` | -這代表使用者現在可以看見「哪裡已準備好、哪裡仍未運作、被哪個 gate 阻擋」。但還不能宣稱三個 Agent 已經在 production runtime 主動互傳訊息或自主學習。 +這代表使用者現在可以看見「哪裡已準備好、哪裡仍未運作、被哪個 gate 阻擋、下一步要如何驗證」。但還不能宣稱三個 Agent 已經在 production runtime 主動互傳訊息或自主學習。 ## 2. 你可以怎麼看到、感受到 | 感知方式 | UI / 通道 | 目前狀態 | 之後亮綠燈條件 | |---|---|---|---| -| Agent 心跳 | governance proof panel | 待 live read model | `agent_sessions.last_seen_at` 有資料 | +| Agent 心跳 | governance proof panel | read model gate 已定義,live count `0` | `agent_sessions` safe read no-write smoke 通過後才可讀 live | | Agent 發話 / 收話收據 | governance proof panel | 待 Redis consumer group | `Redis Streams XADD + consumer ack` 可 replay | | 互相接手 | AwoooP timeline / governance proof panel | 待 handoff envelope | `parent_turn_id` 串起 OpenClaw / Hermes / NemoTron turns | | 互相挑戰 | AwoooP timeline | 契約已定義 | `turn_type=challenge` 且有補證 / 拒收紀錄 | @@ -47,20 +51,23 @@ | 產物 | 內容 | |---|---| | `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 committed snapshot,完成度 `45%`,live count 全為 `0` | +| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | P2-403A + P2-403B committed snapshot,完成度 `55%`,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` | | `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 | -| governance UI | 新增證據階梯、目前真相、Agent lane、可觀測訊號、runtime gates、前端 redaction | +| `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 | ## 5. 後續優先順序 | 優先 | ID | 工作 | gate | |---:|---|---|---| -| 1 | P2-403B | AgentSession / Redis Streams live read model 與 worker gate planning | DB / Redis approval | -| 2 | P2-403C | Redis Streams consumer group dry-run、handoff envelope、ack / dead letter / replay | runtime dry-run approval | -| 3 | P2-403D | learning writeback approval package、KM / playbook trust / replay score 趨勢 | writeback approval | -| 4 | P2-403E | Telegram gateway dry-run、E2E 送達與 receipt 顯示 | Telegram E2E approval | -| 5 | P2-403F | 成長趨勢週報與 operator feedback applied 指標 | trend evidence | +| 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 | +| 3 | P2-403E | Telegram gateway dry-run、E2E 送達與 receipt 顯示 | Telegram E2E approval | +| 4 | P2-403F | 成長趨勢週報與 operator feedback applied 指標 | trend evidence | ## 6. 紅線 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 6d7348885..b93a7fa67 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 只讀契約與治理 UI 摘要。權威細節以 MASTER §3.2.1c / §3.2.1d、`ai_agent_proactive_operations_contract_v1`、`ai_agent_interaction_learning_proof_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 只讀契約與治理 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` 為準。 ## 1. 本波完成度 @@ -15,7 +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` | -| 整體主動營運與版本生命週期 | 100% | P2-402A~G 與 P2-403A 只讀契約、snapshot、API、測試與治理 UI 已完成;runtime 排程、工具安裝、CI 變更、實際 PR 建立與更新、host probe、升級、重啟、learning write、Telegram receipt 仍未開 gate | +| 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 | ## 2. 可交給 AI Agent 的工作分類 @@ -53,8 +54,11 @@ | `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;live counts 全部 `0` | +| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | 證據階梯、live truth、三 Agent lane、可觀測訊號、runtime gates;P2-403B 後完成度 `55%`,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` | +| `GET /api/v1/agents/agent-live-read-model-gate` | 只讀 API;不連 DB、不讀寫 Redis、不啟動 worker、不發 Telegram | ## 4. P2-402B Repo-only 版本新鮮度快照 @@ -65,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-403B live AgentSession / Redis read model;現階段仍只讀 | +| K8s / Gitea / observability / Ansible / backup / web surfaces | OpenClaw + Hermes | baseline_ready / planned_next | 下一步進入 P2-403C Redis Streams consumer group dry-run;現階段仍只讀 | 本波只把「每天要看哪些 repo 內版本來源」定義成可驗證資料面。每日排程、外部 registry 查詢、主機/K3s live probe、Telegram digest 與 Gitea PR lane 都仍是下一階段 gate。 @@ -135,7 +139,8 @@ P2-402G 的重點是把前面六個資料契約接回治理頁,讓統帥可以 | P2-402F | 2 | host OS / K3s / stateful services 版本只讀盤點 | 已完成,只讀;SSH / kubectl / upgrade / reboot 未啟用 | | P2-402G | 3 | governance UI 顯示可委派能力 | 已完成,只讀;不顯示敏感端點或工作對話內容 | | P2-403A | 4 | Agent 互動、接手、學習、成長與 Telegram receipt 證據面 | 已完成,只讀;live counts 全為 `0` | -| P2-403B | 5 | live AgentSession / Redis read model:心跳、訊息、接手、學習回寫的逐項批准模型 | DB / Redis approval package | +| 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 | ## 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 79c71397f..dbe0c9e46 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": 45, + "overall_completion_percent": 55, "current_priority": "P2", - "current_task_id": "P2-403A", - "next_task_id": "P2-403B", + "current_task_id": "P2-403B", + "next_task_id": "P2-403C", "read_only_mode": true, "runtime_authority": "proof_surface_only_no_live_worker", - "status_note": "本快照建立使用者可感知的 Agent 互動、溝通、學習與成長證據面;目前只讀證據面已落地,live AgentSession/Redis/Telegram/learning writeback 尚未開 gate。" + "status_note": "P2-403B 已把 AgentSession / Redis Streams live read model gate、唯讀查詢、回滾與無寫入 smoke 接入證據鏈;live AgentSession/Redis/Telegram/learning writeback 尚未開 gate。" }, "live_truth": { "runtime_loop_enabled": false, @@ -45,11 +45,11 @@ { "level_id": "live_read_model", "display_name": "Live AgentSession 讀回", - "status": "live_pending", - "completion_percent": 0, - "operator_meaning": "啟用後你會看到每個 Agent 最後心跳、最近工作、目前狀態與資料時間。", - "source_of_truth": "agent_sessions + timeline_events read model", - "next_gate": "P2-403B migration proposal、唯讀查詢、回滾計畫。" + "status": "contract_ready", + "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。" }, { "level_id": "message_bus_receipts", @@ -270,9 +270,9 @@ { "gate_id": "agent_session_read_model_gate", "display_name": "AgentSession 唯讀 read model", - "status": "approval_required", - "required_before_green": "必須先提供 migration proposal、查詢索引、回滾計畫與無寫入 smoke。", - "next_task_id": "P2-403B" + "status": "ready", + "required_before_green": "P2-403B 已提供既有表確認、safe selected fields、查詢索引、回滾計畫與無寫入 smoke;live DB query 仍未批准。", + "next_task_id": "P2-403C" }, { "gate_id": "redis_stream_consumer_gate", @@ -309,7 +309,7 @@ "display_name": "Hot AgentSession Memory", "storage_or_service": "agent_sessions + Redis Streams", "visible_growth_signal": "心跳、turn count、handoff accepted、challenge count", - "current_state": "schema_existing_read_model_pending" + "current_state": "read_model_gate_defined_live_read_pending" }, { "layer_id": "warm_knowledge_memory", @@ -365,11 +365,10 @@ }, "rollups": { "proof_level_count": 7, - "contract_ready_level_count": 2, + "contract_ready_level_count": 3, "live_pending_level_ids": [ "handoff_receipts", "learning_growth", - "live_read_model", "message_bus_receipts", "telegram_receipts" ], @@ -378,7 +377,6 @@ "operator_surface_count": 5, "runtime_gate_count": 5, "blocked_gate_ids": [ - "agent_session_read_model_gate", "frontend_redaction_gate", "learning_writeback_gate", "redis_stream_consumer_gate", diff --git a/docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json b/docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json new file mode 100644 index 000000000..694d66061 --- /dev/null +++ b/docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json @@ -0,0 +1,306 @@ +{ + "schema_version": "ai_agent_live_read_model_gate_v1", + "generated_at": "2026-06-11T23:50:00+08:00", + "program_status": { + "overall_completion_percent": 55, + "current_priority": "P2", + "current_task_id": "P2-403B", + "next_task_id": "P2-403C", + "read_only_mode": true, + "runtime_authority": "gate_plan_only_no_live_worker", + "status_note": "P2-403B 已把 AgentSession / Redis Streams live read model 的唯讀查詢、worker gate、回滾與無寫入 smoke 變成可審查契約;尚未開啟 live DB query、Redis consumer group、runtime worker、learning writeback 或 Telegram 發送。" + }, + "source_refs": [ + "apps/api/src/db/models.py:AgentSession", + "apps/api/migrations/phase_aiops_p1_p2_p6_tables.sql:agent_sessions", + "docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json", + "docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md" + ], + "live_truth": { + "live_agent_session_readback_enabled": false, + "live_redis_stream_read_enabled": false, + "runtime_worker_enabled": false, + "telegram_receipt_send_enabled": false, + "learning_writeback_enabled": false, + "active_live_agent_sessions": 0, + "live_redis_events_24h": 0, + "live_handoffs_24h": 0, + "live_learning_writes_24h": 0, + "telegram_digest_receipts_24h": 0, + "truth_note": "目前完成的是 read model gate plan;治理頁可以看見下一步要驗證什麼,但不能把 0 筆 live session 說成 Agent 已在 production 主動互動。" + }, + "existing_storage_contract": { + "db_table": "agent_sessions", + "schema_status": "existing_phase_aiops_table_detected", + "migration_delta_required": false, + "approved_for_live_query": false, + "safe_read_query_defined": true, + "safe_selected_fields": [ + "id", + "session_id", + "incident_id", + "agent_role", + "input_hash", + "latency_ms", + "vote", + "degraded", + "created_at" + ], + "forbidden_selected_fields": [ + "output_json", + "prompt", + "raw_prompt", + "conversation_transcript", + "private_reasoning", + "chain_of_thought", + "secret_plaintext", + "credential_value" + ], + "required_indexes": [ + "ix_agent_sessions_session_id", + "ix_agent_sessions_incident_id", + "ix_agent_sessions_created_at", + "ix_agent_sessions_session_role" + ], + "read_query_contract": "SELECT id, session_id, incident_id, agent_role, input_hash, latency_ms, vote, degraded, created_at FROM agent_sessions WHERE created_at >= :window_start ORDER BY created_at DESC LIMIT :safe_limit", + "query_limits": { + "default_window_hours": 24, + "max_limit": 50, + "order_by": "created_at DESC" + } + }, + "redis_stream_contract": { + "stream_namespace": "awoooi:agent:*", + "candidate_streams": [ + "awoooi:agent:events", + "awoooi:agent:handoffs", + "awoooi:agent:learning" + ], + "consumer_group_allowed": false, + "xadd_allowed": false, + "xreadgroup_allowed": false, + "dead_letter_required": true, + "replay_required_before_worker": true, + "event_envelope_required_fields": [ + "event_id", + "trace_id", + "session_id", + "incident_id", + "agent_id", + "event_type", + "event_status", + "created_at", + "evidence_ref" + ], + "forbidden_event_fields": [ + "secret_value", + "credential_value", + "raw_prompt", + "conversation_transcript", + "private_reasoning", + "chain_of_thought" + ] + }, + "read_model_cards": [ + { + "card_id": "agent_heartbeat_readback", + "display_name": "Agent 心跳讀回", + "owner_agent": "openclaw", + "source_of_truth": "agent_sessions.created_at", + "readiness_status": "query_contract_ready", + "operator_signal": "看到各 Agent 最近一次安全欄位 readback 時間與 degraded 狀態。", + "next_gate": "P2-403C Redis dry-run 前先做 DB no-write smoke。" + }, + { + "card_id": "agent_vote_quality", + "display_name": "投票與降級品質", + "owner_agent": "openclaw", + "source_of_truth": "agent_sessions.vote + degraded + latency_ms", + "readiness_status": "query_contract_ready", + "operator_signal": "看到 approve / reject / request_revision / degraded 的聚合趨勢。", + "next_gate": "P2-403C 加上 message receipt 後才能判斷互相接手。" + }, + { + "card_id": "agent_role_turns", + "display_name": "Agent role turn 摘要", + "owner_agent": "hermes", + "source_of_truth": "agent_sessions.session_id + agent_role", + "readiness_status": "query_contract_ready", + "operator_signal": "看到同一 session 內哪些角色參與,但不顯示 Agent 原始輸出。", + "next_gate": "P2-403C 定義 handoff envelope。" + }, + { + "card_id": "redis_message_receipts", + "display_name": "Redis 訊息收據", + "owner_agent": "hermes", + "source_of_truth": "Redis Streams event envelope", + "readiness_status": "approval_required", + "operator_signal": "看到 XADD / ack / dead-letter 的脫敏收據;目前尚未允許讀寫 Redis。", + "next_gate": "P2-403C consumer group dry-run approval。" + }, + { + "card_id": "learning_receipt_bridge", + "display_name": "學習收據橋接", + "owner_agent": "nemotron", + "source_of_truth": "knowledge_entries + playbook_trust_history + replay_results", + "readiness_status": "blocked_by_writeback_gate", + "operator_signal": "看到學習回寫與 replay 分數改善的聚合摘要;目前 writeback 未批准。", + "next_gate": "P2-403D learning writeback approval package。" + } + ], + "worker_gate_plan": [ + { + "gate_id": "db_read_model_no_write_smoke", + "display_name": "DB read model 無寫入 smoke", + "owner_agent": "openclaw", + "status": "approval_required", + "required_evidence": "pytest loader + API endpoint + SQL read-only query review;確認未 import DB session writer、未 INSERT / UPDATE / DELETE。", + "blocked_action": "live_db_query" + }, + { + "gate_id": "redis_stream_replay_dry_run", + "display_name": "Redis Streams replay dry-run", + "owner_agent": "hermes", + "status": "approval_required", + "required_evidence": "固定 fixture 重放 event envelope,確認 ack / dead-letter / replay idempotency。", + "blocked_action": "redis_consumer_group" + }, + { + "gate_id": "worker_kill_switch", + "display_name": "worker 熔斷與停用開關", + "owner_agent": "openclaw", + "status": "blocked", + "required_evidence": "環境變數預設關閉、runtime health gate、回滾腳本與告警降噪規則。", + "blocked_action": "runtime_worker" + }, + { + "gate_id": "frontend_redaction_readback", + "display_name": "前端 readback 脫敏驗證", + "owner_agent": "hermes", + "status": "approval_required", + "required_evidence": "只顯示 safe fields、聚合數、event id、來源與 gate;不顯示工作視窗內容或 Agent 原始輸出。", + "blocked_action": "unsafe_frontend_display" + }, + { + "gate_id": "telegram_receipt_dry_run_gate", + "display_name": "Telegram receipt dry-run gate", + "owner_agent": "openclaw", + "status": "blocked", + "required_evidence": "Gateway dry-run、token 注入 E2E、failure-only / action-required policy、送達收據與 fallback。", + "blocked_action": "telegram_send" + } + ], + "rollback_plan": [ + { + "rollback_id": "disable_feature_flag", + "step": "若 read model API 發生異常,先關閉前端顯示與 runtime feature flag,保留 P2-403A 證據面。", + "owner_agent": "openclaw" + }, + { + "rollback_id": "stop_worker_before_queue", + "step": "若 P2-403C 後 worker 開始 dry-run,先停 worker,再檢查 Redis pending / dead-letter,不刪資料。", + "owner_agent": "hermes" + }, + { + "rollback_id": "freeze_learning_writeback", + "step": "若 learning bridge 發現髒資料,維持 writeback gate 關閉,只保留候選學習報告。", + "owner_agent": "nemotron" + }, + { + "rollback_id": "telegram_send_lock", + "step": "若 Telegram gateway 有錯誤,保留 queue / receipt evidence,但直接發送維持關閉。", + "owner_agent": "openclaw" + } + ], + "no_write_smoke_plan": [ + { + "smoke_id": "loader_rejects_live_flags", + "status": "defined", + "writes_allowed": false, + "assertion": "loader 會拒絕任何 live DB query、runtime worker、Redis consumer、Telegram 或 learning writeback allowed=true。" + }, + { + "smoke_id": "safe_field_projection", + "status": "defined", + "writes_allowed": false, + "assertion": "safe_selected_fields 不包含 output_json、prompt、逐字稿、私有推理或機密欄位。" + }, + { + "smoke_id": "api_returns_gate_only", + "status": "defined", + "writes_allowed": false, + "assertion": "API 只回傳 committed snapshot,不連 DB、不連 Redis、不送 Telegram。" + }, + { + "smoke_id": "frontend_redaction_scan", + "status": "defined", + "writes_allowed": false, + "assertion": "前端只顯示狀態、safe fields 數量、gate、聚合指標與下一步。" + }, + { + "smoke_id": "rollback_plan_present", + "status": "defined", + "writes_allowed": false, + "assertion": "每個 future runtime gate 都有停用或凍結策略。" + } + ], + "display_redaction_contract": { + "redaction_required": true, + "work_window_conversation_display_allowed": false, + "agent_raw_output_display_allowed": false, + "secret_value_display_allowed": false, + "allowed_frontend_content": [ + "狀態", + "聚合數", + "safe field 名稱", + "來源表", + "event id", + "gate id", + "下一步", + "回滾步驟摘要" + ], + "forbidden_frontend_content": [ + "工作視窗內容", + "Agent 原始輸出", + "提示詞", + "私有推理", + "機密值", + "未脫敏 payload" + ], + "frontend_display_policy": "治理頁只顯示 gate、safe fields、聚合數、來源與下一步;不顯示工作視窗內容、Agent 原始輸出、提示詞、私有推理、機密值或未脫敏 payload。" + }, + "approval_boundaries": { + "db_migration_allowed": false, + "live_db_query_allowed": false, + "redis_xadd_allowed": false, + "redis_consumer_group_allowed": false, + "runtime_worker_allowed": false, + "telegram_direct_send_allowed": false, + "learning_writeback_allowed": false, + "secret_plaintext_allowed": false, + "conversation_transcript_display_allowed": false, + "private_reasoning_display_allowed": false, + "agent_raw_output_display_allowed": false + }, + "rollups": { + "source_ref_count": 4, + "read_model_card_count": 5, + "gate_count": 5, + "approval_required_gate_ids": [ + "db_read_model_no_write_smoke", + "frontend_redaction_readback", + "redis_stream_replay_dry_run", + "telegram_receipt_dry_run_gate", + "worker_kill_switch" + ], + "query_contract_ready_card_ids": [ + "agent_heartbeat_readback", + "agent_role_turns", + "agent_vote_quality" + ], + "rollback_step_count": 4, + "no_write_smoke_count": 5, + "forbidden_frontend_content_count": 6, + "live_truth_count_total": 0 + } +} 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 84c3a2f9a..324450181 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-403A", - "next_task_id": "P2-403B", + "current_task_id": "P2-403B", + "next_task_id": "P2-403C", "read_only_mode": true, "runtime_authority": "contract_only_no_version_or_runtime_update", - "status_note": "P2-403A 已把 Agent 互動、接手、學習、成長與 Telegram receipt 證據面接入 governance UI;live AgentSession / message / handoff / learning write / Telegram receipt 目前全為 0,下一步是 P2-403B live read model。" + "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。" }, "external_source_evidence": [ { @@ -673,7 +673,16 @@ "completion_percent": 100, "owner_agent": "Hermes + OpenClaw", "summary": "建立 Agent 互動、接手、學習、成長與 Telegram receipt 證據面;治理頁顯示目前真相、證據階梯、Agent lanes、可觀測訊號、runtime gates 與 redaction policy。", - "next_gate": "P2-403B_live_read_model" + "next_gate": "P2-403B_completed" + }, + { + "task_id": "P2-403B", + "priority": "P2", + "status": "done", + "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" } ], "approval_boundaries": { @@ -694,7 +703,7 @@ "cadence_count": 5, "mcp_tool_count": 8, "rag_memory_count": 4, - "rollout_task_count": 8, + "rollout_task_count": 9, "auto_execute_allowed_count": 0, "approval_required_capability_count": 23, "blocked_update_domain_ids": [ diff --git a/docs/schemas/ai_agent_live_read_model_gate_v1.schema.json b/docs/schemas/ai_agent_live_read_model_gate_v1.schema.json new file mode 100644 index 000000000..bacadf042 --- /dev/null +++ b/docs/schemas/ai_agent_live_read_model_gate_v1.schema.json @@ -0,0 +1,385 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.wooo.work/schemas/ai_agent_live_read_model_gate_v1.schema.json", + "title": "AI Agent Live Read Model Gate v1", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "live_truth", + "existing_storage_contract", + "redis_stream_contract", + "read_model_cards", + "worker_gate_plan", + "rollback_plan", + "no_write_smoke_plan", + "display_redaction_contract", + "approval_boundaries", + "rollups" + ], + "properties": { + "schema_version": { + "const": "ai_agent_live_read_model_gate_v1" + }, + "generated_at": { + "type": "string" + }, + "program_status": { + "type": "object", + "required": [ + "overall_completion_percent", + "current_priority", + "current_task_id", + "next_task_id", + "read_only_mode", + "runtime_authority" + ], + "properties": { + "overall_completion_percent": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "current_priority": { + "type": "string" + }, + "current_task_id": { + "type": "string" + }, + "next_task_id": { + "type": "string" + }, + "read_only_mode": { + "const": true + }, + "runtime_authority": { + "const": "gate_plan_only_no_live_worker" + } + }, + "additionalProperties": true + }, + "source_refs": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "live_truth": { + "type": "object", + "required": [ + "live_agent_session_readback_enabled", + "live_redis_stream_read_enabled", + "runtime_worker_enabled", + "telegram_receipt_send_enabled", + "learning_writeback_enabled", + "active_live_agent_sessions", + "live_redis_events_24h", + "live_handoffs_24h", + "live_learning_writes_24h", + "telegram_digest_receipts_24h" + ], + "properties": { + "live_agent_session_readback_enabled": { + "const": false + }, + "live_redis_stream_read_enabled": { + "const": false + }, + "runtime_worker_enabled": { + "const": false + }, + "telegram_receipt_send_enabled": { + "const": false + }, + "learning_writeback_enabled": { + "const": false + }, + "active_live_agent_sessions": { + "const": 0 + }, + "live_redis_events_24h": { + "const": 0 + }, + "live_handoffs_24h": { + "const": 0 + }, + "live_learning_writes_24h": { + "const": 0 + }, + "telegram_digest_receipts_24h": { + "const": 0 + } + }, + "additionalProperties": true + }, + "existing_storage_contract": { + "type": "object", + "required": [ + "db_table", + "schema_status", + "migration_delta_required", + "approved_for_live_query", + "safe_read_query_defined", + "safe_selected_fields", + "forbidden_selected_fields", + "required_indexes", + "read_query_contract" + ], + "properties": { + "db_table": { + "const": "agent_sessions" + }, + "migration_delta_required": { + "const": false + }, + "approved_for_live_query": { + "const": false + }, + "safe_read_query_defined": { + "const": true + }, + "safe_selected_fields": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "forbidden_selected_fields": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "required_indexes": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "additionalProperties": true + }, + "redis_stream_contract": { + "type": "object", + "required": [ + "stream_namespace", + "candidate_streams", + "consumer_group_allowed", + "xadd_allowed", + "xreadgroup_allowed", + "event_envelope_required_fields" + ], + "properties": { + "consumer_group_allowed": { + "const": false + }, + "xadd_allowed": { + "const": false + }, + "xreadgroup_allowed": { + "const": false + }, + "event_envelope_required_fields": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "additionalProperties": true + }, + "read_model_cards": { + "type": "array", + "items": { + "$ref": "#/$defs/read_model_card" + }, + "minItems": 1 + }, + "worker_gate_plan": { + "type": "array", + "items": { + "$ref": "#/$defs/worker_gate" + }, + "minItems": 1 + }, + "rollback_plan": { + "type": "array", + "items": { + "$ref": "#/$defs/rollback_step" + }, + "minItems": 1 + }, + "no_write_smoke_plan": { + "type": "array", + "items": { + "$ref": "#/$defs/no_write_smoke" + }, + "minItems": 1 + }, + "display_redaction_contract": { + "type": "object", + "required": [ + "redaction_required", + "work_window_conversation_display_allowed", + "agent_raw_output_display_allowed", + "secret_value_display_allowed", + "forbidden_frontend_content" + ], + "properties": { + "redaction_required": { + "const": true + }, + "work_window_conversation_display_allowed": { + "const": false + }, + "agent_raw_output_display_allowed": { + "const": false + }, + "secret_value_display_allowed": { + "const": false + }, + "forbidden_frontend_content": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "additionalProperties": true + }, + "approval_boundaries": { + "type": "object", + "required": [ + "db_migration_allowed", + "live_db_query_allowed", + "redis_xadd_allowed", + "redis_consumer_group_allowed", + "runtime_worker_allowed", + "telegram_direct_send_allowed", + "learning_writeback_allowed", + "secret_plaintext_allowed", + "conversation_transcript_display_allowed", + "private_reasoning_display_allowed", + "agent_raw_output_display_allowed" + ], + "properties": { + "db_migration_allowed": { + "const": false + }, + "live_db_query_allowed": { + "const": false + }, + "redis_xadd_allowed": { + "const": false + }, + "redis_consumer_group_allowed": { + "const": false + }, + "runtime_worker_allowed": { + "const": false + }, + "telegram_direct_send_allowed": { + "const": false + }, + "learning_writeback_allowed": { + "const": false + }, + "secret_plaintext_allowed": { + "const": false + }, + "conversation_transcript_display_allowed": { + "const": false + }, + "private_reasoning_display_allowed": { + "const": false + }, + "agent_raw_output_display_allowed": { + "const": false + } + }, + "additionalProperties": false + }, + "rollups": { + "type": "object", + "required": [ + "source_ref_count", + "read_model_card_count", + "gate_count", + "approval_required_gate_ids", + "query_contract_ready_card_ids", + "rollback_step_count", + "no_write_smoke_count", + "forbidden_frontend_content_count", + "live_truth_count_total" + ], + "properties": { + "live_truth_count_total": { + "const": 0 + } + }, + "additionalProperties": true + } + }, + "$defs": { + "read_model_card": { + "type": "object", + "required": [ + "card_id", + "display_name", + "owner_agent", + "source_of_truth", + "readiness_status", + "operator_signal", + "next_gate" + ], + "additionalProperties": true + }, + "worker_gate": { + "type": "object", + "required": [ + "gate_id", + "display_name", + "owner_agent", + "status", + "required_evidence", + "blocked_action" + ], + "additionalProperties": true + }, + "rollback_step": { + "type": "object", + "required": [ + "rollback_id", + "step", + "owner_agent" + ], + "additionalProperties": true + }, + "no_write_smoke": { + "type": "object", + "required": [ + "smoke_id", + "status", + "writes_allowed", + "assertion" + ], + "properties": { + "status": { + "const": "defined" + }, + "writes_allowed": { + "const": false + } + }, + "additionalProperties": true + } + } +} 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 054df601b..31cd4287c 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,8 @@ 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 互動 / 接手 / 學習 / 成長證據面;目前 live session、message、handoff、learning write、Telegram receipt 全部 `0`,下一步 P2-403B | +| `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_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 主動營運委派與版本生命週期契約 @@ -689,6 +690,9 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | `docs/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json` | P2-402F host / K3s / stateful 版本只讀盤點 schema;鎖定 SSH / kubectl / upgrade / drain / reboot / restart / Telegram 全部 false | | `docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json` | P2-402F committed snapshot:5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務、6 個只讀 probe step、maintenance window approval package | | `GET /api/v1/agents/agent-host-stateful-version-inventory` | 只讀 API;只回傳 host / K3s / stateful 版本盤點,不 SSH、不 kubectl、不升級、不重啟、不發 Telegram | +| `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 | | `/zh-TW/governance?tab=automation-inventory` | P2-402G governance UI:顯示可委派能力、版本生命週期、Host / K3s / stateful 只讀關卡與 Telegram / redaction gate;不提供執行按鈕 | **採用順序:** @@ -700,7 +704,8 @@ Repo / registry / release notes / K8s / host / observability / backup evidence 5. 建立 host OS / K3s / stateful services 版本只讀盤點,先固定維護窗口、備份、rollback 與 smoke gate。✅ P2-402F 已建立 snapshot/API;SSH、kubectl、upgrade、drain、reboot、stateful restart 仍未授權。 6. 把可委派能力、版本生命週期、Host / K3s / stateful 只讀關卡接入 governance UI。✅ P2-402G 已完成;不顯示敏感端點、不提供執行按鈕。 7. 先建立可被統帥看見的互動 / 接手 / 學習 / 成長證據面。✅ P2-403A 已完成;live count 仍為 `0`,不宣稱 runtime 已運作。 -8. 最後才進人工批准後的 live read model / dry-run / smoke / canary / production rollout。下一步 P2-403B AgentSession / Redis read model gate planning。 +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。 #### 3.2.1d 2026-06-11 Agent 互動、學習與成長證據面 @@ -712,7 +717,7 @@ Repo / registry / release notes / K8s / host / observability / backup evidence |---|---|---| | 契約可見 | 三 Agent 分工、MCP/RAG、Telegram/redaction 邊界 | 已完成 | | 證據面板 | 「目前真相」、證據階梯、Agent lane、runtime gate | 已完成 | -| Live read model | Agent 心跳、最近狀態、資料時間 | P2-403B 待辦 | +| 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 待辦 | | 學習回寫 | KM、Playbook trust、learning failure、replay score | P2-403D 待辦 | @@ -723,9 +728,13 @@ 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 committed snapshot;完成度 `45%`,live truth counts 全部 `0` | +| `docs/evaluations/ai_agent_interaction_learning_proof_2026-06-11.json` | P2-403A + P2-403B committed snapshot;完成度 `55%`,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 | | `/zh-TW/governance?tab=automation-inventory` | 顯示證據階梯、目前真相、三 Agent lane、可觀測訊號、runtime gates 與 redaction policy | **硬性紅線:**