// Row 126 (#1620, REQ-LIMIT-2): the account a launch spends. Only a claude-code launch carries // launch.account.name; the bus environment must hold the named login set and no other credential // variable, and the refusals name variables, never values. A pi launch has account null and skips // both checks. A stub host answers the ops; no sandbox, Docker or session process. import test from 'node:test'; import assert from 'node:assert/strict'; import {readFileSync} from 'node:fs'; import {createLauncher} from '../src/launcher.mjs'; import {initializeControl} from '../../processing-control/src/state.mjs'; import {fixture,tmp,withoutHostCredentials} from './helpers.mjs'; import {launchLogFile,SESSION_CREDENTIALS} from '../../seat/src/session.mjs'; const VALUE='stand-in-value-7f3a'; const ACCOUNT={name:'jason-max',credential:'CLAUDE_CODE_OAUTH_TOKEN'}; // set is the credential variables the bus environment holds; every other one is removed. A pi // launch gets one made-up pi key, so its own preflight passes on any host. proxy goes to the launcher's // testProxy option, a static session proxy context (row 332, #774 F4); edit changes the rest of the file. function setup(t,{harness='claude-code',account=ACCOUNT,set=['CLAUDE_CODE_OAUTH_TOKEN'],proxy=null,edit=doc=>doc}={}){ const root=tmp(t),f=fixture(root,'acme',doc=>{ doc.roles.coder.vars.model='claude-sonnet-5-5';doc.roles.coder.vars.harness=harness; if(account)doc.launch.account=account; return edit(doc); }),ops=[]; initializeControl(f.dataRoot); withoutHostCredentials(f.env,{piKey:harness==='pi'}); for(const k of set)f.env[k]=VALUE; const host={business:'acme',beforeClose:()=>{},op:async x=>{ ops.push(x); if(x.op==='identity')return {business:'acme',role:'pm'}; if(x.op==='mayLaunch')return {allow:true}; if(x.op==='authorizeLaunch')throw Object.assign(Error('fixture authorization stop'),{code:'fixture-policy'}); return {}; }}; const launcher=createLauncher({host,system:JSON.parse(readFileSync(f.config,'utf8')),env:f.env,bwrap:'/usr/bin/true',log:()=>{},...(proxy?{testProxy:proxy}:{})}); const last=()=>JSON.parse(readFileSync(launchLogFile(f.dataRoot,'acme'),'utf8').trim().split('\n').at(-1)); const may=()=>ops.find(x=>x.op==='mayLaunch'); return {launcher,ops,last,may}; } test('REQ-LIMIT-2 a claude-code launch asks mayLaunch with launch.account.name',async t=>{ const {launcher,may}=setup(t); assert.equal((await launcher.fromSession('fixture-cap','coder')).error,'fixture-policy'); assert.equal(may().account,'jason-max'); }); test('REQ-LIMIT-2 each login set the account can name is accepted when the bus host holds exactly it',async t=>{ for(const set of SESSION_CREDENTIALS){ const {launcher,may}=setup(t,{account:{name:'jason-max',credential:set.join('+')},set}); assert.equal((await launcher.fromSession('fixture-cap','coder')).error,'fixture-policy',set.join('+')); assert.equal(may().account,'jason-max'); } }); test('REQ-LIMIT-2 account-credential-missing: the named set is not a login set, or the bus host lacks part of it',async t=>{ for(const [credential,set] of [ ['ANTHROPIC_API_KEY',['CLAUDE_CODE_OAUTH_TOKEN']], // The host holds a login (OAuth), so no-session-credential passes; the named set lacks its URL. ['ANTHROPIC_AUTH_TOKEN+ANTHROPIC_BASE_URL',['CLAUDE_CODE_OAUTH_TOKEN','ANTHROPIC_AUTH_TOKEN']], ['ANTHROPIC_AUTH_TOKEN',['ANTHROPIC_AUTH_TOKEN','ANTHROPIC_BASE_URL']], ['ANTHROPIC_BASE_URL+ANTHROPIC_AUTH_TOKEN',['ANTHROPIC_AUTH_TOKEN','ANTHROPIC_BASE_URL']], ]){ const {launcher,ops,last}=setup(t,{account:{name:'jason-max',credential},set}); assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'account-credential-missing'},credential); const event=last(); assert.equal(event.code,'account-credential-missing'); assert.match(event.why,new RegExp(`^account jason-max names ${credential.split('+').join(' with ')},`)); assert.ok(!JSON.stringify(event).includes(VALUE),'no value in the log'); assert.equal(ops.find(x=>x.op==='mayLaunch'),undefined,credential); } }); test('REQ-LIMIT-2 account-credential-ambiguous: the bus host sets a credential variable outside the named set, ANTHROPIC_BASE_URL included',async t=>{ for(const [extra,named] of [[['ANTHROPIC_API_KEY'],'ANTHROPIC_API_KEY'],[['ANTHROPIC_BASE_URL'],'ANTHROPIC_BASE_URL'], [['ANTHROPIC_AUTH_TOKEN','ANTHROPIC_BASE_URL'],'ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL']]){ const {launcher,ops,last}=setup(t,{set:['CLAUDE_CODE_OAUTH_TOKEN',...extra]}); assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'account-credential-ambiguous'},named); const event=last(); assert.equal(event.why,`account jason-max names CLAUDE_CODE_OAUTH_TOKEN, but the bus host also sets ${named}`); assert.ok(!JSON.stringify(event).includes(VALUE),'no value in the log'); assert.equal(ops.find(x=>x.op==='mayLaunch'),undefined,named); } }); // Usage records keep the account's name, so a name that is a credential variable's value refuses, // whichever variable holds it, before any message that names the account. test('REQ-LIMIT-2 account-is-credential: launch.account.name is the value of a credential variable the bus host sets',async t=>{ for(const set of [['CLAUDE_CODE_OAUTH_TOKEN'],['CLAUDE_CODE_OAUTH_TOKEN','ANTHROPIC_API_KEY']]){ const {launcher,ops,last}=setup(t,{account:{name:VALUE,credential:'CLAUDE_CODE_OAUTH_TOKEN'},set}); assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'account-is-credential'},set.join()); const event=last(); assert.equal(event.code,'account-is-credential'); assert.match(event.why,/^launch\.account\.name is the value of [A-Z_, ]+; it names the account, never its credential$/); for(const k of set)assert.ok(event.why.includes(k),k); assert.ok(!JSON.stringify(event).includes(VALUE),'no value in the log'); assert.equal(ops.find(x=>x.op==='mayLaunch'),undefined,set.join()); } }); test('REQ-LIMIT-2 a pi launch carries account null and skips both account checks',async t=>{ // No session login variable at all, and two at once: neither refuses a pi launch. pi itself runs // on the made-up pi key setup gives it, which is no session login. for(const set of [[],['CLAUDE_CODE_OAUTH_TOKEN','ANTHROPIC_API_KEY']]){ const {launcher,may}=setup(t,{harness:'pi',set}); assert.equal((await launcher.fromSession('fixture-cap','coder')).error,'fixture-policy',set.join()); assert.equal(may().account,null); } }); test('REQ-LIMIT-2 with no launch.account a claude-code launch carries null and the environment is not checked for ambiguity',async t=>{ const {launcher,may}=setup(t,{account:null,set:['CLAUDE_CODE_OAUTH_TOKEN','ANTHROPIC_API_KEY']}); assert.equal((await launcher.fromSession('fixture-cap','coder')).error,'fixture-policy'); assert.equal(may().account,null); }); // Row 332 R2 (#1830, review F3): the account checks on a launch with a session proxy context. A // proxy model is routed through the launcher's testProxy option; no proxy runs. const PROXY={baseUrl:'http://127.0.0.1:9',key:'fx332-acct-key'}; const proxyModel=doc=>{ doc.launch.models['proxy-sonnet']={provider:'proxy',model:'claude-sonnet-5-5',route:'anthropic'}; doc.roles.coder.vars.model='proxy-sonnet'; return doc; }; test('row 332 R2 a proxy session refuses account-credential-missing when launch.account names a host login, not proxy',async t=>{ const {launcher,ops,last}=setup(t,{proxy:PROXY,edit:proxyModel}); assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'account-credential-missing'}); const event=last(); assert.equal(event.code,'account-credential-missing'); assert.equal(event.why,'account jason-max needs the declared proxy credential'); for(const value of [VALUE,PROXY.key])assert.ok(!JSON.stringify(event).includes(value),'no value in the log'); assert.equal(ops.find(x=>x.op==='mayLaunch'),undefined); }); test('row 332 R2 launch.account credential proxy with no proxy session refuses account-credential-missing',async t=>{ const {launcher,ops,last}=setup(t,{account:{name:'jason-max',credential:'proxy'}}); assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'account-credential-missing'}); const event=last(); assert.equal(event.code,'account-credential-missing'); assert.equal(event.why,'account jason-max needs the declared proxy credential'); assert.ok(!JSON.stringify(event).includes(VALUE),'no value in the log'); assert.equal(ops.find(x=>x.op==='mayLaunch'),undefined); }); test('row 332 R2 account-is-credential: launch.account.name is the session proxy key; another name passes the account checks',async t=>{ const {launcher,ops,last}=setup(t,{account:{name:PROXY.key,credential:'proxy'},set:[],proxy:PROXY,edit:proxyModel}); assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'account-is-credential'}); const event=last(); assert.equal(event.why,'launch.account.name is the value of session proxy key; it names the account, never its credential'); assert.ok(!JSON.stringify(event).includes(PROXY.key),'no proxy key in the log'); assert.equal(ops.find(x=>x.op==='mayLaunch'),undefined); // The same proxy session and credential with an account name that is no key value launches. const named=setup(t,{account:{name:'jason-max',credential:'proxy'},set:[],proxy:PROXY,edit:proxyModel}); assert.equal((await named.launcher.fromSession('fixture-cap','coder')).error,'fixture-policy'); assert.deepEqual(named.may(),{op:'mayLaunch',business:'acme',instance:'coder',provider:'proxy',model:'claude-sonnet-5-5',account:'jason-max'}); });