// Row 194 (#1692, D1.1, V2-V5, D4.1): the broker half of the lifecycle verbs. Launch, regenerate // and delete are requests the launcher's pass takes through the host ops. Terminate revokes and // cancels at the verb and names the runs the pass signals. The launcher half is in cli lifecycle.test.mjs. import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { Store } from '../src/store.mjs'; import { Broker, LIFECYCLE_REQUEST_MS, TESTER_VERBS } from '../src/broker.mjs'; const shipped = (name) => JSON.parse(readFileSync(fileURLToPath(new URL(`../../../roles/${name}.json`, import.meta.url)), 'utf8')); const none = { authority: { withinRole: ['message.send'], crossRole: [] } }; const R = 'a'.repeat(64); const doc = (extra = {}) => ({ id: 'demo', human: 'jason', arbiters: { technical: 'pm', delivery: 'chief' }, vars: { 'bus.tester': true }, definitions: { 'chief-of-staff': {}, assistant: { messaging: shipped('assistant').messaging } }, roles: { chief: { definition: 'chief-of-staff', authority: shipped('chief-of-staff').authority }, pm: { definition: 'pm', ...none }, coder: { definition: 'coder', ...none }, assistant: { definition: 'assistant', ...none }, }, retired: ['old'], ...extra, }); const OTHER = { id: 'other', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' }, roles: {chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery'], crossRole: [] } }, 'only-there': none } }; function setup(t, { organization = null, testerOn = undefined, processing = undefined } = {}) { const root = mkdtempSync(join(tmpdir(), 'lifecycle-verbs-')); const store = new Store(root); t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); }); const tester = { on: true }; const b = new Broker({ store, businesses: { demo: doc(), other: OTHER }, organization, testerOn: testerOn === undefined ? () => tester.on : testerOn, ...(processing ? { processing } : {}) }); const caps = { human: b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }), tester: b.bindTester({ business: 'demo' }), reader: b.bindReader({ business: 'demo' }), }; const call = (who, verb, args = {}) => b.request(caps[who], { verb, args }); const code = (fn) => { try { fn(); } catch (e) { return e.code; } return null; }; const ask = (who, verb, args = {}) => code(() => call(who, verb, args)); let runs = 0; const bind = (role, { claim = true, thread, name = `${role}-${++runs}` } = {}) => { caps[name] = b.bindLaunch({ business: 'demo', role, run: name, harness: 'pi', ...(thread ? { thread } : {}) }); if (claim) call(name, 'role.claim'); return name; }; const lifecycle = () => store.get("SELECT count(*) n FROM events WHERE kind='action.allowed' AND json_extract(body,'$.action') LIKE 'lifecycle.%'").n; const inputs = () => store.get("SELECT count(*) n FROM events WHERE kind='human.input'").n; const take = (request) => b.lifecycleTake({ business: 'demo', request }); const done = (request, extra = {}) => b.lifecycleDone({ business: 'demo', request, ...extra }); const state = (request) => call('human', 'instance.request', { request }); return { b, store, caps, call, code, ask, bind, lifecycle, inputs, take, done, state, tester }; } // Moves the clock past a request's 60 s, for the rest of the test. const later = (t) => { const now = Date.now(); t.mock.method(Date, 'now', () => now + LIFECYCLE_REQUEST_MS + 1000); }; test('row 194 T12: agent and reader sessions refuse the lifecycle verbs before any effect; the tester gets launch, end and the request read, never regenerate', (t) => { const f = setup(t); f.bind('chief'); f.bind('pm'); const verbs = [['instance.launch', { instance: 'coder' }], ['instance.regenerate', { instance: 'coder' }], ['instance.end', { instance: 'coder' }], ['instance.request', { request: 'r1' }]]; for (const [verb, args] of verbs) { assert.equal(f.ask('reader', verb, args), 'read-only', `reader ${verb}`); for (const who of ['chief-1', 'pm-2']) assert.equal(f.ask(who, verb, args), 'human-required', `${who} ${verb}`); } assert.equal(f.ask('tester', 'instance.regenerate', { instance: 'coder' }), 'tester-refused'); assert.equal(f.lifecycle(), 0); for (const verb of ['instance.launch', 'instance.end', 'instance.request']) assert.ok(TESTER_VERBS.includes(verb), verb); assert.equal(TESTER_VERBS.includes('instance.regenerate'), false); // Each human verb passes the D4 hook with its LIFECYCLE_HUMAN_VERBS entry. const { request } = f.call('human', 'instance.launch', { instance: 'coder' }); f.call('human', 'instance.regenerate', { instance: 'pm' }); assert.deepEqual(f.call('human', 'instance.end', { instance: 'coder' }).cancelled, [request]); }); test('row 194 V2 and V4 at the verb: the request is open, by the human, with a human.input; thread, retired, unknown and revoked refuse with no event', (t) => { const f = setup(t); const before = f.inputs(); const { request } = f.call('human', 'instance.launch', { instance: 'coder' }); const r = f.state(request); assert.deepEqual([r.action, r.instance, r.by, r.state, r.done, r.cancelled], ['lifecycle.launch', 'coder', 'jason', 'open', null, null]); assert.equal(f.inputs(), before + 1); const raw = JSON.parse(f.store.get("SELECT body FROM events WHERE json_extract(body,'$.request')=? AND json_extract(body,'$.action')='lifecycle.launch'", request).body); assert.deepEqual(raw, { action: 'lifecycle.launch', request, via: 'cli', by: 'jason' }); const count = f.lifecycle(); assert.equal(f.ask('human', 'instance.launch', { instance: 'assistant' }), 'not-thread-launch'); assert.equal(f.ask('human', 'instance.regenerate', { instance: 'assistant' }), 'regenerate-thread'); assert.equal(f.ask('human', 'instance.launch', { instance: 'old' }), 'instance-retired'); assert.equal(f.ask('human', 'instance.regenerate', { instance: 'old' }), 'instance-retired'); assert.equal(f.ask('human', 'instance.launch', { instance: 'ghost' }), 'unknown-role'); assert.equal(f.ask('human', 'instance.launch', { instance: 'coder', extra: 1 }), 'invalid-request'); assert.equal(f.ask('human', 'instance.launch', { instance: '../x' }), 'invalid-request'); f.call('human', 'launch.revoke'); assert.equal(f.ask('human', 'instance.launch', { instance: 'coder' }), 'launch-revoked'); assert.equal(f.ask('human', 'instance.regenerate', { instance: 'coder' }), 'launch-revoked'); assert.equal(f.lifecycle(), count); assert.equal(f.ask('human', 'instance.request', { request: 'no-such' }), 'unknown-request'); }); test('row 194 D1.1 take: a request is taken once, closes once, and lifecycleRequests lists only what is not closed', (t) => { const f = setup(t); const { request } = f.call('human', 'instance.launch', { instance: 'coder' }); const second = f.call('human', 'instance.regenerate', { instance: 'pm' }).request; assert.deepEqual(f.b.lifecycleRequests('demo').map((r) => [r.request, r.state]), [[request, 'open'], [second, 'open']]); assert.equal(f.code(() => f.done(request, { run: 'run-x' })), 'request-not-taken'); const go = f.take(request); assert.deepEqual([go.go, go.refusal, go.state, go.action, go.instance, go.by], [true, null, 'taken', 'lifecycle.launch', 'coder', 'jason']); const again = f.take(request); assert.deepEqual([again.go, again.refusal, again.state], [false, 'request-closed', 'taken']); assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.action')='lifecycle.taken'").n, 1); assert.equal(f.code(() => f.done(request, { refusal: 'Not A Code' })), 'invalid-request'); assert.equal(f.code(() => f.done(request, { run: '../x' })), 'invalid-request'); assert.equal(f.code(() => f.done(request, { extra: 1 })), 'invalid-request'); const closed = f.done(request, { run: 'run-x' }); assert.deepEqual([closed.state, closed.done], ['closed', { run: 'run-x', transaction: null, refusal: null }]); assert.equal(f.code(() => f.done(request, { run: 'run-x' })), 'request-closed'); assert.deepEqual(f.take(request).refusal, 'request-closed'); assert.deepEqual(f.b.lifecycleRequests('demo').map((r) => r.request), [second]); assert.equal(f.code(() => f.take('no-such')), 'unknown-request'); assert.equal(f.code(() => f.b.lifecycleRequests('ghost')), 'unknown-business'); // A request from one business isn't another's. assert.equal(f.code(() => f.b.lifecycleTake({ business: 'other', request: second })), 'unknown-request'); }); test('row 194 D1.1 take: a launch.revoke after the request closes it launch-revoked, and a request past 60 s closes request-expired', (t) => { const f = setup(t); const revoked = f.call('human', 'instance.launch', { instance: 'coder' }).request; const regen = f.call('human', 'instance.regenerate', { instance: 'coder' }).request; f.call('human', 'launch.revoke'); for (const request of [revoked, regen]) { const r = f.take(request); assert.deepEqual([r.go, r.refusal, r.state, r.done.refusal], [false, 'launch-revoked', 'closed', 'launch-revoked']); } assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.action')='lifecycle.taken'").n, 0); f.call('human', 'launch.restore'); const old = f.call('human', 'instance.launch', { instance: 'coder' }).request; later(t); const r = f.take(old); assert.deepEqual([r.go, r.refusal, r.state], [false, 'request-expired', 'closed']); assert.deepEqual(f.b.lifecycleRequests('demo'), []); }); // T15 (D6): row 191's switch, read through the broker's processing reader as the bus reads its file. test('row 194 T15 (bus): while halted, launch and regenerate refuse processing-halted at the verb and at the take, hire and fire refuse, terminate goes; an unreadable switch refuses with its code', (t) => { const generation = '0b6e7f6e-4c1d-4a8e-9a51-6d2f3c4b5a69'; const sw = { state: { version: 1, halted: false, generation } }; const f = setup(t, { processing: { read: () => sw.state } }); const chief = f.bind('chief'); const coder = f.bind('coder'); const launch = f.call('human', 'instance.launch', { instance: 'coder' }).request; const regen = f.call('human', 'instance.regenerate', { instance: 'pm' }).request; const testerLaunch = f.call('tester', 'instance.launch', { instance: 'pm' }).request; sw.state = { version: 1, halted: true, generation }; const count = f.lifecycle(); assert.equal(f.ask('human', 'instance.launch', { instance: 'coder' }), 'processing-halted'); assert.equal(f.ask('human', 'instance.regenerate', { instance: 'coder' }), 'processing-halted'); assert.equal(f.ask('tester', 'instance.launch', { instance: 'coder' }), 'processing-halted'); assert.equal(f.lifecycle(), count, 'a refused verb records no request'); for (const request of [launch, regen, testerLaunch]) { const r = f.take(request); assert.deepEqual([r.go, r.refusal, r.state, r.done.refusal], [false, 'processing-halted', 'closed', 'processing-halted'], request); } // Row 191's work table refuses the chief's hire and fire before anything runs. assert.equal(f.ask(chief, 'instance.hire', { name: 'helper', definition: 'coder', reason: 'more hands' }), 'processing-halted'); assert.equal(f.ask(chief, 'instance.fire', { name: 'coder', reason: 'done' }), 'processing-halted'); // Terminate is allowed while halted: it revokes, and its end goes at the take. const end = f.call('human', 'instance.end', { instance: 'coder' }); assert.deepEqual(end.runs, [coder]); assert.equal(f.take(end.request).go, true); sw.state = { halted: true, invalid: true, code: 'processing-control-invalid' }; assert.equal(f.ask('human', 'instance.launch', { instance: 'pm' }), 'processing-control-invalid'); sw.state = { version: 1, halted: false, generation }; const after = f.call('human', 'instance.launch', { instance: 'coder' }).request; assert.equal(f.take(after).go, true); }); test('row 194 T12a: the tester launches and ends on its own business with actor tester; bus.tester off in the file refuses the call and the take tester-off', (t) => { const f = setup(t); const inputs = f.inputs(); const { request } = f.call('tester', 'instance.launch', { instance: 'coder' }); const r = f.call('tester', 'instance.request', { request }); assert.deepEqual([r.by, r.state], ['tester', 'open']); assert.equal(JSON.parse(f.store.get("SELECT body FROM events WHERE json_extract(body,'$.request')=? AND json_extract(body,'$.action')='lifecycle.launch'", request).body).via, 'tester'); assert.equal(f.inputs(), inputs, 'a tester request is not human input'); const human = f.call('human', 'instance.launch', { instance: 'pm' }).request; // A tester reads only its own requests. assert.equal(f.ask('tester', 'instance.request', { request: human }), 'unknown-request'); // The cap is bound to demo: an instance only `other` declares is unknown here. assert.equal(f.ask('tester', 'instance.launch', { instance: 'only-there' }), 'unknown-role'); // bus.tester switched off in the file, with no restart. f.tester.on = false; assert.equal(f.ask('tester', 'instance.launch', { instance: 'coder' }), 'tester-off'); assert.equal(f.ask('tester', 'instance.end', { instance: 'coder' }), 'tester-off'); const off = f.take(request); assert.deepEqual([off.go, off.refusal], [false, 'tester-off']); assert.equal(f.take(human).go, true, 'the human request is not the tester switch'); // Anything but exactly true is off. f.tester.on = 'true'; assert.equal(f.ask('tester', 'instance.launch', { instance: 'coder' }), 'tester-off'); f.tester.on = true; const later2 = f.call('tester', 'instance.launch', { instance: 'coder' }).request; const ended = f.call('tester', 'instance.end', { instance: 'coder' }); assert.deepEqual(ended.cancelled, [later2]); assert.equal(f.state(ended.request).by, 'tester'); assert.equal(f.state(later2).cancelled.by, 'tester'); }); test('row 194 V3 terminate: claims revoked, open requests cancelled, bound runs named, taken requests listed; nothing at all answers not-held', (t) => { const f = setup(t); const held = f.bind('coder'); const unclaimed = f.bind('coder', { claim: false }); const open = f.call('human', 'instance.launch', { instance: 'coder' }).request; const taken = f.call('human', 'instance.regenerate', { instance: 'coder' }).request; f.take(taken); const other = f.call('human', 'instance.launch', { instance: 'pm' }).request; const out = f.call('human', 'instance.end', { instance: 'coder', reason: 'stuck on a loop' }); assert.deepEqual([out.runs, out.cancelled, out.taken], [[held, unclaimed], [open], [taken]]); const claim = f.store.get("SELECT * FROM role_claims WHERE role='coder' ORDER BY seq DESC LIMIT 1"); assert.deepEqual([claim.op, claim.holder_run, claim.by, claim.reason], ['revoke', held, 'jason', 'stuck on a loop']); assert.deepEqual([f.state(open).state, f.state(open).cancelled], ['closed', { by: 'jason' }]); assert.equal(f.state(taken).state, 'taken', 'a taken request is past cancelling'); assert.equal(f.state(other).state, 'open', 'another instance keeps its request'); // The end is a request the pass takes, with the runs to signal. It never expires. const end = f.state(out.request); assert.deepEqual([end.action, end.runs, end.state], ['lifecycle.end', [held, unclaimed], 'open']); later(t); assert.equal(f.take(out.request).go, true); assert.equal(f.take(open).refusal, 'request-closed'); // The runs stay named while bound and not ended. f.done(taken, { run: 'coder-new' }); for (const run of [held, unclaimed]) f.b.endLaunch({ business: 'demo', run, reason: 'signalled' }); assert.equal(f.ask('human', 'instance.end', { instance: 'coder' }), 'not-held'); assert.equal(f.ask('human', 'instance.end', { instance: 'old' }), 'unknown-role'); assert.equal(f.ask('human', 'instance.end', { instance: 'coder', reason: '' }), 'invalid-request'); }); test('row 194 V3 terminate of a thread instance: the human ends every thread, the tester only its own', (t) => { const f = setup(t); const hp = f.call('human', 'project.create', { name: 'human work' }).id; const tp = f.call('tester', 'project.create', { name: 'test work' }).id; const H = f.call('human', 'thread.create', { project: hp, name: 'mine' }).id; const T = f.call('tester', 'thread.create', { project: tp, name: 'probe' }).id; const h = f.bind('assistant', { thread: H }); const tr = f.bind('assistant', { thread: T }); const tester = f.call('tester', 'instance.end', { instance: 'assistant' }); assert.deepEqual(tester.runs, [tr]); assert.equal(f.store.get('SELECT op FROM thread_claims WHERE thread=? ORDER BY seq DESC LIMIT 1', H).op, 'claim'); assert.equal(f.store.get('SELECT op FROM thread_claims WHERE thread=? ORDER BY seq DESC LIMIT 1', T).op, 'revoke'); const human = f.call('human', 'instance.end', { instance: 'assistant' }); assert.deepEqual(human.runs, [h, tr]); assert.equal(f.store.get('SELECT op FROM thread_claims WHERE thread=? ORDER BY seq DESC LIMIT 1', H).op, 'revoke'); }); // The writer stand-in: instances.check answers per instance, instances.set commits a transaction. function writer(answers) { const calls = []; const organization = (id, verb, args, actor) => { calls.push([verb, args, actor]); if (verb === 'instances.check') { const a = answers[args.instance] ?? { revision: R }; if (a instanceof Error) throw a; return a; } if (verb === 'instances.set') return { transaction: `tx-${calls.length}`, restart: false }; return { ok: true }; }; return { calls, organization }; } const refused = (code) => Object.assign(new Error(code), { code }); const lastListed = () => Object.assign(new Error('coder is the last name in launch.instances'), { name: 'BusinessError' }); test('row 194 T6 V5 order at the verb: the writer\'s named refusals, then instance-running, instance-starting, the last-listed refusal and the revision', (t) => { const w = writer({ chief: refused('organization-instance-referenced'), pm: lastListed(), coder: lastListed() }); const f = setup(t, { organization: w.organization }); const del = (instance, revision = R) => f.ask('human', 'instances.set', { revision, instance, op: 'delete' }); // A named refusal comes before the broker's checks, even with a claim held. f.bind('chief'); assert.equal(del('chief'), 'organization-instance-referenced'); // The last-listed refusal waits until running and starting have passed. const run = f.bind('coder'); assert.equal(del('coder'), 'instance-running'); f.call('human', 'instance.end', { instance: 'coder' }); f.b.endLaunch({ business: 'demo', run, reason: 'signalled' }); const starting = f.call('human', 'instance.launch', { instance: 'coder' }).request; assert.equal(del('coder'), 'instance-starting'); f.take(starting); assert.equal(del('coder'), 'instance-starting', 'a taken request with no done is still starting'); f.done(starting, { refusal: 'launch-refused' }); assert.equal(del('coder'), 'invalid-request'); assert.equal(del('pm'), 'invalid-request'); assert.equal(del('assistant', 'b'.repeat(64)), 'revision-conflict'); assert.equal(del('assistant', 'not-a-revision'), 'invalid-request'); assert.equal(f.ask('human', 'instances.set', { revision: R, instance: 'assistant', op: 'delete', definition: 'x' }), 'invalid-request'); assert.ok(w.calls.every(([verb]) => verb === 'instances.check'), 'the checks write nothing'); assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.action')='lifecycle.delete'").n, 0); const { request } = f.call('human', 'instances.set', { revision: R, instance: 'assistant', op: 'delete' }); const r = f.state(request); assert.deepEqual([r.action, r.instance, r.by, r.revision, r.retry, r.state], ['lifecycle.delete', 'assistant', 'jason', R, false, 'open']); assert.deepEqual(w.calls.at(-1), ['instances.check', { instance: 'assistant' }, 'jason']); // An open delete is a starting request too: a second delete waits for the first. assert.equal(del('assistant'), 'instance-starting'); }); test('row 194 V5: an open launch past its 60 s is not starting, as it can only close request-expired', (t) => { const w = writer({}); const f = setup(t, { organization: w.organization }); f.call('human', 'instance.launch', { instance: 'coder' }); later(t); assert.ok(f.call('human', 'instances.set', { revision: R, instance: 'coder', op: 'delete' }).request); }); test('row 194 V5 after the take: lifecycleDelete re-checks running and starting, then commits through the writer with the human as actor', (t) => { const w = writer({}); const f = setup(t, { organization: w.organization }); const { request } = f.call('human', 'instances.set', { revision: R, instance: 'coder', op: 'delete' }); const del = () => f.b.lifecycleDelete({ business: 'demo', request }); assert.equal(f.code(del), 'request-not-taken'); assert.equal(f.take(request).go, true); // A launch recorded after the delete's verb and a claim taken since both stop it. const launch = f.call('human', 'instance.launch', { instance: 'coder' }).request; assert.equal(f.code(del), 'instance-starting'); f.call('human', 'instance.end', { instance: 'coder' }); const run = f.bind('coder'); assert.equal(f.code(del), 'instance-running'); f.call('human', 'instance.end', { instance: 'coder' }); f.b.endLaunch({ business: 'demo', run, reason: 'signalled' }); assert.equal(f.state(launch).cancelled.by, 'jason'); const out = del(); assert.deepEqual(w.calls.at(-1), ['instances.set', { revision: R, instance: 'coder', op: 'delete' }, 'jason']); assert.deepEqual(out, { transaction: out.transaction, retry: false }); assert.match(out.transaction, /^tx-/); assert.equal(f.done(request, { transaction: out.transaction }).done.transaction, out.transaction); const launchReq = f.call('human', 'instance.launch', { instance: 'pm' }).request; assert.equal(f.code(() => f.b.lifecycleDelete({ business: 'demo', request: launchReq })), 'request-not-taken'); }); test('row 194 V5 retry: a retired name deletes again only after workspace-rename-failed, and that request commits nothing', (t) => { const w = writer({}); const f = setup(t, { organization: w.organization }); assert.equal(f.ask('human', 'instances.set', { revision: R, instance: 'old', op: 'delete' }), 'organization-instance-retired'); const { request } = f.call('human', 'instances.set', { revision: R, instance: 'pm', op: 'delete' }); f.take(request); const { transaction } = f.b.lifecycleDelete({ business: 'demo', request }); f.done(request, { transaction, refusal: 'workspace-rename-failed' }); // The bus reloads after the commit, as the runtime does. const next = doc({ retired: ['old', 'pm'], arbiters: { technical: 'chief', delivery: 'chief' } }); delete next.roles.pm; f.b.reloadBusiness('demo', next); const writes = w.calls.length; const retry = f.call('human', 'instances.set', { revision: R, instance: 'pm', op: 'delete' }).request; assert.deepEqual([f.state(retry).retry, f.state(retry).revision], [true, R]); f.take(retry); // S3: the retry answers the failed delete's transaction, which names the rename's target. assert.ok(transaction); assert.deepEqual(f.b.lifecycleDelete({ business: 'demo', request: retry }), { transaction, retry: true }); assert.equal(w.calls.length, writes, 'a retry neither checks nor writes the file'); f.done(retry); assert.equal(f.ask('human', 'instances.set', { revision: R, instance: 'pm', op: 'delete' }), 'organization-instance-retired'); }); test('row 194 V5 runtime: a delete request, the take and lifecycleDelete commit the file with retired, log the human and reload so the name no longer binds', async (t) => { const { startBroker } = await import('../src/runtime.mjs'); const { busOrganization } = await import('../src/organization-context.mjs'); const { loadBusiness } = await import('../../organization/src/organization-config.mjs'); const { resolveInstance } = await import('../../organization/src/resolve.mjs'); const { businessDoc, rolesCopy, systemFor, writeJson } = await import('../../organization/tests/helpers.mjs'); const root = mkdtempSync(join(tmpdir(), 'lifecycle-runtime-')); const rolesDir = rolesCopy(root); const file = writeJson(join(root, 'businesses/demo.json'), { ...businessDoc(root, 'demo'), plugins: { harness: ['pi'] } }); mkdirSync(join(root, 'data'), { mode: 0o700 }); const b = loadBusiness('demo', { dir: root, rolesDir }); const resolved = {}; for (const i of Object.keys(b.roles)) resolved[i] = resolveInstance({ system: systemFor(root), business: b, instance: i }); const bus = await startBroker({ dataRoot: join(root, 'data'), businesses: { demo: busOrganization(b, resolved) }, organization: { dir: root, rolesDir, system: systemFor(root) } }); t.after(async () => { await bus.close(); rmSync(root, { recursive: true, force: true }); }); const human = bus.broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }); const ask = (verb, args) => bus.broker.request(human, { verb, args }); const code = (fn) => { try { fn(); } catch (e) { return e.code; } return null; }; const revision = () => ask('instances').revision; ask('instances.set', { revision: revision(), instance: 'sec-1', op: 'add', definition: 'security-reviewer', launch: true }); const before = readFileSync(file, 'utf8'); // The real writer's refusals reach the verb, and none of them writes. assert.equal(code(() => ask('instances.set', { revision: revision(), instance: 'coder', op: 'delete' })), 'organization-credentials-out-of-scope'); assert.equal(code(() => ask('instances.set', { revision: revision(), instance: 'pm', op: 'delete' })), 'organization-instance-referenced'); assert.equal(code(() => ask('instances.set', { revision: 'b'.repeat(64), instance: 'sec-1', op: 'delete' })), 'revision-conflict'); assert.equal(readFileSync(file, 'utf8'), before); const { request } = ask('instances.set', { revision: revision(), instance: 'sec-1', op: 'delete' }); assert.equal(readFileSync(file, 'utf8'), before, 'the verb only records the request'); assert.deepEqual(bus.lifecycleRequests('demo').map((r) => [r.request, r.action, r.instance]), [[request, 'lifecycle.delete', 'sec-1']]); assert.equal(bus.lifecycleTake({ business: 'demo', request }).go, true); const { transaction } = bus.lifecycleDelete({ business: 'demo', request }); const after = JSON.parse(readFileSync(file, 'utf8')); assert.equal(Object.hasOwn(after.roles, 'sec-1'), false); assert.deepEqual(after.retired, ['sec-1']); assert.equal(after.launch.instances.includes('sec-1'), false); const log = readFileSync(join(root, 'businesses/.history/demo/changes.jsonl'), 'utf8').trim().split('\n').map(JSON.parse).at(-1); assert.deepEqual([log.actor, log.state, log.transaction], ['jason', 'committed', transaction]); assert.equal(bus.lifecycleDone({ business: 'demo', request, transaction }).state, 'closed'); assert.equal(code(() => bus.broker.bindLaunch({ business: 'demo', role: 'sec-1', run: 'run-1', harness: 'pi' })), 'unknown-role'); assert.equal(code(() => ask('instance.launch', { instance: 'sec-1' })), 'instance-retired'); });