Files
stack/packages/bus/tests/project-runtime.test.mjs
jason.woltje a8d9eecf65 organization: move packages/business to packages/organization (rename r2 stage B1)
git mv packages/business -> packages/organization (@mosaic/organization). The
old package stays as one-line re-export shims of each moved module, with
src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs
forwarding to the moved executable. BusinessError is OrganizationConfigError
(one constructor, name kept BusinessError); OrganizationError stays distinct.
The bus adapter gains organization-context.mjs (busOrganization), with
business.mjs (busBusiness) as the same function.

Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh
COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure
walker (organization-imports.mjs, both roots required) move to the new path.
v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage
A contract tests stay where they were and pass as before.
2026-11-09 11:47:48 -06:00

84 lines
5.8 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]), [
// Rows330/331: pure verb/refusal vocabulary, never the host management implementation.
"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 { ID_PATTERN, THREAD_DEFINITIONS, END_PROTECTED, forkProblem, forkUrl, credentialKind, validArgv, commandAllowed, HOST_ID, CREATIVE_DEFINITIONS, unhireableAction, 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/landing.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: 'coder', delivery: 'coder' }, roles: { 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');
});