300 lines
12 KiB
Python
300 lines
12 KiB
Python
|
|
"""
|
|||
|
|
决策授权体系 — L0-L4 五级风险授权,数字分身根据风险等级自动判定执行权限
|
|||
|
|
"""
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
from typing import Any, Dict, List, Optional
|
|||
|
|
|
|||
|
|
from app.services.fingerprint_engine import fingerprint_engine
|
|||
|
|
from app.services.shadow_executor import shadow_executor
|
|||
|
|
from app.services.behavior_collector import behavior_collector
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
AUTH_LEVELS = {
|
|||
|
|
"L0": {
|
|||
|
|
"name": "全自动执行",
|
|||
|
|
"description": "低风险操作,数字分身直接执行",
|
|||
|
|
"risk_range": (0, 0.2),
|
|||
|
|
"requires_approval": False,
|
|||
|
|
"notify": False,
|
|||
|
|
"examples": ["格式化代码", "拼写检查修正", "简单文案调整"],
|
|||
|
|
},
|
|||
|
|
"L1": {
|
|||
|
|
"name": "自动执行+通知",
|
|||
|
|
"description": "低风险,自动执行后通知用户",
|
|||
|
|
"risk_range": (0.2, 0.4),
|
|||
|
|
"requires_approval": False,
|
|||
|
|
"notify": True,
|
|||
|
|
"examples": ["合并小PR", "回复常规邮件", "更新文档"],
|
|||
|
|
},
|
|||
|
|
"L2": {
|
|||
|
|
"name": "建议+确认",
|
|||
|
|
"description": "中风险,数字分身生成建议,需用户一次确认",
|
|||
|
|
"risk_range": (0.4, 0.6),
|
|||
|
|
"requires_approval": True,
|
|||
|
|
"notify": True,
|
|||
|
|
"examples": ["修改API接口", "调整配置参数", "代码重构"],
|
|||
|
|
},
|
|||
|
|
"L3": {
|
|||
|
|
"name": "详细审批",
|
|||
|
|
"description": "高风险,需要详细说明和多重审批",
|
|||
|
|
"risk_range": (0.6, 0.8),
|
|||
|
|
"requires_approval": True,
|
|||
|
|
"notify": True,
|
|||
|
|
"examples": ["修改数据库Schema", "涉及资金的变更", "权限调整"],
|
|||
|
|
},
|
|||
|
|
"L4": {
|
|||
|
|
"name": "禁止自动",
|
|||
|
|
"description": "极高风险,始终需要人工操作",
|
|||
|
|
"risk_range": (0.8, 1.0),
|
|||
|
|
"requires_approval": True,
|
|||
|
|
"notify": True,
|
|||
|
|
"examples": ["删除生产数据", "修改权限体系", "涉及合规操作"],
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RISK_FACTORS = {
|
|||
|
|
"data_mutation": {"weight": 0.25, "keywords": ["DELETE", "DROP", "UPDATE", "INSERT", "ALTER", "TRUNCATE", "删除", "修改", "写入", "变更"]},
|
|||
|
|
"permission_change": {"weight": 0.25, "keywords": ["权限", "role", "permission", "auth", "授权", "访问控制"]},
|
|||
|
|
"financial": {"weight": 0.30, "keywords": ["支付", "金额", "资金", "退款", "结算", "payment", "refund", "price", "cost", "费用"]},
|
|||
|
|
"production_impact": {"weight": 0.20, "keywords": ["生产", "production", "线上", "prod", "正式环境", "发布", "deploy"]},
|
|||
|
|
"user_data": {"weight": 0.15, "keywords": ["用户数据", "user.*data", "PII", "手机号", "身份证", "隐私"]},
|
|||
|
|
"irreversible": {"weight": 0.20, "keywords": ["不可逆", "irreversible", "清空", "重置", "reset", "hard delete"]},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class DecisionAuthorizer:
|
|||
|
|
"""决策授权引擎 — L0-L4 风险定级"""
|
|||
|
|
|
|||
|
|
def evaluate(self, user_id: str, action: str, target: str = "",
|
|||
|
|
context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|||
|
|
"""评估操作风险等级并返回授权决策。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
user_id: 用户ID
|
|||
|
|
action: 操作描述(如 "修改数据库schema", "回复邮件")
|
|||
|
|
target: 操作目标(如具体表名、文件路径)
|
|||
|
|
context: 附加上下文信息
|
|||
|
|
"""
|
|||
|
|
context = context or {}
|
|||
|
|
|
|||
|
|
# 1. 计算风险分数
|
|||
|
|
risk_score, risk_factors = self._calculate_risk(action, target, context)
|
|||
|
|
|
|||
|
|
# 2. 确定授权等级
|
|||
|
|
auth_level = self._determine_level(risk_score)
|
|||
|
|
|
|||
|
|
# 3. 用户信任度调整
|
|||
|
|
fp = fingerprint_engine.get_fingerprint(user_id)
|
|||
|
|
trust_bonus = self._compute_trust_bonus(user_id, fp)
|
|||
|
|
adjusted_score = max(0, risk_score - trust_bonus)
|
|||
|
|
adjusted_level = self._determine_level(adjusted_score)
|
|||
|
|
|
|||
|
|
# 4. 影子模式对比(如果是新类别操作)
|
|||
|
|
shadow_comparison = None
|
|||
|
|
category = context.get("category", self._infer_category(action))
|
|||
|
|
if risk_score > 0.3:
|
|||
|
|
shadow_suggestion = shadow_executor.generate_suggestion(user_id, category, {
|
|||
|
|
"action": action, "target": target, "risk_score": risk_score,
|
|||
|
|
})
|
|||
|
|
shadow_comparison = {
|
|||
|
|
"suggestion_generated": True,
|
|||
|
|
"shadow_confidence": shadow_suggestion.get("confidence", 0),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
decision = {
|
|||
|
|
"action": action,
|
|||
|
|
"target": target,
|
|||
|
|
"risk_score": round(risk_score, 3),
|
|||
|
|
"adjusted_score": round(adjusted_score, 3),
|
|||
|
|
"raw_level": auth_level,
|
|||
|
|
"adjusted_level": adjusted_level,
|
|||
|
|
"trust_bonus": round(trust_bonus, 3),
|
|||
|
|
"risk_factors": risk_factors,
|
|||
|
|
"requires_approval": AUTH_LEVELS[adjusted_level]["requires_approval"],
|
|||
|
|
"notify_user": AUTH_LEVELS[adjusted_level]["notify"],
|
|||
|
|
"level_detail": AUTH_LEVELS[adjusted_level],
|
|||
|
|
"shadow_comparison": shadow_comparison,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 如果需要审批,生成审批要求
|
|||
|
|
if decision["requires_approval"]:
|
|||
|
|
decision["approval_requirements"] = self._generate_approval_requirements(
|
|||
|
|
adjusted_level, action, risk_factors
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 记录行为
|
|||
|
|
behavior_collector.log_fire_and_forget(
|
|||
|
|
user_id=user_id,
|
|||
|
|
category="decision",
|
|||
|
|
action="authorize_action",
|
|||
|
|
context={"action": action, "target": target},
|
|||
|
|
result={"level": adjusted_level, "risk_score": adjusted_score},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return decision
|
|||
|
|
|
|||
|
|
def _calculate_risk(self, action: str, target: str,
|
|||
|
|
context: Dict[str, Any]) -> tuple:
|
|||
|
|
"""计算操作风险分数和风险因子。"""
|
|||
|
|
text = f"{action} {target}"
|
|||
|
|
total_score = 0
|
|||
|
|
max_score = 0
|
|||
|
|
matched_factors = []
|
|||
|
|
|
|||
|
|
for factor_name, factor in RISK_FACTORS.items():
|
|||
|
|
max_score += factor["weight"]
|
|||
|
|
for keyword in factor["keywords"]:
|
|||
|
|
if keyword.lower() in text.lower():
|
|||
|
|
severity = self._factor_severity(factor_name, text)
|
|||
|
|
contribution = factor["weight"] * severity
|
|||
|
|
total_score += contribution
|
|||
|
|
matched_factors.append({
|
|||
|
|
"factor": factor_name,
|
|||
|
|
"contribution": round(contribution, 3),
|
|||
|
|
"severity": round(severity, 2),
|
|||
|
|
})
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
# 归一化
|
|||
|
|
if max_score > 0:
|
|||
|
|
risk_score = min(1.0, total_score / max_score)
|
|||
|
|
else:
|
|||
|
|
risk_score = 0.1
|
|||
|
|
|
|||
|
|
# 上下文调整
|
|||
|
|
if context.get("is_dry_run"):
|
|||
|
|
risk_score *= 0.3
|
|||
|
|
if context.get("has_rollback_plan"):
|
|||
|
|
risk_score *= 0.7
|
|||
|
|
if context.get("is_peak_hours"):
|
|||
|
|
risk_score *= 1.2
|
|||
|
|
|
|||
|
|
return min(1.0, risk_score), matched_factors
|
|||
|
|
|
|||
|
|
def _factor_severity(self, factor_name: str, text: str) -> float:
|
|||
|
|
"""评估单个风险因子的严重程度。"""
|
|||
|
|
if factor_name == "financial":
|
|||
|
|
if any(kw in text for kw in ["退款", "refund", "实际扣款"]):
|
|||
|
|
return 1.0
|
|||
|
|
return 0.7
|
|||
|
|
if factor_name == "irreversible":
|
|||
|
|
if "删除" in text or "DELETE" in text.upper():
|
|||
|
|
return 1.0
|
|||
|
|
return 0.6
|
|||
|
|
if factor_name == "production_impact":
|
|||
|
|
if "生产" in text or "production" in text.lower():
|
|||
|
|
return 0.9
|
|||
|
|
return 0.5
|
|||
|
|
return 0.5
|
|||
|
|
|
|||
|
|
def _determine_level(self, score: float) -> str:
|
|||
|
|
"""根据分数确定授权等级。"""
|
|||
|
|
if score <= 0.2:
|
|||
|
|
return "L0"
|
|||
|
|
elif score <= 0.4:
|
|||
|
|
return "L1"
|
|||
|
|
elif score <= 0.6:
|
|||
|
|
return "L2"
|
|||
|
|
elif score <= 0.8:
|
|||
|
|
return "L3"
|
|||
|
|
else:
|
|||
|
|
return "L4"
|
|||
|
|
|
|||
|
|
def _compute_trust_bonus(self, user_id: str,
|
|||
|
|
fp: Optional[Dict[str, Any]]) -> float:
|
|||
|
|
"""根据用户历史行为计算信任加成。"""
|
|||
|
|
if not fp:
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
total = fp.get("total_behaviors", 0)
|
|||
|
|
avg_response = fp.get("avg_response_time_ms")
|
|||
|
|
|
|||
|
|
bonus = 0
|
|||
|
|
# 行为数据量
|
|||
|
|
if total > 500:
|
|||
|
|
bonus += 0.1
|
|||
|
|
elif total > 100:
|
|||
|
|
bonus += 0.05
|
|||
|
|
|
|||
|
|
# 平均响应快 → 经验丰富
|
|||
|
|
if avg_response and avg_response < 30000:
|
|||
|
|
bonus += 0.05
|
|||
|
|
|
|||
|
|
# 影子模式准确率(如果有)
|
|||
|
|
try:
|
|||
|
|
accuracy = shadow_executor.get_accuracy(user_id)
|
|||
|
|
avg_acc = accuracy.get("average_accuracy", 0)
|
|||
|
|
if avg_acc > 0.9:
|
|||
|
|
bonus += 0.1
|
|||
|
|
elif avg_acc > 0.8:
|
|||
|
|
bonus += 0.05
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
return min(bonus, 0.2)
|
|||
|
|
|
|||
|
|
def _infer_category(self, action: str) -> str:
|
|||
|
|
"""从操作描述推断类别。"""
|
|||
|
|
if any(kw in action for kw in ["代码", "code", "PR", "review"]):
|
|||
|
|
return "code_review"
|
|||
|
|
if any(kw in action for kw in ["邮件", "email", "回复", "reply"]):
|
|||
|
|
return "email"
|
|||
|
|
if any(kw in action for kw in ["文档", "document", "合同", "contract"]):
|
|||
|
|
return "document"
|
|||
|
|
return "decision"
|
|||
|
|
|
|||
|
|
def _generate_approval_requirements(self, level: str, action: str,
|
|||
|
|
risk_factors: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|||
|
|
"""生成审批要求。"""
|
|||
|
|
requirements = {
|
|||
|
|
"L2": {
|
|||
|
|
"approvers": ["直接上级或项目负责人"],
|
|||
|
|
"detail_required": ["操作摘要", "影响范围", "回滚方案"],
|
|||
|
|
"auto_approve_after_hours": 24,
|
|||
|
|
},
|
|||
|
|
"L3": {
|
|||
|
|
"approvers": ["技术负责人", "产品负责人"],
|
|||
|
|
"detail_required": ["操作摘要", "详细方案", "影响范围评估", "回滚方案", "测试结果"],
|
|||
|
|
"auto_approve_after_hours": 48,
|
|||
|
|
},
|
|||
|
|
"L4": {
|
|||
|
|
"approvers": ["技术负责人", "产品负责人", "安全负责人"],
|
|||
|
|
"detail_required": ["操作摘要", "详细方案", "影响范围评估", "风险评估报告", "回滚方案", "应急预案"],
|
|||
|
|
"auto_approve_after_hours": None, # 永不自动批准
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
base = requirements.get(level, requirements["L2"])
|
|||
|
|
base["risk_factors"] = [r["factor"] for r in risk_factors]
|
|||
|
|
base["action"] = action
|
|||
|
|
return base
|
|||
|
|
|
|||
|
|
def get_authorization_summary(self, user_id: str, days: int = 30) -> Dict[str, Any]:
|
|||
|
|
"""获取用户授权历史摘要。"""
|
|||
|
|
# 这里需要 behavior_collector 的查询能力
|
|||
|
|
behaviors = behavior_collector.get_user_behaviors(
|
|||
|
|
user_id=user_id, category="decision", limit=100
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
by_level = {"L0": 0, "L1": 0, "L2": 0, "L3": 0, "L4": 0}
|
|||
|
|
for b in behaviors:
|
|||
|
|
result = b.get("result") if isinstance(b, dict) else (b.result or {})
|
|||
|
|
level = result.get("level", "L2") if isinstance(result, dict) else "L2"
|
|||
|
|
by_level[level] = by_level.get(level, 0) + 1
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"user_id": user_id,
|
|||
|
|
"period_days": days,
|
|||
|
|
"total_decisions": sum(by_level.values()),
|
|||
|
|
"by_level": by_level,
|
|||
|
|
"auto_executed": by_level["L0"] + by_level["L1"],
|
|||
|
|
"required_approval": by_level["L2"] + by_level["L3"] + by_level["L4"],
|
|||
|
|
"auto_rate": round((by_level["L0"] + by_level["L1"]) / max(sum(by_level.values()), 1), 3),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
decision_authorizer = DecisionAuthorizer()
|