47 lines
2.0 KiB
Python
47 lines
2.0 KiB
Python
"""依赖是全部满足才可执行;保护节点必须覆盖每个终点的依赖闭包。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from muse.任务运行.接口 import 执行计划, 步骤处理器
|
|
from muse.共享.错误 import Muse错误
|
|
|
|
|
|
class 流程校验错误(Muse错误):
|
|
错误码 = "MUSE_FLOW_INVALID"
|
|
|
|
|
|
def 核对流程(计划: 执行计划, 处理器: dict[str, 步骤处理器], 必需保护: tuple[str, ...]) -> None:
|
|
节点 = {项.步骤ID: 项 for 项 in 计划.步骤}
|
|
if not 节点 or len(节点) != len(计划.步骤):
|
|
raise 流程校验错误("流程步骤不能为空或重名")
|
|
祖先: dict[str, set[str]] = {}
|
|
访问中: set[str] = set()
|
|
|
|
def 遍历(身份: str) -> set[str]:
|
|
if 身份 in 访问中:
|
|
raise 流程校验错误("流程依赖存在环")
|
|
if 身份 not in 节点:
|
|
raise 流程校验错误("依赖节点不存在")
|
|
if 身份 in 祖先:
|
|
return 祖先[身份]
|
|
访问中.add(身份)
|
|
结果: set[str] = set()
|
|
for 依赖 in 节点[身份].依赖:
|
|
结果 |= 遍历(依赖) | {依赖}
|
|
if 节点[依赖].输出合同 != 节点[身份].输入合同:
|
|
raise 流程校验错误("相邻步骤的输入输出合同不兼容")
|
|
访问中.remove(身份)
|
|
祖先[身份] = 结果
|
|
return 结果
|
|
|
|
for 身份 in 节点:
|
|
遍历(身份)
|
|
处理 = 处理器[身份]
|
|
if 节点[身份].角色 != 处理.角色 or not set(节点[身份].工具) <= set(处理.允许工具):
|
|
raise 流程校验错误("槽位角色或工具许可不兼容")
|
|
已被依赖 = {依赖 for 项 in 计划.步骤 for 依赖 in 项.依赖}
|
|
for 终点 in 节点.keys() - 已被依赖:
|
|
路径保护 = {处理器[身份].保护职责 for 身份 in 祖先[终点] | {终点}}
|
|
if not set(必需保护) <= 路径保护:
|
|
raise 流程校验错误("存在绕过必需保护的流程终点")
|