test(good-fences): lend-the-mower journey (red)
The spec is a sequence diagram of the a2030.md story: I lend a governed mower; the neighbor's harness shows the model only `about`, runs its code as a tenant, and the governor stops the tipsy tenant at a 2-cell fence. The test runs the actors through a causal tracer and compares the trace with the diagram, then checks the yard and what the model saw. The tracer comes from agoric-sdk PR #12895, extended to name participants made mid-sequence (governor, tenant) and to draw thrown errors as replies. Red: src/ doesn't exist yet (ERR_MODULE_NOT_FOUND for src/fence.ts). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b4ab820486
commit
9a3e7348c6
6 files changed
+486
No files matched your search
@@ -0,0 +1,131 @@
|
||||
/** @file The lend-the-mower journey, traced and compared with its sequence diagram. */
|
||||
import './lockdown.ts';
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
import { makeFencePage } from '../src/fence.ts';
|
||||
import { makePlayground } from '../src/governor.ts';
|
||||
import { aboutMower, makeMowerFactory } from '../src/mower.ts';
|
||||
import { makeNeighbor } from '../src/neighbor.ts';
|
||||
import { makeCompartmentLoader } from '../src/tenant-loader.ts';
|
||||
import { makeYard } from '../src/yard.ts';
|
||||
import {
|
||||
type CausalSequenceViz,
|
||||
makeCausalSequenceTracer,
|
||||
md,
|
||||
mmd,
|
||||
} from '../tools/sequence-diagram-actor-sim.ts';
|
||||
|
||||
const designDoc = new URL('../docs/lend-the-mower.md', import.meta.url);
|
||||
|
||||
/** a tenant that pushes until something stops it, or gives up after 20 */
|
||||
const tipsyTenant = `(env) => {
|
||||
env.mower.start();
|
||||
try {
|
||||
for (let i = 0; i < 20; i += 1) env.mower.push();
|
||||
} catch (err) {
|
||||
return err.message;
|
||||
}
|
||||
return 'mowed 20 cells';
|
||||
}`;
|
||||
|
||||
/** `{ a, b: { c } }`: keys, and the keys of object-valued members */
|
||||
const shape = (record: Readonly<Record<string, unknown>>) =>
|
||||
`{ ${Object.entries(record)
|
||||
.map(([key, value]) =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
? `${key}: { ${Object.keys(value).join(', ')} }`
|
||||
: key,
|
||||
)
|
||||
.join(', ')} }`;
|
||||
|
||||
const quoted = (result: unknown) => `'${String(result)}'`;
|
||||
|
||||
const viz = harden({
|
||||
lend: {
|
||||
label: args => {
|
||||
const { webkey, limit } = args[0] as { webkey: string; limit: number };
|
||||
return `lend({ webkey: '${webkey}', limit: ${limit}, say })`;
|
||||
},
|
||||
},
|
||||
mower: { result: () => 'mower' },
|
||||
govern: { args: args => `mower, ${String(args[1])}`, result: () => 'governed' },
|
||||
chat: {
|
||||
label: args => `chat(${shape(args[0] as Record<string, unknown>)})`,
|
||||
result: quoted,
|
||||
},
|
||||
complete: {
|
||||
label: args =>
|
||||
`complete([${(args[0] as Record<string, unknown>[]).map(shape).join(', ')}])`,
|
||||
result: () => 'code',
|
||||
},
|
||||
load: {
|
||||
args: args => `code, { ${Object.keys(args[1] as object).join(', ')} }`,
|
||||
result: () => 'tenant',
|
||||
},
|
||||
run: { result: quoted },
|
||||
}) satisfies CausalSequenceViz;
|
||||
|
||||
test('a governed mower stops a tipsy tenant at the fence', async () => {
|
||||
const lines = (await readFile(designDoc, 'utf8')).split('\n');
|
||||
const section = md.skipToH(2, 'Lend a governed mower to a tipsy tenant')(lines);
|
||||
const diagram = md.eachFence('mermaid', section).next().value;
|
||||
if (!diagram) throw Error('lend-the-mower Mermaid block not found');
|
||||
const documented = mmd.extractArrows(diagram);
|
||||
|
||||
const ES = makeCausalSequenceTracer(viz);
|
||||
const yard = makeYard({ width: 8, height: 8 });
|
||||
const factory = makeMowerFactory(ES, {
|
||||
yard,
|
||||
size: { width: 8, height: 8 },
|
||||
mintKey: () => 'mower-key',
|
||||
});
|
||||
const key = factory.make({ x: 4, y: 4 });
|
||||
const mower = factory.mower(key);
|
||||
|
||||
const prompts: unknown[] = [];
|
||||
const model = harden({
|
||||
complete(messages: readonly unknown[]) {
|
||||
prompts.push(messages);
|
||||
return tipsyTenant;
|
||||
},
|
||||
});
|
||||
const playground = makePlayground(ES);
|
||||
const loader = makeCompartmentLoader(ES);
|
||||
const neighbor = makeNeighbor(ES, { model, loader });
|
||||
const fence = makeFencePage(ES, { factory, playground, neighbor });
|
||||
|
||||
ES.declareParticipants({
|
||||
F: fence,
|
||||
MF: factory,
|
||||
P: playground,
|
||||
M: mower,
|
||||
Y: yard,
|
||||
N: neighbor,
|
||||
L: model,
|
||||
TL: loader,
|
||||
});
|
||||
ES.declareMade({ G: [playground, 'govern'], T: [loader, 'load'] });
|
||||
|
||||
ES.start(fence);
|
||||
const outcome = ES(fence).lend({
|
||||
webkey: key,
|
||||
limit: 2,
|
||||
say: 'you can use this mower for the weekend',
|
||||
});
|
||||
|
||||
assert.deepEqual(ES.snapshot(), documented);
|
||||
assert.equal(outcome, 'fence: 2 cells from start');
|
||||
assert.deepEqual(yard.cutCells(), ['4,4', '4,3', '4,2']);
|
||||
assert.deepEqual(prompts, [
|
||||
[
|
||||
{
|
||||
role: 'user',
|
||||
content: 'you can use this mower for the weekend',
|
||||
about: { mower: aboutMower },
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
Reference in new issue
Block a user