Codex 系统手册

需求分层和执行策略组合

stage-4.1.211 个文件

这一阶段像开工前的“规矩汇总台”,属于幕后支撑。它先读 requirements.toml、企业下发配置等来源,把每一层拆清楚:普通配置普通合,读文件禁令、执行规则、hook 这些安全项按专门办法追加或校验,不能被后来的配置冲掉。最后 stack 把多层叠成最终要求;执行策略部分把“哪些命令能跑”翻成机器能检查的规则,并能安全追加白名单;网络代理配置会随文件变化重载;hook 规则则保护用户自己的启用和信任选择。

本阶段的文件11

执行策略输入

这些文件定义面向需求的 TOML 中如何表示执行策略数据、如何在磁盘上修订,以及如何由运行时策略加载器使用。

config/src/requirements_exec_policy.rs源码 ↗
configconfig load

这个文件像一位“门卫规则翻译员”。用户在 requirements.toml 里写规则,比如某个命令遇到哪些参数时要提示用户,或者直接禁止。文件先定义这些 TOML 配置长什么样,再把它们转换成 codex-execpolicy 使用的 Policy(执行策略对象)。转换时它会很严格:规则不能为空,匹配模式不能为空,一个模式位置只能写单个 token(词)或 any_of(多个可选词)之一,不能两个都写;decision(决定)也必须写。而且 requirements.toml 里不允许写 allow(允许),因为这些规则会和别的配置合并,系统会取更保守的结果,允许项在这里反而容易造成误解。最后,它把规则按“第一个命令词”分组,这样运行时检查命令时能更快找到相关规则。

函数细节8
RequirementsExecPolicy::new18–20 ↗
fn new(policy: Policy) -> Self

作用:把一个已经做好的 Policy 包装成 RequirementsExecPolicy。这样代码能明确知道:这份策略来自 requirements.toml,而不是别的配置来源。

数据流:输入是一份内部执行策略 Policy → 函数不改动它,只是放进 RequirementsExecPolicy 这个外壳里 → 输出一个带来源语义的策略对象。

调用关系:测试里会直接用它造出 requirements 专用策略,检查当 requirements 策略和父级策略不同时系统怎么合并、怎么保留主机可执行文件等行为。RequirementsExecPolicyToml::to_requirements_policy 也会在转换完成后用它做最后包装。

调用图:被 3 处调用(child_does_not_use_parent_exec_policy_when_requirements_exec_policy_differs, merges_requirements_exec_policy_network_rules, preserves_host_executables_when_requirements_overlay_is_present)。

RequirementsExecPolicy::eq24–26 ↗
fn eq(&self, other: &Self) -> bool

作用:判断两份 RequirementsExecPolicy 是否“内容上一样”。它不是简单比较内存地址,而是比较整理后的规则指纹。

数据流:输入是两份策略对象 → 分别交给 policy_fingerprint 生成可比较的规则清单 → 如果两个清单完全一样,就认为这两份策略相等。

调用关系:它实现的是相等比较能力,常用于测试或配置变更判断。它把真正的比较工作交给 policy_fingerprint,因为策略内部规则可能存放顺序不同,直接比较不可靠。

调用图:调用 1 个内部函数(policy_fingerprint)。

RequirementsExecPolicy::as_ref32–34 ↗
fn as_ref(&self) -> &Policy

作用:让 RequirementsExecPolicy 可以被当成普通 Policy 来读取。这样需要执行策略的代码不用关心外面多包了一层。

数据流:输入是一个 RequirementsExecPolicy → 函数取出里面保存的 Policy 引用 → 输出这份 Policy 的只读引用,不复制也不修改。

调用关系:它是一个适配接口。其他代码如果只认识 Policy,也可以通过这个函数拿到内部策略继续工作。

policy_fingerprint37–46 ↗
fn policy_fingerprint(policy: &Policy) -> Vec<String>

作用:把一份策略变成稳定的“规则指纹”,方便判断两份策略是不是等价。可以把它理解成给规则清单拍一张排序后的照片。

数据流:输入是一份 Policy → 读取里面所有按程序名分组的规则,把每条规则转成“程序名:规则内容”的字符串 → 排序后输出字符串列表。排序能消除原本存放顺序带来的影响。

调用关系:RequirementsExecPolicy::eq 会调用它来做相等判断。它依赖 Policy 的 rules 信息,把复杂的规则结构变成容易比较的普通文本清单。

调用图:被 1 处调用(eq);外部调用 3 个(new, rules, format!)。

RequirementsExecPolicyDecisionToml::as_decision84–90 ↗
fn as_decision(self) -> Decision

作用:把 TOML 里读到的决定值,转换成执行策略系统内部使用的决定值。也就是把配置语言翻译成程序内部语言。

数据流:输入是 requirements.toml 里的 Allow、Prompt 或 Forbidden → 按一一对应关系转换 → 输出 codex_execpolicy::Decision 里的 Allow、Prompt 或 Forbidden。

调用关系:RequirementsExecPolicyToml::to_policy 在处理每条规则时会用到它。注意 to_policy 会先拦住 requirements.toml 里的 Allow,所以这个函数本身只是负责转换,不负责判断 allow 是否允许出现。

RequirementsExecPolicyToml::to_policy125–183 ↗
fn to_policy(&self) -> Result<Policy, RequirementsExecPolicyParseError>

作用:这是本文件最核心的转换函数:把 requirements.toml 里的规则列表,检查并变成真正可执行的 Policy。有人加载 requirements.toml 后,就靠它把文字配置变成系统能用的规则。

数据流:输入是反序列化后的 TOML 规则结构 → 先检查规则列表、说明文字、匹配模式和决定值是否合法 → 再把每个模式 token 转成内部 PatternToken,并按第一个 token 建立规则分组 → 最后输出 Policy;如果发现配置有问题,就输出带具体位置的错误。

调用关系:RequirementsExecPolicyToml::to_requirements_policy 会调用它作为第一步转换。它在转换每个模式位置时会交给 parse_pattern_token 做细粒度检查;在处理 decision 时会使用 RequirementsExecPolicyDecisionToml::as_decision;最后创建内部 PrefixRule 和 Policy。

调用图:被 1 处调用(to_requirements_policy);外部调用 4 个(from, new, new, new)。

RequirementsExecPolicyToml::to_requirements_policy185–189 ↗
fn to_requirements_policy(
        &self,
    ) -> Result<RequirementsExecPolicy, RequirementsExecPolicyParseError>

作用:把 TOML 规则直接转换成带 requirements 来源标记的 RequirementsExecPolicy。它是给外部调用者用的更方便入口。

数据流:输入是一份 RequirementsExecPolicyToml → 先调用 to_policy 得到内部 Policy → 再用 RequirementsExecPolicy::new 包装 → 输出 RequirementsExecPolicy;如果 to_policy 报错,它也把错误原样传出去。

调用关系:它站在配置加载流程的末端:前面已经把 TOML 文件读成结构体,调用它后就得到系统后续可以合并和使用的 requirements 执行策略。

调用图:调用 1 个内部函数(to_policy)。

parse_pattern_token192–236 ↗
fn parse_pattern_token(
    token: &RequirementsExecPolicyPatternTokenToml,
    rule_index: usize,
    token_index: usize,
) -> Result<PatternToken, RequirementsExecPolicyParseError>

作用:检查并转换一段匹配模式里的单个位置。这个位置要么是一个固定词,要么是一组可选词,不能空,也不能两种写法同时出现。

数据流:输入是一个 TOML token,以及它所在的规则编号和 token 编号 → 根据 token 和 any_of 的填写情况做校验 → 合法时输出内部 PatternToken;不合法时输出带规则位置和原因的错误,方便用户知道哪里写错了。

调用关系:RequirementsExecPolicyToml::to_policy 在处理每条 prefix_rule 的 pattern 时会反复使用它。它只管单个模式位置的合法性,整体规则是否为空、decision 是否缺失等问题由 to_policy 处理。

调用图:外部调用 2 个(Alts, Single)。

execpolicy/src/amend.rs源码 ↗
domain_logiccross-cutting

这个文件解决的是“自动改策略文件”的问题。策略文件可以理解成一张门禁表,里面写着哪些命令或网络地址可以放行。这里提供两种追加方式:一种是追加命令前缀规则,比如允许以 curl 开头的命令;另一种是追加网络规则,比如允许访问 api.github.com 的 HTTPS。它会先把输入整理成策略文件能读懂的一行文本,再确保策略文件所在目录存在,然后打开文件、加文件锁。文件锁就是一把“请别人先别同时写”的锁,能减少两个程序同时追加导致内容交叉的风险。写入前它会读一遍已有内容,如果同一行已经存在,就直接结束,不重复添加。它还会处理文件末尾没有换行的情况,先补一个换行再追加。需要注意的是,这些函数会做阻塞式磁盘读写,也就是调用时线程会等文件操作完成;如果放在异步程序里,应该放到专门跑阻塞任务的地方。

函数细节10
blocking_append_allow_prefix_rule65–81 ↗
fn blocking_append_allow_prefix_rule(
    policy_path: &Path,
    prefix: &[String],
) -> Result<(), AmendError>

作用:把一条“允许某个命令前缀”的规则追加到策略文件里。比如传入 ["echo", "Hello"],意思是以后遇到以这些词开头的命令,可以直接允许。

数据流:进去的是策略文件路径和一组命令词。它先检查这组词不能为空,再把每个词转成 JSON 字符串格式,避免引号、空格等特殊字符把规则写坏;然后拼成一行 prefix_rule(...) 规则。最后把这行规则交给底层写文件函数;成功时文件多出这条规则,失败时返回具体错误。

调用关系:这是给外部调用的入口之一。测试里的多个用例会调用它来验证目录创建、追加规则、补换行和避免格式问题。它自己不直接碰文件细节,而是把拼好的规则交给 append_rule_line 去处理目录和写入。

调用图:调用 1 个内部函数(append_rule_line);被 4 处调用(appends_prefix_and_network_rules, appends_rule_and_creates_directories, appends_rule_without_duplicate_newline, inserts_newline_when_missing_before_append);外部调用 1 个(format!)。

blocking_append_network_rule85–125 ↗
fn blocking_append_network_rule(
    policy_path: &Path,
    host: &str,
    protocol: NetworkRuleProtocol,
    decision: Decision,
    justification: Option<&str>,
) -> Result<(), AmendError>

作用:把一条网络访问规则追加到策略文件里。它用来记录某个具体主机、某种协议,以及最终决定是允许、询问还是拒绝。

数据流:进去的是策略文件路径、主机名、协议、决定和可选说明。它先规范化主机名,比如统一大小写,并拒绝通配符这类不够具体的主机;再检查说明文字不能是空白;然后把主机、协议、决定、说明都安全地转成 JSON 字符串,拼成一行 network_rule(...)。最后交给 append_rule_line 写入文件;成功时策略文件新增网络规则,失败时返回原因。

调用关系:这是另一个给外部调用的入口。测试会用它验证网络规则能被正确写入、能和前缀规则共存,也会验证非法通配符主机会被拒绝。它依赖 normalize_network_rule_host 清洗主机名,依赖协议的 as_policy_string 得到策略文件里的协议写法,最后把成品规则交给 append_rule_line

调用图:调用 3 个内部函数(append_rule_line, as_policy_string, normalize_network_rule_host);被 3 处调用(appends_network_rule, appends_prefix_and_network_rules, rejects_wildcard_network_rule_host);外部调用 4 个(InvalidNetworkRule, format!, to_string, vec!)。

append_rule_line127–145 ↗
fn append_rule_line(policy_path: &Path, rule: &str) -> Result<(), AmendError>

作用:在真正写规则前,先确保策略文件的直接父目录存在。它像写信前先确认信箱所在楼层已经建好。

数据流:进去的是策略文件路径和已经拼好的规则行。它先取出文件所在目录;如果路径没有父目录,就报错。然后尝试创建这个直接父目录;如果目录已经存在,就当作正常情况继续。最后把路径和规则行交给 append_locked_line。它本身主要改变的是磁盘上的目录状态。

调用关系blocking_append_allow_prefix_ruleblocking_append_network_rule 都会把拼好的规则交给它。它负责“写文件前的准备工作”,准备好后再把真正的追加、加锁、去重交给 append_locked_line

调用图:调用 1 个内部函数(append_locked_line);被 2 处调用(blocking_append_allow_prefix_rule, blocking_append_network_rule);外部调用 2 个(parent, create_dir)。

append_locked_line147–193 ↗
fn append_locked_line(policy_path: &Path, line: &str) -> Result<(), AmendError>

作用:安全地把一行规则追加到策略文件末尾,同时避免重复写入。它是这个文件里真正动手读写策略文件的地方。

数据流:进去的是策略文件路径和一行规则文本。它打开文件;如果文件不存在就创建;然后加文件锁,避免合作的其他写入者同时改同一个文件。接着从文件开头读完整内容:如果已经有完全相同的一行,就什么也不写;如果文件末尾缺换行,就先补换行;最后追加新规则并带上换行。出来的是成功或具体的打开、锁定、读取、定位、写入错误,磁盘文件可能被新增一行。

调用关系:它只被 append_rule_line 调用,是整个追加流程的最后一环。上层函数负责把用户输入变成规则文本,它负责把这行文本稳妥地落到文件里。

调用图:被 1 处调用(append_rule_line);外部调用 4 个(new, Start, new, format!)。

tests::appends_rule_and_creates_directories202–218 ↗
fn appends_rule_and_creates_directories()

作用:验证追加命令前缀规则时,如果策略目录还不存在,程序会创建目录并写出正确内容。

数据流:进去的是测试临时目录里构造出的策略文件路径和一组命令词。测试调用 blocking_append_allow_prefix_rule,然后读取生成的策略文件,比较内容是否正好是一行允许规则。结果是确认目录和文件都能被正确创建,规则格式也正确。

调用关系:这是 Rust 测试运行器执行的用例之一。它直接覆盖 blocking_append_allow_prefix_rule,间接覆盖 append_rule_line 的建目录行为和 append_locked_line 的写文件行为。

调用图:调用 1 个内部函数(blocking_append_allow_prefix_rule);外部调用 4 个(from, assert_eq!, read_to_string, tempdir)。

tests::appends_rule_without_duplicate_newline221–245 ↗
fn appends_rule_without_duplicate_newline()

作用:验证文件本来已经以换行结尾时,再追加新规则不会多插入空行。

数据流:测试先创建策略目录,并写入一条已有规则,且末尾带换行。然后调用 blocking_append_allow_prefix_rule 追加第二条规则,再读取文件检查两条规则是否紧挨着各占一行,中间没有多余空行。

调用关系:这个测试由测试运行器调用。它检查的是 append_locked_line 对已有文件内容的处理,特别是“已有换行就不要再补一个”的行为。

调用图:调用 1 个内部函数(blocking_append_allow_prefix_rule);外部调用 6 个(from, assert_eq!, create_dir_all, read_to_string, write, tempdir)。

tests::inserts_newline_when_missing_before_append248–271 ↗
fn inserts_newline_when_missing_before_append()

作用:验证文件末尾没有换行时,追加新规则前会自动补一个换行,避免两条规则挤在同一行。

数据流:测试先写入一条没有结尾换行的旧规则。然后调用 blocking_append_allow_prefix_rule 追加新规则。最后读取文件,确认旧规则和新规则被分成两行,并且文件最后也有换行。

调用关系:这个测试由测试运行器调用。它专门盯住 append_locked_line 里的补换行逻辑,防止策略文件被写成难以解析的一长行。

调用图:调用 1 个内部函数(blocking_append_allow_prefix_rule);外部调用 6 个(from, assert_eq!, create_dir_all, read_to_string, write, tempdir)。

tests::appends_network_rule274–293 ↗
fn appends_network_rule()

作用:验证网络规则能按预期格式写入策略文件,并且主机名会被规范化。

数据流:进去的是临时策略文件路径、大小写混合的主机名、HTTPS 协议、允许决定和一段说明。测试调用 blocking_append_network_rule,然后读取文件,确认主机名变成小写,协议和决定也按策略文件要求写好,说明文字也被保留。

调用关系:这个测试由测试运行器调用。它覆盖 blocking_append_network_rule 的主机名规范化、字段序列化和最终写入流程,也间接走到 append_rule_lineappend_locked_line

调用图:调用 1 个内部函数(blocking_append_network_rule);外部调用 3 个(assert_eq!, read_to_string, tempdir)。

tests::appends_prefix_and_network_rules296–318 ↗
fn appends_prefix_and_network_rules()

作用:验证同一个策略文件里可以先追加命令前缀规则,再追加网络规则,二者按顺序共存。

数据流:测试先调用 blocking_append_allow_prefix_rule 写入一条 curl 前缀规则,再调用 blocking_append_network_rule 写入一条访问 api.github.com 的网络规则。最后读取文件,确认两条规则按调用顺序各占一行。

调用关系:这个测试由测试运行器调用。它把两个公开追加函数放在一起跑,验证它们共享同一套 append_rule_lineappend_locked_line 写入机制时不会互相破坏。

调用图:调用 2 个内部函数(blocking_append_allow_prefix_rule, blocking_append_network_rule);外部调用 4 个(from, assert_eq!, read_to_string, tempdir)。

tests::rejects_wildcard_network_rule_host321–336 ↗
fn rejects_wildcard_network_rule_host()

作用:验证网络规则不能使用通配符主机名,比如 *.example.com。这样做是为了避免一条规则放行过大的范围。

数据流:测试把带通配符的主机名传给 blocking_append_network_rule。函数应该返回错误,而不是写入文件。测试再比较错误文字,确认错误原因明确指出网络规则必须是具体主机,不能用通配符。

调用关系:这个测试由测试运行器调用。它主要覆盖 blocking_append_network_rule 调用 normalize_network_rule_host 后的拒绝路径,确保非法输入会在写文件前被挡住。

调用图:调用 1 个内部函数(blocking_append_network_rule);外部调用 2 个(assert_eq!, tempdir)。

core/src/network_proxy_loader.rs源码 ↗
orchestrationstartup 和 config reload

网络代理要决定哪些网站能访问、哪些要拦、是否允许本地代理、是否启用中间人检查等。如果这些规则散落在系统配置、用户配置、项目配置和执行策略里,代理自己直接读会很乱,也容易漏掉安全限制。这个文件就像一个“配置总装车间”:先找到 Codex 的配置目录,加载所有配置层;再把它们按优先级合并;然后把权限配置和执行策略里的网络规则一起变成 NetworkProxyConfig,也就是代理真正能用的配置。同时,它会把受信任配置层里的限制单独提出来,确保用户或项目配置不能突破管理员设下的边界。最后,它用文件修改时间做一个简单的“哨兵”:保存每个配置文件上次的修改时间,运行中一旦发现文件变新、消失或重新出现,就重新构建网络代理状态。

函数细节23
build_network_proxy_state41–44 ↗
async fn build_network_proxy_state() -> Result<NetworkProxyState>

作用:这是给外部最常用的入口:直接拿到一个带自动重载能力的网络代理状态。调用它的人不用关心配置怎么读、重载器怎么装,只要拿结果用就行。

数据流:进去没有业务参数,只读取当前机器上的 Codex 配置和策略文件 → 它先让 build_network_proxy_state_and_reloader 做出配置状态和重载器,再把两者装进 NetworkProxyState → 出来的是一个网络代理运行状态,里面已经带着以后检查配置变更的能力。

调用关系:它是本文件对外组装完整网络代理状态的门面。它把主要工作交给 build_network_proxy_state_and_reloader,最后调用 with_reloader 把状态和 MtimeConfigReloader 绑在一起。

调用图:调用 2 个内部函数(build_network_proxy_state_and_reloader, with_reloader);外部调用 1 个(new)。

build_network_proxy_state_and_reloader46–50 ↗
async fn build_network_proxy_state_and_reloader() -> Result<(ConfigState, MtimeConfigReloader)>

作用:这个函数同时做两件事:构建当前可用的代理配置状态,并准备一个以后能重新加载配置的重载器。适合那些既想拿到当前配置、又想自己控制怎么接入重载机制的地方使用。

数据流:进去没有额外参数 → 它调用 build_config_state_with_mtimes 读配置并记录每个配置层文件的修改时间 → 出来是一对结果:当前 ConfigState,以及基于这些修改时间创建的 MtimeConfigReloader。

调用关系:build_network_proxy_state 会调用它来完成底层准备。它自己不解析配置细节,而是把重活交给 build_config_state_with_mtimes,然后用 MtimeConfigReloader::new 包成可重载对象。

调用图:调用 2 个内部函数(new, build_config_state_with_mtimes);被 1 处调用(build_network_proxy_state)。

build_config_state_with_mtimes52–87 ↗
async fn build_config_state_with_mtimes() -> Result<(ConfigState, Vec<LayerMtime>)>

作用:这是本文件的核心装配函数:从磁盘上的 Codex 配置和执行策略,构建网络代理真正要用的 ConfigState。它还顺手记录配置文件的修改时间,方便以后判断要不要重载。

数据流:进去没有业务参数,但会读取 CODEX_HOME、配置层、执行策略文件和各配置文件元数据 → 它加载配置层,尝试加载执行策略;如果执行策略只是解析失败,会记录警告并用空策略继续;然后合并网络配置、检查受信任限制、收集修改时间,并调用底层 build_config_state 生成运行状态 → 出来是 ConfigState 和一组 LayerMtime。

调用关系:启动时由 build_network_proxy_state_and_reloader 调用,运行中重载时由 MtimeConfigReloader::maybe_reload 和 MtimeConfigReloader::reload_now 再次调用。它把配置合并交给 config_from_layers,把安全边界检查交给 enforce_trusted_constraints,把文件时间收集交给 collect_layer_mtimes。

调用图:调用 6 个内部函数(load_config_layers_state, find_codex_home, load_exec_policy, collect_layer_mtimes, config_from_layers, enforce_trusted_constraints);被 3 处调用(maybe_reload, reload_now, build_network_proxy_state_and_reloader);外部调用 5 个(new, build_config_state, default, empty, warn!)。

collect_layer_mtimes89–109 ↗
fn collect_layer_mtimes(stack: &ConfigLayerStack) -> Vec<LayerMtime>

作用:这个函数找出每个有效配置层背后的配置文件,并记下它们当前的修改时间。这样系统以后能知道配置有没有被人改过。

数据流:进去是已经加载好的 ConfigLayerStack → 它按从低优先级到高优先级取出启用的配置层,只挑出有真实文件路径的层,比如系统、用户、项目和旧式托管配置 → 出来是一组 LayerMtime,每个都包含文件路径和当时读到的修改时间。

调用关系:build_config_state_with_mtimes 在完成配置构建时调用它。它生成的数据会交给 MtimeConfigReloader,用来做后续“文件有没有变”的判断。

调用图:调用 1 个内部函数(get_layers);被 1 处调用(build_config_state_with_mtimes)。

enforce_trusted_constraints111–120 ↗
fn enforce_trusted_constraints(
    layers: &ConfigLayerStack,
    config: &NetworkProxyConfig,
) -> Result<NetworkProxyConstraints>

作用:这个函数负责确认最终网络配置没有违反受信任配置层设下的限制。简单说,就是防止用户配置把管理员或系统配置规定的安全边界绕过去。

数据流:进去是全部配置层和已经合并出的 NetworkProxyConfig → 它先从受信任配置层提取 NetworkProxyConstraints,再用 validate_policy_against_constraints 检查当前配置是否符合这些限制 → 如果符合,出来的是这些限制;如果不符合,出来的是带上下文的错误。

调用关系:build_config_state_with_mtimes 在真正生成代理状态前会调用它。它把限制提取交给 network_constraints_from_trusted_layers,把最终校验交给网络代理库里的验证函数。

调用图:调用 1 个内部函数(network_constraints_from_trusted_layers);被 1 处调用(build_config_state_with_mtimes);外部调用 1 个(validate_policy_against_constraints)。

network_constraints_from_trusted_layers122–143 ↗
fn network_constraints_from_trusted_layers(
    layers: &ConfigLayerStack,
) -> Result<NetworkProxyConstraints>

作用:这个函数只看“用户不能随便改”的配置层,从里面提取网络代理的硬限制。它的作用是把管理员级别的网络要求变成一份可检查的约束清单。

数据流:进去是配置层堆栈 → 它按优先级合并非用户控制的配置层,跳过用户、项目和会话参数这些用户可控来源;然后把合并后的 TOML 配置转成结构化表格,找出当前权限档案对应的网络配置,并把里面的限制写入 NetworkProxyConstraints → 出来是一份网络代理约束。

调用关系:它由 enforce_trusted_constraints 调用。它会用 is_user_controlled_layer 判断哪些层要跳过,用 network_tables_from_toml 解析配置,用 selected_network_from_tables 找到选中的网络配置,再用 apply_network_constraints 把配置内容转成约束。

调用图:调用 5 个内部函数(get_layers, apply_network_constraints, is_user_controlled_layer, network_tables_from_toml, selected_network_from_tables);被 1 处调用(enforce_trusted_constraints);外部调用 4 个(merge_toml_values, default, Table, new)。

apply_network_constraints145–182 ↗
fn apply_network_constraints(network: NetworkToml, constraints: &mut NetworkProxyConstraints)

作用:这个函数把一段网络配置里的可限制项,逐项拷贝到约束对象里。它关心的是“最高规则允许什么”,不是普通用户想怎么配。

数据流:进去是一份 NetworkToml 网络配置和一个可修改的 NetworkProxyConstraints → 它检查 enabled、mode、是否允许上游代理、Unix socket、本地绑定、域名允许/拒绝等字段;有写明的就放进约束,没有写明的就不动 → 出来没有单独返回值,但 constraints 被补上了这些限制。

调用关系:network_constraints_from_trusted_layers 找到受信任网络配置后会调用它。遇到域名规则时,它借助 overlay_network_domain_permissions 复用正常配置合并逻辑,避免约束里的域名规则和普通配置规则解释不一致。

调用图:调用 1 个内部函数(overlay_network_domain_permissions);被 1 处调用(network_constraints_from_trusted_layers);外部调用 1 个(default)。

network_tables_from_toml190–195 ↗
fn network_tables_from_toml(value: &toml::Value) -> Result<NetworkTablesToml>

作用:这个函数把泛用的 TOML 配置值,转换成这里只关心的网络权限表结构。TOML 可以理解成一种常见的配置文件格式。

数据流:进去是一个 toml::Value,也就是已经读出的配置内容 → 它复制一份并尝试反序列化,反序列化就是把松散的配置文本变成有字段名的 Rust 结构 → 出来是 NetworkTablesToml;如果字段形状不对,就返回带说明的错误。

调用关系:config_from_layers 和 network_constraints_from_trusted_layers 都会调用它。前者用它解析完整配置,后者用它解析受信任层合并后的限制配置。

调用图:被 2 处调用(config_from_layers, network_constraints_from_trusted_layers);外部调用 1 个(clone)。

selected_network_from_tables197–212 ↗
fn selected_network_from_tables(parsed: NetworkTablesToml) -> Result<Option<NetworkToml>>

作用:这个函数根据 default_permissions 选出真正生效的网络配置。它解决的问题是:配置文件里可能有多个权限档案,必须知道当前默认用哪一个。

数据流:进去是 NetworkTablesToml,里面可能有 default_permissions 和 permissions 表 → 如果没有默认权限,返回没有网络配置;如果默认权限是内置档案,也返回没有自定义网络配置;如果名字像内置但不认识,会报错;否则从 permissions 表里解析指定档案 → 出来是这个档案里的 NetworkToml,或者 None。

调用关系:普通配置合并、约束提取和测试辅助都会用它。它依赖权限档案相关函数判断内置名字、拒绝未知内置名字,并解析自定义权限档案。

调用图:被 3 处调用(apply_network_tables, apply_network_tables, network_constraints_from_trusted_layers);外部调用 3 个(is_builtin_permission_profile_name, reject_unknown_builtin_permission_profile, resolve_permission_profile)。

apply_network_tables215–220 ↗
fn apply_network_tables(config: &mut NetworkProxyConfig, parsed: NetworkTablesToml) -> Result<()>

作用:这是测试用的辅助函数,用来把解析好的网络权限表直接应用到 NetworkProxyConfig 上。它让测试可以绕过整套配置加载流程,只验证网络表如何生效。

数据流:进去是一个可修改的 NetworkProxyConfig 和解析好的 NetworkTablesToml → 它先用 selected_network_from_tables 找到当前应选的网络配置;如果找到了,就把这段配置应用到代理配置上 → 出来没有单独结果,传入的 config 会被改动;出错时返回错误。

调用关系:这个函数只在测试编译时存在。它调用 selected_network_from_tables,目的是让测试专注于“选出的网络配置是否正确应用”。

调用图:调用 1 个内部函数(selected_network_from_tables)。

NetworkConfigAccumulator::apply_network_tables230–235 ↗
fn apply_network_tables(&mut self, parsed: NetworkTablesToml) -> Result<()>

作用:这个方法把一组网络权限表加到累积器里。累积器像一个临时篮子,用来一边合并普通网络设置,一边收集中间人检查的 hook 和 action。

数据流:进去是累积器自身和 NetworkTablesToml → 它先选出当前默认权限档案里的网络配置;如果有,就交给 apply_network 继续合并 → 出来没有单独返回值,但累积器内部的 config、mitm_hooks 和 mitm_actions 可能被更新。

调用关系:config_from_layers 创建累积器后会使用这一类方法来合并配置。它本身不处理每个字段,而是把真正应用网络配置的工作交给 NetworkConfigAccumulator::apply_network。

调用图:调用 2 个内部函数(apply_network, selected_network_from_tables)。

NetworkConfigAccumulator::apply_network237–249 ↗
fn apply_network(&mut self, mut network: NetworkToml)

作用:这个方法把一段 NetworkToml 网络配置合进累积器。特别重要的是,它会把中间人检查相关的 hook 和 action 先单独收起来,等最后统一校验。

数据流:进去是累积器和一段 NetworkToml → 它先取出 mitm 配置,避免普通应用过程把它直接消耗掉;然后把常规网络设置写入 NetworkProxyConfig;如果有 mitm actions 或 hooks,就追加到累积器保存的有序表里 → 出来没有单独返回值,但累积器内容变多了。

调用关系:NetworkConfigAccumulator::apply_network_tables 选出网络配置后会调用它。它把普通网络字段交给 NetworkToml 自己的 apply_to_network_proxy_config,自己重点处理需要跨配置层合并和最后校验的 mitm 部分。

调用图:调用 1 个内部函数(apply_to_network_proxy_config);被 1 处调用(apply_network_tables);外部调用 1 个(extend)。

NetworkConfigAccumulator::finish251–266 ↗
fn finish(mut self) -> Result<NetworkProxyConfig>

作用:这个方法把累积器收尾,变成最终的 NetworkProxyConfig。它会校验中间人检查 hook 引用的 action 是否真的存在,避免运行时才发现配置断了。

数据流:进去是已经累积了一批网络设置、mitm_hooks 和 mitm_actions 的累积器 → 如果存在 hook,就把 hook 和 action 组成 NetworkMitmToml,检查 hook 引用是否合法,再转换成运行时可用的 hook;最后根据网络模式或 hook 是否存在决定是否开启 mitm 标记 → 出来是完整的 NetworkProxyConfig,或者配置错误。

调用关系:config_from_layers 在合并完网络表后调用它拿最终配置。它是累积器流程的最后一步,负责把前面暂存的 mitm 配置变成代理运行时真正会用的形式。

调用图:外部调用 1 个(is_empty)。

config_from_layers269–286 ↗
fn config_from_layers(
    layers: &ConfigLayerStack,
    exec_policy: &codex_execpolicy::Policy,
) -> Result<NetworkProxyConfig>

作用:这个函数把所有启用的配置层合并成一份 NetworkProxyConfig,并把执行策略里的网络规则也加进去。它是“配置文件内容”变成“网络代理配置”的主要通道。

数据流:进去是 ConfigLayerStack 和执行策略 Policy → 它按从低到高的优先级合并所有启用配置层,解析出网络权限表,用 NetworkConfigAccumulator 应用并收尾,然后把执行策略里允许和拒绝的域名写入配置 → 出来是一份 NetworkProxyConfig。

调用关系:build_config_state_with_mtimes 在构建代理状态时调用它。它内部依次使用 network_tables_from_toml、NetworkConfigAccumulator 的流程,以及 apply_exec_policy_network_rules。

调用图:调用 3 个内部函数(get_layers, apply_exec_policy_network_rules, network_tables_from_toml);被 1 处调用(build_config_state_with_mtimes);外部调用 4 个(merge_toml_values, default, Table, new)。

apply_exec_policy_network_rules288–307 ↗
fn apply_exec_policy_network_rules(
    config: &mut NetworkProxyConfig,
    exec_policy: &codex_execpolicy::Policy,
)

作用:这个函数把执行策略里的网络域名规则补进网络代理配置。执行策略可以理解成对程序能做什么的额外安全规则。

数据流:进去是可修改的 NetworkProxyConfig 和执行策略 Policy → 它从策略里取出允许访问的域名和禁止访问的域名;允许的逐个写成 Allow,禁止的逐个写成 Deny → 出来没有单独返回值,但 config 的域名权限表被更新。

调用关系:config_from_layers 在普通配置合并完成后调用它。它不直接操作底层表,而是把每个域名交给 upsert_network_domain,保证新增或覆盖域名权限的方式一致。

调用图:调用 1 个内部函数(upsert_network_domain);被 1 处调用(config_from_layers);外部调用 1 个(compiled_network_domains)。

upsert_network_domain309–317 ↗
fn upsert_network_domain(
    config: &mut NetworkProxyConfig,
    host: String,
    permission: codex_network_proxy::NetworkDomainPermission,
)

作用:这个函数给网络配置添加或更新一个域名权限。upsert 的意思是“有就更新,没有就插入”。

数据流:进去是可修改的 NetworkProxyConfig、一个域名字符串和权限类型,比如允许或拒绝 → 它把域名交给 normalize_host 规范化,再写入网络配置的域名权限表 → 出来没有单独返回值,但对应域名的权限已经被设置好。

调用关系:apply_exec_policy_network_rules 会为执行策略里的每个允许或拒绝域名调用它。它是执行策略域名规则落到代理配置里的最后一小步。

调用图:被 1 处调用(apply_exec_policy_network_rules)。

is_user_controlled_layer319–326 ↗
fn is_user_controlled_layer(layer: &ConfigLayerSource) -> bool

作用:这个函数判断一个配置层是不是用户能控制的来源。它用来区分“可以被用户改的设置”和“应该被当作安全边界的设置”。

数据流:进去是一个 ConfigLayerSource,也就是配置来自哪里 → 它检查来源是否是用户配置、项目配置或会话参数 → 出来是 true 或 false,true 表示用户可控。

调用关系:network_constraints_from_trusted_layers 用它来跳过用户可控层。这样提取安全约束时,只会看系统级、托管级这类更可信的配置来源。

调用图:被 1 处调用(network_constraints_from_trusted_layers);外部调用 1 个(matches!)。

LayerMtime::new335–338 ↗
fn new(path: AbsolutePathBuf) -> Self

作用:这个构造函数给某个配置文件路径拍一张“修改时间快照”。之后系统就能拿新时间和旧时间比,判断文件有没有变。

数据流:进去是一个绝对路径 AbsolutePathBuf → 它尝试读取文件元数据,再取出文件最后修改时间;如果文件不存在或读取失败,就把时间记为 None → 出来是一个 LayerMtime,里面有路径和当时的修改时间。

调用关系:collect_layer_mtimes 会为每个配置文件路径调用它。生成的 LayerMtime 会被 MtimeConfigReloader 保存,用于之后的重载检查。

调用图:外部调用 1 个(metadata)。

MtimeConfigReloader::new346–350 ↗
fn new(layer_mtimes: Vec<LayerMtime>) -> Self

作用:这个构造函数创建一个基于文件修改时间的配置重载器。它保存最初那批配置文件的时间快照。

数据流:进去是一组 LayerMtime → 它把这组快照放进 RwLock,RwLock 是读写锁,意思是很多地方可以同时读取,但修改时要独占,避免并发混乱 → 出来是 MtimeConfigReloader。

调用关系:build_network_proxy_state_and_reloader 在初次构建配置状态后调用它。之后网络代理会通过这个重载器判断是否需要刷新配置。

调用图:被 1 处调用(build_network_proxy_state_and_reloader);外部调用 1 个(new)。

MtimeConfigReloader::needs_reload352–363 ↗
async fn needs_reload(&self) -> bool

作用:这个方法检查配置文件是否发生过变化。只要文件变新、原来没有现在有了、或者原来有现在没了,它就认为需要重载。

数据流:进去是重载器自身保存的文件时间快照 → 它读取锁里的 LayerMtime 列表,逐个查看当前文件元数据和修改时间,再和旧时间比较 → 出来是布尔值:true 表示应该重新加载配置,false 表示暂时不用。

调用关系:MtimeConfigReloader::maybe_reload 会先调用它。它只负责判断,不负责真正重新读取配置。

调用图:被 1 处调用(maybe_reload)。

MtimeConfigReloader::source_label385–387 ↗
fn source_label(&self) -> String

作用:这个方法给重载器提供一个人能看懂的来源名称。日志或状态信息里可以用它说明配置来自哪里。

数据流:进去是重载器自身,但不读取具体配置内容 → 它直接返回固定文字“config layers” → 出来是一个字符串。

调用关系:它实现了 ConfigReloader 接口的一部分。网络代理框架需要统一询问不同重载器的来源名称时,会调用它。

MtimeConfigReloader::maybe_reload389–391 ↗
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>>

作用:这个方法按需重载配置:只有发现配置文件变了,才重新构建网络代理状态。这样可以避免每次检查都做昂贵的完整加载。

数据流:进去是重载器自身 → 它先调用 needs_reload 看文件时间有没有变化;如果没变,返回 None;如果变了,就重新运行 build_config_state_with_mtimes,拿到新状态和新时间快照,并更新内部保存的快照 → 出来是 Some(ConfigState) 表示有新配置,或 None 表示无需更新。

调用关系:它是 ConfigReloader 接口里“有变化才刷新”的实现。网络代理运行中想轻量检查配置更新时会走这里;真正重建配置的活仍交给 build_config_state_with_mtimes。

调用图:调用 2 个内部函数(needs_reload, build_config_state_with_mtimes);外部调用 1 个(pin)。

MtimeConfigReloader::reload_now393–395 ↗
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState>

作用:这个方法强制立刻重载配置,不管文件修改时间有没有变化。适合用户明确要求刷新,或者上层系统想无条件重建状态的时候。

数据流:进去是重载器自身 → 它直接调用 build_config_state_with_mtimes 重新读取全部配置和策略,生成新的 ConfigState 与时间快照,然后更新内部保存的快照 → 出来是新的 ConfigState,出错则返回错误。

调用关系:它实现了 ConfigReloader 接口里的强制刷新能力。和 maybe_reload 不同,它跳过 needs_reload 判断,直接把工作交给 build_config_state_with_mtimes。

调用图:调用 1 个内部函数(build_config_state_with_mtimes);外部调用 1 个(pin)。

需求架构和层准备

这些文件建立需求模型,并准备各个需求来源以供后续组合。

config/src/requirements_layers/mod.rs源码 ↗
configconfig load / cross-cutting

可以把这个文件想成一个文件夹前台。真正干活的代码分散在 hooks、layer、permissions、rules、stack 这些子文件里,分别处理钩子、单层需求、权限、规则、以及多层需求怎么叠起来。这个文件本身不写具体规则,也不计算结果;它的作用是告诉 Rust 编译器“这些子模块属于这一组”,并把两个最常用的东西拿出来给外部使用:RequirementsLayerEntry 表示一条需求层里的配置项,compose_requirements 用来把多层需求合成最终结果。没有它,外面的代码就得知道内部文件怎么分布,引用会更乱,也更容易因为内部结构调整而坏掉。

config/src/config_requirements.rs源码 ↗
configconfig load

可以把这个文件理解成配置系统里的“规章制度翻译器”。管理员可能会写 TOML 配置文件,也可能通过 MDM(移动设备管理,企业远程管电脑的方式)或云端下发规则,比如只能用只读沙箱、不能联网、某些工具必须审批、某些文件路径不许读。这个文件先描述这些规则长什么样,再记录规则来自哪里,方便报错时告诉用户“是谁不让你这么做”。接着它会合并多层规则:高优先级的先算,低优先级的只能补空缺;应用开关比较特殊,只要任一层明确禁用,就保持禁用。最后它把原始 TOML 变成 ConfigRequirements 这种运行时对象,里面的字段会带“约束”,也就是以后有人想改配置时,可以立刻判断是否越界。文件底部还有大量测试,确保旧格式兼容、新旧网络写法不能混用、空列表该拒绝、来源信息不会丢。

函数细节118
RequirementSource::composite53–64 ↗
fn composite(sources: impl IntoIterator<Item = RequirementSource>) -> Self

作用:把多个规则来源合成一个来源说明,方便报错时一次说清楚“这些限制来自哪些地方”。如果只有一个来源,就不额外包装。

数据流:进去的是一串 RequirementSource → 它会展开里面已经是组合来源的项,并去掉重复项 → 出来的是 Unknown、单个来源,或一个按优先级排列的 Composite 来源。

调用关系:它会把具体展开工作交给 RequirementSource::append_to_composite。配置合并、应用限制和测试在需要描述多层规则来源时会用它。

调用图:被 4 处调用(constraint_error_includes_composite_requirement_source, apply_to, merge_output_source, source_for_top_level_keys);外部调用 1 个(new)。

RequirementSource::append_to_composite66–79 ↗
fn append_to_composite(self, flattened: &mut Vec<RequirementSource>)

作用:这是合并来源时用的小助手,负责把嵌套的来源摊平成一层,并避免重复。

数据流:进去的是一个来源和一个正在收集的列表 → 如果来源本身是组合,就逐个拆开;否则检查列表里有没有,没有才加入 → 改动的是传入的列表,没有单独返回值。

调用关系:它主要服务 RequirementSource::composite,是组合来源去重和展平的实际干活者。

RequirementSource::fmt83–112 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把规则来源变成普通人能读的文字,比如文件路径、MDM 键名、企业规则名称。

数据流:进去的是一个 RequirementSource → 根据具体类型拼出一段说明文字 → 写到格式化输出里,供错误消息或日志显示。

调用关系:当 ConstraintError 等错误需要展示来源时,Rust 的格式化系统会调用它。

调用图:外部调用 1 个(write!)。

ConstrainedWithSource::new122–124 ↗
fn new(value: Constrained<T>, source: Option<RequirementSource>) -> Self

作用:把一个“带限制的值”和它的来源绑在一起。这样以后这个值被拒绝修改时,能说明是哪条规则限制了它。

数据流:进去的是 Constrained<T> 和可选来源 → 它们被放进 ConstrainedWithSource 结构 → 出来的是一个同时保存值、限制和来源的新对象。

调用关系:ConfigRequirements::default 和 ConfigRequirements::try_from 会大量使用它来生成最终配置约束。

调用图:被 17 处调用(resolve_allowed_windows_sandbox_setup_mode_rejects_disallowed_mode, default, try_from, test_requirements_web_search_mode_allowlist_does_not_warn_when_unset, default, from, from_configured_with_optional_warnings, requirements_managed_hooks_execute_from_managed_dir, requirements_managed_hooks_execute_windows_command_override, requirements_managed_hooks_load_when_managed_dir_is_missing (+7 more))。

ConstrainedWithSource::deref130–132 ↗
fn deref(&self) -> &Self::Target

作用:让 ConstrainedWithSource 用起来像里面的 Constrained 值一样,少写一层 .value。

数据流:进去的是对包装对象的引用 → 返回里面 Constrained 的引用 → 不改动任何数据。

调用关系:这是 Rust 的 Deref 机制,调用方读取约束值或调用约束方法时会自动用到。

ConstrainedWithSource::deref_mut136–138 ↗
fn deref_mut(&mut self) -> &mut Self::Target

作用:让外部可以像操作内部 Constrained 一样修改它,同时仍保留来源信息。

数据流:进去的是对包装对象的可变引用 → 返回内部 Constrained 的可变引用 → 后续修改会作用在包装对象内部。

调用关系:当代码需要更新受约束的配置值时,Rust 会通过这个方法把操作转到内部对象上。

ConfigRequirements::default169–208 ↗
fn default() -> Self

作用:提供一套“没有管理员限制时”的默认要求。默认情况下,大多数东西允许用户选择,权限配置从只读起步。

数据流:没有输入 → 创建各个字段的默认 ConstrainedWithSource、空的可选项和默认 Web 搜索模式 → 返回完整的 ConfigRequirements。

调用关系:配置加载没有发现要求时会用它;它调用 ConstrainedWithSource::new、Constrained::allow_any 等方法来搭默认约束。

调用图:调用 4 个内部函数(new, allow_any, allow_any_from_default, read_only)。

ConfigRequirements::exec_policy_source212–214 ↗
fn exec_policy_source(&self) -> Option<&RequirementSource>

作用:取出命令执行规则来自哪里,便于提示用户某条命令策略是谁下发的。

数据流:进去的是当前 ConfigRequirements → 如果有执行策略,就返回它的来源引用;没有就返回 None → 不改动配置。

调用关系:其他模块在展示或解释执行策略时会调用它。

PluginRequirementsToml::is_empty235–237 ↗
fn is_empty(&self) -> bool

作用:判断一个插件要求是不是实际上什么都没写。

数据流:进去的是插件要求 → 检查 mcp_servers 是否不存在或为空 → 返回 true 或 false。

调用关系:ConfigRequirementsToml::is_empty 会用它判断整个配置是否为空。

NetworkDomainPermissionsToml::is_empty247–249 ↗
fn is_empty(&self) -> bool

作用:判断网络域名允许/禁止列表是不是空的。

数据流:进去的是域名权限表 → 查看内部 map 有没有条目 → 返回是否为空。

调用关系:网络配置检查和测试会用它快速判断有没有域名规则。

NetworkDomainPermissionsToml::allowed_domains251–259 ↗
fn allowed_domains(&self) -> Option<Vec<String>>

作用:从域名权限表里挑出明确允许访问的域名模式。

数据流:进去的是 allow/deny 混合表 → 过滤出权限为 Allow 的项并复制域名字符串 → 如果有结果返回列表,没有则返回 None。

调用关系:旧接口或展示层需要单独的允许列表时会用它。

NetworkDomainPermissionsToml::denied_domains261–269 ↗
fn denied_domains(&self) -> Option<Vec<String>>

作用:从域名权限表里挑出明确禁止访问的域名模式。

数据流:进去的是 allow/deny 混合表 → 过滤出权限为 Deny 的项并复制域名字符串 → 如果有结果返回列表,没有则返回 None。

调用关系:旧接口或展示层需要单独的禁止列表时会用它。

NetworkDomainPermissionToml::fmt280–286 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把域名权限枚举显示成配置里常见的 allow 或 deny。

数据流:进去的是 Allow 或 Deny → 转成对应小写字符串 → 写到格式化输出。

调用关系:日志、错误消息或序列化周边展示这个权限时会用到。

调用图:外部调用 1 个(write_str)。

NetworkUnixSocketPermissionsToml::is_empty296–298 ↗
fn is_empty(&self) -> bool

作用:判断 Unix socket 权限表是不是空的。Unix socket 可以理解成机器内部程序之间通信用的本地接口。

数据流:进去的是 socket 权限表 → 检查内部条目数量 → 返回是否为空。

调用关系:网络配置逻辑和测试用它判断有没有本地 socket 规则。

NetworkUnixSocketPermissionsToml::allow_unix_sockets300–306 ↗
fn allow_unix_sockets(&self) -> Vec<String>

作用:从 Unix socket 权限表里取出允许连接的路径列表。

数据流:进去的是 allow/deny 混合表 → 过滤出 Allow 的路径 → 返回字符串列表,禁止项会被忽略。

调用关系:兼容旧配置或给其他模块传允许列表时会用它。

NetworkUnixSocketPermissionToml::fmt317–323 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把 Unix socket 权限显示成 allow 或 deny。

数据流:进去的是权限枚举 → 转成小写字符串 → 写到格式化输出。

调用关系:用于错误信息、日志或调试输出。

调用图:外部调用 1 个(write_str)。

NetworkRequirementsToml::deserialize365–412 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:读取网络要求配置,并把旧写法转换成新的统一写法。它还会防止新旧两套写法混在一起造成歧义。

数据流:进去的是 TOML 反序列化器 → 先读成 RawNetworkRequirementsToml,检查 domains 不能和 allowed_domains/denied_domains 混用,unix_sockets 不能和 allow_unix_sockets 混用 → 返回规范化后的 NetworkRequirementsToml 或报错。

调用关系:serde 读取 requirements.toml 中 experimental_network 时会调用它;它会用 legacy_domain_permissions_from_lists 和 legacy_unix_socket_permissions_from_list 兼容老字段。

调用图:被 1 处调用(deserialize);外部调用 2 个(custom, deserialize)。

legacy_domain_permissions_from_lists418–433 ↗
fn legacy_domain_permissions_from_lists(
    allowed_domains: Option<Vec<String>>,
    denied_domains: Option<Vec<String>>,
) -> Option<NetworkDomainPermissionsToml>

作用:把旧版 allowed_domains 和 denied_domains 两个列表,转换成新版“域名到 allow/deny”的表。

数据流:进去的是两个可选字符串列表 → 允许列表里的模式标为 Allow,禁止列表里的模式标为 Deny,后写入的同名项会覆盖前面的 → 如果最终表非空就返回它,否则返回 None。

调用关系:NetworkRequirementsToml::deserialize 在读到旧字段时会调用它。

调用图:外部调用 1 个(new)。

legacy_unix_socket_permissions_from_list435–445 ↗
fn legacy_unix_socket_permissions_from_list(
    allow_unix_sockets: Option<Vec<String>>,
) -> Option<NetworkUnixSocketPermissionsToml>

作用:把旧版 allow_unix_sockets 列表转换成新版 socket 权限表。

数据流:进去的是可选路径列表 → 每个路径都标为 Allow → 如果列表非空返回权限表,否则返回 None。

调用关系:NetworkRequirementsToml::deserialize 用它兼容旧配置。

NetworkConstraints::deserialize465–471 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:让运行时网络约束也能直接从 TOML 读取,同时复用网络要求的兼容和校验规则。

数据流:进去的是反序列化器 → 先读成 NetworkRequirementsToml → 再转换成 NetworkConstraints 返回。

调用关系:serde 读取 NetworkConstraints 时调用它,实际解析交给 NetworkRequirementsToml::deserialize。

调用图:调用 1 个内部函数(deserialize)。

NetworkConstraints::from475–500 ↗
fn from(value: NetworkRequirementsToml) -> Self

作用:把配置文件里的网络要求,搬到运行时使用的网络约束结构里。

数据流:进去的是 NetworkRequirementsToml → 拆出所有字段再原样放入 NetworkConstraints → 返回新的约束对象。

调用关系:ConfigRequirements::try_from 和 NetworkConstraints::deserialize 都依赖这个转换。

FilesystemRequirementsToml::deserialize519–545 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:读取文件系统限制,并禁止把 permissions.filesystem 错当成普通权限 profile 来写。

数据流:进去的是 TOML 反序列化器 → 读取 deny_read 和几个保留字段 → 如果出现 description、extends 等 profile 字段就报错,否则返回只包含 deny_read 的对象。

调用关系:serde 读取 permissions.filesystem 时会调用它,确保该表只用于要求级别的文件限制。

调用图:外部调用 2 个(custom, deserialize)。

FilesystemConstraints::from563–569 ↗
fn from(value: PermissionsRequirementsToml) -> Self

作用:从权限配置里抽出真正的文件系统约束,目前主要是不允许读取哪些路径。

数据流:进去的是 PermissionsRequirementsToml → 找到 filesystem.deny_read,没有就用空列表 → 返回 FilesystemConstraints。

调用关系:ConfigRequirements::try_from 在把 TOML 要求变成运行时要求时会用它。

FilesystemDenyReadPattern::as_str577–579 ↗
fn as_str(&self) -> &str

作用:取出禁止读取路径模式的原始字符串。

数据流:进去的是模式对象 → 返回内部字符串切片 → 不做复制也不修改。

调用关系:其他模块需要拿这个模式去匹配路径或展示时会用它。

FilesystemDenyReadPattern::contains_glob581–583 ↗
fn contains_glob(&self) -> bool

作用:判断这个禁止读取规则是不是包含通配符。通配符就是 *、?、[ 这类可匹配一批文件名的符号。

数据流:进去的是模式对象 → 扫描字符串里的字符 → 只要发现通配符字符就返回 true,否则 false。

调用关系:文件访问检查或测试可用它区分普通绝对路径和模式路径。

FilesystemDenyReadPattern::from_input585–606 ↗
fn from_input(input: &str) -> Result<Self, String>

作用:把用户写的禁止读取路径整理成标准形式,既支持普通路径,也支持带通配符的路径模式。

数据流:进去的是输入字符串 → 如果没有通配符,就按绝对路径规则解析;如果有通配符,就先把通配符前面的目录部分转成绝对路径,再拼回后缀 → 返回规范化后的 FilesystemDenyReadPattern 或错误文字。

调用关系:FilesystemDenyReadPattern::deserialize 会调用它;它内部依赖 deserialize_absolute_path 和 split_glob_pattern。

调用图:调用 2 个内部函数(deserialize_absolute_path, split_glob_pattern);外部调用 1 个(format!)。

FilesystemDenyReadPattern::from610–612 ↗
fn from(value: AbsolutePathBuf) -> Self

作用:把一个已经确认是绝对路径的对象包装成禁止读取模式。

数据流:进去的是 AbsolutePathBuf → 转成字符串 → 返回 FilesystemDenyReadPattern。

调用关系:测试和其他代码在已有绝对路径时用这个转换,避免再解析字符串。

调用图:调用 1 个内部函数(to_string_lossy)。

FilesystemDenyReadPattern::deserialize616–622 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:让 TOML 里的字符串可以直接读成禁止读取路径模式,并在读取时完成校验。

数据流:进去的是反序列化器 → 先读出字符串 → 调用 from_input 规范化;成功返回模式,失败转成 TOML 解析错误。

调用关系:serde 读取 deny_read 数组里的每一项时会调用它。

调用图:外部调用 2 个(from_input, deserialize)。

deserialize_absolute_path625–628 ↗
fn deserialize_absolute_path(input: &str) -> Result<AbsolutePathBuf, String>

作用:把字符串按项目的绝对路径规则解析出来。

数据流:进去的是路径字符串 → 用 AbsolutePathBuf 的反序列化逻辑检查并转换 → 成功返回绝对路径,失败返回错误字符串。

调用关系:FilesystemDenyReadPattern::from_input 用它规范化普通路径和通配符前缀。

调用图:调用 1 个内部函数(deserialize);被 1 处调用(from_input);外部调用 1 个(new)。

split_glob_pattern630–653 ↗
fn split_glob_pattern(input: &str) -> (&str, &str)

作用:把带通配符的路径拆成“需要先规范化的目录前缀”和“通配符后缀”。

数据流:进去的是路径模式字符串 → 找到第一个通配符,再往前找路径分隔符 → 返回目录前缀和后缀两个字符串切片。

调用关系:FilesystemDenyReadPattern::from_input 用它保证通配符前面的目录仍按绝对路径处理。

调用图:被 1 处调用(from_input);外部调用 1 个(cfg!)。

is_path_separator655–661 ↗
fn is_path_separator(ch: char) -> bool

作用:判断一个字符是不是当前系统的路径分隔符。

数据流:进去的是字符 → 在 Windows 上接受 / 和 \,其他系统只接受 / → 返回 true 或 false。

调用关系:split_glob_pattern 用它寻找目录边界。

调用图:外部调用 1 个(cfg!)。

is_glob_metacharacter663–665 ↗
fn is_glob_metacharacter(ch: char) -> bool

作用:判断一个字符是不是通配符元字符,也就是会改变匹配含义的符号。

数据流:进去的是字符 → 检查是否为 *、? 或 [ → 返回布尔值。

调用关系:contains_glob、from_input 和 split_glob_pattern 都靠它识别通配符。

调用图:外部调用 1 个(matches!)。

WebSearchModeRequirement::from676–682 ↗
fn from(mode: WebSearchMode) -> Self

作用:把运行时的 Web 搜索模式转成 requirements.toml 里的要求枚举。

数据流:进去的是 WebSearchMode → 按 Disabled、Cached、Live 一一映射 → 返回 WebSearchModeRequirement。

调用关系:限制 Web 搜索模式时,用它在运行时类型和配置要求类型之间转换。

WebSearchMode::from686–692 ↗
fn from(mode: WebSearchModeRequirement) -> Self

作用:把配置要求里的 Web 搜索模式转成运行时真正使用的模式。

数据流:进去的是 WebSearchModeRequirement → 按 Disabled、Cached、Live 一一映射 → 返回 WebSearchMode。

调用关系:ConfigRequirements::try_from 在建立 Web 搜索约束和错误提示时会用这种转换。

WebSearchModeRequirement::fmt696–702 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把 Web 搜索要求显示成 disabled、cached 或 live 这样的配置文字。

数据流:进去的是模式枚举 → 选择对应小写字符串 → 写到格式化输出。

调用关系:错误信息和展示配置时会用到。

调用图:外部调用 1 个(write!)。

ComputerUseRequirementsToml::is_empty711–713 ↗
fn is_empty(&self) -> bool

作用:判断电脑使用相关要求是否没有实际内容。

数据流:进去的是 computer_use 配置 → 检查 allow_locked_computer_use 是否没设置 → 返回是否为空。

调用关系:ConfigRequirementsToml::is_empty 用它判断整体配置是不是空。

WindowsRequirementsToml::is_empty722–724 ↗
fn is_empty(&self) -> bool

作用:判断 Windows 专用要求是否没有实际内容。

数据流:进去的是 Windows 配置 → 检查 allowed_sandbox_implementations 是否没设置 → 返回是否为空。

调用关系:ConfigRequirementsToml::is_empty 用它过滤空配置。

FeatureRequirementsToml::is_empty734–736 ↗
fn is_empty(&self) -> bool

作用:判断功能开关要求表是不是空的。

数据流:进去的是功能名到布尔值的表 → 看是否没有条目 → 返回 true 或 false。

调用关系:ConfigRequirementsToml::is_empty 和 ConfigRequirements::try_from 会用它忽略空功能要求。

AppToolRequirementToml::is_empty745–747 ↗
fn is_empty(&self) -> bool

作用:判断某个应用工具的要求是否什么都没设置。

数据流:进去的是工具要求 → 检查 approval_mode 是否为空 → 返回是否为空。

调用关系:AppToolsRequirementsToml::is_empty 会逐个调用它。

AppToolsRequirementsToml::is_empty757–759 ↗
fn is_empty(&self) -> bool

作用:判断一个应用下所有工具要求是否都为空。

数据流:进去的是工具要求表 → 检查每个工具是否为空 → 全部为空才返回 true。

调用关系:AppRequirementToml::is_empty 用它判断应用要求是否有内容。

AppRequirementToml::is_empty769–775 ↗
fn is_empty(&self) -> bool

作用:判断某个应用的启用状态和工具审批要求是否都没写。

数据流:进去的是应用要求 → 检查 enabled 是否为空,并检查 tools 是否为空或不存在 → 返回是否为空。

调用关系:AppsRequirementsToml::is_empty 用它判断整个 apps 表是否有实际要求。

AppsRequirementsToml::is_empty785–787 ↗
fn is_empty(&self) -> bool

作用:判断所有应用要求是否都没有实际内容。

数据流:进去的是应用要求表 → 对每个应用调用 AppRequirementToml::is_empty → 全部为空才返回 true。

调用关系:ConfigRequirementsToml::is_empty 用它判断 apps 配置是否算空。

merge_app_requirements_descending793–819 ↗
fn merge_app_requirements_descending(
    base: &mut AppsRequirementsToml,
    incoming: AppsRequirementsToml,
)

作用:把低优先级来源的应用要求合并进高优先级结果里。重点规则是:禁用优先,只要哪层说禁用,就禁用。

数据流:进去的是可修改的 base 和 incoming → 逐个应用合并 enabled,false 会压过 true;工具审批模式则高优先级已有值就保留,缺失时才用低优先级补上 → base 被原地更新。

调用关系:ConfigRequirementsWithSources::merge_unset_fields 在合并 apps 字段时调用它;多项测试专门验证这个优先级规则。

调用图:被 8 处调用(merge_unset_fields, merge_app_requirements_descending_keeps_higher_true_when_lower_is_unset, merge_app_requirements_descending_prefers_false_from_lower_precedence, merge_app_requirements_descending_preserves_higher_false_when_lower_missing_app, merge_app_requirements_descending_preserves_higher_tool_approval_mode, merge_app_requirements_descending_unions_distinct_apps, merge_app_requirements_descending_uses_lower_tool_approval_when_higher_missing, merge_app_requirements_descending_uses_lower_value_when_higher_missing)。

Sourced::new865–867 ↗
fn new(value: T, source: RequirementSource) -> Self

作用:把一个配置值和“它从哪里来”绑在一起。

数据流:进去的是值和 RequirementSource → 生成 Sourced<T> → 返回带来源的新对象。

调用关系:合并配置、转换运行时要求和其他配置模块都会用它保存来源信息。

调用图:被 21 处调用(try_from, merge_unset_fields, merge, apply_to, merge, populate_merged_regular_fields_with_sources, resolve_bootstrap_auth_keyring_backend_kind_uses_secret_auth_storage_feature, filter_mcp_servers_by_allowlist_blocks_all_when_empty, filter_mcp_servers_by_allowlist_enforces_identity_rules, filter_plugin_mcp_servers_by_allowlist_blocks_unlisted_plugin (+11 more))。

Sourced::deref873–875 ↗
fn deref(&self) -> &Self::Target

作用:让 Sourced 像里面的值一样被读取,减少访问 .value 的样板代码。

数据流:进去的是 Sourced 的引用 → 返回内部值的引用 → 不复制也不修改。

调用关系:Rust 自动解引用时会用它,让调用方更自然地访问带来源值。

ConfigRequirementsWithSources::merge_unset_fields904–989 ↗
fn merge_unset_fields(&mut self, source: RequirementSource, other: ConfigRequirementsToml)

作用:把一层新读到的 requirements 配置合并进已有结果,但只填空位,不覆盖更高优先级已经给出的值。

数据流:进去的是当前带来源配置、这一层的来源和原始 TOML 配置 → 空白 guardian_policy_config 会被忽略;普通字段只在当前没有值时填入并记录来源;apps 字段会特殊合并 → 当前对象被更新。

调用关系:配置加载器按优先级读取多层配置时会反复调用它;它用 Sourced::new 记录来源,并把 apps 合并交给 merge_app_requirements_descending。

调用图:调用 2 个内部函数(new, merge_app_requirements_descending);外部调用 1 个(fill_missing_take!)。

ConfigRequirementsWithSources::into_toml991–1039 ↗
fn into_toml(self) -> ConfigRequirementsToml

作用:把“带来源”的配置还原成普通 TOML 形状,只保留值,不保留来源。

数据流:进去的是 ConfigRequirementsWithSources 自身 → 拆开每个 Sourced,只取 value;remote_sandbox_config 固定设为 None → 返回 ConfigRequirementsToml。

调用关系:需要把合并后的配置再投影成 TOML 结构给别处使用时会调用它。

normalize_hostname1042–1045 ↗
fn normalize_hostname(hostname: &str) -> Option<String>

作用:把主机名整理成方便匹配的标准样子。

数据流:进去的是主机名字符串 → 去掉前后空白和末尾点号,转成小写;如果最后为空就返回 None → 否则返回标准化字符串。

调用关系:ConfigRequirementsToml::apply_remote_sandbox_config 和 hostname_matches_any_pattern 会用它匹配远程沙箱规则。

hostname_matches_any_pattern1047–1053 ↗
fn hostname_matches_any_pattern(hostname: &str, patterns: &[String]) -> bool

作用:判断一个主机名是否匹配任意一个通配符模式。

数据流:进去的是已标准化主机名和模式列表 → 逐个标准化模式,并用 WildMatchPattern 做大小写不敏感匹配 → 只要一个匹配就返回 true。

调用关系:apply_remote_sandbox_config 用它找到第一条适合当前机器的远程沙箱配置。

SandboxModeRequirement::from1073–1079 ↗
fn from(mode: SandboxMode) -> Self

作用:把运行时沙箱模式转成 requirements.toml 中的沙箱要求类型。

数据流:进去的是 SandboxMode → ReadOnly、WorkspaceWrite、DangerFullAccess 分别映射到同名要求 → 返回 SandboxModeRequirement。

调用关系:配置约束需要比较运行时沙箱模式和管理员允许列表时会用它。

ConfigRequirementsToml::apply_remote_sandbox_config1089–1103 ↗
fn apply_remote_sandbox_config(&mut self, hostname: Option<&str>)

作用:根据当前机器主机名,选择远程沙箱专用规则并覆盖顶层 allowed_sandbox_modes。

数据流:进去的是可修改的 TOML 配置和可选主机名 → 如果没有远程配置或主机名无效就不动;否则找第一条匹配主机名模式的配置 → 把 allowed_sandbox_modes 改成那条配置里的值。

调用关系:配置加载后、合并前可调用它,让不同远程机器得到不同沙箱限制。

ConfigRequirementsToml::is_empty1105–1149 ↗
fn is_empty(&self) -> bool

作用:判断一份 requirements.toml 是否没有任何实际要求。

数据流:进去的是原始配置结构 → 逐个检查字段是否为空,嵌套结构会调用各自 is_empty,空白 guardian_policy_config 也算空 → 返回 true 或 false。

调用关系:配置加载器和测试用它区分“没配置”和“配置了 false 这样的有效值”。

ConfigRequirements::try_from1155–1459 ↗
fn try_from(toml: ConfigRequirementsWithSources) -> Result<Self, Self::Error>

作用:把合并后、带来源的原始要求,编译成运行时真正能执行检查的 ConfigRequirements。

数据流:进去的是 ConfigRequirementsWithSources → 为审批策略、审批 reviewer、沙箱权限、Windows 沙箱、Web 搜索、hooks、驻留地等建立 Constrained 约束;空列表会报错;执行规则会解析成 policy;网络和文件系统会转成约束结构 → 成功返回 ConfigRequirements,失败返回带来源的 ConstraintError。

调用关系:这是本文件最核心的转换步骤。配置加载完成后会调用它,后续用户改设置或执行操作时就靠这些约束判断是否允许。

调用图:调用 7 个内部函数(new, new, allow_any, allow_any_from_default, new, empty_field, read_only);外部调用 1 个(format!)。

sandbox_mode_requirement_for_permission_profile1462–1483 ↗
fn sandbox_mode_requirement_for_permission_profile(
    permission_profile: &PermissionProfile,
) -> SandboxModeRequirement

作用:根据一个权限配置推断它相当于哪种沙箱强度。

数据流:进去的是 PermissionProfile → 完全关闭沙箱算 DangerFullAccess,外部沙箱算 ExternalSandbox;受管权限则查看文件系统策略,有全盘写权限算危险全访问,有写权限算工作区写,否则算只读 → 返回 SandboxModeRequirement。

调用关系:ConfigRequirements::try_from 在检查 permission_profile 是否符合 allowed_sandbox_modes 时调用它。

调用图:调用 1 个内部函数(file_system_sandbox_policy)。

tests::tokens1499–1501 ↗
fn tokens(cmd: &[&str]) -> Vec<String>

作用:测试用小工具,把命令 token 的字符串切片变成 String 列表。

数据流:进去的是 &str 数组 → 每个元素复制成 String → 返回 Vec<String>。

调用关系:执行策略测试用它构造命令输入。

tests::system_requirements_toml_file_for_test1503–1507 ↗
fn system_requirements_toml_file_for_test() -> Result<AbsolutePathBuf>

作用:测试用小工具,构造一个临时目录里的 requirements.toml 绝对路径。

数据流:没有业务输入 → 取系统临时目录并拼上 requirements.toml → 转成 AbsolutePathBuf 返回。

调用关系:多个测试用它模拟系统 requirements.toml 的来源路径。

调用图:调用 1 个内部函数(try_from);外部调用 1 个(temp_dir)。

tests::composite_requirement_source_flattens_and_deduplicates_sources1510–1526 ↗
fn composite_requirement_source_flattens_and_deduplicates_sources()

作用:验证组合来源会展开嵌套来源,并去掉重复来源。

数据流:进去的是测试里构造的 MDM 来源和旧 MDM 来源 → 调用 RequirementSource::composite → 断言结果顺序正确且不重复。

调用关系:它直接覆盖 RequirementSource::composite 的关键行为。

调用图:外部调用 1 个(assert_eq!)。

tests::with_unknown_source1528–1588 ↗
fn with_unknown_source(toml: ConfigRequirementsToml) -> ConfigRequirementsWithSources

作用:测试用辅助函数,把普通 ConfigRequirementsToml 包装成所有字段都来自 Unknown 的带来源配置。

数据流:进去的是 ConfigRequirementsToml → 拆出每个字段,存在的就包成 Sourced<..., Unknown> → 返回 ConfigRequirementsWithSources。

调用关系:大量反序列化测试用它跳过来源细节,专注测试约束结果。

tests::deserialize_allow_managed_hooks_only1591–1601 ↗
fn deserialize_allow_managed_hooks_only() -> Result<()>

作用:测试 allow_managed_hooks_only = true 能正确读取,并让配置不再被认为是空。

数据流:进去的是一段 TOML 字符串 → 解析成 ConfigRequirementsToml → 断言字段为 Some(true) 且 is_empty 为 false。

调用关系:覆盖 ConfigRequirementsToml 的布尔字段读取和空配置判断。

调用图:外部调用 3 个(assert!, assert_eq!, from_str)。

tests::allow_managed_hooks_only_false_is_still_configured1604–1614 ↗
fn allow_managed_hooks_only_false_is_still_configured() -> Result<()>

作用:测试 false 也是明确配置,不能被当成没写。

数据流:进去的是 allow_managed_hooks_only = false 的 TOML → 解析后检查字段为 Some(false) → 断言整体不为空。

调用关系:防止 is_empty 把 false 误判为空。

调用图:外部调用 3 个(assert!, assert_eq!, from_str)。

tests::deserialize_managed_permission_profiles1617–1659 ↗
fn deserialize_managed_permission_profiles() -> Result<()>

作用:测试受管权限 profile、默认权限和 profile 继承关系能从 TOML 读出来。

数据流:进去的是包含 allowed_permission_profiles、default_permissions 和 permissions.* 的 TOML → 解析配置 → 断言允许列表、默认值和 profile 内容都存在。

调用关系:覆盖 ConfigRequirementsToml 对权限 profile 目录的反序列化。

调用图:外部调用 3 个(assert!, assert_eq!, from_str)。

tests::deserialize_allow_appshots1662–1672 ↗
fn deserialize_allow_appshots() -> Result<()>

作用:测试 allow_appshots = true 能正确读取。

数据流:进去的是 TOML → 解析 → 断言 allow_appshots 为 Some(true) 且配置不为空。

调用关系:覆盖应用截图开关的读取。

调用图:外部调用 3 个(assert!, assert_eq!, from_str)。

tests::filesystem_requirements_table_cannot_define_a_permission_profile1675–1690 ↗
fn filesystem_requirements_table_cannot_define_a_permission_profile()

作用:测试 permissions.filesystem 不能写成普通权限 profile。

数据流:进去的是在 permissions.filesystem 下写 extends 的 TOML → 尝试解析并期望失败 → 检查错误消息说明该表是保留用途。

调用关系:覆盖 FilesystemRequirementsToml::deserialize 的防误用校验。

调用图:外部调用 1 个(assert!)。

tests::allow_appshots_false_is_still_configured1693–1703 ↗
fn allow_appshots_false_is_still_configured() -> Result<()>

作用:测试 allow_appshots = false 也算明确配置。

数据流:进去的是 TOML → 解析 → 断言字段为 Some(false),整体不为空。

调用关系:保护 ConfigRequirementsToml::is_empty 对 false 值的处理。

调用图:外部调用 3 个(assert!, assert_eq!, from_str)。

tests::allow_remote_control_false_is_still_configured1706–1716 ↗
fn allow_remote_control_false_is_still_configured() -> Result<()>

作用:测试 allow_remote_control = false 也不会被当成空配置。

数据流:进去的是 TOML → 解析 → 断言字段为 Some(false) 且配置不为空。

调用关系:覆盖远程控制开关的空值判断。

调用图:外部调用 3 个(assert!, assert_eq!, from_str)。

tests::deserialize_computer_use_requirements1719–1735 ↗
fn deserialize_computer_use_requirements() -> Result<()>

作用:测试 computer_use 表能正确读取锁屏电脑使用限制。

数据流:进去的是 computer_use TOML → 解析 → 断言 allow_locked_computer_use 为 Some(false),整体不为空。

调用关系:覆盖 ComputerUseRequirementsToml 和 is_empty 行为。

调用图:外部调用 3 个(assert!, assert_eq!, from_str)。

tests::merge_unset_fields_copies_every_field_and_sets_sources1738–1839 ↗
fn merge_unset_fields_copies_every_field_and_sets_sources()

作用:测试 merge_unset_fields 会复制所有该复制的字段,并给它们记录正确来源。

数据流:进去的是一个空目标、一份包含多字段的配置和来源 → 调用 merge_unset_fields → 断言每个字段都被包成 Sourced,值和来源都正确。

调用关系:这是 ConfigRequirementsWithSources::merge_unset_fields 的全面回归测试。

调用图:外部调用 4 个(from, assert_eq!, default, vec!)。

tests::merge_unset_fields_fills_missing_values1842–1886 ↗
fn merge_unset_fields_fills_missing_values() -> Result<()>

作用:测试目标为空时,合并会把新来源里的字段填进去。

数据流:进去的是只包含 allowed_approval_policies 的 TOML → 合并到空目标 → 断言目标得到该字段和对应来源。

调用关系:覆盖 merge_unset_fields 的基本“补空位”行为。

调用图:外部调用 3 个(assert_eq!, default, from_str)。

tests::merge_unset_fields_does_not_overwrite_existing_values1889–1940 ↗
fn merge_unset_fields_does_not_overwrite_existing_values() -> Result<()>

作用:测试低优先级来源不能覆盖已有高优先级值。

数据流:先把 never 合并进目标 → 再尝试合并 on-request → 断言最终仍是 never,来源仍是第一次的来源。

调用关系:保护配置优先级规则。

调用图:外部调用 3 个(assert_eq!, default, from_str)。

tests::merge_unset_fields_ignores_blank_guardian_override1943–1973 ↗
fn merge_unset_fields_ignores_blank_guardian_override()

作用:测试空白 guardian_policy_config 不会占住位置,后面的有效配置还能生效。

数据流:先合并只含空白文本的 guardian 配置 → 再合并系统来源的有效文本 → 断言最终保存的是有效文本和系统来源。

调用关系:覆盖 merge_unset_fields 对 guardian_policy_config 的特殊清理逻辑。

调用图:外部调用 4 个(default, assert_eq!, default, system_requirements_toml_file_for_test)。

tests::deserialize_guardian_policy_config1976–1990 ↗
fn deserialize_guardian_policy_config() -> Result<()>

作用:测试多行 guardian_policy_config 能按原文读取。

数据流:进去的是多行 TOML 字符串 → 解析 → 断言字段内容包含预期文本和换行。

调用关系:覆盖 guardian 策略文本字段的反序列化。

调用图:外部调用 2 个(assert_eq!, from_str)。

tests::blank_guardian_policy_config_is_empty1993–2004 ↗
fn blank_guardian_policy_config_is_empty() -> Result<()>

作用:测试只有空白的 guardian_policy_config 不算有效配置。

数据流:进去的是空白多行字符串 TOML → 解析 → 调用 is_empty 并断言为 true。

调用关系:覆盖 ConfigRequirementsToml::is_empty 对空白文本的处理。

调用图:外部调用 2 个(assert!, from_str)。

tests::allowed_approvals_reviewers_is_not_empty2007–2016 ↗
fn allowed_approvals_reviewers_is_not_empty() -> Result<()>

作用:测试 allowed_approvals_reviewers 写了值时,配置不为空。

数据流:进去的是 reviewer allowlist TOML → 解析 → 断言 is_empty 为 false。

调用关系:覆盖审批 reviewer 限制字段的空配置判断。

调用图:外部调用 2 个(assert!, from_str)。

tests::deserialize_filesystem_deny_read_requirements2019–2054 ↗
fn deserialize_filesystem_deny_read_requirements() -> Result<()>

作用:测试禁止读取的普通绝对路径能从配置读到运行时约束里。

数据流:进去的是包含 deny_read 路径列表的 TOML → 解析、包装未知来源、转换成 ConfigRequirements → 断言 filesystem 约束里有相同路径和来源。

调用关系:覆盖 FilesystemDenyReadPattern、FilesystemConstraints::from 和 ConfigRequirements::try_from 的组合行为。

调用图:外部调用 5 个(assert_eq!, cfg!, with_unknown_source, format!, from_str)。

tests::deserialize_filesystem_deny_read_glob_requirements2057–2081 ↗
fn deserialize_filesystem_deny_read_glob_requirements() -> Result<()>

作用:测试带通配符的 deny_read 路径会被规范化并保存。

数据流:进去的是 ./private/**/*.txt 这类模式 → 在临时目录上下文中解析并转换 → 断言结果等于 from_input 规范化后的模式。

调用关系:覆盖 FilesystemDenyReadPattern::from_input 的通配符路径处理。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, with_unknown_source, temp_dir, from_str)。

tests::deserialize_apps_requirements2084–2104 ↗
fn deserialize_apps_requirements() -> Result<()>

作用:测试 apps 表中应用 enabled 字段能正确读取。

数据流:进去的是一个应用 enabled = false 的 TOML → 解析 → 断言 apps map 中对应应用被标为 Some(false)。

调用关系:覆盖 AppsRequirementsToml 和 AppRequirementToml 的 TOML 结构。

调用图:外部调用 2 个(assert_eq!, from_str)。

tests::deserialize_apps_tool_requirements2107–2134 ↗
fn deserialize_apps_tool_requirements() -> Result<()>

作用:测试应用工具级别的 approval_mode 能正确读取。

数据流:进去的是 apps.<id>.tools.<tool> 下的 TOML → 解析 → 断言工具名和审批模式被放进嵌套 map。

调用关系:覆盖 AppToolsRequirementsToml 和 AppToolRequirementToml 的读取。

调用图:外部调用 2 个(assert_eq!, from_str)。

tests::apps_requirements2136–2151 ↗
fn apps_requirements(entries: &[(&str, Option<bool>)]) -> AppsRequirementsToml

作用:测试用辅助函数,快速构造只有应用 enabled 状态的 AppsRequirementsToml。

数据流:进去的是应用 ID 和可选启用状态的数组 → 转成 BTreeMap → 返回 AppsRequirementsToml。

调用关系:多个 app 合并测试用它构造输入和期望值。

tests::app_tool_requirements2153–2174 ↗
fn app_tool_requirements(
        app_id: &str,
        tool_name: &str,
        approval_mode: AppToolApproval,
    ) -> AppsRequirementsToml

作用:测试用辅助函数,快速构造一个应用某个工具的审批要求。

数据流:进去的是应用 ID、工具名和审批模式 → 包装成嵌套的 apps/tools 结构 → 返回 AppsRequirementsToml。

调用关系:工具审批合并测试用它简化样板数据。

调用图:外部调用 1 个(from)。

tests::merge_app_requirements_descending_unions_distinct_apps2177–2190 ↗
fn merge_app_requirements_descending_unions_distinct_apps()

作用:测试不同来源里的不同应用会合并到同一张表里。

数据流:进去的是高优先级含 connector_high、低优先级含 connector_low 的数据 → 调用合并 → 断言两个应用都保留。

调用关系:覆盖 merge_app_requirements_descending 的并集行为。

调用图:调用 1 个内部函数(merge_app_requirements_descending);外部调用 2 个(assert_eq!, apps_requirements)。

tests::merge_app_requirements_descending_prefers_false_from_lower_precedence2193–2203 ↗
fn merge_app_requirements_descending_prefers_false_from_lower_precedence()

作用:测试低优先级来源说禁用时,也能把应用禁用掉。

数据流:高优先级说 enabled=true,低优先级说 false → 合并 → 断言最终 false。

调用关系:验证“禁用优先”规则,而不是简单高优先级永远赢。

调用图:调用 1 个内部函数(merge_app_requirements_descending);外部调用 2 个(assert_eq!, apps_requirements)。

tests::merge_app_requirements_descending_keeps_higher_true_when_lower_is_unset2206–2216 ↗
fn merge_app_requirements_descending_keeps_higher_true_when_lower_is_unset()

作用:测试低优先级没写 enabled 时,不会抹掉高优先级的 true。

数据流:高优先级 true,低优先级 None → 合并 → 断言仍为 true。

调用关系:覆盖 app enabled 合并的空值处理。

调用图:调用 1 个内部函数(merge_app_requirements_descending);外部调用 2 个(assert_eq!, apps_requirements)。

tests::merge_app_requirements_descending_uses_lower_value_when_higher_missing2219–2229 ↗
fn merge_app_requirements_descending_uses_lower_value_when_higher_missing()

作用:测试高优先级没有该应用时,会采用低优先级的应用设置。

数据流:base 为空,incoming 有应用 true → 合并 → 断言结果包含该应用 true。

调用关系:覆盖 merge_app_requirements_descending 的补充行为。

调用图:调用 1 个内部函数(merge_app_requirements_descending);外部调用 2 个(assert_eq!, apps_requirements)。

tests::merge_app_requirements_descending_preserves_higher_false_when_lower_missing_app2232–2242 ↗
fn merge_app_requirements_descending_preserves_higher_false_when_lower_missing_app()

作用:测试高优先级禁用的应用不会因为低优先级没提到而消失。

数据流:base 有应用 false,incoming 为空 → 合并 → 断言仍保留 false。

调用关系:保护禁用设置不会被后续空配置冲掉。

调用图:调用 1 个内部函数(merge_app_requirements_descending);外部调用 2 个(assert_eq!, apps_requirements)。

tests::merge_app_requirements_descending_preserves_higher_tool_approval_mode2245–2267 ↗
fn merge_app_requirements_descending_preserves_higher_tool_approval_mode()

作用:测试工具审批模式已经由高优先级设置时,低优先级不能覆盖。

数据流:base 中工具是 Approve,incoming 中同工具是 Prompt → 合并 → 断言仍是 Approve。

调用关系:覆盖 merge_app_requirements_descending 的工具审批优先级。

调用图:调用 1 个内部函数(merge_app_requirements_descending);外部调用 2 个(assert_eq!, app_tool_requirements)。

tests::merge_app_requirements_descending_uses_lower_tool_approval_when_higher_missing2270–2288 ↗
fn merge_app_requirements_descending_uses_lower_tool_approval_when_higher_missing()

作用:测试高优先级缺少工具审批模式时,可以用低优先级补上。

数据流:base 只有应用没有工具审批,incoming 有工具 Approve → 合并 → 断言工具审批被补入。

调用关系:覆盖工具要求的补空位逻辑。

调用图:调用 1 个内部函数(merge_app_requirements_descending);外部调用 3 个(assert_eq!, app_tool_requirements, apps_requirements)。

tests::merge_unset_fields_merges_apps_across_sources_with_enabled_evaluation2291–2330 ↗
fn merge_unset_fields_merges_apps_across_sources_with_enabled_evaluation()

作用:测试 merge_unset_fields 会跨来源合并 apps,并正确处理 enabled 的禁用优先规则。

数据流:先合并高优先级应用,再合并低优先级应用 → apps 字段被合并而不是整体忽略 → 断言共享应用被禁用,来源仍保留高优先级来源。

调用关系:连接 merge_unset_fields 和 merge_app_requirements_descending 的集成测试。

调用图:外部调用 4 个(default, assert_eq!, default, apps_requirements)。

tests::merge_unset_fields_apps_empty_higher_source_does_not_block_lower_disables2333–2355 ↗
fn merge_unset_fields_apps_empty_higher_source_does_not_block_lower_disables()

作用:测试高优先级 apps 表为空时,不会阻止低优先级禁用某个应用。

数据流:先合并空 apps,再合并包含禁用应用的 apps → 断言最终包含禁用应用。

调用关系:覆盖 apps 字段的特殊合并,而不是普通字段“已有就不管”。

调用图:外部调用 4 个(default, assert_eq!, default, apps_requirements)。

tests::constraint_error_includes_requirement_source2358–2409 ↗
fn constraint_error_includes_requirement_source() -> Result<()>

作用:测试违反约束时报错会带上规则来源。

数据流:构造带系统文件来源的审批、reviewer 和沙箱限制 → 转成运行时要求 → 尝试设置不允许的值并断言错误里含同一来源。

调用关系:覆盖 ConfigRequirements::try_from 创建约束时是否正确捕获 requirement_source。

调用图:外部调用 5 个(try_from, assert_eq!, default, system_requirements_toml_file_for_test, from_str)。

tests::constraint_error_includes_composite_requirement_source2412–2442 ↗
fn constraint_error_includes_composite_requirement_source() -> Result<()>

作用:测试组合来源也会出现在约束错误里。

数据流:构造一个由 MDM 和旧 MDM 组成的 composite 来源 → 合并限制并转成要求 → 触发不允许的审批策略并检查错误来源。

调用关系:同时覆盖 RequirementSource::composite 和 ConfigRequirements::try_from 的错误来源传播。

调用图:调用 1 个内部函数(composite);外部调用 4 个(try_from, assert_eq!, default, from_str)。

tests::constrained_fields_store_requirement_source2445–2489 ↗
fn constrained_fields_store_requirement_source() -> Result<()>

作用:测试最终 ConfigRequirements 中每个受约束字段都保存了来源。

数据流:进去的是包含多种限制的 TOML 和旧 MDM 来源 → 合并并转换 → 断言审批、沙箱、Web 搜索、功能、驻留地等字段来源正确。

调用关系:覆盖 ConstrainedWithSource 和 Sourced 在 try_from 中的来源保存。

调用图:外部调用 4 个(try_from, assert_eq!, default, from_str)。

tests::deserialize_allowed_approval_policies2492–2544 ↗
fn deserialize_allowed_approval_policies() -> Result<()>

作用:测试允许的审批策略列表能限制运行时审批策略。

数据流:进去的是 allowed_approval_policies TOML → 转成要求 → 检查初始值取第一个允许项,允许项可设置,不允许项返回错误。

调用关系:覆盖 ConfigRequirements::try_from 对 approval_policy 的约束生成。

调用图:外部调用 4 个(assert!, assert_eq!, with_unknown_source, from_str)。

tests::deserialize_allowed_approvals_reviewers2547–2573 ↗
fn deserialize_allowed_approvals_reviewers() -> Result<()>

作用:测试允许的审批 reviewer 列表能正确读取并约束。

数据流:进去的是 allowed_approvals_reviewers TOML → 转成要求 → 断言初始 reviewer 和可设置项正确。

调用关系:覆盖 approvals_reviewer 约束。

调用图:外部调用 4 个(assert!, assert_eq!, with_unknown_source, from_str)。

tests::deserialize_allowed_windows_sandbox_implementations2576–2603 ↗
fn deserialize_allowed_windows_sandbox_implementations() -> Result<()>

作用:测试 Windows 沙箱实现 allowlist 会限制可选实现。

数据流:进去的是只允许 elevated 的 Windows TOML → 转成要求 → 断言初始值为 Elevated,Elevated 可设置,Unelevated 和 None 不可设置。

调用关系:覆盖 ConfigRequirements::try_from 的 windows_sandbox_mode 分支。

调用图:外部调用 4 个(assert!, assert_eq!, with_unknown_source, from_str)。

tests::empty_allowed_windows_sandbox_implementations_is_rejected2606–2621 ↗
fn empty_allowed_windows_sandbox_implementations_is_rejected() -> Result<()>

作用:测试 Windows 沙箱实现允许列表为空时会被拒绝。

数据流:进去的是 allowed_sandbox_implementations = [] → 解析成功但转换失败 → 断言错误是 EmptyField。

调用关系:覆盖 try_from 对空 Windows allowlist 的校验。

调用图:外部调用 2 个(assert_eq!, from_str)。

tests::allowed_windows_sandbox_implementations_prefer_elevated_fallback2624–2638 ↗
fn allowed_windows_sandbox_implementations_prefer_elevated_fallback() -> Result<()>

作用:测试 Windows 同时允许 elevated 和 unelevated 时,默认优先选 elevated。

数据流:进去的是两个实现都允许的 TOML → 转成要求 → 断言初始值为 Elevated。

调用关系:覆盖 windows_sandbox_mode 初始值选择规则。

调用图:外部调用 3 个(assert_eq!, with_unknown_source, from_str)。

tests::deserialize_legacy_allowed_approvals_reviewer2641–2654 ↗
fn deserialize_legacy_allowed_approvals_reviewer() -> Result<()>

作用:测试旧名字 guardian_subagent 仍能读成现在的 AutoReview。

数据流:进去的是包含 guardian_subagent 的 TOML → 解析并转换 → 断言 reviewer 值为 AutoReview。

调用关系:覆盖协议类型反序列化的向后兼容。

调用图:外部调用 3 个(assert_eq!, with_unknown_source, from_str)。

tests::empty_allowed_approvals_reviewers_is_rejected2657–2673 ↗
fn empty_allowed_approvals_reviewers_is_rejected() -> Result<()>

作用:测试 reviewer 允许列表为空时会报错。

数据流:进去的是 allowed_approvals_reviewers = [] → 转换成 ConfigRequirements 时失败 → 断言 EmptyField 字段名正确。

调用关系:覆盖 ConfigRequirements::try_from 对空 reviewer 列表的校验。

调用图:外部调用 4 个(try_from, assert_eq!, with_unknown_source, from_str)。

tests::deserialize_allowed_sandbox_modes2676–2728 ↗
fn deserialize_allowed_sandbox_modes() -> Result<()>

作用:测试 allowed_sandbox_modes 会根据权限 profile 推断沙箱强度并限制它。

数据流:进去的是允许 read-only 和 workspace-write 的 TOML → 构造只读、工作区写、危险全访问、外部沙箱等 profile → 断言前两个允许,后两个拒绝。

调用关系:覆盖 sandbox_mode_requirement_for_permission_profile 和 permission_profile 约束。

调用图:调用 2 个内部函数(workspace_write_with, from_absolute_path);外部调用 5 个(assert!, assert_eq!, cfg!, with_unknown_source, from_str)。

tests::deserialize_remote_sandbox_config_requires_hostname_patterns_list2731–2764 ↗
fn deserialize_remote_sandbox_config_requires_hostname_patterns_list() -> Result<()>

作用:测试 remote_sandbox_config 的 hostname_patterns 必须是列表,不接受单个字符串。

数据流:先解析合法列表写法并断言结果 → 再解析字符串写法并期望报错 → 检查错误提示包含类型不匹配。

调用关系:覆盖 RemoteSandboxConfigToml 的 TOML 结构要求。

调用图:外部调用 3 个(assert!, assert_eq!, from_str)。

tests::remote_sandbox_config_first_match_overrides_top_level2767–2824 ↗
fn remote_sandbox_config_first_match_overrides_top_level() -> Result<()>

作用:测试远程沙箱配置命中时,第一条匹配规则会覆盖顶层沙箱限制。

数据流:进去的是顶层只读和两条远程规则 → 对 BUILD-01.EXAMPLE.COM. 应用远程规则 → 合并转换后断言使用第一条匹配的 read-only + workspace-write。

调用关系:覆盖 apply_remote_sandbox_config、hostname 匹配和后续约束转换。

调用图:调用 2 个内部函数(workspace_write_with, from_absolute_path);外部调用 6 个(try_from, assert!, assert_eq!, cfg!, default, from_str)。

tests::remote_sandbox_config_non_match_preserves_top_level2827–2855 ↗
fn remote_sandbox_config_non_match_preserves_top_level() -> Result<()>

作用:测试主机名不匹配远程规则时,顶层沙箱限制保持不变。

数据流:进去的是顶层只读和一条不匹配远程规则 → 应用 laptop.example.com → 转换后尝试危险全访问被只读限制拒绝。

调用关系:覆盖 apply_remote_sandbox_config 的未命中路径。

调用图:外部调用 4 个(try_from, assert_eq!, default, from_str)。

tests::remote_sandbox_config_does_not_override_higher_precedence_sandbox_modes2858–2894 ↗
fn remote_sandbox_config_does_not_override_higher_precedence_sandbox_modes() -> Result<()>

作用:测试低优先级来源的远程沙箱规则不能覆盖高优先级已设置的沙箱限制。

数据流:高优先级设置只读,低优先级远程规则允许工作区写 → 分别应用远程配置后合并 → 断言最终仍只允许只读。

调用关系:覆盖 apply_remote_sandbox_config 与 merge_unset_fields 优先级配合。

调用图:外部调用 4 个(try_from, assert_eq!, default, from_str)。

tests::deserialize_allowed_web_search_modes2897–2928 ↗
fn deserialize_allowed_web_search_modes() -> Result<()>

作用:测试 Web 搜索模式 allowlist 能限制 live 搜索,但总是允许 disabled。

数据流:进去的是 allowed_web_search_modes = [cached] → 转成要求 → 断言 Cached 初始,Disabled 可设,Live 被拒绝。

调用关系:覆盖 ConfigRequirements::try_from 的 Web 搜索模式约束。

调用图:外部调用 4 个(assert!, assert_eq!, with_unknown_source, from_str)。

tests::allowed_web_search_modes_allows_disabled2931–2958 ↗
fn allowed_web_search_modes_allows_disabled() -> Result<()>

作用:测试只允许 disabled 时,Web 搜索只能关闭。

数据流:进去的是 allowed_web_search_modes = [disabled] → 转换 → 断言初始为 Disabled,Cached 被拒绝。

调用关系:覆盖 Web 搜索 allowlist 的严格禁用场景。

调用图:外部调用 4 个(assert!, assert_eq!, with_unknown_source, from_str)。

tests::allowed_web_search_modes_empty_restricts_to_disabled2961–2988 ↗
fn allowed_web_search_modes_empty_restricts_to_disabled() -> Result<()>

作用:测试 Web 搜索允许列表为空时,效果是只能 disabled。

数据流:进去的是空列表 → try_from 会自动把 Disabled 加入可接受集合 → 断言 Cached 被拒绝,Disabled 可用。

调用关系:覆盖 Web 搜索空列表的特殊语义。

调用图:外部调用 4 个(assert!, assert_eq!, with_unknown_source, from_str)。

tests::deserialize_feature_requirements2991–3014 ↗
fn deserialize_feature_requirements() -> Result<()>

作用:测试 features 表能保存任意功能名到开关值的要求。

数据流:进去的是 features 下 apps=false、personality=true → 转成要求 → 断言 feature_requirements 带 Unknown 来源且内容正确。

调用关系:覆盖 FeatureRequirementsToml 的 flatten 读取和 try_from 保留行为。

调用图:外部调用 3 个(assert_eq!, with_unknown_source, from_str)。

tests::deserialize_managed_hooks_requirements3017–3040 ↗
fn deserialize_managed_hooks_requirements() -> Result<()>

作用:测试受管 hooks 配置能读出目录和钩子处理器。

数据流:进去的是 hooks TOML → 解析成 ManagedHooksRequirementsToml → 断言 managed_dir、handler_count 和 PreToolUse 数量正确。

调用关系:虽然 hooks 类型来自别处,这里验证 requirements 文件能承载它。

调用图:外部调用 2 个(assert_eq!, from_str)。

tests::merge_unset_fields_does_not_overwrite_existing_hooks3043–3093 ↗
fn merge_unset_fields_does_not_overwrite_existing_hooks() -> Result<()>

作用:测试高优先级 hooks 不会被后续低优先级 hooks 覆盖。

数据流:先合并 cloud hooks,再合并 system hooks → 断言最终 managed_dir 仍是 cloud,来源仍是旧 MDM。

调用关系:覆盖 merge_unset_fields 对 hooks 作为普通字段的优先级规则。

调用图:外部调用 3 个(assert_eq!, default, system_requirements_toml_file_for_test)。

tests::managed_hooks_constraint_rejects_drift3096–3132 ↗
fn managed_hooks_constraint_rejects_drift() -> Result<()>

作用:测试受管 hooks 一旦成为约束,就不能被改成别的 hooks 配置。

数据流:进去的是一份 hooks 要求 → 转成 ConfigRequirements → 尝试把 managed_hooks 改成另一个目录 → 断言返回 InvalidValue 错误。

调用关系:覆盖 ConfigRequirements::try_from 为 managed_hooks 建立精确匹配约束。

调用图:外部调用 5 个(assert!, with_unknown_source, default, from, from_str)。

tests::network_requirements_are_preserved_as_constraints_with_source3135–3211 ↗
fn network_requirements_are_preserved_as_constraints_with_source() -> Result<()>

作用:测试新版 experimental_network 配置会完整保存在运行时网络约束里,并保留来源。

数据流:进去的是包含 domains、unix_sockets 和多个网络开关的 TOML → 合并并转换 → 断言 NetworkConstraints 的每个字段和值都正确。

调用关系:覆盖 NetworkRequirementsToml::deserialize、NetworkConstraints::from 和来源保存。

调用图:外部调用 4 个(try_from, assert_eq!, default, from_str)。

tests::legacy_network_requirements_are_preserved_as_constraints_with_source3214–3278 ↗
fn legacy_network_requirements_are_preserved_as_constraints_with_source() -> Result<()>

作用:测试旧版网络字段 allowed_domains、denied_domains、allow_unix_sockets 仍能转成新版约束。

数据流:进去的是旧写法 TOML → 合并并转换 → 断言 domains 和 unix_sockets 被规范化成新版权限表。

调用关系:覆盖 legacy_domain_permissions_from_lists 和 legacy_unix_socket_permissions_from_list 的兼容逻辑。

调用图:外部调用 4 个(try_from, assert_eq!, default, from_str)。

tests::mixed_legacy_and_canonical_network_requirements_are_rejected3281–3315 ↗
fn mixed_legacy_and_canonical_network_requirements_are_rejected()

作用:测试网络配置不能把旧写法和新写法混用。

数据流:分别构造 domains 与 allowed_domains 混用、unix_sockets 与 allow_unix_sockets 混用的 TOML → 解析都应失败 → 断言错误消息包含不能混用的提示。

调用关系:覆盖 NetworkRequirementsToml::deserialize 的歧义防护。

调用图:外部调用 1 个(assert!)。

tests::network_permission_containers_project_allowed_and_denied_entries3318–3373 ↗
fn network_permission_containers_project_allowed_and_denied_entries()

作用:测试网络权限表能正确拆出允许和禁止的条目。

数据流:进去的是手写的域名权限表和 socket 权限表 → 调用 allowed_domains、denied_domains、allow_unix_sockets → 断言返回列表只包含对应权限的项。

调用关系:覆盖 NetworkDomainPermissionsToml 和 NetworkUnixSocketPermissionsToml 的投影方法。

调用图:外部调用 2 个(from, assert_eq!)。

tests::deserialize_mcp_server_requirements3376–3412 ↗
fn deserialize_mcp_server_requirements() -> Result<()>

作用:测试顶层 MCP server 要求能按命令或 URL 两种身份读取。MCP 可以理解成外部工具服务的接入配置。

数据流:进去的是两个 mcp_servers 配置,一个 command,一个 url → 解析并转换 → 断言结果 map 中两种身份都正确。

调用关系:覆盖 McpServerIdentity、McpServerRequirement 和 ConfigRequirements::try_from 的保留行为。

调用图:外部调用 3 个(assert_eq!, with_unknown_source, from_str)。

tests::deserialize_plugin_mcp_server_requirements3415–3461 ↗
fn deserialize_plugin_mcp_server_requirements() -> Result<()>

作用:测试插件里的 MCP server 要求也能正确读取。

数据流:进去的是 plugins.<plugin>.mcp_servers 配置 → 解析并转换 → 断言每个插件下的 server 身份正确。

调用关系:覆盖 PluginRequirementsToml 的嵌套 MCP server 读取。

调用图:外部调用 3 个(assert_eq!, with_unknown_source, from_str)。

tests::deserialize_exec_policy_requirements3464–3491 ↗
fn deserialize_exec_policy_requirements() -> Result<()>

作用:测试命令执行规则能从 requirements 里读出,并真正用于判断命令。

数据流:进去的是禁止 rm 前缀的 rules TOML → 转成 exec_policy → 用 rm -rf 检查策略,断言结果是 Forbidden 且命中前缀规则。

调用关系:覆盖 RequirementsExecPolicyToml 到 RequirementsExecPolicy 的转换和 ConfigRequirements::try_from 的接入。

调用图:外部调用 3 个(assert_eq!, with_unknown_source, from_str)。

tests::exec_policy_error_includes_requirement_source3494–3521 ↗
fn exec_policy_error_includes_requirement_source() -> Result<()>

作用:测试执行规则解析失败时,错误里会带上配置来源。

数据流:进去的是缺少 decision 的非法 rules TOML 和系统文件来源 → try_from 解析执行策略失败 → 断言错误包含同一来源和具体原因。

调用关系:覆盖 ConfigRequirements::try_from 对 exec_policy 解析错误的包装。

调用图:外部调用 5 个(try_from, assert_eq!, default, system_requirements_toml_file_for_test, from_str)。

config/src/requirements_layers/layer.rs源码 ↗
configconfig load / requirements composition

这里的“层”可以理解成一张透明胶片:每张胶片上写了一部分配置,最后要一层层叠起来。这个文件先接收一层配置,配置可能是 TOML 字符串,也可能已经是解析好的 TOML 值。它会把同一份内容解析两遍:一遍保留成普通配置,另一遍读成项目认识的“需求配置”结构。像 rules、hooks、permissions 这类字段不是普通键值,合并方式更讲究,所以会被单独拿出来保存。远程沙箱配置也会先根据机器名算出真正允许的沙箱模式,再写回普通配置里。最后,它会从普通 TOML 里删掉这些特殊字段,避免后面重复处理。

函数细节11
RequirementsLayerEntry::from_toml19–25 ↗
fn from_toml(source: RequirementSource, contents: impl Into<String>) -> Self

作用:把一段 TOML 文本包装成一个配置层入口。有人从文件或字符串读到配置内容时,会用它先做成统一格式,方便后面继续处理。

数据流:进去的是配置来源说明和一段可转成字符串的 TOML 内容 → 它把内容转成字符串,记录来源,并把基准目录先留空 → 出来的是一个 RequirementsLayerEntry,表示“这是一层还没正式解析的配置”。

调用关系:它通常由 load_requirements_toml 或 layer 在读到文本配置时调用。之后这个入口会交给 ComposableRequirementsLayer::from_entry,真正解析和整理里面的内容。

调用图:被 2 处调用(load_requirements_toml, layer);外部调用 2 个(into, String)。

RequirementsLayerEntry::from_toml_value27–33 ↗
fn from_toml_value(source: RequirementSource, value: TomlValue) -> Self

作用:把已经解析好的 TOML 值包装成配置层入口。这样旧格式或别的流程已经解析过配置时,就不用再绕回字符串。

数据流:进去的是配置来源说明和一个 TomlValue(已经解析好的 TOML 数据)→ 它直接保存这个值,记录来源,并把基准目录先留空 → 出来的是同样可被后续合成流程使用的 RequirementsLayerEntry。

调用关系:它会被 requirements_layers_from_legacy_scheme 用来接收旧方案转换出的配置层。后续和文本配置一样,都进入 ComposableRequirementsLayer::from_entry。

调用图:被 1 处调用(requirements_layers_from_legacy_scheme);外部调用 1 个(Value)。

RequirementsLayerEntry::with_base_dir35–38 ↗
fn with_base_dir(mut self, base_dir: AbsolutePathBuf) -> Self

作用:给这一层配置补上一个“基准目录”。如果配置里有相对路径,就需要知道它应该相对于哪个目录来理解。

数据流:进去的是一个已有的 RequirementsLayerEntry 和一个绝对路径目录 → 它把这个目录写进 entry 的 base_dir 字段 → 出来的是带了基准目录的新 entry,本身没有解析 TOML。

调用关系:它通常接在 from_toml 或 from_toml_value 之后使用,相当于给这层配置贴上“路径从这里算起”的标签。真正用到这个标签的是 ComposableRequirementsLayer::from_entry 解析时的临时路径保护逻辑。

ComposableRequirementsLayer::from_entry55–92 ↗
fn from_entry(
        layer: RequirementsLayerEntry,
        hostname_resolver: &dyn Fn() -> Option<String>,
    ) -> Result<Self, RequirementsCompositionError>

作用:把一个原始配置层入口变成可参与合并的配置层。它会解析 TOML、处理远程沙箱设置、分离特殊字段,并把普通配置整理干净。

数据流:进去的是 RequirementsLayerEntry,以及一个按需获取主机名的函数 → 它先按基准目录解析普通 TOML,再解析成需求配置结构;如果存在远程沙箱配置,才尝试取主机名并计算真实的沙箱模式;随后把结果写回普通 TOML,删掉特殊字段 → 出来的是 ComposableRequirementsLayer,里面包含干净的普通配置和单独保存的规则、钩子、权限。

调用关系:它由 add_layer 在往配置栈里加一层时调用,是这一文件的核心入口。它把解析工作交给 parse_layer_toml 和 parse_layer_requirements,把远程沙箱落地交给 materialize_remote_sandbox_config,把清理特殊字段交给 strip_special_fields。

调用图:调用 4 个内部函数(materialize_remote_sandbox_config, parse_layer_requirements, parse_layer_toml, strip_special_fields);被 1 处调用(add_layer)。

parse_layer_toml102–117 ↗
fn parse_layer_toml(
    toml: &RequirementsLayerToml,
    source: &RequirementSource,
) -> Result<TomlValue, RequirementsCompositionError>

作用:把这一层配置读成通用的 TOML 数据。这里的 TOML 可以理解成一种常见的配置文件格式,像表格一样保存键和值。

数据流:进去的是一层配置的原始 TOML 表示,以及它来自哪里 → 如果是字符串,就调用 TOML 解析器读成 TomlValue;如果本来就是 TomlValue,就复制一份 → 出来的是普通 TOML 数据;如果解析失败,出来的是带来源信息的错误。

调用关系:它只被 ComposableRequirementsLayer::from_entry 调用,用来保留一份“普通配置版本”。这份结果后面会被 materialize_remote_sandbox_config 修改,再被 strip_special_fields 清理。

调用图:被 1 处调用(from_entry);外部调用 1 个(from_str)。

parse_layer_requirements119–141 ↗
fn parse_layer_requirements(
    toml: &RequirementsLayerToml,
    source: &RequirementSource,
) -> Result<ConfigRequirementsToml, RequirementsCompositionError>

作用:把同一层配置读成项目专用的需求配置结构。这样代码才能理解 rules、hooks、permissions、remote_sandbox_config 这些有特殊含义的字段。

数据流:进去的是一层配置的原始 TOML 表示,以及来源说明 → 如果是字符串,就直接反序列化成 ConfigRequirementsToml;如果是已有 TOML 值,就尝试转换成 ConfigRequirementsToml → 出来的是结构化的需求配置;失败时返回带来源的解析错误。

调用关系:它只被 ComposableRequirementsLayer::from_entry 调用。from_entry 用它的结果来计算远程沙箱配置,并把规则、钩子、权限这些字段单独放进 domain_fields。

调用图:被 1 处调用(from_entry);外部调用 1 个(from_str)。

materialize_remote_sandbox_config143–159 ↗
fn materialize_remote_sandbox_config(
    layer_toml: &mut TomlValue,
    requirements: &ConfigRequirementsToml,
) -> Result<(), RequirementsCompositionError>

作用:把“远程沙箱配置”变成最终普通配置里真正使用的 allowed_sandbox_modes。沙箱可以理解成给程序活动范围加围栏,限制它能做什么。

数据流:进去的是可修改的普通 TOML,以及已经解析好的需求配置 → 它先从普通 TOML 顶层删掉 remote_sandbox_config;如果需求配置里已经算出了 allowed_sandbox_modes,就把它转成 TOML 值并写回顶层 → 出来时普通 TOML 被更新;如果转换失败,返回组合后的解析错误。

调用关系:它由 ComposableRequirementsLayer::from_entry 在应用远程沙箱规则之后调用。它内部用 remove_top_level_field 删除旧字段,用 toml_value_from_serializable 把 Rust 数据转回 TOML。

调用图:调用 2 个内部函数(remove_top_level_field, toml_value_from_serializable);被 1 处调用(from_entry);外部调用 1 个(as_table_mut)。

toml_value_from_serializable161–167 ↗
fn toml_value_from_serializable(
    value: T,
) -> Result<TomlValue, RequirementsCompositionError>

作用:把一个可以序列化的数据转成 TOML 值。序列化就是把程序里的结构变成配置文件能表达的数据。

数据流:进去的是任意实现了 serde::Serialize 的值 → 它尝试把这个值转换成 TomlValue → 出来的是 TOML 值;如果转换失败,就包装成 RequirementsCompositionError::ComposedParse 错误。

调用关系:它被 materialize_remote_sandbox_config 调用,专门负责把 allowed_sandbox_modes 这种程序里的结构放回普通 TOML 配置中。

调用图:被 1 处调用(materialize_remote_sandbox_config);外部调用 1 个(try_from)。

strip_special_fields169–173 ↗
fn strip_special_fields(layer_toml: &mut TomlValue)

作用:从普通 TOML 里删掉需要特殊合并的字段,防止它们后面被当作普通配置重复处理。

数据流:进去的是可修改的普通 TOML → 它删除顶层 rules、hooks,并删除 permissions.filesystem.deny_read 这个深层字段;如果删完后中间表变空,还会顺手清掉空壳 → 出来时这份 TOML 只保留适合普通合并的内容。

调用关系:它由 ComposableRequirementsLayer::from_entry 在特殊字段已经单独保存后调用。它把顶层删除交给 remove_top_level_field,把嵌套删除交给 remove_nested_field_and_prune_empty。

调用图:调用 2 个内部函数(remove_nested_field_and_prune_empty, remove_top_level_field);被 1 处调用(from_entry)。

remove_top_level_field175–177 ↗
fn remove_top_level_field(value: &mut TomlValue, key: &str) -> Option<TomlValue>

作用:从 TOML 最外层删除一个指定字段。它是一个小工具,用来避免到处重复写“先确认是表,再删键”的代码。

数据流:进去的是可修改的 TOML 值和一个字段名 → 如果这个 TOML 值是表,就从表的最外层移除这个字段;如果不是表,就什么也不做 → 出来的是被删掉的值,或者没有删到时返回空。

调用关系:它被 materialize_remote_sandbox_config 用来删除 remote_sandbox_config,也被 strip_special_fields 用来删除 rules 和 hooks。它是清理普通 TOML 时最基础的橡皮擦。

调用图:被 2 处调用(materialize_remote_sandbox_config, strip_special_fields);外部调用 1 个(as_table_mut)。

remove_nested_field_and_prune_empty179–197 ↗
fn remove_nested_field_and_prune_empty(value: &mut TomlValue, path: &[&str]) -> Option<TomlValue>

作用:删除 TOML 里一条多层路径上的字段,并清理删完后留下的空表。比如删掉 permissions.filesystem.deny_read 后,如果 filesystem 变空,也会移走。

数据流:进去的是可修改的 TOML 值,以及一串路径名 → 它沿着路径一层层往下找,找到最后一个键就删除;返回时再检查沿途的表,如果某层已经空了,就把空表也删掉 → 出来的是被删除的值,或者找不到时返回空,同时原 TOML 被整理干净。

调用关系:它被 strip_special_fields 调用,用来处理普通顶层删除做不到的深层字段。这个函数让特殊权限字段被拿走后,不会在配置里留下没意义的空架子。

调用图:被 1 处调用(strip_special_fields);外部调用 1 个(as_table_mut)。

字段专属合并策略

这些文件实现特殊的按字段组合规则,在堆叠需求层时覆盖普通 TOML 合并。

config/src/requirements_layers/permissions.rs源码 ↗
domain_logicconfig merge

这个文件解决的是一个很具体的安全配置问题:不同来源的配置会一层层合并,普通配置通常是“后来的覆盖前面的”,但“禁止读取文件”的规则不能这么做。比如一层说不能读密钥文件,另一层说不能读日志文件,最终应该两个都不能读,而不是只保留最后一个。这里的 DenyReadMergeState 就像一个临时购物篮,先把各层里的 deny_read 条目收集起来,并去掉重复项,同时记住这些条目来自哪些配置来源。最后 apply_to 再把这个购物篮里的内容放回真正的 permissions 配置里。如果原来没有权限配置,它会新建一份;如果已经有,就把缺少的禁止读取规则补进去。文件还会合并来源信息,方便之后知道最终规则是由哪些配置共同形成的。

函数细节3
DenyReadMergeState::merge20–39 ↗
fn merge(
        &mut self,
        incoming: Option<PermissionsRequirementsToml>,
        source: &RequirementSource,
    )

作用:把某一层配置里的 permissions.filesystem.deny_read 取出来,加入当前的临时合并状态。它只追加新规则,不会加入重复项,也不会覆盖已有规则。

数据流:进去的是一份可能存在的权限配置 incoming,以及这份配置的来源 source。它一路检查里面有没有 filesystem.deny_read,而且列表不能是空的;如果没有,就什么也不做。若有,它逐个看禁止读取规则:当前状态里还没有的就放进去,并记录这条规则的来源。出来后,DenyReadMergeState 里的 deny_read 清单可能变长,source 也可能被更新成合并后的来源。

调用关系:它是在多层需求配置合并时被调用的收集步骤。每读到一层权限配置,就用它把 deny_read 规则先攒起来;当新增规则时,它会把来源记录工作交给 DenyReadMergeState::merge_source。真正把结果写回最终配置的事,则由后面的 DenyReadMergeState::apply_to 完成。

调用图:调用 1 个内部函数(merge_source)。

DenyReadMergeState::apply_to41–73 ↗
fn apply_to(self, target: &mut Option<Sourced<PermissionsRequirementsToml>>)

作用:把前面收集好的 deny_read 规则写进最终的权限配置里。它负责把临时清单落地,保证最终配置真的包含所有应禁止读取的文件规则。

数据流:进去的是已经收集好的 DenyReadMergeState,以及目标权限配置 target。它先看临时 deny_read 清单是不是空的;空的话直接结束。非空时,它确定这些规则的来源,如果没记录来源就用 Unknown。然后看 target 是否已有权限配置:没有就新建一份只包含 filesystem.deny_read 的配置;已有就找到或创建 filesystem 区块,再找到或创建 deny_read 列表,把缺少的规则追加进去。最后,如果目标配置原来的来源和这次来源不同,它会把来源合成为一个复合来源。结果是 target 被原地更新,包含合并后的禁止读取规则和更完整的来源信息。

调用关系:它是这个文件里的收尾步骤,通常在所有配置层都用 DenyReadMergeState::merge 收集完之后执行。它会用 Sourced::new 创建带来源的配置,也会用 RequirementSource::composite 把多个来源合在一起,让最终配置既有内容,也能追溯来处。

调用图:调用 2 个内部函数(composite, new);外部调用 1 个(default)。

DenyReadMergeState::merge_source75–81 ↗
fn merge_source(&mut self, source: &RequirementSource)

作用:更新临时状态里记录的配置来源。第一次看到来源时直接保存,之后再看到新的来源时就把它们合成一个“来自多个地方”的来源。

数据流:进去的是当前状态 self 和一个新的 source。它先看 self.source 有没有值:没有就复制这次 source 存进去;已经有的话,就调用 merge_output_source,把已有来源和新来源合并。出来后,self.source 会表示目前收集到的 deny_read 规则来自哪里,可能是单一来源,也可能是多个来源的组合。

调用关系:它只被 DenyReadMergeState::merge 调用,是 merge 发现新 deny_read 规则时顺手做的来源记录工作。具体怎么把两个来源合成一个更准确的来源,它交给 merge_output_source 去处理。

调用图:调用 1 个内部函数(merge_output_source);被 1 处调用(merge);外部调用 1 个(clone)。

config/src/requirements_layers/hooks.rs源码 ↗
domain_logicconfig load

项目里的 requirements 可以来自多层配置,就像一摞透明纸叠起来看最终规则。hook 事件适合追加:上一层有一个“工具使用前执行 A”,下一层再加一个 B,两个都保留。但 hook 的托管目录不一样,它像“仓库地址”,当前操作系统上只能认一个;如果两层给了不同地址,就宁可报错,也不猜该用谁。这个文件用 HookDirectoryField 区分普通 managed_dir 和 Windows 专用 windows_managed_dir,并能判断当前平台该看哪个。HookMergeState 记录合并过程中已经见过的目录和值来自哪一层,方便冲突时报清楚来源。合并时,它先处理当前平台的目录:相同就没事,不同就报冲突;再处理非当前平台的目录:只在空着时填上,方便同一套配置同时带着不同系统的目录。最后,所有 hook 事件列表都会按顺序追加进去。

函数细节12
HookDirectoryField::current_platform25–31 ↗
fn current_platform() -> Self

作用:判断当前运行的机器该使用哪个 hook 目录字段。Windows 用 windows_managed_dir,其他系统用 managed_dir。

数据流:它不接收外部参数,只读取编译时的系统判断 → 如果目标是 Windows,就返回 WindowsManagedDir;否则返回 ManagedDir → 后续合并配置时就知道哪个目录是“当前真正会生效的目录”。

调用关系:compose_requirements_for_hostname 和 compose_requirements_with_hostname_resolver 在开始组合配置时会用它选出当前平台字段。这个选择会传给 HookMergeState,影响后面遇到目录冲突时是严格报错,还是只做备用填充。

调用图:被 2 处调用(compose_requirements_for_hostname, compose_requirements_with_hostname_resolver);外部调用 1 个(cfg!)。

HookDirectoryField::field_name33–38 ↗
fn field_name(self) -> &'static str

作用:把内部的目录字段枚举变成人能看懂的配置名。主要用于报错,让用户知道到底是哪一项冲突了。

数据流:输入是一个 HookDirectoryField → 它把 ManagedDir 转成 hooks.managed_dir,把 WindowsManagedDir 转成 hooks.windows_managed_dir → 输出一段固定文字,用在冲突信息里。

调用关系:merge_active_singleton 在发现当前平台目录冲突时会调用它。这样 composition_conflict 生成的错误不会只说“某个字段错了”,而是明确指出具体配置项。

调用图:被 1 处调用(merge_active_singleton)。

HookDirectoryField::inactive40–45 ↗
fn inactive(self) -> Self

作用:找出“不是当前这个”的另一个目录字段。比如当前是普通 managed_dir,就返回 Windows 专用字段。

数据流:输入一个目录字段 → 它做一次二选一切换 → 输出另一个字段,用来表示当前平台暂时不会用到的目录配置。

调用关系:HookMergeState::merge 用它把目录分成当前平台字段和非当前平台字段。当前平台字段要严格防冲突,非当前平台字段则只在空着时补上。

HookMergeState::new54–59 ↗
fn new(directory_field: HookDirectoryField) -> Self

作用:创建一个 hook 合并器,准备开始把多层 hook 配置叠加到一起。它会记住当前平台应该使用哪个目录字段。

数据流:输入当前平台的目录字段 → 它建立一个空的来源记录表,并保存这个字段 → 输出新的 HookMergeState,供后续每合并一层配置时使用。

调用关系:compose 在开始合并 requirements 时会创建它。之后每读到一层 hook 配置,都会交给 HookMergeState::merge 处理。

调用图:被 1 处调用(compose);外部调用 1 个(new)。

HookMergeState::merge61–107 ↗
fn merge(
        &mut self,
        target: &mut Option<Sourced<ManagedHooksRequirementsToml>>,
        incoming: Option<ManagedHooksRequirementsToml>,
        source: &RequirementSource,
    ) -> Re

作用:把一层新的 hook 配置合进已有结果里。这是本文件的核心函数,负责追加事件、填目录、发现当前平台目录冲突。

数据流:输入已有合并结果 target、新来的 hook 配置 incoming、以及这层配置的来源 source → 如果新配置为空就直接跳过;如果之前还没有结果,就保存它并记录目录来源;如果已有结果,就取出当前平台和非当前平台的目录分别处理,再把所有 hook 事件列表追加进去 → 输出成功或冲突错误,并可能修改 target 和来源信息。

调用关系:外层 requirements 合并流程会反复调用它,每次处理一层配置。它把具体小活分给 take_hook_dir、hook_dir_mut、merge_active_singleton、fill_singleton、append_hook_events;只要最终内容变了,就调用 merge_output_source 更新“这个结果来自哪些层”的来源说明。

调用图:调用 8 个内部函数(new, fill_singleton, merge_active_singleton, track_singleton_source, append_hook_events, hook_dir_mut, take_hook_dir, merge_output_source);外部调用 1 个(clone)。

HookMergeState::track_singleton_source109–120 ↗
fn track_singleton_source(
        &mut self,
        field: HookDirectoryField,
        value: &Option<PathBuf>,
        source: &RequirementSource,
    )

作用:记录某个 hook 目录值最早是从哪一层配置来的。这样后面如果冲突,错误信息能指出双方来源。

数据流:输入目录字段、该字段当前值、以及配置来源 → 如果这个字段有值,就在来源表里登记;如果已经登记过,就保留最早的来源 → 它不返回新数据,只更新 HookMergeState 内部记录。

调用关系:HookMergeState::merge 在第一次接收 hook 配置时会调用它记录 managed_dir 和 windows_managed_dir 的来源。之后 merge_active_singleton 发现冲突时,会查这些记录来生成更准确的错误。

调用图:被 1 处调用(merge)。

HookMergeState::merge_active_singleton122–160 ↗
fn merge_active_singleton(
        &mut self,
        field: HookDirectoryField,
        existing: &mut Option<PathBuf>,
        incoming: Option<PathBuf>,
        incoming_source: &RequirementSource,

作用:合并当前平台真正会用的 hook 目录。它的规则很严格:没有旧值就填上;旧值相同就接受;旧值不同就报冲突。

数据流:输入字段名、已有目录、新来的目录、新配置来源 → 如果新来的目录为空,就什么都不做;如果已有目录和新目录不同,就找出旧目录来源并生成冲突错误;如果已有为空,就写入新目录并记录来源 → 输出是否改变了结果,或者输出合并失败的错误。

调用关系:HookMergeState::merge 在处理当前平台字段时调用它。它会用 field_name 给错误标明配置名,并把真正的错误构造交给 composition_conflict。

调用图:调用 2 个内部函数(field_name, composition_conflict);被 1 处调用(merge);外部调用 2 个(clone, format!)。

HookMergeState::fill_singleton162–180 ↗
fn fill_singleton(
        &mut self,
        field: HookDirectoryField,
        existing: &mut Option<PathBuf>,
        incoming: Option<PathBuf>,
        incoming_source: &RequirementSource,
    ) -

作用:合并非当前平台的 hook 目录,但只做“空位填充”。它不会因为后来的不同值覆盖已有值。

数据流:输入字段名、已有目录、新来的目录、新配置来源 → 如果已有目录为空且新目录存在,就填进去并记录来源;否则保持原样 → 输出一个布尔值,表示这次有没有真的改动。

调用关系:HookMergeState::merge 用它处理当前系统不会直接使用的目录字段。这样同一套配置可以携带 Windows 和非 Windows 的目录,但不会让备用字段的差异打断当前平台的配置合并。

调用图:被 1 处调用(merge)。

take_hook_dir183–191 ↗
fn take_hook_dir(
    hooks: &mut ManagedHooksRequirementsToml,
    field: HookDirectoryField,
) -> Option<PathBuf>

作用:从一份 hook 配置里取出指定的目录字段,并把原位置清空。这样后续处理事件时不会重复碰这个目录值。

数据流:输入一份可修改的 ManagedHooksRequirementsToml 和要取的字段 → 它根据字段拿走 managed_dir 或 windows_managed_dir 的值 → 输出这个目录值,原配置里的对应位置变成空。

调用关系:HookMergeState::merge 在拆分新来的配置时调用它。取出的当前平台目录交给 merge_active_singleton,非当前平台目录交给 fill_singleton。

调用图:被 1 处调用(merge)。

hook_dir_mut193–201 ↗
fn hook_dir_mut(
    hooks: &mut ManagedHooksRequirementsToml,
    field: HookDirectoryField,
) -> &mut Option<PathBuf>

作用:找到已有 hook 配置里某个目录字段的位置,并给调用者一个可修改入口。它相当于帮忙指到正确的抽屉。

数据流:输入一份可修改的 ManagedHooksRequirementsToml 和字段类型 → 它选择 managed_dir 或 windows_managed_dir → 输出这个字段的可修改引用,让别的函数能直接填值或检查值。

调用关系:HookMergeState::merge 调用它拿到已有结果中的目录位置,然后把这个位置交给 merge_active_singleton 或 fill_singleton 去做实际合并。

调用图:被 1 处调用(merge)。

append_hook_events203–231 ↗
fn append_hook_events(existing: &mut HookEventsToml, incoming: HookEventsToml) -> bool

作用:把新配置里的所有 hook 事件追加到已有配置后面。它保证这些事件是“越叠越多”,不会互相覆盖。

数据流:输入已有的 HookEventsToml 和新来的 HookEventsToml → 它把新来的每一种事件列表逐个拆出来,比如 pre_tool_use、post_tool_use、session_start 等,再分别追加到已有列表末尾 → 输出是否有任何列表发生了变化。

调用关系:HookMergeState::merge 在目录处理完后调用它合并事件。它把每个具体列表的追加动作交给 append_vec;代码故意逐项列出所有事件,目的是以后新增事件类型时,开发者必须明确决定它该怎么合并。

调用图:调用 1 个内部函数(append_vec);被 1 处调用(merge)。

append_vec233–237 ↗
fn append_vec(existing: &mut Vec<T>, mut incoming: Vec<T>) -> bool

作用:把一个列表接到另一个列表后面,并告诉调用者这次有没有真的加东西。

数据流:输入已有列表和新列表 → 它先看新列表是不是空的,然后把新列表里的元素搬到已有列表末尾 → 输出 true 或 false,表示已有列表是否因为这次追加而改变。

调用关系:append_hook_events 会反复调用它,分别追加每一种 hook 事件列表。它是最底层的小工具,让上层不用重复写同样的“追加并判断是否改变”的代码。

调用图:被 1 处调用(append_hook_events)。

config/src/requirements_layers/rules.rs源码 ↗
configconfig load

项目的配置可能来自多层,比如默认配置、用户配置、项目配置。这个文件解决的问题是:当这些层都写了 requirements 的执行规则时,应该怎么拼在一起。这里的做法很直白:新来的规则如果不存在,就什么也不做;如果目标里还没有规则,就把新规则连同它的来源一起放进去;如果目标里已经有规则,就把新规则追加到已有规则后面。这样规则像排队一样保留下来,高优先级的规则先进入队伍,最后的顺序仍然能看出谁更重要。同时,它还会合并“来源”信息,让之后报错或查看配置时知道这些规则是从哪些地方来的。

函数细节1
merge10–26 ↗
fn merge(
    target: &mut Option<Sourced<RequirementsExecPolicyToml>>,
    incoming: Option<RequirementsExecPolicyToml>,
    source: &RequirementSource,
)

作用:把一层新读到的 requirements 执行规则合并到已经收集好的规则里。有人会用它来把多份配置拼成一份最终配置,并且保留规则顺序和来源信息。

数据流:进去的是一个可修改的目标配置、一个可能存在的新配置、以及新配置来自哪里。它先看新配置有没有内容;没有就直接结束。有内容但目标还是空的,就用 Sourced::new 把规则和来源包在一起放进去。目标已有内容时,它会把 incoming 里的 prefix_rules 追加到 existing.value.prefix_rules 后面,并用 merge_output_source 把来源记录也合并进去。出来的结果是 target 被更新,规则更多了,来源也更完整了。

调用关系:它是在 compose 组合多层 requirements 配置时被调用的一个小步骤。它自己不决定哪些层优先,只负责按收到的顺序把规则加进去;需要创建带来源的数据时交给 Sourced::new,需要合并来源说明时交给 merge_output_source。

调用图:调用 2 个内部函数(new, merge_output_source);被 1 处调用(compose);外部调用 1 个(clone)。

栈组装和下游钩子状态

这些文件组装完整的分层需求结果,然后从生成的配置栈派生与钩子相关的有效持久化状态。

config/src/requirements_layers/stack.rs源码 ↗
domain_logicconfig load

可以把这里想成一叠透明胶片:底下的是默认要求,上面的是更高优先级的要求。普通字段按常见规则合并:低优先级先给默认值,高优先级再覆盖。但有些字段不能这么粗暴,比如远程沙箱配置要先按当前主机名判断,规则要让高优先级排前面,钩子要检查是否冲突,文件读取黑名单要做并集。这个文件的主流程就是:先把每一层变成可合并的内部格式;再把普通 TOML 配置合起来;然后单独处理规则、钩子、拒绝读取权限这些特殊字段;最后给每个结果标上“来自哪一层”。这些来源信息很重要,出错或审计时能知道是哪份配置影响了最终结果。

函数细节14
Error::from53–55 ↗
fn from(error: RequirementsCompositionError) -> Self

作用:把这里专用的合并错误转换成标准的输入输出错误。这样外层代码即使只认识普通 io 错误,也能接住配置合并失败。

数据流:进去的是一个 RequirementsCompositionError,也就是“要求配置合并时出了什么错” → 它把这个错误包进 io::Error,并标记为 InvalidData,意思是数据内容不合法 → 出来的是标准 io::Error,原始错误信息还在里面。

调用关系:它是错误转换的小桥梁。配置读取或加载流程如果需要返回 io::Error,就会借它把本文件里的专业错误换成更通用的错误类型。

调用图:外部调用 1 个(new)。

compose_requirements58–62 ↗
fn compose_requirements(
    layers: impl IntoIterator<Item = RequirementsLayerEntry>,
) -> Result<Option<ConfigRequirementsWithSources>, RequirementsCompositionError>

作用:这是正常代码最常用的入口:给它一组要求配置层,它返回合成后的最终要求。它会使用真实的本机主机名来判断哪些远程沙箱配置该生效。

数据流:进去的是按优先级排列的一串 RequirementsLayerEntry → 它把这些层交给带主机名解析能力的内部函数 → 出来的是可能存在的 ConfigRequirementsWithSources;如果所有层都没有有效内容,就返回 None;如果解析或合并失败,就返回错误。

调用关系:它站在公开入口的位置,不自己干复杂合并,而是把活交给 compose_requirements_with_hostname_resolver。这样正常运行用真实主机名,测试时可以换成假的主机名。

调用图:调用 1 个内部函数(compose_requirements_with_hostname_resolver)。

compose_requirements_for_hostname65–75 ↗
fn compose_requirements_for_hostname(
    layers: impl IntoIterator<Item = RequirementsLayerEntry>,
    hostname: Option<&str>,
) -> Result<Option<ConfigRequirementsWithSources>, RequirementsCompositi

作用:这是测试用的入口,允许测试代码指定一个主机名。这样不用真的改机器名字,也能检查远程沙箱匹配逻辑对不对。

数据流:进去的是一组配置层和一个可选主机名 → 它把主机名保存成闭包,每次需要时返回同一个值,并使用当前平台默认的钩子目录字段 → 出来的是按这个假主机名合成后的要求,或合并错误。

调用关系:它只在测试中启用。它把工作交给 compose_requirements_with_hostname_resolver_and_hook_directory,同时调用 HookDirectoryField::current_platform 来模拟真实平台默认行为。

调用图:调用 2 个内部函数(current_platform, compose_requirements_with_hostname_resolver_and_hook_directory)。

compose_requirements_for_hostname_and_hook_directory78–89 ↗
fn compose_requirements_for_hostname_and_hook_directory(
    layers: impl IntoIterator<Item = RequirementsLayerEntry>,
    hostname: Option<&str>,
    hook_directory_field: HookDirectoryField,
) -> Re

作用:这是更细的测试入口,既能指定主机名,也能指定钩子目录字段。它方便测试不同操作系统或目录规则下的合并行为。

数据流:进去的是配置层、可选主机名、指定的 HookDirectoryField → 它把主机名变成可重复调用的解析器,并把指定钩子目录规则一起传下去 → 出来的是合成结果或错误。

调用关系:它服务于测试场景,把所有可变条件都交给 compose_requirements_with_hostname_resolver_and_hook_directory,让测试能精确控制环境。

调用图:调用 1 个内部函数(compose_requirements_with_hostname_resolver_and_hook_directory)。

compose_requirements_with_hostname_resolver91–100 ↗
fn compose_requirements_with_hostname_resolver(
    layers: impl IntoIterator<Item = RequirementsLayerEntry>,
    hostname_resolver: impl Fn() -> Option<String>,
) -> Result<Option<ConfigRequirementsW

作用:这是内部过渡函数:它允许调用者提供“怎么拿主机名”的方法,但钩子目录仍使用当前平台默认值。

数据流:进去的是配置层和一个主机名解析函数 → 它取当前平台对应的 HookDirectoryField,再把两者传给更底层的合成函数 → 出来的是最终要求、None 或错误。

调用关系:compose_requirements 会调用它。它本身不直接合并,只是补齐默认钩子目录设置,然后转交给 compose_requirements_with_hostname_resolver_and_hook_directory。

调用图:调用 2 个内部函数(current_platform, compose_requirements_with_hostname_resolver_and_hook_directory);被 1 处调用(compose_requirements)。

compose_requirements_with_hostname_resolver_and_hook_directory102–116 ↗
fn compose_requirements_with_hostname_resolver_and_hook_directory(
    layers: impl IntoIterator<Item = RequirementsLayerEntry>,
    hostname_resolver: impl Fn() -> Option<String>,
    hook_directory_

作用:这是合并流程的核心调度入口。它把主机名解析、钩子目录规则和多层配置一起送进一个 RequirementsLayerStack 里。

数据流:进去的是配置层、主机名解析函数、钩子目录字段 → 它用 OnceCell 缓存主机名,意思是只有真正需要时才查一次;然后新建 RequirementsLayerStack,把每一层加入栈中 → 最后调用栈的 compose,出来最终合并结果或错误。

调用关系:测试入口和正常入口最终都会来到这里。它负责搭好“合并机器”,但每层如何转成可合并格式交给 RequirementsLayerStack::add_layer,真正合并交给 RequirementsLayerStack::compose。

调用图:调用 1 个内部函数(new);被 3 处调用(compose_requirements_for_hostname, compose_requirements_for_hostname_and_hook_directory, compose_requirements_with_hostname_resolver);外部调用 1 个(new)。

RequirementsLayerStack::new124–129 ↗
fn new(hook_directory_field: HookDirectoryField) -> Self

作用:创建一个空的要求配置层栈。这个栈像一个临时篮子,先收集所有配置层,再统一合并。

数据流:进去的是钩子目录字段,也就是判断 hooks 里哪个目录字段适用于当前环境的依据 → 它创建一个空 layers 列表,并保存这个目录规则 → 出来的是新的 RequirementsLayerStack。

调用关系:compose_requirements_with_hostname_resolver_and_hook_directory 会先调用它。之后每个输入层会通过 RequirementsLayerStack::add_layer 放进来。

调用图:被 1 处调用(compose_requirements_with_hostname_resolver_and_hook_directory);外部调用 1 个(new)。

RequirementsLayerStack::add_layer131–141 ↗
fn add_layer(
        &mut self,
        layer: RequirementsLayerEntry,
        hostname_resolver: &dyn Fn() -> Option<String>,
    ) -> Result<(), RequirementsCompositionError>

作用:把一层原始要求配置加入栈中,并在加入前做必要的解析和预处理。这样后面的合并不用再面对原始文本或未判断的条件。

数据流:进去的是一个 RequirementsLayerEntry 和主机名解析器 → 它调用 ComposableRequirementsLayer::from_entry,把这一层转换成可合并的形式;其中可能会按主机名判断远程沙箱配置是否适用 → 成功后这一层被追加到栈里;失败则返回解析错误。

调用关系:compose_requirements_with_hostname_resolver_and_hook_directory 在遍历所有层时调用它。它把单层预处理交给 from_entry,自己负责把结果放入栈中。

调用图:调用 1 个内部函数(from_entry)。

RequirementsLayerStack::compose143–187 ↗
fn compose(
        self,
    ) -> Result<Option<ConfigRequirementsWithSources>, RequirementsCompositionError>

作用:真正把栈里的所有要求配置合成一份最终结果。它既处理普通字段,也处理规则、钩子、拒绝读取文件这类特殊字段。

数据流:进去的是已经装好多层配置的 RequirementsLayerStack → 它先从低优先级到高优先级合并普通 TOML 字段,再解析成 ConfigRequirementsToml;接着把普通字段填进带来源的输出;然后从高优先级到低优先级合并规则、hooks 和 deny_read,保证高优先级内容排在前面 → 出来的是带来源信息的最终要求;如果最终为空则返回 None,冲突或解析失败则返回错误。

调用关系:这是整个文件的主发动机。它调用 merge_toml_values 合并普通配置,调用 populate_merged_regular_fields_with_sources 标来源,又把特殊字段分别交给 rules、HookMergeState 和 DenyReadMergeState 处理。

调用图:调用 4 个内部函数(merge_toml_values, new, merge, populate_merged_regular_fields_with_sources);外部调用 4 个(Table, default, default, new)。

populate_merged_regular_fields_with_sources190–266 ↗
fn populate_merged_regular_fields_with_sources(
    output: &mut ConfigRequirementsWithSources,
    requirements: ConfigRequirementsToml,
    layers: &[ComposableRequirementsLayer],
)

作用:把已经合并好的普通要求字段填进最终输出,并给每个字段标记“它主要来自哪一层配置”。

数据流:进去的是待填写的输出对象、已经合并并解析好的 ConfigRequirementsToml、以及所有配置层 → 它逐个检查普通字段:如果字段有值,就放进 output,并用 source_for_top_level_keys 找到来源;guardian_policy_config 还会额外忽略空白字符串 → 出来没有单独返回值,但 output 被填上了值和来源。

调用关系:RequirementsLayerStack::compose 在普通 TOML 合并完成后调用它。它只处理普通字段,hooks、rules、remote_sandbox_config 等特殊字段刻意不在这里处理。

调用图:调用 2 个内部函数(new, source_for_top_level_keys);被 1 处调用(compose);外部调用 1 个(set_sourced!)。

source_for_top_level_keys268–297 ↗
fn source_for_top_level_keys(
    layers: &[ComposableRequirementsLayer],
    keys: &[&str],
) -> RequirementSource

作用:找出某个顶层配置字段最终应该标记为来自哪份配置。对于表格类字段,如果多层一起贡献了内容,它会生成一个“组合来源”。

数据流:进去的是所有已处理配置层,以及一个或多个可能的顶层字段名 → 它从低到高找哪些层包含这个字段,最后一个命中的通常是赢家;如果赢家的值不是表格,就直接返回赢家来源;如果是表格,并且多层表格一起合并过,就返回 composite 来源,表示多个来源共同组成 → 出来的是 RequirementSource。

调用关系:populate_merged_regular_fields_with_sources 给每个普通字段标来源时会调用它。它内部依靠 top_level_value_for_keys 找字段值,并在需要时调用 RequirementSource::composite 合成来源。

调用图:调用 1 个内部函数(composite);被 1 处调用(populate_merged_regular_fields_with_sources);外部调用 1 个(iter)。

top_level_value_for_keys299–302 ↗
fn top_level_value_for_keys(value: &'a TomlValue, keys: &[&str]) -> Option<&'a TomlValue>

作用:在一份 TOML 配置的最外层,按给定字段名找第一个存在的值。它是一个很小的查找工具。

数据流:进去的是一个 TOML 值和一组字段名 → 它先确认这个 TOML 值是表格;如果不是表格就找不到;如果是表格,就按字段名顺序查找 → 出来的是找到的 TOML 值引用,或者 None。

调用关系:source_for_top_level_keys 用它判断每一层是否包含某个字段。它不参与合并,只负责安全地看一眼顶层字段。

调用图:外部调用 1 个(as_table)。

merge_output_source304–308 ↗
fn merge_output_source(existing: &mut RequirementSource, incoming: &RequirementSource)

作用:把两个来源标记合在一起。它用于某个输出结果其实由多份配置共同贡献时,避免只记住其中一个来源。

数据流:进去的是一个可修改的 existing 来源和一个 incoming 来源 → 如果两者相同,它什么也不改;如果不同,它把两者做成 composite 组合来源 → 出来没有单独返回值,但 existing 会被更新成能代表多个来源的标记。

调用关系:其他特殊合并逻辑会调用它,比如规则、权限或类似字段在合并来源时。它是来源追踪的共享小工具。

调用图:调用 1 个内部函数(composite);被 3 处调用(merge, merge_source, merge);外部调用 1 个(clone)。

composition_conflict310–322 ↗
fn composition_conflict(
    field: String,
    existing_source: RequirementSource,
    incoming_source: RequirementSource,
    message: impl Into<String>,
) -> RequirementsCompositionError

作用:创建一个“配置合并冲突”的错误。比如两个来源对同一个只能有一个答案的字段给出了互相打架的要求,就用它说明问题。

数据流:进去的是冲突字段名、已有来源、新来来源、以及错误说明文字 → 它把这些信息装进 RequirementsCompositionError::Conflict → 出来的是一个结构化错误,里面清楚写着哪个字段、哪两个来源、为什么冲突。

调用关系:特殊合并逻辑发现冲突时会调用它,尤其是 merge_active_singleton 这类需要保证唯一有效项的流程。它不解决冲突,只负责把冲突说清楚并交给上层返回。

调用图:被 1 处调用(merge_active_singleton);外部调用 1 个(into)。

hooks/src/config_rules.rs源码 ↗
configconfig load / hook discovery

hook 可以理解成“在某些时机自动执行的小动作”,比如工具运行前先做检查。这个文件专门处理 hook 的状态配置:哪些 hook 被用户关掉了,哪些 hook 的内容哈希值已经被用户信任了。配置系统有很多层,像一摞纸:用户配置、这次启动传入的参数、项目配置、插件配置等。这里故意只看“用户配置”和“本次会话参数”,而且连被标记为禁用的层也会看;这样做是为了尊重用户自己的选择,不让项目或插件替用户改开关。核心函数会按优先级从低到高读取配置,后面的配置可以覆盖前面的配置,但覆盖是“按字段”来的:比如后面只写了 trusted_hash,就不会顺手把前面写的 enabled 抹掉。文件里的测试则用几个小例子确认:优先级正确、字段能合并、坏格式会被跳过而不是把程序弄崩。

函数细节7
hook_states_from_stack16–70 ↗
fn hook_states_from_stack(
    config_layer_stack: Option<&ConfigLayerStack>,
) -> HashMap<String, HookStateToml>

作用:这个函数把一整摞配置层整理成一张表,表里记录每个 hook 的最终状态。别人会用它来知道某个 hook 是否被用户启用、禁用,或者是否信任过某个哈希值。

数据流:进去的是一个可选的配置层栈;如果根本没有配置,就直接出来一张空表。它从低优先级到高优先级逐层看,只接受用户配置和本次会话参数这两类来源,然后找到 hooks.state 下面的每个 hook 状态。格式不对、key 为空、内容无法转换的条目都会被跳过。最后出来的是 HashMap(一张按 hook 名字查状态的表),里面每个 hook 的 enabled 和 trusted_hash 会按后来的高优先级配置逐项更新。

调用关系:它在 hook 发现流程里被 discover_handlers 调用,相当于发现 hook 之前先把“用户到底怎么设置这些 hook”整理清楚。它自己主要依赖配置层栈提供的层列表,并用 matches! 判断这一层是不是允许写入用户 hook 状态的来源。

调用图:被 1 处调用(discover_handlers);外部调用 2 个(new, matches!)。

tests::hook_states_from_stack_respects_layer_precedence83–114 ↗
fn hook_states_from_stack_respects_layer_precedence()

作用:这个测试确认:当用户配置和会话参数都写了同一个 hook 的开关时,高优先级的会话参数会赢。

数据流:它先造出两层配置:用户层把某个 hook 设为 false,会话参数层把同一个 hook 设为 true。然后把这两层放进配置栈,调用 hook_states_from_stack,最后检查结果是不是 true。也就是说,之前低优先级的 false 会被之后高优先级的 true 覆盖。

调用关系:它是 hook_states_from_stack 的安全网之一。测试运行时会构造 ConfigLayerStack,再把结果交给 assert_eq! 比较,确保主函数没有把优先级顺序弄反。

调用图:调用 1 个内部函数(new);外部调用 3 个(default, assert_eq!, vec!)。

tests::hook_states_from_stack_merges_fields_across_layers117–160 ↗
fn hook_states_from_stack_merges_fields_across_layers()

作用:这个测试确认:不同配置层可以分别补充同一个 hook 的不同字段,而不是后一层把前一层整块擦掉。

数据流:它先造一层用户配置,只写 enabled=false;再造一层会话参数,只写 trusted_hash=sha256:trusted。调用 hook_states_from_stack 后,期待结果同时保留 enabled=false 和 trusted_hash。换句话说,输入是两块不完整的信息,输出是一份合并后的完整状态。

调用关系:它专门盯住 hook_states_from_stack 里“按字段覆盖”的设计。测试通过 ConfigLayerStack::new 组装配置,再用 assert_eq! 验证合并行为符合预期。

调用图:调用 1 个内部函数(new);外部调用 3 个(default, assert_eq!, vec!)。

tests::hook_states_from_stack_ignores_malformed_hook_events163–199 ↗
fn hook_states_from_stack_ignores_malformed_hook_events()

作用:这个测试确认:hooks 下面除了 state 之外,即使有格式不对的事件配置,也不会影响读取 hook 状态。

数据流:它用 JSON 形式造出一份配置,里面 hooks.state 是正常的,但另一个 hook 事件 SessionStart 被故意写成错误格式。然后它把配置转成 TomlValue,放进用户配置层,调用 hook_states_from_stack。结果应该仍然读出 state 里的 enabled=false,并忽略那个坏掉的事件字段。

调用关系:它保护的是主函数的容错行为。hook_states_from_stack 只关心 hooks.state,不应该因为同一段 hooks 配置里别的内容坏了就放弃读取状态。

调用图:调用 1 个内部函数(new);外部调用 5 个(default, assert_eq!, from_value, json!, vec!)。

tests::hook_states_from_stack_ignores_malformed_state_entries202–240 ↗
fn hook_states_from_stack_ignores_malformed_state_entries()

作用:这个测试确认:state 表里某个 hook 状态写坏了,只会跳过那一条,不会影响其他正常条目。

数据流:它造出一份 hooks.state:一个正常 key 的 enabled=false,另一个 malformed 条目的 enabled 被故意写成字符串。调用 hook_states_from_stack 后,输出只包含正常那条。坏条目没有变成错误返回,也没有把整张状态表污染掉。

调用关系:它检查 hook_states_from_stack 遇到单条配置转换失败时的行为。主函数会尝试把每个状态转成 HookStateToml,失败就 continue,也就是跳过继续看下一条。

调用图:调用 1 个内部函数(new);外部调用 5 个(default, assert_eq!, from_value, json!, vec!)。

tests::config_with_hook_override242–250 ↗
fn config_with_hook_override(key: &str, enabled: Option<bool>) -> TomlValue

作用:这是测试里的小帮手,用来快速造一份只包含 enabled 开关的 hook 配置。它让测试代码不用每次手写完整的配置结构。

数据流:进去的是 hook 的 key 和一个可选的 enabled 值。它把这些包成 HookStateToml,并把 trusted_hash 留空,然后交给 config_with_hook_state。出来的是一份 TomlValue 形式的配置,可以直接塞进配置层。

调用关系:它服务于测试函数,尤其是需要快速构造“启用或禁用某个 hook”的场景。它不自己拼最终配置,而是把真正的组装工作交给 tests::config_with_hook_state。

调用图:外部调用 1 个(config_with_hook_state)。

tests::config_with_hook_state252–262 ↗
fn config_with_hook_state(key: &str, state: HookStateToml) -> TomlValue

作用:这是测试里的配置生成器,用来把一个 hook 状态包装成真实配置长相。测试用它模拟用户配置文件里 hooks.state 的内容。

数据流:进去的是 hook 的 key 和 HookStateToml 状态。它先把状态序列化成 JSON 值,再嵌进 hooks.state 这个结构里,最后转换成 TomlValue。出来的是一份测试可用的配置对象。

调用关系:它被 tests::config_with_hook_override 调用,也被需要更细字段的测试直接调用。它把测试数据做成和真实配置类似的形状,让 hook_states_from_stack 可以按正常路径读取。

调用图:外部调用 3 个(from_value, json!, to_value)。