Files
stack/packages/bus/tests/request-intake.test.mjs
jason.woltje e6ab8bc1fd feat(bus,plugins,organization,cli,webui): SP9 remove system types outright, bus schema 3v-3w drops project_system_types (#1873)
Jason's 2026-11-12 ruling: the only deployed system is the dev environment on
dragon-lin, so the system-type plug-in kind goes with no retired kind, fence,
conversion or new refusal code.

- plugins: system-type leaves KINDS; system-types.mjs and the five modules are
  deleted. An org file naming the kind refuses unknown-kind.
- organization: the plugins.system-type check, systemTypesFence and the
  system-types.console right are removed.
- bus: system-types.console, project.system-types[.set], the system-type half
  of the tester check, the runtime reads and system-type-not-enabled are
  removed. Step 3v-3w drops project_system_types, its index, its four guards
  and its rows; SCHEMA_VERSION is 3w (32 tables, 126 triggers, 5 views).
- cli: mosaic org system-types and mosaic project system-types[-set] are
  removed; stack-release COUNTED and the budget supervisor's closed list move
  to 3w.
- channel: the project.system-types verbs are removed.
- webui: the System types page, route, dashboard row, chips and project
  selection are removed.
- docs: REQ-SYS-1/2 now describe starter packs; README system-type text and
  the requirements titles follow.
2026-11-12 21:02:46 -06:00

628 lines
50 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync, mkdirSync, symlinkSync, readFileSync, writeFileSync, chmodSync, existsSync } from 'node:fs';
import { DatabaseSync } from 'node:sqlite';
import { spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createHash } from 'node:crypto';
import { Store } from '../src/store.mjs';
import { Broker } from '../src/broker.mjs';
import { startBroker } from '../src/runtime.mjs';
import { Client } from '../src/client.mjs';
const journalModule = await import('../src/request-store.mjs').catch(e => {
if (e.code === 'ERR_MODULE_NOT_FOUND') return {}; throw e;
});
const lifecycleModule = await import('../src/request-lifecycle.mjs').catch(e => {
if (e.code === 'ERR_MODULE_NOT_FOUND') return {}; throw e;
});
const hash = x => createHash('sha256').update(x).digest('hex');
const intent = 'Build a single-file timer.\nKeep these exact bytes.\n';
function setup(t, { prepared = false, authority = true, halted = false, configureHost = () => {}, userText = intent } = {}) {
assert.equal(typeof journalModule.createRequestStore, 'function', 'host intake journal exists');
assert.equal(typeof lifecycleModule.createRequestLifecycle, 'function', 'host lifecycle exists');
const root = mkdtempSync(join(tmpdir(), 'request-intake-'));
let store = new Store(root), lifecycle, journal, cleanupRuntime;
let processingHalted = false;
t.after(async () => { await cleanupRuntime?.close(); store.close(); rmSync(root, { recursive: true, force: true }); });
const business = { id: 'demo', human: 'jason', vars: { 'bus.tester': true }, arbiters: { technical: 'cto', delivery: 'pm' },
roles: Object.fromEntries(['pm', 'cto', 'coder', 'otherpm'].map(key => [key, {
definition: key === 'otherpm' ? 'pm' : key,
authority: { withinRole: authority || key !== 'pm' ? ['message.send'] : [], crossRole: [] },
}])), projects: { factory: { root: join(root, 'factory') }, old: { root: join(root, 'old'), repo: join(root, 'old-code') },
library: { kind: 'library', root: join(root, 'library') }, unknown: { root: join(root, 'unknown') } } };
let revision = 'org-1';
const plans = (state, args, source, existing) => ['autonomous', 'guided'].map(mode => ({
version: 1, business: state.business, request: state.request, project: args.project,
name: args.name, chat_project: null, mode, organization_revision: revision,
source: { project: args.source_project, commit: 'a'.repeat(40), remote: null, forks: [] },
code_root: existing ? business.projects[args.project].root : join(root, 'products/demo', args.project, 'code'),
records_root: existing ? (business.projects[args.project].repo ?? join(root, 'projects/demo', args.project)) : join(root, 'projects/demo', args.project), tracker: { root: 1, project: args.tracker_project },
staff: { pm: state.pm, technical_reviewer: 'cto', coder: 'coder', reviewer: 'cto', chief: 'cto' },
intent: { text: source.text, sha256: hash(source.text), source_message: source.source_message, generated: false },
project_vars: {}, existing,
}));
const host = {
listProjects: () => [
{ business: 'demo', project: 'old', name: 'Old', tracker: { root: 1, project: 2 } },
{ business: 'demo', project: 'library', name: 'Library', tracker: { root: 1, project: 2 } },
{ business: 'foreign', project: 'old', name: 'Foreign', tracker: { root: 1, project: 2 } },
{ business: 'demo', project: 'unknown', name: 'Unknown', tracker: null },
],
...(prepared ? {
preparePlans: (state, args, source) => plans(state, args, source, false),
prepareExisting: (state, args, source) => plans(state, args, source, true)[0],
admitPlan: plan => plan.organization_revision === revision ? { ready: true } : { ready: false, code: 'organization-stale' },
} : {}),
};
configureHost(host);
const factory = services => {
journal = journalModule.createRequestStore(store);
lifecycle = lifecycleModule.createRequestLifecycle({ journal, services: { ...host, ...services } });
return lifecycle;
};
const b = new Broker({ store, businesses: { demo: business }, requestLifecycle: factory,
...(halted ? { processing: { read: () => ({ version: 1, halted: processingHalted, generation: '12345678-1234-4234-8234-123456789abc' }) } } : {}) });
const caps = { human: b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }),
reader: b.bindReader({ business: 'demo' }), tester: b.bindTester({ business: 'demo' }),
console: b.bindConsole({ business: 'demo', human: 'jason', subject: { provider: 'oidc', iss: 'https://id.test', sub: 'jason' } }) };
for (const role of ['pm', 'cto', 'coder', 'otherpm']) {
caps[role] = b.bindLaunch({ business: 'demo', role, run: role + '-run', harness: 'pi' });
b.request(caps[role], { verb: 'role.claim' });
}
const call = (who, verb, args = {}) => b.request(caps[who], { verb, args });
const source = call('human', 'message.send', { to: 'pm', body: userText });
processingHalted = halted;
const propose = state => ({ request: source.request, revision: state.revision, op: 'plan-1',
source_project: 'factory', project: 'timer', name: 'Timer', tracker_project: 2 });
return { root, b, store, journal, lifecycle, host, call, source, propose, business,
runtimeCleanup: runtime => { cleanupRuntime = runtime; },
changeOrganization: () => { revision = 'org-2'; },
reopen: () => { store.close(); store = new Store(root); return journalModule.createRequestStore(store); } };
}
test('opens_without_task_or_requirement', async t => {
const x = setup(t), state = await x.call('pm', 'request.open', { request: x.source.request });
assert.equal(state.request, x.source.request); assert.equal(state.source_message, x.source.id);
assert.equal(state.phase, 'selection'); assert.equal(state.owner, 'jason'); assert.equal(state.project, null);
assert.deepEqual(state.tasks, []); assert.equal(state.snapshot, null);
assert.equal(x.store.get("SELECT count(*) n FROM events WHERE kind='task.created'").n, 0);
assert.equal(x.store.get('SELECT body FROM messages WHERE id=?', x.source.id).body, intent);
});
test('duplicate_open_is_same_request', async t => {
const x = setup(t), args = { request: x.source.request };
const [a, b] = await Promise.all([x.call('pm', 'request.open', args), x.call('pm', 'request.open', args)]);
assert.deepEqual(a, b); assert.equal(x.store.get('SELECT count(*) n FROM request_events').n, 1);
assert.deepEqual(await x.call('human', 'request.get', args), a);
});
test('rejects_foreign_input', async t => {
const x = setup(t);
for (const request of [x.source.id, 'absent']) await assert.rejects(async () => x.call('pm', 'request.open', { request }), /request-origin-refused/);
x.store.run("INSERT INTO events(id,at,business,kind,body) VALUES(?,?,?,?,?)", 'foreign', new Date().toISOString(), 'foreign', 'human.input', JSON.stringify({ kind: 'instruction', message: x.source.id }));
x.store.run("INSERT INTO events(id,at,business,kind,actor_role,actor_run,subject,body) VALUES(?,?,?,?,?,?,?,?)", 'forged', new Date().toISOString(), 'demo', 'human.input', 'coder', 'coder-run', x.source.id, JSON.stringify({ kind: 'instruction', message: x.source.id }));
for (const request of ['foreign', 'forged']) await assert.rejects(async () => x.call('pm', 'request.open', { request }), /request-origin-refused/);
await assert.rejects(async () => x.call('pm', 'request.open', { request: x.source.request, text: 'forged' }), /invalid-request/);
assert.equal(x.store.get('SELECT count(*) n FROM request_events').n, 0);
});
test('non_owner_cannot_select', async t => {
const x = setup(t, { prepared: true });
const opened = await x.call('pm', 'request.open', { request: x.source.request });
const state = await x.call('pm', 'request.propose', x.propose(opened));
for (const who of ['pm', 'cto', 'coder', 'otherpm']) assert.throws(() => x.call(who, 'decision.resolve', { id: state.selection.decision, choice: 'guided' }), /human-required/);
await assert.rejects(async () => x.call('otherpm', 'request.propose', x.propose(state)), /request-access-refused/);
const preview = x.call('console', 'decision.preview.console', { id: state.selection.decision });
assert.equal(preview.kind, 'decision'); assert.deepEqual(preview.options.map(o => o.key), ['autonomous', 'guided', 'existing']);
const d = x.store.get('SELECT * FROM decisions WHERE id=?', state.selection.decision);
assert.equal(d.route_to, 'human'); assert.equal(d.action, 'project.bootstrap'); assert.equal(d.blocking, 0);
assert.equal(d.task_ref, null); assert.equal(d.requirement_ref, null);
const raise = JSON.parse(x.store.get("SELECT body FROM events WHERE json_extract(body,'$.decision')=? AND json_extract(body,'$.operation')='decision.raise'", d.id).body);
assert.equal(raise.approvalChoice, 'autonomous');
});
test('reopen_store_reduces_same_state', async t => {
const x = setup(t, { prepared: true }); const a = await x.call('pm', 'request.open', { request: x.source.request });
const state = await x.call('pm', 'request.propose', x.propose(a));
assert.deepEqual(x.reopen().readRequest('demo', x.source.request), state);
});
test('missing_resources_opens_one_setup_wait', async t => {
const x = setup(t), a = await x.call('pm', 'request.open', { request: x.source.request });
const one = await x.call('pm', 'request.propose', x.propose(a));
assert.equal(one.wait.kind, 'setup'); assert.equal(one.wait.code, 'request-preparation-unavailable');
assert.deepEqual(await x.lifecycle.resume('demo', a.request), one);
assert.deepEqual(await x.lifecycle.recover(), [one]);
assert.equal(x.store.get('SELECT count(*) n FROM decisions').n, 0);
assert.equal(x.store.get("SELECT count(*) n FROM request_events WHERE kind='setup.wait'").n, 1);
});
test('guided_is_a_selection_and_execution_waits_for_later_capability', async t => {
const x = setup(t, { prepared: true }); const a = await x.call('pm', 'request.open', { request: x.source.request });
const state = await x.call('pm', 'request.propose', x.propose(a));
x.call('human', 'decision.resolve', { id: state.selection.decision, choice: 'guided' });
const selected = await x.lifecycle.resume('demo', a.request);
assert.equal(selected.mode, 'guided'); assert.equal(selected.phase, 'bootstrap');
assert.equal(selected.plan_digest, state.selection.bindings.find(b => b.choice === 'guided').digest);
assert.equal(selected.wait.code, 'request-bootstrap-unavailable');
assert.deepEqual(await x.lifecycle.resume('demo', a.request), selected);
assert.equal(x.store.get('SELECT count(*) n FROM request_effects').n, 0);
assert.equal(x.store.get("SELECT count(*) n FROM events WHERE kind='task.created'").n, 0);
});
test('stale_revision_and_changed_plan_refuse_before_confirmation', async t => {
const x = setup(t, { prepared: true }); const a = await x.call('pm', 'request.open', { request: x.source.request });
const state = await x.call('pm', 'request.propose', x.propose(a));
await assert.rejects(async () => x.call('pm', 'request.propose', { ...x.propose(a), op: 'plan-2' }), /request-revision-conflict/);
x.changeOrganization(); x.call('human', 'decision.resolve', { id: state.selection.decision, choice: 'autonomous' });
const stopped = await x.lifecycle.resume('demo', a.request);
assert.equal(stopped.phase, 'selection'); assert.equal(stopped.project, null); assert.equal(stopped.wait.code, 'organization-stale');
});
test('existing_picker_filters_authorized_software_mappings_then_confirms_exact_association', async t => {
const x = setup(t, { prepared: true }); const a = await x.call('pm', 'request.open', { request: x.source.request });
const state = await x.call('pm', 'request.propose', x.propose(a));
assert.deepEqual((await x.call('pm', 'request.projects', { request: a.request })).map(p => p.project), ['old']);
x.call('human', 'decision.resolve', { id: state.selection.decision, choice: 'existing' });
const picker = await x.lifecycle.resume('demo', a.request);
assert.equal(picker.phase, 'selection'); assert.equal(picker.project, null); assert.equal(picker.wait.code, 'request-existing-project');
const candidate = await x.call('pm', 'request.propose', { ...x.propose(picker), op: 'existing-1', source_project: 'old', project: 'old', name: 'Old' });
assert.equal(candidate.project, null); assert.equal(candidate.selection.bindings[0].plan.existing, true);
const d = x.call('console', 'decision.preview.console', { id: candidate.selection.decision });
assert.deepEqual(d.options.map(o => o.key), ['confirm', 'back']);
x.call('console', 'decision.resolve.console', { id: d.id, choice: 'confirm', digest: d.digest });
const bound = await x.lifecycle.resume('demo', a.request);
assert.equal(bound.project, 'old'); assert.equal(bound.snapshot, null); assert.deepEqual(bound.tasks, []);
assert.equal(bound.wait.code, 'request-attachment-unavailable');
});
// A mapping with no `repo` keeps its records repository at the data-root default; the guard must compare
// code_root to `root` and records_root to that default, not the other way round.
test('existing_project_without_a_repo_binds_its_root_and_the_default_records_repository', async t => {
const x = setup(t, { prepared: true });
delete x.business.projects.old.repo;
x.b.reloadBusiness('demo', x.business);
const picker = await reachPicker(x);
const candidate = await x.call('pm', 'request.propose', existingArgs(picker, 'existing-1'));
const [{ plan }] = candidate.selection.bindings;
assert.equal(plan.existing, true);
assert.equal(plan.code_root, x.business.projects.old.root);
assert.equal(plan.records_root, join(x.root, 'projects/demo/old'));
});
// The Task 1 guard compared the two roots crosswise, so a plan that swaps them was the one it accepted.
for (const repo of [true, false]) test(`existing_project_plan_with_swapped_roots_is_refused_${repo ? 'with' : 'without'}_a_repo`, async t => {
const x = setup(t, { prepared: true, configureHost: host => {
const real = host.prepareExisting;
host.prepareExisting = (...a) => { const p = real(...a); return { ...p, code_root: p.records_root, records_root: p.code_root }; };
} });
if (!repo) { delete x.business.projects.old.repo; x.b.reloadBusiness('demo', x.business); }
const picker = await reachPicker(x);
await assert.rejects(async () => x.call('pm', 'request.propose', existingArgs(picker, 'swapped-1')), /request-plan-binding-refused/);
});
test('withdrawal_cancels_without_resources_and_preserves_source', async t => {
const x = setup(t, { prepared: true }); const a = await x.call('pm', 'request.open', { request: x.source.request });
const state = await x.call('pm', 'request.propose', x.propose(a));
x.call('human', 'decision.withdraw', { id: state.selection.decision });
const cancelled = await x.lifecycle.resume('demo', a.request);
assert.equal(cancelled.phase, 'cancelled'); assert.equal(cancelled.project, null);
assert.equal(x.store.get('SELECT body FROM messages WHERE id=?', x.source.id).body, intent);
assert.equal(x.store.get('SELECT count(*) n FROM request_effects').n, 0);
});
test('journal_cas_strict_events_and_append_only_sql_guards', async t => {
const x = setup(t); const state = await x.call('pm', 'request.open', { request: x.source.request });
const event = { business: 'demo', request: state.request, expectedRevision: state.revision, kind: 'setup.wait',
actor: { role: null, run: null }, body: { code: 'resource-missing', subject: state.request } };
x.journal.appendRequest(event);
assert.throws(() => x.journal.appendRequest(event), /request-revision-conflict/);
assert.throws(() => x.journal.appendRequest({ ...event, expectedRevision: 2, body: { ...event.body, state: {} } }), /invalid-request-event/);
for (const sql of ['UPDATE request_events SET kind=kind', 'DELETE FROM request_events',
'INSERT OR REPLACE INTO request_events SELECT * FROM request_events']) assert.throws(() => x.store.run(sql), /append-only/);
});
test('effect_receipts_reserve_digest_kind_and_terminal_result_once', async t => {
const x = setup(t); const state = await x.call('pm', 'request.open', { request: x.source.request });
const r = { version: 1, business: 'demo', request: state.request, op: 'boot-1', kind: 'bootstrap', digest: hash('plan'), stage: 'prepared', result: null, code: null };
assert.deepEqual(x.journal.prepareEffect(r), r); assert.deepEqual(x.journal.prepareEffect(r), r);
assert.throws(() => x.journal.prepareEffect({ ...r, digest: hash('other') }), /request-effect-conflict/);
const done = { ...r, stage: 'committed', result: { project: 'timer' } };
assert.deepEqual(x.journal.finishEffect(done), done); assert.deepEqual(x.journal.finishEffect(done), done);
assert.deepEqual(x.journal.prepareEffect(r), done, 'retry receives the committed evidence, never a fresh execution reservation');
assert.throws(() => x.journal.finishEffect({ ...done, result: { project: 'other' } }), /request-effect-conflict/);
assert.throws(() => x.journal.finishEffect({ ...done, op: 'unprepared' }), /request-effect-unprepared/);
for (const sql of ['UPDATE request_effects SET kind=kind', 'DELETE FROM request_effects',
'INSERT OR REPLACE INTO request_effects SELECT * FROM request_effects']) assert.throws(() => x.store.run(sql), /append-only/);
});
test('request_verbs_retain_role_grant_processing_and_origin_visibility_gates', async t => {
const x = setup(t, { authority: false });
await assert.rejects(async () => x.call('pm', 'request.open', { request: x.source.request }), /decision-required/);
for (const who of ['reader','tester','coder','cto','otherpm']) await assert.rejects(async () => x.call(who, 'request.open', { request: x.source.request }), /read-only|tester-refused|request-access-refused|request-origin-refused/);
assert.equal(x.store.get('SELECT count(*) n FROM request_events').n, 0);
const halted = setup(t, { halted: true });
await assert.rejects(async () => halted.call('pm', 'request.open', { request: halted.source.request }), /processing-halted/);
assert.equal(halted.store.get('SELECT count(*) n FROM request_events').n, 0);
});
test('proposal_is_idempotent_but_op_revision_plan_and_resource_bindings_are_strict', async t => {
const x = setup(t, { prepared: true }); const a = await x.call('pm', 'request.open', { request: x.source.request });
const args = x.propose(a), state = await x.call('pm', 'request.propose', args);
assert.deepEqual(await x.call('pm', 'request.propose', args), state);
await assert.rejects(async () => x.call('pm', 'request.propose', { ...args, name: 'Other' }), /request-operation-conflict/);
assert.equal(x.store.get('SELECT count(*) n FROM decisions').n, 1);
const proposed = JSON.parse(x.store.get("SELECT body FROM request_events WHERE kind='selection.proposed'").body);
const plan = proposed.bindings[0].plan;
const bad = structuredClone(plan); bad.intent.text = 'Forged original'; bad.intent.sha256 = hash(bad.intent.text);
assert.throws(() => x.journal.appendRequest({ business: 'demo', request: a.request, expectedRevision: state.revision,
kind: 'selection.proposed', actor: { role: 'pm', run: 'pm-run' }, body: { ...proposed,
bindings: [{ ...proposed.bindings[0], plan: bad, digest: journalModule.digest(bad) }, proposed.bindings[1]] } }), /invalid-request-event/);
});
test('plan_preparation_rejects_unknown_keys_changed_intent_tracker_modes_and_symlinks', async t => {
for (const fault of ['extra','intent','tracker','mode','symlink','partial','overlap']) {
const x = setup(t, { prepared: true }); const a = await x.call('pm', 'request.open', { request: x.source.request });
const original = x.host.preparePlans;
x.host.preparePlans = (state, args, source) => {
const plans = original(state, args, source);
if (fault === 'extra') plans[0].authority = 'all';
if (fault === 'intent') { plans[0].intent.text = 'forged'; plans[0].intent.sha256 = hash('forged'); }
if (fault === 'tracker') plans[0].tracker.project = 99;
if (fault === 'mode') plans[1].source.commit = 'b'.repeat(40);
return plans;
};
// The factory takes a host snapshot; construct a second controller for these adversarial hooks.
const fakeIdentity = { business: 'demo', role: 'pm', run: 'pm-run' };
const controller = lifecycleModule.createRequestLifecycle({ journal: x.journal, services: { ...x.host, dataRoot: x.root,
origin: () => ({ owner: 'jason', pm: 'pm', source_message: x.source.id, thread: null, session: null, text: intent }),
authorize: () => {}, project: (_b, p) => x.business.projects[p]?.kind === 'library' ? null : x.business.projects[p],
projectPaths: () => Object.values(x.business.projects).flatMap(p => [p.root, p.repo]).filter(Boolean),
raiseDecision: () => assert.fail('invalid plan must not raise'), readDecision: () => null } });
if (fault === 'symlink') symlinkSync(x.root, join(x.root, 'products'));
if (fault === 'partial') { const parent = join(x.root, 'products/demo/timer'); mkdirSync(parent, { recursive: true }); writeFileSync(join(parent, 'unrelated.txt'), 'unrelated'); }
if (fault === 'overlap') x.business.projects.other = { root: join(x.root, 'products/demo/timer') };
await assert.rejects(() => controller.propose(fakeIdentity, x.propose(a)), /invalid-request-plan|request-plan-binding-refused|request-plan-path-collision/, fault);
assert.equal(x.store.get('SELECT count(*) n FROM decisions').n, 0, fault);
}
});
test('3n_to_3o_is_explicit_offline_preserves_rows_and_old_engine_refuses_new_schema', t => {
const root = mkdtempSync(join(tmpdir(), 'request-migration-'));
t.after(() => rmSync(root, { recursive: true, force: true })); mkdirSync(join(root, 'bus'), { mode: 0o700 });
// Tasks 2, 3, 4 and 6b (3o→3p, 3p→3q, 3q→3r, 3r→3s) widen only request_events.kind, so the 3n schema is the current one with the old list.
const path = join(root, 'bus/bus.sqlite'), currentDDL = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8')
// 3s-3t (#797) adds project_archives after the request tables; the pre-3t fixtures below
// predate it and the replayed chain recreates it, so the step's text comes out here too.
.replace(readFileSync(new URL('../src/migrations/3s-3t.sql', import.meta.url), 'utf8'), '')
// 3u-3v (#1873, U4a) adds store_tasks; the same holds for it.
.replace(readFileSync(new URL('../src/migrations/3u-3v.sql', import.meta.url), 'utf8'), '')
.replace("'setup.wait','bootstrap.stale','bootstrap.completed','prd.submitted','prd.activated','prd.invalidated','question.asked','question.answered','attach.selected','mode.changed','advanced','cancelled'", "'setup.wait','cancelled'");
const newDDL = readFileSync(new URL('../src/migrations/3n-3o.sql', import.meta.url), 'utf8');
assert.ok(currentDDL.endsWith(newDDL)); const oldDDL = currentDDL.slice(0, -newDDL.length);
const { schemaDigest, SCHEMA_VERSION } = awaitImportStore;
const db = new DatabaseSync(path); db.exec(oldDDL);
db.prepare('INSERT INTO meta VALUES(?,?)').run('schema_version','3n');
db.prepare('INSERT INTO meta VALUES(?,?)').run('schema_digest',schemaDigest(db));
db.prepare('INSERT INTO meta VALUES(?,?)').run('comment_cursor_epoch','2026-11-03T00:00:00.000Z');
db.prepare("INSERT INTO events(id,at,business,kind,body) VALUES('preserved','2026-11-03T00:00:00.000Z','demo','action.allowed','{}')").run();
db.close(); chmodSync(path, 0o600); const before = readFileSync(path);
assert.throws(() => new Store(root), /schema-mismatch/); assert.deepEqual(readFileSync(path), before);
const cli = spawnSync(process.execPath, [new URL('../src/migrate-schema.mjs', import.meta.url).pathname, root], { encoding: 'utf8' });
assert.equal(cli.status, 0, cli.stderr); assert.equal(cli.stdout, 'bus schema 3w ready\n'); assert.equal(SCHEMA_VERSION, '3w');
const store = new Store(root); assert.equal(store.get("SELECT id FROM events").id, 'preserved');
for (const table of ['request_events','request_effects','request_snapshots','request_snapshot_events','request_project_events']) assert.equal(store.get(`SELECT count(*) n FROM ${table}`).n, 0);
store.close(); assert.equal(spawnSync(process.execPath, [new URL('../src/migrate-schema.mjs', import.meta.url).pathname, root]).status, 0);
// The actual base engine refuses the migrated fixture. No live config or data is read.
const moduleRoot = join(root, 'old-engine'); mkdirSync(join(moduleRoot, 'src/migrations'), { recursive: true });
writeFileSync(join(moduleRoot, 'schema.sql'), oldDDL);
const oldSource = spawnSync('git', ['show','1e1357cb6e97825f6080bee15ab45efce794b137:packages/bus/src/store.mjs'], { encoding: 'utf8' });
assert.equal(oldSource.status, 0); writeFileSync(join(moduleRoot, 'src/store.mjs'), oldSource.stdout);
for (const migration of ['3b','3b-3c','3c-3d','3d-3e','3e-3f','3f-3g','3g-3h','3h-3i','3i-3j','3j-3k','3k-3l','3l-3m','3m-3n'])
writeFileSync(join(moduleRoot, 'src/migrations', migration + '.sql'), readFileSync(new URL('../src/migrations/' + migration + '.sql', import.meta.url)));
const old = spawnSync(process.execPath, ['--input-type=module','-e', `import {Store} from ${JSON.stringify(join(moduleRoot, 'src/store.mjs'))}; try { new Store(${JSON.stringify(root)}); process.exit(9); } catch(e) { if(e.message !== 'schema-mismatch') throw e; }`], { encoding: 'utf8' });
assert.equal(old.status, 0, old.stderr);
});
const awaitImportStore = await import('../src/store.mjs');
test('3o_to_3p_widens_only_the_event_kinds_and_keeps_rows_seq_and_guards', t => {
const root = mkdtempSync(join(tmpdir(), 'request-migration-3p-'));
t.after(() => rmSync(root, { recursive: true, force: true })); mkdirSync(join(root, 'bus'), { mode: 0o700 });
const path = join(root, 'bus/bus.sqlite');
// The 3o schema is the current one with the narrower kind list.
const ddl3o = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8')
.replace(readFileSync(new URL('../src/migrations/3s-3t.sql', import.meta.url), 'utf8'), '')
// 3u-3v (#1873, U4a) adds store_tasks; the same holds for it.
.replace(readFileSync(new URL('../src/migrations/3u-3v.sql', import.meta.url), 'utf8'), '')
.replace("'setup.wait','bootstrap.stale','bootstrap.completed','prd.submitted','prd.activated','prd.invalidated','question.asked','question.answered','attach.selected','mode.changed','advanced','cancelled'", "'setup.wait','cancelled'");
const { schemaDigest } = awaitImportStore, AT = '2026-11-03T00:00:00.000Z';
const db = new DatabaseSync(path); db.exec(ddl3o);
db.prepare('INSERT INTO meta VALUES(?,?)').run('schema_version', '3o');
db.prepare('INSERT INTO meta VALUES(?,?)').run('schema_digest', schemaDigest(db));
db.prepare('INSERT INTO meta VALUES(?,?)').run('comment_cursor_epoch', AT);
db.prepare("INSERT INTO messages(id,at,business,from_role,from_run,to_role,class,body) VALUES('m1',?,'demo','human','human','pm','info','build a timer')").run(AT);
db.prepare("INSERT INTO events(id,at,business,kind,subject,body) VALUES('r1',?,'demo','human.input','m1',?)").run(AT, JSON.stringify({ message: 'm1', kind: 'instruction' }));
const add = (revision, kind) => db.prepare("INSERT INTO request_events(business,request,revision,kind,at,body) VALUES('demo','r1',?,?,?,'{}')").run(revision, kind, AT);
add(1, 'opened'); add(2, 'setup.wait');
assert.throws(() => add(3, 'bootstrap.stale'), /CHECK constraint failed/, 'the 3o schema refuses the new kind');
db.prepare("INSERT INTO request_effects(at,version,business,request,op,kind,digest,stage) VALUES(?,1,'demo','r1','bootstrap:x:reserve','bootstrap',?,'prepared')").run(AT, 'a'.repeat(64));
const before = db.prepare('SELECT * FROM request_events ORDER BY seq').all(), effects = db.prepare('SELECT * FROM request_effects ORDER BY seq').all();
db.close(); chmodSync(path, 0o600);
assert.throws(() => new Store(root), /schema-mismatch/);
const cli = spawnSync(process.execPath, [new URL('../src/migrate-schema.mjs', import.meta.url).pathname, root], { encoding: 'utf8' });
assert.equal(cli.status, 0, cli.stderr); assert.equal(cli.stdout, 'bus schema 3w ready\n');
const store = new Store(root);
assert.deepEqual(store.all('SELECT * FROM request_events ORDER BY seq'), before, 'every row keeps its seq and bytes');
assert.deepEqual(store.all('SELECT * FROM request_effects ORDER BY seq'), effects);
const next = (revision, kind) => store.run("INSERT INTO request_events(business,request,revision,kind,at,body) VALUES('demo','r1',?,?,?,'{}')", revision, kind, AT);
next(3, 'bootstrap.stale');
assert.equal(store.get('SELECT max(seq) n FROM request_events').n, 3, 'the sequence carries on from the old table');
assert.throws(() => next(3, 'bootstrap.completed'), /append-only/, 'a replayed revision is refused');
assert.throws(() => next(5, 'selection.proposed'), /request-revision-conflict/, 'the revision guard is back');
assert.throws(() => store.run("INSERT INTO request_events(business,request,revision,kind,at,body) VALUES('demo','nope',1,'opened',?,'{}')", AT), /request-origin-refused/, 'the origin guard is back');
assert.throws(() => store.run("INSERT INTO request_events(business,request,revision,kind,at,body) VALUES('demo','r1',4,'surprise',?,'{}')", AT), /CHECK constraint failed/);
assert.throws(() => store.run("UPDATE request_events SET kind='opened' WHERE seq=1"), /append-only/);
assert.throws(() => store.run('DELETE FROM request_events'), /append-only/);
assert.throws(() => store.run("INSERT INTO request_effects(at,version,business,request,op,kind,digest,stage) VALUES(?,1,'demo','r1','bootstrap:x:reserve','delivery',?,'prepared')", AT, 'a'.repeat(64)), /request-effect-conflict/, 'the effect guard is back');
store.close();
assert.equal(spawnSync(process.execPath, [new URL('../src/migrate-schema.mjs', import.meta.url).pathname, root]).status, 0, 'a second run is a no-op');
// The Task 1 engine (3o) refuses the migrated fixture and does not change it.
const migrated = readFileSync(path), moduleRoot = join(root, 'old-engine'); mkdirSync(join(moduleRoot, 'src/migrations'), { recursive: true });
writeFileSync(join(moduleRoot, 'schema.sql'), ddl3o);
const oldSource = spawnSync('git', ['show', '138171ca0:packages/bus/src/store.mjs'], { encoding: 'utf8' });
assert.equal(oldSource.status, 0); writeFileSync(join(moduleRoot, 'src/store.mjs'), oldSource.stdout);
for (const m of ['3b','3b-3c','3c-3d','3d-3e','3e-3f','3f-3g','3g-3h','3h-3i','3i-3j','3j-3k','3k-3l','3l-3m','3m-3n','3n-3o'])
writeFileSync(join(moduleRoot, 'src/migrations', m + '.sql'), readFileSync(new URL('../src/migrations/' + m + '.sql', import.meta.url)));
const old = spawnSync(process.execPath, ['--input-type=module', '-e', `import {Store} from ${JSON.stringify(join(moduleRoot, 'src/store.mjs'))}; try { new Store(${JSON.stringify(root)}); process.exit(9); } catch(e) { if(e.message !== 'schema-mismatch') throw e; }`], { encoding: 'utf8' });
assert.equal(old.status, 0, old.stderr); assert.deepEqual(readFileSync(path), migrated);
});
test('real_runtime_socket_loads_host_lifecycle_and_recovery_keeps_intake_without_capabilities', async t => {
const root = mkdtempSync(join(tmpdir(), 'request-runtime-'));
let runtime;
t.after(async () => { await runtime?.close(); rmSync(root, { recursive: true, force: true }); });
const business = { id: 'demo', human: 'jason', arbiters: { technical: 'pm', delivery: 'pm' },
roles: { pm: { definition: 'pm', authority: { withinRole: ['message.send'], crossRole: [] } } } };
const launch = async () => {
runtime = await startBroker({ dataRoot: root, businesses: { demo: business } });
const cap = runtime.broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
return new Client({ path: runtime.path, cap });
};
let client = await launch(); const source = await client.call('message.send', { to: 'pm', body: intent });
const opened = await client.call('request.open', { request: source.request });
assert.equal(opened.phase, 'selection'); assert.deepEqual(await client.call('request.get', { request: source.request }), opened);
await runtime.close(); runtime = null; client = await launch();
assert.deepEqual(await client.call('request.get', { request: source.request }), opened);
assert.deepEqual(await runtime.requestLifecycle.recover(), [opened]);
});
test('effect_and_request_unique_keys_refuse_replacement_without_explicit_seq', async t => {
const x = setup(t); const state = await x.call('pm', 'request.open', { request: x.source.request });
assert.throws(() => x.store.run(`INSERT OR REPLACE INTO request_events(business,request,revision,kind,actor_role,actor_run,at,body)
SELECT business,request,revision,kind,actor_role,actor_run,at,body FROM request_events`), /append-only/);
for (const table of ['request_snapshots','request_snapshot_events','request_project_events']) {
// Exercise triggers with valid rows; the future producers get immutable storage, no writer API here.
if (table === 'request_snapshots') x.store.run(`INSERT INTO request_snapshots(id,at,business,project,body) VALUES('snapshot',?,'demo','old',?)`, new Date().toISOString(), JSON.stringify({version:1,id:'snapshot',business:'demo',project:'old',origin:'product'}));
else if (table === 'request_snapshot_events') x.store.run(`INSERT INTO request_snapshot_events(at,business,project,revision,snapshot,kind,cause) VALUES(?,'demo','old',1,'snapshot','activated','fixture')`, new Date().toISOString());
else x.store.run(`INSERT INTO request_project_events(at,business,project,revision,kind,body) VALUES(?,'demo','old',1,'attached','{}')`, new Date().toISOString());
for (const sql of [`UPDATE ${table} SET at=at`, `DELETE FROM ${table}`, `INSERT OR REPLACE INTO ${table} SELECT * FROM ${table}`]) assert.throws(() => x.store.run(sql), /append-only/);
}
const event = { business: 'demo', request: state.request, expectedRevision: state.revision, kind: 'selection.proposed', actor: { role: 'pm', run: 'pm-run' }, body: {} };
assert.throws(() => x.journal.appendRequest(event), /invalid-request-event/);
});
test('preparation_rechecks_live_pm_claim_before_recording_any_decision', async t => {
let start, release;
const started = new Promise(r => { start = r; }), gate = new Promise(r => { release = r; });
const x = setup(t, { prepared: true, configureHost: host => {
const prepare = host.preparePlans; host.preparePlans = async (...args) => { start(); await gate; return prepare(...args); };
} });
const state = await x.call('pm', 'request.open', { request: x.source.request });
const proposed = x.call('pm', 'request.propose', x.propose(state));
await started; x.call('pm', 'role.release'); release();
await assert.rejects(() => proposed, /not-holder/);
assert.equal(x.store.get('SELECT count(*) n FROM decisions').n, 0);
assert.equal(x.journal.readRequest('demo', state.request).revision, state.revision);
});
test('admission_is_required_and_setup_wait_cannot_claim_ready', async t => {
const x = setup(t, { prepared: true, configureHost: host => { delete host.admitPlan; } });
const opened = await x.call('pm', 'request.open', { request: x.source.request });
const state = await x.call('pm', 'request.propose', x.propose(opened));
assert.equal(state.wait.code, 'request-admission-unavailable'); assert.equal(state.selection, null);
assert.equal(x.store.get('SELECT count(*) n FROM decisions').n, 0);
});
test('undeclared_projects_and_changed_existing_tracker_mapping_refuse', async t => {
const x = setup(t, { prepared: true }); const state = await x.call('pm', 'request.open', { request: x.source.request });
await assert.rejects(async () => x.call('pm', 'request.propose', { ...x.propose(state), source_project: 'library' }), /request-project-refused/);
await assert.rejects(async () => x.call('pm', 'request.propose', { ...x.propose(state), source_project: 'foreign' }), /request-project-refused/);
const selected = await x.call('pm', 'request.propose', x.propose(state));
x.call('human', 'decision.resolve', { id: selected.selection.decision, choice: 'existing' });
const picker = await x.lifecycle.resume('demo', state.request);
await assert.rejects(async () => x.call('pm', 'request.propose', { ...x.propose(picker), op: 'bad-picker', source_project: 'old', project: 'old', name: 'Old', tracker_project: 99 }), /request-project-refused/);
assert.equal(x.journal.readRequest('demo', state.request).project, null);
});
test('existing_confirmation_rechecks_exact_current_tracker_association', async t => {
let board = 2;
const x = setup(t, { prepared: true, configureHost: host => { host.listProjects = () => [{ business: 'demo', project: 'old', name: 'Old', tracker: { root: 1, project: board } }]; } });
const opened = await x.call('pm', 'request.open', { request: x.source.request });
const mode = await x.call('pm', 'request.propose', x.propose(opened));
x.call('human', 'decision.resolve', { id: mode.selection.decision, choice: 'existing' });
const picker = await x.lifecycle.resume('demo', opened.request);
const candidate = await x.call('pm', 'request.propose', { ...x.propose(picker), op: 'old-binding', source_project: 'old', project: 'old', name: 'Old' });
board = 3;
x.call('human', 'decision.resolve', { id: candidate.selection.decision, choice: 'confirm' });
const stopped = await x.lifecycle.resume('demo', opened.request);
assert.equal(stopped.project, null); assert.equal(stopped.wait.code, 'request-existing-project-changed');
assert.deepEqual(stopped.tasks, []);
});
test('journal_refuses_fabricated_original_text_even_on_host_append', t => {
const x = setup(t);
assert.throws(() => x.journal.appendRequest({ business: 'demo', request: x.source.request, expectedRevision: 0, kind: 'opened', actor: { role: 'pm', run: 'pm-run' },
body: { source_message: x.source.id, thread: null, session: null, owner: 'jason', pm: 'pm',
intent: { text: 'fabricated', sha256: hash('fabricated'), source_message: x.source.id, generated: false } } }), /request-origin-refused/);
assert.equal(x.store.get('SELECT count(*) n FROM request_events').n, 0);
});
test('preparation_can_report_a_precise_setup_wait_without_inventing_a_plan', async t => {
const x = setup(t, { prepared: true, configureHost: host => { host.preparePlans = () => ({ ready: false, code: 'tracker-board-unavailable' }); } });
const opened = await x.call('pm', 'request.open', { request: x.source.request });
const waiting = await x.call('pm', 'request.propose', x.propose(opened));
assert.equal(waiting.wait.code, 'tracker-board-unavailable'); assert.equal(waiting.project, null); assert.equal(waiting.selection, null);
assert.deepEqual(await x.lifecycle.recover(), [waiting]);
assert.equal(x.store.get('SELECT count(*) n FROM decisions').n, 0);
});
test('oversize_resource_display_keeps_exact_intake_in_one_explicit_setup_wait', async t => {
const userText = 'Original user intent\n' + 'a'.repeat(6000);
const x = setup(t, { prepared: true, userText });
const opened = await x.call('pm', 'request.open', { request: x.source.request });
const waiting = await x.call('pm', 'request.propose', x.propose(opened));
assert.equal(waiting.wait.code, 'request-plan-display-too-large');
assert.equal(JSON.parse(x.store.get("SELECT body FROM request_events WHERE kind='opened'").body).intent.text, userText);
assert.equal(x.store.get('SELECT count(*) n FROM decisions').n, 0);
});
test('runtime_restart_recovers_a_recorded_autonomous_selection_once_without_bootstrap_effects', async t => {
const x = setup(t, { prepared: true }); const opened = await x.call('pm', 'request.open', { request: x.source.request });
const selection = await x.call('pm', 'request.propose', x.propose(opened));
x.call('human', 'decision.resolve', { id: selection.selection.decision, choice: 'autonomous' });
x.store.close();
const business = { ...x.business, projects: Object.fromEntries(Object.entries(x.business.projects).filter(([, p]) => p.kind !== 'library')) };
const runtime = await startBroker({ dataRoot: x.root, businesses: { demo: business }, requestServices: x.host });
x.runtimeCleanup(runtime);
const cap = runtime.broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
const client = new Client({ path: runtime.path, cap });
const recovered = await client.call('request.get', { request: opened.request });
assert.equal(recovered.phase, 'bootstrap'); assert.equal(recovered.mode, 'autonomous');
assert.equal(recovered.wait.code, 'request-bootstrap-unavailable');
assert.equal(recovered.plan_digest, selection.selection.bindings[0].digest);
assert.deepEqual(await runtime.requestLifecycle.recover(), [recovered]);
assert.deepEqual(recovered.tasks, []); assert.equal(recovered.snapshot, null);
assert.equal(existsSync(join(x.root, 'products')), false);
assert.equal(existsSync(join(x.root, 'projects')), false);
});
// Review r1 I2/I3: a host whose inventory, faults and in-flight calls the test controls.
function controlled(t) {
const control = { board: 2, fault: {}, pause: null };
const hold = async at => {
const pause = control.pause;
if (pause?.at !== at) return;
control.pause = null; pause.start(); await pause.gate;
};
// Arm one pause; the next preparation/admission call of that kind parks until `release()`.
control.pauseAt = at => {
let start, release;
const started = new Promise(r => { start = r; }), gate = new Promise(r => { release = r; });
control.pause = { at, start, gate }; return { started, release };
};
const x = setup(t, { prepared: true, configureHost: host => {
host.listProjects = () => [{ business: 'demo', project: 'old', name: 'Old', tracker: { root: 1, project: control.board } }];
const { prepareExisting, preparePlans, admitPlan } = host;
host.prepareExisting = async (...args) => {
await hold('prepare');
if (control.fault.prepare) return { ready: false, code: control.fault.prepare };
const plan = prepareExisting(...args);
if (control.fault.big) plan.project_vars = { suites: Array.from({ length: 20 }, (_, i) => String(i).padStart(3, '0') + 'a'.repeat(280)) };
return plan;
};
host.preparePlans = async (...args) => { await hold('prepare'); return preparePlans(...args); };
host.admitPlan = async plan => { await hold('admit'); return control.fault.admit ? { ready: false, code: control.fault.admit } : admitPlan(plan); };
} });
return { x, control };
}
async function reachPicker(x) {
const opened = await x.call('pm', 'request.open', { request: x.source.request });
const mode = await x.call('pm', 'request.propose', x.propose(opened));
x.call('human', 'decision.resolve', { id: mode.selection.decision, choice: 'existing' });
return x.lifecycle.resume('demo', opened.request);
}
const existingArgs = (state, op, extra = {}) => ({ request: state.request, revision: state.revision, op, source_project: 'old', project: 'old', name: 'Old', tracker_project: 2, ...extra });
const count = (x, sql) => x.store.get(sql).n;
const moveOldMapping = x => x.b.reloadBusiness('demo', { ...x.business, projects: { ...x.business.projects, old: { ...x.business.projects.old, repo: join(x.root, 'old-code-moved') } } });
// A fresh-operation retry after the setup correction must land in the existing-project confirmation and bind it.
async function confirmRetry(x, retried) {
assert.equal(retried.route, 'existing'); assert.equal(retried.project, null);
assert.equal(retried.wait.kind, 'decision'); assert.equal(retried.wait.code, 'request-existing-confirmation');
assert.equal(retried.selection.bindings.length, 1); assert.equal(retried.selection.bindings[0].plan.existing, true);
x.call('human', 'decision.resolve', { id: retried.selection.decision, choice: 'confirm' });
const bound = await x.lifecycle.resume('demo', retried.request);
assert.equal(bound.project, 'old'); assert.equal(bound.phase, 'discovery'); assert.equal(bound.wait.code, 'request-attachment-unavailable');
assert.deepEqual(bound.tasks, []); assert.equal(bound.snapshot, null);
}
for (const w of [
{ name: 'preparation', code: 'tracker-temporarily-unavailable', arm: c => { c.fault.prepare = 'tracker-temporarily-unavailable'; } },
{ name: 'admission', code: 'organization-stale', arm: c => { c.fault.admit = 'organization-stale'; } },
{ name: 'oversized_display', code: 'request-plan-display-too-large', arm: c => { c.fault.big = true; } },
]) test(`existing_route_survives_a_${w.name}_wait_and_a_fresh_operation_retry_resumes_the_picker`, async t => {
const { x, control } = controlled(t), picker = await reachPicker(x);
assert.equal(picker.route, 'existing'); assert.equal(picker.wait.code, 'request-existing-project');
w.arm(control);
const waiting = await x.call('pm', 'request.propose', existingArgs(picker, 'existing-1'));
assert.equal(waiting.wait.kind, 'setup'); assert.equal(waiting.wait.code, w.code);
assert.equal(waiting.route, 'existing'); assert.equal(waiting.selection, null); assert.equal(waiting.project, null);
assert.equal(count(x, 'SELECT count(*) n FROM decisions'), 1, 'only the mode decision exists');
control.fault = {}; // the capability is restored
const retried = await x.call('pm', 'request.propose', existingArgs(waiting, 'existing-2'));
assert.equal(count(x, 'SELECT count(*) n FROM decisions'), 2);
// The earlier operation stays one immutable wait event; replaying it records nothing new.
assert.equal(count(x, "SELECT count(*) n FROM request_events WHERE json_extract(body,'$.op')='existing-1'"), 1);
const events = count(x, 'SELECT count(*) n FROM request_events');
assert.deepEqual(await x.call('pm', 'request.propose', existingArgs(picker, 'existing-1')), x.journal.readRequest('demo', picker.request));
assert.equal(count(x, 'SELECT count(*) n FROM request_events'), events);
await confirmRetry(x, retried);
});
test('existing_route_survives_a_changed_association_wait_and_a_fresh_operation_retry_resumes_the_picker', async t => {
const { x, control } = controlled(t), picker = await reachPicker(x);
const candidate = await x.call('pm', 'request.propose', existingArgs(picker, 'existing-1'));
control.board = 3;
x.call('human', 'decision.resolve', { id: candidate.selection.decision, choice: 'confirm' });
const stopped = await x.lifecycle.resume('demo', picker.request);
assert.equal(stopped.wait.kind, 'setup'); assert.equal(stopped.wait.code, 'request-existing-project-changed');
assert.equal(stopped.route, 'existing'); assert.equal(stopped.project, null);
const retried = await x.call('pm', 'request.propose', existingArgs(stopped, 'existing-2', { tracker_project: 3 }));
assert.equal(retried.selection.bindings[0].plan.tracker.project, 3);
await confirmRetry(x, retried);
});
test('new_project_route_still_refuses_an_existing_project_name', async t => {
const { x } = controlled(t), opened = await x.call('pm', 'request.open', { request: x.source.request });
assert.equal(opened.route, null);
await assert.rejects(async () => x.call('pm', 'request.propose', { ...x.propose(opened), project: 'old' }), /request-project-collision/);
});
// Review r1 I3: the association changes while preparation or admission is in flight.
for (const c of [
{ name: 'existing_board', route: 'existing', code: 'request-existing-project-changed', change: (x, control) => { control.board = 3; } },
{ name: 'existing_mapping', route: 'existing', code: 'request-plan-binding-refused', change: moveOldMapping },
{ name: 'new_project_collision', route: 'mode', code: 'request-project-collision',
change: x => x.b.reloadBusiness('demo', { ...x.business, projects: { ...x.business.projects, timer: { root: join(x.root, 'timer-records') } } }) },
]) test(`selection_is_not_confirmed_when_${c.name}_changes_during_admission`, async t => {
const { x, control } = controlled(t);
let state, choice;
if (c.route === 'existing') {
state = await x.call('pm', 'request.propose', existingArgs(await reachPicker(x), 'existing-1')); choice = 'confirm';
} else {
state = await x.call('pm', 'request.propose', x.propose(await x.call('pm', 'request.open', { request: x.source.request }))); choice = 'autonomous';
}
x.call('human', 'decision.resolve', { id: state.selection.decision, choice });
const gate = control.pauseAt('admit'), resuming = x.lifecycle.resume('demo', state.request);
await gate.started; await c.change(x, control); gate.release();
const stopped = await resuming;
assert.equal(count(x, "SELECT count(*) n FROM request_events WHERE kind='selection.confirmed'"), 0);
assert.equal(stopped.wait.kind, 'setup'); assert.equal(stopped.wait.code, c.code);
assert.equal(stopped.project, null); assert.equal(stopped.mode, null); assert.equal(stopped.plan_digest, null);
assert.equal(stopped.phase, 'selection'); assert.deepEqual(stopped.tasks, []);
assert.deepEqual(await x.lifecycle.resume('demo', state.request), stopped);
});
for (const c of [
{ name: 'existing_board_during_preparation', route: 'existing', at: 'prepare', code: 'request-existing-project-changed', change: (x, control) => { control.board = 3; } },
{ name: 'existing_board_during_admission', route: 'existing', at: 'admit', code: 'request-existing-project-changed', change: (x, control) => { control.board = 3; } },
{ name: 'existing_mapping_during_admission', route: 'existing', at: 'admit', code: 'request-plan-binding-refused', change: moveOldMapping },
{ name: 'new_project_collision_during_admission', route: 'mode', at: 'admit', code: 'request-project-collision',
change: x => x.b.reloadBusiness('demo', { ...x.business, projects: { ...x.business.projects, timer: { root: join(x.root, 'timer-records') } } }) },
]) test(`proposal_records_a_stale_wait_not_a_decision_when_${c.name}`, async t => {
const { x, control } = controlled(t);
const base = c.route === 'existing' ? await reachPicker(x) : await x.call('pm', 'request.open', { request: x.source.request });
const args = c.route === 'existing' ? existingArgs(base, 'late-1') : x.propose(base);
const decisions = count(x, 'SELECT count(*) n FROM decisions'), proposed = count(x, "SELECT count(*) n FROM request_events WHERE kind='selection.proposed'");
const gate = control.pauseAt(c.at), proposing = x.call('pm', 'request.propose', args);
await gate.started; await c.change(x, control); gate.release();
const stopped = await proposing;
assert.equal(count(x, 'SELECT count(*) n FROM decisions'), decisions, 'no decision for a stale plan');
assert.equal(count(x, "SELECT count(*) n FROM request_events WHERE kind='selection.proposed'"), proposed);
assert.equal(stopped.wait.kind, 'setup'); assert.equal(stopped.wait.code, c.code);
assert.equal(stopped.selection, null); assert.equal(stopped.project, null); assert.equal(stopped.phase, 'selection');
assert.equal(stopped.route, c.route === 'existing' ? 'existing' : null);
});