feat(api): expose log writeback executor readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
This commit is contained in:
@@ -103,6 +103,9 @@ from src.services.ai_agent_live_read_model_gate import (
|
||||
from src.services.ai_agent_log_controlled_writeback_plan_readback import (
|
||||
load_latest_ai_agent_log_controlled_writeback_plan_readback,
|
||||
)
|
||||
from src.services.ai_agent_log_controlled_writeback_executor_readback import (
|
||||
load_latest_ai_agent_log_controlled_writeback_executor_readback,
|
||||
)
|
||||
from src.services.ai_agent_log_feedback_receipt_dry_run import (
|
||||
load_latest_ai_agent_log_feedback_receipt_dry_run,
|
||||
)
|
||||
@@ -1964,6 +1967,38 @@ async def get_agent_log_controlled_writeback_plan_readback() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-log-controlled-writeback-executor-readback",
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 AI Agent LOG controlled writeback executor readback",
|
||||
description=(
|
||||
"把 LOG controlled writeback plan 轉成 AI Agent 可消費的 executor batch、"
|
||||
"next action queue、check-mode、rollback 與 post-apply verifier readback。"
|
||||
"低中高風險 metadata writeback 採 AI controlled apply;critical 仍需 break-glass。"
|
||||
"此端點只輸出 executor readback,不 dispatch executor、不寫 KM、不寫 RAG index、"
|
||||
"不更新 PlayBook trust、不呼叫 MCP tool、不保存 raw log payload、不讀 secret、不呼叫 GitHub。"
|
||||
),
|
||||
)
|
||||
async def get_agent_log_controlled_writeback_executor_readback() -> dict[str, Any]:
|
||||
"""Return LOG feedback controlled writeback executor and consumption readback."""
|
||||
try:
|
||||
payload = await asyncio.to_thread(
|
||||
load_latest_ai_agent_log_controlled_writeback_executor_readback
|
||||
)
|
||||
return redact_public_lan_topology(payload)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error("ai_agent_log_controlled_writeback_executor_readback_invalid", error=str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="AI Agent LOG controlled writeback executor readback 無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-telegram-receipt-approval-package",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
"""AI Agent LOG controlled writeback executor readback.
|
||||
|
||||
Turns the verified LOG controlled writeback plan into executor-ready batches
|
||||
and AI Agent consumption context. This endpoint-level readback opens the
|
||||
controlled-apply route for low / medium / high metadata writeback decisions,
|
||||
while keeping the function itself side-effect free: it does not dispatch an
|
||||
executor, write KM, index RAG, update PlayBook trust, call MCP tools, trigger
|
||||
workflows, or persist raw log payloads.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from src.services.ai_agent_log_controlled_writeback_plan_readback import (
|
||||
load_latest_ai_agent_log_controlled_writeback_plan_readback,
|
||||
)
|
||||
|
||||
_SCHEMA_VERSION = "ai_agent_log_controlled_writeback_executor_readback_v1"
|
||||
_PLAN_READY_STATUS = "controlled_writeback_plan_ready"
|
||||
_TARGETS = ("km", "rag", "playbook", "mcp", "verifier", "ai_agent")
|
||||
_EXECUTOR_ROUTE = "ai_agent_metadata_writeback_executor"
|
||||
|
||||
|
||||
def load_latest_ai_agent_log_controlled_writeback_executor_readback() -> dict[str, Any]:
|
||||
"""Return executor-ready LOG writeback batches for AI Agent consumption."""
|
||||
plan = load_latest_ai_agent_log_controlled_writeback_plan_readback()
|
||||
writeback_plans = _writeback_plans(plan)
|
||||
plan_ready = (
|
||||
plan.get("status") == _PLAN_READY_STATUS
|
||||
and plan.get("active_blockers") == []
|
||||
and (plan.get("rollups") or {}).get("controlled_writeback_plan_ready") is True
|
||||
)
|
||||
execution_batches = _execution_batches(writeback_plans)
|
||||
active_blockers = _active_blockers(
|
||||
plan_ready=plan_ready,
|
||||
writeback_plans=writeback_plans,
|
||||
execution_batches=execution_batches,
|
||||
)
|
||||
|
||||
return {
|
||||
"schema_version": _SCHEMA_VERSION,
|
||||
"priority": "P1-LOG-KM-RAG-MCP-PLAYBOOK",
|
||||
"scope": "ai_agent_log_controlled_writeback_executor",
|
||||
"status": (
|
||||
"controlled_writeback_executor_ready"
|
||||
if not active_blockers
|
||||
else "blocked_waiting_controlled_writeback_executor_inputs"
|
||||
),
|
||||
"readback": {
|
||||
"workplan_id": "P1-LOG-CONTROLLED-WRITEBACK-EXECUTOR",
|
||||
"workplan_title": "LOG feedback controlled writeback executor and AI Agent consumption readback",
|
||||
"source_schema_version": plan.get("schema_version"),
|
||||
"source_status": plan.get("status"),
|
||||
"safe_next_step": "dispatch_controlled_metadata_writeback_batches_then_post_apply_verify",
|
||||
},
|
||||
"executor_policy": {
|
||||
"executor_route": _EXECUTOR_ROUTE,
|
||||
"low_medium_high_controlled_apply_enabled": True,
|
||||
"owner_review_required_for_low_medium_high": False,
|
||||
"critical_break_glass_required": True,
|
||||
"target_selector_required": True,
|
||||
"source_of_truth_diff_required": True,
|
||||
"check_mode_required": True,
|
||||
"rollback_required": True,
|
||||
"post_apply_verifier_required": True,
|
||||
},
|
||||
"execution_batches": execution_batches,
|
||||
"agent_consumption_context": _agent_consumption_context(execution_batches),
|
||||
"rollups": {
|
||||
"source_writeback_plan_count": len(writeback_plans),
|
||||
"execution_batch_count": len(execution_batches),
|
||||
"ready_execution_batch_count": sum(
|
||||
1
|
||||
for batch in execution_batches
|
||||
if batch["status"] == "ready_for_controlled_executor_dispatch"
|
||||
),
|
||||
"target_count": len(_TARGETS),
|
||||
"target_selector_count": sum(
|
||||
batch["target_selector_count"] for batch in execution_batches
|
||||
),
|
||||
"source_of_truth_diff_count": sum(
|
||||
batch["source_of_truth_diff_count"] for batch in execution_batches
|
||||
),
|
||||
"check_mode_ready_count": sum(
|
||||
1 for batch in execution_batches if batch["check_mode"]["enabled"] is True
|
||||
),
|
||||
"rollback_ready_count": sum(
|
||||
1 for batch in execution_batches if batch["rollback"]["required"] is True
|
||||
),
|
||||
"post_apply_verifier_ready_count": sum(
|
||||
1
|
||||
for batch in execution_batches
|
||||
if batch["post_apply_verifier"]["required"] is True
|
||||
),
|
||||
"controlled_executor_dispatch_ready": not active_blockers,
|
||||
"controlled_apply_enabled_by_policy": True,
|
||||
"runtime_dispatch_performed": False,
|
||||
},
|
||||
"active_blockers": active_blockers,
|
||||
"operation_boundaries": {
|
||||
"executor_readback_only": True,
|
||||
"controlled_apply_enabled_by_policy": True,
|
||||
"executor_dispatch_performed": False,
|
||||
"km_write_performed": False,
|
||||
"rag_index_write_performed": False,
|
||||
"playbook_trust_write_performed": False,
|
||||
"mcp_tool_call_performed": False,
|
||||
"agent_runtime_action_performed": False,
|
||||
"workflow_trigger_performed": False,
|
||||
"raw_log_payload_persisted": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"github_api_used": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _writeback_plans(plan: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
plans = plan.get("writeback_plans")
|
||||
if not isinstance(plans, list):
|
||||
return []
|
||||
return [item for item in plans if isinstance(item, dict)]
|
||||
|
||||
|
||||
def _execution_batches(writeback_plans: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return [
|
||||
_execution_batch(target, _plans_for_target(writeback_plans, target))
|
||||
for target in _TARGETS
|
||||
]
|
||||
|
||||
|
||||
def _execution_batch(target: str, plans: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
risk_tier = "medium" if target in {"km", "rag", "playbook"} else "low"
|
||||
target_selectors = [plan.get("target_selector") or {} for plan in plans]
|
||||
diffs = [plan.get("source_of_truth_diff") or {} for plan in plans]
|
||||
rollback_refs = [
|
||||
str((plan.get("rollback") or {}).get("rollback_ref") or "")
|
||||
for plan in plans
|
||||
]
|
||||
verifier_refs = [
|
||||
str((plan.get("post_apply_verifier") or {}).get("verifier_ref") or "")
|
||||
for plan in plans
|
||||
]
|
||||
ready = _batch_ready(plans)
|
||||
return {
|
||||
"batch_id": f"log-feedback-controlled-writeback::{target}",
|
||||
"target": target,
|
||||
"target_surface": _target_surface(target),
|
||||
"risk_tier": risk_tier,
|
||||
"executor_route": _EXECUTOR_ROUTE,
|
||||
"status": (
|
||||
"ready_for_controlled_executor_dispatch"
|
||||
if ready
|
||||
else "blocked_waiting_batch_controls"
|
||||
),
|
||||
"apply_mode": "controlled_apply",
|
||||
"dispatch_enabled_by_policy": True,
|
||||
"plan_count": len(plans),
|
||||
"plan_ids": [str(plan.get("plan_id") or "") for plan in plans],
|
||||
"receipt_ids": [str(plan.get("receipt_id") or "") for plan in plans],
|
||||
"target_selector_count": len(target_selectors),
|
||||
"target_selectors": target_selectors,
|
||||
"source_of_truth_diff_count": len(diffs),
|
||||
"source_of_truth_diffs": diffs,
|
||||
"check_mode": {
|
||||
"enabled": True,
|
||||
"required": True,
|
||||
"checks": [
|
||||
"resolve_target_selectors",
|
||||
"compare_source_of_truth_diffs",
|
||||
"verify_metadata_only_redaction",
|
||||
"verify_rollback_refs",
|
||||
"verify_post_apply_verifier_refs",
|
||||
],
|
||||
},
|
||||
"rollback": {
|
||||
"required": True,
|
||||
"rollback_refs": rollback_refs,
|
||||
"strategy": "mark_receipts_superseded_and_remove_target_bindings",
|
||||
},
|
||||
"post_apply_verifier": {
|
||||
"required": True,
|
||||
"verifier_refs": verifier_refs,
|
||||
"canonical_readback": (
|
||||
"/api/v1/agents/agent-log-controlled-writeback-executor-readback"
|
||||
),
|
||||
},
|
||||
"runtime_dispatch_performed": False,
|
||||
}
|
||||
|
||||
|
||||
def _batch_ready(plans: list[dict[str, Any]]) -> bool:
|
||||
if not plans:
|
||||
return False
|
||||
for plan in plans:
|
||||
if plan.get("status") != "controlled_apply_ready":
|
||||
return False
|
||||
if plan.get("write_enabled_by_plan") is not False:
|
||||
return False
|
||||
if not plan.get("target_selector"):
|
||||
return False
|
||||
if not plan.get("source_of_truth_diff"):
|
||||
return False
|
||||
if (plan.get("check_mode") or {}).get("enabled") is not True:
|
||||
return False
|
||||
if (plan.get("rollback") or {}).get("required") is not True:
|
||||
return False
|
||||
if (plan.get("post_apply_verifier") or {}).get("required") is not True:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _agent_consumption_context(execution_batches: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
ready_batches = [
|
||||
batch
|
||||
for batch in execution_batches
|
||||
if batch["status"] == "ready_for_controlled_executor_dispatch"
|
||||
]
|
||||
return {
|
||||
"context_id": "ai-agent-log-controlled-writeback-consumption-v1",
|
||||
"consumable_by": [
|
||||
"ai_agent_autonomous_runtime_control",
|
||||
"awooop_work_items",
|
||||
"alert_triage_loop",
|
||||
"km_rag_playbook_learning_loop",
|
||||
"mcp_audit_context_loop",
|
||||
],
|
||||
"evidence_chain": [
|
||||
"/api/v1/agents/agent-log-intelligence-integration-readback",
|
||||
"/api/v1/agents/agent-log-feedback-receipt-dry-run",
|
||||
"/api/v1/agents/agent-log-post-write-verifier-dry-run",
|
||||
"/api/v1/agents/agent-log-controlled-writeback-plan-readback",
|
||||
],
|
||||
"next_action_queue": [
|
||||
{
|
||||
"batch_id": batch["batch_id"],
|
||||
"target": batch["target"],
|
||||
"executor_route": batch["executor_route"],
|
||||
"apply_mode": batch["apply_mode"],
|
||||
"plan_count": batch["plan_count"],
|
||||
"check_mode_required": batch["check_mode"]["required"],
|
||||
"rollback_required": batch["rollback"]["required"],
|
||||
"post_apply_verifier_required": batch["post_apply_verifier"]["required"],
|
||||
}
|
||||
for batch in ready_batches
|
||||
],
|
||||
"learning_feedback_targets": [
|
||||
"km",
|
||||
"rag",
|
||||
"playbook",
|
||||
"mcp",
|
||||
"verifier",
|
||||
"ai_agent",
|
||||
],
|
||||
"raw_payload_required": False,
|
||||
}
|
||||
|
||||
|
||||
def _active_blockers(
|
||||
*,
|
||||
plan_ready: bool,
|
||||
writeback_plans: list[dict[str, Any]],
|
||||
execution_batches: list[dict[str, Any]],
|
||||
) -> list[str]:
|
||||
blockers = []
|
||||
if not plan_ready:
|
||||
blockers.append("controlled_writeback_plan_not_ready")
|
||||
if not writeback_plans:
|
||||
blockers.append("source_writeback_plans_missing")
|
||||
for target in _TARGETS:
|
||||
batch = next(
|
||||
(item for item in execution_batches if item.get("target") == target),
|
||||
None,
|
||||
)
|
||||
if not batch:
|
||||
blockers.append(f"{target}_execution_batch_missing")
|
||||
continue
|
||||
if batch["status"] != "ready_for_controlled_executor_dispatch":
|
||||
blockers.append(f"{target}_execution_batch_not_ready")
|
||||
if batch["dispatch_enabled_by_policy"] is not True:
|
||||
blockers.append(f"{target}_dispatch_policy_not_enabled")
|
||||
if batch["runtime_dispatch_performed"] is not False:
|
||||
blockers.append(f"{target}_runtime_dispatch_already_performed")
|
||||
return _unique(blockers)
|
||||
|
||||
|
||||
def _plans_for_target(plans: list[dict[str, Any]], target: str) -> list[dict[str, Any]]:
|
||||
return [plan for plan in plans if plan.get("target") == target]
|
||||
|
||||
|
||||
def _target_surface(target: str) -> str:
|
||||
return {
|
||||
"km": "knowledge_memory",
|
||||
"rag": "rag_chunk_index",
|
||||
"playbook": "playbook_trust_learning",
|
||||
"mcp": "mcp_audit_context",
|
||||
"verifier": "post_apply_verifier_feedback",
|
||||
"ai_agent": "agent_decision_context",
|
||||
}.get(target, "unknown")
|
||||
|
||||
|
||||
def _unique(values: list[str]) -> list[str]:
|
||||
seen = set()
|
||||
result = []
|
||||
for value in values:
|
||||
if value in seen:
|
||||
continue
|
||||
seen.add(value)
|
||||
result.append(value)
|
||||
return result
|
||||
Reference in New Issue
Block a user