fix(sre): reconcile typed backup playbooks
This commit is contained in:
@@ -356,6 +356,52 @@ def _learning_profile(
|
||||
}
|
||||
|
||||
|
||||
def _reconcile_existing_playbook_profile(
|
||||
playbook: PlaybookRecord,
|
||||
*,
|
||||
profile: dict[str, Any],
|
||||
mode: str,
|
||||
) -> bool:
|
||||
"""Idempotently remove legacy cold-start semantics from typed routes."""
|
||||
|
||||
if profile["affected_services"] != ["backup_restore"]:
|
||||
return False
|
||||
desired = {
|
||||
"description": profile["description"],
|
||||
"symptom_pattern": {
|
||||
"alert_names": [],
|
||||
"affected_services": profile["affected_services"],
|
||||
"severity_range": ["P0", "P1", "P2"],
|
||||
"label_patterns": {},
|
||||
"keywords": profile["keywords"],
|
||||
},
|
||||
"repair_steps": [{
|
||||
"step_number": 1,
|
||||
"action_type": profile["action_type"],
|
||||
"command": mode,
|
||||
"expected_result": profile["expected_result"],
|
||||
"rollback_command": profile["rollback_command"],
|
||||
"requires_approval": False,
|
||||
"risk_level": "LOW",
|
||||
}],
|
||||
"notes": profile["notes"],
|
||||
"stateful_targets": [],
|
||||
"requires_pre_backup": False,
|
||||
}
|
||||
changed = False
|
||||
for field, value in desired.items():
|
||||
if getattr(playbook, field, None) != value:
|
||||
setattr(playbook, field, value)
|
||||
changed = True
|
||||
tags = list(playbook.tags or [])
|
||||
for tag in profile["tags"]:
|
||||
if tag not in tags:
|
||||
tags.append(tag)
|
||||
changed = True
|
||||
playbook.tags = tags
|
||||
return changed
|
||||
|
||||
|
||||
async def _ensure_playbook_trust(
|
||||
identity: Agent99DispatchIdentity,
|
||||
*,
|
||||
@@ -423,13 +469,21 @@ async def _ensure_playbook_trust(
|
||||
else:
|
||||
tags = list(playbook.tags or [])
|
||||
incidents = list(playbook.source_incident_ids or [])
|
||||
if run_tag not in tags:
|
||||
profile_changed = _reconcile_existing_playbook_profile(
|
||||
playbook,
|
||||
profile=profile,
|
||||
mode=mode,
|
||||
)
|
||||
tags = list(playbook.tags or [])
|
||||
new_run = run_tag not in tags
|
||||
if new_run:
|
||||
tags.append(run_tag)
|
||||
playbook.success_count = int(playbook.success_count or 0) + 1
|
||||
playbook.trust_score = min(
|
||||
1.0,
|
||||
0.9 * float(playbook.trust_score or 0.3) + 0.1,
|
||||
)
|
||||
if new_run or profile_changed:
|
||||
playbook.version = int(playbook.version or 0) + 1
|
||||
if identity.incident_id not in incidents:
|
||||
incidents.append(identity.incident_id)
|
||||
|
||||
@@ -71,6 +71,47 @@ def test_backup_learning_profile_is_typed_and_no_write() -> None:
|
||||
assert "verified recovery" not in profile["km_title"]
|
||||
|
||||
|
||||
def test_existing_backup_playbook_profile_is_reconciled_idempotently() -> None:
|
||||
identity = build_agent99_dispatch_identity(
|
||||
project_id="awoooi",
|
||||
incident_id="INC-20260719-BACKUP-PLAYBOOK",
|
||||
source_fingerprint="backup-playbook-fingerprint",
|
||||
route_id="agent99:backup_health:BackupCheck",
|
||||
)
|
||||
profile = job._learning_profile(identity, mode="BackupCheck")
|
||||
playbook = SimpleNamespace(
|
||||
description="legacy recovery",
|
||||
symptom_pattern={"affected_services": ["cold-start-gate"]},
|
||||
repair_steps=[{"action_type": "agent99_mode", "command": "Recover"}],
|
||||
notes="legacy",
|
||||
stateful_targets=["cold-start-gate"],
|
||||
requires_pre_backup=True,
|
||||
tags=["agent99", "controlled_dispatch"],
|
||||
)
|
||||
|
||||
changed = job._reconcile_existing_playbook_profile(
|
||||
playbook,
|
||||
profile=profile,
|
||||
mode="BackupCheck",
|
||||
)
|
||||
replay_changed = job._reconcile_existing_playbook_profile(
|
||||
playbook,
|
||||
profile=profile,
|
||||
mode="BackupCheck",
|
||||
)
|
||||
|
||||
assert changed is True
|
||||
assert replay_changed is False
|
||||
assert playbook.symptom_pattern["affected_services"] == ["backup_restore"]
|
||||
assert playbook.repair_steps[0]["action_type"] == "agent99_readonly_check"
|
||||
assert playbook.repair_steps[0]["command"] == "BackupCheck"
|
||||
assert playbook.repair_steps[0]["rollback_command"] == (
|
||||
"not_applicable_no_write"
|
||||
)
|
||||
assert "backup_restore" in playbook.tags
|
||||
assert "cold-start" not in str(playbook.__dict__)
|
||||
|
||||
|
||||
class _Context:
|
||||
def __init__(self, db) -> None: # type: ignore[no-untyped-def]
|
||||
self.db = db
|
||||
|
||||
Reference in New Issue
Block a user