/** @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>) => `{ ${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)})`, result: quoted, }, complete: { label: args => `complete([${(args[0] as Record[]).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 }, }, ], ]); });