Files
sausage/host/js/chain-test.ts
T

84 lines
6.2 KiB
TypeScript

// Real bash tools and extension commands retain the nearest-first grant ancestry (D47, D63).
import { BACKGROUND_CONTEXT as context } from '@earendil-works/chord/context';
import { createModels } from '@earendil-works/pi-ai/models';
import { fauxProvider, fauxAssistantMessage, fauxToolCall } from '@earendil-works/pi-ai/providers/faux';
import { Harness, createRegistry, defineDoc, defineExtension, defineTask } from '@earendil-works/pi-durable';
import { CodingTools } from '@earendil-works/pi-durable/tools';
import { environment, conversationChains } from './environment';
import { exec, fs } from './extensions';
import { openStorage } from './sqlite';
const assert = (condition: any, message: string) => { if (!condition) throw new Error(message); };
const equal = (a: any, b: any) => assert(JSON.stringify(a) === JSON.stringify(b), 'Wrong conversation chain');
const Started = defineDoc<{ by?: number }>({ kind: 'board.started', version: 1, checkpointWhen: () => false,
scope: 'conversation', history: 'latest', fork: 'initial', initial: () => ({}) });
const Anchor = defineTask({ name: 'chain.anchor', version: 1, initial: () => ({ phase: 'done' }),
phases: { done: (_task: any, runtime: any, ctx: any) => runtime.commit(() => ({ status: 'terminal', outcome: { status: 'completed', result: null } }), ctx) } });
const models = createModels(), faux = fauxProvider({ tokensPerSecond: 100000 }); models.setProvider(faux.provider);
const registry = createRegistry(); registry.install(CodingTools); registry.install(defineExtension({ name: 'chain', tasks: [Anchor] }));
const harness = await Harness.open(await openStorage(globalThis.storagePath), { models, registry,
env: async (target: any) => environment('/', await chain(target.conversationId)) }, context);
const chain = conversationChains(harness, context);
const agent = { model: { provider: 'faux', modelId: 'faux-1' }, extensions: [CodingTools], cwd: '/' };
const root = await harness.root(context, { agent });
const owned = async (parent: any) => parent.commit(async (tx: any) => {
const taskId = await tx.createTask(Anchor, null, { ownership: { kind: 'conversation' } });
return tx.createConversation({ ownership: { kind: 'task', taskId } });
}, context);
let records: any;
if (!globalThis.chainReady) {
const parentRecord = await owned(root);
const parent = await harness.conversation(parentRecord.id, context);
const child = await owned(parent);
// Starter ancestry can repeat an owner ancestor, but the chain lists it once.
await harness.commit(async (tx: any) => { (await tx.doc(Started, child.id)).by = root.id; }, context);
const entry = await parent.commit((tx: any) => tx.appendEntry(parent.id, { kind: 'chain.origin', data: {} }), context);
const fork = await parent.commit((tx: any) => tx.forkConversation(parent.id, entry.id, { ownership: { kind: 'ownerless' } }), context);
const unrelated = await harness.commit((tx: any) => tx.createConversation({ ownership: { kind: 'ownerless' } }), context);
const branch = await harness.commit(async (tx: any) => {
const record = await tx.createConversation({ ownership: { kind: 'ownerless' } });
(await tx.doc(Started, record.id)).by = parent.id;
return record;
}, context);
const ownedFork = await root.commit(async (tx: any) => {
const taskId = await tx.createTask(Anchor, null, { ownership: { kind: 'conversation' } });
(await tx.doc(Started, fork.id)).by = unrelated.id;
return tx.forkConversation(parent.id, fork.parent.at, { ownership: { kind: 'task', taskId } });
}, context);
records = { parent: parentRecord, child, fork, branch, unrelated, ownedFork };
await fs.write('/chain-ids', JSON.stringify(records));
await harness.close(context);
} else {
records = JSON.parse(await fs.read('/chain-ids'));
const { parent, child, fork, branch, unrelated, ownedFork } = records;
harness.resume();
try {
for (const record of [child, fork, branch, unrelated]) {
const conversation = await harness.conversation(record.id, context);
await conversation.configure(agent, context);
faux.setResponses([
fauxAssistantMessage([fauxToolCall('bash', { command: `curl http://host.sausage/whoami` }, { id: `identity-${record.id}` })], { stopReason: 'toolUse' }),
fauxAssistantMessage([fauxToolCall('bash', { command: `curl -i http://127.0.0.1:${globalThis.chainPort}/token` }, { id: `fetch-${record.id}` })], { stopReason: 'toolUse' }),
fauxAssistantMessage('done'),
]);
await (await conversation.submit({ type: 'input', content: 'Check inherited access' }, context)).wait(context);
const entries = (await conversation.context(context)).entries;
const tools = entries.filter((entry: any) => entry.kind === 'pi.tool-result');
const output = (entry: any) => entry.model.flatMap((message: any) => message.content ?? []).filter((block: any) => block.type === 'text').map((block: any) => block.text).join('');
const identity = JSON.parse(output(tools[0]));
equal(identity.conversations, record.id === unrelated.id ? [String(record.id)] : [String(record.id), String(parent.id), String(root.id), ...(record.id === fork.id ? [String(unrelated.id)] : [])]);
const response = output(tools[1]);
assert(response.includes(record.id === unrelated.id ? '403' : 'accepted'), 'Wrong inherited authorization');
}
equal(await chain(ownedFork.id), [String(ownedFork.id), String(root.id)]);
assert(await harness.snapshot(Started, unrelated.id, context) === undefined, 'Missing starter must remain absent');
assert(chain(child.id) === chain(child.id), 'The chain must use its cached promise');
const denied = await exec(['curl', '-i', `http://127.0.0.1:${globalThis.chainPort}/unrelated`], { cwd: '/', conversations: await chain(child.id) });
assert(denied.stdout.includes('403'), 'An unrelated grant must not cover the child');
const delegated = await exec(['curl', 'http://host.sausage/whoami'], { cwd: '/', conversations: await chain(child.id) });
assert(delegated.code === 0, 'Extension exec failed');
equal(JSON.parse(delegated.stdout).conversations, [String(child.id), String(parent.id), String(root.id)]);
const cleared = await exec(['curl', 'http://host.sausage/whoami'], { cwd: '/', conversations: [] });
assert(cleared.code === 0, 'Explicit empty chain failed'); equal(JSON.parse(cleared.stdout).conversations, []);
} finally { await harness.close(context); }
}