Files
stack/packages/bus/tests/library.test.mjs

124 lines
9.5 KiB
JavaScript

// C2-04 M4 and real async transport, with made-up local library data.
import test from 'node:test';
import assert from 'node:assert/strict';
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Store } from '../src/store.mjs';
import { Broker } from '../src/broker.mjs';
import { serve } from '../src/server.mjs';
import { Client } from '../src/client.mjs';
import { startBroker } from '../src/runtime.mjs';
import { mappingEntry, repo, git, BASE, MARKER } from '../../library/tests/helpers.mjs';
import * as library from '../../library/src/index.mjs';
function world(t, adapter = {}) {
const root = mkdtempSync(join(tmpdir(), 'bus-library-')), store = new Store(root);
t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); });
const business = { id: 'acme', human: 'jason', vars: { 'bus.tester': true },
projects: { books: mappingEntry() }, arbiters: { technical: 'chief', delivery: 'chief' }, roles: {chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery'], crossRole: [] } }, coder: { authority: { withinRole: [], crossRole: [] } } } };
const broker = new Broker({ store, businesses: { acme: business }, library: adapter });
const human = broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true });
const agent = broker.bindLaunch({ business: 'acme', role: 'coder', run: 'library-agent', harness: 'pi' });
const reader = broker.bindReader({ business: 'acme' }), tester = broker.bindTester({ business: 'acme' });
const call = (cap, verb, args = {}) => Promise.resolve().then(() => broker.request(cap, { verb, args }));
return { root, store, broker, business, human, agent, reader, tester, call };
}
test('C2-04 M4: both verbs reject every agent before arguments or adapter access', async t => {
let touched = 0;
const f = world(t, { readFile: () => { touched++; }, status: () => { touched++; } });
for (const verb of ['library.file', 'library.status']) {
await assert.rejects(f.call(f.agent, verb, { unknown: true }), { code: 'human-required' });
await assert.rejects(f.call(f.tester, verb, { unknown: true }), { code: 'human-required' });
await assert.rejects(f.call(f.reader, verb, { unknown: true }), { code: 'read-only' });
}
assert.equal(touched, 0);
const refusals = f.store.all("SELECT body FROM events WHERE kind='action.refused'").map(row => JSON.parse(row.body));
assert.equal(refusals.length, 6);
assert.equal(JSON.stringify(refusals).includes('unknown'), false);
const absent = world(t, null);
for (const verb of ['library.file', 'library.status'])
await assert.rejects(absent.call(absent.agent, verb, {}), { code: 'human-required' });
});
test('C2-04 bus: human reads validate keys and return adapter results without path leakage', async t => {
const seen = [];
const f = world(t, { readFile: async args => { seen.push(args); return { project: 'books', path: 'Book One/A.md', commit: 'a'.repeat(40), content: '# Made up\n' }; },
status: async args => { seen.push(args); return { project: 'books', served: null, problems: [] }; }, LibraryError: library.LibraryError });
assert.equal((await f.call(f.human, 'library.file', { project: 'books', path: 'Book One/A.md' })).content, '# Made up\n');
assert.equal((await f.call(f.human, 'library.status', { project: 'books' })).served, null);
assert.equal(seen[0].business.id, 'acme'); assert.equal(seen[1].project, 'books');
for (const [verb, args] of [['library.file', { project: 'books' }], ['library.file', { project: 'books', path: 'Book One/A.md', commit: 'a'.repeat(40) }], ['library.status', { project: 'books', extra: 1 }]])
await assert.rejects(f.call(f.human, verb, args), { code: 'invalid-request' });
});
test('C2-04 socket: each async library verb resolves its result rather than serializing {}', async t => {
const f = world(t, { readFile: async () => ({ project: 'books', path: 'Book One/A.md', commit: 'a'.repeat(40), content: '# Fixture\n' }),
status: async () => ({ project: 'books', served: null, problems: [] }), LibraryError: library.LibraryError });
const path = join(f.root, 'bus.sock');
const server = await serve({ broker: f.broker, path, authenticateHuman: () => f.broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true }) });
t.after(() => server.close());
const human = new Client({ path, human: { fixture: true } });
assert.deepEqual(await human.call('library.status', { project: 'books' }), { project: 'books', served: null, problems: [] });
assert.equal((await human.call('library.file', { project: 'books', path: 'Book One/A.md' })).content, '# Fixture\n');
});
test('C2-04 runtime: protectedRoots skips library but retains real code-root credential protection', async t => {
const f = repo(t), dataRoot = join(f.root, 'data'), token = join(f.root, 'fixture-token');
mkdirSync(dataRoot, { mode: 0o700 }); writeFileSync(token, 'made-up-fixture-token-123456', { mode: 0o600 });
const business = { id: 'acme', human: 'jason', projects: { books: mappingEntry(), stack: { root: f.repo } },
arbiters: { technical: 'chief', delivery: 'chief' }, roles: {chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery'], crossRole: [] } }, coder: { authority: { withinRole: [], crossRole: [] }, credentials: { gitea: { file: token, rotateBy: '2099-01-01' } } } } };
const runtime = await startBroker({ dataRoot, businesses: { acme: business } });
await runtime.close();
const mirror = library.mirrorPath({ business, project: 'books', dataRoot });
assert.equal(git(mirror, 'rev-parse', '--is-bare-repository'), 'true');
const bad = join(f.repo, 'credential'); writeFileSync(bad, 'made-up-fixture-token-123456', { mode: 0o600 });
business.roles.coder.credentials.gitea.file = bad;
await assert.rejects(startBroker({ dataRoot, businesses: { acme: business } }));
});
test('C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket', async t => {
const f = repo(t), dataRoot = join(f.root, 'data');
mkdirSync(dataRoot, { mode: 0o700 });
const business = { id: 'acme', human: 'jason', projects: { books: mappingEntry() }, arbiters: { technical: 'chief', delivery: 'chief' }, roles: {chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery'], crossRole: [] } }, coder: { authority: { withinRole: [], crossRole: [] } } } };
const mirror = join(dataRoot, 'projects/acme/books'); mkdirSync(mirror, { recursive: true });
writeFileSync(join(mirror, 'keep'), 'made-up note');
await assert.rejects(startBroker({ dataRoot, businesses: { acme: business } }), { code: 'library-git' });
assert.equal(existsSync(join(dataRoot, 'bus.sock')), false);
assert.equal(readFileSync(join(mirror, 'keep'), 'utf8'), 'made-up note');
});
test('C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive', { timeout: 15000 }, async t => {
const f = repo(t), dataRoot = join(f.root, 'data');
mkdirSync(dataRoot, { mode: 0o700 });
const business = { id: 'acme', human: 'jason', projects: { books: mappingEntry() }, arbiters: { technical: 'chief', delivery: 'chief' }, roles: {chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery'], crossRole: [] } }, coder: { authority: { withinRole: [], crossRole: [] } } } };
const runtime = await startBroker({ dataRoot, businesses: { acme: business } });
t.after(() => runtime.close());
const cap = runtime.broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true });
const path = join(f.root, 'human-fixture.sock');
const server = await serve({ broker: runtime.broker, path, authenticateHuman: () => runtime.broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true }) }); t.after(() => server.close());
const human = new Client({ path, human: { fixture: true } });
assert.equal((await human.call('library.status', { project: 'books' })).served, null);
const mirror = library.mirrorPath({ business, project: 'books', dataRoot });
git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', mirror, 'HEAD:refs/heads/served');
writeFileSync(join(mirror, library.SYNC_STATE_FILE), JSON.stringify({ version: 1, branch: 'main', served: f.commit,
lastFetch: null, lastError: null, diverged: null, problems: [] }), { mode: 0o600 });
const file = await human.call('library.file', { project: 'books', path: 'Book One/Characters/Tomas.md' });
assert.equal(file.content, BASE['Book One/Characters/Tomas.md']);
assert.equal(file.commit, f.commit);
let release, done = false;
const hold = library.withLibraryLock(mirror, () => new Promise(r => { release = r; }));
const deadline = Date.now() + 5000;
while (!release && Date.now() < deadline) await new Promise(r => setTimeout(r, 5));
assert.equal(typeof release, 'function');
t.after(async () => { release(); await hold; });
const pending = human.call('library.status', { project: 'books' }).then(result => { done = true; return result; });
const ordinary = await Promise.race([human.call('agents'), new Promise((_, reject) => {
const timer = setTimeout(() => reject(Error('ordinary bus read blocked by lock acquisition')), 1000); timer.unref();
})]);
assert.ok(ordinary && typeof ordinary === 'object'); assert.equal(done, false);
release(); await hold;
assert.equal((await pending).served, f.commit);
});