fix(sre): type backup learning as read-only

This commit is contained in:
Your Name
2026-07-19 01:10:47 +08:00
parent 2285df21c9
commit 0f30d1350f
2 changed files with 88 additions and 20 deletions

View File

@@ -300,6 +300,60 @@ def _playbook_id(identity: Agent99DispatchIdentity) -> str:
return f"PB-A99-{digest}"
def _learning_profile(
identity: Agent99DispatchIdentity,
*,
mode: str,
) -> dict[str, Any]:
if "backup_health" in identity.route_id:
return {
"description": (
"Verified Agent99 read-only BackupCheck with independent DR "
"evidence readback; no backup or restore write was performed."
),
"affected_services": ["backup_restore"],
"keywords": [
"agent99", "backup", "restore", "escrow", "offsite", mode,
],
"action_type": "agent99_readonly_check",
"expected_result": "independent backup/DR verifier passes",
"rollback_command": "not_applicable_no_write",
"tags": ["agent99", "controlled_dispatch", "backup_restore"],
"notes": (
"Backup/restore/delete/retention/escrow writes and raw secret "
"values are not permitted or persisted."
),
"km_title": f"Agent99 verified backup readback {identity.incident_id}",
"km_content": (
f"Run {identity.run_id} on route {identity.route_id} completed "
f"read-only Agent99 {mode or 'BackupCheck'} with independent "
"backup/DR verifier success. No backup, restore, delete, "
"retention or escrow write was performed; LLM had no runtime authority."
),
"km_category": "資料保護/DR Scorecard",
}
return {
"description": (
"Verified Agent99 controlled route with independent outcome "
"readback and same-run learning receipts."
),
"affected_services": ["cold-start-gate"],
"keywords": ["agent99", "cold-start", mode],
"action_type": "agent99_mode",
"expected_result": "independent verifier passes",
"rollback_command": "Agent99 Status (no-write)",
"tags": ["agent99", "controlled_dispatch"],
"notes": "No raw logs or secret values are persisted.",
"km_title": f"Agent99 verified recovery {identity.incident_id}",
"km_content": (
f"Run {identity.run_id} on route {identity.route_id} completed "
f"Agent99 mode {mode or 'Status'} with independent verifier success. "
"Raw logs and secret values are not stored."
),
"km_category": "AI自動化/Agent99受控修復",
}
async def _ensure_playbook_trust(
identity: Agent99DispatchIdentity,
*,
@@ -307,6 +361,7 @@ async def _ensure_playbook_trust(
) -> str | None:
playbook_id = _playbook_id(identity)
run_tag = f"agent99_run:{identity.run_id}"
profile = _learning_profile(identity, mode=mode)
now = now_taipei()
try:
async with get_db_context(identity.project_id) as db:
@@ -324,25 +379,22 @@ async def _ensure_playbook_trust(
playbook_id=playbook_id,
project_id=identity.project_id,
name=f"Agent99 {mode or 'Status'}: {identity.route_id}",
description=(
"Verified Agent99 controlled route with independent "
"outcome readback and same-run learning receipts."
),
description=profile["description"],
status="approved",
source="ai_extracted",
symptom_pattern={
"alert_names": [],
"affected_services": ["cold-start-gate"],
"affected_services": profile["affected_services"],
"severity_range": ["P0", "P1", "P2"],
"label_patterns": {},
"keywords": ["agent99", "cold-start", mode],
"keywords": profile["keywords"],
},
repair_steps=[{
"step_number": 1,
"action_type": "agent99_mode",
"action_type": profile["action_type"],
"command": mode,
"expected_result": "independent verifier passes",
"rollback_command": "Agent99 Status (no-write)",
"expected_result": profile["expected_result"],
"rollback_command": profile["rollback_command"],
"requires_approval": False,
"risk_level": "LOW",
}],
@@ -356,8 +408,8 @@ async def _ensure_playbook_trust(
trust_score=0.37,
approved_by="agent99_independent_verifier",
approved_at=now,
tags=["agent99", "controlled_dispatch", run_tag],
notes="No raw logs or secret values are persisted.",
tags=[*profile["tags"], run_tag],
notes=profile["notes"],
requires_approval_level="auto",
stateful_targets=[],
requires_pre_backup=False,
@@ -401,20 +453,16 @@ async def _ensure_km_writeback(
mode: str,
) -> str | None:
knowledge = get_knowledge_service()
profile = _learning_profile(identity, mode=mode)
try:
entry = await knowledge.create_entry(
KnowledgeEntryCreate(
title=f"Agent99 verified recovery {identity.incident_id}",
content=(
f"Run {identity.run_id} on route {identity.route_id} "
f"completed Agent99 mode {mode or 'Status'} with independent "
"verifier success. Raw logs and secret values are not stored."
),
title=profile["km_title"],
content=profile["km_content"],
entry_type=EntryType.INCIDENT_CASE,
category="AI自動化/Agent99受控修復",
category=profile["km_category"],
tags=[
"agent99",
"controlled_dispatch",
*profile["tags"],
f"automation_run_id:{identity.run_id}",
],
source=EntrySource.AI_EXTRACTED,

View File

@@ -51,6 +51,26 @@ def test_agent99_playbook_identity_is_project_scoped() -> None:
assert job._playbook_id(IDENTITY) != job._playbook_id(other)
def test_backup_learning_profile_is_typed_and_no_write() -> None:
identity = build_agent99_dispatch_identity(
project_id="awoooi",
incident_id="INC-20260719-BACKUP-LEARNING",
source_fingerprint="backup-learning-fingerprint",
route_id="agent99:backup_health:BackupCheck",
)
profile = job._learning_profile(identity, mode="BackupCheck")
assert profile["affected_services"] == ["backup_restore"]
assert profile["action_type"] == "agent99_readonly_check"
assert profile["rollback_command"] == "not_applicable_no_write"
assert profile["km_category"] == "資料保護/DR Scorecard"
assert "verified backup readback" in profile["km_title"]
assert "LLM had no runtime authority" in profile["km_content"]
assert "cold-start" not in str(profile)
assert "verified recovery" not in profile["km_title"]
class _Context:
def __init__(self, db) -> None: # type: ignore[no-untyped-def]
self.db = db