尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Havenlon 执行控制工程 II 02|裁决面与执行面,为什么必须分开?

Havenlon 执行控制工程 II 02|裁决面与执行面,为什么必须分开? 在很多系统里策略判断和真正执行之间只隔着一行代码规则返回允许于是调用执行接口。这种写法非常自然。策略存在的意义本来就是回答这件事能不能做既然答案已经是允许继续做下去似乎顺理成章。但把它放进高风险场景问题就浮出来了负责判断能不能做的组件同时拥有了真正去做的能力。裁决与执行被重新绑回同一处裁决运行时表面上只是判断者实际上已经成为能够直接改变现实的执行主体。前面辛苦建立的职责分离在最后一行代码上失效。所以在真正动手构建执行边界时一条基础原则是运行时只产生裁决不直接产生现实动作。裁决是一个结果执行是另一个独立步骤两者之间必须存在明确边界。一、裁决与执行本来就是两个对象先把概念拆开。裁决表示的是根据当前输入、规则和状态这项操作是否获得继续执行的资格它的输出是允许、拒绝或其他有限的结论。执行表示的是真正调用某个能够改变现实的能力——发送交易、调用支付接口、修改生产环境、删除对象、驱动一台设备。前者是关于现实的判断后者是现实本身发生变化的过程。它们天然不是一回事只是在普通业务代码里很容易被压缩成一句如果允许就执行。一旦这样写运行时的输出就不再只是一个事实而变成了执行触发器。允许的含义也从当前规则认为可以继续悄悄升格成现实现在必须发生变化。这一步的代价可以从能力角度看得更清楚。假设策略在判断一笔付款它读取金额、账户、审批状态和额度条件都成立于是直接发起转账。功能上非常高效但这时运行时同时握有三种能力——读取事实、解释规则、改变现实。它既是裁判又是执行者。于是一旦运行时自身出现规则错误、输入被污染、版本错配或运行环境失陷错误的裁决就会直接变成现实结果中间不再有第二道独立边界。运行时的一次失陷等于裁决面与执行面一起失陷。从故障隔离的角度看这是相当危险的耦合。二、方便如何把裁决者变成执行平台现实工程中这两者被绑在一起往往不是因为设计者忽视安全而是因为顺手。策略需要查数据就给它数据访问需要调用外部风险服务就给它网络权限判断通过之后要转账顺手给上支付接口部署规则要看环境顺手让它去问编排系统。每一步都有充分理由累积下来运行时逐渐拥有读取、判断、修改、调用、执行、恢复的全套能力。越方便职责越多职责越多攻击面越大。一个原本纯粹的裁决引擎就这样演化成一个握有大量现实能力的自动化平台——恰好与小而确定的可信边界背道而驰。三、两个平面更清晰的做法是把整个过程拆成两个平面。裁决面负责消费已经明确提供的事实、运行规则、检查不变量、输出裁决但不直接改变现实。执行面负责接收经过验证的执行资格、重新核对必要条件、构造最终执行参数、调用具体能力、产出结果与证据。事实 → 运行时 → 裁决 → 执行边界 → 执行器 → 现实运行时的终点是裁决不是现实。这条链看上去比直接调用多了一层。而正是这一层给系统留下了继续验证的机会。四、二次验证验证的是绑定关系既然运行时已经判断过一次为什么执行前还要再检查因为运行时判断的对象和最终执行的对象之间可能已经不同。运行时对某个收款方和某个金额作出了允许而进入执行环节时收款方已经被换成另一个。如果执行侧只检查裁决结论是允许然后照做那么前面的判断实际上已经失效——被判断的是一个对象被执行的是另一个。裁决必须与具体执行对象绑定执行侧至少要能确认我现在准备做的是不是运行时当时真正裁决过的那件事。这里容易产生误解如果运行时已经检查了额度执行器是否也要再实现一遍完整的额度规则未必。那样会出现两套规则反而引入不一致的风险。更合理的分工是——运行时根据策略判断这个明确对象是否被允许执行器确认当前准备执行的对象仍然等于被裁决的对象动作类型没变目标没变关键参数没变裁决未过期协议语义一致。执行侧验证的是绑定关系而不是重新承担全部策略计算。这件事必须发生在靠近执行的位置因为一次请求在系统里会经历多次转换业务对象、意图、策略输入、裁决对象、执行参数、底层请求。每一次转换都可能带来语义漂移。如果运行时直接执行它就同时承担了策略判断和参数映射事后很难分清错误来自规则还是来自转换。独立的执行侧则可以把最终参数单独暴露成一个明确对象让系统重新确认最终的目标是什么金额是什么环境是什么动作是什么——这些才是现实即将变化时真正有意义的事实。状态同样如此。运行时作出判断时看到的是某一个时刻的状态而执行器动手时面对的是另一个时刻的现实中间隔着队列、网络、异步调度、人工等待或设备通信。运行时判断某台设备处于安全状态因而放行几秒钟后设备已经进入故障状态——如果执行侧只相信那份旧结论现实已经变了执行却仍在沿用过去的判断。这正是第一季讨论过的过期状态。独立执行侧的价值之一就是留出最后一次状态确认的机会不必重跑完整策略但至少检查那些一旦变化就会让执行资格失效的关键条件。五、隔离必须落到权限层如果运行时和执行器跑在同一个进程里、拥有相同的系统权限、使用同一套凭据、访问同一片网络并且由同一个模块直接调用那么逻辑上虽然有两个名字安全上未必存在真正的隔离。这一季谈怎么造就不能停留在代码结构的抽象上还要落到能力集合运行时不需要支付凭据、设备控制能力、广播能力或生产环境写权限执行器不需要修改策略、加载规则、解释任意脚本或访问全部业务数据。两边都只拿完成自身职责所必需的最小能力。只有这样两个平面才不只是代码分层而是权限分层。这带来一个很实际的收益缩小高价值凭据的暴露范围。执行一项动作可能需要私钥、接口凭据、设备证书或环境令牌。如果运行时直接执行这些东西必须暴露在运行时所处的环境里——而运行时通常还承担规则解析、复杂输入处理、策略加载和上下文判断攻击面比执行器大得多。把执行能力独立出去就能让最危险的能力停留在尽可能小的可信范围内。分开之后还要防止反向问题执行器越写越胖开始查业务数据、跑规则、处理审批和工作流最终又变成一个大型应用。执行器应该知道怎样执行但不应该重新决定业务上为什么应该执行。它的职责应该是窄的接收明确的执行对象验证必要的绑定做有限的状态检查映射到具体接口触发动作取回结果生成证据。复杂的业务判断留在上层执行边界只保留那些必须靠近现实的少量不变量。窄才容易审计、测试、隔离和加固也才有可能在将来迁移到不同的运行环境。六、执行侧必须能够拒绝一份合法裁决这是整个架构成立与否的关键。运行时已经输出允许执行侧有没有资格返回拒绝如果没有它仍然只是一个远程调用的代理。真正独立的执行边界必须允许裁决为允许、执行为拒绝同时成立理由可以是最终参数不匹配、裁决已过期、状态已变化、执行对象不一致、前序证据不完整、协议版本不符、必要的本地条件不成立。这不是执行侧在推翻策略它表达的是另一件事这份裁决现在已经不足以支持当前这一次现实动作。由此允许这个结论本身也应当被降权理解——它不是命令而是继续资格当前阶段没有发现阻止条件可以进入下一层验证。只有后续边界继续确认了对象、参数、状态、上下文和有效性执行资格才真正成立。这样任何一个上游结论都不会具备穿透整条链路的力量。要做到这一点裁决就不能只是一个布尔值。如果运行时最终只返回真下游几乎无法知道它为什么产生、针对哪个对象、何时形成、属于哪套策略、有没有过期、对应哪份意图。裁决更适合成为一个明确的协议对象在概念上能够表达这是哪一次判断针对哪个执行对象结论是什么在什么策略语义和时间上下文下形成带有哪些必要的绑定关系。这里不需要展开具体结构重要的是它可以被验证和引用而不只是一次函数返回值——只有成为对象执行侧才谈得上判断当前动作是否仍然属于这份裁决。同样裁决不该拥有无限生命周期。上午形成的一个允许到了下午是否还成立明天呢如果没有时间语义一个过去合法的结论很容易变成永久通行证。裁决与意图、审批一样需要新鲜度和有效范围而确认它是否仍在时效之内正是执行侧必须承担的一项职责。七、能力分离、失败处理与最后的可拒绝窗口上一篇强调运行时最好无副作用这里可以补上另一半即使运行时想产生副作用也不应该天然拥有通往执行接口的能力。否则会出现一种荒谬的状态——规则表面上返回了拒绝但在返回之前已经悄悄触发了现实动作。语言层不允许权限层也不给两边共同构成边界。这样即便运行时自身出现严重逻辑问题也更难直接转化为任意的现实执行。执行失败之后的处理同样需要克制。常见的自动化做法是把错误回抛给上游由它改参数、换接口、找备用路径再试一次。这在普通系统里很灵活但在执行控制里意味着上游正在逐渐获得自主寻找可执行路径的能力。一个安全边界返回拒绝不能被理解成换个办法。操作性错误、安全拒绝、状态冲突、执行失败应当具有不同的后续语义可以重新收集事实、重新提交裁决但不能因为任务还没完成就不断试探能穿过边界的路径。在真正靠近不可逆点的地方还可以进一步拆分准备与提交。很多高风险动作天然存在这两个阶段先解析最终参数、验证对象、检查状态、构建待执行内容此时现实尚未改变只有最后的提交才触发不可逆动作。这个结构的价值在于它让系统在最后时刻能够明确看到现在真正准备发生的动作究竟是什么并在提交前完成最终的绑定检查、状态确认和证据准备。原则是在不可逆点之前尽量保留最后一次可拒绝的窗口。分离之后证据链也会更清楚。一次执行最终至少要能解释哪一份意图产生了哪一个裁决由哪个执行侧接收执行前看到了什么关键状态最终触发了什么动作结果是什么。这条链上的每一步都不能只靠系统说它们属于同一件事而需要明确的绑定关系。把判断与执行拆开不只是安全隔离也让谁判断和谁执行终于成为两个不同的责任节点。八、把顺手做掉的事情重新拆开从责任模型看这两者对应两种不同性质的权力一种是解释权——根据规则解释当前事实另一种是操作权——真正把一个数字决定变成现实变化。长期绑在同一个组件里一次失陷的影响就会覆盖两者拆开之后至少可以做到运行时出错不等于自动执行错误执行器被调用不等于自动获得业务授权。这不是假设任何一边永远正确而是让单边错误更难独自走完一条完整的失败链。Agent 场景让这条分界更紧迫。一个能力很强的自主系统已经具备理解、规划、工具调用、策略生成、状态分析和错误恢复如果它所处的运行时还直接握有支付、部署、设备控制和密钥使用能力那么一次判断偏差、一次提示注入或一次上下文污染都可能迅速变成现实动作。更稳妥的结构是它可以产生意图、计划、建议、策略输入甚至候选裁决但真正进入执行时仍要穿过独立边界。越聪明的系统越应该明确区分判断能力和改变现实的能力。软件工程天生喜欢合并既然运行时已经知道可以执行顺手调用既然执行器已经拿到状态顺手判断既然规则需要上下文顺手查询既然出错了顺手恢复。每一处都合理。而高风险执行控制做的事情常常相反——把本来可以顺手完成的事情重新拆开谁收集事实谁判断谁授权谁执行谁记录证据。这些拆分不是为了架构图好看而是为了不让任何一个局部组件同时握有解释、授权、执行和改写历史的完整能力。需要说明的是这套结构不消除风险。它降低的是单点失陷直接兑换成现实后果的概率增加的是独立验证的位置并在最靠近现实的地方保留一次拒绝的余地。所以最终的形态不该是规则放行现实随之发生而更接近事实 → 运行时 → 裁决 → 执行边界 → 执行器 → 结果 → 证据裁决与现实之间被刻意留出了一段距离。这段距离就是系统最后一次确认允许是否仍有资格变成发生。裁决者不应该天然拥有改变现实的能力。真正的执行边界不是让判断系统变得越来越强而是让判断与执行之间始终保持一种关系你可以说可以但现实不会仅仅因为你说了可以就自动发生。
返回列表