Files
stack/packages/bus/tests/model-keys.test.mjs

349 lines
26 KiB
JavaScript

import {materializeCopies,withCopies} from '../../organization/src/agent-copies.mjs';
import {loadRootAgents} from '../../organization/src/agent-levels.mjs';
import { approveTemplateHire, ownerConsole } from './hire-fixtures.mjs';
// P2: real private Credentials, fake loopback management/model server. No live config.
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import http from 'node:http';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { createHash, randomBytes } from 'node:crypto';
import { Credentials } from '../src/credentials.mjs';
import { ManagementClient } from '../../model-proxy/src/client.mjs';
import { ModelKeys, REVOKE_TIMEOUT_MS, REVOKE_POLL_MS } from '../src/model-keys.mjs';
import { startBroker } from '../src/runtime.mjs';
import { busOrganization } from '../src/organization-context.mjs';
import { Registry } from '../../seat/src/session.mjs';
import { loadBusiness } from '../../organization/src/organization-config.mjs';
import { resolveInstance } from '../../organization/src/resolve.mjs';
import { businessDoc, rolesCopy, systemFor, writeJson } from '../../organization/tests/helpers.mjs';
const hash = s => createHash('sha256').update(s).digest('hex').slice(0, 12);
const failCode = code => e => e.code === code;
async function fixture(t, { delayed = 0 } = {}) {
const root = fs.mkdtempSync(join(tmpdir(), 'p2-keys-')), configDir = join(root, 'config');
const storeRoot = join(configDir, 'secrets', 'agents'); fs.mkdirSync(storeRoot, { recursive: true, mode: 0o700 });
const prefix = `msk-${hash(configDir)}-`, makeKey = () => prefix + randomBytes(32).toString('base64url');
const sentinel = prefix + 'sentinel-' + randomBytes(32).toString('base64url');
const state = { keys: [sentinel], calls: [], delay: delayed, removed: new Map(), failGet: false, failPut: false, hideConfirm: false, probes: 0, status: null };
const server = http.createServer(async (req, res) => {
const management = req.url === '/v8/management/config/access/api-keys';
state.calls.push(management ? req.method : 'PROBE');
if (management) {
if (req.headers.authorization !== 'Bearer fx331-manage') { res.writeHead(401); res.end('{}'); return; }
if ((req.method === 'GET' && state.failGet) || (req.method === 'PUT' && state.failPut)) { res.writeHead(503); res.end('{}'); return; }
if (req.method === 'GET') { res.end(JSON.stringify(state.hideConfirm ? [sentinel] : state.keys)); return; }
const chunks = []; for await (const c of req) chunks.push(c);
const next = JSON.parse(Buffer.concat(chunks));
for (const k of state.keys.filter(k => !next.includes(k))) state.removed.set(k, performance.now());
state.keys = next; res.end('{"status":"ok"}'); return;
}
state.probes++;
const key = req.headers.authorization?.slice(7);
const accepted = state.keys.includes(key) || (state.removed.has(key) && performance.now() - state.removed.get(key) < state.delay);
state.onProbe?.(state.status ?? (accepted ? 200 : 401));
res.writeHead(state.status ?? (accepted ? 200 : 401)); res.end('{}');
});
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
const baseUrl = `http://127.0.0.1:${server.address().port}`, receipts = [], stopped = [], runs = [];
let now = Date.parse('2026-10-24T00:00:00.000Z');
const credentials = new Credentials({ storeRoot, dataRoot: join(root, 'data'), modelAgents: ['demo/coder', 'demo/other'] });
const client = new ManagementClient({ baseUrl, key: 'fx331-manage', receipt: r => receipts.push(r) });
const create = () => new ModelKeys({ credentials, client, configDir, baseUrl, receipt: r => receipts.push(r), now: () => now,
runs: instance => runs.filter(r => r.instance === instance), stopRuns: async rs => { stopped.push(...rs.map(r => r.run)); for (const r of rs) runs.splice(runs.indexOf(r), 1); } });
const manager = create();
const closers = [];
t.after(async () => { for (const close of closers) await close(); credentials.close(); server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); fs.rmSync(root, { recursive: true, force: true }); });
return { root, configDir, credentials, client, server, closers, state, manager, create, makeKey, sentinel, receipts, baseUrl, stopped, runs, advance: ms => { now += ms; } };
}
test('issue confirms GET/PUT/GET before private file and returns P3 shape with only own key', async t => {
const f = await fixture(t); await f.manager.reconcile(); f.state.calls.length = 0;
const answer = await f.manager.ensure('demo/coder');
assert.deepEqual(Object.keys(answer).sort(), ['baseUrl', 'key']); assert.equal(answer.baseUrl, f.baseUrl);
assert.deepEqual(f.state.calls, ['GET', 'PUT', 'GET']);
assert.equal(f.state.keys.includes(answer.key), true);
assert.equal(f.credentials.status().find(s => s.instance === 'demo/coder').date, '2026-11-23');
assert.equal(JSON.stringify(f.receipts).includes(answer.key), false);
const other = await f.manager.ensure('demo/other'); assert.equal(other.key === answer.key, false);
});
for (const fault of ['failGet', 'failPut', 'hideConfirm']) test(`proxy-key-issue-failed: ${fault} writes no model record`, async t => {
const f = await fixture(t); await f.manager.reconcile(); f.state[fault] = true;
await assert.rejects(f.manager.ensure('demo/coder'), failCode('proxy-key-issue-failed'));
assert.equal(f.credentials.modelRecord('demo/coder') === null, true);
assert.equal(fs.existsSync(join(f.configDir, 'secrets', 'agents', 'demo', 'coder', 'model')), false);
});
test('credential-revoke-failed: missing confirming GET keeps model file and revoking status', async t => {
const f = await fixture(t); await f.manager.reconcile(); await f.manager.ensure('demo/coder');
f.state.failGet = true;
await assert.rejects(f.manager.prepareRevoke('demo/coder'), failCode('credential-revoke-failed'));
assert.equal(f.credentials.status().find(s => s.instance === 'demo/coder').state, 'revoking');
});
test('revoke confirms absence then polls delayed client refusal before local removal', async t => {
const f = await fixture(t, { delayed: 105 }); await f.manager.reconcile(); const { key } = await f.manager.ensure('demo/coder');
f.state.calls.length = 0; const before = performance.now();
await f.manager.prepareRevoke('demo/coder');
assert.ok(performance.now() - before >= 100); assert.deepEqual(f.state.calls.slice(0, 3), ['GET', 'PUT', 'GET']);
assert.ok(f.state.probes >= 2); assert.equal(f.state.keys.includes(key), false);
assert.equal(f.credentials.modelRecord('demo/coder')?.state, 'revoking');
f.manager.completeRevoke('demo/coder'); assert.equal(f.credentials.modelRecord('demo/coder') === null, true);
assert.equal(JSON.stringify(f.receipts).includes(key), false);
});
test('reconcile restores missing recorded keys, removes own unknown, and preserves foreign and sentinel', async t => {
const f = await fixture(t); await f.manager.reconcile(); const { key } = await f.manager.ensure('demo/coder');
const unknown = f.makeKey(), foreign = 'fx331-human'; f.state.keys = [f.sentinel, unknown, foreign]; f.state.calls.length = 0;
await f.create().reconcile();
assert.equal(f.state.keys.includes(key), true); assert.equal(f.state.keys.includes(unknown), false);
assert.equal(f.state.keys.includes(foreign), true); assert.equal(f.state.keys.includes(f.sentinel), true);
assert.deepEqual(f.state.calls.slice(0, 3), ['GET', 'PUT', 'GET']);
});
test('reconcile revoking never restores key and completes delayed revocation after restart', async t => {
const f = await fixture(t, { delayed: 105 }); await f.manager.reconcile(); const { key } = await f.manager.ensure('demo/coder');
const record = f.credentials.modelRecord('demo/coder');
f.credentials.writeModel('demo/coder', { ...record, state: 'revoking', revokingAt: '2026-10-24T00:00:00.000Z' });
await f.create().reconcile();
assert.equal(f.state.keys.includes(key), false); assert.equal(f.credentials.modelRecord('demo/coder') === null, true);
});
test('proxy-open: no own sentinel blocks reconcile and every launch', async t => {
const f = await fixture(t); f.state.keys = ['fx331-human'];
await assert.rejects(f.manager.reconcile(), failCode('proxy-open'));
await assert.rejects(f.manager.preflight('demo/coder'), failCode('proxy-open'));
assert.equal(f.state.probes, 0);
});
test('proxy-key-revoking: launch hold occurs before any preflight call', async t => {
const f = await fixture(t); await f.manager.reconcile(); await f.manager.ensure('demo/coder');
const record = f.credentials.modelRecord('demo/coder'); f.credentials.writeModel('demo/coder', { ...record, state: 'revoking', revokingAt: '2026-10-24T00:00:00.000Z' });
await assert.rejects(f.manager.preflight('demo/coder'), failCode('proxy-key-revoking')); assert.equal(f.state.probes, 0);
});
test('proxy-revoke-revision-conflict keeps local revoking record and ids-only remote-revoked receipt', async t => {
const f = await fixture(t); await f.manager.reconcile(); const { key } = await f.manager.ensure('demo/coder'); await f.manager.prepareRevoke('demo/coder');
assert.throws(() => f.manager.revisionConflict('demo/coder'), failCode('proxy-revoke-revision-conflict'));
assert.equal(f.credentials.modelRecord('demo/coder').state, 'revoking');
assert.equal(f.state.keys.includes(key), false);
assert.ok(f.receipts.some(r => r.remoteState === 'revoked' && r.localState === 'kept'));
assert.equal(JSON.stringify(f.receipts).includes(key), false);
await f.create().reconcile(); assert.equal(f.credentials.modelRecord('demo/coder') === null, true);
});
for (const status of [401, 503, 302]) test(`proxy-key-refused: preflight rejects HTTP ${status} with exactly one probe`, async t => {
const f = await fixture(t); await f.manager.reconcile(); await f.manager.ensure('demo/coder'); f.state.status = status;
await assert.rejects(f.manager.preflight('demo/coder'), failCode('proxy-key-refused')); assert.equal(f.state.probes, 1);
});
test('rotate keeps old key while its run lives, new launches use new, exit removes old', async t => {
const f = await fixture(t); await f.manager.reconcile(); const first = await f.manager.ensure('demo/coder');
f.runs.push({ instance: 'demo/coder', run: 'old', keyId: hash(first.key) });
const next = await f.manager.rotate('demo/coder'); assert.equal(next.key === first.key, false);
assert.equal(f.state.keys.includes(first.key), true); assert.equal((await f.manager.ensure('demo/coder')).key === next.key, true);
await f.manager.sweep(); assert.equal(f.state.keys.includes(first.key), true);
f.runs.length = 0; await f.manager.sweep(); assert.equal(f.state.keys.includes(first.key), false);
});
test('rotation grace stops surviving old-key runs before remote revocation', async t => {
const f = await fixture(t); await f.manager.reconcile(); const first = await f.manager.ensure('demo/coder');
f.runs.push({ instance: 'demo/coder', run: 'old', keyId: hash(first.key) }); await f.manager.rotate('demo/coder');
f.advance(120 * 60_000); await f.manager.sweep(); assert.deepEqual(f.stopped, ['old']); assert.equal(f.state.keys.includes(first.key), false);
});
test('revocation polling constants retain the approved five-second bound and 25ms spacing', () => {
assert.equal(REVOKE_TIMEOUT_MS, 5000); assert.equal(REVOKE_POLL_MS, 25);
});
test('credential-revoke-failed: polling cannot treat management absence as client revocation', async t => {
const f = await fixture(t, { delayed: Infinity }); await f.manager.reconcile(); await f.manager.ensure('demo/coder');
const start = performance.now();
await assert.rejects(f.manager.prepareRevoke('demo/coder'), failCode('credential-revoke-failed'));
const elapsed = performance.now() - start;
assert.ok(elapsed >= 4900 && elapsed < 6500, 'five-second total revocation polling bound');
assert.equal(f.credentials.modelRecord('demo/coder').state, 'revoking');
});
test('proxy-down: unreachable listener refuses preflight', async t => {
const f = await fixture(t); await f.manager.reconcile(); await f.manager.ensure('demo/coder');
await new Promise(resolve => f.server.close(resolve));
await assert.rejects(f.manager.preflight('demo/coder'), failCode('proxy-down'));
});
test('proxy-stop-failed: old proxy key and retiring record survive a failed grace stop, next sweep retries', async t => {
const f = await fixture(t); await f.manager.reconcile(); const first = await f.manager.ensure('demo/coder');
f.runs.push({ instance: 'demo/coder', run: 'old', keyId: hash(first.key) });
let stopped = false;
const manager = new ModelKeys({ credentials: f.credentials, client: f.client, configDir: f.configDir, baseUrl: f.baseUrl,
now: () => Date.parse('2026-10-24T03:00:00.000Z'), runs: () => f.runs,
stopRuns: async () => { if (!stopped) throw Error('fx331-stop'); f.runs.length = 0; } });
await manager.reconcile(); await manager.rotate('demo/coder');
// This manager created a fresh 120-minute window at 03:00; use a second view
// with time beyond that deadline to exercise failure then retry of the same record.
const overdue = new ModelKeys({ credentials: f.credentials, client: f.client, configDir: f.configDir, baseUrl: f.baseUrl,
now: () => Date.parse('2026-10-24T06:00:00.000Z'), runs: () => f.runs,
stopRuns: async () => { if (!stopped) throw Error('fx331-stop'); f.runs.length = 0; } });
await overdue.reconcile();
await assert.rejects(overdue.sweep(), failCode('proxy-stop-failed'));
assert.equal(f.state.keys.includes(first.key), true); assert.equal(f.credentials.modelRecord('demo/coder').retiring.length, 1);
stopped = true; await overdue.sweep(); assert.equal(f.state.keys.includes(first.key), false);
});
async function bootBusiness(f, {copyLevel = null,...binding} = {}) {
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\n## Authority\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: [],
});
doc.plugins = { ...doc.plugins, harness: ['pi'] };
doc.roles['sec-1'] = { definition: 'security-reviewer' }; doc.launch.instances.push('sec-1');
const copyEntry={definition:'aide',vars:{harness:'pi',model:'claude-opus-5-5'}};
if(copyLevel==='project')doc.projects.stack.agents={aide:copyEntry};
if(copyLevel==='root')writeJson(join(f.configDir,'agents.json'),{agentsVersion:1,agents:{aide:copyEntry}});
const copies=copyLevel?materializeCopies({orgId:'demo',doc,rolesDir,root:loadRootAgents(f.configDir,{rolesDir})}):null;
const file = writeJson(join(f.configDir, 'businesses', 'demo.json'), doc);
fs.writeFileSync(join(f.configDir, 'secrets', 'proxy-management.key'), 'fx331-manage', { mode: 0o600 });
const business = copies?withCopies(doc,file,{rolesDir,snapshot:copies}):loadBusiness('demo', { dir: f.configDir, rolesDir }), resolved = {};
for (const instance of Object.keys(business.roles)) resolved[instance] = resolveInstance({ system, business, instance });
fs.mkdirSync(join(f.root, 'data'), { mode: 0o700 });
// Fresh mkdtemp fixture + exclusive mkdir above: no prior session registry can exist.
new Registry(join(f.root, 'data'));
const bus = await startBroker({ dataRoot: join(f.root, 'data'), businesses: { demo: busOrganization(business, resolved) },
copies, organization: { dir: f.configDir, rolesDir, system }, proxy: { port: Number(new URL(f.baseUrl).port), binary: '/fixture-unused' }, ...binding });
f.closers.push(() => bus.close());
const chief = bus.broker.bindLaunch({ business: 'demo', role: 'chief', run: 'chief-run', harness: 'pi' });
bus.broker.request(chief, { verb: 'role.claim' });
const fire = () => bus.broker.request(ownerConsole(bus.broker), { verb: 'hire.start.console', args: { op:'fire',level:'org',name: 'sec-1', reason: 'fixture fire' } });
const listed = () => JSON.parse(fs.readFileSync(file, 'utf8')).launch.instances.includes('sec-1');
return { bus, chief, fire, listed, file, rolesDir, system };
}
test('bus fire proves remote 401 before delisting the agent and deleting model file', async t => {
const f = await fixture(t, { delayed: 105 }); await f.manager.reconcile(); const issued = await f.manager.ensure('demo/sec-1');
const b = await bootBusiness(f); let sawRefusal = false;
f.state.onProbe = status => {
assert.equal(b.listed(), true); assert.equal(b.bus.credentials.modelRecord('demo/sec-1')?.state, 'revoking');
if (status === 401) sawRefusal = true;
};
const result = await b.fire(); f.state.onProbe = null;
assert.equal(sawRefusal, true); assert.equal(result.delisted, true); assert.equal(b.listed(), false);
assert.equal(f.state.keys.includes(issued.key), false); assert.equal(b.bus.credentials.modelRecord('demo/sec-1') === null, true);
});
test('credential-revoke-failed: bus fire on failed remote PUT keeps membership and model record', async t => {
const f = await fixture(t); await f.manager.reconcile(); const issued = await f.manager.ensure('demo/sec-1'); const b = await bootBusiness(f);
const before = fs.readFileSync(b.file, 'utf8'); f.state.failPut = true;
await assert.rejects(b.fire(), failCode('credential-revoke-failed'));
assert.equal(fs.readFileSync(b.file, 'utf8'), before); assert.equal(b.listed(), true);
assert.equal(f.state.keys.includes(issued.key), true); assert.equal(b.bus.credentials.modelRecord('demo/sec-1')?.state, 'revoking');
});
test('proxy-revoke-revision-conflict: racing organization write keeps record, next boot finishes intent', async t => {
const f = await fixture(t); await f.manager.reconcile(); const issued = await f.manager.ensure('demo/sec-1'); const b = await bootBusiness(f);
let raced = false;
f.state.onProbe = status => { if (status === 401 && !raced) { raced = true; fs.appendFileSync(b.file, '\n'); } };
await assert.rejects(b.fire(), failCode('proxy-revoke-revision-conflict')); f.state.onProbe = null;
assert.equal(b.listed(), true); assert.equal(f.state.keys.includes(issued.key), false);
assert.equal(b.bus.credentials.modelRecord('demo/sec-1')?.state, 'revoking');
await b.bus.close(); f.closers.length = 0;
const record = new Credentials({ storeRoot: join(f.configDir, 'secrets', 'agents'), dataRoot: join(f.root, 'data'), modelAgents: ['demo/sec-1'] });
const manager = new ModelKeys({ credentials: record, client: f.client, configDir: f.configDir, baseUrl: f.baseUrl });
await manager.reconcile(); assert.equal(record.modelRecord('demo/sec-1') === null, true); record.close();
});
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 approveTemplateHire(b.bus.broker,b.rolesDir,{name:'sec-new',definition:'aide'});
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');
assert.deepEqual(f.state.calls.slice(calls, calls + 3), ['GET', 'PUT', 'GET']);
});
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(approveTemplateHire(b.bus.broker,b.rolesDir,{name:'sec-new',definition:'aide'}), 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 => {
const f = await fixture(t); await f.manager.reconcile(); await f.manager.ensure('demo/coder');
f.state.keys = f.state.keys.filter(k => k !== f.sentinel);
await assert.rejects(f.manager.preflight('demo/coder'), failCode('proxy-open')); assert.equal(f.state.probes, 0);
});
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 approveTemplateHire(b.bus.broker,b.rolesDir,{name:'sec-new',definition:'aide'});
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);
});
test('first launch issues implicit key and performs exactly one successful models probe', async t => {
const f = await fixture(t); await f.manager.reconcile();
const answer = await f.manager.preflight('demo/coder');
assert.equal(f.state.probes, 1); assert.equal(f.state.keys.includes(answer.key), true);
});
test('steady boot reconcile reads management list once and writes nothing', async t => {
const f = await fixture(t); await f.manager.reconcile(); assert.deepEqual(f.state.calls, ['GET']);
});
test('thirty-day rotation due automatically replaces and retires an unused old key', async t => {
const f = await fixture(t); await f.manager.reconcile(); const first = await f.manager.ensure('demo/coder');
f.advance(30 * 86400_000); await f.manager.sweep();
const current = await f.manager.ensure('demo/coder'); assert.equal(current.key === first.key, false);
assert.equal(f.state.keys.includes(first.key), false); assert.equal(f.state.keys.includes(current.key), true);
});
test('org registration preserves a running sibling and fences admission on uncertainty', async t => {
const f = await fixture(t);
const { createProxyLock } = await import('../src/proxy-lock.mjs');
const stateDir = join(f.root, 'proxy'); fs.mkdirSync(stateDir, { mode: 0o700 });
const lock = createProxyLock({ stateDir, identity: f.baseUrl });
const betaRoot = join(f.root, 'beta-secrets'); fs.mkdirSync(betaRoot, { mode: 0o700 });
const betaCredentials = new Credentials({ storeRoot: betaRoot, dataRoot: join(f.root, 'beta-data'), modelAgents: ['demo/other'] });
t.after(() => betaCredentials.close());
const orgManager = (org, credentials) => new ModelKeys({ credentials, client: f.client, configDir: f.configDir,
baseUrl: f.baseUrl, org, lock });
const alpha = orgManager('alpha', f.credentials), beta = orgManager('beta', betaCredentials);
await alpha.reconcile();
const first = await alpha.ensure('demo/coder');
await beta.reconcile();
assert.equal(await f.client.probeKey(first.key), 200);
assert.ok(first.key.startsWith(`mso-${hash(f.configDir)}-alpha-`));
assert.equal(f.state.keys.filter(k => k.startsWith(`mso-${hash(f.configDir)}-beta-sentinel-`)).length, 1);
await beta.reconcile();
assert.equal(f.state.keys.filter(k => k.startsWith(`mso-${hash(f.configDir)}-beta-sentinel-`)).length, 1);
await lock.run(async () => lock.fence({ org: 'beta', op: 'test' }));
assert.throws(() => alpha.assertLaunchable('demo/coder'), failCode('proxy-uncertain'));
await assert.rejects(beta.ensure('demo/other'), failCode('proxy-uncertain'));
});
test('synthetic trusted org boot registers an org sentinel and issues an org key', async t => {
const f = await fixture(t);
fs.mkdirSync(join(f.root, 'proxy'), { mode: 0o700 });
const b = await bootBusiness(f, { org: { id: 'demo' }, instanceDataRoot: f.root, instanceConfigDir: f.configDir });
const answer = await b.bus.modelPreflight({ business: 'demo', role: 'sec-1' });
assert.ok(answer.key.startsWith(`mso-${hash(f.configDir)}-demo-`));
assert.equal(f.state.keys.filter(key => key.startsWith(`mso-${hash(f.configDir)}-demo-sentinel-`)).length, 1);
const { createProxyLock } = await import('../src/proxy-lock.mjs');
const lock = createProxyLock({ stateDir: join(f.root, 'proxy'), identity: `${f.configDir}:${new URL(f.baseUrl).port}` });
await lock.run(async () => lock.fence({ org: 'demo', op: 'fixture' }));
await assert.rejects(b.bus.modelPreflight({ business: 'demo', role: 'sec-1' }), failCode('proxy-uncertain'));
});
test('beta bus registers while alpha bus runs and alpha key stays accepted', async t => {
const f = await fixture(t); fs.mkdirSync(join(f.root, 'proxy'), { mode: 0o700 });
const binding = { instanceDataRoot: f.root, instanceConfigDir: f.configDir };
const alpha = await bootBusiness(f, { ...binding, org: { id: 'alpha' } });
const key = (await alpha.bus.modelPreflight({ business: 'demo', role: 'sec-1' })).key;
const doc = businessDoc(f.root, 'beta'); doc.plugins = { ...doc.plugins, harness: ['pi'] };
writeJson(join(f.configDir, 'businesses', 'beta.json'), doc);
const business = loadBusiness('beta', { dir: f.configDir, rolesDir: alpha.rolesDir }), resolved = {};
for (const instance of Object.keys(business.roles)) resolved[instance] = resolveInstance({ system: alpha.system, business, instance });
const betaRoot = join(f.root, 'beta-bus'); fs.mkdirSync(betaRoot, { mode: 0o700 }); new Registry(betaRoot);
const beta = await startBroker({ dataRoot: betaRoot, businesses: { beta: busOrganization(business, resolved) },
organization: { dir: f.configDir, rolesDir: alpha.rolesDir, system: alpha.system },
proxy: { port: Number(new URL(f.baseUrl).port), binary: '/fixture-unused' }, ...binding, org: { id: 'beta' } });
f.closers.push(() => beta.close());
assert.equal(await f.client.probeKey(key), 200);
assert.ok(f.state.keys.some(value => value.startsWith(`mso-${hash(f.configDir)}-beta-sentinel-`)));
assert.equal((await alpha.bus.modelPreflight({ business: 'demo', role: 'sec-1' })).key, key);
});
for(const level of ['root','project'])test(`A4 ${level} fire removes its implicit model key before publication`,async t=>{
const f=await fixture(t),b=await bootBusiness(f,{copyLevel:level});
const instance=level==='root'?'aide':'aide.stack';
const issued=await b.bus.modelPreflight({business:'demo',role:instance});
assert.ok(issued.key);
await b.bus.broker.request(ownerConsole(b.bus.broker),{verb:'hire.start.console',args:{op:'fire',level,...(level==='project'?{project:'stack'}:{}),name:'aide',reason:'Copy work finished'}});
assert.equal(f.state.keys.includes(issued.key),false,'remote generation is revoked');
assert.equal(b.bus.credentials.modelRecord('demo/'+instance),null,'private generation is removed');
const human=b.bus.broker.bindHuman({business:'demo',human:'jason',via:'cli',outsideAgent:true});
assert.throws(()=>b.bus.broker.request(human,{verb:'instance.launch',args:{instance}}));
});