/** @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'; }`; /** diagram-local names, bound by the reply arrows that introduce them */ const local = new WeakMap(); const bind = (name: string) => (result: unknown) => { local.set(result as object, name); return name; }; /** `{ a, b: { c: governed } }`: keys, nested objects by their keys, and * objects with a diagram-local name by that name */ const shape = (record: object): string => `{ ${Object.entries(record) .map(([key, value]) => { if (value === null || typeof value !== 'object') return key; const name = local.get(value); if (name) return `${key}: ${name}`; return Array.isArray(value) ? key : `${key}: ${shape(value)}`; }) .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: bind('mower') }, govern: { args: args => `${local.get(args[0] as object)}, ${String(args[1])}`, result: bind('governed'), }, chat: { label: args => `chat(${shape(args[0] as object)})`, result: quoted, }, complete: { label: args => `complete([${(args[0] as object[]).map(shape).join(', ')}])`, result: () => 'code', }, load: { args: args => `code, ${shape(args[1] as object)}`, 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 }, }, ], ]); });