        plan=json.loads((folder/'planned-sha.json').read_text());planned={r['name']:r['sha256'] for r in plan};changes=output.get('changes',[]);check('改稿限定两个既有文件',{r.get('name') for r in changes}=={'a.txt','b.txt'} and len(changes)==2);written=[];conflicts=[];audit=[]
        target=folder/'updated';target.mkdir(exist_ok=True)
        for row in changes:
            name=row.get('name');source=folder/'source-files'/str(name)
            if name not in planned or Path(str(name)).name!=name:conflicts.append({'name':name,'reason':'not-allowlisted'});continue
            actual=sha(source)
            if actual!=planned[name]:conflicts.append({'name':name,'reason':'sha-changed'});audit.append({'name':name,'planned_sha':planned[name],'actual_sha':actual,'action':'blocked'});continue
            text=row.get('text','');temp=target/(name+'.tmp');temp.write_text(text);temp.replace(target/name);written.append(name);audit.append({'name':name,'planned_sha':planned[name],'actual_sha':actual,'action':'write-copy'})
        check('仅未变动a写入副本，b冲突保留',written==['a.txt'] and any(r['name']=='b.txt' and r['reason']=='sha-changed' for r in conflicts));check('原a和用户改动b都未覆盖',sha(folder/'source-files/a.txt')==planned['a.txt'] and (folder/'source-files/b.txt').read_text()=='用户在预览后手动修改B，请保留。');check('本地路径穿越负例被拒绝',Path('../escape.txt').name!='../escape.txt');write_csv(artifact('write-plan.csv'),audit,['name','planned_sha','actual_sha','action']);dump(artifact('conflicts.json'),conflicts);artifacts.append('updated/a.txt');result={'writtenCopies':written,'conflicts':conflicts,'originalFilesPreserved':True}
