// Execute the exact staged/extracted updater against isolated accepted-r2 bytes. import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import crypto from 'node:crypto'; import assert from 'node:assert/strict'; import {fileURLToPath,pathToFileURL} from 'node:url'; const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..'); const releaseDir=path.join(root,'docs/workshop-skill-release-20261109-r3'); const packageDir=fs.realpathSync(process.argv[2]||path.join(releaseDir,'package')); const output=path.resolve(process.argv[3]||path.join(releaseDir,'staged-rehearsal.json')); const updater=await import(pathToFileURL(path.join(packageDir,'update-skill.mjs'))); const hash=b=>crypto.createHash('sha256').update(b).digest('hex'); const digest=p=>hash(fs.readFileSync(p)); function fileMap(dir){const out={};for(const e of fs.readdirSync(dir,{withFileTypes:true})){const p=path.join(dir,e.name);if(e.isDirectory())for(const[n,h]of Object.entries(fileMap(p)))out[e.name+'/'+n]=h;else out[e.name]=digest(p);}return out;} const temp=fs.mkdtempSync(path.join(os.tmpdir(),'ksf-docs-package-rehearsal-')); try{ const dataRoot=path.join(temp,'data'),targetDir=path.join(dataRoot,'skills/ksf-workshop-runtime'),libraryDir=path.join(dataRoot,'skills/ksf-voc-library'); fs.mkdirSync(path.dirname(targetDir),{recursive:true}); fs.cpSync(path.join(releaseDir,'rehearsal-baseline'),targetDir,{recursive:true}); fs.cpSync(path.join(root,'skills/ksf-voc-library'),libraryDir,{recursive:true});fs.unlinkSync(path.join(libraryDir,'SKILL.md')); fs.writeFileSync(path.join(libraryDir,'FILES.sha256'),Object.entries(fileMap(libraryDir)).filter(([n])=>n!=='FILES.sha256').map(([n,h])=>h+' '+n+'\n').join('')); fs.writeFileSync(path.join(targetDir,'credential-binding.json'),'{"fixtureOnly":true}',{mode:0o600}); const before=fileMap(dataRoot),options={packageDir,targetDir}; const dry=updater.publicPlan(updater.planUpdate(options));assert.equal(dry.changedFiles,9);assert.deepEqual(fileMap(dataRoot),before); const applied=updater.applyUpdate(options),verify=updater.verifyUpdate({backup:applied.backup}); assert.equal(verify.installedFilesVerified,39);assert.equal(verify.changedFilesVerified,9); const after=fileMap(dataRoot);assert.equal(updater.applyUpdate(options).alreadyApplied,true);assert.deepEqual(fileMap(dataRoot),after); const rolled=updater.rollbackUpdate({backup:applied.backup});assert.equal(rolled.legacyEntrypointRetired,true); const restored=fileMap(dataRoot);for(const[n,h]of Object.entries(before))assert.equal(restored[n],h,n); assert.equal(fs.existsSync(path.join(targetDir,'references/workshop-feedback.md')),false); const result={ok:true,checkedAt:new Date().toISOString(),packageDirectory:packageDir, execution:{platform:process.platform,node:process.version,scope:'imported packaged updater functions in isolated temporary directories; not original-instance CLI'}, workshopBaseline:'exact 38 accepted r2 ZIP members, each SHA checked by packager', researchFixture:'self-consistent public fixture; four independently pinned runtime hashes, not a full remote export', dryReadOnly:true,plannedUpdates:8,plannedCreates:1,applySucceeded:true,installedFilesVerified:39, idempotent:true,rollbackRestoredOriginalBytes:true,legacyEntrypointRemainedRetired:true, updaterSha256:digest(path.join(packageDir,'update-skill.mjs')), runnerSha256:digest(path.join(packageDir,'run-update.py')), skillSha256:verify.skillSha256,manifestSha256:verify.manifestSha256, productionCalls:0,productionDatabaseWrites:0,paidCalls:0,remoteInstalled:false}; fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n');console.log(JSON.stringify(result,null,2)); }finally{ const abs=fs.realpathSync(temp);assert.ok(abs.startsWith(fs.realpathSync(os.tmpdir())+path.sep)&&path.basename(abs).startsWith('ksf-docs-package-rehearsal-')); fs.rmSync(abs,{recursive:true}); }