rehearse-workshop-docs-update.mjs 3.8 KB

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