Codex 验收前的一步:工作区测试通过,暂存区版本也通过了吗?

给绿色测试加上版本标签,再判断它能否证明下一次提交。

你能带走什么
  • 把测试结果绑定到实际源码版本
  • 识别 HEAD 与工作区相同但索引不同的抵消案例
  • 保留两层 patch、NUL 路径与获准内容
  • 区别单文件版本探针和完整索引测试

1. 测试绿了,准备提交的那份代码却可能没变

让 Codex 修复一个断言,终端显示通过,编辑器里也只剩正确的值。准备提交时,最容易省掉的一步,是确认刚才运行的文件与暂存区里的文件是不是同一份。测试通常从工作目录读取代码;已经暂存的内容不会随着后续编辑自动更新。

本文把这个差别缩成一个可以完整复跑的案例:cancel.py 在 HEAD 中是 result = 1,暂存区中是 result = 999,工作区又被改回 result = 1。对这个路径执行 git diff HEAD,输出为空;从工作区取出的源码通过断言,从索引取出的源码失败。只要普通提交仍使用当前索引,这个错误版本就仍是候选提交内容。

以下实验于 2026 年 10 月 7 日在 Git 2.52.0、Python 3.12.14、Linux 上实际运行。它只创建并删除自己的临时合成仓库,不接受已有仓库路径,不安装依赖,不启动 Codex 或其他模型。文中的测试结果来自 Git 与 Python 子进程,不能解读为对模型审阅能力的测评。

2. 先把验收对象说清楚

HEAD、索引、工作区可以分别理解为最近提交、下一次普通提交的候选内容、当前磁盘上的内容。这里的“普通提交”指没有 -a、没有另行指定路径、没有其他工具或钩子改写索引的情形。commit -a、路径提交、提交钩子以及提交前继续编辑,都可能改变最终对象;不能把本例的结论机械套给所有提交方式。

HEAD 与工作区相同,中间的暂存区仍保存 999。两层差异方向相反。
HEAD 与工作区相同,中间的暂存区仍保存 999。两层差异方向相反。

两层 patch 把过程留下来了:cached 比较 HEAD 到索引,显示 1 被改成 999;默认 diff 比较索引到工作区,显示 999 又被改成 1。HEAD 到工作区的端点比较会抵消这两步。本文另外放了几个辅助文件,因此“空输出”只针对 cancel.py,不是声称整个合成仓库的 git diff HEAD 都为空。

git diff --cached --no-ext-diff --no-textconv -- cancel.py
git diff --no-ext-diff --no-textconv -- cancel.py
git diff HEAD --no-ext-diff --no-textconv -- cancel.py

在真实协作中,先问“要证明当前工作区能运行,还是要证明准备提交的版本能运行”。问题选定后,再给测试记录写上对应版本。一个没有版本标签的绿色结果,只能证明某次运行结束时退出码为零。

3. 在空目录复跑,不接触现有项目

准备一个空目录,把后文四份文件按标题保存到同一目录:lab.py、verify.py、expected.json、run_matrix.py。保留 UTF-8 编码,在该目录用下面的命令启动。完整源码、夹具内容和独立预期都在本文。

python run_matrix.py

运行器会新建 clean 与 poisoned 两个目录,每轮在系统 /tmp 下创建独立仓库,退出时清理临时仓库,只保留 evidence 观察记录。目录已存在就报错,避免覆盖上一次结果;要复跑,请换一个新空目录并使用这四份文件。环境要求是可信 PATH 中的 Git 和 Python 3,不支持把未知 Git 二进制当作安全输入。

预期的关键输出如下。索引探针退出码 1 是我们特意构造的失败;整个实验和验证器仍应成功退出。完整 stderr 保存在 report.json。

HEAD:      result = 1
INDEX:     result = 999
WORKTREE:  result = 1
工作区探针 rc = 0;索引探针 rc = 1
未跟踪路径数 = 5
PASS: independent values, two patches, NUL paths, test binding and unchanged observation
PASS: clean / poisoned semantic reports match; per-run index bytes and mtimes preserved

实验不是把已有仓库拷贝出去再跑测试。它只取 cancel.py 的两份精确源文本,各自在新 Python 进程中接上同一句 assert result == 1。这样足够证明版本错位,却不能证明整个暂存区的依赖、导入关系、构建产物或项目测试套件通过。我们刻意不把单文件检查命名为“索引全量测试”。

4. 让结果回到它实际运行的内容

相同断言分别接在两个精确源文本后运行,记录源码与实际执行代码的 SHA256。
相同断言分别接在两个精确源文本后运行,记录源码与实际执行代码的 SHA256。

report.json 为每次探针记录来源、路径、源码原文、源码 SHA256、断言、实际执行代码的 SHA256、Python 参数、运行目录标识、stdout、stderr 与退出码。记录还绑定同一次观察的 HEAD、status.z 和两层 patch 哈希。这里使用 python -I -B -c,在临时根目录运行已知的合成文本;它不从用户项目导入模块。

证据文件中的 <synthetic-root> 是临时路径的规范化标识,不是读者应输入的路径。真实工程的测试记录应保存实际运行目录、可复现命令、代码版本和依赖版本;仅写“pytest 通过”不足以判断究竟验证了哪份内容。

verify.py 从独立手写的 expected.json 读预期。它检查三份源码、两层 patch 中的确切增删行、两个探针退出码、五个未跟踪路径及内容摘要,还复算测试与观察记录之间的哈希。该验证器用于发现复跑偏差或文件损坏,不是防伪签名;能同时改写结果与校验器的人仍能伪造一份自洽报告。

5. 收集时要保留内容,不能只留状态字母

snapshot 目录里保存 status.z、staged.patch、working.patch、两份 name-status NUL 流、untracked.z、未跟踪内容 JSON 与文件哈希清单。两份 patch 使用 --binary 和 --full-index 保存 Git 输出;这不表示本例验证了所有二进制格式。未跟踪清单限定为未被忽略的文件,ignored/cache.txt 是明确排除的哨兵。

辅助夹具包含一个同时拥有两层改动的 both.txt、一个只暂存的文件、两种删除和一次暂存改名后继续编辑。它们用于确认输出分层,本文的验收问题仍集中在 cancel.py。改名是相似度检测的结果,实验固定 --find-renames=50%,不会把一次 R100 判定当作永久文件身份。

五个未跟踪文件包含空格、制表符、换行和前导短横线。路径流按 NUL 分隔,不能按换行拆分,也不能塞进 shell 变量后假设字节不变。porcelain v1 的改名 NUL 记录还有第二个路径,且新路径在前;name-status 的改名记录也占多个字段。实验保存这两种原始输出,避免发明一个只会处理普通文件名的通用解析器。

paths = raw_untracked.split(b'\0')[:-1]
# 此处只用于 ls-files --others -z 的单路径记录
# 不要把这行原样拿去解析含 rename 的 status/name-status

文件名清单无法代替内容审阅,哈希也无法还原源文件。此处所有未跟踪内容均为已知合成文本,所以逐份保留。真实项目中的秘密、大文件或未获准目录,应先明确范围,不能为收集证据顺手读取或上传。

采集前后比较 index 字节与 mtime、工作区文件内容与 mtime;状态字母相同仍可能内容不同。
采集前后比较 index 字节与 mtime、工作区文件内容与 mtime;状态字母相同仍可能内容不同。

观察开始前与探针结束后,脚本比较 index 文件的字节哈希和 mtime,并比较工作区所有合成普通文件的内容哈希与 mtime。随后另做一个破坏性负例:继续改写已有未暂存修改的 both.txt,status 完全相同,patch 却改变了。这个负例发生在观察窗口之后,不混入前面的测试证据。

这是一份安静环境中的观察记录,不是文件系统原子快照。若编辑器、Codex 或其他进程同时写入,前后检查也不能证明中间没有变化;应暂停写入后重采,并在提交前再次核对候选内容。

6. 环境隔离和退出码的两个细节

lab.py 为 Git 子进程构造环境白名单,不继承 GIT_DIR、GIT_WORK_TREE、GIT_INDEX_FILE 或 GIT_CONFIG_* 等重定向。它使用独立 HOME/XDG、禁用全局与系统 Git 配置、空模板与空 hooksPath,并设置 --no-pager、--no-optional-locks、禁用 fsmonitor、--no-ext-diff 和 --no-textconv。实验还把外部 diff、textconv 和 fsmonitor 配置指向合成哨兵,确认该哨兵未执行。

run_matrix.py 对正常与受污染的父环境分别运行。两轮的内容、patch、测试结果和规范化命令记录一致。两个临时仓库有不同的文件时间,index 中还含文件系统 stat 缓存,因此不要求两轮 index 哈希相同;要求的是每轮采集前后 index 字节与 mtime 均不变。正文提供的比较器明确排除跨仓库的这些差异,而每轮验证器仍保留原值逐项检查。

这些控制只为合成实验可复现。已有仓库的忽略规则、属性和过滤器可能依赖其配置,清空它们会改变语义;禁用 external diff 与 textconv 也不是运行恶意仓库的沙箱。真实仓库不能仅凭这份脚本就获得“安全全覆盖”的结论。

退出码也需要结合命令解释:普通 git diff 在有差异时仍可返回 0;--exit-code 和 --no-index 用 1 表示差异。但本机 Git 2.52.0 对 --no-index 的缺失输入同样返回 1,stderr 为 Could not access。脚本故意保存这一负例。不要用“1 就是成功发现差异”或 || true 吞掉诊断。

git diff --no-index --no-ext-diff --no-textconv -- /dev/null approved-file
# 同时记录 stdout、stderr 和退出码;输入缺失不能算正常差异

--no-index 直接比较文件系统路径,不提供仓库根目录保护。实验比较仓库外文件时,两端仍在同一个获准的临时合成根目录内。路径是否获准来自任务授权,不来自 no-index;显式使用仓库外路径本身也不等于越权。不要把例子改成对任意真实目录递归读取。

7. 给 Codex 的交接语句要包含版本目标

把这个练习迁回日常项目时,先给出仓库与允许路径,并确认目前验收的是哪一层。下面是一段可按实际路径填写的请求;执行项目测试会运行项目代码,仍需放在已经批准的环境和测试范围内。

请检查已批准仓库中的指定路径,目标是确认下一次普通提交的候选内容。
先只读保存 HEAD、暂存区 patch、工作区 patch 和非忽略未跟踪清单。
不要暂存、stash、清理、提交或改写我现有的修改。
把已经运行的测试标明为工作区测试,并记录命令、目录、源码版本和退出码。
若索引与工作区不同,逐项说明哪些测试证据不能用于索引版本。
需要执行额外代码或导出完整索引树时,先说明方案和未覆盖项。

发现索引仍为 999 后,修复取决于提交意图。如果 1 才是要提交的值,可以在确认该路径的全部差异后重新暂存获准内容,再验证更新后的候选版本。如果某些改动刻意没有暂存,就不该用一次宽泛的 git add 把它们一起带入提交。本文不自动替读者做这个决定。

需要真正验证完整索引时,测试代码、被测模块、依赖配置以及构建输入都必须来自定义清楚的同一个版本。导出目录应独立于原工作区,不能借测试改变原 index 或覆盖用户文件。符号链接可能指向目录外,子模块条目只有提交引用,并不等于已获取其中代码;未合并索引、稀疏检出、skip-worktree、assume-unchanged、自定义过滤器和并发修改都需要另行处理。本文没有实现这种完整导出器,也没有验证这些场景。

最后保留一句能复查的话:“这条命令,在这个版本上,以这个退出码结束。”工作区的绿色结果仍然有价值;只有把它和要提交的版本对应起来,才知道它能回答哪个验收问题。

8. 完整源码一:合成夹具与观察记录 lab.py

此文件包含全部初始内容和变更步骤。它只创建自己的临时仓库;不提供现有仓库入口。

#!/usr/bin/env python3
"""Synthetic-only experiment: never accepts an existing repository path."""
import hashlib, json, os, pathlib, platform, subprocess, tempfile, sys
OUT = pathlib.Path.cwd() / 'evidence'
OUT.mkdir(exist_ok=False)  # Never overwrite an earlier observation.
records=[]
def sha(b): return hashlib.sha256(b).hexdigest()
with tempfile.TemporaryDirectory(prefix='index-test-lab-',dir='/tmp') as tmp:
    root=pathlib.Path(tmp); repo=root/'repo'; repo.mkdir(); home=root/'home';home.mkdir();hooks=root/'hooks';hooks.mkdir()
    # Allowlist, rather than merely clearing three Git redirect variables.
    env={'PATH':os.environ['PATH'],'HOME':str(home),'XDG_CONFIG_HOME':str(home),
         'LC_ALL':'C','GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':os.devnull,
         'GIT_TERMINAL_PROMPT':'0','GIT_OPTIONAL_LOCKS':'0','GIT_PAGER':'cat',
         'GIT_AUTHOR_DATE':'2026-01-01T00:00:00Z','GIT_COMMITTER_DATE':'2026-01-01T00:00:00Z'}
    prefix=['git','--no-pager','--no-optional-locks','-c',f'core.hooksPath={hooks}',
            '-c','core.fsmonitor=false','-c','color.ui=false','-C',str(repo)]
    def git(*args,ok=(0,)):
        r=subprocess.run(prefix+list(args),env=env,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        records.append({'argv':['git',*args],'rc':r.returncode,'stdout':r.stdout.decode('utf-8','backslashreplace'),'stderr':r.stderr.decode('utf-8','backslashreplace')})
        if r.returncode not in ok: raise RuntimeError(records[-1])
        return r.stdout
    def put(p,s): (repo/p).write_text(s)
    git('init','-b','main','--template=');git('config','user.name','Synthetic Lab');git('config','user.email','lab@example.invalid');git('config','commit.gpgsign','false')
    bases={'.gitattributes':'*.txt diff=blocked\n','only-staged.txt':'base\n','both.txt':'HEAD\n','cancel.py':'result = 1\n','staged-delete.txt':'remove staged\n','working-delete.txt':'remove working\n','old-name.txt':'rename payload\n','.gitignore':'ignored/\n'}
    for p,s in bases.items():put(p,s)
    git('add','--','.');git('commit','-m','synthetic baseline')
    baseline=git('rev-parse','HEAD').decode().strip()
    helper=root/'external-helper.sh';marker=root/'external-helper-ran'
    helper.write_text('#!/bin/sh\nprintf invoked > '+str(marker)+'\nexit 7\n');helper.chmod(0o700)
    git('config','diff.external',str(helper));git('config','diff.blocked.textconv',str(helper))
    git('config','core.fsmonitor',str(helper))
    # Default diff can be empty although staged and untracked changes exist.
    put('only-staged.txt','staged\n');git('add','--','only-staged.txt');put('new file.txt','untracked evidence\n')
    empty_default=git('diff','--no-ext-diff','--no-textconv')
    assert empty_default==b''
    phase1={'default_diff_empty':True,'cached_nonempty':bool(git('diff','--cached','--no-ext-diff','--no-textconv')),'status':git('status','--porcelain=v1','-z','--untracked-files=all').decode()}
    put('both.txt','INDEX\n');git('add','--','both.txt');put('both.txt','WORKTREE\n')
    put('cancel.py','result = 999\n');git('add','--','cancel.py');put('cancel.py',bases['cancel.py'])
    git('rm','--','staged-delete.txt');(repo/'working-delete.txt').unlink()
    git('mv','--','old-name.txt','new-name.txt');put('new-name.txt','rename payload\nworking extra\n')
    weird=['space name.txt','tab\tname.txt','line\nname.txt','-leading.txt']
    for p in weird:put(p,'synthetic untracked\n')
    (repo/'ignored').mkdir();put('ignored/cache.txt','intentionally excluded\n')
    diff=['diff','--no-ext-diff','--no-textconv','--no-color','--find-renames=50%']
    def names(*a):return git(*diff,*a,'--name-only','-z').split(b'\0')[:-1]
    working=names();staged=names('--cached');net=names('HEAD')
    untracked=git('ls-files','--others','--exclude-standard','-z').split(b'\0')[:-1]
    assert b'cancel.py' in working and b'cancel.py' in staged and b'cancel.py' not in net
    assert b'only-staged.txt' not in working and b'only-staged.txt' in staged
    assert b'both.txt' in working and b'both.txt' in staged
    assert b'new file.txt' not in working+staged+net and b'new file.txt' in untracked
    assert set(untracked)=={p.encode() for p in weird+['new file.txt']}
    assert b'staged-delete.txt' in staged and b'working-delete.txt' in working
    ns=git(*diff,'--cached','--name-status','-z');assert b'R100\0old-name.txt\0new-name.txt\0' in ns
    index_bytes=(repo/'.git/index').read_bytes();index_stat=(repo/'.git/index').stat()
    wt_before={str(p.relative_to(repo)):{'sha256':sha(p.read_bytes()),'mtime_ns':p.stat().st_mtime_ns} for p in repo.rglob('*') if p.is_file() and '.git' not in p.relative_to(repo).parts}
    snap=OUT/'snapshot';snap.mkdir(exist_ok=True)
    for name,args in [('status.z',['status','--porcelain=v1','-z','--untracked-files=all']),('staged.patch',diff+['--cached','--binary','--full-index']),('working.patch',diff+['--binary','--full-index']),('untracked.z',['ls-files','--others','--exclude-standard','-z']),('staged-names.z',diff+['--cached','--name-status','-z']),('working-names.z',diff+['--name-status','-z'])]:
        (snap/name).write_bytes(git(*args))
    untracked_manifest=[{'path':os.fsdecode(p),'bytes':len((repo/os.fsdecode(p)).read_bytes()),'sha256':sha((repo/os.fsdecode(p)).read_bytes()),'content_utf8':(repo/os.fsdecode(p)).read_text()} for p in untracked]
    (snap/'untracked-content.json').write_text(json.dumps(untracked_manifest,ensure_ascii=True,indent=2)+'\n')
    assert (repo/'.git/index').read_bytes()==index_bytes and (repo/'.git/index').stat().st_mtime_ns==index_stat.st_mtime_ns
    assert wt_before=={str(p.relative_to(repo)):{'sha256':sha(p.read_bytes()),'mtime_ns':p.stat().st_mtime_ns} for p in repo.rglob('*') if p.is_file() and '.git' not in p.relative_to(repo).parts}
    # Worktree source passes its assertion, staged source demonstrably does not.
    working_source=(repo/'cancel.py').read_text();index_source=git('show',':cancel.py').decode()
    assert working_source=='result = 1\n' and index_source=='result = 999\n'
    probes=[]
    for label,source in [('working',working_source),('index',index_source)]:
        test_code=source+'assert result == 1\n'
        r=subprocess.run([sys.executable,'-I','-B','-c',test_code],cwd=root,env=env,capture_output=True)
        probes.append({'tree':label,'path':'cancel.py','source':source,'source_sha256':sha(source.encode()),'assertion':'assert result == 1\n','executed_code_sha256':sha(test_code.encode()),'command':['python','-I','-B','-c','<source + assertion>'],'cwd':'<synthetic-root>','rc':r.returncode,'stdout':r.stdout.decode(),'stderr':r.stderr.decode(),'head':baseline,'status_sha256':sha((snap/'status.z').read_bytes()),'staged_patch_sha256':sha((snap/'staged.patch').read_bytes()),'working_patch_sha256':sha((snap/'working.patch').read_bytes())})
    assert [p['rc'] for p in probes]==[0,1]
    observation={'index_before_sha256':sha(index_bytes),'index_after_sha256':sha((repo/'.git/index').read_bytes()),'index_before_mtime_ns':index_stat.st_mtime_ns,'index_after_mtime_ns':(repo/'.git/index').stat().st_mtime_ns,'worktree_before':wt_before,'worktree_after':{str(p.relative_to(repo)):{'sha256':sha(p.read_bytes()),'mtime_ns':p.stat().st_mtime_ns} for p in repo.rglob('*') if p.is_file() and '.git' not in p.relative_to(repo).parts}}
    assert observation['worktree_before']==observation['worktree_after']
    assert observation['index_before_sha256']==observation['index_after_sha256']
    assert observation['index_before_mtime_ns']==observation['index_after_mtime_ns']
    # Status alone does not bind content: change a preexisting unstaged edit.
    status_before=git('status','--porcelain=v1','-z','--untracked-files=all');patch_before=git(*diff)
    put('both.txt','WORKTREE changed again\n')
    assert git('status','--porcelain=v1','-z','--untracked-files=all')==status_before
    assert git(*diff)!=patch_before
    # Exit codes: plain diff returns zero with changes; exit-code/no-index use 1.
    git(*diff,'--','cancel.py');plain_rc=records[-1]['rc']
    git(*diff,'--exit-code','--','cancel.py',ok=(1,));difference_rc=records[-1]['rc']
    git(*diff,'--exit-code','HEAD','--','cancel.py');net_rc=records[-1]['rc']
    outside=root/'outside.txt';outside.write_text('outside repo but synthetic and authorized\n')
    git('diff','--no-index','--no-ext-diff','--no-textconv','--',os.devnull,str(repo/'new file.txt'),ok=(1,));noindex_rc=records[-1]['rc']
    git('diff','--no-index','--no-ext-diff','--no-textconv','--',str(repo/'new file.txt'),str(outside),ok=(1,));outside_rc=records[-1]['rc']
    git('diff','--no-index','--no-ext-diff','--no-textconv','--',str(outside),str(root/'missing'),ok=(1,));error_rc=records[-1]['rc'];error_stderr=records[-1]['stderr']
    # Missing no-index input can also return 1 on this Git: retain stderr, don't label all 1 as success.
    assert not marker.exists(), 'External helper unexpectedly ran'
    report={'git_version':git('--version').decode().strip(),'python':platform.python_version(),'platform':platform.platform(),'head':baseline,'three_versions':{'HEAD':git('show','HEAD:cancel.py').decode(),'INDEX':index_source,'WORKTREE':working_source},'observation':observation,'phase1':phase1,'paths':{'working':[os.fsdecode(p) for p in working],'staged':[os.fsdecode(p) for p in staged],'net_HEAD':[os.fsdecode(p) for p in net],'untracked':[os.fsdecode(p) for p in untracked]},'assertions':{'staged_cancel_invisible_in_HEAD_diff':True,'same_path_three_versions':True,'five_untracked_files_NUL_roundtrip':True,'staged_R100_rename_and_unstaged_edit':True,'snapshot_does_not_change_index_bytes_or_mtime':True,'snapshot_does_not_change_worktree':True,'external_diff_textconv_fsmonitor_helpers_not_run':True,'same_status_different_patch':True,'working_result':1,'index_result':999},'exit_codes':{'ordinary_diff_changed':plain_rc,'diff_exit_code_changed':difference_rc,'diff_HEAD_cancelled':net_rc,'no_index_changed':noindex_rc,'no_index_outside_repo':outside_rc,'no_index_missing_input':error_rc,'no_index_missing_stderr':error_stderr},'behavior_probes':probes,'commands':records}
    report=json.loads(json.dumps(report).replace(str(root),'<synthetic-root>'))
    (OUT/'report.json').write_text(json.dumps(report,ensure_ascii=True,indent=2)+'\n')
    (snap/'manifest.json').write_text(json.dumps({'head':baseline,'git_version':report['git_version'],'scope':'synthetic regular files; nonignored untracked; no concurrent writes; snapshot is evidence, not atomic freeze','files':{p.name:sha(p.read_bytes()) for p in sorted(snap.iterdir()) if p.name!='manifest.json'}},indent=2)+'\n')
    print(json.dumps({'three_versions':report['three_versions'],'probe_rc':[p['rc'] for p in probes],'untracked_count':len(untracked),'index_unchanged':True,'worktree_unchanged_during_observation':True,'no_index_missing_rc':error_rc,'no_index_missing_stderr':report['exit_codes']['no_index_missing_stderr']},ensure_ascii=False,indent=2))

9. 完整源码二:独立预期 expected.json

预期值独立写出,不从本轮报告自动生成。退出码负例按本文实测的 Git 2.52.0 固定;其他版本若表现不同,应先检查 stderr 和版本,不能直接改成放宽所有错误。

{
  "three_versions": {"HEAD": "result = 1\n", "INDEX": "result = 999\n", "WORKTREE": "result = 1\n"},
  "probe_rc": [0, 1],
  "untracked": ["-leading.txt", "line\nname.txt", "new file.txt", "space name.txt", "tab\tname.txt"],
  "staged_cancel": ["-result = 1", "+result = 999"],
  "working_cancel": ["-result = 999", "+result = 1"],
  "exit_codes": {"ordinary_diff_changed": 0, "diff_exit_code_changed": 1, "diff_HEAD_cancelled": 0, "no_index_changed": 1, "no_index_outside_repo": 1, "no_index_missing_input": 1}
}

10. 完整源码三:证据校验 verify.py

#!/usr/bin/env python3
"""Independent expected values; read-only verification of one lab run."""
import hashlib, json, pathlib
P=pathlib.Path
expected=json.loads((P(__file__).resolve().parent/'expected.json').read_text())
evidence=P.cwd()/'evidence'
r=json.loads((evidence/'report.json').read_text())
s=evidence/'snapshot'
def sha(b): return hashlib.sha256(b).hexdigest()
assert r['three_versions']==expected['three_versions']
assert [p['rc'] for p in r['behavior_probes']]==expected['probe_rc']
for name,key in [('staged.patch','staged_cancel'),('working.patch','working_cancel')]:
    # Match exact patch lines, including the independent expected signs.
    lines=(s/name).read_text().splitlines()
    assert all(line in lines for line in expected[key])
assert b'MM cancel.py\0' in (s/'status.z').read_bytes()
assert 'cancel.py' not in r['paths']['net_HEAD']
assert sorted((s/'untracked.z').read_bytes().split(b'\0')[:-1])==sorted(p.encode() for p in expected['untracked'])
contents=json.loads((s/'untracked-content.json').read_text())
assert sorted(x['path'] for x in contents)==sorted(expected['untracked'])
for x in contents:
    b=x['content_utf8'].encode()
    assert x['sha256']==sha(b) and x['bytes']==len(b)
for probe,tree in zip(r['behavior_probes'],['WORKTREE','INDEX']):
    source=expected['three_versions'][tree]
    assert probe['source']==source
    assert probe['source_sha256']==sha(source.encode())
    assert probe['assertion']=='assert result == 1\n'
    assert probe['executed_code_sha256']==sha((source+probe['assertion']).encode())
    assert probe['head']==r['head']
    for field,file in [('status_sha256','status.z'),('staged_patch_sha256','staged.patch'),('working_patch_sha256','working.patch')]:
        assert probe[field]==sha((s/file).read_bytes())
o=r['observation']
assert o['index_before_sha256']==o['index_after_sha256']
assert o['index_before_mtime_ns']==o['index_after_mtime_ns']
assert o['worktree_before']==o['worktree_after']
for key,value in expected['exit_codes'].items(): assert r['exit_codes'][key]==value
assert 'Could not access' in r['exit_codes']['no_index_missing_stderr']
manifest=json.loads((s/'manifest.json').read_text())
for file,digest in manifest['files'].items(): assert sha((s/file).read_bytes())==digest
assert all(r['assertions'][key] for key in ['external_diff_textconv_fsmonitor_helpers_not_run','same_status_different_patch'])
print('PASS: independent values, two patches, NUL paths, test binding and unchanged observation')

11. 完整源码四:双环境复跑 run_matrix.py

#!/usr/bin/env python3
"""Run in an empty directory. Requires only Python and Git on PATH."""
import json, os, pathlib, subprocess, sys
here=pathlib.Path(__file__).resolve().parent
for label in ['clean','poisoned']:
    out=pathlib.Path.cwd()/label
    out.mkdir(exist_ok=False)
    env={k:v for k,v in os.environ.items() if not k.startswith('GIT_')}
    if label=='poisoned':
        env.update(GIT_DIR='/nonexistent',GIT_WORK_TREE='/nonexistent',
            GIT_INDEX_FILE='/nonexistent',GIT_CONFIG_COUNT='1',
            GIT_CONFIG_KEY_0='diff.external',GIT_CONFIG_VALUE_0='/nonexistent',
            GIT_EXTERNAL_DIFF='/nonexistent',GIT_TEMPLATE_DIR='/nonexistent',
            GIT_PAGER='/nonexistent',GIT_CONFIG_GLOBAL='/nonexistent')
    for script in ['lab.py','verify.py']:
        subprocess.run([sys.executable,str(here/script)],cwd=out,env=env,check=True)
a=json.loads(pathlib.Path('clean/evidence/report.json').read_text())
b=json.loads(pathlib.Path('poisoned/evidence/report.json').read_text())
def semantic(v):
    if isinstance(v,dict):
        # Index contains filesystem stat data; independent repos differ here.
        # verify.py checks each before/after pair without normalization.
        return {k:semantic(x) for k,x in v.items()
                if 'mtime' not in k and k not in ['index_before_sha256','index_after_sha256']}
    if isinstance(v,list): return [semantic(x) for x in v]
    return v
assert semantic(a)==semantic(b)
print('PASS: clean / poisoned semantic reports match; per-run index bytes and mtimes preserved')

12. 补充实验:导出目录里的测试通过了,却少跑了一项

前面的单文件探针回答了源码版本是否一致。继续往前走一步,如果把暂存区导出到独立目录再跑测试,导出过程本身也要验收。这个补充实验于 2026 年 10 月 13 日在 Linux、Git 2.52.0、Python 3.12.14 上运行:暂存树里有两个测试,git archive 导出的目录只剩烟雾测试;内置 unittest discover 跑了一个测试,显示 OK,退出码为 0。被省略的核心测试恰好会发现 VALUE = 999 的错误。

这里的遗漏来自固定夹具中的 export-ignore 属性。它是明确配置的归档行为,拿归档目录作为完整暂存树的替身时,才会影响验收结论。导出目录通过的那项测试确实通过了,问题在于原先约定的另一项测试根本没有进入运行目录。

同一棵五文件暂存树:archive 遗漏核心测试,只跑通过的烟雾测试;原始 blob 完整运行两项后发现失败。
先确认测试输入没有丢失,再确认运行了约定的测试。

补充代码只创建自己的临时合成仓库,范围为根目录下五个固定名称、模式为 100644 的普通文件。原文四份脚本和单文件探针保持原样;下面的实验是另一个独立目录中的程序,没有执行 Codex、模型或真实项目。

13. 五份输入,把缺失的测试和保留下来的测试分开看

fixtures.json 给出完整初始内容。value.py 起初是 VALUE = 1,先把 999 暂存,再把工作区改回 1。test_core.py 检查数值等于 1,test_smoke.py 只检查它是整数。这是两项不同的检查:999 能通过类型检查,却不能通过数值检查。固定的 .gitattributes 让归档忽略 test_core.py,并给 version.txt 设置 export-subst。

暂存树的五个路径:
.gitattributes
test_core.py
test_smoke.py
value.py
version.txt

固定 test ID:
test_core.ValueTests.test_value
test_smoke.SmokeTests.test_value_type

程序把合成仓库的 index 复制到另一个临时文件,再为这份副本设置 GIT_INDEX_FILE,调用 write-tree 得到明确的 tree ID。write-tree 会创建 Git 对象,也可能更新所用索引的缓存,因此这里没有在原 index 上执行。随后按这个 tree ID 列出对象,逐项要求固定路径、100644 模式和 blob 类型,再用 cat-file blob 读取原始字节。Git 的说明分别给出了 write-tree 的索引来源及完整合并前提,以及 cat-file 指定类型时返回原始对象内容的行为。

读取器遇到第六个路径、子目录、非预期名称、可执行文件、符号链接或子模块条目就拒绝。这个白名单让实验可复查,也把适用范围限定得很窄。不要删去这些检查后,把它当作任意仓库的索引导出工具。

14. 路径和字节要在导入前核对,测试身份要在运行后核对

gate(reference, candidate) 比较完整路径集合以及同名文件的精确字节,分别报告 missing、extra、changed。reference 来自固定暂存树的原始 blob,candidate 来自归档读取结果。当前归档缺少 test_core.py,因此 guarded_run 在建立运行目录之前就返回 tests_started=false,后续导入不会开始。为展示错误路径,实验另外保留一次明确标注的无门禁归档运行。

归档读取只处理程序自己生成的 tar:在内存里核对固定名称、重复成员、普通文件类型、尺寸上限和执行位,再逐份取内容;没有调用 extract 或 extractall。通过文件门禁后,程序在新目录写入已核对的内容,启动单独的 Python 进程。测试发现会导入测试模块,所以测试 ID 无法靠这里的动态发现机制在导入前检查;运行器会把发现的 ID、发现数、实际运行数、失败、错误、跳过项及 loader 错误记录回来。

suite_accepted 随后要求 ID 与 expected.json 中两个固定名称完全相符,发现数和运行数都为 2,且无失败、错误、跳过、加载错误,退出码为 0。文件门禁和运行结果检查有各自的职责。只比较文件数会漏掉同数量改名,只比较路径会漏掉被改成无条件通过的测试,只比较测试数量也无法确认执行的是哪两项检查。

工作区:        2 tests / 0 failures / rc 0
archive:       1 test  / 0 failures / rc 0
原始暂存 blob: 2 tests / 1 failure  / rc 1
显式重新暂存后:2 tests / 0 failures / rc 0

archive 文件门禁:missing = ["test_core.py"]
guarded archive:tests_started = false

这里还保留了一个容易混淆的对照:本机内置 python -I -B -m unittest discover 在空目录运行时,记录的是 NO TESTS RAN 和退出码 5。归档的绿色结果来自一项确实运行且通过的烟雾测试,不能把它讲成内置命令“零测试也返回 0”。CPython v3.12.14 的 TestProgram 实现包含无测试退出分支。本文用于生成结构化结果的简化运行器则按 TestResult.wasSuccessful() 选择退出码,所以 suite_accepted 仍明确拒绝空套件;它没有把简化运行器的行为当作内置 CLI 的保证。

15. 同一棵树,归档属性还可能改变文件内容

工作区的 .gitattributes 被故意改成取消 test_core.py 的 export-ignore,但这份变化没有暂存。默认 archive 仍按固定树里的属性省略测试。另一次加入 --worktree-attributes 的归档会保留测试,文件门禁在当前夹具上通过。git-archive 的属性说明解释了默认树内属性、工作区属性选项和 info/attributes 的覆盖入口。这组对照说明,导出记录需要把属性来源也写清楚;仅保存 tree ID 还没有描述完整的归档命令与环境。

version.txt 的原始内容为 build=$Format:%H$。把 bare tree ID 交给 archive 时,这段占位符原样保留。程序再用 commit-tree 创建一个仅供实验的提交对象,核对该对象的 ^{tree} 正是同一 tree ID,再归档这个提交对象;此时占位符被替换成这个提交的哈希,门禁在 missing 之外还报告 changed=["version.txt"]。这个临时提交对象没有更新 HEAD。

gitattributes 的 export-subst 说明将替换与提交信息是否可用联系起来,并区分 tree 与 commit/tag 输入。本次程序实际比较的是 bare tree 与 commit;tag 行为只援引官方说明,没有把未执行的 tag 分支写成实测。用占位符变化推断两个归档来自不同源码树,在这个例子中就会得出错误结论。

16. 恢复时,明确改变哪一个候选版本

发现暂存值仍为 999 后,程序只在自己的临时合成仓库执行 git add -- value.py。此时工作区的值已是 1,所以这一步明确改变了索引候选内容。随后重新复制 index、生成新的 tree ID、读取五份原始 blob,并用新树自己的内容作为门禁基准,运行相同的两个测试。结果为两项通过,退出码 0;.gitattributes 的工作区改动没有被这次单路径暂存带入。

重新暂存之前,报告检查了原 index 字节、index mtime、HEAD,以及工作区 value.py 的内容均与观察起点一致。不要把这四项检查扩写成对整个工作区每个文件或其 mtime 的承诺。write-tree 和 commit-tree 仍会在临时仓库对象库中写入对象;这里的“不变”只指列明的观察对象和阶段。

实际项目要不要重新暂存,由要提交的内容决定。若测试文件、依赖配置或其他未暂存修改也影响结果,单独暂存 value.py 未必够用。先说明允许的仓库、路径、运行环境与提交意图,再决定后续动作;这份程序没有已有仓库入口,也不会代替读者对真实改动做这个决定。

17. 在新的空目录复跑,并保留失败发生的位置

补充部分共五份文件:archive_lab.py、fixtures.json、expected.json、test_archive_lab.py、test_mutants.py,完整内容都在下文。把它们放在一个新目录,使用可信的 Git 和 Python 3;实测环境为 Git 2.52.0、Python 3.12.14。脚本不安装依赖,不联网。下面按源码包的目录结构运行;若手动保存下文代码,则在保存目录直接执行后三行。

cd source/supplement
python -B archive_lab.py evidence-new
python -B -m unittest -v test_archive_lab
python -B test_mutants.py

evidence-new 必须不存在,避免覆盖上一轮证据。它会保存 report.json、原始暂存输入 JSON、归档 tar、归档 CLI 的 stdout/stderr,以及空套件 CLI 的 stderr。stdout 中的四组摘要应与上文一致。测试中的预期失败是“原始暂存版本的一项数值断言失败”;实验主程序只有核对完这些独立预期后才成功退出。不要用 || true 把实验自身的异常也当作这个预期失败。

expected.json 的路径、ID、数值和各阶段结果是固定预期,不从本轮报告生成。test_archive_lab.py 用独立小夹具检查门禁,包括文件遗漏、额外文件、同数量改名、同路径改写、占位符替换、空测试、同数量错误 ID、加载错误和失败/错误/跳过/退出码。test_mutants.py 先要求原函数通过固定判定,再换入八个故意错误的实现,分别证明指定反例能检出“全部放行”“只数文件”“只看路径”“忽略额外文件”“只看退出码”“只数测试”“忽略跳过”“忽略加载错误”。它们是定向退化检查,不能据此推断任意实现错误都会被发现。

Git 子命令出错或超时,程序立即抛错;输出目录已存在、JSON 不合法、归档成员越界、固定预期不符也会停止。程序不会自行放宽路径集合或更新预期。先查看失败阶段、stderr 和版本,再决定是环境差异还是夹具被改动。内置 CLI 的空测试退出码作为本次环境的固定对照,换环境出现不同结果时应保留记录,不能无声改成“任意非零都算通过”。请勿使用 Python -O;这里的验收断言需要正常执行。

隔离的 HOME、Git 配置、属性来源及空钩子目录只服务于已知合成输入;读取的 tar 也是本程序自己生成的。未验证已有仓库、嵌套目录、符号链接、子模块、可执行文件、冲突索引、稀疏索引、自定义过滤器、并发改动、恶意测试代码或操作系统沙箱。本例也没有形成提交事务,不能保证检查完成后其他进程不会继续改写真实项目。

18. 补充完整源码一:固定夹具 fixtures.json

这五份 UTF-8 文本是实验的全部仓库输入;测试源码也在其中。

{
  ".gitattributes": "test_core.py export-ignore\nversion.txt export-subst\n",
  "value.py": "VALUE = 1\n",
  "test_core.py": "import unittest\nfrom value import VALUE\n\nclass ValueTests(unittest.TestCase):\n    def test_value(self):\n        self.assertEqual(VALUE, 1)\n",
  "version.txt": "build=$Format:%H$\n",
  "test_smoke.py": "import unittest\nfrom value import VALUE\n\nclass SmokeTests(unittest.TestCase):\n    def test_value_type(self):\n        self.assertIsInstance(VALUE, int)\n"
}

19. 补充完整源码二:独立预期 expected.json

预期明确区分归档里剩下的一项测试与完整树中的两项测试。

{
  "tree_paths": [
    ".gitattributes",
    "test_core.py",
    "test_smoke.py",
    "value.py",
    "version.txt"
  ],
  "archive_paths": [
    ".gitattributes",
    "test_smoke.py",
    "value.py",
    "version.txt"
  ],
  "index_value": "VALUE = 999\n",
  "working_value": "VALUE = 1\n",
  "source_version": "build=$Format:%H$\n",
  "test_ids": [
    "test_core.ValueTests.test_value",
    "test_smoke.SmokeTests.test_value_type"
  ],
  "working": {
    "tests_run": 2,
    "failures": 0,
    "errors": 0,
    "skipped": 0,
    "rc": 0
  },
  "archive": {
    "tests_run": 1,
    "failures": 0,
    "errors": 0,
    "skipped": 0,
    "rc": 0
  },
  "raw_index": {
    "tests_run": 2,
    "failures": 1,
    "errors": 0,
    "skipped": 0,
    "rc": 1
  },
  "repaired_index": {
    "tests_run": 2,
    "failures": 0,
    "errors": 0,
    "skipped": 0,
    "rc": 0
  },
  "archive_test_ids": [
    "test_smoke.SmokeTests.test_value_type"
  ],
  "naive_cli_rc": 0,
  "zero_cli_rc": 5,
  "cli": {
    "working": {
      "rc": 0,
      "ran": 2,
      "status": "OK"
    },
    "archive": {
      "rc": 0,
      "ran": 1,
      "status": "OK"
    },
    "raw_index": {
      "rc": 1,
      "ran": 2,
      "status": "FAILED (failures=1)"
    },
    "repaired_index": {
      "rc": 0,
      "ran": 2,
      "status": "OK"
    }
  }
}

20. 补充完整源码三:导出与运行 archive_lab.py

"""Only this five-file, trusted synthetic Git fixture; not a repository exporter."""
import hashlib
import io
import json
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys
import tarfile
import tempfile

HERE = Path(__file__).resolve().parent
NAMES = {'.gitattributes', 'test_core.py', 'test_smoke.py', 'value.py', 'version.txt'}
GIT = shutil.which('git', path='/usr/local/bin:/usr/bin:/bin')
RUNNER = '''import json, pathlib, sys, unittest
sys.path.insert(0, str(pathlib.Path.cwd()))
loader = unittest.TestLoader()
suite = loader.discover('.', pattern='test*.py')
def ids(s):
    if isinstance(s, unittest.TestSuite):
        return [name for t in s for name in ids(t)]
    return [s.id()]
test_ids = ids(suite)
count = suite.countTestCases()
r = unittest.TestResult()
suite.run(r)
print(json.dumps(dict(ids=test_ids, discovered=count, tests_run=r.testsRun,
    failures=len(r.failures), errors=len(r.errors), skipped=len(r.skipped),
    loader_errors=loader.errors), sort_keys=True))
sys.exit(0 if r.wasSuccessful() else 1)
'''


def sha(raw):
    return hashlib.sha256(raw).hexdigest()


def gate(reference, candidate):
    """Exact filenames and raw blob contents, not counts alone."""
    missing = sorted(reference.keys() - candidate.keys())
    extra = sorted(candidate.keys() - reference.keys())
    changed = sorted(p for p in reference.keys() & candidate.keys()
                     if reference[p] != candidate[p])
    return dict(accepted=not (missing or extra or changed), missing=missing,
                extra=extra, changed=changed)


def suite_accepted(result, expected_ids):
    """Trusted fixed fixture contract. Counts alone cannot name a suite."""
    return (sorted(result['ids']) == sorted(expected_ids)
            and result['discovered'] == len(expected_ids)
            and result['tests_run'] == len(expected_ids)
            and len(expected_ids) > 0
            and all(result[k] == 0 for k in ('failures', 'errors', 'skipped', 'rc'))
            and not result['loader_errors'])


def guarded_run(path, reference, candidate, env, expected_ids):
    check = gate(reference, candidate)
    if not check['accepted']:
        return dict(accepted=False, tests_started=False, export_gate=check)
    materialize(path, candidate)
    run = test_run(path, env)
    return dict(accepted=suite_accepted(run, expected_ids), tests_started=True,
                export_gate=check, run=run)


def read_fixed_tar(raw):
    """Read our own archive in memory. Never call extract/extractall."""
    result = {}
    with tarfile.open(fileobj=io.BytesIO(raw), mode='r:') as archive:
        members = archive.getmembers()
        if len(members) > len(NAMES):
            raise ValueError('outside fixed fixture')
        for item in members:
            if (item.name not in NAMES or item.name in result or not item.isfile()
                    or item.size > 65536 or item.mode & 0o111):
                raise ValueError('outside fixed regular-file fixture')
            result[item.name] = archive.extractfile(item).read()
    return result


def materialize(path, files):
    if not files.keys() <= NAMES:
        raise ValueError('outside fixed names')
    path.mkdir(exist_ok=False)
    for name, raw in files.items():
        (path / name).write_bytes(raw)


def test_run(path, env):
    command = [sys.executable, '-I', '-B', '-c', RUNNER]
    r = subprocess.run(command, cwd=path, env=env, capture_output=True, timeout=15)
    result = json.loads(r.stdout)
    result.update(rc=r.returncode, stderr=r.stderr.decode())
    return result


def main(out):
    out.mkdir(exist_ok=False)
    fixtures = {k: v.encode() for k, v in json.loads((HERE/'fixtures.json').read_text()).items()}
    expected = json.loads((HERE/'expected.json').read_text())
    if set(fixtures) != NAMES:
        raise ValueError('fixture names changed')
    with tempfile.TemporaryDirectory(prefix='index-archive-') as temp:
        root = Path(temp)
        repo, home, hooks = root/'repo', root/'home', root/'hooks'
        home.mkdir(); hooks.mkdir(); materialize(repo, fixtures)
        env = dict(PATH='/usr/local/bin:/usr/bin:/bin', HOME=str(home),
                   XDG_CONFIG_HOME=str(home), LC_ALL='C', GIT_CONFIG_NOSYSTEM='1',
                   GIT_CONFIG_GLOBAL=os.devnull, GIT_ATTR_NOSYSTEM='1',
                   GIT_AUTHOR_NAME='Fixture Author', GIT_AUTHOR_EMAIL='fixture@example.invalid',
                   GIT_COMMITTER_NAME='Fixture Author', GIT_COMMITTER_EMAIL='fixture@example.invalid',
                   GIT_AUTHOR_DATE='2026-01-01T00:00:00Z',
                   GIT_COMMITTER_DATE='2026-01-01T00:00:00Z')
        prefix = [GIT, '--no-pager', '--no-optional-locks', '-C', str(repo),
                  '-c', f'core.hooksPath={hooks}', '-c', 'core.fsmonitor=false',
                  '-c', f'core.attributesFile={os.devnull}', '-c', 'core.autocrlf=false',
                  '-c', 'commit.gpgsign=false']
        commands = []
        def git(*args, index=None):
            local_env = dict(env)
            if index is not None:
                local_env['GIT_INDEX_FILE'] = str(index)
            r = subprocess.run(prefix+list(args), env=local_env, capture_output=True, timeout=15)
            commands.append(dict(argv=list(args), rc=r.returncode,
                                 stderr=r.stderr.decode(), stdout_sha256=sha(r.stdout)))
            if r.returncode:
                raise RuntimeError(commands[-1])
            return r.stdout
        git('init', '-b', 'main', '--template=')
        git('add', '--', *sorted(NAMES))
        git('commit', '-m', 'five trusted files')
        head = git('rev-parse', 'HEAD').decode().strip()
        (repo/'value.py').write_bytes(b'VALUE = 999\n')
        git('add', '--', 'value.py')
        (repo/'value.py').write_bytes(b'VALUE = 1\n')
        # Worktree attribute differs. Default archive must still read pinned tree attrs.
        (repo/'.gitattributes').write_bytes(b'# working-only override\ntest_core.py -export-ignore\n')
        index_path = repo/'.git/index'
        index_before = index_path.read_bytes()
        index_mtime = index_path.stat().st_mtime_ns
        def freeze(label):
            copy = root/(label+'-index')
            copy.write_bytes(index_path.read_bytes())
            # write-tree creates tree objects and may update this copied index's cache.
            return git('write-tree', index=copy).decode().strip()
        def tree_files(tree):
            listing = git('ls-tree', '-r', '-z', '--full-tree', tree)
            entries, files = [], {}
            for record in listing.split(b'\0')[:-1]:
                metadata, path = record.split(b'\t', 1)
                mode, kind, oid = metadata.decode().split()
                name = path.decode('ascii')
                if name not in NAMES or mode != '100644' or kind != 'blob':
                    raise ValueError('outside fixed tree')
                raw = git('cat-file', 'blob', oid)
                files[name] = raw
                entries.append(dict(path=name, mode=mode, kind=kind, oid=oid,
                                    size=len(raw), sha256=sha(raw)))
            if set(files) != NAMES:
                raise ValueError('tree is not complete fixture')
            return entries, files
        tree = freeze('staged')
        entries, raw_files = tree_files(tree)
        archive_bytes = git('archive', '--format=tar', tree)
        archive_files = read_fixed_tar(archive_bytes)
        (out/'staged.tar').write_bytes(archive_bytes)
        # This materialization intentionally illustrates the naïve archive path.
        # The input is generated here, and the reader allows only the five fixed names.
        naive = root/'naive'
        materialize(naive, archive_files)
        raw_dir = root/'raw-index'; materialize(raw_dir, raw_files)
        runs = dict(working=test_run(repo, env), archive=test_run(naive, env),
                    raw_index=test_run(raw_dir, env))
        cli = subprocess.run([sys.executable, '-I', '-B', '-m', 'unittest', 'discover'],
                             cwd=naive, env=env, capture_output=True, timeout=15)
        (out/'naive-unittest.stdout').write_bytes(cli.stdout)
        (out/'naive-unittest.stderr').write_bytes(cli.stderr)
        empty_dir = root/'empty-suite'; empty_dir.mkdir()
        zero_cli = subprocess.run([sys.executable, '-I', '-B', '-m', 'unittest', 'discover'],
                                  cwd=empty_dir, env=env, capture_output=True, timeout=15)
        (out/'zero-unittest.stderr').write_bytes(zero_cli.stderr)
        assert zero_cli.returncode == expected['zero_cli_rc']
        assert b'Ran 0 tests' in zero_cli.stderr and b'NO TESTS RAN' in zero_cli.stderr
        # Attribute-source probe: same tree/content, only attribute lookup changes.
        override_files = read_fixed_tar(git('archive', '--format=tar', '--worktree-attributes', tree))
        # Both object types name exactly the same tree. Only commit has substitution context.
        commit = git('commit-tree', tree, '-m', 'synthetic attribute probe').decode().strip()
        assert git('rev-parse', commit+'^{tree}').decode().strip() == tree
        commit_files = read_fixed_tar(git('archive', '--format=tar', commit))
        gates = dict(archive=gate(raw_files, archive_files),
                     worktree_attributes=gate(raw_files, override_files),
                     raw_index=gate(raw_files, raw_files),
                     commit_archive=gate(raw_files, commit_files))
        guarded = dict(archive=guarded_run(root/'blocked-archive', raw_files, archive_files,
                                           env, expected['test_ids']),
                       raw_index=guarded_run(root/'checked-index', raw_files, raw_files,
                                             env, expected['test_ids']))
        assert not (root/'blocked-archive').exists()
        unchanged = dict(index_bytes=index_before == index_path.read_bytes(),
                         index_mtime=index_mtime == index_path.stat().st_mtime_ns,
                         head=head == git('rev-parse', 'HEAD').decode().strip(),
                         working_value=(repo/'value.py').read_bytes() == b'VALUE = 1\n')
        assert all(unchanged.values())
        # Recovery in this disposable fixture: explicitly stage just value.py.
        # Do not use this step in a user's repository without scope/approval.
        git('add', '--', 'value.py')
        repaired_tree = freeze('repaired')
        repaired_entries, repaired_files = tree_files(repaired_tree)
        repaired_dir = root/'repaired'; materialize(repaired_dir, repaired_files)
        runs['repaired_index'] = test_run(repaired_dir, env)
        cli_runs = {}
        for label, directory in [('working', repo), ('archive', naive),
                                 ('raw_index', raw_dir), ('repaired_index', repaired_dir)]:
            r = subprocess.run([sys.executable, '-I', '-B', '-m', 'unittest', 'discover'],
                               cwd=directory, env=env, capture_output=True, timeout=15)
            oracle = expected['cli'][label]
            assert r.returncode == oracle['rc']
            assert ('Ran '+str(oracle['ran'])+' test').encode() in r.stderr
            assert oracle['status'].encode() in r.stderr
            (out/('cli-'+label+'.stdout')).write_bytes(r.stdout)
            (out/('cli-'+label+'.stderr')).write_bytes(r.stderr)
            cli_runs[label] = dict(rc=r.returncode, stdout=r.stdout.decode(),
                                   stderr=r.stderr.decode())
        guarded['repaired_index'] = guarded_run(root/'checked-repaired', repaired_files,
                                                repaired_files, env, expected['test_ids'])
        for label in ('working', 'archive', 'raw_index', 'repaired_index'):
            for field, value in expected[label].items():
                assert runs[label][field] == value, (label, field, runs[label])
        for label in ('working', 'raw_index', 'repaired_index'):
            assert runs[label]['ids'] == expected['test_ids']
            assert runs[label]['discovered'] == len(expected['test_ids'])
        assert runs['archive']['ids'] == expected['archive_test_ids']
        assert runs['archive']['discovered'] == len(expected['archive_test_ids'])
        assert cli.returncode == expected['naive_cli_rc']
        assert b'Ran 1 test' in cli.stderr and b'OK' in cli.stderr
        assert sorted(raw_files) == expected['tree_paths']
        assert sorted(archive_files) == expected['archive_paths']
        assert raw_files['value.py'].decode() == expected['index_value']
        assert raw_files['version.txt'].decode() == expected['source_version']
        assert archive_files['version.txt'] == raw_files['version.txt']
        assert commit_files['version.txt'] == ('build='+commit+'\n').encode()
        assert gates['archive'] == dict(accepted=False, missing=['test_core.py'], extra=[], changed=[])
        assert gates['worktree_attributes']['accepted']
        report = dict(git_version=git('--version').decode().strip(),
                      python=platform.python_version(), tree=tree, commit=commit,
                      repaired_tree=repaired_tree, tree_entries=entries,
                      repaired_entries=repaired_entries, runs=runs, cli_runs=cli_runs, gates=gates, guarded=guarded,
                      unchanged_before_explicit_repair=unchanged,
                      archive_paths=sorted(archive_files),
                      tree_attributes=raw_files['.gitattributes'].decode(),
                      working_attributes=(repo/'.gitattributes').read_text(),
                      substitution=dict(raw=raw_files['version.txt'].decode(),
                                        bare_tree=archive_files['version.txt'].decode(),
                                        commit=commit_files['version.txt'].decode()),
                      naive_cli_rc=cli.returncode, zero_cli_rc=zero_cli.returncode, commands=commands)
        report_text = json.dumps(report, indent=2).replace(str(root), '<synthetic-root>')
        (out/'report.json').write_text(report_text+'\n')
        (out/'raw-index-inputs.json').write_text(json.dumps({k:v.decode() for k,v in raw_files.items()}, indent=2)+'\n')
        summary = {label:{k:run[k] for k in ('discovered','tests_run','failures','errors','rc')}
                   for label,run in runs.items()}
        summary.update(archive_gate=gates['archive'], commit_archive_gate=gates['commit_archive'],
                       index_unchanged_before_repair=all(unchanged.values()))
        print(json.dumps(summary, indent=2))
    return 0

if __name__ == '__main__':
    if len(sys.argv) != 2:
        raise SystemExit('usage: python archive_lab.py NEW_OUTPUT_DIRECTORY')
    raise SystemExit(main(Path(sys.argv[1]).resolve()))

21. 补充完整源码四:门禁反例 test_archive_lab.py

"""Independent names, contents and test IDs; no expected values from live output."""
import copy
import tempfile
from pathlib import Path
import unittest
from archive_lab import gate, guarded_run, suite_accepted

class ExportGateTests(unittest.TestCase):
    def setUp(self):
        self.reference = {'.gitattributes': b'test_core.py export-ignore\n',
                          'value.py': b'VALUE = 999\n',
                          'test_core.py': b'assert VALUE == 1\n'}

    def test_exact_blob_set(self):
        self.assertEqual(gate(self.reference, dict(self.reference)),
                         {'accepted': True, 'missing': [], 'extra': [], 'changed': []})

    def test_omitted_test(self):
        candidate = dict(self.reference); del candidate['test_core.py']
        self.assertEqual(gate(self.reference, candidate),
                         {'accepted': False, 'missing': ['test_core.py'], 'extra': [], 'changed': []})

    def test_same_count_different_file(self):
        candidate = dict(self.reference)
        candidate['test_decoy.py'] = candidate.pop('test_core.py')
        self.assertEqual(len(candidate), len(self.reference))
        self.assertEqual(gate(self.reference, candidate),
                         {'accepted': False, 'missing': ['test_core.py'],
                          'extra': ['test_decoy.py'], 'changed': []})

    def test_extra_file(self):
        candidate = dict(self.reference, **{'unexpected.txt': b'not in tree\n'})
        self.assertEqual(gate(self.reference, candidate),
                         {'accepted': False, 'missing': [],
                          'extra': ['unexpected.txt'], 'changed': []})

    def test_same_paths_changed_test(self):
        candidate = dict(self.reference); candidate['test_core.py'] = b'assert True\n'
        self.assertEqual(set(candidate), set(self.reference))
        self.assertEqual(gate(self.reference, candidate)['changed'], ['test_core.py'])
        self.assertFalse(gate(self.reference, candidate)['accepted'])

    def test_missing_test_blocks_before_directory_or_import(self):
        candidate = dict(self.reference); del candidate['test_core.py']
        with tempfile.TemporaryDirectory() as d:
            out = Path(d)/'must-not-exist'
            result = guarded_run(out, self.reference, candidate, {}, ['test_core.Case.test_value'])
            self.assertFalse(result['tests_started'])
            self.assertFalse(out.exists())

    def test_substituted_blob(self):
        self.assertEqual(gate({'version.txt': b'build=$Format:%H$\n'},
                              {'version.txt': b'build=0123456789\n'})['changed'], ['version.txt'])

class SuiteGateTests(unittest.TestCase):
    def setUp(self):
        self.ids = ['test_core.ValueTests.test_value']
        self.good = dict(ids=self.ids, discovered=1, tests_run=1, failures=0,
                         errors=0, skipped=0, rc=0, loader_errors=[])

    def test_exact_success(self):
        self.assertTrue(suite_accepted(self.good, self.ids))

    def test_zero_tests_with_rc_zero_is_rejected(self):
        result = dict(self.good, ids=[], discovered=0, tests_run=0)
        self.assertFalse(suite_accepted(result, self.ids))

    def test_same_count_different_test_is_rejected(self):
        result = dict(self.good, ids=['test_decoy.OtherTests.test_trivial'])
        self.assertFalse(suite_accepted(result, self.ids))

    def test_failure_error_skip_or_wrong_exit_is_rejected(self):
        for name in ['failures', 'errors', 'skipped', 'rc']:
            with self.subTest(name=name):
                result = dict(self.good, **{name: 1})
                self.assertFalse(suite_accepted(result, self.ids))

    def test_loader_errors_are_rejected(self):
        result = dict(self.good, loader_errors=['synthetic import failure'])
        self.assertFalse(suite_accepted(result, self.ids))

    def test_expected_empty_suite_is_not_acceptance(self):
        result = dict(self.good, ids=[], discovered=0, tests_run=0)
        self.assertFalse(suite_accepted(result, []))

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

22. 补充完整源码五:定向变异 test_mutants.py

"""Fixed local gate oracles, then deliberately wrong implementations."""
import json
from archive_lab import gate, suite_accepted

REF = {'test_core.py': b'core assertion\n', 'test_smoke.py': b'smoke assertion\n'}
IDS = ['test_core.ValueTests.test_value', 'test_smoke.SmokeTests.test_value_type']
GOOD = dict(ids=IDS, discovered=2, tests_run=2, failures=0, errors=0,
            skipped=0, rc=0, loader_errors=[])
FILE_CASES = [
    ('exact', dict(REF), True),
    ('missing', {'test_smoke.py': REF['test_smoke.py']}, False),
    ('renamed_same_count', {'test_other.py': REF['test_core.py'],
                            'test_smoke.py': REF['test_smoke.py']}, False),
    ('changed', dict(REF, **{'test_core.py': b'pass\n'}), False),
    ('extra', dict(REF, **{'test_extra.py': b'pass\n'}), False),
]
RUN_CASES = [
    ('exact', dict(GOOD), True),
    ('empty', dict(GOOD, ids=[], discovered=0, tests_run=0), False),
    ('wrong_ids_same_count', dict(GOOD, ids=['other.A.test_a', 'other.A.test_b']), False),
    ('failed', dict(GOOD, failures=1, rc=1), False),
    ('skipped', dict(GOOD, skipped=1), False),
    ('loader_error', dict(GOOD, loader_errors=['fixture import failure']), False),
]

def file_failures(fn):
    return [name for name, files, wanted in FILE_CASES
            if bool(fn(REF, files)) != wanted]

def run_failures(fn):
    return [name for name, result, wanted in RUN_CASES
            if bool(fn(result, IDS)) != wanted]

assert file_failures(lambda r, c: gate(r, c)['accepted']) == []
assert run_failures(suite_accepted) == []
# Each target must fail on its named fixed oracle, not on a crash.
mutants = [
    ('accept_every_export', 'missing', file_failures(lambda r, c: True)),
    ('file_count_only', 'renamed_same_count', file_failures(lambda r, c: len(r) == len(c))),
    ('paths_only', 'changed', file_failures(lambda r, c: r.keys() == c.keys())),
    ('ignore_extra', 'extra', file_failures(lambda r, c: all(c.get(k) == v for k, v in r.items()))),
    ('exit_only', 'empty', run_failures(lambda r, ids: r['rc'] == 0)),
    ('count_without_ids', 'wrong_ids_same_count', run_failures(lambda r, ids:
        r['tests_run'] == len(ids) and r['rc'] == 0)),
    ('ignore_skips', 'skipped', run_failures(lambda r, ids:
        suite_accepted(dict(r, skipped=0), ids))),
    ('ignore_loader_error', 'loader_error', run_failures(lambda r, ids:
        suite_accepted(dict(r, loader_errors=[]), ids))),
]
for name, required, failures in mutants:
    assert required in failures, (name, required, failures)
print(json.dumps({'baseline': 'PASS', 'killed': [dict(mutant=n, required_case=c,
      failed_cases=f) for n, c, f in mutants]}, indent=2))

23. 补充材料:完整离线包与源码包

下载的 ZIP 现在包含 fullarticle-offline.html 完整离线文章、四幅内嵌图及 figures/ 下独立 SVG、原文和补充实验的九份源码、固定夹具和预期、历史样例输出及 SHA256 清单。解压后打开 fullarticle-offline.html 即可离线阅读;官方参考链接需要联网后打开。

source/original 保留原文四份文件;source/supplement 为补充实验五份文件;source/sample-output 保留 2026 年 10 月 13 日的样例结果。复跑时请使用新输出目录。2026 年 10 月 20 日补齐了旧 ZIP 缺失的离线文章和四幅 SVG,并重新核对完整文件清单;没有把历史样例改写成这次复跑结果。

SHA256 清单用于发现文件变化,不是签名,也不能证明输出不可伪造。程序、输入、预期和报告都由同一人改写时,仍可生成表面自洽的结果。要在工程验收中采用类似方式,还需要独立确定测试范围、保护证据来源并复核要提交的版本。

下载完整离线文章、源码与样例结果 ZIP