fix(agent): auto-authorize noncritical alert control
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m38s
CD Pipeline / build-and-deploy (push) Successful in 6m58s
CD Pipeline / post-deploy-checks (push) Successful in 1m42s

This commit is contained in:
ogt
2026-07-11 11:46:38 +08:00
parent 044c8c732a
commit d3fa6feef4
10 changed files with 542 additions and 97 deletions

View File

@@ -159,7 +159,7 @@ def approval_request_to_record_data(
record_data = {
"action": request.action,
"description": request.description,
"status": ApprovalStatus.APPROVED if risk_level == RiskLevel.LOW else ApprovalStatus.PENDING,
"status": ApprovalStatus.APPROVED if required_sigs == 0 else ApprovalStatus.PENDING,
"risk_level": risk_level,
"required_signatures": required_sigs,
"current_signatures": 0,
@@ -169,7 +169,7 @@ def approval_request_to_record_data(
"requested_by": request.requested_by,
"expires_at": request.expires_at,
"extra_metadata": metadata or None,
"resolved_at": now if risk_level == RiskLevel.LOW else None,
"resolved_at": now if required_sigs == 0 else None,
# 戰略 B: 告警風暴收斂
"fingerprint": fingerprint,
"hit_count": 1,
@@ -279,7 +279,7 @@ def _approval_decision_mode(record: ApprovalRecord) -> str:
risk_level = _record_value(record.risk_level)
if _approval_needs_human(record) or current > 0:
return "manual"
if risk_level == "low" and required == 0:
if risk_level in {"low", "medium", "high"} and required == 0:
return "auto"
return "manual"
@@ -442,6 +442,66 @@ class ApprovalDBService:
return approval_record_to_request(record)
async def apply_current_owner_policy(
self,
approval_id: UUID,
) -> tuple[ApprovalRequest | None, bool]:
"""Promote a legacy pending non-critical gate to controlled automation.
Existing alert fingerprints can otherwise keep converging onto an old
PENDING row for 24 hours. This bounded reconciliation updates only one
row, re-runs the current critical classifier, and leaves critical rows
untouched for break-glass handling.
"""
async with get_db_context() as db:
result = await db.execute(
select(ApprovalRecord)
.where(ApprovalRecord.id == str(approval_id))
.with_for_update()
)
record = result.scalar_one_or_none()
if record is None:
return None, False
current = approval_record_to_request(record)
status = _record_value(record.status)
effective_risk = classify_risk(
action=str(record.action or ""),
blast_radius=current.blast_radius,
explicit_level=current.risk_level,
)
if status != "pending" or effective_risk == RiskLevel.CRITICAL:
return current, False
metadata = dict(record.extra_metadata or {})
metadata.update(
{
"owner_policy": "global_product_governance_v2",
"owner_review_gate": "auto_waived_for_low_medium_high",
"controlled_apply_required": True,
"legacy_pending_promoted": True,
}
)
record.status = ApprovalStatus.APPROVED
record.risk_level = effective_risk
record.required_signatures = 0
record.current_signatures = 0
record.signatures = []
record.resolved_at = datetime.now(UTC)
record.extra_metadata = metadata
add_approval_created_timeline_event(db, record)
await db.flush()
await db.refresh(record)
logger.info(
"approval_promoted_by_current_owner_policy",
id=record.id,
risk_level=effective_risk.value,
incident_id=record.incident_id,
)
return approval_record_to_request(record), True
async def find_by_fingerprint(
self,
fingerprint: str,