Files
stack/agents/steelbeak/work/queue-227/target-packet/reviewed-range-working.diff
jason.woltje 46c07df75f docs(row227): R1 to R4 packets, run receipts and author log appends (#1728)
Commits the row 227 evidence under agents/steelbeak/work/queue-227/: the remaining gate/ files, runs/ (first, corrected1, priorityprobe), target-packet, target-packet-r2, target-packet-r3, target-packet-r4, static/, and the two top-level manifests. Also commits BUILD.md and the append-only BUILD-LOG.md and docs/SESSIONS.md entries. The R4 packet hashes are PACKET.sha256 4eb60915b2144137df9b5a42afd0c5b266718d71de051fb8be53a27430a2cad4 and CURRENT-SOURCE.sha256 0cfd47c2073bc18a3ba7bff1ce6fdf39fe33a64b7fca209be71517b9feb89020, both checked against the files. The overlay bytes are committed in 4ac6f13bc. This commit adds receipts and packets only and records no scripts/test-case2.sh run on the R4 overlay.
2026-11-02 13:40:09 -06:00

520 lines
33 KiB
Diff

diff --git a/scripts/case2/console.mjs b/scripts/case2/console.mjs
new file mode 100644
index 000000000..b832f7aba
--- /dev/null
+++ b/scripts/case2/console.mjs
@@ -0,0 +1,43 @@
+// C2-14 S10: actual Console HTTP chat endpoints and browser DOM over the same acceptance bus.
+// The unrelated Board is a local 503 fixture. No chat response, citation check or notice is stubbed.
+import { createServer } from 'node:http';
+import { startServer } from '../../packages/webui/src/serve.mjs';
+import { consoleBus } from '../../packages/webui/src/console-bus.mjs';
+import { browser } from '../../packages/webui/tests/browser.mjs';
+
+const shutdown = server => new Promise((resolve, reject) => {
+ server.close(error => error ? reject(error) : resolve());
+ server.closeAllConnections?.();
+});
+const expect = (value, label) => { if (!value) throw new Error(label); };
+export async function consoleAcceptance({ humanCall, thread, reply, writeNotice, writeCommit, cleanups }) {
+ const board = createServer((req, res) => { res.writeHead(503); res.end('Acceptance Board is not configured.'); });
+ await new Promise(resolve => board.listen(0, '127.0.0.1', resolve));
+ cleanups.push(() => shutdown(board));
+ const options = consoleBus(humanCall);
+ const server = await startServer({ port: 0, board: `http://127.0.0.1:${board.address().port}`, ...options, log: () => {} });
+ cleanups.push(() => shutdown(server));
+ const origin = `http://127.0.0.1:${server.address().port}`;
+ const response = await fetch(`${origin}/api/chat/threads/${thread}/messages`);
+ expect(response.ok, 'Console message endpoint refused');
+ const body = await response.json();
+ expect(Array.isArray(body.rows), 'Console message shape');
+ const checked = body.rows.find(m => m.id === reply.id), notice = body.rows.find(m => m.id === writeNotice.id);
+ expect(checked?.body === reply.body && checked.check?.citations?.[0]?.scope === 'canon', 'Console dropped stored reply/check');
+ expect(notice?.body === writeNotice.body, 'Console dropped broker notice');
+ const view = await browser({ machine: false });
+ cleanups.push(() => view.close());
+ await view.navigate(`${origin}/#/chat/threads/${thread}`);
+ const rendered = await view.evaluate(`(async () => {
+ for (let i = 0; i < 150; i++) {
+ const bodies = [...document.querySelectorAll('.chat-log .chat-body')].map(e => e.textContent);
+ const notices = [...document.querySelectorAll('.chat-notice > span:first-child')].map(e => e.textContent);
+ const canon = [...document.querySelectorAll('.chat-cites')].some(e => e.textContent.includes('canon'));
+ if (bodies.some(t => t === 'Grey eyes.') && canon && notices.some(t => t === ${JSON.stringify(`bean wrote ${JSON.parse(writeNotice.body).path} @${writeCommit.slice(0, 7)}`)}))
+ return { reply: true, canon, notice: true };
+ await new Promise(resolve => setTimeout(resolve, 40));
+ }
+ return { reply: false, canon: false, notice: false };
+ })()`);
+ expect(rendered.reply && rendered.canon && rendered.notice, 'Console did not render actual checked reply and write notice');
+}
diff --git a/scripts/case2/e2e.mjs b/scripts/case2/e2e.mjs
new file mode 100644
index 000000000..a18e84e2d
--- /dev/null
+++ b/scripts/case2/e2e.mjs
@@ -0,0 +1,266 @@
+// C2-14 (#1728): one scratch-root acceptance driver. No live host or real model.
+// The human uses bindHuman in-process because a development agent cannot prove human CLI ancestry.
+// The creative session is real createLauncher -> runner -> C2-06's scripted citation adapter.
+// All 17 steps run against landed APIs. Execution is serialized by Sage (Mos1961).
+import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
+import { dirname, join, resolve } from 'node:path';
+import { DatabaseSync } from 'node:sqlite';
+import { scratchCa, fakeGitea } from '../../packages/seat/tests/fake-gitea.mjs';
+import { BASE, MARKER, git, mappingEntry } from '../../packages/library/tests/helpers.mjs';
+import * as library from '../../packages/library/src/index.mjs';
+import { launchFixture } from './launch.mjs';
+import { consoleAcceptance } from './console.mjs';
+
+const FETCH_TOKEN = 'fx227-fetch-q7-2270';
+const SEAT_TOKEN = 'fx227-seat-q7-2270';
+const HUMAN_TOKEN = 'fx227-human-q7-2270';
+const USER = 'books-bot', BUSINESS = 'acme';
+const CHARACTER = 'Book One/Characters/Mara Vell.md';
+const SUPPORT = 'Book One/Notes/Eyes.md';
+const WRITE = 'Book One/Notes/Agent.md';
+const EDITOR = { name: 'Fixture author', email: '[email protected]', user: 'writer' };
+const secrets = [FETCH_TOKEN, SEAT_TOKEN, HUMAN_TOKEN];
+const redact = value => secrets.reduce((s, token) => s.replaceAll(token, '[fixture credential]'), String(value));
+let failures = 0;
+const out = line => process.stdout.write(redact(line) + '\n');
+const expect = (cond, message) => { if (!cond) throw new Error(message); };
+async function step(id, text, fn) {
+ try { await fn(); out(`ok ${id} ${text}`); }
+ catch (e) { failures++; out(`not ok ${id} ${text}: ${e?.code ?? ''} ${String(e?.message ?? e).split('\n')[0].slice(0, 300)}`.trimEnd()); }
+}
+async function refuses(promise, code) {
+ try { await promise; }
+ catch (e) { if (e?.code === code) return; throw new Error(`refused ${e?.code ?? 'unknown'}, expected ${code}`); }
+ throw new Error(`answered, expected ${code}`);
+}
+async function until(fn, label, ms = 20000) {
+ const end = Date.now() + ms;
+ while (Date.now() < end) { const result = await fn(); if (result) return result; await new Promise(r => setTimeout(r, 30)); }
+ throw new Error(`timed out: ${label}`);
+}
+function holdsToken(dir) {
+ for (const name of readdirSync(dir, { recursive: true })) {
+ const file = join(dir, name);
+ let st;
+ try { st = statSync(file); } catch { continue; }
+ if (st.isFile() && secrets.some(value => readFileSync(file).includes(value))) return file;
+ }
+ return null;
+}
+function readEvents(dataRoot, where = "kind='action.refused'") {
+ const db = new DatabaseSync(join(dataRoot, 'bus', 'bus.sqlite'), { readOnly: true });
+ try { return db.prepare(`SELECT body FROM events WHERE ${where} ORDER BY seq`).all().map(row => JSON.parse(row.body)); }
+ finally { db.close(); }
+}
+
+const cleanups = [];
+let closing = null;
+const close = () => (closing ??= (async () => {
+ const errors = [];
+ for (const cleanup of cleanups.reverse()) { try { await cleanup(); } catch { errors.push('cleanup failed'); } }
+ if (errors.length) throw new Error('acceptance resource cleanup failed');
+})());
+for (const [name, status] of [['SIGTERM', 143], ['SIGINT', 130]])
+ process.once(name, () => { void close().finally(() => process.exit(status)); });
+
+async function main(root) {
+ const work = join(root, 'her', 'books');
+ mkdirSync(work, { recursive: true });
+ git(work, 'init', '-q', '-b', 'main');
+ const files = { ...BASE, [SUPPORT]: '# Eye notes\nGrey eyes are in the character record.\n' };
+ for (const [path, text] of Object.entries(files)) {
+ mkdirSync(dirname(join(work, path)), { recursive: true }); writeFileSync(join(work, path), text);
+ }
+ git(work, 'add', '-A'); git(work, 'commit', '-qm', 'her first commit');
+ const first = git(work, 'rev-parse', 'HEAD');
+ const tls = scratchCa(join(root, 'ca'));
+ const gitea = await fakeGitea({ dir: join(root, 'gitea'), tls, user: USER, token: FETCH_TOKEN,
+ users: { [USER]: FETCH_TOKEN, 'acme-bean': SEAT_TOKEN, [EDITOR.user]: HUMAN_TOKEN } });
+ cleanups.push(() => gitea.close());
+ const remote = gitea.repo('writer', 'books');
+ mkdirSync(dirname(remote), { recursive: true });
+ git(root, 'clone', '-q', '--bare', '--no-local', work, remote);
+ const push = (changed, { from = null } = {}) => {
+ if (from) git(work, 'reset', '-q', '--hard', from);
+ for (const [path, text] of Object.entries(changed)) {
+ mkdirSync(dirname(join(work, path)), { recursive: true }); writeFileSync(join(work, path), text);
+ }
+ git(work, 'add', '-A'); git(work, 'commit', '-qm', 'her next commit');
+ git(work, 'push', '-q', '--force', remote, 'HEAD:refs/heads/main');
+ return git(work, 'rev-parse', 'HEAD');
+ };
+ const remoteHead = () => git(remote, 'rev-parse', 'refs/heads/main');
+ const entry = { ...mappingEntry(), remote: `${gitea.origin}/writer/books.git`, user: USER, editor: EDITOR };
+ entry.universes.harbor.supporting = ['Book One/Notes/'];
+ const f = await launchFixture(root, entry, { fetchToken: FETCH_TOKEN, seatToken: SEAT_TOKEN, humanToken: HUMAN_TOKEN, ca: tls.ca });
+ cleanups.push(() => f.close());
+ const { runtime: bus, dataRoot } = f, broker = bus.broker;
+ const human = broker.bindHuman({ business: BUSINESS, human: 'jason', via: 'cli', outsideAgent: true });
+ const call = (cap, verb, args = {}) => Promise.resolve().then(() => broker.request(cap, { verb, args }));
+ const humanCall = (verb, args = {}) => call(human, verb, args);
+ const sync = async (args = {}) => {
+ for (let i = 0; ; i++) {
+ try { return await humanCall('library.sync', { project: 'books', ...args }); }
+ catch (e) { if (e?.code !== 'library-sync-busy' || i >= 100) throw e; await new Promise(r => setTimeout(r, 100)); }
+ }
+ };
+ const status = () => humanCall('library.status', { project: 'books' });
+ const mapping = library.checkMapping(entry);
+ const mirror = library.mirrorPath({ business: f.business, project: 'books', dataRoot });
+ const source = async () => ({ repo: mirror, mapping, commit: (await status()).served });
+
+ await step('S1a', 'her bare layout classifies canon, manuscript, playground, unmapped and second-universe paths', () => {
+ expect(git(remote, 'rev-parse', '--is-bare-repository') === 'true', 'remote is not bare');
+ const of = p => library.classify(mapping, p);
+ expect(of(CHARACTER).scope === 'canon' && of(CHARACTER).type === 'character', 'character');
+ expect(of('Book One/Chapters/Chapter 01.md').type === 'manuscript', 'chapter');
+ expect(of('Book One/Playground/What If.md').scope === 'playground', 'playground');
+ expect(of('Inbox Note.md').scope === 'unmapped', 'inbox');
+ expect(of('Second World/Chapters/01.md').universe === 'second', 'second universe');
+ });
+ await step('S1b', 'a supporting folder and her editor are part of the checked mapping', async () => {
+ expect(library.classify(mapping, SUPPORT).scope === 'supporting', 'supporting scope');
+ expect(library.supportingPath(mapping, 'harbor', WRITE, await library.listTree(remote, first)) === WRITE, 'supporting write path');
+ expect(mapping.editor.name === EDITOR.name && mapping.editor.email === EDITOR.email, 'editor');
+ });
+ await step('S2a', 'library.sync fetches her bare repo and serves its head', async () => {
+ const state = await sync();
+ expect(state.served === first && state.served === remoteHead(), 'wrong served commit');
+ expect(state.lastError === null && state.diverged === null, 'sync error or divergence');
+ });
+ await step('S2b', 'library.file reads canon and playground at served, and refuses an unmapped file', async () => {
+ const canon = await humanCall('library.file', { project: 'books', path: CHARACTER });
+ expect(canon.commit === first && canon.content.includes(MARKER), 'canon file');
+ const play = await humanCall('library.file', { project: 'books', path: 'Book One/Playground/What If.md' });
+ expect(play.content.includes(MARKER), 'playground file');
+ await refuses(humanCall('library.file', { project: 'books', path: 'Inbox Note.md' }), 'project-read-path');
+ });
+ await step('S2c', 'her fast-forward push is served by the next sync', async () => {
+ const next = push({ 'Book One/Places/Harbor Town.md': `---\ntype: place\n---\nSalt and tar. ${MARKER}\n` });
+ expect((await sync()).served === next, 'fast-forward not served');
+ });
+ await step('S2d', 'fixture credentials occur nowhere under the data root, including the mirror', () => {
+ expect(holdsToken(dataRoot) === null, 'credential found under the data root');
+ });
+
+ let thread, asked, reply, writeCommit, writeNotice;
+ let citedCommit;
+ await step('S3a', 'she makes a creative thread and asks in it', async () => {
+ const project = (await humanCall('project.create', { name: 'books', repo: 'books' })).id;
+ thread = (await humanCall('thread.create', { project, name: 'Book One', instance: 'bean' })).id;
+ const looked = await library.get(await source(), { universe: 'harbor', path: CHARACTER });
+ expect(looked && looked.scope === 'canon', 'lookup missing');
+ const index = looked.lines.findIndex(([line, text]) => Number.isSafeInteger(line) && text.includes('Grey eyes.'));
+ expect(index >= 0, 'lookup quote missing');
+ citedCommit = looked.commit;
+ const cite = { path: CHARACTER, commit: citedCommit, lines: [looked.lines[index][0], looked.lines[index][0]], quote: 'Grey eyes.', as: 'canon' };
+ const valid = text => JSON.stringify({ text, citations: [cite] });
+ const bad = JSON.stringify({ text: 'Playground is canon.', citations: [{ path: 'Book One/Playground/What If.md',
+ commit: citedCommit, lines: [4, 4], quote: MARKER, as: 'canon' }] });
+ f.setPlan([{ lookups: [{ universe: 'harbor', path: CHARACTER }], text: valid('Grey eyes.') }, { text: bad }, { text: valid('The playground cannot establish canon.') }]);
+ asked = (await humanCall('thread.send', { thread, body: "What colour are Mara Vell's eyes?" })).id;
+ });
+ await step('S3b', 'the real launcher runs the C2-06 fake harness and stores its reply in her thread', async () => {
+ reply = await until(async () => (await humanCall('thread.messages', { thread })).find(m => m.in_reply_to === asked && m.class === 'RESULT'), 'launcher reply');
+ expect(reply.from_role === 'bean' && f.cap(thread), 'reply did not use managed bean run');
+ const record = f.records.find(r => r.thread === thread);
+ expect(record && Number.isSafeInteger(record.pid) && record.pid > 1, 'no launched process record');
+ const runDir = dirname(join(dataRoot, 'launches', BUSINESS, record.run, 'session.json'));
+ expect(Number(readFileSync(join(runDir, 'fixture-count'), 'utf8')) === 1, 'scripted adapter was not called exactly once');
+ });
+ await step('S3c', 'her stored reply carries a verified lookup citation at the served commit', () => {
+ const body = JSON.parse(reply.body);
+ expect(body.text === 'Grey eyes.' && body.citations[0].commit === citedCommit, 'wrong reply');
+ const record = f.records.find(r => r.thread === thread);
+ const lookups = readFileSync(join(dataRoot, 'launches', BUSINESS, record.run, 'fixture-lookups.jsonl'), 'utf8').trim().split('\n').map(line => JSON.parse(line));
+ expect(lookups.length === 1 && lookups[0].path === CHARACTER && lookups[0].commit === citedCommit
+ && lookups[0].scope === 'canon' && lookups[0].lines.some(([line, text]) => line === body.citations[0].lines[0] && text.includes('Grey eyes.')), 'managed harness lookup did not supply the cited source');
+ expect(reply.check?.citations?.length === 1 && reply.check.citations[0].scope === 'canon', 'missing stored guard check');
+ const events = readEvents(dataRoot, "kind='action.allowed'");
+ expect(events.some(e => e.action === 'message.checked' && e.message === reply.id && e.check.citations[0].scope === 'canon'), 'no stored check event');
+ });
+ await step('S4', 'a playground-as-canon reply refuses, and the runner retries without storing the bad body', async () => {
+ const next = (await humanCall('thread.send', { thread, body: 'Can the playground establish canon?' })).id;
+ const second = await until(async () => (await humanCall('thread.messages', { thread })).find(m => m.in_reply_to === next && m.class === 'RESULT'), 'guard retry');
+ expect(JSON.parse(second.body).text === 'The playground cannot establish canon.', 'retry answer');
+ const history = await humanCall('thread.messages', { thread });
+ expect(!history.some(m => m.body.includes('Playground is canon.')), 'refused reply persisted');
+ expect(readEvents(dataRoot).some(e => e.code === 'playground-cited-as-canon'), 'missing refusal');
+ const record = f.records.find(r => r.thread === thread);
+ expect(Number(readFileSync(join(dataRoot, 'launches', BUSINESS, record.run, 'fixture-count'), 'utf8')) === 3, 'retry count');
+ });
+ await step('S5a', 'her force-push holds library-diverged without moving served', async () => {
+ const before = (await status()).served;
+ const forced = push({ 'Book One/Places/Harbor Town.md': `---\ntype: place\n---\nRewritten. ${MARKER}\n` }, { from: first });
+ await refuses(sync(), 'library-diverged');
+ const state = await status();
+ expect(state.served === before && state.diverged?.commit === forced, 'force-push was not held');
+ expect(readEvents(dataRoot).some(e => e.code === 'library-diverged'), 'missing divergence refusal');
+ });
+ await step('S5b', 'a stale accept refuses, then her accepted tip becomes served', async () => {
+ await refuses(sync({ accept: first }), 'library-accept-stale');
+ const tip = remoteHead(), state = await sync({ accept: tip });
+ expect(state.served === tip && state.diverged === null, 'tip not accepted');
+ });
+
+ // Write assertions are measured here, after divergence acceptance; setup is never write evidence.
+ await step('S6', 'the fixture-granted seat writes one supporting commit with its trailer and a thread notice', async () => {
+ const before = remoteHead(), requestsBefore = gitea.requests.length;
+ const result = await call(f.cap(thread), 'library.doc.write', { universe: 'harbor', path: WRITE, base: null, content: '# Agent note\nA supporting observation.\n' });
+ writeCommit = remoteHead();
+ expect(result.commit === writeCommit && result.path === WRITE, 'write result');
+ expect(writeCommit !== before && git(remote, 'rev-list', '--count', `${before}..${writeCommit}`) === '1', 'write must add exactly one commit');
+ expect(git(remote, 'show', '-s', '--format=%P', writeCommit) === before, 'write parent');
+ expect(git(remote, 'show', '-s', '--format=%B', writeCommit).includes('Mosaic-Agent: acme/bean'), 'missing agent trailer');
+ expect(git(remote, 'show', '-s', '--format=%an <%ae>', writeCommit) === 'Bean fixture <[email protected]>', 'seat author');
+ expect(gitea.requests.slice(requestsBefore).some(r => r.method === 'POST' && r.user === 'acme-bean' && r.path.endsWith('/git-receive-pack')), 'seat credential not used for push');
+ writeNotice = (await humanCall('thread.messages', { thread })).find(m => {
+ try { const body = JSON.parse(m.body); return body.notice === 'library-write' && body.commit === writeCommit; } catch { return false; }
+ });
+ expect(writeNotice?.from_role === 'bean', 'notice missing from her thread');
+ const noticeBody = JSON.parse(writeNotice.body);
+ expect(Object.keys(noticeBody).sort().join(',') === 'commit,notice,op,path' && noticeBody.notice === 'library-write'
+ && noticeBody.op === 'write' && noticeBody.path === WRITE && noticeBody.commit === writeCommit, 'notice body');
+ });
+ await step('S7', 'a story-path write refuses library-write-story-path and leaves her remote unchanged', async () => {
+ const before = remoteHead(), served = (await status()).served;
+ await refuses(call(f.cap(thread), 'library.doc.write', { universe: 'harbor', path: 'Book One/Chapters/Chapter 01.md', base: null, content: 'Unwanted story edit.\n' }), 'library-write-story-path');
+ expect(remoteHead() === before && (await status()).served === served, 'refusal moved a ref');
+ });
+ await step('S8', 'her revert of the agent write adds exactly one editor-authored commit', async () => {
+ const before = remoteHead(), requestsBefore = gitea.requests.length;
+ // A2 (sage-3596): revert names the business project key as well as the commit.
+ const result = await humanCall('library.revert', { project: 'books', commit: writeCommit });
+ const after = remoteHead();
+ expect(after !== before && git(remote, 'rev-list', '--count', `${before}..${after}`) === '1', 'revert commit count');
+ expect(git(remote, 'show', '-s', '--format=%P', after) === before, 'revert must have exactly one parent');
+ expect(gitea.requests.slice(requestsBefore).some(r => r.method === 'POST' && r.user === EDITOR.user && r.path.endsWith('/git-receive-pack')), 'revert did not use her credential');
+ expect(result.reverted === writeCommit && result.commit === after, 'revert result');
+ expect(!git(remote, 'ls-tree', '-r', '--name-only', after).split('\n').includes(WRITE), 'revert did not remove the newly created note');
+ expect(git(remote, 'show', '-s', '--format=%an <%ae>', after) === `${EDITOR.name} <${EDITOR.email}>`, 'revert editor');
+ });
+ await step('S9', 'her library.file.save adds one commit with her editor as author', async () => {
+ const before = remoteHead(), requestsBefore = gitea.requests.length, base = git(remote, 'rev-parse', `${before}:${CHARACTER}`);
+ const content = git(remote, 'show', `${before}:${CHARACTER}`) + '\nHer saved note.\n';
+ const result = await humanCall('library.file.save', { project: 'books', path: CHARACTER, base, content });
+ const after = remoteHead();
+ expect(after !== before && git(remote, 'rev-list', '--count', `${before}..${after}`) === '1', 'save commit count');
+ expect(git(remote, 'show', '-s', '--format=%P', after) === before, 'save must have exactly one parent');
+ expect(git(remote, 'show', '-s', '--format=%an <%ae>', after) === `${EDITOR.name} <${EDITOR.email}>`, 'save editor');
+ expect(git(remote, 'show', `${after}:${CHARACTER}`).endsWith('Her saved note.'), 'save content');
+ expect(result.commit === after && result.path === CHARACTER, 'save result');
+ expect(gitea.requests.slice(requestsBefore).some(r => r.method === 'POST' && r.user === EDITOR.user && r.path.endsWith('/git-receive-pack')), 'save did not use her credential');
+ });
+ await step('S10', 'the real Console renders her checked reply and the broker write notice', async () => {
+ expect(writeNotice && writeCommit, 'actual write notice is required');
+ await consoleAcceptance({ humanCall, thread, reply, writeNotice, writeCommit, cleanups });
+ });
+ expect(holdsToken(dataRoot) === null, 'credential persisted during the later steps');
+}
+
+const root = process.argv[2];
+if (!root) { process.stderr.write('usage: node scripts/case2/e2e.mjs <scratch dir>\n'); process.exit(2); }
+try { await main(resolve(root)); }
+catch (e) { failures++; process.stderr.write(redact(`e2e: stopped: ${e?.code ?? ''} ${String(e?.message ?? e).split('\n')[0]}`) + '\n'); }
+finally { await close(); }
+if (failures) process.exitCode = 1;
diff --git a/scripts/case2/launch.mjs b/scripts/case2/launch.mjs
new file mode 100644
index 000000000..a45b57ed8
--- /dev/null
+++ b/scripts/case2/launch.mjs
@@ -0,0 +1,121 @@
+// C2-14 fixture: the real host/launcher with C2-06's scripted citation adapter. No model binary runs.
+import { chmodSync, mkdirSync, writeFileSync } from 'node:fs';
+import { join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { BusError, writeGrantEvent } from '../../packages/bus/src/broker.mjs';
+import { Store } from '../../packages/bus/src/store.mjs';
+import { startBroker } from '../../packages/bus/src/runtime.mjs';
+import { grantDecision } from '../../packages/bus/tests/host-fixtures.mjs';
+import { launchDoc } from '../../packages/business/tests/helpers.mjs';
+import { fakeHarnesses, fixture } from '../../packages/cli/tests/helpers.mjs';
+import { bootConfig, loadSystem } from '../../packages/cli/src/config.mjs';
+import { createLauncher } from '../../packages/cli/src/launcher.mjs';
+import { SESSION_CREDENTIALS } from '../../packages/seat/src/session.mjs';
+import * as library from '../../packages/library/src/index.mjs';
+
+const ADAPTER = fileURLToPath(new URL('./citation-adapter.mjs', import.meta.url));
+const REPLY_ADAPTER = fileURLToPath(new URL('../../packages/harness/tests/fixtures/citation-adapter.mjs', import.meta.url));
+const ROTATE = '2099-01-01';
+const AT = '2026-10-24T00:00:00.000Z';
+const shell = value => "'" + String(value).replaceAll("'", "'\\''") + "'";
+
+// A fixture grant is part of the brief. It uses the normal store shape; no executor/network mint or
+// role-policy modification occurs. This is deliberately distinct from the row287 grant acceptance.
+function seedGrant(dataRoot, storeRoot, token) {
+ const dir = join(storeRoot, 'acme', 'bean');
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
+ writeFileSync(join(dir, 'gitea'), token + '\n', { mode: 0o600 });
+ const store = new Store(dataRoot);
+ try {
+ const decision = grantDecision(store, { business: 'acme', grant: {
+ method: 'gitea', agent: 'bean', target: { projects: ['books'] }, rotateBy: ROTATE,
+ } });
+ store.run('INSERT INTO gitea_users(business,agent,user,id,nonce,decision) VALUES(?,?,?,?,?,?)',
+ 'acme', 'bean', 'acme-bean', 1, '2270227022702270', decision);
+ store.run('INSERT INTO gitea_grants(business,agent,user,token_id,token_name,projects,rotate_by,decision) VALUES(?,?,?,?,?,?,?,?)',
+ 'acme', 'bean', 'acme-bean', 1, 'fixture-227', '["books"]', ROTATE, decision);
+ writeGrantEvent(store, AT, 'acme', { operation: 'grant.gitea', decision }, decision);
+ writeGrantEvent(store, AT, 'acme', { action: 'grant.applied', method: 'gitea', agent: 'bean',
+ grantDecision: decision, user: 'acme-bean', userId: 1, tokenId: 1, forks: [],
+ repositories: [{ project: 'books', repository: 'fixture', level: 'write' }] }, 'bean');
+ } finally { store.close(); }
+}
+
+export async function launchFixture(root, entry, { fetchToken, seatToken, humanToken, ca }) {
+ const configDir = join(root, 'config'), storeRoot = join(configDir, 'secrets', 'agents');
+ const slotDir = join(configDir, 'secrets', 'acme');
+ mkdirSync(slotDir, { recursive: true, mode: 0o700 });
+ for (const [slot, token] of [[library.SLOT, fetchToken], [library.HUMAN_SLOT, humanToken]])
+ writeFileSync(join(slotDir, slot), token + '\n', { mode: 0o600 });
+ const plan = join(root, 'citation-plan.json');
+ writeFileSync(plan, '[]\n', { mode: 0o600 });
+ const f = fixture(root, 'acme', doc => {
+ // The unused factory roles remain valid, but no tracker is configured or polled.
+ delete doc.tracker;
+ doc.projects = { books: entry };
+ doc.roles.chief.credentials = { gitea: { file: join(slotDir, library.SLOT), rotateBy: ROTATE } };
+ doc.roles.bean = { definition: 'companion',
+ credentials: { gitea: { store: true, rotateBy: ROTATE } },
+ identity: { name: 'Bean fixture', email: '[email protected]', gitea: { url: new URL(entry.remote).origin, user: 'acme-bean' } },
+ publish: ['books'], vars: { harness: 'pi', model: 'claude-sonnet-5-5', thinking: 'high' } };
+ doc.launch = { ...launchDoc('chief', ['bean'], { opus: 1, sonnet: 1 }), threadIdleSeconds: 3600 };
+ doc.plugins = { ...doc.plugins, channel: ['webui'] };
+ doc.replyLimits = { requireCitation: true, citationsPerReply: 20 };
+ return doc;
+ });
+ chmodSync(configDir, 0o700);
+ for (const key of SESSION_CREDENTIALS.flat()) delete f.env[key];
+ seedGrant(f.dataRoot, storeRoot, seatToken);
+ const system = loadSystem({ env: f.env });
+ const { trackers, ...boot } = bootConfig({ system, businessId: 'acme', env: f.env });
+ if (trackers !== undefined) throw new Error('acceptance fixture must not configure tracker polling');
+ const command = (kind, url, opts) => {
+ const c = library.syncCommand(kind, url, opts);
+ return { argv: c.argv, env: { ...c.env, GIT_SSL_CAINFO: ca } };
+ };
+ const runtime = await startBroker({ ...boot, librarySync: { command, intervalMs: 3_600_000, configDir } });
+ const hooks = [], cleanups = [], caps = new Map(), records = [], logs = [];
+ let closing = null;
+ const close = () => (closing ??= (async () => {
+ try { for (const hook of hooks) await hook(0); }
+ finally {
+ try { await runtime.close(); }
+ finally { for (const cleanup of cleanups.reverse()) await cleanup(); }
+ }
+ })());
+ try {
+ const ops = {
+ identity: m => runtime.identity(m.cap), authorizeLaunch: m => runtime.authorizeLaunch(m.cap, m.instance),
+ refuse: m => runtime.refuse(m.cap, m.code, m.subject), endLaunch: m => runtime.endLaunch(m.record),
+ credentialStatus: m => runtime.credentialStatus(m.business, m.role), threadsState: m => runtime.threadsState(m.business),
+ threadIdle: m => runtime.threadIdle(m.record), mayLaunch: m => runtime.mayLaunch(m),
+ landHeld: m => runtime.landHeld(m.record), landHolders: m => runtime.landHolders(m.business),
+ landRelease: m => runtime.landRelease(m.record), sessionClears: m => runtime.sessionClears(m.business),
+ lifecycleRequests: m => runtime.lifecycleRequests(m.business),
+ };
+ const wrap = fn => async value => {
+ try { return fn(value); }
+ catch (e) { throw Object.assign(new Error('fixture host refused'), { code: e instanceof BusError ? e.code : 'fixture-host-refused' }); }
+ };
+ const host = { business: 'acme', path: runtime.path, dataRoot: f.dataRoot,
+ op: wrap(m => { if (!Object.hasOwn(ops, m.op)) throw new BusError('invalid-host-request'); return ops[m.op](m); }),
+ bindLaunch: wrap(record => {
+ const bound = runtime.bindLaunch(record);
+ records.push(record); caps.set(record.thread, bound.cap);
+ return bound;
+ }),
+ beforeClose: fn => void hooks.push(fn),
+ };
+ const adapter = join(root, 'citation-adapter.sh');
+ writeFileSync(adapter, `cp ${shell(plan)} "$(dirname "$MOSAIC_TURN_MARKER")/fixture-steps.json"\nexec ${shell(process.execPath)} ${shell(ADAPTER)}\n`, { mode: 0o644 });
+ const launcher = createLauncher({ host, system, env: f.env,
+ makeLoader: await fakeHarnesses({ after: fn => cleanups.push(fn) }, { pi: adapter }, write =>
+ write('channel', 'webui', `export * from ${JSON.stringify(new URL('../../packages/plugins/channel/webui/index.mjs', import.meta.url).href)};`)),
+ readOnly: [ADAPTER, REPLY_ADAPTER, plan], harnessVersion: '0.85.1',
+ sessionDefaults: { pollInterval: 50 }, threadTickMs: 50, log: line => logs.push(line),
+ });
+ await launcher.listen();
+ return { ...f, runtime, launcher, logs, records, plan, cap: thread => caps.get(thread), close,
+ business: boot.businesses.acme, setPlan: steps => writeFileSync(plan, JSON.stringify(steps) + '\n') };
+ } catch (e) { await close(); throw e; }
+}
diff --git a/scripts/test-case2.sh b/scripts/test-case2.sh
new file mode 100755
index 000000000..6f0564648
--- /dev/null
+++ b/scripts/test-case2.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+# C2-14 (#1728, row227): one acceptance run on a scratch root, no skipped steps.
+# Entry point for the real launcher/runner, C2-06 scripted adapter, fake TLS Gitea and Console DOM.
+# Run only in the serialized target slot (Mos1961); full gates/review/landing belong to the parent.
+# The only human stand-in is bindHuman in-process; a development agent cannot prove human CLI ancestry.
+set -uo pipefail
+cd "$(dirname "$0")/.."
+SANDBOX="$(mktemp -d)"
+DRIVER_PID=''
+CASE2_HOME="$SANDBOX/home"
+CASE2_CONFIG="$SANDBOX/root/config/config.json"
+mkdir -p "$CASE2_HOME"
+cleanup() {
+ local result=$?
+ trap - EXIT INT TERM
+ if [ -n "$DRIVER_PID" ]; then
+ kill -TERM "$DRIVER_PID" 2>/dev/null || true
+ wait "$DRIVER_PID" 2>/dev/null || true
+ fi
+ rm -rf "$SANDBOX"
+ exit "$result"
+}
+trap cleanup EXIT
+trap 'exit 130' INT
+trap 'exit 143' TERM
+PASS=0 FAIL=0
+if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
+ C_OK=$'\033[0;32m'; C_FAIL=$'\033[0;31m'; C_RESET=$'\033[0m'
+else
+ C_OK=''; C_FAIL=''; C_RESET=''
+fi
+check() {
+ if [ "$2" = 0 ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $1"
+ else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $1"; fi
+}
+DRIVER=scripts/case2/e2e.mjs
+STEPS='S1a S1b S2a S2b S2c S2d S3a S3b S3c S4 S5a S5b S6 S7 S8 S9 S10'
+node --check "$DRIVER" >/dev/null 2>&1
+check "syntax: $DRIVER" "$?"
+OUT="$SANDBOX/steps.txt" ERR="$SANDBOX/stderr.txt"
+env HOME="$CASE2_HOME" XDG_CONFIG_HOME="$CASE2_HOME/.config" MOSAIC_CONFIG="$CASE2_CONFIG" GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 node "$DRIVER" "$SANDBOX/root" >"$OUT" 2>"$ERR" &
+DRIVER_PID=$!
+wait "$DRIVER_PID"
+RC=$?
+DRIVER_PID=''
+check "driver ran to the end (exit $RC)" "$RC"
+if [ "$RC" -ne 0 ]; then grep '^e2e: ' "$ERR" | head -5; fi
+seen=''
+while IFS= read -r line; do
+ case "$line" in
+ 'ok '*) id="${line#ok }"; id="${id%% *}"; check "${line#ok }" 0 ;;
+ 'not ok '*) id="${line#not ok }"; id="${id%% *}"; check "${line#not ok }" 1 ;;
+ *) id=''; check 'driver line not understood (a skip cannot pass acceptance)' 1 ;;
+ esac
+ [ -n "$id" ] && seen="$seen $id"
+done <"$OUT"
+[ "${seen# }" = "$STEPS" ]
+check 'every acceptance step ran once, in order, with nothing skipped' "$?"
+if grep -qF -e 'fx227-fetch-q7-2270' -e 'fx227-seat-q7-2270' -e 'fx227-human-q7-2270' "$OUT" "$ERR"; then
+ check 'fixture credentials are in no driver output' 1
+else
+ check 'fixture credentials are in no driver output' 0
+fi
+printf '\nselftest: %s passed, %s failed, 0 skipped\n' "$PASS" "$FAIL"
+[ "$FAIL" -eq 0 ]