风险分级与审批策略
正在读取学习记录…
订单 Agent 为订单 order-1 提议退款 50 元。正常审批流程让另一位工作人员看到具体订单与金额,同意后才执行一次。预览退款方案不改变订单;真正退款会改变资金状态,因此需要不同的执行规则。
风险分级是根据动作的影响、金额、目标和可逆性选择处理流程。审批是可信人员确认一项具体动作。若审批只保存“同意”两个字,动作金额或目标在等待期间变化,执行器就难以判断该同意适用于哪个请求。
金额用整数分表示:5000 分是 50 元,10000 分是 100 元。这让阈值比较精确,避免浮点金额带来的舍入差异。
把“已同意”变成一张具体动作凭据
Action 保存发起人、租户、操作、目标和金额,冻结字段使夹具中的动作不被原地修改。Approval 保存被同意的动作、另一审批人、到期时间和已使用状态。到期时间与当前时间都采用固定秒数,方便重复观察边界。
risk 把预览归低风险、100元以内退款归中风险,其他动作转高风险流程。decide 对中风险要求审批逐字段匹配,再检查审批人、有效期和使用状态。未使用的有效审批才能改为已使用,返回“执行一次”。
真实系统需要在事务或原子条件更新中完成“验证未使用→标记使用→执行/登记执行请求”,同时配幂等键,即重复请求引用同一业务动作编号。这样才能处理并发和网络重试;Python内存布尔值不能协调多个服务实例。
| 动作条件 | 等级 | 执行路径 |
|---|---|---|
| 预览方案,无订单变更 | 低 | 直接返回预览 |
| 退款0到100元 | 中 | 另一人审批、具体动作匹配、一次使用 |
| 退款超过100元或未知操作 | 高 | 转独立高风险流程 |
| 负数金额 | 输入拒绝 | 先修正金额 |
在空目录保存并运行两个文件
在 PowerShell 输入以下命令创建练习目录,并进入目录。目录创建好后,在编辑器中新建下面两份文件并保存到这个目录。
New-Item -ItemType Directory -Force l8-approval
Set-Location l8-approvalmacOS 终端:
mkdir -p l8-approval
cd l8-approval先保存 approval_demo.py。它包含前面解释的审批绑定以及正常和边界输入。
"""审批绑定具体动作,额度以整数分表示。"""
from dataclasses import dataclass
@dataclass(frozen=True)
class Action:
actor: str
tenant: str
operation: str
target: str
cents: int
@dataclass
class Approval:
action: Action
approver: str
expires: int
used: bool = False
LIMIT = 10000
def risk(action):
if action.cents < 0:
return "拒绝"
if action.operation == "preview":
return "低"
if action.operation == "refund" and action.cents <= LIMIT:
return "中"
return "高"
def decide(action, approval, now):
level = risk(action)
if level == "低":
return "允许:预览"
if level != "中":
return "拒绝:超出自动流程"
if approval is None:
return "待审批"
# 不同金额、目标、租户、操作或发起人都需要重新审批。
if approval.action != action:
return "拒绝:审批动作不匹配"
if approval.approver == action.actor:
return "拒绝:需另一审批人"
if now >= approval.expires:
return "拒绝:审批过期"
if approval.used:
return "拒绝:审批已使用"
approval.used = True
return "允许:执行一次"
def main():
action = Action("u1", "A", "refund", "order-1", 5000)
approval = Approval(action, "u2", 120)
print("风险等级 ->", risk(action))
print("无审批 ->", decide(action, None, 100))
print("已审批 ->", decide(action, approval, 100))
print("重复执行 ->", decide(action, approval, 100))
changed = Action("u1", "A", "refund", "order-1", 6000)
print("金额变化 ->", decide(changed, approval, 100))
if __name__ == "__main__":
main()再保存 test_approval.py。测试通过程序的公开函数检查正常路径与拒绝路径;测试文件和主程序放在同一目录,才能按文件名导入。
import unittest
# 固定良性输入检查正常、拒绝和边界路径,不连接外部服务。
from approval_demo import Action, Approval, decide, risk
class ApprovalTests(unittest.TestCase):
def setUp(self):
self.action = Action("u1", "A", "refund", "order-1", 5000)
self.approval = Approval(self.action, "u2", 120)
def test_pending_and_once(self):
self.assertEqual(decide(self.action, None, 100), "待审批")
self.assertEqual(decide(self.action, self.approval, 100), "允许:执行一次")
self.assertEqual(decide(self.action, self.approval, 100), "拒绝:审批已使用")
def test_amount_change(self):
changed = Action("u1", "A", "refund", "order-1", 6000)
self.assertEqual(decide(changed, self.approval, 100), "拒绝:审批动作不匹配")
self.assertFalse(self.approval.used)
def test_expiry_boundary(self):
self.assertEqual(decide(self.action, self.approval, 120), "拒绝:审批过期")
def test_self_approval(self):
approval = Approval(self.action, "u1", 120)
self.assertEqual(decide(self.action, approval, 100), "拒绝:需另一审批人")
def test_risk_boundaries(self):
self.assertEqual(risk(Action("u1", "A", "refund", "x", 10000)), "中")
self.assertEqual(risk(Action("u1", "A", "refund", "x", 10001)), "高")
self.assertEqual(risk(Action("u1", "A", "refund", "x", -1)), "拒绝")
def test_low_risk(self):
self.assertEqual(decide(Action("u1", "A", "preview", "x", 0), None, 100),
"允许:预览")
def test_binding_tenant(self):
changed = Action("u1", "B", "refund", "order-1", 5000)
self.assertEqual(decide(changed, self.approval, 100), "拒绝:审批动作不匹配")
if __name__ == "__main__":
unittest.main()返回同一个终端,先列出当前目录内容,确认两个文件名都以 .py 结尾,然后运行主程序。
ls
python -X utf8 approval_demo.py程序输出:
风险等级 -> 中
无审批 -> 待审批
已审批 -> 允许:执行一次
重复执行 -> 拒绝:审批已使用
金额变化 -> 拒绝:审批动作不匹配50元被分到中风险,没有审批时等待。有效审批第一次允许、第二次因已经使用而拒绝。金额改为60元后,虽然仍然属于中风险,审批却不再匹配。这能说明风险等级和审批绑定各自解决不同问题:同等级的两笔操作也可能需要不同审批。程序返回执行决定,实际退款账务没有接入。
运行测试:
python -X utf8 -m unittest -v test_approval.py末尾应显示 Ran 7 tests 和 OK。若提示找不到模块,先用 dir 核对目录与文件名。
改一处,观察决定怎样变化
把 LIMIT = 10000 改成 LIMIT = 4000,重新运行主程序。第一行由 风险等级 -> 中 变成 风险等级 -> 高,后面的执行决定改为 拒绝:超出自动流程。50元超过新阈值,已有审批也不能绕过分级。恢复10000。
python -X utf8 approval_demo.py恢复原值后,重新运行测试。
等待审批期间发生了什么
| 现象 | 需要核对 | 对应处理 |
|---|---|---|
| 审批后订单或金额变化 | 动作完整字段 | 生成新动作,重新审批 |
| 审批正好到期 | now >= expires | 到期拒绝,刷新审批 |
| 两个执行请求都成功 | 使用标记更新是否原子 | 数据库条件更新、事务、幂等键 |
| 发起人自己完成审批 | 可信审批人身份 | 在审批服务校验角色与职责分离 |
| 门槛改小仍沿用旧决定 | 当前策略是否重新计算 | 执行前重新检查,记录策略版本 |
审批页面应展示操作、对象、金额、目标位置和重要参数,使工作人员能确认真实副作用。审批服务还要认证审批人并检查其审批额度;示例把审批对象当作可信夹具创建,未实现签名凭据或审批人登录。执行失败后的审批是否可再次使用,应由事务状态和业务补偿流程决定,不能直接把 used 改回 False。
OWASP 交易授权指南强调授权与具体交易数据绑定,以及步骤顺序与服务端校验。将它用于 Agent 时,要在实际工具副作用之前落实校验。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“风险分级与审批策略”,哪种做法更稳妥?
对应知识点:L8-approval
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L8-approval
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L8-approval
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L8-approval
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。