mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
Bind the original archive and receipt identities, preserve the cancelled attempt, and emit synchronous stage progress. No shipping source or provider input changes. Co-Authored-By: Paperclip <noreply@paperclip.ing>
69 lines
8.1 KiB
JavaScript
69 lines
8.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import cp from 'node:child_process';
|
|
import crypto from 'node:crypto';
|
|
import path from 'node:path';
|
|
import {pathToFileURL} from 'node:url';
|
|
const [sourceDirectory, originalDirectory, evidenceDirectory] = process.argv.slice(2);
|
|
const w=fs.realpathSync(sourceDirectory), prior=fs.realpathSync(originalDirectory), r=path.resolve(evidenceDirectory);
|
|
assert.equal(process.platform,'darwin');assert.equal(process.arch,'x64');
|
|
const source='06a3d9739f402baa7f8be7aaa29dda6db7354bfb';
|
|
const receiptPin='60a7f44bf2c60ae8be54cd19ec4244e044ac0bee315d93fbf5ac8f86ea8377c0';
|
|
const hash=p=>crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
|
|
assert.equal(hash(path.join(prior,'receipt.json')),receiptPin);
|
|
const old=JSON.parse(fs.readFileSync(path.join(prior,'receipt.json'),'utf8'));
|
|
assert.equal(old.sourceRevision,source);assert.equal(old.orchestrationRevision,'f88624c7f749adb1255186734a2a0d7386e9ad0c');
|
|
assert.equal(old.target,'darwin-x64');assert.equal(old.nativeHardware.rosetta,false);
|
|
assert.equal(old.nativeHardware.nodeArch,'x64');assert.equal(old.nativeHardware.uname,'x86_64');
|
|
assert.equal(old.providerCalls,0);assert.equal(old.providerCredentials,false);
|
|
assert.equal(old.archives.length,18);
|
|
const completed=new Set();
|
|
for(const s of old.steps.filter(x=>x.status==='passed')) {
|
|
assert.equal(s.exitCode,0);assert.equal(s.errorCode,null);assert.equal(hash(path.join(prior,s.label+'.log')),s.logSha256);completed.add(s.label);
|
|
}
|
|
for(const s of ['normal-workspace-build','normal-standalone-public-build','normal-public-npm-install','verify-normal-native-daemon'])assert.ok(completed.has(s));
|
|
fs.mkdirSync(r,{recursive:true});const home=path.join(r,'home');fs.mkdirSync(home);
|
|
const env={HOME:home,PATH:process.env.PATH,LANG:'en_US.UTF-8',CI:'true',PAPERCLIP_TELEMETRY_ENABLED:'false',PAPERCLIP_UI_DEV_MIDDLEWARE:'false',PAPERCLIP_DISABLE_PLUGIN_AUTOBUILD:'1',npm_config_audit:'false',npm_config_fund:'false',npm_config_foreground_scripts:'true',npm_config_cache:path.join(r,'npm-cache')};
|
|
const orchestration=process.env.PI_QUALIFICATION_ORCHESTRATION_SHA;
|
|
for(const key of Object.keys(process.env))delete process.env[key];Object.assign(process.env,env);
|
|
const query=(cmd,args,cwd=w)=>cp.execFileSync(cmd,args,{cwd,env,encoding:'utf8'}).trim();
|
|
const receipt={schema:'paperclip.pi.native-macos-continuation.v1',status:'running',sourceRevision:source,orchestrationRevision:orchestration,target:'darwin-x64',providerCredentials:false,providerCalls:0,automaticRetries:0,steps:[],archives:[],startedAt:new Date().toISOString(),buildEvidence:{run:37694650556,artifactId:11517098137,receiptSha256:receiptPin,originalStatus:old.status,completedSteps:[...completed],reason:'Continue interrupted Pi setup from verified original packages; preserve cancelled attempt'},lockSha256:old.lockSha256,releaseVersion:old.releaseVersion};
|
|
const save=()=>fs.writeFileSync(path.join(r,'receipt.json'),JSON.stringify(receipt,null,2)+'\n');
|
|
const progress=x=>fs.writeSync(process.stdout.fd,JSON.stringify(x)+'\n');
|
|
function run(label,command,args,cwd,timeout) {
|
|
const p=path.join(r,label+'.log'),fd=fs.openSync(p,'wx'),s={label,status:'running',startedAt:new Date().toISOString()};receipt.steps.push(s);save();progress(s);let x;
|
|
try{x=cp.spawnSync(command,args,{cwd,env,stdio:['ignore',fd,fd],timeout,killSignal:'SIGKILL'});}finally{fs.closeSync(fd);}
|
|
Object.assign(s,{status:x.status===0&&!x.error?'passed':'failed',exitCode:x.status,errorCode:x.error?.code??null,logSha256:hash(p),finishedAt:new Date().toISOString()});save();progress(s);assert.equal(s.status,'passed',label);
|
|
}
|
|
function graph(root,label) {
|
|
const entries=[],real=fs.realpathSync(root);
|
|
function visit(dir){for(const name of fs.readdirSync(dir).sort()){
|
|
const p=path.join(dir,name),rel=path.relative(root,p),st=fs.lstatSync(p);
|
|
if(st.isSymbolicLink()){const resolved=fs.realpathSync(p);assert.ok(resolved===real||resolved.startsWith(real+path.sep));entries.push({path:rel,symlink:fs.readlinkSync(p)});}
|
|
else if(st.isDirectory())visit(p);else if(st.isFile())entries.push({path:rel,bytes:st.size,sha256:hash(p)});else assert.fail(rel);
|
|
}}visit(root);const p=path.join(r,label+'.json');fs.writeFileSync(p,JSON.stringify({root:real,entries},null,2)+'\n');return{entries:entries.length,sha256:hash(p)};
|
|
}
|
|
try{
|
|
assert.equal(query('git',['rev-parse','HEAD']),source);assert.equal(query('git',['status','--porcelain']),'');assert.equal(query('uname',['-m']),'x86_64');
|
|
const translated=cp.spawnSync('sysctl',['-n','sysctl.proc_translated'],{encoding:'utf8'});assert.notEqual(translated.stdout.trim(),'1');
|
|
receipt.nativeHardware={uname:'x86_64',node:process.version,nodeArch:process.arch,rosetta:false,osVersion:query('sw_vers',['-productVersion'])};assert.equal(process.version,'v24.21.0');
|
|
fs.cpSync(prior,path.join(r,'original-build'),{recursive:true});fs.mkdirSync(path.join(r,'archives'));
|
|
for(const a of old.archives){const p=path.resolve(prior,a.path);assert.ok(p.startsWith(path.join(prior,'archives')+path.sep));assert.equal(hash(p),a.sha256);fs.copyFileSync(p,path.join(r,a.path));receipt.archives.push(a);}save();
|
|
const consumer=path.join(r,'consumer');fs.mkdirSync(consumer);fs.writeFileSync(path.join(consumer,'package.json'),JSON.stringify({private:true,type:'module'}));
|
|
run('normal-public-npm-install','npm',['install','--omit=dev',...receipt.archives.map(a=>path.join(r,a.path))],consumer,1200000);
|
|
const lockPath=path.join(consumer,'package-lock.json'),lock=fs.readFileSync(lockPath);receipt.consumerLockSha256=hash(lockPath);fs.copyFileSync(lockPath,path.join(r,'consumer-package-lock.json'));
|
|
for(const a of receipt.archives)assert.equal(JSON.parse(fs.readFileSync(path.join(consumer,'node_modules',a.name,'package.json'))).version,receipt.releaseVersion);
|
|
const server=path.join(consumer,'node_modules/@paperclipai/server'),cli=path.join(consumer,'node_modules/paperclipai/dist/index.js'),daemon=path.join(server,'dist/vendor/paperclip-runner/bin/paperclip-runnerd');assert.equal(hash(daemon),old.daemonSha256);
|
|
run('verify-normal-native-daemon','codesign',['--verify','--strict',daemon],consumer,60000);receipt.binaryDescription=query('file',[daemon],consumer);assert.ok(receipt.binaryDescription.includes('Mach-O 64-bit executable x86_64'));receipt.daemonSha256=hash(daemon);
|
|
receipt.beforePiGraph=graph(path.join(consumer,'node_modules'),'installed-graph-before-pi');save();
|
|
run('normal-pi-runtime-setup',process.execPath,[cli,'runtime','setup','pi'],consumer,300000);
|
|
const setup=JSON.parse(fs.readFileSync(path.join(r,'normal-pi-runtime-setup.log'),'utf8').trim());assert.equal(setup.status,'installed_verified');assert.equal(setup.target,'darwin-x64');assert.deepEqual(fs.readFileSync(lockPath),lock);
|
|
const {verifyPiInstallation}=await import(pathToFileURL(path.join(server,'dist/vendor/paperclip-runner/drivers/acpx/pi-installation.js')));
|
|
const {QUALIFIED_ACPX_PROFILES}=await import(pathToFileURL(path.join(server,'dist/vendor/paperclip-runner/drivers/acpx/qualified-profiles.js')));
|
|
assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion,18);assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest,'sha256:9d3e7d8269f1a0af94616552dfc69671932688b81dbbd5bab9ef356b3a9cbccb');receipt.installation=await verifyPiInstallation(QUALIFIED_ACPX_PROFILES.pi);save();
|
|
receipt.afterPiGraph=graph(path.join(consumer,'node_modules'),'installed-graph-after-pi');save();
|
|
run('public-pi-admission-probe',process.execPath,[path.join(w,'scripts/pi-public-install-probe.mjs'),server],consumer,100000);
|
|
receipt.probe=JSON.parse(fs.readFileSync(path.join(r,'public-pi-admission-probe.log'),'utf8'));assert.equal(receipt.probe.promptCalls,0);assert.equal(receipt.probe.cleanRunnerExit,true);assert.equal(receipt.probe.target,'darwin-x64');assert.equal(hash(daemon),old.daemonSha256);
|
|
receipt.status='passed';receipt.finishedAt=new Date().toISOString();save();progress({status:'passed',target:receipt.target,sourceRevision:source,daemonSha256:receipt.daemonSha256,providerCalls:0});
|
|
}catch(e){receipt.status='failed';receipt.error=String(e);receipt.finishedAt=new Date().toISOString();save();progress({status:'failed',error:receipt.error,providerCalls:0});process.exitCode=1;}
|