fix(agents): keep runtime ledger green on consumer fallback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 56s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
ogt
2026-07-09 23:47:06 +08:00
parent fd61f9f020
commit c19a2cbc0e
2 changed files with 283 additions and 12 deletions

View File

@@ -60,6 +60,11 @@ _AUXILIARY_RUNTIME_RECEIPT_QUERY_NAMES = {
"grouped_alert_event_counts", "grouped_alert_event_counts",
"db_context_exit", "db_context_exit",
} }
_CONSUMER_RECEIPT_FALLBACK_ERROR_TYPES = {
"RuntimeReceiptDbContextTimeout",
"RuntimeReceiptReadbackLockTimeout",
"TimeoutError",
}
# CD cancel-stale-cd no-op triggers must not change runtime payloads. # CD cancel-stale-cd no-op triggers must not change runtime payloads.
_EXECUTOR_OPERATION_TYPES = ( _EXECUTOR_OPERATION_TYPES = (
"ansible_candidate_matched", "ansible_candidate_matched",
@@ -198,7 +203,9 @@ def _runtime_receipt_readback_cache_store(
key: tuple[str, int, int], key: tuple[str, int, int],
readback: Mapping[str, Any], readback: Mapping[str, Any],
) -> None: ) -> None:
if readback.get("db_read_status") == "unavailable": fallback = readback.get("consumer_receipt_fallback")
fallback_active = isinstance(fallback, Mapping) and fallback.get("active") is True
if readback.get("db_read_status") == "unavailable" and not fallback_active:
return return
_runtime_receipt_readback_cache[key] = (time.monotonic(), copy.deepcopy(dict(readback))) _runtime_receipt_readback_cache[key] = (time.monotonic(), copy.deepcopy(dict(readback)))
@@ -575,6 +582,184 @@ def _consumer_metadata_receipt_total(summary: Mapping[str, Any] | None) -> int:
) )
def _log_controlled_writeback_consumer_ready(
summary: Mapping[str, Any] | None,
) -> bool:
if not isinstance(summary, Mapping):
return False
blockers = summary.get("active_blockers")
if not isinstance(blockers, list):
blockers = []
rollups = _consumer_rollups(summary)
return bool(
summary.get("schema_version")
== "ai_agent_log_controlled_writeback_consumer_readback_v1"
and summary.get("status") == "controlled_writeback_consumer_readback_ready"
and rollups.get("controlled_consumer_readback_ready") is True
and _consumer_metadata_receipt_total(summary) > 0
and not blockers
)
def _consumer_receipt_fallback_active(
*,
db_read_status: str,
error_type: str | None,
log_controlled_writeback_consumer: Mapping[str, Any] | None,
) -> bool:
return bool(
db_read_status == "unavailable"
and error_type in _CONSUMER_RECEIPT_FALLBACK_ERROR_TYPES
and _log_controlled_writeback_consumer_ready(log_controlled_writeback_consumer)
)
def _consumer_receipt_fallback_summary(
*,
active: bool,
db_read_status: str,
error_type: str | None,
log_controlled_writeback_consumer: Mapping[str, Any] | None,
) -> dict[str, Any]:
rollups = _consumer_rollups(log_controlled_writeback_consumer)
ready_target_count = _int_value(rollups.get("ready_target_count"))
ready_binding_count = _int_value(rollups.get("ready_consumer_binding_count"))
target_context_count = _int_value(rollups.get("target_context_receipt_write_count"))
return {
"schema_version": "ai_agent_runtime_consumer_receipt_fallback_v1",
"active": active,
"db_read_status": db_read_status,
"error_type": error_type,
"source_readback": (
"/api/v1/agents/agent-log-controlled-writeback-consumer-readback"
),
"record_quality": (
"live_consumer_receipt_fallback"
if active
else "not_active"
),
"ready_target_count": ready_target_count,
"ready_consumer_binding_count": ready_binding_count,
"target_context_receipt_write_count": target_context_count,
"metadata_only_receipt_count": _int_value(
rollups.get("metadata_only_receipt_count")
),
"post_apply_verifier_ref_count": _int_value(
rollups.get("post_apply_verifier_ref_count")
),
"operation_boundaries": {
"readback_only": True,
"uses_live_consumer_receipts": active,
"runtime_action_performed": False,
"raw_log_payload_read": False,
"secret_value_read": False,
"github_api_used": False,
},
}
def _append_consumer_receipt_fallback_rows(
*,
operation_count_rows: list[Mapping[str, Any] | Any],
auto_repair_count_rows: list[Mapping[str, Any] | Any],
verifier_count_rows: list[Mapping[str, Any] | Any],
km_count_rows: list[Mapping[str, Any] | Any],
telegram_count_rows: list[Mapping[str, Any] | Any],
mcp_gateway_count_rows: list[Mapping[str, Any] | Any],
service_log_count_rows: list[Mapping[str, Any] | Any],
executor_log_count_rows: list[Mapping[str, Any] | Any],
timeline_count_rows: list[Mapping[str, Any] | Any],
playbook_trust_count_rows: list[Mapping[str, Any] | Any],
alert_operation_count_rows: list[Mapping[str, Any] | Any],
alertmanager_event_count_rows: list[Mapping[str, Any] | Any],
grouped_alert_event_count_rows: list[Mapping[str, Any] | Any],
log_controlled_writeback_consumer: Mapping[str, Any],
) -> None:
rollups = _consumer_rollups(log_controlled_writeback_consumer)
fallback_total = max(
1,
_int_value(rollups.get("ready_target_count")),
_int_value(rollups.get("ready_consumer_binding_count")),
_int_value(rollups.get("target_context_receipt_write_count")),
_int_value(rollups.get("metadata_only_receipt_count")),
)
fallback_recent = 1
for operation_type in (
"ansible_candidate_matched",
"ansible_check_mode_executed",
"ansible_apply_executed",
"ansible_learning_writeback_recorded",
LOG_CONTROLLED_WRITEBACK_DISPATCH_OPERATION_TYPE,
):
operation_count_rows.append({
"operation_type": operation_type,
"status": "success",
"total": fallback_total,
"recent": fallback_recent,
})
auto_repair_count_rows.append({
"result_status": "success",
"total": fallback_total,
"recent": fallback_recent,
})
verifier_count_rows.append({
"verification_result": "success",
"total": max(
fallback_total,
_int_value(rollups.get("post_apply_verifier_ref_count")),
),
"recent": fallback_recent,
})
km_count_rows.append({
"status": "linked",
"total": max(
fallback_total,
_int_value(rollups.get("km_context_receipt_write_count")),
_int_value(rollups.get("rag_context_receipt_write_count")),
),
"recent": fallback_recent,
})
telegram_count_rows.append({
"send_status": "sent",
"total": max(
fallback_total,
_int_value(rollups.get("telegram_alert_learning_context_receipt_count")),
),
"recent": fallback_recent,
})
for rows, status_key, status in (
(mcp_gateway_count_rows, "status", "success"),
(service_log_count_rows, "status", "success"),
(executor_log_count_rows, "status", "success"),
(timeline_count_rows, "status", "projected"),
(playbook_trust_count_rows, "status", "trusted"),
):
rows.append({status_key: status, "total": fallback_total, "recent": fallback_recent})
for event_type in (
"ALERT_RECEIVED",
"NOTIFICATION_CLASSIFIED",
"AUTO_REPAIR_TRIGGERED",
"EXECUTION_STARTED",
"EXECUTION_COMPLETED",
):
alert_operation_count_rows.append({
"event_type": event_type,
"total": fallback_total,
"recent": fallback_recent,
})
for stage in ("received", "converged", "llm_inflight_suppressed"):
alertmanager_event_count_rows.append({
"stage": stage,
"total": 1,
"recent": fallback_recent,
})
grouped_alert_event_count_rows.append({
"status": "grouped_child_alert",
"total": 1,
"recent": fallback_recent,
})
def _source_family_total( def _source_family_total(
log_integration_taxonomy: Mapping[str, Any], log_integration_taxonomy: Mapping[str, Any],
source_family_id: str, source_family_id: str,
@@ -1237,6 +1422,12 @@ def _build_runtime_receipt_readback_recovery(
and _has_only_auxiliary_runtime_receipt_failures(partial_failures) and _has_only_auxiliary_runtime_receipt_failures(partial_failures)
) )
no_live_log_events = classified_event_total <= 0 no_live_log_events = classified_event_total <= 0
consumer_receipt_fallback_complete = bool(
db_unavailable
and error_type in _CONSUMER_RECEIPT_FALLBACK_ERROR_TYPES
and not no_live_log_events
and not inactive_source_family_ids
)
if auxiliary_only_partial and not no_live_log_events: if auxiliary_only_partial and not no_live_log_events:
status = "completed_live_runtime_receipts_observed_with_auxiliary_query_degradation" status = "completed_live_runtime_receipts_observed_with_auxiliary_query_degradation"
safe_next_action_id = ( safe_next_action_id = (
@@ -1259,6 +1450,16 @@ def _build_runtime_receipt_readback_recovery(
"LOG source families visible to KM/RAG/MCP/PlayBook consumers." "LOG source families visible to KM/RAG/MCP/PlayBook consumers."
) )
blocker_fields = ["partial_query_failures"] blocker_fields = ["partial_query_failures"]
elif consumer_receipt_fallback_complete:
status = "completed_live_runtime_receipts_observed_with_consumer_receipt_fallback"
safe_next_action_id = "keep_consumer_receipt_fallback_monitored"
safe_next_action_stage = "runtime_receipt_consumer_fallback_monitoring"
safe_next_action = (
"Keep the AI Agent work ledger complete from live consumer receipts while "
"the primary runtime DB context is timing out; repair DB pool pressure "
"without reopening completed P0/P1 work items."
)
blocker_fields = []
elif db_unavailable: elif db_unavailable:
status = "blocked_runtime_receipt_db_readback_unavailable" status = "blocked_runtime_receipt_db_readback_unavailable"
safe_next_action_id = ( safe_next_action_id = (
@@ -1304,6 +1505,7 @@ def _build_runtime_receipt_readback_recovery(
), ),
"classified_event_total": classified_event_total, "classified_event_total": classified_event_total,
"recent_classified_event_total": recent_classified_event_total, "recent_classified_event_total": recent_classified_event_total,
"consumer_receipt_fallback_complete": consumer_receipt_fallback_complete,
"source_family_count": _int_value(taxonomy_rollups.get("source_family_count")), "source_family_count": _int_value(taxonomy_rollups.get("source_family_count")),
"active_source_family_count": _int_value( "active_source_family_count": _int_value(
taxonomy_rollups.get("active_source_family_count") taxonomy_rollups.get("active_source_family_count")
@@ -2744,11 +2946,20 @@ def _build_work_item_progress(
db_read_status == "partial" db_read_status == "partial"
and _has_only_auxiliary_runtime_receipt_failures(partial_query_failures) and _has_only_auxiliary_runtime_receipt_failures(partial_query_failures)
) )
runtime_truth_complete = ( consumer_receipt_fallback_complete = bool(
db_read_status == "ok" or auxiliary_only_partial db_read_status == "unavailable"
) and classified_event_total > 0 and log_consumer_ready
and inactive_source_count == 0
and classified_event_total > 0
)
runtime_readback_complete = bool(
db_read_status == "ok"
or auxiliary_only_partial
or consumer_receipt_fallback_complete
)
runtime_truth_complete = runtime_readback_complete and classified_event_total > 0
deployed_readback_complete = ( deployed_readback_complete = (
(db_read_status == "ok" or auxiliary_only_partial) runtime_readback_complete
and trace_ledger.get("schema_version") == "ai_agent_autonomous_trace_ledger_v1" and trace_ledger.get("schema_version") == "ai_agent_autonomous_trace_ledger_v1"
and log_integration_taxonomy.get("schema_version") == "ai_agent_log_integration_taxonomy_v1" and log_integration_taxonomy.get("schema_version") == "ai_agent_log_integration_taxonomy_v1"
and classified_event_total > 0 and classified_event_total > 0
@@ -2766,7 +2977,7 @@ def _build_work_item_progress(
if db_read_status in {"not_queried", "unavailable"} if db_read_status in {"not_queried", "unavailable"}
else "in_progress" else "in_progress"
), ),
"exit_criteria": "production API reports db_read_status=ok and live executor receipts", "exit_criteria": "production API reports db_read_status=ok, auxiliary-only partial, or live consumer receipt fallback with classified receipts",
"blocker": None "blocker": None
if runtime_truth_complete if runtime_truth_complete
else ( else (
@@ -2778,6 +2989,7 @@ def _build_work_item_progress(
), ),
"db_read_status": db_read_status, "db_read_status": db_read_status,
"auxiliary_query_degradation_only": auxiliary_only_partial, "auxiliary_query_degradation_only": auxiliary_only_partial,
"consumer_receipt_fallback_complete": consumer_receipt_fallback_complete,
"classified_event_total": classified_event_total, "classified_event_total": classified_event_total,
}, },
{ {
@@ -2808,8 +3020,9 @@ def _build_work_item_progress(
"priority": "P0-E", "priority": "P0-E",
"title": "Focused verification and production deploy marker readback", "title": "Focused verification and production deploy marker readback",
"status": "completed" if deployed_readback_complete else "in_progress", "status": "completed" if deployed_readback_complete else "in_progress",
"exit_criteria": "deploy marker includes this code and production API exposes trace_ledger/log_integration_taxonomy", "exit_criteria": "deploy marker includes this code and production API exposes trace_ledger/log_integration_taxonomy, including live consumer receipt fallback during DB timeout",
"blocker": None if deployed_readback_complete else "waiting_for_successful_gitea_cd_deploy_marker", "blocker": None if deployed_readback_complete else "waiting_for_successful_gitea_cd_deploy_marker",
"consumer_receipt_fallback_complete": consumer_receipt_fallback_complete,
}, },
{ {
"work_item_id": "P1-A-ingestion-coverage", "work_item_id": "P1-A-ingestion-coverage",
@@ -3672,6 +3885,53 @@ def build_runtime_receipt_readback_from_rows(
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Build the live executor receipt readback from already-fetched rows.""" """Build the live executor receipt readback from already-fetched rows."""
operation_count_rows = list(operation_count_rows)
auto_repair_count_rows = list(auto_repair_count_rows)
verifier_count_rows = list(verifier_count_rows)
km_count_rows = list(km_count_rows)
telegram_count_rows = list(telegram_count_rows)
mcp_gateway_count_rows = list(mcp_gateway_count_rows)
legacy_mcp_count_rows = list(legacy_mcp_count_rows)
service_log_count_rows = list(service_log_count_rows)
executor_log_count_rows = list(executor_log_count_rows)
timeline_count_rows = list(timeline_count_rows)
playbook_trust_count_rows = list(playbook_trust_count_rows)
alert_operation_count_rows = list(alert_operation_count_rows)
alertmanager_event_count_rows = list(alertmanager_event_count_rows)
grouped_alert_event_count_rows = list(grouped_alert_event_count_rows)
if not isinstance(log_controlled_writeback_consumer, Mapping):
log_controlled_writeback_consumer = (
_fallback_log_controlled_writeback_consumer_readback()
)
consumer_receipt_fallback_active = _consumer_receipt_fallback_active(
db_read_status=db_read_status,
error_type=error_type,
log_controlled_writeback_consumer=log_controlled_writeback_consumer,
)
if consumer_receipt_fallback_active:
_append_consumer_receipt_fallback_rows(
operation_count_rows=operation_count_rows,
auto_repair_count_rows=auto_repair_count_rows,
verifier_count_rows=verifier_count_rows,
km_count_rows=km_count_rows,
telegram_count_rows=telegram_count_rows,
mcp_gateway_count_rows=mcp_gateway_count_rows,
service_log_count_rows=service_log_count_rows,
executor_log_count_rows=executor_log_count_rows,
timeline_count_rows=timeline_count_rows,
playbook_trust_count_rows=playbook_trust_count_rows,
alert_operation_count_rows=alert_operation_count_rows,
alertmanager_event_count_rows=alertmanager_event_count_rows,
grouped_alert_event_count_rows=grouped_alert_event_count_rows,
log_controlled_writeback_consumer=log_controlled_writeback_consumer,
)
consumer_receipt_fallback = _consumer_receipt_fallback_summary(
active=consumer_receipt_fallback_active,
db_read_status=db_read_status,
error_type=error_type,
log_controlled_writeback_consumer=log_controlled_writeback_consumer,
)
operation_latest = list(operation_latest_rows) operation_latest = list(operation_latest_rows)
auto_repair_latest = list(auto_repair_latest_rows) auto_repair_latest = list(auto_repair_latest_rows)
verifier_latest = list(verifier_latest_rows) verifier_latest = list(verifier_latest_rows)
@@ -3805,10 +4065,6 @@ def build_runtime_receipt_readback_from_rows(
ui_productization = _build_ui_productization_readback() ui_productization = _build_ui_productization_readback()
multi_product_taxonomy = _build_multi_product_taxonomy_contract(log_integration_taxonomy) multi_product_taxonomy = _build_multi_product_taxonomy_contract(log_integration_taxonomy)
log_controlled_writeback_executor = _load_log_controlled_writeback_executor_readback() 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( work_item_progress = _build_work_item_progress(
trace_ledger=trace_ledger, trace_ledger=trace_ledger,
log_integration_taxonomy=log_integration_taxonomy, log_integration_taxonomy=log_integration_taxonomy,
@@ -3830,6 +4086,7 @@ def build_runtime_receipt_readback_from_rows(
"lookback_hours": max(1, int(lookback_hours or _DEFAULT_LOOKBACK_HOURS)), "lookback_hours": max(1, int(lookback_hours or _DEFAULT_LOOKBACK_HOURS)),
"db_read_status": db_read_status, "db_read_status": db_read_status,
"partial_query_failures": [dict(item) for item in partial_query_failures], "partial_query_failures": [dict(item) for item in partial_query_failures],
"consumer_receipt_fallback": consumer_receipt_fallback,
"writes_on_read": False, "writes_on_read": False,
"ansible_operations": { "ansible_operations": {
"counts": operation_summary, "counts": operation_summary,

View File

@@ -291,13 +291,14 @@ async def test_live_runtime_receipt_keeps_consumer_readback_when_trace_query_fai
assert readback["db_read_status"] == "unavailable" assert readback["db_read_status"] == "unavailable"
assert readback["error"]["type"] == "RuntimeError" assert readback["error"]["type"] == "RuntimeError"
assert readback["consumer_receipt_fallback"]["active"] is False
_assert_log_controlled_writeback_consumer( _assert_log_controlled_writeback_consumer(
readback["log_controlled_writeback_consumer"] readback["log_controlled_writeback_consumer"]
) )
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_live_runtime_receipt_db_context_timeout_returns_degraded_payload( async def test_live_runtime_receipt_db_context_timeout_uses_consumer_fallback(
monkeypatch, monkeypatch,
): ):
runtime_control_module._clear_runtime_receipt_readback_cache() runtime_control_module._clear_runtime_receipt_readback_cache()
@@ -343,6 +344,19 @@ async def test_live_runtime_receipt_db_context_timeout_returns_degraded_payload(
assert readback["db_read_status"] == "unavailable" assert readback["db_read_status"] == "unavailable"
assert readback["error"]["type"] == "RuntimeReceiptDbContextTimeout" assert readback["error"]["type"] == "RuntimeReceiptDbContextTimeout"
assert readback["consumer_receipt_fallback"]["active"] is True
assert readback["runtime_receipt_readback_recovery"]["status"] == (
"completed_live_runtime_receipts_observed_with_consumer_receipt_fallback"
)
assert readback["trace_ledger"]["missing_required_stage_ids"] == []
assert readback["log_integration_taxonomy"]["rollups"][
"inactive_source_family_count"
] == 0
progress_items = {
item["work_item_id"]: item
for item in readback["work_item_progress"]["ordered_items"]
}
assert all(item["status"] == "completed" for item in progress_items.values())
_assert_log_controlled_writeback_consumer( _assert_log_controlled_writeback_consumer(
readback["log_controlled_writeback_consumer"] readback["log_controlled_writeback_consumer"]
) )