Files
stack/packages/bus/tests/project-runtime.test.mjs

89 lines
6.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { startBroker } from '../src/runtime.mjs';
import { Client } from '../src/client.mjs';
import { startTimeOf } from '../../seat/src/proc.mjs';
import { initProject } from '../../project/src/index.mjs';
import { fixture } from './spec-fixtures.mjs';
test('Broker keeps the sandbox client import closure: only the listed imports, no lazy project import', () => {
const source = readFileSync(new URL('../src/broker.mjs', import.meta.url), 'utf8');
assert.deepEqual([...source.matchAll(/^import .*;$/gm)].map(m => m[0]), [
"import { autonomyEvent, decisionResolution } from './autonomy-state.mjs';",
"import { autonomyClass, authorityBudgetCovered, checkAutonomy, decisionAutonomy, resolverSubject } from '../../organization/src/autonomy.mjs';",
// Rows330/331: pure verb/refusal vocabulary, never the host management implementation.
"import { ownRails } from '../../organization/src/rails.mjs';",
"import { refusalGuidance } from './refusals.mjs';",
"import { PROXY_PAGE_VERBS, PROXY_PAGE_CODES } from './proxy-page-vocabulary.mjs';",
// Row 210 (#1707): createHash gives host.run events the argv, stdin and output sha256 values.
"import { randomUUID, randomBytes, createHash } from 'node:crypto';",
// Row 331 r5: parsing-only validation, individually mounted; no PIN or host state.
"import { validManagementKey, proxyTiming } from '../../model-proxy/src/validation.mjs';",
// Row 265 (#1770): pure room address parsing, mounted in RUNNER_FILES beside the broker.
"import { parseAddress } from './address.mjs';",
// Row 65 (#1558): bindLaunch checks a run's transcript path is absolute.
"import { isAbsolute, dirname } from 'node:path';",
// Row 191 / sage-1367: the one additional read-only CODE source, with no project/git imports.
"import { initializeControl, processingStatus } from '../../processing-control/src/state.mjs';",
// Row 65: the plug-in name pattern a harness takes. kinds.mjs is in RUNNER_FILES and imports nothing.
"import { NAME as HARNESS_NAME } from '../../plugins/src/kinds.mjs';",
"import { loadRequirements } from '../../tasks/src/requirements.mjs';",
// Row 137 (#1631): land-work's checks, in RUNNER_FILES beside the broker. They import nothing.
"import { MERGE, LandingRefusal, readLanding } from './landing.mjs';",
// Row 141 (#1635): the business ID_PATTERN for instance names. The vocabulary is already in
// RUNNER_FILES and imports nothing. Row 155 (#1649) reads THREAD_DEFINITION and END_PROTECTED
// from it too. Row 199 (#1697) reads the D9c fork parse, forkProblem and forkUrl, from it.
// Row 210 (#1707) reads credentialKind and the D6 argv check, validArgv and commandAllowed, from it.
// Row 234 (#1740) reads HOST_ID for the hosts verbs.
// Row 213 (#1714) reads CREATIVE_DEFINITIONS for the creative raise refusal (M6). Row 194
// (#1692, D2.4) reads unhireableAction, the hire veto's action name.
// Row 236 (#1742, #436b) reads giteaName and parseGiteaUrl for the Gitea grant path.
// FED R3 r5 (row 274, #414) reads covers, a user's grant check, from it.
// Row 219 reads THREAD_DEFINITIONS from the same vocabulary for creative thread admission.
"import { pluginName, ID_PATTERN, THREAD_DEFINITIONS, END_PROTECTED, forkProblem, forkUrl, credentialKind, validArgv, commandAllowed, HOST_ID, CREATIVE_DEFINITIONS, giteaName, parseGiteaUrl, covers, grantScopeOf, grantInScope } from '../../organization/src/vocabulary.mjs';",
]);
assert.doesNotMatch(source, /\bimport\s*\(/);
assert.doesNotMatch(readFileSync(new URL('../src/proxy-page-vocabulary.mjs', import.meta.url), 'utf8'), /\bimport\b/);
assert.doesNotMatch(readFileSync(new URL('../src/autonomy-state.mjs', import.meta.url), 'utf8'), /\bimport\b/);
assert.doesNotMatch(readFileSync(new URL('../../organization/src/autonomy.mjs', import.meta.url), 'utf8'), /\bimport\b/);
assert.doesNotMatch(readFileSync(new URL('../../organization/src/vocabulary.mjs', import.meta.url), 'utf8'), /\bimport\b/);
assert.doesNotMatch(readFileSync(new URL('../../plugins/src/kinds.mjs', import.meta.url), 'utf8'), /\bimport\b/);
});
test('without a trusted project adapter all three async paths refuse project-not-configured', async t => {
const f = await fixture(t, { adapter: false });
for (const [verb, args] of [
['spec.read', { project: 'stack' }],
['record.write', { project: 'stack', path: 'docs/a.md', record: '# A' }],
['decision.raise', { project: 'stack', spec_path: 'spec/north-star.md', spec_commit: f.commit }],
]) await assert.rejects(f.call(f.coder, verb, args), { code: 'project-not-configured' });
assert.equal(f.store.get('SELECT count(*) n FROM decisions').n, 0);
});
test('real startBroker injects the project adapter for socket reads and bound-author record writes', async t => {
const root = mkdtempSync(join(tmpdir(), 'project-runtime-'));
let runtime;
t.after(async () => { await runtime?.close(); rmSync(root, { recursive: true, force: true }); });
// Row 65: the bound run's harness is switched on.
const business = { id: 'demo', human: 'jason', plugins: { harness: ['pi'] }, projects: { stack: { root: join(root, 'code') } },
arbiters: { technical: 'chief', delivery: 'chief' }, roles: {chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery'], crossRole: [] } }, coder: {
definition: 'coder', authority: { withinRole: ['record.write'], crossRole: [] },
} } };
const initialized = await initProject({ business, project: 'stack', dataRoot: root });
runtime = await startBroker({ dataRoot: root, businesses: { demo: business }, launches: [{
business: 'demo', role: 'coder', run: 'runtime-run', harness: 'pi',
pid: process.pid, startTime: startTimeOf(process.pid),
}] });
const client = new Client({ path: runtime.path, cap: runtime.launches[0].cap });
await client.call('role.claim');
const spec = await client.call('spec.read', { project: 'stack' });
assert.equal(spec.commit, initialized.commit);
assert.equal(spec.confirmed, true);
const record = await client.call('record.write', { project: 'stack', path: 'docs/runtime.md', record: '# Runtime\n' });
assert.match(record.commit, /^[0-9a-f]{40}$/);
assert.equal(readFileSync(join(initialized.repo, record.path), 'utf8'), '# Runtime\n');
});