rehearse-workshop-quality-update.mjs 4.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  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-r5');
  10. const packageDir=fs.realpathSync(process.argv[2]||path.join(releaseDir,'package'));
  11. const version=process.argv[4]||'r2';assert.ok(['r2','r3','r4'].includes(version));
  12. const [expectedChanges,expectedUpdates,expectedCreates]={r2:[17,14,3],r3:[16,14,2],r4:[9,8,1]}[version];
  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,41);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. for(const n of Object.keys(restored).filter(n=>!n.startsWith('ksf-workshop-research-backup-')))assert.ok(Object.hasOwn(before,n),n);
  34. assert.equal(fs.existsSync(path.join(targetDir,'references/workshop-innovation-reasoning.md')),false);
  35. const result={ok:true,checkedAt:new Date().toISOString(),packageDirectory:packageDir,
  36. execution:{platform:process.platform,node:process.version,scope:'imported packaged updater functions in isolated temporary directories; not original-instance CLI'},
  37. baselineId:version,workshopBaseline:'exact accepted '+version+' ZIP state reconstructed and every member SHA checked by packager',
  38. researchFixture:'self-consistent public fixture; four independently pinned runtime hashes, not a full remote export',
  39. dryReadOnly:true,plannedUpdates:expectedUpdates,plannedCreates:expectedCreates,applySucceeded:true,installedFilesVerified:41,
  40. idempotent:true,rollbackRestoredOriginalBytes:true,legacyEntrypointRemainedRetired:true,
  41. updaterSha256:digest(path.join(packageDir,'update-skill.mjs')),
  42. runnerSha256:digest(path.join(packageDir,'run-update.py')),
  43. skillSha256:verify.skillSha256,manifestSha256:verify.manifestSha256,
  44. productionCalls:0,productionDatabaseWrites:0,paidCalls:0,remoteInstalled:false};
  45. fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n');console.log(JSON.stringify(result,null,2));
  46. }finally{
  47. const abs=fs.realpathSync(temp);assert.ok(abs.startsWith(fs.realpathSync(os.tmpdir())+path.sep)&&path.basename(abs).startsWith('ksf-research-package-rehearsal-'));
  48. fs.rmSync(abs,{recursive:true});
  49. }