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'); });