84 lines
6.2 KiB
TypeScript
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); }
|
|
}
|