feat(api): expose log consumer readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 29s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (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 / tests (push) Successful in 29s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -277,6 +277,8 @@ jobs:
|
||||
;;
|
||||
apps/api/src/services/ai_agent_log_controlled_writeback_dispatch.py)
|
||||
;;
|
||||
apps/api/src/services/ai_agent_log_controlled_writeback_consumer_readback.py)
|
||||
;;
|
||||
apps/api/src/services/ai_agent_autonomous_runtime_control.py)
|
||||
;;
|
||||
apps/api/src/services/awooop_ansible_audit_service.py)
|
||||
@@ -381,6 +383,8 @@ jobs:
|
||||
;;
|
||||
apps/api/tests/test_ai_agent_log_controlled_writeback_dispatch_api.py)
|
||||
;;
|
||||
apps/api/tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py)
|
||||
;;
|
||||
apps/api/tests/test_ai_agent_autonomous_runtime_control.py)
|
||||
;;
|
||||
apps/api/tests/test_awooop_truth_chain_service.py)
|
||||
@@ -572,6 +576,7 @@ jobs:
|
||||
src/services/ai_agent_log_controlled_writeback_plan_readback.py \
|
||||
src/services/ai_agent_log_controlled_writeback_executor_readback.py \
|
||||
src/services/ai_agent_log_controlled_writeback_dispatch.py \
|
||||
src/services/ai_agent_log_controlled_writeback_consumer_readback.py \
|
||||
src/services/ai_agent_autonomous_runtime_control.py \
|
||||
src/services/awooop_ansible_audit_service.py \
|
||||
src/services/awooop_ansible_check_mode_service.py \
|
||||
@@ -627,6 +632,7 @@ jobs:
|
||||
tests/test_ai_agent_log_controlled_writeback_plan_readback_api.py \
|
||||
tests/test_ai_agent_log_controlled_writeback_executor_readback_api.py \
|
||||
tests/test_ai_agent_log_controlled_writeback_dispatch_api.py \
|
||||
tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py \
|
||||
tests/test_ai_agent_autonomous_runtime_control.py \
|
||||
tests/test_awooop_truth_chain_service.py \
|
||||
tests/test_shadow_auto_approve.py \
|
||||
|
||||
@@ -100,6 +100,9 @@ from src.services.ai_agent_learning_writeback_approval_package import (
|
||||
from src.services.ai_agent_live_read_model_gate import (
|
||||
load_latest_ai_agent_live_read_model_gate,
|
||||
)
|
||||
from src.services.ai_agent_log_controlled_writeback_consumer_readback import (
|
||||
load_latest_ai_agent_log_controlled_writeback_consumer_readback,
|
||||
)
|
||||
from src.services.ai_agent_log_controlled_writeback_dispatch import (
|
||||
dispatch_latest_ai_agent_log_controlled_writeback,
|
||||
)
|
||||
@@ -381,12 +384,12 @@ from src.services.gitea_private_inventory_closeout_validation import (
|
||||
from src.services.gitea_private_inventory_p0_scorecard import (
|
||||
load_latest_gitea_private_inventory_p0_scorecard,
|
||||
)
|
||||
from src.services.gitea_workflow_runner_owner_attestation_request import (
|
||||
load_latest_gitea_workflow_runner_owner_attestation_request,
|
||||
)
|
||||
from src.services.gitea_workflow_runner_health import (
|
||||
load_latest_gitea_workflow_runner_health,
|
||||
)
|
||||
from src.services.gitea_workflow_runner_owner_attestation_request import (
|
||||
load_latest_gitea_workflow_runner_owner_attestation_request,
|
||||
)
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_controlled_execution_preflight,
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
@@ -2198,6 +2201,34 @@ async def post_agent_log_controlled_writeback_dispatch() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-log-controlled-writeback-consumer-readback",
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 AI Agent LOG controlled writeback consumer readback",
|
||||
description=(
|
||||
"讀取 automation_operation_log 中的 LOG metadata-only dispatch receipts,"
|
||||
"並投影成 KM / RAG / PlayBook / MCP / verifier / AI Agent consumer "
|
||||
"context binding。此端點不寫 KM、不寫 RAG index、不更新 PlayBook trust、"
|
||||
"不呼叫 MCP tool、不發 Telegram、不觸發 workflow、不保存 raw log payload、"
|
||||
"不讀 secret、不呼叫 GitHub。"
|
||||
),
|
||||
)
|
||||
async def get_agent_log_controlled_writeback_consumer_readback() -> dict[str, Any]:
|
||||
"""Read live metadata-only LOG controlled writeback consumer bindings."""
|
||||
try:
|
||||
payload = await load_latest_ai_agent_log_controlled_writeback_consumer_readback()
|
||||
return redact_public_lan_topology(payload)
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error(
|
||||
"ai_agent_log_controlled_writeback_consumer_readback_invalid",
|
||||
error=str(exc),
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="AI Agent LOG controlled writeback consumer readback 無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-telegram-receipt-approval-package",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -18,6 +18,9 @@ from sqlalchemy import text
|
||||
from src.core.config import settings
|
||||
from src.core.logging import get_logger
|
||||
from src.db.base import get_db_context
|
||||
from src.services.ai_agent_log_controlled_writeback_consumer_readback import (
|
||||
load_latest_ai_agent_log_controlled_writeback_consumer_readback,
|
||||
)
|
||||
from src.services.ai_agent_log_controlled_writeback_dispatch import (
|
||||
OPERATION_TYPE as LOG_CONTROLLED_WRITEBACK_DISPATCH_OPERATION_TYPE,
|
||||
)
|
||||
@@ -269,6 +272,91 @@ def _load_log_controlled_writeback_executor_readback() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _fallback_log_controlled_writeback_consumer_readback(
|
||||
error_type: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
readback = {
|
||||
"schema_version": "ai_agent_log_controlled_writeback_consumer_readback_v1",
|
||||
"priority": "P1-LOG-KM-RAG-MCP-PLAYBOOK",
|
||||
"scope": "ai_agent_log_controlled_writeback_consumer_readback",
|
||||
"status": "blocked_waiting_controlled_writeback_consumer_receipts",
|
||||
"readback": {
|
||||
"workplan_id": "P1-LOG-CONTROLLED-WRITEBACK-CONSUMER-READBACK",
|
||||
"workplan_title": (
|
||||
"LOG metadata ledger receipts consumable by KM / RAG / PlayBook / "
|
||||
"MCP / verifier / AI Agent context"
|
||||
),
|
||||
"source_operation_type": LOG_CONTROLLED_WRITEBACK_DISPATCH_OPERATION_TYPE,
|
||||
"source_executor_route": "ai_agent_metadata_writeback_executor",
|
||||
"safe_next_step": "repair_log_controlled_writeback_consumer_readback_then_retry",
|
||||
},
|
||||
"controlled_consume": {
|
||||
"mode": "blocked_waiting_consumer_readback",
|
||||
"controlled_consume_allowed": False,
|
||||
"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,
|
||||
"runtime_target_write_performed": False,
|
||||
},
|
||||
"consumer_bindings": [],
|
||||
"target_rollups": [],
|
||||
"rollups": {
|
||||
"target_count": 6,
|
||||
"dispatch_ledger_row_count": 0,
|
||||
"consumer_binding_count": 0,
|
||||
"ready_consumer_binding_count": 0,
|
||||
"ready_target_count": 0,
|
||||
"metadata_only_receipt_count": 0,
|
||||
"post_apply_verifier_ref_count": 0,
|
||||
"controlled_consumer_readback_ready": False,
|
||||
"runtime_target_write_performed": False,
|
||||
},
|
||||
"active_blockers": ["log_controlled_writeback_consumer_readback_unavailable"],
|
||||
"operation_boundaries": {
|
||||
"consumer_readback_only": True,
|
||||
"metadata_ledger_read_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,
|
||||
"telegram_send_performed": False,
|
||||
"workflow_trigger_performed": False,
|
||||
"raw_log_payload_persisted": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"github_api_used": False,
|
||||
},
|
||||
}
|
||||
if error_type:
|
||||
readback["readback"]["error_type"] = error_type
|
||||
return readback
|
||||
|
||||
|
||||
async def _load_log_controlled_writeback_consumer_readback(
|
||||
*,
|
||||
project_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Attach LOG consumer bindings without writing KM/RAG/PlayBook/MCP targets."""
|
||||
|
||||
try:
|
||||
return await load_latest_ai_agent_log_controlled_writeback_consumer_readback(
|
||||
project_id=project_id,
|
||||
)
|
||||
except Exception as exc: # pragma: no cover - keeps runtime control API visible
|
||||
logger.warning(
|
||||
"log_controlled_writeback_consumer_readback_failed",
|
||||
project_id=project_id,
|
||||
error_type=type(exc).__name__,
|
||||
)
|
||||
return _fallback_log_controlled_writeback_consumer_readback(
|
||||
error_type=type(exc).__name__,
|
||||
)
|
||||
|
||||
|
||||
def _trace_stage(
|
||||
*,
|
||||
stage_id: str,
|
||||
@@ -1625,6 +1713,7 @@ def _build_work_item_progress(
|
||||
trace_ledger: Mapping[str, Any],
|
||||
log_integration_taxonomy: Mapping[str, Any],
|
||||
log_controlled_writeback_executor: Mapping[str, Any],
|
||||
log_controlled_writeback_consumer: Mapping[str, Any],
|
||||
agent_decision_wiring: Mapping[str, Any],
|
||||
learning_loop: Mapping[str, Any],
|
||||
alert_noise_reduction: Mapping[str, Any],
|
||||
@@ -1687,6 +1776,20 @@ def _build_work_item_progress(
|
||||
and log_executor_rollups.get("controlled_executor_dispatch_ready") is True
|
||||
and not log_executor_blockers
|
||||
)
|
||||
log_consumer_rollups = log_controlled_writeback_consumer.get("rollups")
|
||||
if not isinstance(log_consumer_rollups, Mapping):
|
||||
log_consumer_rollups = {}
|
||||
log_consumer_blockers = log_controlled_writeback_consumer.get("active_blockers")
|
||||
if not isinstance(log_consumer_blockers, list):
|
||||
log_consumer_blockers = []
|
||||
log_consumer_ready = (
|
||||
log_controlled_writeback_consumer.get("schema_version")
|
||||
== "ai_agent_log_controlled_writeback_consumer_readback_v1"
|
||||
and log_controlled_writeback_consumer.get("status")
|
||||
== "controlled_writeback_consumer_readback_ready"
|
||||
and log_consumer_rollups.get("controlled_consumer_readback_ready") is True
|
||||
and not log_consumer_blockers
|
||||
)
|
||||
ui_rollups = ui_productization.get("rollups")
|
||||
if not isinstance(ui_rollups, Mapping):
|
||||
ui_rollups = {}
|
||||
@@ -1799,11 +1902,24 @@ def _build_work_item_progress(
|
||||
),
|
||||
"active_blocker_count": len(log_executor_blockers),
|
||||
},
|
||||
{
|
||||
"work_item_id": "P1-F-log-controlled-writeback-consumer",
|
||||
"priority": "P1-F",
|
||||
"title": "LOG metadata receipts consumable by KM / RAG / MCP / PlayBook / AI Agent",
|
||||
"status": "completed" if log_consumer_ready else "in_progress" if log_executor_ready else "pending",
|
||||
"exit_criteria": "runtime-control exposes ready consumer bindings for all LOG metadata writeback targets",
|
||||
"remaining_consumer_binding_count": max(
|
||||
0,
|
||||
_int_value(log_consumer_rollups.get("target_count"))
|
||||
- _int_value(log_consumer_rollups.get("ready_target_count")),
|
||||
),
|
||||
"active_blocker_count": len(log_consumer_blockers),
|
||||
},
|
||||
{
|
||||
"work_item_id": "P2-A-ui-ux-productization",
|
||||
"priority": "P2-A",
|
||||
"title": "Professional product UI replacing text-heavy surfaces",
|
||||
"status": "completed" if p2a_completed else "in_progress" if log_executor_ready else "pending",
|
||||
"status": "completed" if p2a_completed else "in_progress" if log_consumer_ready else "pending",
|
||||
"exit_criteria": "AI automation status is shown as dense dashboard controls, filters, counters, and action rails",
|
||||
"remaining_ui_surface_count": ui_surface_missing,
|
||||
},
|
||||
@@ -2584,6 +2700,7 @@ def build_runtime_receipt_readback_from_rows(
|
||||
alert_operation_count_rows: Iterable[Mapping[str, Any] | Any] = (),
|
||||
alertmanager_event_count_rows: Iterable[Mapping[str, Any] | Any] = (),
|
||||
grouped_alert_event_count_rows: Iterable[Mapping[str, Any] | Any] = (),
|
||||
log_controlled_writeback_consumer: Mapping[str, Any] | None = None,
|
||||
error_type: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the live executor receipt readback from already-fetched rows."""
|
||||
@@ -2709,10 +2826,15 @@ def build_runtime_receipt_readback_from_rows(
|
||||
ui_productization = _build_ui_productization_readback()
|
||||
multi_product_taxonomy = _build_multi_product_taxonomy_contract(log_integration_taxonomy)
|
||||
log_controlled_writeback_executor = _load_log_controlled_writeback_executor_readback()
|
||||
if not isinstance(log_controlled_writeback_consumer, Mapping):
|
||||
log_controlled_writeback_consumer = (
|
||||
_fallback_log_controlled_writeback_consumer_readback()
|
||||
)
|
||||
work_item_progress = _build_work_item_progress(
|
||||
trace_ledger=trace_ledger,
|
||||
log_integration_taxonomy=log_integration_taxonomy,
|
||||
log_controlled_writeback_executor=log_controlled_writeback_executor,
|
||||
log_controlled_writeback_consumer=log_controlled_writeback_consumer,
|
||||
agent_decision_wiring=agent_decision_wiring,
|
||||
learning_loop=learning_loop,
|
||||
alert_noise_reduction=alert_noise_reduction,
|
||||
@@ -2840,6 +2962,7 @@ def build_runtime_receipt_readback_from_rows(
|
||||
"trace_ledger": trace_ledger,
|
||||
"log_integration_taxonomy": log_integration_taxonomy,
|
||||
"log_controlled_writeback_executor": log_controlled_writeback_executor,
|
||||
"log_controlled_writeback_consumer": dict(log_controlled_writeback_consumer),
|
||||
"agent_decision_wiring": agent_decision_wiring,
|
||||
"learning_loop": learning_loop,
|
||||
"alert_noise_reduction": alert_noise_reduction,
|
||||
@@ -2876,6 +2999,15 @@ def _attach_runtime_receipt_readback(
|
||||
log_executor_blockers = log_executor.get("active_blockers")
|
||||
if not isinstance(log_executor_blockers, list):
|
||||
log_executor_blockers = []
|
||||
log_consumer = readback.get("log_controlled_writeback_consumer")
|
||||
if not isinstance(log_consumer, Mapping):
|
||||
log_consumer = {}
|
||||
log_consumer_rollups = log_consumer.get("rollups")
|
||||
if not isinstance(log_consumer_rollups, Mapping):
|
||||
log_consumer_rollups = {}
|
||||
log_consumer_blockers = log_consumer.get("active_blockers")
|
||||
if not isinstance(log_consumer_blockers, list):
|
||||
log_consumer_blockers = []
|
||||
operation_counts = (readback.get("ansible_operations") or {}).get("counts")
|
||||
if not isinstance(operation_counts, Mapping):
|
||||
operation_counts = {}
|
||||
@@ -2992,6 +3124,48 @@ def _attach_runtime_receipt_readback(
|
||||
"live_log_controlled_writeback_recent_dispatch_count": _int_value(
|
||||
log_dispatch_summary.get("recent")
|
||||
),
|
||||
"live_log_controlled_writeback_consumer_binding_count": _int_value(
|
||||
log_consumer_rollups.get("consumer_binding_count")
|
||||
),
|
||||
"live_log_controlled_writeback_consumer_ready_binding_count": _int_value(
|
||||
log_consumer_rollups.get("ready_consumer_binding_count")
|
||||
),
|
||||
"live_log_controlled_writeback_consumer_ready_target_count": _int_value(
|
||||
log_consumer_rollups.get("ready_target_count")
|
||||
),
|
||||
"live_log_controlled_writeback_consumer_ready_count": (
|
||||
1
|
||||
if log_consumer.get("status")
|
||||
== "controlled_writeback_consumer_readback_ready"
|
||||
else 0
|
||||
),
|
||||
"live_log_controlled_writeback_consumer_blocker_count": len(
|
||||
log_consumer_blockers
|
||||
),
|
||||
"live_log_controlled_writeback_consumer_metadata_only_count": _int_value(
|
||||
log_consumer_rollups.get("metadata_only_receipt_count")
|
||||
),
|
||||
"live_log_controlled_writeback_consumer_verifier_ref_count": _int_value(
|
||||
log_consumer_rollups.get("post_apply_verifier_ref_count")
|
||||
),
|
||||
"live_log_controlled_writeback_km_consumer_binding_count": _int_value(
|
||||
log_consumer_rollups.get("km_consumer_binding_count")
|
||||
),
|
||||
"live_log_controlled_writeback_rag_consumer_binding_count": _int_value(
|
||||
log_consumer_rollups.get("rag_consumer_binding_count")
|
||||
),
|
||||
"live_log_controlled_writeback_playbook_consumer_binding_count": _int_value(
|
||||
log_consumer_rollups.get("playbook_consumer_binding_count")
|
||||
),
|
||||
"live_log_controlled_writeback_mcp_consumer_binding_count": _int_value(
|
||||
log_consumer_rollups.get("mcp_consumer_binding_count")
|
||||
),
|
||||
"live_log_controlled_writeback_verifier_consumer_binding_count": _int_value(
|
||||
log_consumer_rollups.get("verifier_consumer_binding_count")
|
||||
),
|
||||
"live_log_controlled_writeback_ai_agent_consumer_binding_count": _int_value(
|
||||
log_consumer_rollups.get("ai_agent_consumer_binding_count")
|
||||
),
|
||||
"live_agent_decision_wiring_stage_count": _int_value(
|
||||
((readback.get("agent_decision_wiring") or {}).get("rollups") or {}).get(
|
||||
"stage_count"
|
||||
@@ -3483,6 +3657,11 @@ async def load_ai_agent_autonomous_runtime_receipt_readback(
|
||||
"grouped_alert_event_counts",
|
||||
_RUNTIME_GROUPED_ALERT_EVENT_COUNTS_SQL,
|
||||
)
|
||||
log_controlled_writeback_consumer = (
|
||||
await _load_log_controlled_writeback_consumer_readback(
|
||||
project_id=project_id,
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"ai_agent_autonomous_runtime_receipt_readback_failed",
|
||||
@@ -3519,6 +3698,7 @@ async def load_ai_agent_autonomous_runtime_receipt_readback(
|
||||
alert_operation_count_rows=alert_operation_counts,
|
||||
alertmanager_event_count_rows=alertmanager_event_counts,
|
||||
grouped_alert_event_count_rows=grouped_alert_event_counts,
|
||||
log_controlled_writeback_consumer=log_controlled_writeback_consumer,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
"""AI Agent LOG controlled writeback consumer readback.
|
||||
|
||||
Reads live metadata-only dispatch receipts from automation_operation_log and
|
||||
turns them into KM / RAG / PlayBook / MCP / verifier / AI Agent consumer
|
||||
bindings. This endpoint does not write those target systems, call MCP tools,
|
||||
trigger workflows, or persist raw log payloads.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
from src.db.base import get_db_context
|
||||
from src.services.ai_agent_log_controlled_writeback_dispatch import (
|
||||
EXECUTOR_ROUTE,
|
||||
OPERATION_TYPE,
|
||||
)
|
||||
|
||||
SCHEMA_VERSION = "ai_agent_log_controlled_writeback_consumer_readback_v1"
|
||||
DEFAULT_PROJECT_ID = "awoooi"
|
||||
_TARGETS = ("km", "rag", "playbook", "mcp", "verifier", "ai_agent")
|
||||
_CONSUMER_SURFACES = {
|
||||
"km": "knowledge_memory_context",
|
||||
"rag": "rag_index_candidate_context",
|
||||
"playbook": "playbook_trust_candidate_context",
|
||||
"mcp": "mcp_audit_feedback_context",
|
||||
"verifier": "post_apply_verifier_feedback_context",
|
||||
"ai_agent": "autonomous_runtime_decision_context",
|
||||
}
|
||||
|
||||
|
||||
async def load_latest_ai_agent_log_controlled_writeback_consumer_readback(
|
||||
*,
|
||||
project_id: str = DEFAULT_PROJECT_ID,
|
||||
) -> dict[str, Any]:
|
||||
"""Return live consumer bindings for LOG controlled writeback receipts."""
|
||||
|
||||
async with get_db_context(project_id) as db:
|
||||
result = await db.execute(
|
||||
text("""
|
||||
SELECT
|
||||
op_id::text AS op_id,
|
||||
operation_type,
|
||||
actor,
|
||||
status,
|
||||
created_at,
|
||||
input ->> 'semantic_operation_type' AS semantic_operation_type,
|
||||
input ->> 'ledger_operation_type' AS ledger_operation_type,
|
||||
input ->> 'dispatch_receipt_id' AS dispatch_receipt_id,
|
||||
input ->> 'batch_id' AS batch_id,
|
||||
input ->> 'target' AS target,
|
||||
input ->> 'target_surface' AS target_surface,
|
||||
input ->> 'risk_tier' AS risk_tier,
|
||||
input ->> 'project_id' AS project_id,
|
||||
input ->> 'raw_payload_included' AS raw_payload_included,
|
||||
output ->> 'next_action' AS next_action,
|
||||
output ->> 'km_write_performed' AS km_write_performed,
|
||||
output ->> 'rag_index_write_performed' AS rag_index_write_performed,
|
||||
output ->> 'playbook_trust_write_performed' AS playbook_trust_write_performed,
|
||||
output ->> 'mcp_tool_call_performed' AS mcp_tool_call_performed,
|
||||
output ->> 'telegram_send_performed' AS telegram_send_performed,
|
||||
output -> 'post_apply_verifier_refs' AS post_apply_verifier_refs
|
||||
FROM automation_operation_log
|
||||
WHERE coalesce(input ->> 'semantic_operation_type', operation_type)
|
||||
= :operation_type
|
||||
ORDER BY created_at DESC, op_id DESC
|
||||
LIMIT 50
|
||||
"""),
|
||||
{"operation_type": OPERATION_TYPE},
|
||||
)
|
||||
rows = _result_rows(result)
|
||||
bindings = [_consumer_binding(row) for row in rows]
|
||||
active_blockers = _active_blockers(bindings)
|
||||
|
||||
return {
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"priority": "P1-LOG-KM-RAG-MCP-PLAYBOOK",
|
||||
"scope": "ai_agent_log_controlled_writeback_consumer_readback",
|
||||
"status": (
|
||||
"controlled_writeback_consumer_readback_ready"
|
||||
if not active_blockers
|
||||
else "blocked_waiting_controlled_writeback_consumer_receipts"
|
||||
),
|
||||
"readback": {
|
||||
"workplan_id": "P1-LOG-CONTROLLED-WRITEBACK-CONSUMER-READBACK",
|
||||
"workplan_title": (
|
||||
"LOG metadata ledger receipts consumable by KM / RAG / PlayBook / "
|
||||
"MCP / verifier / AI Agent context"
|
||||
),
|
||||
"source_operation_type": OPERATION_TYPE,
|
||||
"source_executor_route": EXECUTOR_ROUTE,
|
||||
"safe_next_step": (
|
||||
"consume_metadata_receipts_in_km_rag_playbook_agent_context_with_post_apply_verifier"
|
||||
),
|
||||
},
|
||||
"controlled_consume": {
|
||||
"mode": "controlled_consumer_readback_ready"
|
||||
if not active_blockers
|
||||
else "blocked_waiting_consumer_readback",
|
||||
"controlled_consume_allowed": not active_blockers,
|
||||
"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,
|
||||
"runtime_target_write_performed": False,
|
||||
},
|
||||
"consumer_bindings": bindings,
|
||||
"target_rollups": _target_rollups(bindings),
|
||||
"rollups": {
|
||||
"target_count": len(_TARGETS),
|
||||
"dispatch_ledger_row_count": len(rows),
|
||||
"consumer_binding_count": len(bindings),
|
||||
"ready_consumer_binding_count": sum(
|
||||
1 for binding in bindings if binding["status"] == "ready_for_consumer_context"
|
||||
),
|
||||
"ready_target_count": sum(
|
||||
1 for item in _target_rollups(bindings) if item["ready_binding_count"] > 0
|
||||
),
|
||||
"metadata_only_receipt_count": sum(
|
||||
1 for binding in bindings if binding["raw_payload_included"] is False
|
||||
),
|
||||
"post_apply_verifier_ref_count": sum(
|
||||
len(binding["post_apply_verifier_refs"]) for binding in bindings
|
||||
),
|
||||
"controlled_consumer_readback_ready": not active_blockers,
|
||||
"km_consumer_binding_count": _target_count(bindings, "km"),
|
||||
"rag_consumer_binding_count": _target_count(bindings, "rag"),
|
||||
"playbook_consumer_binding_count": _target_count(bindings, "playbook"),
|
||||
"mcp_consumer_binding_count": _target_count(bindings, "mcp"),
|
||||
"verifier_consumer_binding_count": _target_count(bindings, "verifier"),
|
||||
"ai_agent_consumer_binding_count": _target_count(bindings, "ai_agent"),
|
||||
"runtime_target_write_performed": False,
|
||||
},
|
||||
"active_blockers": active_blockers,
|
||||
"operation_boundaries": {
|
||||
"consumer_readback_only": True,
|
||||
"metadata_ledger_read_performed": True,
|
||||
"km_write_performed": False,
|
||||
"rag_index_write_performed": False,
|
||||
"playbook_trust_write_performed": False,
|
||||
"mcp_tool_call_performed": False,
|
||||
"agent_runtime_action_performed": False,
|
||||
"telegram_send_performed": False,
|
||||
"workflow_trigger_performed": False,
|
||||
"raw_log_payload_persisted": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"github_api_used": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _consumer_binding(row: Mapping[str, Any]) -> dict[str, Any]:
|
||||
target = str(row.get("target") or "")
|
||||
verifier_refs = _strings(row.get("post_apply_verifier_refs"))
|
||||
raw_payload_included = str(row.get("raw_payload_included") or "").lower() == "true"
|
||||
return {
|
||||
"consumer_binding_id": f"consumer::{row.get('dispatch_receipt_id') or row.get('op_id')}",
|
||||
"dispatch_receipt_id": str(row.get("dispatch_receipt_id") or ""),
|
||||
"ledger_op_id": str(row.get("op_id") or ""),
|
||||
"target": target,
|
||||
"target_surface": str(row.get("target_surface") or ""),
|
||||
"consumer_surface": _CONSUMER_SURFACES.get(target, "unknown_consumer_surface"),
|
||||
"risk_tier": str(row.get("risk_tier") or ""),
|
||||
"status": (
|
||||
"ready_for_consumer_context"
|
||||
if _binding_ready(row, raw_payload_included)
|
||||
else "blocked_waiting_consumer_binding_controls"
|
||||
),
|
||||
"project_id": str(row.get("project_id") or ""),
|
||||
"executor_route": str(row.get("actor") or ""),
|
||||
"semantic_operation_type": str(row.get("semantic_operation_type") or ""),
|
||||
"ledger_operation_type": str(row.get("ledger_operation_type") or ""),
|
||||
"ledger_status": str(row.get("status") or ""),
|
||||
"raw_payload_included": raw_payload_included,
|
||||
"next_action": str(row.get("next_action") or ""),
|
||||
"post_apply_verifier_refs": verifier_refs,
|
||||
"target_selector": {
|
||||
"dispatch_receipt_id": str(row.get("dispatch_receipt_id") or ""),
|
||||
"target": target,
|
||||
"target_surface": str(row.get("target_surface") or ""),
|
||||
"consumer_surface": _CONSUMER_SURFACES.get(target, "unknown_consumer_surface"),
|
||||
},
|
||||
"source_of_truth_diff": {
|
||||
"current_state": "metadata_dispatch_receipt_in_ledger",
|
||||
"desired_state": "metadata_receipt_available_to_consumer_context",
|
||||
"delta_kind": f"{target}_consumer_context_binding",
|
||||
"raw_payload_included": raw_payload_included,
|
||||
},
|
||||
"check_mode": {
|
||||
"enabled": True,
|
||||
"checks": [
|
||||
"dispatch_receipt_id_present",
|
||||
"target_surface_present",
|
||||
"metadata_only_raw_payload_absent",
|
||||
"post_apply_verifier_refs_present",
|
||||
"ledger_status_success",
|
||||
],
|
||||
},
|
||||
"rollback": {
|
||||
"required": True,
|
||||
"rollback_ref": (
|
||||
"rollback://ai-agent-log-controlled-writeback-consumer/"
|
||||
f"{row.get('dispatch_receipt_id') or row.get('op_id')}"
|
||||
),
|
||||
"strategy": "ignore_consumer_binding_and_mark_receipt_superseded",
|
||||
},
|
||||
"post_apply_verifier": {
|
||||
"required": True,
|
||||
"verifier_refs": verifier_refs,
|
||||
},
|
||||
"target_write_performed": False,
|
||||
}
|
||||
|
||||
|
||||
def _binding_ready(row: Mapping[str, Any], raw_payload_included: bool) -> bool:
|
||||
if str(row.get("semantic_operation_type") or "") != OPERATION_TYPE:
|
||||
return False
|
||||
if str(row.get("actor") or "") != EXECUTOR_ROUTE:
|
||||
return False
|
||||
if str(row.get("status") or "") != "success":
|
||||
return False
|
||||
if str(row.get("target") or "") not in _TARGETS:
|
||||
return False
|
||||
if not str(row.get("dispatch_receipt_id") or ""):
|
||||
return False
|
||||
if not str(row.get("target_surface") or ""):
|
||||
return False
|
||||
if raw_payload_included:
|
||||
return False
|
||||
return bool(_strings(row.get("post_apply_verifier_refs")))
|
||||
|
||||
|
||||
def _active_blockers(bindings: list[dict[str, Any]]) -> list[str]:
|
||||
blockers: list[str] = []
|
||||
if not bindings:
|
||||
blockers.append("log_controlled_writeback_dispatch_receipts_missing")
|
||||
missing_targets = [target for target in _TARGETS if _target_count(bindings, target) == 0]
|
||||
for target in missing_targets:
|
||||
blockers.append(f"{target}_consumer_binding_missing")
|
||||
for binding in bindings:
|
||||
if binding["status"] != "ready_for_consumer_context":
|
||||
blockers.append(f"{binding['target'] or 'unknown'}_consumer_binding_not_ready")
|
||||
if binding["raw_payload_included"] is not False:
|
||||
blockers.append(f"{binding['target'] or 'unknown'}_raw_payload_included")
|
||||
return _unique(blockers)
|
||||
|
||||
|
||||
def _target_rollups(bindings: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"target": target,
|
||||
"consumer_surface": _CONSUMER_SURFACES[target],
|
||||
"binding_count": _target_count(bindings, target),
|
||||
"ready_binding_count": sum(
|
||||
1
|
||||
for binding in bindings
|
||||
if binding["target"] == target
|
||||
and binding["status"] == "ready_for_consumer_context"
|
||||
),
|
||||
"metadata_only": all(
|
||||
binding["raw_payload_included"] is False
|
||||
for binding in bindings
|
||||
if binding["target"] == target
|
||||
),
|
||||
}
|
||||
for target in _TARGETS
|
||||
]
|
||||
|
||||
|
||||
def _target_count(bindings: list[dict[str, Any]], target: str) -> int:
|
||||
return sum(1 for binding in bindings if binding["target"] == target)
|
||||
|
||||
|
||||
def _result_rows(result: Any) -> list[dict[str, Any]]:
|
||||
mappings = getattr(result, "mappings", None)
|
||||
if callable(mappings):
|
||||
return [dict(row) for row in mappings().all()]
|
||||
all_rows = getattr(result, "all", None)
|
||||
if callable(all_rows):
|
||||
return [_row_mapping(row) for row in all_rows()]
|
||||
return [_row_mapping(row) for row in result]
|
||||
|
||||
|
||||
def _row_mapping(row: Any) -> dict[str, Any]:
|
||||
if isinstance(row, Mapping):
|
||||
return dict(row)
|
||||
mapping = getattr(row, "_mapping", None)
|
||||
if mapping is not None:
|
||||
return dict(mapping)
|
||||
return dict(row)
|
||||
|
||||
|
||||
def _strings(value: Any) -> list[str]:
|
||||
if isinstance(value, list):
|
||||
return [str(item) for item in value]
|
||||
if isinstance(value, tuple):
|
||||
return [str(item) for item in value]
|
||||
return []
|
||||
|
||||
|
||||
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
|
||||
@@ -54,6 +54,101 @@ def _assert_log_controlled_writeback_executor(payload: dict):
|
||||
assert boundaries["github_api_used"] is False
|
||||
|
||||
|
||||
def _log_controlled_writeback_consumer_readback() -> dict:
|
||||
targets = ("km", "rag", "playbook", "mcp", "verifier", "ai_agent")
|
||||
bindings = [
|
||||
{
|
||||
"consumer_binding_id": f"consumer::receipt::{target}",
|
||||
"dispatch_receipt_id": f"receipt::{target}",
|
||||
"ledger_op_id": f"op::{target}",
|
||||
"target": target,
|
||||
"target_surface": f"{target}_metadata_feedback_binding",
|
||||
"consumer_surface": f"{target}_consumer_context",
|
||||
"status": "ready_for_consumer_context",
|
||||
"raw_payload_included": False,
|
||||
"post_apply_verifier_refs": [f"verifier://{target}"],
|
||||
"target_write_performed": False,
|
||||
}
|
||||
for target in targets
|
||||
]
|
||||
return {
|
||||
"schema_version": "ai_agent_log_controlled_writeback_consumer_readback_v1",
|
||||
"priority": "P1-LOG-KM-RAG-MCP-PLAYBOOK",
|
||||
"scope": "ai_agent_log_controlled_writeback_consumer_readback",
|
||||
"status": "controlled_writeback_consumer_readback_ready",
|
||||
"readback": {
|
||||
"workplan_id": "P1-LOG-CONTROLLED-WRITEBACK-CONSUMER-READBACK",
|
||||
"source_operation_type": "log_controlled_writeback_dispatched",
|
||||
"source_executor_route": "ai_agent_metadata_writeback_executor",
|
||||
},
|
||||
"controlled_consume": {
|
||||
"controlled_consume_allowed": True,
|
||||
"runtime_target_write_performed": False,
|
||||
},
|
||||
"consumer_bindings": bindings,
|
||||
"target_rollups": [
|
||||
{
|
||||
"target": target,
|
||||
"consumer_surface": f"{target}_consumer_context",
|
||||
"binding_count": 1,
|
||||
"ready_binding_count": 1,
|
||||
"metadata_only": True,
|
||||
}
|
||||
for target in targets
|
||||
],
|
||||
"rollups": {
|
||||
"target_count": 6,
|
||||
"dispatch_ledger_row_count": 6,
|
||||
"consumer_binding_count": 6,
|
||||
"ready_consumer_binding_count": 6,
|
||||
"ready_target_count": 6,
|
||||
"metadata_only_receipt_count": 6,
|
||||
"post_apply_verifier_ref_count": 6,
|
||||
"controlled_consumer_readback_ready": True,
|
||||
"km_consumer_binding_count": 1,
|
||||
"rag_consumer_binding_count": 1,
|
||||
"playbook_consumer_binding_count": 1,
|
||||
"mcp_consumer_binding_count": 1,
|
||||
"verifier_consumer_binding_count": 1,
|
||||
"ai_agent_consumer_binding_count": 1,
|
||||
"runtime_target_write_performed": False,
|
||||
},
|
||||
"active_blockers": [],
|
||||
"operation_boundaries": {
|
||||
"consumer_readback_only": True,
|
||||
"metadata_ledger_read_performed": True,
|
||||
"km_write_performed": False,
|
||||
"rag_index_write_performed": False,
|
||||
"playbook_trust_write_performed": False,
|
||||
"mcp_tool_call_performed": False,
|
||||
"agent_runtime_action_performed": False,
|
||||
"telegram_send_performed": False,
|
||||
"workflow_trigger_performed": False,
|
||||
"raw_log_payload_persisted": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"github_api_used": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _assert_log_controlled_writeback_consumer(payload: dict):
|
||||
assert payload["schema_version"] == (
|
||||
"ai_agent_log_controlled_writeback_consumer_readback_v1"
|
||||
)
|
||||
assert payload["status"] == "controlled_writeback_consumer_readback_ready"
|
||||
assert payload["active_blockers"] == []
|
||||
assert payload["controlled_consume"]["controlled_consume_allowed"] is True
|
||||
assert payload["controlled_consume"]["runtime_target_write_performed"] is False
|
||||
assert payload["rollups"]["consumer_binding_count"] == 6
|
||||
assert payload["rollups"]["ready_consumer_binding_count"] == 6
|
||||
assert payload["rollups"]["ready_target_count"] == 6
|
||||
assert payload["rollups"]["controlled_consumer_readback_ready"] is True
|
||||
assert payload["operation_boundaries"]["consumer_readback_only"] is True
|
||||
assert payload["operation_boundaries"]["mcp_tool_call_performed"] is False
|
||||
assert payload["operation_boundaries"]["raw_log_payload_persisted"] is False
|
||||
assert payload["operation_boundaries"]["github_api_used"] is False
|
||||
|
||||
|
||||
def test_runtime_receipt_auxiliary_sql_keeps_source_family_counts_schema_safe():
|
||||
from src.services.ai_agent_autonomous_runtime_control import (
|
||||
_RUNTIME_OPERATION_COUNTS_SQL,
|
||||
@@ -134,6 +229,11 @@ def test_ai_agent_autonomous_runtime_control_exposes_reports_and_executor_receip
|
||||
_assert_log_controlled_writeback_executor(
|
||||
data["runtime_receipt_readback"]["log_controlled_writeback_executor"]
|
||||
)
|
||||
consumer = data["runtime_receipt_readback"]["log_controlled_writeback_consumer"]
|
||||
assert consumer["schema_version"] == (
|
||||
"ai_agent_log_controlled_writeback_consumer_readback_v1"
|
||||
)
|
||||
assert consumer["status"] == "blocked_waiting_controlled_writeback_consumer_receipts"
|
||||
assert data["rollups"]["live_log_controlled_writeback_executor_batch_count"] == 6
|
||||
assert data["rollups"]["live_log_controlled_writeback_executor_ready_batch_count"] == 6
|
||||
assert data["rollups"]["live_log_controlled_writeback_executor_ready_count"] == 1
|
||||
@@ -141,6 +241,9 @@ def test_ai_agent_autonomous_runtime_control_exposes_reports_and_executor_receip
|
||||
assert data["rollups"]["live_log_controlled_writeback_next_action_queue_count"] == 6
|
||||
assert data["rollups"]["live_log_controlled_writeback_dispatch_count"] == 0
|
||||
assert data["rollups"]["live_log_controlled_writeback_recent_dispatch_count"] == 0
|
||||
assert data["rollups"]["live_log_controlled_writeback_consumer_binding_count"] == 0
|
||||
assert data["rollups"]["live_log_controlled_writeback_consumer_ready_count"] == 0
|
||||
assert data["rollups"]["live_log_controlled_writeback_consumer_blocker_count"] == 1
|
||||
assert data["runtime_receipt_readback"]["learning_loop"]["status"] == "in_progress"
|
||||
assert (
|
||||
data["runtime_receipt_readback"]["learning_loop"]["rollups"][
|
||||
@@ -398,6 +501,7 @@ def test_runtime_receipt_readback_summarizes_live_executor_closure_rows():
|
||||
grouped_alert_event_count_rows=[
|
||||
{"status": "grouped_child_alert", "total": 4, "recent": 1},
|
||||
],
|
||||
log_controlled_writeback_consumer=_log_controlled_writeback_consumer_readback(),
|
||||
)
|
||||
|
||||
assert readback["db_read_status"] == "ok"
|
||||
@@ -533,6 +637,9 @@ def test_runtime_receipt_readback_summarizes_live_executor_closure_rows():
|
||||
_assert_log_controlled_writeback_executor(
|
||||
readback["log_controlled_writeback_executor"]
|
||||
)
|
||||
_assert_log_controlled_writeback_consumer(
|
||||
readback["log_controlled_writeback_consumer"]
|
||||
)
|
||||
decision_wiring = readback["agent_decision_wiring"]
|
||||
assert decision_wiring["schema_version"] == "ai_agent_decision_wiring_readback_v1"
|
||||
assert decision_wiring["status"] == "completed"
|
||||
@@ -642,6 +749,7 @@ def test_runtime_receipt_readback_summarizes_live_executor_closure_rows():
|
||||
"P1-C-learning-loop",
|
||||
"P1-D-alert-noise-reduction",
|
||||
"P1-E-log-controlled-writeback-executor",
|
||||
"P1-F-log-controlled-writeback-consumer",
|
||||
"P2-A-ui-ux-productization",
|
||||
"P2-B-multi-product-expansion",
|
||||
]
|
||||
@@ -656,13 +764,16 @@ def test_runtime_receipt_readback_summarizes_live_executor_closure_rows():
|
||||
assert progress["ordered_items"][9]["remaining_executor_batch_count"] == 0
|
||||
assert progress["ordered_items"][9]["active_blocker_count"] == 0
|
||||
assert progress["ordered_items"][10]["status"] == "completed"
|
||||
assert progress["ordered_items"][10]["remaining_ui_surface_count"] == 0
|
||||
assert progress["ordered_items"][10]["remaining_consumer_binding_count"] == 0
|
||||
assert progress["ordered_items"][10]["active_blocker_count"] == 0
|
||||
assert progress["ordered_items"][11]["status"] == "completed"
|
||||
assert progress["ordered_items"][11]["remaining_product_scope_count"] == 0
|
||||
assert progress["ordered_items"][11]["remaining_ui_surface_count"] == 0
|
||||
assert progress["ordered_items"][12]["status"] == "completed"
|
||||
assert progress["ordered_items"][12]["remaining_product_scope_count"] == 0
|
||||
assert progress["source_family_items"]
|
||||
assert {item["status"] for item in progress["source_family_items"]} == {"completed"}
|
||||
assert progress["rollups"]["source_family_work_item_count"] == 10
|
||||
assert progress["rollups"]["completed_count"] == 22
|
||||
assert progress["rollups"]["completed_count"] == 23
|
||||
assert progress["rollups"]["pending_count"] == 0
|
||||
|
||||
|
||||
@@ -830,6 +941,7 @@ def test_runtime_receipt_work_items_use_learning_receipts_without_latest_telegra
|
||||
grouped_alert_event_count_rows=[
|
||||
{"status": "grouped_child_alert", "total": 4, "recent": 1},
|
||||
],
|
||||
log_controlled_writeback_consumer=_log_controlled_writeback_consumer_readback(),
|
||||
)
|
||||
|
||||
assert readback["latest_flow_closure"]["closed"] is False
|
||||
@@ -848,10 +960,11 @@ def test_runtime_receipt_work_items_use_learning_receipts_without_latest_telegra
|
||||
assert statuses["P1-C-learning-loop"] == "completed"
|
||||
assert statuses["P1-D-alert-noise-reduction"] == "completed"
|
||||
assert statuses["P1-E-log-controlled-writeback-executor"] == "completed"
|
||||
assert statuses["P1-F-log-controlled-writeback-consumer"] == "completed"
|
||||
assert statuses["P2-A-ui-ux-productization"] == "completed"
|
||||
assert statuses["P2-B-multi-product-expansion"] == "completed"
|
||||
assert {item["status"] for item in progress["source_family_items"]} == {"completed"}
|
||||
assert progress["rollups"]["completed_count"] == 22
|
||||
assert progress["rollups"]["completed_count"] == 23
|
||||
assert progress["rollups"]["pending_count"] == 0
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1 import agents
|
||||
from src.api.v1.agents import router
|
||||
from src.services import (
|
||||
ai_agent_log_controlled_writeback_consumer_readback as consumer_module,
|
||||
)
|
||||
from src.services.ai_agent_log_controlled_writeback_consumer_readback import (
|
||||
load_latest_ai_agent_log_controlled_writeback_consumer_readback,
|
||||
)
|
||||
|
||||
|
||||
class _FakeMappingResult:
|
||||
def __init__(self, rows: list[dict]):
|
||||
self._rows = rows
|
||||
|
||||
def mappings(self):
|
||||
return self
|
||||
|
||||
def all(self) -> list[dict]:
|
||||
return self._rows
|
||||
|
||||
|
||||
class _FakeDb:
|
||||
def __init__(self, rows: list[dict]):
|
||||
self.rows = rows
|
||||
self.params: list[dict] = []
|
||||
|
||||
async def execute(self, _statement, params: dict):
|
||||
self.params.append(params)
|
||||
return _FakeMappingResult(self.rows)
|
||||
|
||||
|
||||
class _FakeContext:
|
||||
def __init__(self, db: _FakeDb):
|
||||
self.db = db
|
||||
|
||||
async def __aenter__(self) -> _FakeDb:
|
||||
return self.db
|
||||
|
||||
async def __aexit__(self, _exc_type, _exc, _tb) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _ledger_rows() -> list[dict]:
|
||||
return [
|
||||
{
|
||||
"op_id": f"op-{target}",
|
||||
"operation_type": "km_linked",
|
||||
"actor": "ai_agent_metadata_writeback_executor",
|
||||
"status": "success",
|
||||
"created_at": "2026-06-30T01:40:00+08:00",
|
||||
"semantic_operation_type": "log_controlled_writeback_dispatched",
|
||||
"ledger_operation_type": "km_linked",
|
||||
"dispatch_receipt_id": f"log_controlled_writeback_dispatched::{target}",
|
||||
"batch_id": f"log-feedback-writeback::{target}",
|
||||
"target": target,
|
||||
"target_surface": f"{target}_metadata_feedback_binding",
|
||||
"risk_tier": "medium" if target in {"km", "rag", "playbook"} else "low",
|
||||
"project_id": "awoooi",
|
||||
"raw_payload_included": "false",
|
||||
"next_action": "consume_metadata_receipt_in_km_rag_playbook_agent_context",
|
||||
"km_write_performed": "false",
|
||||
"rag_index_write_performed": "false",
|
||||
"playbook_trust_write_performed": "false",
|
||||
"mcp_tool_call_performed": "false",
|
||||
"telegram_send_performed": "false",
|
||||
"post_apply_verifier_refs": [
|
||||
f"post-write-verifier://ai-agent-log-feedback/{target}/sample-a"
|
||||
],
|
||||
}
|
||||
for target in ("km", "rag", "playbook", "mcp", "verifier", "ai_agent")
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_log_controlled_writeback_consumer_loader_reads_live_ledger(monkeypatch):
|
||||
fake_db = _FakeDb(_ledger_rows())
|
||||
monkeypatch.setattr(
|
||||
consumer_module,
|
||||
"get_db_context",
|
||||
lambda project_id: _FakeContext(fake_db),
|
||||
)
|
||||
|
||||
payload = await load_latest_ai_agent_log_controlled_writeback_consumer_readback()
|
||||
|
||||
_assert_consumer_readback(payload)
|
||||
assert fake_db.params == [{"operation_type": "log_controlled_writeback_dispatched"}]
|
||||
|
||||
|
||||
def test_log_controlled_writeback_consumer_endpoint_returns_readback(monkeypatch):
|
||||
async def fake_loader():
|
||||
fake_db = _FakeDb(_ledger_rows())
|
||||
monkeypatch.setattr(
|
||||
consumer_module,
|
||||
"get_db_context",
|
||||
lambda project_id: _FakeContext(fake_db),
|
||||
)
|
||||
return await load_latest_ai_agent_log_controlled_writeback_consumer_readback()
|
||||
|
||||
monkeypatch.setattr(
|
||||
agents,
|
||||
"load_latest_ai_agent_log_controlled_writeback_consumer_readback",
|
||||
fake_loader,
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/agents/agent-log-controlled-writeback-consumer-readback"
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
_assert_consumer_readback(response.json())
|
||||
|
||||
|
||||
def _assert_consumer_readback(payload: dict):
|
||||
assert (
|
||||
payload["schema_version"]
|
||||
== "ai_agent_log_controlled_writeback_consumer_readback_v1"
|
||||
)
|
||||
assert payload["priority"] == "P1-LOG-KM-RAG-MCP-PLAYBOOK"
|
||||
assert payload["status"] == "controlled_writeback_consumer_readback_ready"
|
||||
assert payload["active_blockers"] == []
|
||||
assert payload["readback"]["source_operation_type"] == (
|
||||
"log_controlled_writeback_dispatched"
|
||||
)
|
||||
assert payload["readback"]["source_executor_route"] == (
|
||||
"ai_agent_metadata_writeback_executor"
|
||||
)
|
||||
assert payload["controlled_consume"]["controlled_consume_allowed"] is True
|
||||
assert payload["controlled_consume"]["runtime_target_write_performed"] is False
|
||||
assert payload["rollups"]["dispatch_ledger_row_count"] == 6
|
||||
assert payload["rollups"]["consumer_binding_count"] == 6
|
||||
assert payload["rollups"]["ready_consumer_binding_count"] == 6
|
||||
assert payload["rollups"]["ready_target_count"] == 6
|
||||
assert payload["rollups"]["metadata_only_receipt_count"] == 6
|
||||
assert payload["rollups"]["post_apply_verifier_ref_count"] == 6
|
||||
assert payload["rollups"]["controlled_consumer_readback_ready"] is True
|
||||
assert payload["rollups"]["km_consumer_binding_count"] == 1
|
||||
assert payload["rollups"]["rag_consumer_binding_count"] == 1
|
||||
assert payload["rollups"]["playbook_consumer_binding_count"] == 1
|
||||
assert payload["rollups"]["mcp_consumer_binding_count"] == 1
|
||||
assert payload["rollups"]["verifier_consumer_binding_count"] == 1
|
||||
assert payload["rollups"]["ai_agent_consumer_binding_count"] == 1
|
||||
|
||||
target_rollups = {item["target"]: item for item in payload["target_rollups"]}
|
||||
assert set(target_rollups) == {"km", "rag", "playbook", "mcp", "verifier", "ai_agent"}
|
||||
for rollup in target_rollups.values():
|
||||
assert rollup["consumer_surface"]
|
||||
assert rollup["binding_count"] == 1
|
||||
assert rollup["ready_binding_count"] == 1
|
||||
assert rollup["metadata_only"] is True
|
||||
|
||||
bindings = payload["consumer_bindings"]
|
||||
assert {binding["target"] for binding in bindings} == set(target_rollups)
|
||||
for binding in bindings:
|
||||
assert binding["status"] == "ready_for_consumer_context"
|
||||
assert binding["executor_route"] == "ai_agent_metadata_writeback_executor"
|
||||
assert binding["semantic_operation_type"] == "log_controlled_writeback_dispatched"
|
||||
assert binding["ledger_status"] == "success"
|
||||
assert binding["raw_payload_included"] is False
|
||||
assert binding["target_write_performed"] is False
|
||||
assert binding["check_mode"]["enabled"] is True
|
||||
assert binding["rollback"]["required"] is True
|
||||
assert binding["post_apply_verifier"]["required"] is True
|
||||
assert binding["post_apply_verifier"]["verifier_refs"]
|
||||
assert binding["source_of_truth_diff"]["raw_payload_included"] is False
|
||||
|
||||
boundaries = payload["operation_boundaries"]
|
||||
assert boundaries["consumer_readback_only"] is True
|
||||
assert boundaries["metadata_ledger_read_performed"] is True
|
||||
assert boundaries["km_write_performed"] is False
|
||||
assert boundaries["rag_index_write_performed"] is False
|
||||
assert boundaries["playbook_trust_write_performed"] is False
|
||||
assert boundaries["mcp_tool_call_performed"] is False
|
||||
assert boundaries["agent_runtime_action_performed"] is False
|
||||
assert boundaries["telegram_send_performed"] is False
|
||||
assert boundaries["workflow_trigger_performed"] is False
|
||||
assert boundaries["raw_log_payload_persisted"] is False
|
||||
assert boundaries["secret_value_collection_allowed"] is False
|
||||
assert boundaries["github_api_used"] is False
|
||||
@@ -1,3 +1,17 @@
|
||||
## 2026-06-30 — 02:28 P1-LOG KM/RAG/PlayBook/MCP consumer readback
|
||||
|
||||
**照主線完成的實作**:
|
||||
- P0-006 仍是 current P0,但目前只剩 fresh all-host reboot window / approved reboot drill 這個事故級邊界;未重啟主機。
|
||||
- 新增 GET `/api/v1/agents/agent-log-controlled-writeback-consumer-readback`,直接讀 live `automation_operation_log` 的 `log_controlled_writeback_dispatched` metadata-only receipts,投影成 KM / RAG / PlayBook / MCP / verifier / AI Agent consumer bindings。
|
||||
- `/api/v1/agents/agent-autonomous-runtime-control` 已納入 `log_controlled_writeback_consumer`、P1-F work item 與 live rollups,可直接讀到 consumer binding、ready target、metadata-only、verifier ref 與 blocker count。
|
||||
- `.gitea/workflows/cd.yaml` controlled-runtime profile 已納入新 service / test,避免 LOG consumer readback 變更落到錯誤 runner lane。
|
||||
|
||||
**驗證**:
|
||||
- Focused pytest:LOG consumer readback / autonomous runtime-control / CD profile `35 passed`。
|
||||
- `ruff`、`py_compile`、`git diff --check`、Gitea runner pressure guard、Gitea step env secret guard:通過。
|
||||
|
||||
**邊界**:未寫 KM / RAG / PlayBook / MCP target,未呼叫 MCP tool,未發 Telegram,未 workflow_dispatch,未操作 host / Docker / K8s / DB / firewall,未讀 secret / token / raw sessions / SQLite / `.env`,未使用 GitHub / `gh` / GitHub API。
|
||||
|
||||
## 2026-06-30 — 02:05 P1-102 Backup/DR config capture stale blocker closeout
|
||||
|
||||
**照優先順序完成的實作**:
|
||||
|
||||
@@ -189,6 +189,18 @@ def test_ai_log_controlled_writeback_dispatch_stays_on_controlled_runtime_profil
|
||||
assert source in text
|
||||
|
||||
|
||||
def test_ai_log_controlled_writeback_consumer_stays_on_controlled_runtime_profile() -> None:
|
||||
text = _workflow_text()
|
||||
expected_sources = [
|
||||
"apps/api/src/services/ai_agent_log_controlled_writeback_consumer_readback.py)",
|
||||
"apps/api/tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py)",
|
||||
"src/services/ai_agent_log_controlled_writeback_consumer_readback.py",
|
||||
"tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py",
|
||||
]
|
||||
for source in expected_sources:
|
||||
assert source in text
|
||||
|
||||
|
||||
def test_awooop_ansible_check_mode_stays_on_controlled_runtime_profile() -> None:
|
||||
text = _workflow_text()
|
||||
expected_sources = [
|
||||
|
||||
Reference in New Issue
Block a user