import test from 'node:test'; import assert from 'node:assert/strict'; import {execFileSync,spawnSync,spawn} from 'node:child_process'; import {symlinkSync,unlinkSync,mkdtempSync,mkdirSync,writeFileSync,readFileSync,cpSync,rmSync,renameSync,chmodSync,existsSync,statSync,readdirSync,lstatSync,readlinkSync} from 'node:fs'; import {createHash} from 'node:crypto'; import {tmpdir} from 'node:os'; import {join,dirname} from 'node:path'; import {fileURLToPath} from 'node:url'; import {once} from 'node:events'; import {createServer} from 'node:net'; import {createOwnedRunner} from './fixtures/owned-child.mjs'; import {pathToFileURL} from 'node:url'; import {fixture,notifyConfig,BUSINESSES} from './helpers.mjs'; import {reserveOrg,activateOrg} from '../../business/src/org-registry.mjs'; import {writeDeployment} from '../src/deployment.mjs'; import {fakeOidc} from '../../webui/tests/helpers/fake-oidc.mjs'; const ROOT=fileURLToPath(new URL('../../..',import.meta.url)).replace(/\/$/,''); const git=(cwd,...args)=>execFileSync('git',['-c','user.name=fixture','-c','user.email=fixture@invalid','-c','commit.gpgsign=false','-c','core.hooksPath=/dev/null','-C',cwd,...args],{encoding:'utf8'}).trim(); export function setup(t, realPin=false){ const root=mkdtempSync(join(tmpdir(),'stack-copy-')),repo=join(root,'repo'),copies=join(root,'copies'),bin=join(root,'bin'); const runner=createOwnedRunner(t,()=>env); t.after(async()=>{ await runner.close(); execFileSync('chmod',['-R','u+w',root]);rmSync(root,{recursive:true,force:true}); }); mkdirSync(repo);mkdirSync(bin); // A linked node_modules (a deploy tree) is not a directory to .gitignore's node_modules/; npm ci makes the copy's own. const tracked=execFileSync('git',['-C',ROOT,'ls-files','-co','--exclude-standard','--','scripts','packages','adapters','roles','tools/unslop-hook','RELEASE','package.json','package-lock.json',':!packages/*/tests',':!scripts/foundation',':(exclude,glob)**/node_modules'],{encoding:'utf8'}).trim().split('\n'); for(const f of tracked)if(existsSync(join(ROOT,f)))cpSync(join(ROOT,f),join(repo,f)); // Lightweight npm-ci fixture; real pin/host/proof integration is tested separately. const pkg={name:'copy-fixture',version:'1.0.0'}; if(!realPin){writeFileSync(join(repo,'package.json'),JSON.stringify(pkg)); writeFileSync(join(repo,'package-lock.json'),JSON.stringify({name:pkg.name,version:pkg.version,lockfileVersion:3,packages:{'':pkg}}));} else {for(const f of ['package.json','package-lock.json'])cpSync(join(ROOT,f),join(repo,f));} git(repo,'init','-q','-b','refactor');git(repo,'config','gc.auto','0');git(repo,'add','.');git(repo,'commit','-qm','fixture'); const id=git(repo,'rev-parse','HEAD');git(repo,'update-ref','refs/remotes/origin/refactor',id); const env={PATH:`${bin}:${dirname(process.execPath)}:/usr/bin:/bin`,HOME:root,XDG_DATA_HOME:join(root,'data'),XDG_CONFIG_HOME:join(root,'config')}; const search=join(root,'unit-search');mkdirSync(search); const analyze=body=>{writeFileSync(join(bin,'systemd-analyze'),'#!/bin/sh\n'+body);chmodSync(join(bin,'systemd-analyze'),0o755);}; analyze(`printf '%s\\n' '${search}'\n`); const runChild=runner.run; const fake=body=>{writeFileSync(join(bin,'systemctl'),'#!/bin/bash\n'+body);chmodSync(join(bin,'systemctl'),0o755)}; const run=(script,args=[])=>runChild(join(repo,'scripts',script),args); return {root,repo,copies,bin,id,env,run,runChild,fake,search,analyze}; } // Row 66: a session launched from a pinned copy reads each harness module's files from the copy, // so every path a module names under its REPO (adapter, mounts, bundle sources) is in COPY_PATHS. test('a copy carries every file the harness modules read from it',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); const copy=join(s.copies,s.id),paths=[]; for(const kind of ['pi','claude-code']){ const m=await import(pathToFileURL(join(copy,'packages','plugins','harness',kind,'index.mjs')).href); // pi's mounts() resolves node_modules, which this fixture copy lacks; its unslop mounts are its // bundle sources, so the sources stand in for them. paths.push(m.adapter(),...(kind==='pi'?Object.values(m.bundle().sources):m.mounts({env:{}}).map(x=>x.src))); } assert.equal(paths.filter(p=>p.includes('/tools/unslop-hook/')).length,6,paths.join('\n')); for(const p of paths){assert.ok(p.startsWith(copy+'/'),p);assert.ok(existsSync(p),p);} }); test('shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation',async t=>{ const s=setup(t),r=(await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]));assert.equal(r.status,0,r.stderr); const copy=join(s.copies,s.id);assert.equal(statSync(copy).mode&0o777,0o555); assert.deepEqual(JSON.parse(readFileSync(join(copy,'.stack-build.json'),'utf8')), {commit:s.id}); assert.equal(statSync(join(copy,'.stack-build.json')).mode&0o222,0); for(const f of ['scripts/mosaic','adapters/pi/adapter.sh','packages/queue/src/cli.mjs','package.json','package-lock.json','packages/tasks/requirements.json','packages/project/src/index.mjs','packages/library/src/index.mjs','packages/project/schemas/project.schema.json','packages/project/templates/north-star.md','packages/plugins/src/kinds.mjs','packages/plugins/src/loader.mjs','packages/processing-control/src/state.mjs','packages/processing-control/src/containers.mjs','packages/processing-control/src/effects.mjs'])assert.ok(existsSync(join(copy,f)),f); const npm=join(s.bin,'npm');writeFileSync(npm,'#!/bin/sh\nexit 99\n');chmodSync(npm,0o755); const existing=(await s.run('webui-service.sh',['copy',s.id,'--copies',s.copies]));assert.equal(existing.status,0);assert.equal(existing.stdout,`exists: ${copy}\n`);unlinkSync(npm); git(s.repo,'update-ref','refs/remotes/origin/refactor',git(s.repo,'hash-object','-w','package.json')); const denied=(await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]));assert.notEqual(denied.status,0);assert.match(denied.stderr,/origin\/refactor/); }); test('wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); const wrapper=join(s.bin,'mosaic'),marker='# mosaic-cli wrapper (Q81, agents/sage/work/q81/mosaic-cli.sh)'; const lease=`[ "\${1:-}" = __lease-capability ] && exec "${s.bin}/mosaic-old" "$@"`; writeFileSync(wrapper,`#!/usr/bin/env bash\n${marker}\n${lease}\nexec "/old/scripts/mosaic" "$@"\n`); assert.equal((await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin])).status,0); const lines=readFileSync(wrapper,'utf8').trimEnd().split('\n'); assert.equal(lines[1],marker); assert.equal(lines[2],`# CLI runs from pinned copy: ${join(s.copies,s.id)}`); assert.equal(lines[3],'# The Q81 lease probe still answers at mosaic-old.'); assert.equal(lines.at(-1),`exec "${join(s.copies,s.id)}/scripts/mosaic" "$@"`); assert.match(readFileSync(wrapper,'utf8'),new RegExp(s.id));assert.ok(readFileSync(wrapper,'utf8').includes(lease)); const old=join(s.bin,'mosaic-old');writeFileSync(old,'#!/bin/sh\nprintf \"legacy:%s\\n\" \"$1\"\n');chmodSync(old,0o755); assert.equal(spawnSync(wrapper,['__lease-capability'],{env:s.env,encoding:'utf8'}).stdout,'legacy:__lease-capability\n'); const copy=join(s.copies,s.id);s.fake(`case "$*" in *mosaic-bus*) echo 'LoadState=loaded'; echo 'WorkingDirectory=${copy}'; echo 'ExecStart={ path=${copy}/scripts/mosaic ; argv[]=${copy}/scripts/mosaic bus start mosaic-stack --pm ; }';; *) echo 'LoadState=loaded'; echo 'WorkingDirectory=${copy}'; echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';; esac\n`); assert.equal((await s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin])).status,0); s.fake(`echo 'LoadState=loaded'; echo 'WorkingDirectory=${copy}';case \"$*\" in *mosaic-bus*)echo 'ExecStart={ path=/other/scripts/mosaic ; argv[]=/other/scripts/mosaic bus start demo ; }';; *)echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';;esac\n`); assert.equal((await s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin])).status,1,'effective ExecStart mismatch'); s.fake(`echo 'LoadState=loaded'; echo 'WorkingDirectory=/other';\n`);assert.equal((await s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin])).status,1); const foreign=`#!/usr/bin/env bash\n# foreign wrapper\n${lease}\nexec \"${copy}/scripts/mosaic\" \"$@\"\n`;writeFileSync(wrapper,foreign); // Restore matching effective roots: only the foreign marker refuses check. s.fake(`echo 'LoadState=loaded';echo 'WorkingDirectory=${copy}';case "$*" in *mosaic-bus*) echo 'ExecStart={ path=${copy}/scripts/mosaic ; argv[]=${copy}/scripts/mosaic bus start demo ; }';; *)echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';;esac\n`); assert.equal((await s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin])).status,1); assert.notEqual((await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin])).status,0);assert.equal(readFileSync(wrapper,'utf8'),foreign); const backing=join(s.bin,'backing');writeFileSync(backing,`#!/usr/bin/env bash\n${marker}\n${lease}\nexec \"/old/scripts/mosaic\" \"$@\"\n`);unlinkSync(wrapper);symlinkSync(backing,wrapper);assert.notEqual((await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin])).status,0); }); test('row 190 N6: check --instance passes only when the wrapper exports the instance\'s MOSAIC_CONFIG',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); const boot=(await s.run('bootstrap.sh',['--instance','alpha']));assert.equal(boot.status,0,boot.stderr); const copy=join(s.copies,s.id),wrapper=join(s.bin,'mosaic-alpha'),cfg=join(s.root,'.config','mosaic-alpha','config.json'); const w=(await s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin]));assert.equal(w.status,0,w.stderr); s.fake(`echo 'LoadState=loaded';echo 'WorkingDirectory=${copy}';case "$*" in *mosaic-bus-alpha@demo*) echo 'ExecStart={ path=${copy}/scripts/mosaic ; argv[]=${copy}/scripts/mosaic bus start demo ; }';; *mosaic-webui-alpha*) echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';; *) exit 1;; esac\n`); const check=async()=>(await s.run('stack-copy.sh',['check','--instance','alpha','--copies',s.copies,'--bin',s.bin,'--business','demo'])); const good=readFileSync(wrapper,'utf8');assert.equal(good.split('\n')[3],`export MOSAIC_CONFIG='${cfg}'`); let r=await check();assert.equal(r.status,0,r.stdout+r.stderr);assert.equal(r.stdout,`bus: ${copy}\nConsole: ${copy}\nCLI: ${copy}\n`); for(const [label,text] of [ ['another config',good.replace(`export MOSAIC_CONFIG='${cfg}'`,`export MOSAIC_CONFIG='${join(s.root,'.config','mosaic-beta','config.json')}'`)], ['no config line',good.replace(`export MOSAIC_CONFIG='${cfg}'\n`,'')], ]){ writeFileSync(wrapper,text);r=await check(); assert.equal(r.status,1,label);assert.equal(r.stdout,`bus: ${copy}\nConsole: ${copy}\nCLI: ${copy} (MOSAIC_CONFIG mismatch)\n`,label); } }); test('bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); const args=['--copy',s.id,'--copies',s.copies,'--pm']; const render=(await s.run('bus-service.sh',['render',...args]));assert.equal(render.status,0,render.stderr);assert.match(render.stdout,new RegExp(`WorkingDirectory=${s.copies}/${s.id}`));assert.match(render.stdout,/bus start %i --pm/); const units=join(s.root,'units'); for(const state of ['active','activating','deactivating','reloading']){s.fake(`echo 'mosaic-bus@demo.service loaded ${state} running fixture';\n`);const r=(await s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']));assert.notEqual(r.status,0,state);assert.ok(!existsSync(units));} s.fake('exit 1\n');assert.notEqual((await s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload'])).status,0);assert.ok(!existsSync(units)); const dropdir=join(s.search,'mosaic-bus@.service.d');mkdirSync(dropdir);const drop=join(dropdir,'pm.conf');writeFileSync(drop,'[Service]\nExecStart=elsewhere\n'); s.fake(`case "$*" in *list-units*) echo 'mosaic-bus@demo.service loaded inactive dead fixture';; *)exit 99;; esac\n`); const refused=(await s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']));assert.notEqual(refused.status,0);assert.ok(refused.stderr.includes(drop));assert.ok(!existsSync(units)); writeFileSync(drop,'[Service]\nEnvironment=SAFE=1\n');assert.equal((await s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload'])).status,0); }); test('missing package manifest refuses before npm; npm failure removes the unpublished temp copy', async t=>{ const s=setup(t);git(s.repo,'rm','package.json');git(s.repo,'commit','-qm','missing package');const id=git(s.repo,'rev-parse','HEAD');git(s.repo,'update-ref','refs/remotes/origin/refactor',id); const npm=join(s.bin,'npm'),called=join(s.root,'npm-called');writeFileSync(npm,`#!/bin/bash\necho called > '${called}'\nexit 1\n`);chmodSync(npm,0o755); const r=(await s.run('stack-copy.sh',['copy',id,'--copies',s.copies]));assert.notEqual(r.status,0);assert.match(r.stderr,/package.json/);assert.ok(!existsSync(called));assert.ok(!existsSync(join(s.copies,id))); git(s.repo,'reset','--hard',s.id);git(s.repo,'update-ref','refs/remotes/origin/refactor',s.id); const tar=join(s.bin,'tar');writeFileSync(tar,'#!/bin/sh\n/usr/bin/tar \"$@\" || exit $?\nfor a; do dest=\"$a\"; done\nrm \"$dest/package.json\"\n');chmodSync(tar,0o755); const absent=(await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]));assert.notEqual(absent.status,0);assert.match(absent.stderr,/package.json.*missing/);assert.ok(!existsSync(called));unlinkSync(tar); const fail=(await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]));assert.notEqual(fail.status,0);assert.match(fail.stderr,/npm ci failed/);assert.ok(existsSync(called));assert.deepEqual(execFileSync('ls',['-A',s.copies],{encoding:'utf8'}),''); }); async function until(fn,ms=10000){const end=Date.now()+ms;while(Date.now()setTimeout(r,25));}throw Error('fixture startup timed out');} async function childResult(command,args,env,input){ const p=spawn(command,args,{env,stdio:['pipe','pipe','pipe']});let stdout='',stderr=''; p.stdout.on('data',c=>stdout+=c);p.stderr.on('data',c=>stderr+=c);p.stdin.end(input); const timer=setTimeout(()=>p.kill('SIGKILL'),15000); try{const [status]=await once(p,'close');return {status,stdout,stderr};}finally{clearTimeout(timer)} } test('real pinned install loads bus and Console; the Console starts only with --sso and its deployed prerequisites; same-copy mosaic talk passes proof, other trees refuse', {timeout:180000},async t=>{ const s=setup(t,true); const copied=(await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]));assert.equal(copied.status,0,copied.stderr); const root=join(s.copies,s.id),url=f=>pathToFileURL(join(root,f)).href; const {checkEnginePin}=await import(url('packages/conversation/src/pi-pin.mjs'));assert.equal(checkEnginePin(root).version,'0.85.1'); const home=join(s.root,'home'),f=fixture(home),configDir=join(home,'.config','mosaic-pin'); mkdirSync(join(home,'.config'),{recursive:true});renameSync(join(home,'config'),configDir);chmodSync(configDir,0o700); writeFileSync(join(f.dataRoot,'instance.json'),JSON.stringify({instanceVersion:1,name:'pin'})); const ctx={configDir,dataRoot:f.dataRoot,instance:'pin'}; const acme=await reserveOrg(ctx,'acme');await activateOrg(ctx,'acme');notifyConfig(acme.root,'acme',null); const demo=await reserveOrg(ctx,'demo');await activateOrg(ctx,'demo'); // Only synthetic configuration, no inherited credentials/agent environment. const env={...s.env,HOME:home,MOSAIC_CONFIG:join(configDir,'config.json'),MOSAIC_ROLES_DIR:f.roles}; const host=spawn(join(root,'scripts/mosaic'),['bus','start','acme'],{env,stdio:['ignore','pipe','pipe']});let hostOut='',hostErr=''; host.stdout.on('data',c=>hostOut+=c);host.stderr.on('data',c=>hostErr+=c); try{await until(()=>hostOut.includes('\"org\":\"acme\"')||host.exitCode!==null);assert.match(hostOut,/\"org\":\"acme\"/,hostErr);} finally{if(host.exitCode===null&&host.signalCode===null){const done=once(host,'close');const stop=spawnSync(join(root,'scripts/mosaic'),['bus','stop','acme'],{env,encoding:'utf8'});assert.equal(stop.status,0,stop.stderr);await done;}} // The copy is stamped (M3): its Console starts only with --sso, console.sso and its slot, .deployed and accounts.json. const serveArgs=sso=>[join(root,'packages/webui/src/cli.mjs'),'serve','--port','0','--business','acme','--board','http://127.0.0.1:9',...(sso?['--sso']:[])]; const op=await fakeOidc();t.after(()=>op.close());const iss=op.issuer('a'); const ca=join(s.root,'ca.pem');writeFileSync(ca,op.cert);const webEnv={...env,NODE_EXTRA_CA_CERTS:ca}; const startRefused=async(sso,why)=>{const r=await childResult(process.execPath,serveArgs(sso),webEnv,'');assert.equal(r.status,2,r.stderr);assert.match(r.stderr,why);assert.doesNotMatch(r.stdout,/Mosaic Console:/);}; await startRefused(false,/stamped build serve requires --sso/); await startRefused(true,/\.deployed is missing or invalid/); writeDeployment({dataRoot:f.dataRoot},configDir); await startRefused(true,/accounts file not found/); writeFileSync(join(configDir,'accounts.json'),JSON.stringify({accountsVersion:1,revision:1,users:[{id:'u-000000000001',subjects:[{provider:'a',sub:'owner'}],name:'Owner',instanceRole:'operator',state:'active',authGen:1,created:'2026-11-10T00:00:00Z'}],memberships:[],invites:[]}),{mode:0o600}); await startRefused(true,/must be a configured provider/); const config=JSON.parse(readFileSync(env.MOSAIC_CONFIG,'utf8')); config.console={sso:{publicOrigin:'https://console.test',providers:[{id:'a',issuer:iss.issuer,clientId:iss.clientId,clientSecret:{slot:'a'},algs:['RS256'],subjects:[{sub:'owner',name:'Owner'}]}]}}; writeFileSync(env.MOSAIC_CONFIG,JSON.stringify(config)); await startRefused(true,/client secret slot is missing/); mkdirSync(join(configDir,'secrets','mosaic-stack'),{recursive:true,mode:0o700}); const slot=join(configDir,'secrets','mosaic-stack','a');writeFileSync(slot,iss.clientSecret,{mode:0o600});chmodSync(slot,0o600); const web=spawn(process.execPath,serveArgs(true),{env:webEnv,stdio:['ignore','pipe','pipe']});let webOut='',webErr=''; web.stdout.on('data',c=>webOut+=c);web.stderr.on('data',c=>webErr+=c); try{await until(()=>/Mosaic Console: http:/.test(webOut)||web.exitCode!==null);const port=webOut.match(/127\.0\.0\.1:(\d+)/)?.[1];assert.ok(port,webErr);assert.equal((await fetch(`http://127.0.0.1:${port}/healthz`)).status,200);} finally{if(web.exitCode===null&&web.signalCode===null){const done=once(web,'close');web.kill();await done;}} // The production runtime derives cliPath. Only agent ancestry is mocked; // readProcess passes through for tester.mjs (row 110). // real pid/start/argv/nonce and the runtime's entry-path binding remain intact. const proofScript=join(s.root,'proof-runtime.mjs'); writeFileSync(proofScript,` import {mock} from 'node:test'; const humanURL=${JSON.stringify(url('packages/bus/src/human.mjs'))}; const real=await import(humanURL); mock.module(humanURL,{namedExports:{readProcess:real.readProcess,verifyHuman:(proof,options)=>real.verifyHuman(proof,{...options,readProcess:pid=>({...real.readProcess(pid),ppid:1})})}}); const {startBroker}=await import(${JSON.stringify(url('packages/bus/src/runtime.mjs'))}); const runtime=await startBroker({dataRoot:${JSON.stringify(demo.root)},businesses:${JSON.stringify(BUSINESSES)}}); const broker=runtime.broker; const pm=broker.bindLaunch({business:'demo',role:'pm',run:'pm-fixture',harness:'pi'});broker.request(pm,{verb:'role.claim'}); const request=broker.request.bind(broker);broker.request=(cap,r)=>{const result=request(cap,r);if(r.verb==='message.send'&&r.args.to==='pm')request(pm,{verb:'message.send',args:{to:'human',body:'same-copy reply',in_reply_to:result.id}});return result;}; process.on('SIGTERM',async()=>{await runtime.close();process.exit(0)}); console.log('proof runtime ready'); `); const proof=spawn(process.execPath,['--experimental-test-module-mocks',proofScript],{env,stdio:['ignore','pipe','pipe']});let proofOut='',proofErr=''; proof.stdout.on('data',c=>proofOut+=c);proof.stderr.on('data',c=>proofErr+=c); const socket=join(demo.root,'bus','broker.sock'); try{ await until(()=>proofOut.includes('proof runtime ready')||proof.exitCode!==null);assert.match(proofOut,/proof runtime ready/,proofErr); const result=await childResult(join(root,'scripts/mosaic'),['talk','pm','hello','--business','demo','--wait','1'],env); assert.equal(result.status,0,result.stderr);assert.match(result.stdout,/sent .* to pm/);assert.match(result.stdout,/same-copy reply/); for(const other of [ROOT,join(s.root,'other-copy')]){ if(other!==ROOT)cpSync(join(root,'packages'),join(other,'packages'),{recursive:true}); const result=await childResult(process.execPath,[join(other,'packages/bus/src/human-cli.mjs'),socket],env,JSON.stringify({business:'demo',verb:'inbox'})+'\n'); assert.notEqual(result.status,0);assert.equal(result.stderr.trim(),'human-required'); } }finally{if(proof.exitCode===null&&proof.signalCode===null){const done=once(proof,'close');proof.kill('SIGTERM');await done;}} }); test('bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); const copy=join(s.copies,s.id),drop=join(s.root,'pm.conf'); const rows=`LoadState=loaded\nWorkingDirectory=${copy}\nExecStart={ path=${copy}/scripts/mosaic ; argv[]=${copy}/scripts/mosaic bus start demo --pm ; }\nDropInPaths=${drop}\n`; s.fake(`case "$*" in *show*) cat <<'ROWS'\n${rows}ROWS\n;; *is-active*) echo inactive;; *is-enabled*) echo disabled;; *)exit 99;;esac\n`); const status=(await s.run('bus-service.sh',['status','demo']));assert.equal(status.status,0,status.stderr);assert.match(status.stdout,new RegExp(`copy: ${s.id}`));assert.match(status.stdout,/pm: on/);assert.ok(status.stdout.includes(drop)); }); test('drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload',async t=>{ const s=setup(t),units=join(s.root,'units'),dropdir=join(s.search,'mosaic-bus@demo.service.d');mkdirSync(dropdir);const drop=join(dropdir,'override.conf'); for(const field of ['ExecStart','WorkingDirectory']){ writeFileSync(drop,`[Service]\n${field}=override\n`); s.fake(`case "$*" in *list-units*) echo 'mosaic-bus@demo.service loaded inactive dead fixture';; *)exit 99;;esac\n`); const r=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.notEqual(r.status,0);assert.ok(r.stderr.includes(drop));assert.ok(!existsSync(units)); } s.fake('exit 0\n');s.analyze('exit 0\n');const unknown=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.notEqual(unknown.status,0);assert.match(unknown.stderr,/unknown/); mkdirSync(units);const file=join(units,'mosaic-bus@.service');writeFileSync(file,'unchanged'); s.fake("echo 'mosaic-bus@demo.service loaded active running fixture'\n");assert.notEqual((await s.run('bus-service.sh',['uninstall','--dir',units,'--no-reload'])).status,0);assert.equal(readFileSync(file,'utf8'),'unchanged'); }); test('install never shows a template name on systemd 261; template drop-ins still refuse',async t=>{ const s=setup(t),units=join(s.root,'units'),calls=join(s.root,'systemctl-calls'); s.fake(`echo "$*" >> '${calls}'\ncase "$*" in *show*mosaic-bus@.service*) echo 'neither a valid invocation ID nor unit name' >&2; exit 1;; *list-units*) echo 'mosaic-bus@demo.service loaded inactive dead fixture';; *)exit 99;; esac\n`); const result=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.equal(result.status,0,result.stderr); assert.doesNotMatch(readFileSync(calls,'utf8'),/show mosaic-bus@\.service/); // This rejection arm is deliberately exercised; it is not a dead show reply. assert.equal(spawnSync(join(s.bin,'systemctl'),['--user','show','mosaic-bus@.service'],{env:s.env}).status,1); const dropdir=join(s.search,'mosaic-bus@.service.d');mkdirSync(dropdir);const file=join(dropdir,'pm.conf');writeFileSync(file,'[Service]\nExecStart=override\n'); const before=readFileSync(join(units,'mosaic-bus@.service'),'utf8'); const denied=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.notEqual(denied.status,0);assert.ok(denied.stderr.includes(file));assert.equal(readFileSync(join(units,'mosaic-bus@.service'),'utf8'),before); }); test('unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse',async t=>{ const s=setup(t),units=join(s.root,'units'),second=join(s.root,'second-search');mkdirSync(second); const dropdir=join(second,'mosaic-bus@unlisted.service.d');mkdirSync(dropdir);const file=join(dropdir,'cwd.conf');writeFileSync(file,'[Service]\nWorkingDirectory=/other\n'); s.analyze(`printf '%s\\n' '${s.search}' '${second}'\n`); s.fake("case \"$*\" in *list-units*) echo 'mosaic-bus@demo.service loaded inactive dead fixture';; *)exit 99;; esac\n"); const denied=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.notEqual(denied.status,0);assert.ok(denied.stderr.includes(file));assert.ok(!existsSync(units)); s.analyze('exit 1\n');const failed=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.notEqual(failed.status,0);assert.match(failed.stderr,/unit-paths failed/);assert.ok(!existsSync(units)); }); // Row 108: inherited drop-ins apply even without a loaded bus instance. for(const name of ['mosaic-.service.d','service.d'])test(`inherited ${name} override refuses without changing the unit`,async t=>{ const s=setup(t),units=join(s.root,'units');mkdirSync(units); const target=join(units,'mosaic-bus@.service');writeFileSync(target,'previous unit bytes'); s.fake('exit 0\n'); const dropdir=join(s.search,name);mkdirSync(dropdir);const file=join(dropdir,'override.conf'); for(const field of ['WorkingDirectory','ExecStart']){ writeFileSync(file,`[Service]\n${field}=/other\n`); const denied=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload'])); assert.equal(denied.status,1,denied.stderr);assert.ok(denied.stderr.includes(file));assert.equal(readFileSync(target,'utf8'),'previous unit bytes'); } writeFileSync(file,'[Service]\nEnvironment=SAFE=1\n'); const allowed=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.equal(allowed.status,0,allowed.stderr); }); test('inherited drop-in unreadable directory or file refuses before writing',async t=>{ const s=setup(t),units=join(s.root,'units');mkdirSync(units);const target=join(units,'mosaic-bus@.service');writeFileSync(target,'previous unit bytes');s.fake('exit 0\n'); const dropdir=join(s.search,'service.d');symlinkSync(join(s.root,'absent-dir'),dropdir); const dir=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.equal(dir.status,1);assert.ok(dir.stderr.includes(dropdir));assert.equal(readFileSync(target,'utf8'),'previous unit bytes'); unlinkSync(dropdir);mkdirSync(dropdir);const file=join(dropdir,'broken.conf');symlinkSync(join(s.root,'absent-file'),file); const broken=(await s.run('bus-service.sh',['install','--dir',units,'--no-reload']));assert.equal(broken.status,1);assert.ok(broken.stderr.includes(file));assert.equal(readFileSync(target,'utf8'),'previous unit bytes'); }); // Row 232: every file of a copy with its mode and bytes, for the byte-identical comparison. function tree(dir){ const out=[]; const walk=rel=>{for(const e of readdirSync(join(dir,rel),{withFileTypes:true}).sort((a,b)=>a.name{ const s=setup(t),tmp=join(s.root,'t');mkdirSync(tmp);s.env.TMPDIR=tmp; const good=join(s.root,'good.bundle');git(s.repo,'bundle','create','-q',good,'refactor'); const a=join(s.root,'copies-a'),b=join(s.root,'copies-b'); let r=(await s.run('stack-copy.sh',['copy',s.id,'--copies',a]));assert.equal(r.status,0,r.stderr); r=(await s.run('stack-copy.sh',['copy','--bundle',good,s.id,'--copies',b]));assert.equal(r.status,0,r.stderr); const sum=createHash('sha256').update(readFileSync(good)).digest('hex'),release=git(s.repo,'show',`${s.id}:RELEASE`); assert.equal(r.stdout,`copied: ${join(b,s.id)}\nrelease: ${release}\nbundle sha256: ${sum}\n`); assert.deepEqual(tree(join(b,s.id)),tree(join(a,s.id))); assert.equal(statSync(join(b,s.id)).mode&0o777,0o555); const left=()=>readdirSync(tmp).filter(f=>f.startsWith('tmp.')); assert.deepEqual(left(),[],'the bundle\'s temporary repository is removed'); // A second run finds the copy and still names the release. r=(await s.run('stack-copy.sh',['copy','--bundle',good,s.id,'--copies',b]));assert.equal(r.status,0,r.stderr); assert.equal(r.stdout,`exists: ${join(b,s.id)}\nrelease: ${release}\nbundle sha256: ${sum}\n`); // A relative bundle path works from the caller's directory. const c=join(s.root,'copies-c'); r=await s.runChild(join(s.repo,'scripts','stack-copy.sh'),['copy','--bundle','good.bundle',s.id,'--copies',c],{cwd:s.root}); assert.equal(r.status,0,r.stderr);assert.deepEqual(tree(join(c,s.id)),tree(join(a,s.id))); // A commit on another branch of the bundle: the bundle carries it, refactor does not reach it. const side=git(s.repo,'commit-tree','-p',s.id,'-m','side',`${s.id}^{tree}`);git(s.repo,'update-ref','refs/heads/side',side); const both=join(s.root,'both.bundle');git(s.repo,'bundle','create','-q',both,'refactor','side'); const only=join(s.root,'side.bundle');git(s.repo,'bundle','create','-q',only,'side'); const corrupt=join(s.root,'corrupt.bundle'),bytes=readFileSync(good);bytes[bytes.length-30]^=0xff;writeFileSync(corrupt,bytes); const cut=join(s.root,'cut.bundle');writeFileSync(cut,readFileSync(good).subarray(0,200)); const junk=join(s.root,'junk.bundle');writeFileSync(junk,'# v2 git bundle\nnot a ref line\n\n'); const d=join(s.root,'copies-d'); for(const [label,bundle,id,re] of [ ['not reachable',both,side,/not reachable from the bundle's refs\/heads\/refactor/], ['no refactor',only,side,/no refs\/heads\/refactor/], // verify reads the header and prerequisites; a damaged pack fails the fetch's own check. ['not a bundle',junk,s.id,/git bundle verify failed/], ['corrupt pack',corrupt,s.id,/cannot fetch/], ['truncated pack',cut,s.id,/cannot fetch/], ['absent',join(s.root,'none.bundle'),s.id,/bundle is absent/], ['short id',good,s.id.slice(0,12),/full 40-character commit id/], ]){ r=(await s.run('stack-copy.sh',['copy','--bundle',bundle,id,'--copies',d])); assert.equal(r.status,1,label);assert.match(r.stderr,re,label);assert.equal(r.stdout,'',label); assert.ok(!existsSync(join(d,id)),label);assert.deepEqual(left(),[],label); if(existsSync(d))assert.deepEqual(readdirSync(d),[],label); } const link=join(s.root,'link.bundle');symlinkSync(good,link); r=(await s.run('stack-copy.sh',['copy','--bundle',link,s.id,'--copies',d]));assert.equal(r.status,1);assert.match(r.stderr,/symlink/); for(const args of [['copy','--bundle',good,s.id,'--allow-worktree'],['copy','--bundle',good],['copy','--bundle'],['wrapper','--bundle',good,s.id],['check','--units','system'],['copy',s.id,'--units','external'],['copy',s.id,'--create']]) assert.equal((await s.run('stack-copy.sh',args)).status,4,args.join(' ')); }); test('row 232: wrapper --create writes the first wrapper only, the same bytes wrapper ID writes, and refuses over any file',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); const wrapper=join(s.bin,'mosaic'),root=join(s.copies,s.id); let r=(await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin])); assert.equal(r.status,1);assert.match(r.stderr,/wrapper is absent/,'no --create keeps the missing-wrapper refusal');assert.ok(!existsSync(wrapper)); r=(await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin,'--create']));assert.equal(r.status,0,r.stderr); assert.equal(r.stdout,`wrapper: ${root}\n`); const created=readFileSync(wrapper,'utf8');assert.equal(statSync(wrapper).mode&0o777,0o755); r=(await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]));assert.equal(r.status,0,r.stderr); assert.equal(r.stdout,`wrapper: ${root}\n`);assert.equal(readFileSync(wrapper,'utf8'),created,'wrapper ID writes the same bytes'); const foreign='#!/bin/sh\n# not ours\n'; for(const [label,make] of [['own wrapper',()=>{}],['foreign file',()=>writeFileSync(wrapper,foreign)],['symlink',()=>{rmSync(wrapper);symlinkSync(join(s.root,'elsewhere'),wrapper);}]]){ make();const before=existsSync(wrapper)||label==='symlink'?(label==='symlink'?readlinkSync(wrapper):readFileSync(wrapper,'utf8')):null; r=(await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin,'--create'])); assert.equal(r.status,1,label);assert.match(r.stderr,/--create and a wrapper exists/,label); assert.equal(label==='symlink'?readlinkSync(wrapper):readFileSync(wrapper,'utf8'),before,label); assert.deepEqual(readdirSync(s.bin).filter(f=>f.startsWith('.')),[],`${label}: no temporary file left`); } // A named instance: --create writes mosaic-NAME as wrapper --instance does, once. assert.equal((await s.run('bootstrap.sh',['--instance','alpha'])).status,0); const named=join(s.bin,'mosaic-alpha'); r=(await s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin,'--create']));assert.equal(r.status,0,r.stderr); const first=readFileSync(named,'utf8'); r=(await s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin]));assert.equal(r.status,0,r.stderr); assert.equal(readFileSync(named,'utf8'),first); r=(await s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin,'--create'])); assert.equal(r.status,1);assert.match(r.stderr,/--create and a wrapper exists/);assert.equal(readFileSync(named,'utf8'),first); }); test('row 232: check --units external reads the system units, the default reads the user units',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); assert.equal((await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin,'--create'])).status,0); const copy=join(s.copies,s.id),argv=join(s.root,'argv.log'); s.fake(`echo "$*" >> '${argv}'; echo 'LoadState=loaded'; echo 'WorkingDirectory=${copy}'; case "$*" in *mosaic-bus*) echo 'ExecStart={ path=${copy}/scripts/mosaic ; argv[]=${copy}/scripts/mosaic bus start mosaic-stack --pm ; }';; *) echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';; esac\n`); const want=`bus: ${copy}\nConsole: ${copy}\nCLI: ${copy}\n`; let r=(await s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin,'--units','external']));assert.equal(r.status,0,r.stderr);assert.equal(r.stdout,want); assert.deepEqual(readFileSync(argv,'utf8').trimEnd().split('\n'),[ 'show mosaic-bus@mosaic-stack.service -p LoadState -p WorkingDirectory -p ExecStart', 'show mosaic-webui.service -p LoadState -p WorkingDirectory -p ExecStart']); rmSync(argv); for(const args of [[],['--units','user']]){ r=(await s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin,...args]));assert.equal(r.status,0,r.stderr);assert.equal(r.stdout,want); assert.deepEqual(readFileSync(argv,'utf8').trimEnd().split('\n'),[ '--user show mosaic-bus@mosaic-stack.service -p LoadState -p WorkingDirectory -p ExecStart', '--user show mosaic-webui.service -p LoadState -p WorkingDirectory -p ExecStart']); rmSync(argv); } }); // Rename r2 "Selector scopes and parser matrix": check takes --org as --business, each spelling once, both only // with one value; a refused selector reads no unit, and --org is refused outside check as --business is. test('rename r2: check takes --org as --business, once per spelling, and refuses before any unit read',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); assert.equal((await s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin,'--create'])).status,0); const copy=join(s.copies,s.id),argv=join(s.root,'argv.log'),base=['check','--copies',s.copies,'--bin',s.bin]; s.fake(`echo "$*" >> '${argv}'; echo 'LoadState=loaded'; echo 'WorkingDirectory=${copy}'; case "$*" in *mosaic-bus*) echo 'ExecStart={ path=${copy}/scripts/mosaic ; argv[]=${copy}/scripts/mosaic bus start demo ; }';; *) echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';; esac\n`); const shows=b=>[`--user show mosaic-bus@${b}.service -p LoadState -p WorkingDirectory -p ExecStart`,'--user show mosaic-webui.service -p LoadState -p WorkingDirectory -p ExecStart']; for(const sel of [['--business','demo'],['--org','demo'],['--business','demo','--org','demo'],['--org','demo','--business','demo'],['--org','demo','--units','user']]){ const r=await s.run('stack-copy.sh',[...base,...sel]); assert.equal(r.status,0,sel.join(' ')+r.stderr);assert.equal(r.stdout,`bus: ${copy}\nConsole: ${copy}\nCLI: ${copy}\n`,sel.join(' ')); assert.deepEqual(readFileSync(argv,'utf8').trimEnd().split('\n'),shows('demo'),sel.join(' '));rmSync(argv); } // The selector may come before the other options too. let r=await s.run('stack-copy.sh',['check','--org','demo','--copies',s.copies,'--bin',s.bin]);assert.equal(r.status,0,r.stderr); assert.deepEqual(readFileSync(argv,'utf8').trimEnd().split('\n'),shows('demo'));rmSync(argv); for(const [sel,status,message] of [ [['--business','demo','--org','beta'],4,'--business demo and --org beta conflict'], [['--org','beta','--business','demo'],4,'--org beta and --business demo conflict'], [['--business','demo','--business','demo'],4,'--business given twice'], [['--org','demo','--org','demo'],4,'--org given twice'], [['--org','demo','--business','demo','--org','demo'],4,'--org given twice'], [['--org'],4,'--org needs one value'], [['--business'],4,'--business needs one value'], [['--org',''],4,'--org needs one value'], [['--org','--units','user'],4,'--org needs one value, not --units'], [['--business','-x'],4,'--business needs one value, not -x'], [['--org','Demo'],1,'--org Demo is not valid'], [['--business','demo_x'],1,'--business demo_x is not valid'], [['--org','-demo'],4,'--org needs one value, not -demo'], ]){ r=await s.run('stack-copy.sh',[...base,...sel]); assert.equal(r.status,status,sel.join(' '));assert.equal(r.stderr,`stack-copy: ${message}\n`,sel.join(' '));assert.equal(r.stdout,'',sel.join(' ')); assert.ok(!existsSync(argv),`${sel.join(' ')} reads no unit`); } // Positional text after a selector value stays a usage error; --org outside check is one too. for(const args of [[...base,'--org','demo','beta'],['copy',s.id,'--copies',s.copies,'--org','demo'],['wrapper',s.id,'--copies',s.copies,'--bin',s.bin,'--org','demo'],['copy',s.id,'--business','demo']]){ r=await s.run('stack-copy.sh',args);assert.equal(r.status,4,args.join(' '));assert.match(r.stderr,/^usage: stack-copy\.sh /,args.join(' ')); assert.ok(!existsSync(argv),args.join(' ')); } }); test('rename r2: check --instance takes --org for its business and still needs one selector',async t=>{ const s=setup(t);assert.equal((await s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies])).status,0); assert.equal((await s.run('bootstrap.sh',['--instance','alpha'])).status,0); assert.equal((await s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin])).status,0); const copy=join(s.copies,s.id),argv=join(s.root,'argv.log'),base=['check','--instance','alpha','--copies',s.copies,'--bin',s.bin]; s.fake(`echo "$*" >> '${argv}'; echo 'LoadState=loaded';echo 'WorkingDirectory=${copy}';case "$*" in *mosaic-bus-alpha@demo*) echo 'ExecStart={ path=${copy}/scripts/mosaic ; argv[]=${copy}/scripts/mosaic bus start demo ; }';; *mosaic-webui-alpha*) echo 'ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node ${copy}/packages/webui/src/cli.mjs serve ; }';; *) exit 1;; esac\n`); for(const sel of [['--org','demo'],['--business','demo','--org','demo']]){ const r=await s.run('stack-copy.sh',[...base,...sel]);assert.equal(r.status,0,sel.join(' ')+r.stderr); assert.equal(r.stdout,`bus: ${copy}\nConsole: ${copy}\nCLI: ${copy}\n`,sel.join(' ')); assert.equal(readFileSync(argv,'utf8').split('\n')[0],'--user show mosaic-bus-alpha@demo.service -p LoadState -p WorkingDirectory -p ExecStart',sel.join(' '));rmSync(argv); } const r=await s.run('stack-copy.sh',base);assert.equal(r.status,4);assert.match(r.stderr,/^usage: stack-copy\.sh /);assert.ok(!existsSync(argv)); }); test('Vikunja 614: bundle and checkout copy leave an unreadable caller directory before find',async t=>{ const s=setup(t),cwd=join(s.root,'unreadable');mkdirSync(cwd); const bundle=join(s.root,'release.bundle');git(s.repo,'bundle','create','-q',bundle,'refactor'); // Enter while searchable, then reproduce a cwd inherited across a uid drop: // the caller can neither read nor search it. Absolute bundle lookup must work. const enter='cd "$1" && chmod 000 . && [ ! -r . ] && [ ! -x . ] && shift && exec "$@"'; for(const [name,args] of [['bundle',['copy','--bundle',bundle,s.id]],['checkout',['copy',s.id]]]){ const copies=join(s.root,`copies-${name}`); try{ const r=await s.runChild('/bin/sh',['-c',enter,'stack-copy-cwd',cwd,join(s.repo,'scripts','stack-copy.sh'),...args,'--copies',copies]); assert.equal(statSync(cwd).mode&0o555,0,'the inherited cwd is neither readable nor searchable'); assert.equal(r.status,0,r.stdout+r.stderr); assert.doesNotMatch(r.stderr,/Failed to (restore initial working|change) directory|Permission denied/); assert.ok(existsSync(join(copies,s.id,'scripts','mosaic'))); assert.equal(statSync(join(copies,s.id)).mode&0o777,0o555); }finally{ // Restore even after a failed assertion/mutant, before setup's recursive cleanup. chmodSync(cwd,0o700); } } }); //643: the owned npm child announces readiness and waits for this parent. A // synchronous runner cannot service that signal; no elapsed-time guess releases it. test('copy waits for owned child completion after its readiness signal', {timeout:30000}, async t=>{ const s=setup(t),path=join(s.root,'ready.sock'),sockets=new Set(); let ready; const announced=new Promise(resolve=>{ready=resolve}); const server=createServer(socket=>{sockets.add(socket);socket.on('error',()=>{});socket.once('close',()=>sockets.delete(socket));ready(socket)}); await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(path,resolve)}); t.after(async()=>{for(const socket of sockets)socket.destroy();await new Promise(resolve=>server.close(resolve))}); const npm=join(s.bin,'npm'); const program=`const s=require('node:net').connect(${JSON.stringify(path)});s.on('error',()=>process.exit(1));s.once('data',()=>s.end());`; writeFileSync(npm,`#!/bin/sh\nexec '${process.execPath}' -e '${program.replaceAll("'", "'\\''")}'\n`);chmodSync(npm,0o755); const copying=s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]); const socket=await Promise.race([announced,copying.then(r=>{throw Error(`copy exited before readiness: ${r.status}`)})]); assert.ok(!existsSync(join(s.copies,s.id)),'no copy is published while npm is held'); socket.write('continue'); const result=await copying; assert.equal(result.status,0,result.stderr); assert.equal(statSync(join(s.copies,s.id)).mode&0o777,0o555); });