From 0965fe1ea58b1836a4b2868019948cfd509c7b0c Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 25 Oct 2026 01:50:19 -0500 Subject: [PATCH] row331 r4: use approved scratch hireable aide fixture --- agents/magica/work/queue-331/BUILD.md | 41 +++++++++++++ agents/magica/work/queue-331/r4-gate.mjs | 73 ++++++++++++++++++++++++ packages/bus/tests/model-keys.test.mjs | 14 ++++- 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 agents/magica/work/queue-331/r4-gate.mjs diff --git a/agents/magica/work/queue-331/BUILD.md b/agents/magica/work/queue-331/BUILD.md index be3e8bba9..148e460a5 100644 --- a/agents/magica/work/queue-331/BUILD.md +++ b/agents/magica/work/queue-331/BUILD.md @@ -448,3 +448,44 @@ Digest correction: a026f563... identifies the frozen import graph (paths/edges), pins SHA256(committed blob bytes) for45 changed paths at e0578794c; its digest is e4e0353dbd88e829caf4ce3f050443bd55740e1b8af50ed90522962b81d77f37. Its sourceCommit anchor deliberately precedes the later receipt artifact, avoiding self-reference. + +## 2026-10-25 — r4 fixture approved, static path check before push (sage-4453) + +Copied the exact established aide fixture from chief-of-staff.test.mjs:1026-1033 +into bootBusiness under rolesCopy(f.root) only: aide.md, roleVersion2 aide.json, +read tool, network api-only, withinRole message.send, credentials[], copied chief +hireable:['aide']. One comment explains both shipped hire gates. Changed only the +three sec-new hire requests to definition aide. sec-1/security-reviewer stays the +fire target; every assertion and timeout is unchanged. Repository roles/*.json and +product source are untouched. No test run. + +Read-only path check completed before push: +- Broker.#chiefLifecycle checks the definition's eligibility, then invokes the same + instance.hire organization handler (broker:5100 onward). +- modelOrganization.wrap derives only business/agent identity from args.name and + unconditionally awaits modelKeys.ensure(instance) before invoking the writer + (model-keys:227-243); it does not choose a path by definition/harness/network/tools. +- ModelKeys.#issue:94-109 takes instance, rotation flag and optional current list; + it registers implicit model, GETs, PUTs, confirms GET and writes the model record. + It never reads a role definition or its declared credentials. +- Thus successful hire still must show GET/PUT/GET and valid sec-new model metadata; + failed-PUT hire still must refuse proxy-key-issue-failed before membership/file; + newly-hired preflight still calls runtime.modelPreflight for demo/sec-new. +- Runtime.modelPreflight:721-725 checks current role membership and calls the same + ModelKeys.preflight by instance; it is not the harness plug-in preflight. That + preflight uses the existing key, own sentinel and one /v1/models probe. +- Both security-reviewer and aide are credential-free, have network api-only and + no vars/harness/model overrides. They inherit the same fixture/system route. Aide + has fewer built-in tools and different authority solely to satisfy hire eligibility; + those fields are not inputs to this proxy issuance/preflight path. No real harness + is spawned in these three cases. No key-issuance assertion was weakened/skipped. + +R4 runner retains probe-first/full262/test-config/4mutants with whole-file controls, +measured driver/child priority and start/end uptime, with identical test flags and +timeouts. Its receipt clear field now takes MOSAIC_GATE_CLEAR supplied at invocation +so preparation placeholders do not recur. Await separate CLEAR after288 processing +pair and Poe330 diagnostic. Existing27 passing probe cases are not rerun now. + +Import-graph and262 path-list digests remain a026f563... and512d0c1c... respectively. +A new committed-blob candidate manifest will be generated for the r4 source tip; +it has its own content digest and sourceCommit anchor. diff --git a/agents/magica/work/queue-331/r4-gate.mjs b/agents/magica/work/queue-331/r4-gate.mjs new file mode 100644 index 000000000..f3bb5f3f5 --- /dev/null +++ b/agents/magica/work/queue-331/r4-gate.mjs @@ -0,0 +1,73 @@ +// sage-4453: whole-file r4 probe, then full gate; measured priority/load, unchanged test flags/timeouts. +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; +import vm from 'node:vm'; +import { createHash } from 'node:crypto'; +import { execFileSync, spawn } from 'node:child_process'; +const root=process.cwd(),home=path.join(root,'agents/magica/work/queue-331'); +os.setPriority(0,19); +const archive=execFileSync('mktemp',['-d','/mnt/storage/scratch/tmp/XXXXXX'],{encoding:'utf8'}).trim(); +const events=path.join(home,'r4-events.jsonl'),receipt=path.join(home,'r4-receipts.json'); +const candidate=execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(); +fs.writeFileSync(events,'',{flag:'wx'}); +const rows=[]; +const uptime=()=>execFileSync('uptime',[],{encoding:'utf8'}).trim(); +const logEvent=e=>fs.appendFileSync(events,JSON.stringify(e)+'\n'); +const hash=b=>createHash('sha256').update(b).digest('hex'); +async function step(name,command,args,timeout=0){ + const temp=execFileSync('mktemp',['-d','/mnt/storage/scratch/tmp/XXXXXX'],{encoding:'utf8'}).trim(); + fs.mkdirSync(path.join(temp,'home'));fs.mkdirSync(path.join(temp,'config')); + const env={...process.env,TMPDIR:temp,HOME:path.join(temp,'home'),MOSAIC_CONFIG:path.join(temp,'config/config.json')}; + for(const key of ['OPENAI_API_KEY','ANTHROPIC_API_KEY','ANTHROPIC_AUTH_TOKEN','CLAUDE_CODE_OAUTH_TOKEN','OPENROUTER_API_KEY','ZAI_API_KEY','MOSAIC_BUS_CAP'])delete env[key]; + os.setPriority(0,19); + const before={name,event:'start',at:new Date().toISOString(),uptime:uptime(),TMPDIR:temp,nice:os.getPriority(0)}; + logEvent(before); + const file=path.join(archive,String(rows.length+1).padStart(3,'0')+'-'+name.replaceAll('/','_')+'.log'); + const fd=fs.openSync(file,'wx',0o600); + const measuredDriverNice=os.getPriority(0);fs.writeSync(fd,'driverNice='+measuredDriverNice+'\n'); + let timer,timedOut=false; + const child=spawn(command,args,{cwd:root,env,stdio:['ignore',fd,fd]}); + let measuredChildNice=null; + if(child.pid){os.setPriority(child.pid,19);measuredChildNice=os.getPriority(child.pid);} + fs.writeSync(fd,'nice='+String(measuredChildNice)+'\n');fs.closeSync(fd); + if(timeout)timer=setTimeout(()=>{timedOut=true;child.kill('SIGTERM');},timeout); + const outcome=await new Promise(resolve=>{child.once('error',error=>resolve({rc:1,error:error.code}));child.once('close',(code,signal)=>resolve({rc:code??1,signal}));}); + clearTimeout(timer); + const endLoad=uptime(),bytes=fs.readFileSync(file),text=bytes.toString(); + const count=key=>Number(new RegExp('^# '+key+' (\\d+)$','m').exec(text)?.[1]??0); + const shell=/selftest: (\d+) passed, (\d+) failed/.exec(text); + const row={...before,measuredDriverNice,measuredChildNice,event:'end',endAt:new Date().toISOString(),endUptime:endLoad,...outcome,timedOut, + tests:shell?Number(shell[1])+Number(shell[2]):count('tests'),pass:shell?Number(shell[1]):count('pass'),fail:shell?Number(shell[2]):count('fail'),skipped:count('skipped'),cancelled:count('cancelled'), + failedNames:text.split('\n').filter(s=>s.startsWith('not ok ')).map(s=>s.replace(/^not ok \d+ - /,'')),log:{path:file,sha256:hash(bytes),bytes:bytes.length}}; + rows.push(row);logEvent(row);fs.writeFileSync(receipt,JSON.stringify({clear:process.env.MOSAIC_GATE_CLEAR??'not supplied',candidate,archive,rows},null,2)+'\n'); + process.stdout.write(`${name} rc=${row.rc} pass=${row.pass} fail=${row.fail} skip=${row.skipped} start=[${row.uptime}] end=[${endLoad}]\n`); + return row; +} +const testArgs=file=>['--test','--test-reporter=tap',path.join(root,file)]; +const sourceFile=path.join(root,'packages/bus/src/model-keys.mjs'),original=fs.readFileSync(sourceFile,'utf8'); +try{ + const probe=await step('probe-model-keys',process.execPath,testArgs('packages/bus/tests/model-keys.test.mjs')); + if(probe.rc!==0)throw Error('RED whole-file model-keys probe; full gate not started'); + const list=fs.readFileSync(path.join(home,'green-files.txt'),'utf8').trim().split('\n'); + if(list.length!==262||list.includes('packages/cli/tests/case1-accept.test.mjs'))throw Error('frozen list mismatch'); + for(const file of list){const row=await step(file,process.execPath,testArgs(file));if(row.rc!==0)throw Error('RED '+file);} + const shell=await step('scripts/test-config.sh','timeout',['-k','10','1800',path.join(root,'scripts/test-config.sh')]); + if(shell.rc!==0)throw Error('RED scripts/test-config.sh'); + const helper=fs.readFileSync(path.join(home,'mutants.mjs'),'utf8'); + const start=helper.indexOf('const cases = ')+14,end=helper.indexOf('\nos.setPriority',start); + const cases=vm.runInNewContext('('+helper.slice(start,end).trim().replace(/;$/,'')+')'); + for(const m of cases){ + if(original.split(m.from).length!==2)throw Error('mutation anchor not unique '+m.name); + let result; + try{fs.writeFileSync(sourceFile,original.replace(m.from,m.to));result=await step('mutant-'+m.name,process.execPath,testArgs('packages/bus/tests/model-keys.test.mjs'),180000);} + finally{fs.writeFileSync(sourceFile,original);} + const killed=result.rc!==0&&!result.timedOut&&result.failedNames.some(name=>name===m.failure); + logEvent({event:'mutant-result',name:m.name,killed,restoredSha256:hash(fs.readFileSync(sourceFile))}); + if(!killed)throw Error('mutant not killed by named behavior '+m.name); + const positive=await step('positive-'+m.name,process.execPath,testArgs('packages/bus/tests/model-keys.test.mjs'),180000); + if(positive.rc!==0)throw Error('RED positive control '+m.name); + } + logEvent({event:'complete',at:new Date().toISOString(),uptime:uptime()}); +}catch(error){logEvent({event:'stopped',reason:error.message,at:new Date().toISOString(),uptime:uptime()});process.stdout.write(error.message+'\n');process.exitCode=1;} +finally{if(fs.readFileSync(sourceFile,'utf8')!==original)fs.writeFileSync(sourceFile,original);} diff --git a/packages/bus/tests/model-keys.test.mjs b/packages/bus/tests/model-keys.test.mjs index 5ff9dca7e..fd2a6c5d5 100644 --- a/packages/bus/tests/model-keys.test.mjs +++ b/packages/bus/tests/model-keys.test.mjs @@ -174,6 +174,14 @@ test('proxy-stop-failed: old proxy key and retiring record survive a failed grac async function bootBusiness(f) { const rolesDir = rolesCopy(f.root), system = systemFor(f.root), doc = businessDoc(f.root, 'demo'); + // Aide is needed because shipped chief hireable:[] and security-reviewer has review.verdict outside HIRE_ALLOWED. + fs.writeFileSync(join(rolesDir, 'aide.md'), '# Aide\n'); + writeJson(join(rolesDir, 'aide.json'), { + roleVersion: 2, name: 'aide', title: 'Aide', contract: 'aide.md', tools: ['read'], network: 'api-only', + authority: { withinRole: ['message.send'], crossRole: [] }, credentials: [], + }); + const chiefFile = join(rolesDir, 'chief-of-staff.json'); + writeJson(chiefFile, { ...JSON.parse(fs.readFileSync(chiefFile, 'utf8')), hireable: ['aide'] }); doc.plugins = { ...doc.plugins, harness: ['pi'] }; doc.roles['sec-1'] = { definition: 'security-reviewer' }; doc.launch.instances.push('sec-1'); const file = writeJson(join(f.configDir, 'businesses', 'demo.json'), doc); @@ -222,7 +230,7 @@ test('proxy-revoke-revision-conflict: racing organization write keeps record, ne }); test('bus hire issues and confirms implicit model key before publishing new membership', async t => { const f = await fixture(t); const b = await bootBusiness(f); const calls = f.state.calls.length; - const result = await b.bus.broker.request(b.chief, { verb: 'instance.hire', args: { name: 'sec-new', definition: 'security-reviewer', reason: 'fixture hire' } }); + const result = await b.bus.broker.request(b.chief, { verb: 'instance.hire', args: { name: 'sec-new', definition: 'aide', reason: 'fixture hire' } }); assert.equal(typeof result.transaction, 'string'); assert.equal(JSON.parse(fs.readFileSync(b.file, 'utf8')).launch.instances.includes('sec-new'), true); assert.equal(b.bus.credentials.modelRecord('demo/sec-new')?.state, 'valid'); @@ -231,7 +239,7 @@ test('bus hire issues and confirms implicit model key before publishing new memb test('proxy-key-issue-failed: bus hire failure publishes neither membership nor model file', async t => { const f = await fixture(t); const b = await bootBusiness(f); f.state.failPut = true; const before = fs.readFileSync(b.file, 'utf8'); - await assert.rejects(b.bus.broker.request(b.chief, { verb: 'instance.hire', args: { name: 'sec-new', definition: 'security-reviewer', reason: 'fixture hire' } }), failCode('proxy-key-issue-failed')); + await assert.rejects(b.bus.broker.request(b.chief, { verb: 'instance.hire', args: { name: 'sec-new', definition: 'aide', reason: 'fixture hire' } }), failCode('proxy-key-issue-failed')); assert.equal(fs.readFileSync(b.file, 'utf8'), before); assert.equal(b.bus.credentials.modelRecord('demo/sec-new') === null, true); }); test('proxy-open: removing sentinel after boot refuses the next launch before models probe', async t => { @@ -241,7 +249,7 @@ test('proxy-open: removing sentinel after boot refuses the next launch before mo }); test('newly hired role can use runtime preflight without a broker restart', async t => { const f = await fixture(t); const b = await bootBusiness(f); - await b.bus.broker.request(b.chief, { verb: 'instance.hire', args: { name: 'sec-new', definition: 'security-reviewer', reason: 'fixture hire' } }); + await b.bus.broker.request(b.chief, { verb: 'instance.hire', args: { name: 'sec-new', definition: 'aide', reason: 'fixture hire' } }); const proxy = await b.bus.modelPreflight({ business: 'demo', role: 'sec-new' }); assert.equal(proxy.baseUrl, f.baseUrl); assert.equal(f.state.keys.includes(proxy.key), true); });