rehearse-workshop-research-update.mjs 4.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. // Execute the exact staged/extracted updater against isolated accepted-r2 bytes.
  2. import fs from 'node:fs';
  3. import path from 'node:path';
  4. import os from 'node:os';
  5. import crypto from 'node:crypto';
  6. import assert from 'node:assert/strict';
  7. import {fileURLToPath,pathToFileURL} from 'node:url';
  8. const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
  9. const releaseDir=path.join(root,'docs/workshop-skill-release-20261109-r4');
  10. const packageDir=fs.realpathSync(process.argv[2]||path.join(releaseDir,'package'));
  11. const version=process.argv[4]||'r2';assert.ok(['r2','r3'].includes(version));
  12. const expectedChanges=version==='r2'?14:12;
  13. const output=path.resolve(process.argv[3]||path.join(releaseDir,'staged-rehearsal-'+version+'.json'));
  14. const updater=await import(pathToFileURL(path.join(packageDir,'update-skill.mjs')));
  15. const hash=b=>crypto.createHash('sha256').update(b).digest('hex');
  16. const digest=p=>hash(fs.readFileSync(p));
  17. 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;}
  18. const temp=fs.mkdtempSync(path.join(os.tmpdir(),'ksf-research-package-rehearsal-'));
  19. try{
  20. const dataRoot=path.join(temp,'data'),targetDir=path.join(dataRoot,'skills/ksf-workshop-runtime'),libraryDir=path.join(dataRoot,'skills/ksf-voc-library');
  21. fs.mkdirSync(path.dirname(targetDir),{recursive:true});
  22. fs.cpSync(path.join(releaseDir,'rehearsal-baseline-'+version),targetDir,{recursive:true});
  23. fs.cpSync(path.join(root,'skills/ksf-voc-library'),libraryDir,{recursive:true});fs.unlinkSync(path.join(libraryDir,'SKILL.md'));
  24. fs.writeFileSync(path.join(libraryDir,'FILES.sha256'),Object.entries(fileMap(libraryDir)).filter(([n])=>n!=='FILES.sha256').map(([n,h])=>h+' '+n+'\n').join(''));
  25. fs.writeFileSync(path.join(targetDir,'credential-binding.json'),'{"fixtureOnly":true}',{mode:0o600});
  26. const before=fileMap(dataRoot),options={packageDir,targetDir};
  27. const dry=updater.publicPlan(updater.planUpdate(options));assert.equal(dry.changedFiles,expectedChanges);assert.deepEqual(fileMap(dataRoot),before);
  28. const applied=updater.applyUpdate(options),verify=updater.verifyUpdate({backup:applied.backup});
  29. assert.equal(verify.installedFilesVerified,40);assert.equal(verify.changedFilesVerified,expectedChanges);
  30. const after=fileMap(dataRoot);assert.equal(updater.applyUpdate(options).alreadyApplied,true);assert.deepEqual(fileMap(dataRoot),after);
  31. const rolled=updater.rollbackUpdate({backup:applied.backup});assert.equal(rolled.legacyEntrypointRetired,true);
  32. const restored=fileMap(dataRoot);for(const[n,h]of Object.entries(before))assert.equal(restored[n],h,n);
  33. assert.equal(fs.existsSync(path.join(targetDir,'references/workshop-research-loop.md')),false);
  34. const result={ok:true,checkedAt:new Date().toISOString(),packageDirectory:packageDir,
  35. execution:{platform:process.platform,node:process.version,scope:'imported packaged updater functions in isolated temporary directories; not original-instance CLI'},
  36. baselineId:version,workshopBaseline:'exact accepted '+version+' ZIP state reconstructed and every member SHA checked by packager',
  37. researchFixture:'self-consistent public fixture; four independently pinned runtime hashes, not a full remote export',
  38. dryReadOnly:true,plannedUpdates:version==='r2'?12:11,plannedCreates:version==='r2'?2:1,applySucceeded:true,installedFilesVerified:40,
  39. idempotent:true,rollbackRestoredOriginalBytes:true,legacyEntrypointRemainedRetired:true,
  40. updaterSha256:digest(path.join(packageDir,'update-skill.mjs')),
  41. runnerSha256:digest(path.join(packageDir,'run-update.py')),
  42. skillSha256:verify.skillSha256,manifestSha256:verify.manifestSha256,
  43. productionCalls:0,productionDatabaseWrites:0,paidCalls:0,remoteInstalled:false};
  44. fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n');console.log(JSON.stringify(result,null,2));
  45. }finally{
  46. const abs=fs.realpathSync(temp);assert.ok(abs.startsWith(fs.realpathSync(os.tmpdir())+path.sep)&&path.basename(abs).startsWith('ksf-research-package-rehearsal-'));
  47. fs.rmSync(abs,{recursive:true});
  48. }