Codex 配置来源排查:同一条命令,为何两个仓库不同

从两次相同的 CLI 调用出发,找到配置差异真正来自哪一层,再决定最小改动。

你能带走什么
  • 记录版本、目录与实际参数
  • 区分已存在、已启用与最终获选
  • 用 /debug-config 观察来源与策略
  • 先找到原因,再提最小修改候选

1. 两个仓库都选 inspect,先别急着改配置

把同一条 Codex 命令复制到另一个仓库,界面上的推理强度却变了。终端历史看起来一致,两份根配置也逐行相同。这时继续在用户配置里补一遍相同的值,很容易把真正的原因藏起来。先查清这一次启动实际经过哪些配置来源,修改才有落点。

本文用两个新建的合成仓库做来源追踪练习:tide-ledger 保存运单标签草稿,ridge-notes 保存步行笔记索引。内容都很小,没有凭据、插件、钩子或外部服务。练习只盯住 model_reasoning_effort 一个键,分别检查工作目录、profile、项目层、信任门控与命令行覆盖。管理要求单独记录,不在本机安装策略。

核查日期为 2026 年 10 月 7 日。本稿执行了 codex-cli 0.159.2 的版本与帮助查询,运行了夹具生成器、10 项离线测试及固定 Schema 校验。没有启动真实模型会话,也没有执行 /debug-config。因此后面的 high、medium、low 都是待验证的预期,不能当成实测结果。

2. 把普通层级和管理要求分别画出来

当前官方配置基础列出的普通优先级,从高到低为 CLI、受信任的项目配置、选中的 profile、用户配置、云端托管默认、系统配置和内置默认。项目范围内,离工作目录更近的配置优先。排查时按同一个键往上追,避免只比较整份文件的名称。

先记录进入哪些层,再追踪同一个键;requirements 另行约束可用值
01 一个值怎样走到本次会话。图为原创说明,不是真实 CLI 截图。

云端默认和管理员要求需要在记录中分开。官方托管配置说明把 requirements 定义为约束;与强制要求冲突的配置可能回退到兼容值,并给出通知。不要把“CLI 层最高”理解为可以越过策略。本文不创建 requirements.toml,只给已有管理来源留一个观察栏。

表中有一层,也还需要知道它有没有启用。来源诊断输出显示的路径、禁用原因、实际值以及启动通知,应该保存在同一次运行记录里。仅凭磁盘上有 .codex/config.toml,尚不能确认本轮读取了它。

3. 先核对真正可用的诊断入口

官方命令参考提供交互斜杠命令 /debug-config:查看由低到高的配置层顺序、启停状态及策略来源。它输入在 Codex 会话里。系统终端中的 codex debug 是另一组入口,本机 0.159.2 帮助只列出 models、app-server、prompt-input;本稿没有找到 codex debug config 或 codex config dump 的依据。

codex --version
codex --help
codex debug --help

以上三条已在作者环境执行并保存输出。帮助查询出现无法创建 PATH aliases 的只读文件系统警告,版本和 Usage 仍正常返回;没有为消除警告修改权限。它们只核对命令入口,不会给出本文两个仓库的最终配置。

# 下面输入在已获准启动的 Codex 交互会话中
/debug-config

这里的斜杠命令尚未执行。不要为了得到一份“完整诊断”顺手收集认证状态、历史会话或整个环境变量表。若当前客户端不能打开该诊断,先保留版本与帮助,再用静态清单缩小范围;不要让模型口头猜测自己加载了哪个文件。

4. 准备一份不会碰真实配置的目录

需要 Python 3.11 或更新版本和 Git。把文末三份完整程序放在同一个目录,然后选择一个尚不存在的绝对路径。生成器在目标下创建合成文件和两个空 Git 仓库。Git 子进程使用单独的最小环境,不继承 GIT_DIR、GIT_CONFIG_* 等变量,并禁用系统配置、用户配置和模板;路径已规范化,带空格也可用。遇到已有目录会停止,不会清空或覆盖。

python prepare.py "/absolute/path/origin-lab"
python inventory.py "/absolute/path/origin-lab"
python -m unittest -v
origin-lab/
├── codex-home/
│   ├── config.toml
│   └── inspect.config.toml
├── tide-ledger/
│   ├── .git/
│   ├── .codex/config.toml
│   ├── README.md
│   └── app/
│       ├── .codex/config.toml
│       └── manifest.txt
├── ridge-notes/
│   ├── .git/
│   ├── .codex/config.toml
│   ├── README.md
│   └── app/manifest.txt
└── evidence/observation.json

codex-home 在这里只是普通夹具目录。运行生成器不会让它成为当前用户的 CODEX_HOME,也不会改变任何真实项目的 trust。不要把整个真实 ~/.codex 复制过来;其中可能有认证与会话资料。真正启动前,需要在你已经获准使用、具有合适认证方式的隔离环境里安排测试。登录受阻时就记录阻塞,不要求读者抄凭据。

5. 先逐份读完这五个配置文件

下面以 /absolute/path/origin-lab 为例。生成器会用实际绝对路径替换两个 projects 键。信任差异是刻意写进合成文件的题目条件,不能照搬到现有仓库。这里也没有设置模型名,真实运行前须确认账户、默认模型及推理强度的支持情况。

# codex-home/config.toml
model_reasoning_effort = "low"
web_search = "disabled"
approval_policy = "on-request"
sandbox_mode = "read-only"

[projects."/absolute/path/origin-lab/tide-ledger"]
trust_level = "trusted"

[projects."/absolute/path/origin-lab/ridge-notes"]
trust_level = "untrusted"
# codex-home/inspect.config.toml
model_reasoning_effort = "medium"

# tide-ledger/.codex/config.toml(独立文件)
model_reasoning_effort = "high"

# tide-ledger/app/.codex/config.toml(独立文件)
model_reasoning_effort = "low"

# ridge-notes/.codex/config.toml(独立文件)
model_reasoning_effort = "high"

后三份文件各只有一行配置,上面合并展示是为了便于比较,不要把三个同名键粘进一个 TOML 文件。当前高级配置说明要求 profile 使用独立的 NAME.config.toml,并在文件顶层写配置键;0.134.0 之后的 --profile 不再读取旧式 [profiles.NAME]。CODEX_HOME 也决定配置和本地状态位置,换了它就需要重新检查认证与状态范围。

配置参考说明 projects.<path>.trust_level 的受信任和不受信任状态。这里检查的是项目 .codex 层是否进入加载过程,不讨论 AGENTS.md 的指令发现,也不通过写入尝试测试沙箱。

6. 按同一份任务单比较两次启动

先审阅夹具,确认要在哪个隔离环境进行真实测试。以下 Bash/Zsh 模板全部未执行;它们会启动 Codex,可能创建本地状态并使用服务,不是纯 TOML 检查。启动时如果出现认证、信任或新增权限询问,停下来记录,不要为了对齐预期自动接受。

LAB="/absolute/path/origin-lab"
( cd "$LAB/tide-ledger/app" &&   CODEX_HOME="$LAB/codex-home" codex --profile inspect )

( cd "$LAB/ridge-notes/app" &&   CODEX_HOME="$LAB/codex-home" codex --profile inspect )

两条命令中的 CLI 调用相同,只有进入的目录不同。每次打开后先输入 /debug-config,记下该轮 cwd、项目根、选中 profile、层路径、启停原因和策略通知。不要先发送修复任务。若诊断没有展示某个键的最终值,就把该栏留作未知,注明还缺什么观察,不能用预期填空。

合成练习:两个仓库的根配置相同,信任状态和启动目录不同
02 相同命令,为什么仍有两条路径。图为原创说明,不是真实 CLI 截图。

任务一:从 tide-ledger/app 启动,寻找根层和 app 层的两个同名键。在普通层都启用且没有相关额外约束的前提下,待验证的候选是 low。接着从 tide-ledger 根目录启动同样的命令,检查 app 层是否离开本次路径;预期候选改为 high。只改变 cwd,其他条件保持一致。

任务二:从 ridge-notes/app 启动,重点寻找项目层被跳过的原因。合成用户配置标记它为 untrusted;如果该状态被本机版本按预期识别,根层的 high 不应参与普通项目覆盖,profile 的 medium 成为候选。若实际出现 high,先核对路径身份、CODEX_HOME 与真实诊断,不要马上把信任改成 trusted。

任务三:回到 tide-ledger/app,检查 profile 确实被找到。即使 profile 的 medium 已加载,项目层仍可能覆盖它。看到 low 时,需要区分“profile 没读到”和“profile 已读到但同键被后层覆盖”,这两种情况要改的文件不同。

7. 一次覆盖可以帮助定位,但不能代替来源解释

完成前面的来源记录后,可以在另一个新会话中只增加一个普通设置覆盖。下面仍是未执行模板;目标是让诊断里出现 CLI 层,不测试权限扩大。

( cd "$LAB/tide-ledger/app" &&   CODEX_HOME="$LAB/codex-home" codex --profile inspect   -c 'model_reasoning_effort="medium"' )

预期要观察的是 CLI 来源与 medium 候选,而不是宣称所有环境必定采用这个值。若安装版本不支持该键、模型不支持该强度,或者管理要求介入,应先记录实际错误和通知。继续补更多 -c 参数会让下一次比较失去单变量条件。

同样的命令也可能由不同启动器拼出来。排查真实项目时,记录完整参数数组和实际可执行路径;shell 别名、包装脚本、IDE 启动设置都可能加入参数。只保留与本案有关且已脱敏的项,不运行 env 全量导出,不把内部地址或凭据写入公开工单。

8. 按键填写诊断记录,再决定改哪里

配套 observation.json 的观察结果字段为空,observed_value 是 null;key 和 recheck 保留模板默认值。每个案例复制一份记录,先写声明来源,再填实际观察。下面是完整模板;它没有任何真实运行结果。

{
  "case": "",
  "cli_version": "",
  "executable": "",
  "cwd": "",
  "project_root": "",
  "codex_home": "",
  "argv": [],
  "selected_profile": "",
  "layers_low_to_high": [],
  "disabled_layers_and_reasons": [],
  "requirements_sources": [],
  "key": "model_reasoning_effort",
  "candidate_sources": [],
  "observed_value": null,
  "evidence_excerpt_redacted": "",
  "explanation": "",
  "smallest_change_candidate": "",
  "recheck": "not_run"
}
一张记录按键追踪,保留没能确定的地方
03 从差异到最小修改,需要哪些证据。图为原创说明,不是真实 CLI 截图。

最小修改候选要对应已确认的原因。若启动目录误入子目录,修正启动路径比改用户默认更准确;若 app 配置保留了过期强度,审阅并修改那一个键;若旧 profile 表还留在用户配置中,按当前格式迁到独立文件,并在单独审阅后清理旧项。这里只形成候选,不自动改现有文件。

当原因落在未信任项目时,先审阅来源和仓库内容。可以保持不受信任,再用允许的用户/profile 配置表达本次需要;不要把“让项目配置生效”直接等同于“应该授予信任”。当来源落在 requirements,向管理员提供脱敏的冲突键、策略来源和通知,不能通过换目录、换账号或覆盖参数绕过。

改动获准后,保留旧记录,使用同一版本、同一目录和同一条命令重新观察。除了目标值,还检查其他层有没有意外变化。只有本轮真实诊断支持了结果,才把 recheck 改为已验证,并写明证据文件。

9. 离线清单能排除哪些问题

inventory.py 只读取夹具约定的五个文件,输出 SHA-256、声明的强度和顶层键。它不扫描用户主目录,不读取认证文件,不合并配置,不推断项目是否可信,也不生成“最终配置”。路径逃逸和目标文件符号链接会被拒绝;TOML 语法错误由标准库直接报出。

10 项测试覆盖两处 Git 根、继承的 Git 环境和模板不进入初始化、绝对信任路径、相同根声明、仅一侧有嵌套层、空观察结果、拒绝覆盖已有目录、拒绝越界、无效 TOML,以及内容变化引起哈希变化。作者另将五份 TOML 对照固定提交的官方 Schema 验证;Schema、校验依赖与执行程序仅保存在私下 QA,不在正文配套包中。复制正文可以复跑标准库的 10 项测试和 TOML 清单检查,不能据此声称复跑了 Schema 校验。它们能证明配套文件按预定方式生成、语法和字段结构匹配该 Schema;不能证明当前安装二进制最终加载了什么。

本次没有尝试启动交互诊断,真实加载结果统一保留为未执行。文章也没有创建模拟加载器来替代 Codex。若你只能完成离线步骤,交付文件清单、待查层和明确的阻塞原因,已经能让下一位排查者继续工作。

10. 配套文件完整原文

以下三份文件构成全部离线工具,均使用 Python 标准库;Git 仅用于生成两个新仓库。网页中的代码与 ZIP 中的文件保持一致。

prepare.py

"""Create inert synthetic fixtures; never launch Codex or change the user's config."""
import json
import os
import shutil
import pathlib
import subprocess
import sys


def create(target):
    git = shutil.which('git')
    if git is None:
        raise RuntimeError('Git executable not found')
    root = pathlib.Path(target).resolve()
    root.mkdir(parents=True, exist_ok=False)
    a, b = root / 'tide-ledger', root / 'ridge-notes'
    q = lambda p: json.dumps(str(p), ensure_ascii=False)
    files = {
        'codex-home/config.toml': (
            'model_reasoning_effort = "low"\n'
            'web_search = "disabled"\n'
            'approval_policy = "on-request"\n'
            'sandbox_mode = "read-only"\n\n'
            f'[projects.{q(a)}]\ntrust_level = "trusted"\n\n'
            f'[projects.{q(b)}]\ntrust_level = "untrusted"\n'),
        'codex-home/inspect.config.toml': 'model_reasoning_effort = "medium"\n',
        'tide-ledger/.codex/config.toml': 'model_reasoning_effort = "high"\n',
        'tide-ledger/app/.codex/config.toml': 'model_reasoning_effort = "low"\n',
        'ridge-notes/.codex/config.toml': 'model_reasoning_effort = "high"\n',
        'tide-ledger/README.md': '# Tide Ledger\nSynthetic shipping-label draft project.\n',
        'tide-ledger/app/manifest.txt': 'label_format=plain\n',
        'ridge-notes/README.md': '# Ridge Notes\nSynthetic walking-note index project.\n',
        'ridge-notes/app/manifest.txt': 'note_format=plain\n',
        'evidence/observation.json': json.dumps({
            'case': '', 'cli_version': '', 'executable': '',
            'cwd': '', 'project_root': '', 'codex_home': '',
            'argv': [], 'selected_profile': '',
            'layers_low_to_high': [], 'disabled_layers_and_reasons': [],
            'requirements_sources': [], 'key': 'model_reasoning_effort',
            'candidate_sources': [], 'observed_value': None,
            'evidence_excerpt_redacted': '', 'explanation': '',
            'smallest_change_candidate': '', 'recheck': 'not_run'
        }, ensure_ascii=False, indent=2) + '\n',
    }
    for rel, content in files.items():
        p = root / rel
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_text(content, encoding='utf-8')
    # Do not inherit GIT_DIR, GIT_CONFIG_*, templates, or user config.
    git_env = {
        'PATH': os.defpath, 'HOME': str(root), 'XDG_CONFIG_HOME': str(root),
        'LC_ALL': 'C', 'GIT_CONFIG_NOSYSTEM': '1',
        'GIT_CONFIG_SYSTEM': os.devnull, 'GIT_CONFIG_GLOBAL': os.devnull,
    }
    for repo in (a, b):
        subprocess.run([git, 'init', '--quiet', '--template=', str(repo)],
                       cwd=root, env=git_env, check=True)
    print(root)
    return root


if __name__ == '__main__':
    if len(sys.argv) != 2:
        raise SystemExit('Usage: python prepare.py NEW_DIRECTORY')
    create(sys.argv[1])

inventory.py

"""Inventory this article's fixture only. This is NOT a Codex config resolver."""
import hashlib
import json
from pathlib import Path
import sys
import tomllib

FILES = ('codex-home/config.toml', 'codex-home/inspect.config.toml',
         'tide-ledger/.codex/config.toml',
         'tide-ledger/app/.codex/config.toml',
         'ridge-notes/.codex/config.toml')


def inventory(root):
    root = Path(root).resolve(strict=True)
    rows = []
    for rel in FILES:
        p = root / rel
        if p.is_symlink() or not p.resolve(strict=True).is_relative_to(root):
            raise ValueError('fixture path escapes root: ' + rel)
        raw = p.read_bytes()
        data = tomllib.loads(raw.decode('utf-8'))
        rows.append({'file': rel, 'sha256': hashlib.sha256(raw).hexdigest(),
                     'declared_effort': data.get('model_reasoning_effort'),
                     'top_level_keys': sorted(data)})
    return {'scope': 'fixture files only; no effective config claim', 'files': rows}


if __name__ == '__main__':
    if len(sys.argv) != 2:
        raise SystemExit('Usage: python inventory.py FIXTURE_DIRECTORY')
    print(json.dumps(inventory(sys.argv[1]), ensure_ascii=False, indent=2))

test_fixture.py

import json
import os
from pathlib import Path
import tempfile
import tomllib
import unittest
from unittest.mock import patch
from prepare import create
from inventory import inventory


class FixtureTests(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory()
        self.root = create(Path(self.temp.name) / 'fixture with spaces')

    def tearDown(self):
        self.temp.cleanup()

    def test_five_toml_files(self):
        self.assertEqual(len(inventory(self.root)['files']), 5)

    def test_separate_git_roots_ignore_inherited_environment(self):
        outside = Path(self.temp.name) / 'outside-git'
        template = Path(self.temp.name) / 'hostile-template'
        template.mkdir()
        (template / 'unwanted.txt').write_text('must not be copied')
        config = Path(self.temp.name) / 'user-gitconfig'
        config.write_text('[init]\n    templateDir = ' + str(template) + '\n')
        dirty = {
            'GIT_DIR': str(outside), 'GIT_WORK_TREE': str(outside),
            'GIT_COMMON_DIR': str(outside), 'GIT_TEMPLATE_DIR': str(template),
            'GIT_CONFIG': str(config), 'GIT_CONFIG_GLOBAL': str(config),
            'GIT_CONFIG_SYSTEM': str(config), 'GIT_CONFIG_NOSYSTEM': '0',
            'GIT_CONFIG_COUNT': '1', 'GIT_CONFIG_KEY_0': 'init.templateDir',
            'GIT_CONFIG_VALUE_0': str(template),
            'GIT_CONFIG_PARAMETERS': "'init.templateDir=" + str(template) + "'",
        }
        with patch.dict(os.environ, dirty):
            isolated = create(Path(self.temp.name) / 'isolated')
        self.assertFalse(outside.exists())
        for root in (self.root, isolated):
            for name in ('tide-ledger', 'ridge-notes'):
                git_dir = root / name / '.git'
                self.assertTrue(git_dir.is_dir())
                self.assertFalse((git_dir / 'unwanted.txt').exists())

    def test_trust_paths_are_absolute(self):
        data = tomllib.loads((self.root/'codex-home/config.toml').read_text())
        self.assertEqual(data['projects'][str(self.root/'tide-ledger')]['trust_level'], 'trusted')
        self.assertEqual(data['projects'][str(self.root/'ridge-notes')]['trust_level'], 'untrusted')

    def test_same_root_declaration(self):
        self.assertEqual((self.root/'tide-ledger/.codex/config.toml').read_bytes(),
                         (self.root/'ridge-notes/.codex/config.toml').read_bytes())

    def test_nested_layer_only_in_a(self):
        self.assertTrue((self.root/'tide-ledger/app/.codex/config.toml').is_file())
        self.assertFalse((self.root/'ridge-notes/app/.codex/config.toml').exists())

    def test_no_fake_observation(self):
        r = json.loads((self.root/'evidence/observation.json').read_text())
        self.assertIsNone(r['observed_value'])
        self.assertEqual(r['recheck'], 'not_run')

    def test_refuse_existing_target(self):
        with self.assertRaises(FileExistsError):
            create(self.root)

    def test_reject_escape(self):
        p = self.root/'ridge-notes/.codex/config.toml'
        p.unlink()
        p.symlink_to(Path(self.temp.name)/'outside.toml')
        with self.assertRaises(ValueError):
            inventory(self.root)

    def test_invalid_toml_rejected(self):
        (self.root/'ridge-notes/.codex/config.toml').write_text('broken = [')
        with self.assertRaises(tomllib.TOMLDecodeError):
            inventory(self.root)

    def test_hash_changes_with_input(self):
        before = inventory(self.root)['files'][1]['sha256']
        p = self.root/'codex-home/inspect.config.toml'
        p.write_text(p.read_text()+'# local note\n')
        self.assertNotEqual(before, inventory(self.root)['files'][1]['sha256'])


if __name__ == '__main__':
    unittest.main()

11. 补充:往文件末尾加一行,为什么根部还没有它

排查优先级之前,还可以先问一个更小的问题:你刚补上的那一行,解析后真在预想的位置吗?例如,为了保留项目的信任设置,先写下 projects 表,再把新的推理强度追加到文件末尾。搜索能找到 medium,文件也能通过 TOML 解析,前文 inventory.py 的 declared_effort 却仍显示 null。此时还没有证据说是 profile 覆盖了它。

下面用另一组独立的合成文件重现这个差别。/synthetic/tide.ledger 只是 TOML 中的一段字符串,不会被创建、访问或设置信任。01 中的最后一行属于 projects 下的那张项目表,根部没有 model_reasoning_effort。02 只加了空行和注释,归属并没有变化。

inputs/01-misplaced.toml

[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
model_reasoning_effort = "medium"

inputs/02-blank-line.toml

[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"

# A blank line and comment do not leave the current table.
model_reasoning_effort = "medium"

TOML 1.0.0 的表规则规定,表头后的键属于该表,直到下一个表头或文件结束;根表位于文档开头。换行和注释都不建立新的表。这里说的是解析出来的结构,不是 Codex 对某个字段的接受规则。

同一行 medium 写在项目表头后会成为嵌套键;移到首个表头前才是根部字符串。两边都保留 untrusted。
图 4:表头改变后续键的归属;空行和注释不会切回根部(原创结构示意,非 CLI 截图)

前文清单里的 data.get('model_reasoning_effort') 只读根字典。在这个具体案例中,它返回 None,序列化为 JSON 的 null,表示根部没有这个键;它没有递归搜索项目表。把“搜索到了文本”“根部有字符串值”“应用实际采用了值”分开记录,才能知道下一步应检查格式还是继续追踪加载来源。

12. 同名键不是同一路径,修复也不靠“最后一行”

第三份文件特意在根部保留 low,再在项目表中写 medium。两行虽然末尾的键名相同,完整路径却不同,因此是合法 TOML。解析结果应同时保留两个位置;不能用“最后出现的同名行”替代根部查找,也不能据此声称 medium 覆盖了 low。

inputs/03-root-and-nested.toml

model_reasoning_effort = "low"

[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
model_reasoning_effort = "medium"

如果明确要修复的是 01 的根部设置,04 只把 medium 那一行移到第一个表头之前。项目里的 trust_level 仍为 untrusted,其他结构不动。这个移动解决的是文本归属,不要求授予项目更多信任。如果面对的是 03,根部已有 low,就不能再往前添加一个 medium;应先确认哪个根部值才是本次获准表达的意图,再审阅替换与嵌套项清理。

inputs/04-repaired.toml

model_reasoning_effort = "medium"

[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"

05 把两个定义都放在根表,因此产生同一作用域的重复键错误;06 尝试用 [] 返回根表,也会解析失败。这两种结果都不能当作“空配置”继续比较优先级。官方键规则可以用来核对重复定义;错误文字会随解析器版本变化,本练习只固定异常类别。

inputs/05-duplicate-root.toml

model_reasoning_effort = "low"
model_reasoning_effort = "medium"

[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"

inputs/06-empty-header.toml

[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"

[]
model_reasoning_effort = "medium"

还要防止把排版当成结构。07 使用点分键,它和 04 解析成相同的字典。引号中的 /synthetic/tide.ledger 是一个完整键段,里面的点不会再分层。因此后面的探针用数组保存路径,不把路径拼成一段含义不清的点分字符串。

inputs/07-dotted-control.toml

model_reasoning_effort = "medium"
projects."/synthetic/tide.ledger".trust_level = "untrusted"

08 则把 model_reasoning_effort 变成了一张含 value 的表。根部键确实存在,类型却是 table,不能把它说成“缺失”,也不能从里面挑出 medium 冒充根部字符串。这只是类型边界的反例,不是推荐配置格式;本补充没有对这份文件做 Codex Schema 校验。

inputs/08-root-table-value.toml

model_reasoning_effort.value = "medium"

[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"

13. 用解析树核对八份固定输入

把以上八份文件按文件名保存在 inputs/,把下面 scope_probe.py 放在它旁边。Python 3.11 起提供标准库 tomllib;本次实际运行的是 Python 3.12.14。与运行版本对应的CPython 官方 tomllib 文档原文说明,它解析 TOML 1.0.0,返回字典,无效文档抛出 TOMLDecodeError;它不提供 TOML 写回功能。

探针先解析,再遍历字典和数组。root 单独记录根部是否存在及类型;matches 记录所有精确同名键的位置,数组中的位置还带整数下标。注释或字符串中出现键名不会成为匹配项。遍历顺序固定,方便逐字核对输出。

命令行不接受路径参数,只读取脚本旁八个约定文件。它拒绝入口文件夹或文件本身为符号链接、缺失文件和超过 4096 字节的夹具;这些是方便复现的输入约束,不是一个面向不受信任文件系统的安全扫描器。无效 UTF-8 与 TOML 语法错误分别报告,错误时没有半份解析结果。字符串只回显本题使用的 low、medium、high,其余值省略;这个约束也不意味着可以把真实敏感配置交给它。

scope_probe.py

"""Read this exercise's synthetic files only; never load or change Codex config."""
import hashlib
import json
from pathlib import Path
import sys
import tomllib

KEY = "model_reasoning_effort"
CASES = (
    "01-misplaced.toml", "02-blank-line.toml", "03-root-and-nested.toml",
    "04-repaired.toml", "05-duplicate-root.toml", "06-empty-header.toml",
    "07-dotted-control.toml", "08-root-table-value.toml",
)
ROOT = Path(__file__).resolve().parent


def describe(value):
    if isinstance(value, dict):
        return {"kind": "table"}
    if isinstance(value, list):
        return {"kind": "array"}
    if isinstance(value, str):
        # Do not echo arbitrary values even in this synthetic exercise.
        if value in {"low", "medium", "high"}:
            return {"kind": "string", "value": value}
        return {"kind": "string", "value_omitted": True}
    return {"kind": type(value).__name__, "value_omitted": True}


def analyze(raw):
    try:
        document = tomllib.loads(raw.decode("utf-8"))
    except UnicodeDecodeError:
        return {"parse": "error", "error": "UnicodeDecodeError"}
    except tomllib.TOMLDecodeError:
        # Diagnostics may quote input; keep only the stable exception class.
        return {"parse": "error", "error": "TOMLDecodeError"}
    matches = []

    def visit(node, path):
        if isinstance(node, dict):
            for key in sorted(node):
                value = node[key]
                here = path + [key]
                if key == KEY:
                    matches.append({"path": here, **describe(value)})
                visit(value, here)
        elif isinstance(node, list):
            for index, value in enumerate(node):
                visit(value, path + [index])

    visit(document, [])
    root = {"present": KEY in document}
    if KEY in document:
        root.update(describe(document[KEY]))
    return {"parse": "ok", "root": root, "matches": matches}


def read_fixture(folder, name):
    if name not in CASES:
        raise ValueError("unknown fixture name")
    folder = Path(folder)
    if folder.is_symlink():
        raise ValueError("fixture folder must not be a symlink")
    base = folder.resolve(strict=True)
    path = base / name
    if path.is_symlink() or not path.is_file():
        raise ValueError("fixture must be a regular non-symlink file")
    # Each example is tiny. Bound reads without loading a huge replacement file.
    with path.open("rb") as source:
        raw = source.read(4097)
    if len(raw) > 4096:
        raise ValueError("fixture exceeds 4096 bytes")
    return raw


def run(folder):
    reports, hashes = {}, {}
    for name in CASES:
        raw = read_fixture(folder, name)
        reports[name] = analyze(raw)
        hashes[name] = hashlib.sha256(raw).hexdigest()
    return reports, hashes


def main():
    if len(sys.argv) != 1:
        raise SystemExit("No path arguments: this command reads bundled fixtures only")
    actual, hashes = run(ROOT / "inputs")
    expected = json.loads((ROOT / "expected" / "reports.json").read_text("utf-8"))
    print(json.dumps(actual, ensure_ascii=False, indent=2))
    print("INPUT_SHA256 " + json.dumps(hashes, sort_keys=True))
    if actual != expected:
        raise SystemExit("FAIL: review the inputs and fixed oracle; do not regenerate it")
    print("PASS: 8 fixed TOML location cases; Codex runtime not tested")


if __name__ == "__main__":
    main()

expected/reports.json 是先按八个案例逐项写定的结构预期。它不从 analyze 的返回结果生成。保留紧凑写法是为了便于查看各案例;程序比较的是解析后的 JSON 对象,不要求这份 JSON 的排版与标准输出相同。

expected/reports.json

{
  "01-misplaced.toml": {"parse": "ok", "root": {"present": false}, "matches": [{"path": ["projects", "/synthetic/tide.ledger", "model_reasoning_effort"], "kind": "string", "value": "medium"}]},
  "02-blank-line.toml": {"parse": "ok", "root": {"present": false}, "matches": [{"path": ["projects", "/synthetic/tide.ledger", "model_reasoning_effort"], "kind": "string", "value": "medium"}]},
  "03-root-and-nested.toml": {"parse": "ok", "root": {"present": true, "kind": "string", "value": "low"}, "matches": [{"path": ["model_reasoning_effort"], "kind": "string", "value": "low"}, {"path": ["projects", "/synthetic/tide.ledger", "model_reasoning_effort"], "kind": "string", "value": "medium"}]},
  "04-repaired.toml": {"parse": "ok", "root": {"present": true, "kind": "string", "value": "medium"}, "matches": [{"path": ["model_reasoning_effort"], "kind": "string", "value": "medium"}]},
  "05-duplicate-root.toml": {"parse": "error", "error": "TOMLDecodeError"},
  "06-empty-header.toml": {"parse": "error", "error": "TOMLDecodeError"},
  "07-dotted-control.toml": {"parse": "ok", "root": {"present": true, "kind": "string", "value": "medium"}, "matches": [{"path": ["model_reasoning_effort"], "kind": "string", "value": "medium"}]},
  "08-root-table-value.toml": {"parse": "ok", "root": {"present": true, "kind": "table"}, "matches": [{"path": ["model_reasoning_effort"], "kind": "table"}]}
}
python3 -B scope_probe.py

下面是这条命令的完整标准输出,同时保存为 expected/output.txt。最后一条 PASS 只表示八份结构报告与固定预期一致;它没有宣告任何 Codex 设置生效。INPUT_SHA256 记录本版八份文件的字节摘要。根部缺失、根部字符串、根部表和解析错误在这份输出里分别可见。

expected/output.txt

{
  "01-misplaced.toml": {
    "parse": "ok",
    "root": {
      "present": false
    },
    "matches": [
      {
        "path": [
          "projects",
          "/synthetic/tide.ledger",
          "model_reasoning_effort"
        ],
        "kind": "string",
        "value": "medium"
      }
    ]
  },
  "02-blank-line.toml": {
    "parse": "ok",
    "root": {
      "present": false
    },
    "matches": [
      {
        "path": [
          "projects",
          "/synthetic/tide.ledger",
          "model_reasoning_effort"
        ],
        "kind": "string",
        "value": "medium"
      }
    ]
  },
  "03-root-and-nested.toml": {
    "parse": "ok",
    "root": {
      "present": true,
      "kind": "string",
      "value": "low"
    },
    "matches": [
      {
        "path": [
          "model_reasoning_effort"
        ],
        "kind": "string",
        "value": "low"
      },
      {
        "path": [
          "projects",
          "/synthetic/tide.ledger",
          "model_reasoning_effort"
        ],
        "kind": "string",
        "value": "medium"
      }
    ]
  },
  "04-repaired.toml": {
    "parse": "ok",
    "root": {
      "present": true,
      "kind": "string",
      "value": "medium"
    },
    "matches": [
      {
        "path": [
          "model_reasoning_effort"
        ],
        "kind": "string",
        "value": "medium"
      }
    ]
  },
  "05-duplicate-root.toml": {
    "parse": "error",
    "error": "TOMLDecodeError"
  },
  "06-empty-header.toml": {
    "parse": "error",
    "error": "TOMLDecodeError"
  },
  "07-dotted-control.toml": {
    "parse": "ok",
    "root": {
      "present": true,
      "kind": "string",
      "value": "medium"
    },
    "matches": [
      {
        "path": [
          "model_reasoning_effort"
        ],
        "kind": "string",
        "value": "medium"
      }
    ]
  },
  "08-root-table-value.toml": {
    "parse": "ok",
    "root": {
      "present": true,
      "kind": "table"
    },
    "matches": [
      {
        "path": [
          "model_reasoning_effort"
        ],
        "kind": "table"
      }
    ]
  }
}
INPUT_SHA256 {"01-misplaced.toml": "46c2fa941f0d5cb29e85e5bb8b22e58cab21e8bbb6821d11673546b1f8b596cf", "02-blank-line.toml": "edc5aec7dce7061ffe138f855ca609d88df7427a45e7fe22fb1c88be2a049f68", "03-root-and-nested.toml": "28a3acb415bc3cdf860ca9c845b7d632b18e0c54833c0b268b9849eafbaf56d5", "04-repaired.toml": "7132b626f37a599bba42fc0dc1d7b9216ed82857ab903dac22dcaf0954c95c76", "05-duplicate-root.toml": "a09a5b4c12482ca36588799d68eb66bb995be38197da36d2d12ec7913ebf7bb7", "06-empty-header.toml": "1dcfcc89f50b7b3dfd77d83d2ab9559e5467154a27de690ff3d86817edb6d5dc", "07-dotted-control.toml": "2728dabe8bce25bdbd2302d34e4cecb880819b3d63bbbdd6089e501722058159", "08-root-table-value.toml": "b40ae83f686ff9f3a2c338ae6808627f663be02e90c266345a5f11622415528e"}
PASS: 8 fixed TOML location cases; Codex runtime not tested

14. 给解析结果、文件字节和失败方式各留一份验收

结构相同,不代表文件字节相同。01 与 02 的解析报告相同,摘要不同,正好可以作为控制。scope_probe.py 本身会打印摘要,但只比较结构预期;下面 check_output.py 才把完整 stdout 与固定的 expected/output.txt 逐字节比较,同时检查退出码和 stderr。八个摘要还用独立的 sha256sum 工具核对过。

check_output.py

"""Compare the whole stdout, including byte hashes, with a frozen snapshot."""
from pathlib import Path
import subprocess
import sys

ROOT = Path(__file__).resolve().parent


def main():
    if len(sys.argv) != 1:
        raise SystemExit("No arguments: check the bundled snapshot only")
    command = [sys.executable, "-B"]
    if sys.flags.optimize:
        command.append("-O")
    command.append(str(ROOT / "scope_probe.py"))
    result = subprocess.run(command, cwd=ROOT, capture_output=True, timeout=30)
    expected = (ROOT / "expected" / "output.txt").read_bytes()
    if result.returncode != 0:
        raise SystemExit("FAIL: probe exited nonzero; review fixtures and fixed reports")
    if result.stderr:
        raise SystemExit("FAIL: probe wrote unexpected stderr")
    if result.stdout != expected:
        raise SystemExit("FAIL: full output differs from the frozen byte snapshot")
    print("PASS: full stdout and all 8 input hashes match the frozen byte snapshot")


if __name__ == "__main__":
    main()
python3 -B check_output.py
python3 -B -O check_output.py

两条命令都应输出同一句话:

PASS: full stdout and all 8 input hashes match the frozen byte snapshot

若只给夹具加一条注释,结构检查仍可通过,但完整输出检查必须失败。若把 04 的 medium 改成 high,结构检查本身就应失败。这两个负向控制都只在临时副本中执行;验收没有重新生成预期来迁就结果。你若有意扩充练习,应该先写清新的输入与预期,再建立新的快照。

以下 17 项测试覆盖固定报告、空行与摘要、原清单的根部查找、移动前后的无关字段、点分键、根部表类型、表数组下标、注释与字符串、解析错误、未知值隐藏、编码错误、文件边界和只读检查。表数组使用的是 demo 结构,只检验通用 TOML 路径,不是 Codex 配置建议。

test_scope.py

import json
from pathlib import Path
import shutil
import tempfile
import tomllib
import unittest
from scope_probe import ROOT, CASES, KEY, analyze, read_fixture, run


class ScopeTests(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory()
        self.folder = Path(self.temp.name) / "inputs"
        shutil.copytree(ROOT / "inputs", self.folder)

    def tearDown(self):
        self.temp.cleanup()

    def test_all_eight_fixed_reports(self):
        actual, _ = run(self.folder)
        expected = json.loads((ROOT / "expected" / "reports.json").read_text("utf-8"))
        self.assertEqual(actual, expected)
        self.assertEqual(list(actual), list(CASES))

    def test_blank_line_changes_hash_not_location(self):
        reports, hashes = run(self.folder)
        self.assertEqual(reports[CASES[0]], reports[CASES[1]])
        self.assertNotEqual(hashes[CASES[0]], hashes[CASES[1]])

    def test_original_inventory_root_lookup(self):
        bad = tomllib.loads(read_fixture(self.folder, CASES[0]).decode())
        fixed = tomllib.loads(read_fixture(self.folder, CASES[3]).decode())
        self.assertNotIn(KEY, bad)
        self.assertIsNone(bad.get(KEY))
        self.assertEqual(fixed.get(KEY), "medium")

    def test_recovery_preserves_unrelated_project_fields(self):
        bad = tomllib.loads(read_fixture(self.folder, CASES[0]).decode())
        fixed = tomllib.loads(read_fixture(self.folder, CASES[3]).decode())
        bad["projects"]["/synthetic/tide.ledger"].pop(KEY)
        fixed.pop(KEY)
        self.assertEqual(bad, fixed)

    def test_table_header_and_dotted_key_same_document(self):
        a = tomllib.loads(read_fixture(self.folder, CASES[3]).decode())
        b = tomllib.loads(read_fixture(self.folder, CASES[6]).decode())
        self.assertEqual(a, b)
        self.assertEqual(list(a["projects"]), ["/synthetic/tide.ledger"])

    def test_root_presence_does_not_mean_scalar(self):
        report = analyze(read_fixture(self.folder, CASES[7]))
        self.assertEqual(report["root"], {"present": True, "kind": "table"})

    def test_array_of_tables_locations_include_index(self):
        raw = b'[[demo]]\nmodel_reasoning_effort="low"\n[[demo]]\nmodel_reasoning_effort="high"\n'
        report = analyze(raw)
        self.assertEqual(report["root"], {"present": False})
        self.assertEqual(report["matches"], [
            {"path": ["demo", 0, KEY], "kind": "string", "value": "low"},
            {"path": ["demo", 1, KEY], "kind": "string", "value": "high"},
        ])

    def test_comment_and_string_do_not_create_a_key(self):
        raw = b'# model_reasoning_effort="high"\nexample="model_reasoning_effort"\n'
        self.assertEqual(analyze(raw), {"parse": "ok", "root": {"present": False}, "matches": []})

    def test_parse_error_has_no_partial_result(self):
        report = analyze(read_fixture(self.folder, CASES[4]))
        self.assertEqual(report, {"parse": "error", "error": "TOMLDecodeError"})

    def test_unknown_scalar_not_echoed(self):
        report = analyze(b'model_reasoning_effort="fixture-marker-not-an-effort"\n')
        self.assertNotIn("fixture-marker", json.dumps(report))
        self.assertTrue(report["root"]["value_omitted"])

    def test_bad_utf8_is_not_empty_config(self):
        self.assertEqual(analyze(b'\xff'), {"parse": "error", "error": "UnicodeDecodeError"})

    def test_unknown_fixture_name_rejected(self):
        with self.assertRaisesRegex(ValueError, "unknown fixture"):
            read_fixture(self.folder, "../config.toml")

    def test_missing_fixture_does_not_become_absence(self):
        (self.folder / CASES[0]).unlink()
        with self.assertRaisesRegex(ValueError, "regular"):
            run(self.folder)

    def test_symlink_file_rejected(self):
        p = self.folder / CASES[0]
        p.unlink()
        p.symlink_to(self.folder / CASES[3])
        with self.assertRaisesRegex(ValueError, "non-symlink"):
            run(self.folder)

    def test_symlink_folder_rejected(self):
        alias = Path(self.temp.name) / "alias"
        alias.symlink_to(self.folder, target_is_directory=True)
        with self.assertRaisesRegex(ValueError, "folder"):
            run(alias)

    def test_oversize_fixture_rejected(self):
        (self.folder / CASES[0]).write_bytes(b"#" + b"x" * 4096)
        with self.assertRaisesRegex(ValueError, "4096"):
            run(self.folder)

    def test_probe_does_not_modify_input_files(self):
        before = {p.name: p.read_bytes() for p in self.folder.iterdir()}
        run(self.folder)
        self.assertEqual(before, {p.name: p.read_bytes() for p in self.folder.iterdir()})


if __name__ == "__main__":
    unittest.main(verbosity=2)
python3 -B -m unittest -v test_scope
python3 -B -O -m unittest -v test_scope

本次普通模式的完整测试日志如下;耗时只是这次运行的记录,不是固定预期。

test_all_eight_fixed_reports (test_scope.ScopeTests.test_all_eight_fixed_reports) ... ok
test_array_of_tables_locations_include_index (test_scope.ScopeTests.test_array_of_tables_locations_include_index) ... ok
test_bad_utf8_is_not_empty_config (test_scope.ScopeTests.test_bad_utf8_is_not_empty_config) ... ok
test_blank_line_changes_hash_not_location (test_scope.ScopeTests.test_blank_line_changes_hash_not_location) ... ok
test_comment_and_string_do_not_create_a_key (test_scope.ScopeTests.test_comment_and_string_do_not_create_a_key) ... ok
test_missing_fixture_does_not_become_absence (test_scope.ScopeTests.test_missing_fixture_does_not_become_absence) ... ok
test_original_inventory_root_lookup (test_scope.ScopeTests.test_original_inventory_root_lookup) ... ok
test_oversize_fixture_rejected (test_scope.ScopeTests.test_oversize_fixture_rejected) ... ok
test_parse_error_has_no_partial_result (test_scope.ScopeTests.test_parse_error_has_no_partial_result) ... ok
test_probe_does_not_modify_input_files (test_scope.ScopeTests.test_probe_does_not_modify_input_files) ... ok
test_recovery_preserves_unrelated_project_fields (test_scope.ScopeTests.test_recovery_preserves_unrelated_project_fields) ... ok
test_root_presence_does_not_mean_scalar (test_scope.ScopeTests.test_root_presence_does_not_mean_scalar) ... ok
test_symlink_file_rejected (test_scope.ScopeTests.test_symlink_file_rejected) ... ok
test_symlink_folder_rejected (test_scope.ScopeTests.test_symlink_folder_rejected) ... ok
test_table_header_and_dotted_key_same_document (test_scope.ScopeTests.test_table_header_and_dotted_key_same_document) ... ok
test_unknown_fixture_name_rejected (test_scope.ScopeTests.test_unknown_fixture_name_rejected) ... ok
test_unknown_scalar_not_echoed (test_scope.ScopeTests.test_unknown_scalar_not_echoed) ... ok

----------------------------------------------------------------------
Ran 17 tests in 0.011s

OK

测试也要能拒绝错误实现。下面四个变体分别模拟空行切回根部、把嵌套匹配当根部、把根部表误报为缺失、丢掉数组下标。检查器先确认原实现的定向测试通过,再用 unittest.mock 临时替换两个函数查找位置,运行每个错误变体。只有定向测试恰好出现一次 AssertionError、没有错误或跳过,并且原函数已恢复,才算抓住错误;导入失败或其他异常不能算通过。整个检查器成功时退出 0,不修改源码或固定预期。

check_scope_mutants.py

"""Prove four named incorrect implementations fail assertions, not imports.

Runs only bundled synthetic cases. Does not rewrite scope_probe or its oracle.
"""
import json
import re
import unittest
from unittest.mock import patch
import scope_probe
import test_scope

ORIGINAL = scope_probe.analyze
KEY = scope_probe.KEY


def blank_line_resets_table(raw):
    report = ORIGINAL(raw)
    if report["parse"] != "ok":
        return report
    # Wrong algorithm: a blank line is incorrectly treated as leaving a table.
    inside_table = False
    root = {"present": False}
    for line in raw.decode("utf-8").splitlines():
        line = line.strip()
        if not line:
            inside_table = False
        elif line.startswith("["):
            inside_table = True
        elif not inside_table:
            match = re.fullmatch(KEY + r'\s*=\s*"(low|medium|high)"', line)
            if match:
                root = {"present": True, "kind": "string", "value": match[1]}
    report["root"] = root
    return report


def terminal_name_becomes_root(raw):
    report = ORIGINAL(raw)
    if report["parse"] == "ok":
        # Wrong algorithm: the last matching leaf name replaces the root value.
        for match in report["matches"]:
            report["root"] = {"present": True,
                              **{k: v for k, v in match.items() if k != "path"}}
    return report


def table_value_becomes_absent(raw):
    report = ORIGINAL(raw)
    if report["parse"] == "ok" and report["root"].get("kind") == "table":
        # Wrong algorithm: present-but-wrong-type is collapsed into missing.
        report["root"] = {"present": False}
    return report


def array_indices_disappear(raw):
    report = ORIGINAL(raw)
    if report["parse"] == "ok":
        # Wrong algorithm: path serialization keeps strings and drops indices.
        for match in report["matches"]:
            match["path"] = [part for part in match["path"] if not isinstance(part, int)]
    return report


MUTANTS = (
    ("blank_line_resets_table", blank_line_resets_table,
     "test_blank_line_changes_hash_not_location"),
    ("terminal_name_becomes_root", terminal_name_becomes_root,
     "test_all_eight_fixed_reports"),
    ("table_value_becomes_absent", table_value_becomes_absent,
     "test_root_presence_does_not_mean_scalar"),
    ("array_indices_disappear", array_indices_disappear,
     "test_array_of_tables_locations_include_index"),
)


def run_tests(names):
    suite = unittest.TestSuite(test_scope.ScopeTests(name) for name in names)
    result = unittest.TestResult()
    suite.run(result)
    return result


def main():
    baseline = run_tests([name for _, _, name in MUTANTS])
    if not baseline.wasSuccessful() or baseline.testsRun != len(MUTANTS):
        raise SystemExit("FAIL: baseline directed tests did not all pass")
    rows = []
    for name, mutation, test_name in MUTANTS:
        # run() looks up scope_probe.analyze; direct tests use their imported name.
        # Patch both lookup locations, restoring them before the next mutant.
        with patch.object(scope_probe, "analyze", mutation), \
             patch.object(test_scope, "analyze", mutation):
            result = run_tests([test_name])
        if (result.testsRun != 1 or result.errors or result.skipped
                or len(result.failures) != 1
                or "AssertionError" not in result.failures[0][1]):
            raise SystemExit("FAIL: mutant not caught by an assertion: " + name)
        rows.append({"mutant": name, "test": test_name,
                     "outcome": "assertion_failure", "errors": 0})
    if scope_probe.analyze is not ORIGINAL or test_scope.analyze is not ORIGINAL:
        raise SystemExit("FAIL: patched functions were not restored")
    print(json.dumps({"baseline_directed_tests_passed": len(MUTANTS),
                      "mutants": rows}, indent=2))
    print("PASS: 4 mutants caught by assertion failures; 0 test errors")


if __name__ == "__main__":
    main()
python3 -B check_scope_mutants.py
python3 -B -O check_scope_mutants.py

普通模式下的完整变体检查输出如下;优化模式也通过同样检查:

{
  "baseline_directed_tests_passed": 4,
  "mutants": [
    {
      "mutant": "blank_line_resets_table",
      "test": "test_blank_line_changes_hash_not_location",
      "outcome": "assertion_failure",
      "errors": 0
    },
    {
      "mutant": "terminal_name_becomes_root",
      "test": "test_all_eight_fixed_reports",
      "outcome": "assertion_failure",
      "errors": 0
    },
    {
      "mutant": "table_value_becomes_absent",
      "test": "test_root_presence_does_not_mean_scalar",
      "outcome": "assertion_failure",
      "errors": 0
    },
    {
      "mutant": "array_indices_disappear",
      "test": "test_array_of_tables_locations_include_index",
      "outcome": "assertion_failure",
      "errors": 0
    }
  ]
}
PASS: 4 mutants caught by assertion failures; 0 test errors

本次将源码目录、从新增正文代码块重新提取的目录、公开 ZIP 解压目录分别复跑。三处的 17 项测试、八份报告、完整 stdout 比较和四个错误变体均通过;又各自用 -O 运行一次,确认关键判定不依赖可被优化掉的裸 assert。测试数不包含前文的 10 项原练习,也没有追加 Schema 或运行时验收。

15. 回到原清单检查,保留真实加载的未知项

最后把结果接回原清单,避免只证明一个新工具。下面 inventory_scope_demo.py 导入前文未经修改的 inventory.py,在新的临时目录中建立它约定的五个普通文件。除用户配置位置依次放入 01、04,其余四份都固定为一行 model_reasoning_effort = "high"。它不调用 prepare.py,不初始化 Git,不启动 Codex,临时文件离开上下文后清理。

inventory_scope_demo.py

"""Run the article's unchanged inventory over temporary, inert local files."""
import json
from pathlib import Path
import tempfile
from inventory import FILES, inventory
from scope_probe import ROOT, read_fixture


def demo():
    result = {}
    with tempfile.TemporaryDirectory() as temporary:
        root = Path(temporary)
        # Exactly the five known files, no Git init and no Codex process.
        for rel in FILES:
            target = root / rel
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_bytes(b'model_reasoning_effort="high"\n')
        for label, name in (("misplaced", "01-misplaced.toml"),
                            ("repaired", "04-repaired.toml")):
            (root / FILES[0]).write_bytes(read_fixture(ROOT / "inputs", name))
            result[label] = inventory(root)
    return result


if __name__ == "__main__":
    actual = demo()
    expected = json.loads((ROOT / "expected" / "inventory.json").read_text("utf-8"))
    if actual != expected:
        raise SystemExit("FAIL: inventory differs from its fixed expected reports")
    print(json.dumps(actual, ensure_ascii=False, indent=2))

expected/inventory.json 同时是固定预期和该程序的完整标准输出。misplaced 的第一行报告为 declared_effort: null,顶层只有 projects;repaired 的第一行变为 medium,并多出根部键。其余四行应逐项相同。SHA-256 的变化说明读取了另一份字节内容,不说明这份文件已进入应用的加载链。

expected/inventory.json

{
  "misplaced": {
    "scope": "fixture files only; no effective config claim",
    "files": [
      {
        "file": "codex-home/config.toml",
        "sha256": "46c2fa941f0d5cb29e85e5bb8b22e58cab21e8bbb6821d11673546b1f8b596cf",
        "declared_effort": null,
        "top_level_keys": [
          "projects"
        ]
      },
      {
        "file": "codex-home/inspect.config.toml",
        "sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
        "declared_effort": "high",
        "top_level_keys": [
          "model_reasoning_effort"
        ]
      },
      {
        "file": "tide-ledger/.codex/config.toml",
        "sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
        "declared_effort": "high",
        "top_level_keys": [
          "model_reasoning_effort"
        ]
      },
      {
        "file": "tide-ledger/app/.codex/config.toml",
        "sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
        "declared_effort": "high",
        "top_level_keys": [
          "model_reasoning_effort"
        ]
      },
      {
        "file": "ridge-notes/.codex/config.toml",
        "sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
        "declared_effort": "high",
        "top_level_keys": [
          "model_reasoning_effort"
        ]
      }
    ]
  },
  "repaired": {
    "scope": "fixture files only; no effective config claim",
    "files": [
      {
        "file": "codex-home/config.toml",
        "sha256": "7132b626f37a599bba42fc0dc1d7b9216ed82857ab903dac22dcaf0954c95c76",
        "declared_effort": "medium",
        "top_level_keys": [
          "model_reasoning_effort",
          "projects"
        ]
      },
      {
        "file": "codex-home/inspect.config.toml",
        "sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
        "declared_effort": "high",
        "top_level_keys": [
          "model_reasoning_effort"
        ]
      },
      {
        "file": "tide-ledger/.codex/config.toml",
        "sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
        "declared_effort": "high",
        "top_level_keys": [
          "model_reasoning_effort"
        ]
      },
      {
        "file": "tide-ledger/app/.codex/config.toml",
        "sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
        "declared_effort": "high",
        "top_level_keys": [
          "model_reasoning_effort"
        ]
      },
      {
        "file": "ridge-notes/.codex/config.toml",
        "sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
        "declared_effort": "high",
        "top_level_keys": [
          "model_reasoning_effort"
        ]
      }
    ]
  }
}
python3 -B inventory_scope_demo.py

真实排查中,先保留原文件或可恢复版本,在单独副本中确认完整路径与类型,再提出最小修改。若解析报错,先停在编码或语法层;若根部有表而非预期字符串,先核对写法;若根部已经是期望字符串,才继续沿前文的工作目录、profile、项目层和命令行查来源。不要反复追加同一行,也不要把改变 trust 当作修复 TOML 归属的步骤。

本补充核对日期为 2026-10-13,执行范围仅为 Python 3.12.14 的本地合成解析与清单检查。未启动 Codex、未读取或修改真实用户配置、未执行 /debug-config、未对新增反例做 Codex Schema 校验,也未验证客户端会忽略、警告还是拒绝嵌套的同名设置。这些问题仍须在获准的实际环境里独立观察,不能由 TOML 可解析推出答案。

新增程序、夹具和图 4 均为原创;官方资料只用于核对语言及库行为,链接已放在相应说明旁。图 4 以 1120×620 自然尺寸离线渲染核对,未做浏览器页面布局测试。前文三个程序和既有 11 节保持原文,公开包也包含它们,便于读者在同一处保存完整练习。

下载 TOML 作用域补充的完整离线包(SHA-256:1b788a1e77f472c60ae81d19396bce12dd538c031072f2193bc81ba018aaf48b)。包含完整文章、前文三份程序、新探针、八份输入、固定预期、17 项测试、完整输出比较器、错误变体、原清单对照和四幅原创图;不含账户资料、运行缓存或内部工作记录。

16. 来源、许可与复核范围

本文的目录、任务单、记录格式、程序和三幅 SVG 均为原创。官方文档只作事实核对并以链接引用;没有整页搬运。Schema 仅存于私下 QA 目录,不包含在交付 ZIP 中。

滚动文档和本机客户端可能继续变化。复现时先重新核对版本与帮助,再记录你的实际结果。本文没有验证模型可用性、真实项目加载、系统沙箱、网络或组织策略执行效果。