fix(awooop): make learning receipt backfill idempotent
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 19s
CD Pipeline / build-and-deploy (push) Successful in 5m13s
CD Pipeline / post-deploy-checks (push) Successful in 59s

This commit is contained in:
Your Name
2026-06-29 17:11:29 +08:00
parent c618dda134
commit 959bb5328b
2 changed files with 102 additions and 32 deletions

View File

@@ -528,7 +528,7 @@ def _claim_from_apply_operation_row(row: dict[str, Any]) -> tuple[AnsibleCheckMo
input_payload = _json_loads(row.get("input"))
output_payload = _json_loads(row.get("output"))
dry_run_result = _json_loads(row.get("dry_run_result"))
catalog_id = str(input_payload.get("catalog_id") or "")
catalog_id = str(input_payload.get("catalog_id") or row.get("catalog_id") or "")
if not catalog_id:
return None
catalog_item = get_ansible_catalog_item(catalog_id) or {}
@@ -541,12 +541,14 @@ def _claim_from_apply_operation_row(row: dict[str, Any]) -> tuple[AnsibleCheckMo
apply_playbook_path = str(
input_payload.get("apply_playbook_path")
or input_payload.get("playbook_path")
or row.get("playbook_path")
or catalog_item.get("playbook_path")
or ""
)
check_mode_playbook_path = str(
input_payload.get("check_mode_playbook_path")
or input_payload.get("playbook_path")
or row.get("playbook_path")
or apply_playbook_path
)
if not apply_playbook_path:
@@ -563,7 +565,12 @@ def _claim_from_apply_operation_row(row: dict[str, Any]) -> tuple[AnsibleCheckMo
playbook_path=check_mode_playbook_path,
apply_playbook_path=apply_playbook_path,
inventory_hosts=tuple(str(host) for host in inventory_hosts),
risk_level=str(input_payload.get("risk_level") or catalog_item.get("risk_level") or ""),
risk_level=str(
input_payload.get("risk_level")
or row.get("risk_level")
or catalog_item.get("risk_level")
or ""
),
input_payload=input_payload,
)
result = AnsibleRunResult(
@@ -866,37 +873,54 @@ async def _record_post_apply_verifier_and_learning(
from src.repositories.knowledge_repository import KnowledgeDBRepository
async with get_db_context(project_id) as db:
repo = KnowledgeDBRepository(db)
await repo.create(
KnowledgeEntryCreate(
title=f"AI 自動修復沉澱:{claim.incident_id}",
content=(
"AI Agent 已完成 Ansible controlled apply 並寫入驗證摘要。\n\n"
f"- Incident: {claim.incident_id}\n"
f"- Catalog: {claim.catalog_id}\n"
f"- PlayBook: {claim.apply_playbook_path}\n"
f"- Apply operation: {apply_op_id}\n"
f"- Verification result: {verification_result}\n"
f"- Return code: {result.returncode}\n"
f"- Next step: {post_state['next_required_step']}\n"
),
entry_type=EntryType.INCIDENT_CASE,
category="AI自動化/Ansible受控修復",
tags=[
"ai_auto_repair",
"ansible_controlled_apply",
verification_result,
str(claim.catalog_id or ""),
],
source=EntrySource.AI_EXTRACTED,
status=EntryStatus.REVIEW,
related_incident_id=claim.incident_id,
related_playbook_id=str(claim.catalog_id or "")[:36] or None,
path_type=_post_apply_km_path_type(apply_op_id),
created_by="ai_agent_ansible_worker",
)
path_type = _post_apply_km_path_type(apply_op_id)
existing = await db.execute(
text("""
SELECT id
FROM knowledge_entries
WHERE related_incident_id = :incident_id
AND path_type = :path_type
LIMIT 1
"""),
{
"incident_id": claim.incident_id,
"path_type": path_type,
},
)
status["learning"] = True
if existing.scalar() is not None:
status["learning"] = True
else:
repo = KnowledgeDBRepository(db)
await repo.create(
KnowledgeEntryCreate(
title=f"AI 自動修復沉澱:{claim.incident_id}",
content=(
"AI Agent 已完成 Ansible controlled apply 並寫入驗證摘要。\n\n"
f"- Incident: {claim.incident_id}\n"
f"- Catalog: {claim.catalog_id}\n"
f"- PlayBook: {claim.apply_playbook_path}\n"
f"- Apply operation: {apply_op_id}\n"
f"- Verification result: {verification_result}\n"
f"- Return code: {result.returncode}\n"
f"- Next step: {post_state['next_required_step']}\n"
),
entry_type=EntryType.INCIDENT_CASE,
category="AI自動化/Ansible受控修復",
tags=[
"ai_auto_repair",
"ansible_controlled_apply",
verification_result,
str(claim.catalog_id or ""),
],
source=EntrySource.AI_EXTRACTED,
status=EntryStatus.REVIEW,
related_incident_id=claim.incident_id,
related_playbook_id=str(claim.catalog_id or "")[:36] or None,
path_type=path_type,
created_by="ai_agent_ansible_worker",
)
)
status["learning"] = True
except Exception as exc:
logger.warning(
"ansible_post_apply_learning_writeback_failed",
@@ -983,6 +1007,9 @@ async def backfill_missing_auto_repair_execution_receipts_once(
apply.error,
apply.duration_ms,
apply.status,
apply.catalog_id,
apply.playbook_path,
apply.risk_level,
apply.created_at
FROM automation_operation_log apply
WHERE apply.operation_type = 'ansible_apply_executed'