Files
stack/packages/cli/tests/proxy-launch-order.test.mjs
jason.woltje 87db40e0cd fix(cli,seat,discord,organization,control-board,harness,plugins,webui): review round: launcher reasons, session, discord reads, mosaic-config and keys by label (#1873)
Review "changes 8" fix round for org-redact6.

- launcher: a harness module's throw names its system code or class,
  never its message; preflight, loader, bundle, checkout, sandbox and
  route text is relayed with host paths replaced (relay()).
- claude-code and pi preflights name MOSAIC_CLAUDE_BIN and the
  repository's node_modules, never a path; persona and skill bundle
  errors and sandboxProblem name their file or the sandbox by label.
- seat session: thread store, legacy store, overlap, busy lock and
  newRun refusals name the place under the data root.
- discord: unknown keys, binding names and turn ids are described when
  not plain; approvals, journal and repository read errors name the file
  and code; git drops stderr lines that carry a host path and a spawn
  failure names its code only.
- seat, control-board: registration fields and seen keys are described
  when not plain; invalid seat name and layout state the constraint.
- organization: the registry names the config directory and registry
  file by label; --project is quoted only when it is a plain name;
  systemConfigProblem moves to its own module.
- scripts/mosaic-config.mjs: the config by where it came from, the data
  root and its marker by role, keys and arguments only when plain, and
  error codes in place of raw messages. Exit codes unchanged.
- webui sso-config: an unknown console key is named only when plain.

Sentinel tests in each package's path-redaction-labels file fail on
BASE; pins that quoted a path now assert the label and its absence.
2026-11-11 08:53:31 -06:00

287 lines
19 KiB
JavaScript

// Vikunja #774 F1 (row 332 review): with system.proxy set, a launch that can never start refuses in
// the first check, before authorizeLaunch, the workspace and checkouts, and the P2 modelPreflight
// that may issue a key. The first check predicts the route from the harness preflights on a
// stand-in key (R2); only the session proxy key match waits for the admitted key, in the final
// check. Real launcher, bus runtime and runner (managed P2 fixture).
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { join } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { PENDING_ADMISSION_KEY } from '../src/launcher.mjs';
import { sandboxProblem } from '../../seat/src/sandbox.mjs';
import { workspaceDir } from '../../seat/src/session.mjs';
import { managedLaunchFixture } from './model-managed-launch-fixture.mjs';
const skip = sandboxProblem() ?? false;
function editBusiness(f, update) {
const path = join(f.root, 'config', 'businesses', 'acme.json');
const document = JSON.parse(fs.readFileSync(path, 'utf8'));
update(document);
fs.writeFileSync(path, JSON.stringify(document) + '\n', { mode: 0o600 });
}
// A capability the bus issued to a claimed chief, as a launched chief holds.
function chief(f) {
const cap = f.bus.broker.bindLaunch({ business: 'acme', role: 'chief', run: 'chief-774', harness: f.f.doc.roles.chief.vars.harness });
f.bus.broker.request(cap, { verb: 'role.claim' });
return cap;
}
// The refusal came from the first check: nothing after it ran.
function refusedFirst(f, before, preflights, bindings = 0, instance = 'coder') {
assert.equal(f.trace.preflightAttempts, preflights, 'no modelPreflight for a launch the first check refuses');
assert.deepEqual(f.trace.ops.slice(before).filter(op => ['authorizeLaunch', 'credentialStatus', 'modelPreflight'].includes(op)), [],
'no authorizeLaunch, credentialStatus or modelPreflight after the first check');
assert.equal(fs.existsSync(join(workspaceDir(f.f.dataRoot, 'acme', instance), 'stack')), false, 'no checkout clone or fetch');
assert.equal(f.trace.bindings.length, bindings, 'no runner bound for the refused launch');
}
const refusal = (f, code) => f.launchLog().filter(e => e.event === 'refused' && e.code === code);
const claudeCoder = doc => {
doc.launch.models['claude-opus-5-5'].provider = 'anthropic';
doc.roles.coder.vars.model = 'claude-opus-5-5';
};
test('#774 F1 proxy capacity-full refuses in the first check: no modelPreflight, checkout or authorizeLaunch; the ordinary launch before it goes live', { skip, timeout: 60000 }, async t => {
const f = await managedLaunchFixture(t);
const cap = chief(f);
const ordinary = await f.launcher.fromSession(cap, 'reviewer');
assert.equal(ordinary.ok, true, JSON.stringify(ordinary));
assert.ok(f.sessions().find(s => s.run === ordinary.result.run)?.live, 'the ordinary proxy launch is live');
assert.equal(f.trace.preflightAttempts, 1, 'the ordinary proxy launch makes one P2 admission');
editBusiness(f, doc => { doc.launch.limits.total = 1; });
const before = f.trace.ops.length;
assert.deepEqual(await f.launcher.fromSession(cap, 'coder'), { ok: false, error: 'capacity-full' });
refusedFirst(f, before, 1, 1);
assert.deepEqual(refusal(f, 'capacity-full').map(e => e.limit), [{ scope: 'total', limit: 1, running: 1 }]);
f.valueFree();
});
test('#774 F1 proxy limit-reached refuses in the first check: no modelPreflight, checkout or authorizeLaunch', { skip, timeout: 60000 }, async t => {
const f = await managedLaunchFixture(t);
editBusiness(f, doc => {
doc.plugins = { ...doc.plugins, 'limit-check': ['token-limit'] };
doc.launch.limits.tokens = { instances: { coder: { limit: 0, since: '2026-10-18T00:00:00.000Z' } } };
});
const before = f.trace.ops.length;
assert.deepEqual(await f.launcher.fromSession(chief(f), 'coder'), { ok: false, error: 'limit-reached' });
refusedFirst(f, before, 0);
assert.equal(refusal(f, 'limit-reached')[0]?.limit?.check, 'token-limit');
assert.equal(f.runDirectories().length, 0);
f.valueFree();
});
test('#774 F1 proxy sandbox-unavailable refuses in the first check: no modelPreflight, checkout or authorizeLaunch', { skip, timeout: 60000 }, async t => {
const f = await managedLaunchFixture(t, { bwrap: '/nonexistent-774/bwrap' });
const before = f.trace.ops.length;
assert.deepEqual(await f.launcher.fromSession(chief(f), 'coder'), { ok: false, error: 'sandbox-unavailable' });
refusedFirst(f, before, 0);
assert.equal(f.runDirectories().length, 0);
f.valueFree();
});
test('#774 F1 proxy launch.account declared with an upstream credential refuses in the first check, before modelPreflight', { skip, timeout: 60000 }, async t => {
const f = await managedLaunchFixture(t, { harness: 'claude-code' });
editBusiness(f, doc => {
claudeCoder(doc);
doc.launch.account = { name: 'jason-max', credential: 'CLAUDE_CODE_OAUTH_TOKEN' };
});
const before = f.trace.ops.length;
assert.deepEqual(await f.launcher.fromSession(chief(f), 'coder'), { ok: false, error: 'account-credential-missing' });
refusedFirst(f, before, 0);
assert.equal(refusal(f, 'account-credential-missing')[0]?.why, 'account jason-max needs the declared proxy credential');
assert.equal(f.runDirectories().length, 0);
f.valueFree();
});
// The key match needs the admitted key, so it stays in the final check: after modelPreflight,
// before newRun and spawn. The fixture makes P2 admit a key equal to the account name.
test('#774 F1 proxy session key equal to launch.account.name still refuses account-is-credential in the final check', { skip, timeout: 60000 }, async t => {
const f = await managedLaunchFixture(t, { harness: 'claude-code', admit: value => ({ ...value, key: 'jason-max' }) });
editBusiness(f, doc => {
claudeCoder(doc);
doc.launch.account = { name: 'jason-max', credential: 'proxy' };
});
assert.deepEqual(await f.launcher.fromSession(chief(f), 'coder'), { ok: false, error: 'account-is-credential' });
assert.equal(f.trace.preflightAttempts, 1, 'the first check passed and P2 admitted a key');
assert.equal(f.trace.ops.filter(op => op === 'authorizeLaunch').length, 1);
assert.equal(refusal(f, 'account-is-credential')[0]?.why, 'launch.account.name is the value of session proxy key; it names the account, never its credential');
assert.equal(f.trace.bindings.length, 0);
assert.equal(f.sessions().length, 0);
assert.equal(f.runDirectories().length, 0, 'refused before newRun');
f.valueFree();
});
// Row 333: under a daily key quota the bus refuses mayLaunch key-quota-no-model-key for an agent
// with no model key. In the first check that is the one answer that waits for P2 to issue a key.
test('#774 F1 proxy launch of an agent with no key yet passes the first check under a daily key quota, and both checks ask mayLaunch', { skip, timeout: 60000 }, async t => {
const f = await managedLaunchFixture(t);
editBusiness(f, doc => {
doc.plugins = { ...doc.plugins, 'limit-check': ['token-limit'] };
doc.launch.limits.tokens = { keys: { '000000000774': { limit: 1000000, since: '2026-10-18T00:00:00.000Z', reset: '00:00', timeZone: 'UTC' } } };
});
assert.equal(f.bus.credentials.modelRecord('acme/chief'), null, 'chief holds no model key yet');
const answer = await f.launcher.launchPm();
assert.ok(f.sessions().find(s => s.run === answer.run)?.live, 'the launch goes live');
assert.equal(f.trace.successfulPreflights, 1);
assert.equal(f.trace.ops.filter(op => op === 'mayLaunch').length, 2, 'mayLaunch in the first and the final check');
f.valueFree();
});
// #774 R2: the first check gates the route the final check picks. The coder's primary is the real
// claude-code module on claude-opus-5-5 (provider anthropic); MOSAIC_CLAUDE_BIN decides whether its
// preflight passes. The stated fallback is pi on glm-5.3-flash (provider zai).
const NO_CLAUDE = '/nonexistent-774/claude';
// #1873: names the variable, never its value (was `MOSAIC_CLAUDE_BIN ${NO_CLAUDE} isn't ...`).
const NO_CLAUDE_WHY = "MOSAIC_CLAUDE_BIN isn't an executable file at an absolute path";
const routed = (t, bin) => managedLaunchFixture(t, { harness: 'claude-code', extra: ['pi'], real: ['claude-code'], hostEnv: { MOSAIC_CLAUDE_BIN: bin } });
const withFallback = (doc, harness = 'pi', model = 'glm-5.3-flash') => {
claudeCoder(doc);
doc.roles.coder.vars = { ...doc.roles.coder.vars, 'fallback.harness': harness, 'fallback.model': model, 'fallback.thinking': 'high' };
};
const full = (doc, provider, model) => { doc.launch.limits.models = { ...doc.launch.limits.models, [provider]: { ...doc.launch.limits.models?.[provider], [model]: 0 } }; };
const launched = (f, run) => f.launchLog().find(e => e.event === 'launch' && e.run === run);
test('#774 R2 a healthy primary at capacity zero refuses capacity-full in the first check, though the fallback has room', { skip, timeout: 60000 }, async t => {
const f = await routed(t, process.execPath);
editBusiness(f, doc => { withFallback(doc); full(doc, 'anthropic', 'claude-opus-5-5'); });
const before = f.trace.ops.length;
assert.deepEqual(await f.launcher.fromSession(chief(f), 'coder'), { ok: false, error: 'capacity-full' });
assert.deepEqual(refusal(f, 'capacity-full').map(e => e.limit), [{ scope: 'model', limit: 0, running: 0, provider: 'anthropic', model: 'claude-opus-5-5' }]);
refusedFirst(f, before, 0);
assert.equal(f.runDirectories().length, 0);
f.valueFree();
});
test('#774 R2 a failed primary preflight with a stated fallback launches on the fallback, route fallback, reason harness-binary-missing', { skip, timeout: 60000 }, async t => {
const f = await routed(t, NO_CLAUDE);
editBusiness(f, doc => withFallback(doc));
const answer = await f.launcher.fromSession(chief(f), 'coder');
assert.equal(answer.ok, true, JSON.stringify(answer));
assert.deepEqual(answer.result, { run: answer.result.run, instance: 'coder', harness: 'pi', model: 'glm-5.3-flash' });
assert.ok(f.sessions().find(s => s.run === answer.result.run)?.live, 'the fallback launch is live');
const event = launched(f, answer.result.run);
assert.deepEqual([event?.harness, event?.route, event?.routeReason, event?.provider, event?.model], ['pi', 'fallback', 'harness-binary-missing', 'zai', 'glm-5.3-flash']);
assert.equal(f.trace.successfulPreflights, 1);
assert.equal(f.trace.ops.filter(op => op === 'authorizeLaunch').length, 1);
f.valueFree();
});
test('#774 R2 a failed primary preflight at capacity zero still launches on the fallback: the first check gates the predicted route', { skip, timeout: 60000 }, async t => {
const f = await routed(t, NO_CLAUDE);
editBusiness(f, doc => { withFallback(doc); full(doc, 'anthropic', 'claude-opus-5-5'); });
const answer = await f.launcher.fromSession(chief(f), 'coder');
assert.equal(answer.ok, true, JSON.stringify(answer));
assert.ok(f.sessions().find(s => s.run === answer.result.run)?.live, 'the fallback launch is live');
const event = launched(f, answer.result.run);
assert.deepEqual([event?.harness, event?.route, event?.routeReason], ['pi', 'fallback', 'harness-binary-missing']);
assert.equal(refusal(f, 'capacity-full').length, 0);
f.valueFree();
});
test('#774 R2 a failed primary preflight and a fallback at capacity zero refuse capacity-full for the fallback in the first check', { skip, timeout: 60000 }, async t => {
const f = await routed(t, NO_CLAUDE);
editBusiness(f, doc => { withFallback(doc); full(doc, 'zai', 'glm-5.3-flash'); });
const before = f.trace.ops.length;
assert.deepEqual(await f.launcher.fromSession(chief(f), 'coder'), { ok: false, error: 'capacity-full' });
assert.deepEqual(refusal(f, 'capacity-full').map(e => e.limit), [{ scope: 'model', limit: 0, running: 0, provider: 'zai', model: 'glm-5.3-flash' }]);
refusedFirst(f, before, 0);
assert.equal(f.runDirectories().length, 0);
f.valueFree();
});
test('#774 R2 when both preflights fail the first check refuses with the final check\'s combined code and message', { skip, timeout: 60000 }, async t => {
const f = await routed(t, NO_CLAUDE);
editBusiness(f, doc => withFallback(doc, 'claude-code', 'claude-sonnet-5-5'));
const before = f.trace.ops.length;
assert.deepEqual(await f.launcher.fromSession(chief(f), 'coder'), { ok: false, error: 'harness-binary-missing' });
assert.deepEqual(refusal(f, 'harness-binary-missing').map(e => e.why), [`fallback claude-code: ${NO_CLAUDE_WHY} (primary claude-code: ${NO_CLAUDE_WHY})`]);
refusedFirst(f, before, 0);
assert.equal(f.runDirectories().length, 0);
f.valueFree();
});
test('#774 R2 a failed primary preflight with no fallback refuses harness-binary-missing in the first check', { skip, timeout: 60000 }, async t => {
const f = await routed(t, NO_CLAUDE);
editBusiness(f, claudeCoder);
const before = f.trace.ops.length;
assert.deepEqual(await f.launcher.fromSession(chief(f), 'coder'), { ok: false, error: 'harness-binary-missing' });
assert.deepEqual(refusal(f, 'harness-binary-missing').map(e => e.why), [NO_CLAUDE_WHY]);
refusedFirst(f, before, 0);
assert.equal(f.runDirectories().length, 0);
f.valueFree();
});
// Both preflights of the first check saw the stand-in key; none of it reaches the evidence.
test('#774 R2 the stand-in key never reaches the launch log, the authorization record, session.json or the data root', { skip, timeout: 60000 }, async t => {
assert.equal(typeof PENDING_ADMISSION_KEY === 'string' && PENDING_ADMISSION_KEY.length > 0, true);
const f = await routed(t, NO_CLAUDE);
editBusiness(f, doc => withFallback(doc));
const answer = await f.launcher.fromSession(chief(f), 'coder');
assert.equal(answer.ok, true, JSON.stringify(answer));
assert.ok(f.sessions().find(s => s.run === answer.result.run)?.live, 'the fallback launch is live');
const absent = (what, value) => assert.equal(String(value).includes(PENDING_ADMISSION_KEY), false, `the stand-in key reached ${what}`);
const log = f.launchLog();
assert.ok(launched(f, answer.result.run), 'the launch log holds the launch');
absent('the launch log', JSON.stringify(log));
const db = new DatabaseSync(join(f.f.dataRoot, 'bus', 'bus.sqlite'), { readOnly: true });
let events;
try { events = db.prepare('SELECT kind, subject, body FROM events ORDER BY seq').all(); } finally { db.close(); }
assert.equal(events.filter(e => e.kind === 'action.allowed' && JSON.parse(e.body).action === 'role.launch').length, 1, 'one authorization record for the launch');
absent('the bus events', JSON.stringify(events));
const session = join(f.runDirectories().find(d => d.endsWith(`/${answer.result.run}`)), 'session.json');
assert.ok(fs.existsSync(session), 'the run has a session.json');
absent('session.json', fs.readFileSync(session, 'utf8'));
absent('the host log', JSON.stringify(f.logs));
absent('the trace', JSON.stringify(f.trace));
// Every regular file under the data root, the bus database and its WAL included.
const files = fs.readdirSync(f.f.dataRoot, { recursive: true, withFileTypes: true }).filter(e => e.isFile()).map(e => join(e.parentPath, e.name));
assert.ok(files.length > 0);
for (const file of files) {
let bytes;
try { bytes = fs.readFileSync(file); } catch (e) { if (e.code === 'ENOENT') continue; throw e; }
absent(file, bytes);
}
f.valueFree();
});
test('a removed catalogue model is refused by final admission before newRun', { skip, timeout: 60000 }, async t => {
let reads=0;
const offered={key:'zai-fixture-id',module:'cliproxy',upstream:'zai',provider:'zai',model:'fixture-id',label:'fixture-id',available:true,reason:null,harnesses:['pi']};
const modelCatalogue={invalidate(){},read:async()=>({status:'ready',observedAt:'2026-11-06T00:00:00.000Z',models:++reads===1?[offered]:[],notices:[]})};
const f=await managedLaunchFixture(t,{modelCatalogue});
editBusiness(f,doc=>{doc.plugins['model-provider']=['cliproxy'];doc.launch.models['zai-fixture-id']={provider:'zai',model:'fixture-id',module:'cliproxy',upstream:'zai'};doc.roles.coder.vars.model='zai-fixture-id';});
const result=await f.launcher.fromSession(chief(f),'coder');
assert.deepEqual(result,{ok:false,error:'model-provider-unavailable'});
assert.equal(reads,2);assert.equal(f.trace.preflightAttempts,1);assert.equal(f.runDirectories().length,0);assert.equal(f.trace.bindings.length,0);
});
test('fixture catalogue route persists, launches, and then refuses after model removal', { skip, timeout: 60000 }, async t => {
let present=true;
const offered={key:'zai-fixture-id',module:'cliproxy',upstream:'zai',provider:'zai',model:'fixture-id',label:'z.ai / fixture-id',available:true,reason:null,harnesses:['pi']};
const modelCatalogue={invalidate(){},read:async()=>({status:'ready',observedAt:'2026-11-06T00:00:00.000Z',models:present?[offered]:[],notices:[]})};
const f=await managedLaunchFixture(t,{modelCatalogue});
editBusiness(f,doc=>{doc.plugins['model-provider']=['cliproxy'];});
const human=f.bus.broker.bindHuman({business:'acme',human:'jason',via:'cli',outsideAgent:true});
const before=await f.bus.broker.request(human,{verb:'routes'});
assert.equal(before.models.includes(offered.key),false);assert.equal(before.catalogue.models[0].key,offered.key);
const route={harness:'pi',model:offered.key,thinking:'low',fallback:null};
await f.bus.broker.request(human,{verb:'routes.set',args:{revision:before.revision,routes:{coder:route,reviewer:route}}});
const doc=JSON.parse(fs.readFileSync(join(f.root,'config/businesses/acme.json'),'utf8'));
assert.deepEqual(doc.launch.models[offered.key],{provider:'zai',model:'fixture-id',module:'cliproxy',upstream:'zai'});
const cap=chief(f);const launched=await f.launcher.fromSession(cap,'coder');
assert.equal(launched.ok,true,JSON.stringify(launched));
const session=f.sessions().find(x=>x.run===launched.result.run);assert.equal(session.provider,'zai');assert.equal(session.model,'fixture-id');
present=false;const after=await f.bus.broker.request(human,{verb:'routes'});
assert.equal(after.instances.reviewer.model,offered.key);assert.equal(after.catalogue.models.length,0);
assert.deepEqual(await f.launcher.fromSession(cap,'reviewer'),{ok:false,error:'model-provider-unavailable'});
});
test('business revision changed during final limit check refuses before creating a run', { skip, timeout: 60000 }, async t => {
let currentModel='fixture-old';
const modelCatalogue={invalidate(){},read:async()=>({status:'ready',observedAt:'2026-11-06T00:00:00.000Z',models:[{key:`zai-${currentModel}`,module:'cliproxy',upstream:'zai',provider:'zai',model:currentModel,label:currentModel,available:true,reason:null,harnesses:['pi']}],notices:[]})};
const f=await managedLaunchFixture(t,{modelCatalogue});
editBusiness(f,doc=>{doc.plugins['model-provider']=['cliproxy'];doc.launch.models['legacy-fixture']={provider:'zai',model:'fixture-old'};doc.roles.coder.vars.model='legacy-fixture';});
const human=f.bus.broker.bindHuman({business:'acme',human:'jason',via:'cli',outsideAgent:true});
const original=f.bus.mayLaunch; let count=0;
f.bus.mayLaunch=async request=>{const answer=await original(request);if(++count===2){currentModel='fixture-new';const view=f.bus.broker.request(human,{verb:'limits'});const models=structuredClone(view.models);models['legacy-fixture'].model=currentModel;await f.bus.broker.request(human,{verb:'limits.set',args:{revision:view.revision,models}});}return answer;};
assert.deepEqual(await f.launcher.fromSession(chief(f),'coder'),{ok:false,error:'model-provider-unavailable'});
assert.equal(f.runDirectories().length,0);assert.equal(f.trace.bindings.length,0);
});