diff --git a/docs/OBSERVATORY_INSIGHTS.md b/docs/OBSERVATORY_INSIGHTS.md new file mode 100644 index 0000000..7e58367 --- /dev/null +++ b/docs/OBSERVATORY_INSIGHTS.md @@ -0,0 +1,25 @@ +# Observatory instruments + +The expanded instruments below the spatial field provide analytics, a timestamped timeline, FLUJO worker nodes, conversations with tool calls/results, and separate Modal/Fly inventory. The sticky navigation jumps between sections; the scope picker filters them by registered authority. Section navigation preserves the authority URL fragment. Conversations open at the newest message; refresh preserves a reader's position and expanded tool calls when the transcript is unchanged. + +The two-second expiry tick refreshes time-sensitive analytics and worker/provider freshness when their effective evidence changes, even without a new transport frame or changed data object. One captured clock drives the panels. Lease expiry and the 15-second freshness boundary invalidate these panels without rebuilding the timeline or transcript, preserving scroll and tool expansion. Repeated ticks with unchanged evidence do not repaint. + +The existing `brain-swarm/1` exact-origin/frame/channel transport carries an optional `model.observatory` detail envelope. `insights-model.ts` is the positive allowlist shared verbatim with Brain Online's `factory-observatory.ts`. It requires `schemaVersion: 1`, `scope: factory-observatory`, `commands: false`, and sources matching the controller registry's exact `(sourceId, factoryId)`. Unknown extensions are stripped. Malformed/unregistered detail is erased without fabricating worker facts. A standalone same-origin swarm bridge can include this same field. `?preview=1` supplies explicitly labeled sample detail; failed live reads never load samples. + +Machine identities include provider, nullable account, app, run and resource under their registered source/factory. Retained receipts are historical; a refreshed local journal cannot establish fresh cloud health. Paid reservations are displayed separately and never converted into machine identities. Unknown paid spend remains unknown, and logical cell allocation is separate from the paid ledger. Reported conversation token totals can overlap across nested runs. + +Worker registration is a grouping mechanism. Null cell/machine bindings remain visibly unbound. Flow definitions display node labels, types and edges only. The current position comes from the conversation's `currentNodeId`; it is labeled reported running only when the conversation reports `running` and the worker observation is recent. Definitions do not imply node execution or completion. A completed conversation's position stays historical. This view has no execution controls. + +Detail bounds are 32 sources, 128 resources/source, 16 workers/source, 12 conversations/worker, 100 display messages/conversation, 512 nodes/flow, and 2,048 edges/flow. Message/tool-argument shortening is explicitly marked. Private conversation access failures erase transcripts and topology. Controller history starts with the current connection's cursor; snapshots do not reconstruct earlier controller events. + +Validation: `npm run build` and `npm run test:swarm` include namespace, graph-binding, private-field, transcript-denial, byte/window and unknown-spend checks, alongside the existing 10,000-cell and renderer evidence qualification. A JSDOM regression runs the actual instrument module through lease expiry with the same observation objects, then worker/provider freshness expiry on an idle tick; it verifies transcript/timeline identity, scroll and tool expansion are preserved. + +## O visual redesign + +The O is a mouth. Its SVG mark shares the field's deterministic authority palette. The visual language combines serif readings, monospace identities, glass surfaces, measured rules and a numbered section spine. All five observation sections remain expanded. Tool calls, operation receipts and evidence digests start expanded; longer interpretation notes live in Evidence drawers. + +The dial and field's `recent leased` figure show cells with an unexpired running lease and recent heartbeat/source evidence. Uncertain, unknown-outcome and retained records are excluded; the figure and mouth mark do not report a full lease census. The budget rule shows recorded paid holds, the timeline ribbon shows record density in three separate provenance lanes, and flow diagrams show recorded nodes and edges. These marks do not introduce health, inferred execution, reconstructed history or new controls. Node tooltips and the accessible graph description preserve full labels, types and identities. Cyclic or disconnected definitions retain every node within the SVG bounds. Completed operations remain separate from delivered software in the task reading. + +The requested FLUJO flow `6b0b98fd-aa5a-4d8a-bdf5-510bb70a7e03` (`Claude_Opus_Agent`) authored the presentation candidate in conversation `c46dc824-9cc3-45a8-9c19-eabab529f9d8`. Parent review repaired section navigation, task-status labels, graph bounds, receipt expansion and responsive readings before acceptance. The flow definition was not changed. + +The redesign builds successfully and passes 20 viewer checks. Browser qualification covers source-preserving navigation, the actual two registered FLUJO conversations (including the design run's six-node graph and 100/135-message window), the recorded $100 held/$0 available/incomplete paid ledger, expanded historical Modal/Fly receipts, and a 390px viewport without page overflow. The private local qualification harness is separate from shipped source; release and runtime adoption remain the host owner's responsibility. The shared observation schema, transport, source parsing, server redaction and read budgets are unchanged. diff --git a/docs/SWARM.md b/docs/SWARM.md new file mode 100644 index 0000000..91c46d4 --- /dev/null +++ b/docs/SWARM.md @@ -0,0 +1,110 @@ +# Brain Observatory + +`swarm.html` is the multipage FACTORY viewer built with the normal Vite build. +Brain Online embeds it under `/viewer/` from the upstream Brain image and supplies +authenticated observations. Its normal Brain viewer links to `/factory/swarm` +when hosted at `/viewer/`; standalone hosts link to `./swarm.html`. + +The component accepts public v1 snapshots from an explicit operator source +registry. It does not discover factories, receive credentials, execute work, +or infer authority links between sources. All identities include the registry +source and factory. The read-only index preserves every recorded cell, task, +effect, evidence digest and local parent relationship while rendering a bounded +subset. Search, breadcrumbs and 128-child pages navigate the complete index. + +## Hosting + +Embedded hosts pass `?embed=1&channel=<16..128 character channel>` and exchange +same-origin `brain-swarm/1` messages with the exact parent/frame: + +- viewer → parent: `ready` or `select` with the channel and a selection naming + sourceId, factoryId, kind and id; +- parent → viewer: `observe` with channel, increasing integer sequence and model; +- parent → viewer: `ping`; viewer repeats `ready`. + +The model has schemaVersion 1, scope `operator-source-registry`, commands false, +observedAt and at most 32 sources. Each source has id, label, factoryId, +observed/stale/unavailable status, a public canonical FACTORY snapshot or null, +and optional metadata events. Hash links also name the exact factory; changing +the authority behind a registry alias clears its former selection. +`model.ts` positively allowlists these facts; private extensions are dropped +and unsupported execution capability is rejected. +Never put a bearer credential in a URL, iframe message or client environment. + +A standalone deployment must supply its own authenticated same-origin JSON bridge +at `/api/factory/swarm` (or the `source` parameter's same-origin absolute path). +The Brain manager does not automatically expose credentialed FACTORY reads. +Standalone polling uses same-origin credentials, no-store reads and a five-second +delay after each observation. An unavailable bridge shows an empty/unavailable +world; there is no real-data-to-fixture fallback. + +`swarm.html?preview=1&cells=10000` explicitly generates a 10,000-cell design +fixture. Normal preview defaults to 54 cells across three independent sources. +The fixture label remains visible. Brain Online has its own explicit preview +host and uses the same upstream renderer. + +## Scale and rendering + +The full index uses iterative hierarchy layout, deterministic positions and +scoped keys. It handles deep chains and wide parents without recursion or a +force simulation. Visible nodes are capped at 600, labels at 24, and navigation +lists/search results are bounded. Ancestor/sibling context and child pages keep +hidden records reachable. A source overview has no cross-source authority edges. + +The WebGL renderer batches node points and delegation/reference lines, disposes +replaced geometry, suspends hidden documents, renders idle views only on demand, +and caps evidenced activity at 30 frames/second. Reduced motion disables pulse +and animated camera flight. Context loss switches to the 2D renderer, which +uses the same index and scoped selection. Rendering diagnostics report CPU +submission time separately from draw-call and frame counts; they do not prove +GPU timing or physical worker health. + +The Observatory design and first UI implementation were completed by Claude Opus +through a normal local FLUJO subscription-agent conversation at Medium effort +with Bash. The integration pass added camera fitting around visible instruments, +responsive reframing, measured label collision bounds, live reduced-motion +changes and a trailing diagnostics update when on-demand rendering settles. + +Static shapes also preserve status under reduced motion: authority plates use +a hollow keystone, observed idle cells an open ring, recent qualifying leased +work a bullseye, uncertain evidence an amber barred ring, and retained records +a broken grey ring. Unconfirmed running-work evidence is distinct from an +owned unknown external outcome in the inspector. Only an owned unknown outcome +outranks retirement or source staleness; it remains unresolved by completion. + +Fresh observations do not establish worker activity. A cell needs an active +controller, ready lifecycle, recent heartbeat, an unexpired running lease and +no owned unknown outcome. Completed/cancelled operations do not animate as +running work. Unknown external outcomes keep their cell's uncertainty marker +after task completion or retirement, until an observed resolution replaces them. +Pause, effect drain and worker quiescence remain separate facts. +The selected inspector preserves task, candidate and review digests and states +that quiescence is unverified. + +## Development and verification + +Run `npm ci`, `npm run build` and `npm run test:swarm`. Tests use the actual model, +marker, transport and preview modules and cover duplicate record IDs across authorities, complete +10,000-deep/wide navigation, stable layout, stale/lease/terminal activity gates, +positive allowlists and missing-source behavior. Copy the built `dist` artifact +to the consumer's ignored viewer directory for local integration; keep source +in this repository. Qualify desktop/mobile composition and 3D/2D navigation in +the browser against the built artifact before publishing the Brain image. + +The 2026-10-03 fixture qualification reached `cell-3333` in a 10,000-cell world +in both modes and preserved its source/factory identity after actual WebGL +context loss. The world had 10,003 indexed nodes; its overview drew 18 nodes and +the selected neighborhood drew 10, with two GPU draw calls in 3D and no GL calls +in 2D. Desktop, 615-pixel and 390-pixel layouts were checked. Reduced motion +stopped an already-running renderer; 2D settled without an animation loop. +These are fixture/navigation checks, not live FACTORY health evidence. + +After FACTORY's owner restored and qualified its observer, the actual Brain +Online BFF passed a fresh read at 2026-10-03T21:06:54Z: loaded build +`d9d87c6ebbd05b6c82bdbf0f7c537e76eb4b9a66`, paused admission at revision 92, +five cells, three tasks and six effects. Staff reads returned 200, anonymous +reads 401 and customer reads 404; snapshot/event responses were no-store and +contained no server credential. The actual host component and upstream artifact +also displayed these live records in a local qualification harness with a +development staff principal and five-second refreshes. This does not qualify +a production Supabase login or prove provider freshness or worker quiescence. diff --git a/index.html b/index.html index cccf657..c9b2388 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@

brain

+ ⬡ swarm
+ + Waiting for an authorized observation + READ ONLY + +
+ +
+ +
+ +
+ + + +
+
+
+ + +
+

RECORDED FACTORY SPACE

+
+
    +
  • authority
  • +
  • idle
  • +
  • leased
  • +
  • uncertain
  • +
  • retained
  • +
+

shapes report records, never host health

+
+
+
TRACKING
+
Rendering
+
+ +
+
FACTORY owns executionrecords, not host healthworker quiescence unverified
+
+ + diff --git a/test/insights-dom.test.mjs b/test/insights-dom.test.mjs new file mode 100644 index 0000000..ceb9ba3 --- /dev/null +++ b/test/insights-dom.test.mjs @@ -0,0 +1,92 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFile, writeFile, mkdir } from 'node:fs/promises'; +import { JSDOM } from 'jsdom'; +import ts from 'typescript'; + +const generated = new URL('../.vite/insights-dom-tests/', import.meta.url); +await mkdir(generated, { recursive: true }); +for (const name of ['insights', 'insights-model', 'model', 'preview', 'insights-preview', 'marks']) { + const code = await readFile(new URL(`../src/swarm/${name}.ts`, import.meta.url), 'utf8'); + const result = ts.transpileModule(code, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } }).outputText + .replace(/import '\.\/insights\.css';/, '') + .replace(/from '(\.\/[^']+)'/g, "from '$1.mjs'"); + await writeFile(new URL(`${name}.mjs`, generated), result); +} +const { mountInsights } = await import(new URL('insights.mjs', generated)); +const { swarmPreview } = await import(new URL('preview.mjs', generated)); +const { observatoryPreview } = await import(new URL('insights-preview.mjs', generated)); +const { activities } = await import(new URL('model.mjs', generated)); +const { flowDiagram } = await import(new URL('marks.mjs', generated)); + +test('section navigation retains authority fragments and completed operations stay distinct from deliveries', t => { + const previous = globalThis.document; + const dom = new JSDOM('
', { url: 'http://localhost/swarm.html#source=source-1&factory=design-authority-1&kind=source&id=source-1' }); + globalThis.document = dom.window.document; + t.after(() => { globalThis.document = previous; dom.window.close(); }); + let scrolled = false, selections = 0; + const view = mountInsights(() => selections++), swarm = swarmPreview(); + view.observe(observatoryPreview(swarm.sources), swarm); view.render(swarm, null); + document.getElementById('insights-history').scrollIntoView = () => { scrolled = true; }; + const event = new dom.window.MouseEvent('click', { bubbles: true, cancelable: true }); + document.querySelector('a[href="#insights-history"]').dispatchEvent(event); + assert.equal(event.defaultPrevented, true); assert.equal(scrolled, true); assert.equal(selections, 0); + assert.match(dom.window.location.hash, /factory=design-authority-1/); + const tasks = [...document.querySelectorAll('.metric-card')].find(card => card.querySelector('p').textContent === 'Tasks'); + assert.match(tasks.querySelector('small').textContent, /0 delivered · 3 completed/); +}); + +test('cyclic and disconnected recorded flows keep every node inside the drawing with full identities', t => { + const previous = globalThis.document, dom = new JSDOM(); globalThis.document = dom.window.document; + t.after(() => { globalThis.document = previous; dom.window.close(); }); + const nodes = ['a', 'b', 'c', 'unconnected'].map(id => ({ id, label: `Full recorded label for ${id}`, type: 'process' })); + const flow = { nodes, edges: [{ source: 'a', target: 'b' }, { source: 'b', target: 'c' }, { source: 'c', target: 'a' }] }; + const art = flowDiagram(flow, 'b', 'LAST RECORDED POSITION'), [, , width, height] = art.getAttribute('viewBox').split(' ').map(Number); + assert.equal(art.querySelectorAll('.flow-art-edge').length, flow.edges.length); + for (const group of art.querySelectorAll('.flow-art-node')) { + const [x, y] = group.getAttribute('transform').match(/[\d.]+/g).map(Number), rect = group.querySelector('rect'); + assert.ok(x >= 0 && x + Number(rect.getAttribute('width')) <= width); + assert.ok(y >= 0 && y + Number(rect.getAttribute('height')) <= height); + assert.match(group.querySelector('title').textContent, /Full recorded label for/); + } + assert.equal(art.querySelectorAll('.flow-art-node').length, nodes.length); + assert.match(art.querySelector('.is-current title').textContent, /LAST RECORDED POSITION/); +}); + +test('same-object render expires leased analytics; idle ticks expire worker/provider freshness without rebuilding transcripts', t => { + const originalDocument = globalThis.document, originalNow = Date.now, base = originalNow(); let now = base; + const dom = new JSDOM('
'); + globalThis.document = dom.window.document; Date.now = () => now; + t.after(() => { globalThis.document = originalDocument; Date.now = originalNow; dom.window.close(); }); + const swarm = swarmPreview(9); swarm.sample = false; + for (const s of swarm.sources) { + s.snapshot.observedAt = new Date(base).toISOString(); + for (const c of s.snapshot.snapshot.cells) c.heartbeat = s.snapshot.observedAt; + for (const task of s.snapshot.snapshot.tasks) if (task.status === 'running') task.leaseExpiry = new Date(base + 1000).toISOString(); + } + const detail = observatoryPreview(swarm.sources); + for (const s of detail.sources) { + for (const w of s.workers) w.observedAt = new Date(base).toISOString(); + for (const m of s.machines) m.observedAt = new Date(base).toISOString(); + } + const view = mountInsights(() => {}), selection = { sourceId: swarm.sources[0].id, kind: 'source', id: swarm.sources[0].id }; + const metric = label => [...document.querySelectorAll('.metric-card')].find(c => c.querySelector('p').textContent === label); + view.observe(detail, swarm); view.render(swarm, selection); + assert.equal(metric('Recent leased cells').querySelector('strong').textContent, '1'); + assert.match(document.querySelector('#insights-workers').textContent, /REPORTED RUNNING HERE/); + const transcript = document.querySelector('.transcript'), tool = transcript.querySelector('details'), timeline = document.querySelector('.insight-timeline'); tool.open = false; transcript.scrollTop = 23; + now = base + 2000; view.render(swarm, selection); + assert.equal(activities(swarm.sources[0], now).get('cell-1'), 'uncertain'); + assert.equal(metric('Recent leased cells').querySelector('strong').textContent, '0'); + now = base + 15001; view.tick(); + assert.match(metric('Sources').querySelector('small').textContent, /1 stale or unavailable/); + assert.match(document.querySelector('#insights-workers').textContent, /stale observation/); + assert.match(document.querySelector('#insights-workers').textContent, /LAST RECORDED POSITION/); + assert.doesNotMatch(document.querySelector('#insights-workers').textContent, /REPORTED RUNNING HERE/); + assert.match(document.querySelector('#insights-machines').textContent, /STALE PROVIDER OBSERVATION/); + assert.equal(document.querySelector('.transcript'), transcript); + assert.equal(document.querySelector('.insight-timeline'), timeline); + assert.equal(tool.open, false); assert.equal(transcript.scrollTop, 23); + const metrics = document.querySelector('.metrics-grid'); now++; view.tick(); + assert.equal(document.querySelector('.metrics-grid'), metrics, 'unchanged temporal evidence does not repaint'); +}); diff --git a/test/insights.test.mjs b/test/insights.test.mjs new file mode 100644 index 0000000..a5a0c09 --- /dev/null +++ b/test/insights.test.mjs @@ -0,0 +1,46 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFile, writeFile, mkdir } from 'node:fs/promises'; +import ts from 'typescript'; +const generated = new URL('../.vite/insights-tests/', import.meta.url); +await mkdir(generated, { recursive: true }); +for (const name of ['insights-model', 'insights-preview']) { + const code = await readFile(new URL(`../src/swarm/${name}.ts`, import.meta.url), 'utf8'); + const result = ts.transpileModule(code, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } }).outputText.replace("'./insights-model'", "'./insights-model.mjs'"); + await writeFile(new URL(`${name}.mjs`, generated), result); +} +const { parseObservatory, ObservationFloors } = await import(new URL('insights-model.mjs', generated)); +const { observatoryPreview } = await import(new URL('insights-preview.mjs', generated)); +const sources = [{ id: 'one', factoryId: 'factory-one' }, { id: 'two', factoryId: 'factory-two' }]; +test('reused worker, node, conversation and resource IDs stay independent under exact source/factory authority', () => { + const data = parseObservatory(observatoryPreview(sources), sources); + assert.equal(data.sources[0].workers[0].id, data.sources[1].workers[0].id); + assert.notEqual(data.sources[0].factoryId, data.sources[1].factoryId); + const swapped = structuredClone(data); swapped.sources[0].factoryId = 'factory-two'; assert.throws(() => parseObservatory(swapped, sources), /not registered/); + const duplicate = structuredClone(data); duplicate.sources.push(duplicate.sources[0]); assert.throws(() => parseObservatory(duplicate, sources), /Duplicate/); + const forged = structuredClone(data); forged.sources[0].machines[0].key = data.sources[0].machines[1].key; assert.throws(() => parseObservatory(forged, sources), /namespace/); +}); +test('paid floors survive failed detail and repeated rollback, then reset on authority replacement or access erasure', () => { + const floors = new ObservationFloors(), data = observatoryPreview(sources); + assert.equal(floors.accept(data, sources).sources[0].budget.revision, 12); + data.sources[0].budget.revision = 11; + assert.equal(floors.accept(data, sources).sources[0].budget, null); + assert.equal(floors.accept({ ...data, sources: [] }, sources).sources.length, 0); + assert.equal(floors.accept(data, sources).sources[0].budget, null); + floors.accept({ ...data, sources: [] }, []); + assert.equal(floors.accept(data, sources).sources[0].budget.revision, 11); + floors.reset(); assert.equal(floors.accept(data, sources).sources[0].budget.revision, 11); +}); +test('positive projection strips private extensions and rejects system messages, executable authority and unbound graph edges', () => { + const data = observatoryPreview(sources); data.token = 'PRIVATE'; data.sources[0].workers[0].origin = 'PRIVATE'; data.sources[0].workers[0].flows[0].nodes[0].properties = { prompt: 'PRIVATE' }; + assert.equal(JSON.stringify(parseObservatory(data, sources)).includes('PRIVATE'), false); + data.commands = true; assert.throws(() => parseObservatory(data, sources)); data.commands = false; + data.sources[0].workers[0].conversations[0].messages[0].role = 'system'; assert.throws(() => parseObservatory(data, sources)); + data.sources[0].workers[0].conversations[0].messages[0].role = 'user'; data.sources[0].workers[0].flows[0].edges.push({ source: 'foreign-node', target: 'build' }); assert.throws(() => parseObservatory(data, sources), /Unbound/); +}); +test('failed transcript access erases content, and windows/paid holds remain bounded without converting unknown spend to zero', () => { + const data = observatoryPreview(sources); assert.equal(parseObservatory(data, sources).sources[0].budget.meteredCents, null); + const w = data.sources[0].workers[0]; w.conversationStatus = 'unavailable'; assert.throws(() => parseObservatory(data, sources), /erased/); w.conversations = []; assert.equal(parseObservatory(data, sources).sources[0].workers[0].conversations.length, 0); + w.flows[0].nodes = Array.from({ length: 513 }, (_, i) => ({ id: String(i), label: String(i), type: 'process' })); assert.throws(() => parseObservatory(data, sources), /bound/); + w.flows = []; data.sources[0].budget.heldCents = 11000; data.sources[0].budget.availableCents = 0; data.sources[0].budget.reservations[0].heldCents = 9300; assert.equal(parseObservatory(data, sources).sources[0].budget.heldCents, 11000); +}); diff --git a/test/swarm.test.mjs b/test/swarm.test.mjs new file mode 100644 index 0000000..cc5b19a --- /dev/null +++ b/test/swarm.test.mjs @@ -0,0 +1,195 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFile, writeFile, mkdir } from 'node:fs/promises'; +import { performance } from 'node:perf_hooks'; +import ts from 'typescript'; +const generated = new URL('../.vite/swarm-tests/', import.meta.url); +await mkdir(generated, { recursive: true }); +for (const name of ['model', 'preview', 'transport', 'renderer', 'marks']) { + const source = await readFile(new URL(`../src/swarm/${name}.ts`, import.meta.url), 'utf8'); + await writeFile(new URL(`${name}.mjs`, generated), ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } }).outputText); +} +const { parseSwarm, SwarmIndex, nodeKey, activities, selectionFragment, selectionFromFragment } = await import(new URL('model.mjs', generated)); +const { swarmPreview } = await import(new URL('preview.mjs', generated)); +const { readSwarmJson } = await import(new URL('transport.mjs', generated)); +const { markerOf, activityEvidence, sourceColor } = await import(new URL('renderer.mjs', generated)); +const { sourceHue: markSourceHue } = await import(new URL('marks.mjs', generated)); + +test('the field renderer and the instrument marks read one deterministic authority palette', () => { + for (const source of swarmPreview().sources) assert.equal(markSourceHue(source.id), sourceColor(source.id)); + for (const id of ['a', 'factory-9', 'another-registry-alias', '']) assert.equal(markSourceHue(id), sourceColor(id)); +}); + +test('deep links bind a reused registry alias to its exact factory and reject ambiguous/missing authority', () => { + const swarm = swarmPreview(), source = swarm.sources[0], selected = { sourceId: source.id, kind: 'cell', id: 'root' }; + const fragment = selectionFragment(source, selected); + assert.deepEqual(selectionFromFragment(swarm, fragment), selected); + assert.equal(selectionFromFragment(swarm, fragment + '&factory=another'), null); + assert.equal(selectionFromFragment(swarm, `source=${source.id}&kind=cell&id=root`), null); + source.factoryId = 'replacement-authority'; source.snapshot.factoryId = source.factoryId; + assert.equal(selectionFromFragment(swarm, fragment), null); + assert.deepEqual(selectionFromFragment(swarm, selectionFragment(source, selected)), selected); +}); + +test('consumer bounds streamed bytes before parsing and handles split UTF-8 without corruption', async () => { + const value = new TextEncoder().encode('{"label":"ø"}'); + const response = new Response(new ReadableStream({ start(controller) { controller.enqueue(value.slice(0, 11)); controller.enqueue(value.slice(11)); controller.close(); } })); + assert.deepEqual(await readSwarmJson(response, value.length), { label: 'ø' }); + let cancelled = false; + const oversized = new Response(new ReadableStream({ pull(controller) { controller.enqueue(new Uint8Array(64)); }, cancel() { cancelled = true; } })); + await assert.rejects(readSwarmJson(oversized, 32), /exceeds its bound/); + assert.equal(cancelled, true); +}); + +test('same bare root/task/effect identities remain distinct across authorities and registration is separate', () => { + const swarm = parseSwarm(swarmPreview()); + const index = new SwarmIndex(swarm); + const keys = swarm.sources.map(source => nodeKey(source, 'cell', 'root')); + assert.equal(new Set(keys).size, 3); + for (const source of swarm.sources) { + const root = index.nodeFor({ sourceId: source.id, kind: 'cell', id: 'root' }); + assert.equal(root.source.factoryId, source.factoryId); + assert.equal(index.ancestors(root.key)[0].selection.kind, 'source'); + assert.ok(index.ancestors(root.key).every(node => node.source.id === source.id)); + assert.equal(index.nodeFor({ sourceId: source.id, kind: 'task', id: 'exploration' }).source.id, source.id); + } +}); +test('current FACTORY steering, cancellation and worker power effects remain observable', () => { + for (const [kind, state, scope] of [ + ['message', 'succeeded', 'task'], ['flow_cancel', 'cancelled', 'task'], + ['worker_wake', 'succeeded', 'worker'], ['worker_sleep', 'unknown', 'worker'], + ]) { + const swarm = swarmPreview(); + const effect = swarm.sources[2].snapshot.snapshot.effects[0]; + Object.assign(effect, { kind, state, scope }); + const parsed = parseSwarm(swarm).sources[2].snapshot.snapshot.effects[0]; + assert.deepEqual([parsed.kind, parsed.state, parsed.scope], [kind, state, scope]); + } + const unsupported = swarmPreview(); + unsupported.sources[2].snapshot.snapshot.effects[0].kind = 'arbitrary_command'; + assert.throws(() => parseSwarm(unsupported), /Unsupported observation value/); +}); +test('10,000-level delegation can be indexed, searched, selected and drawn without recursive overflow or losing focus', () => { + const swarm = swarmPreview(10000), source = swarm.sources[0]; + swarm.sources = [source]; + const template = source.snapshot.snapshot.cells[0]; + source.snapshot.snapshot.cells = Array.from({ length: 10000 }, (_, index) => ({ ...template, id: index ? `deep-${index}` : 'root', parentId: index === 0 ? null : index === 1 ? 'root' : `deep-${index - 1}`, depth: index })); + const start = performance.now(), index = new SwarmIndex(parseSwarm(swarm)); + const target = index.nodeFor({ sourceId: source.id, kind: 'cell', id: 'deep-9999' }); + assert.ok(target); + assert.equal(index.ancestors(target.key).length, 10001); + assert.equal(index.search('deep-9999')[0].id, 'deep-9999'); + const visible = index.visible(target.key); + assert.ok(visible.length <= 600); + assert.ok(visible.some(node => node.key === target.key)); + assert.ok(Number.isFinite(target.point.x)); + console.log(`Deep-swarm model qualification: ${Math.round(performance.now() - start)}ms, ${index.nodes.size} indexed / ${visible.length} drawn.`); +}); +test('10,000 siblings remain reachable across bounded child pages rather than being silently truncated', () => { + const swarm = swarmPreview(10000), source = swarm.sources[0]; swarm.sources = [source]; + const template = source.snapshot.snapshot.cells[0]; + source.snapshot.snapshot.cells = Array.from({ length: 10000 }, (_, index) => ({ ...template, id: index ? `wide-${index}` : 'root', parentId: index ? 'root' : null, depth: index ? 1 : 0 })); + const index = new SwarmIndex(parseSwarm(swarm)), root = nodeKey(source, 'cell', 'root'), reached = new Set(); + for (let page = 0; page < Math.ceil(9999 / 128); page++) { + const visible = index.visible(root, page); assert.ok(visible.length <= 600); + for (const node of visible) if (node.cell?.id !== 'root' && node.cell) reached.add(node.cell.id); + } + assert.equal(reached.size, 9999); + assert.ok(index.search('wide-9999').some(selection => selection.id === 'wide-9999')); +}); +test('spatial positions survive heartbeat and recorded-status updates', () => { + const swarm = parseSwarm(swarmPreview()), before = new SwarmIndex(swarm); + swarm.sources[0].snapshot.snapshot.cells[1].heartbeat = new Date(Date.now() + 1000).toISOString(); + swarm.sources[0].snapshot.snapshot.tasks[0].status = 'cancelled'; + const after = new SwarmIndex(swarm); + for (const [key, node] of before.nodes) assert.deepEqual(after.nodes.get(key).point, node.point); +}); + +test('large source overviews use recorded roots even when their ID is not root, and source focus shows its delegation context', () => { + const swarm = swarmPreview(3000), source = swarm.sources[0]; swarm.sources = [source]; + source.snapshot.snapshot.cells[0].id = 'coordinator-alpha'; + for (const cell of source.snapshot.snapshot.cells) if (cell.parentId === 'root') cell.parentId = 'coordinator-alpha'; + for (const task of source.snapshot.snapshot.tasks) if (task.owner === 'root') task.owner = 'coordinator-alpha'; + const index = new SwarmIndex(parseSwarm(swarm)), root = nodeKey(source, 'cell', 'coordinator-alpha'); + assert.ok(index.visible(null).some(node => node.key === root)); + assert.ok(index.visible(null).some(node => node.cell?.id === 'cell-1')); + const focused = index.visible(nodeKey(source, 'source', source.id)); + assert.ok(focused.some(node => node.key === root)); + assert.ok(focused.some(node => node.cell?.parentId === 'coordinator-alpha')); + assert.ok(focused.length <= 600); +}); +test('observation freshness never renews worker evidence, and terminal states do not activate it', () => { + const swarm = parseSwarm(swarmPreview()), source = swarm.sources[0], now = Date.parse(source.snapshot.observedAt); + assert.equal(activities(source, now).get('cell-1'), 'recent'); + assert.equal(activities(source, now + 16000).get('cell-1'), 'uncertain'); + source.snapshot.observedAt = new Date(now + 61000).toISOString(); + assert.equal(activities(source, now + 61000).get('cell-1'), 'uncertain'); + source.snapshot.snapshot.tasks[0].status = 'completed'; + assert.equal(activities(source, now + 61000).get('cell-1'), 'idle'); + source.snapshot.snapshot.tasks[0].status = 'cancelled'; + assert.equal(activities(source, now + 61000).get('cell-1'), 'idle'); + source.snapshot.snapshot.tasks[0].status = 'running'; source.status = 'stale'; + assert.equal(activities(source, now).get('cell-1'), 'uncertain'); + source.status = 'observed'; source.snapshot.snapshot.control.status = 'paused'; + assert.equal(activities(source, now).get('cell-1'), 'uncertain'); +}); +test('an unknown external outcome remains uncertain after operational completion, retirement and pause', () => { + const source = parseSwarm(swarmPreview()).sources[0], state = source.snapshot.snapshot; + const now = Date.parse(source.snapshot.observedAt), task = state.tasks[0]; + task.status = 'completed'; + state.cells.find(cell => cell.id === task.owner).status = 'retired'; + state.control.status = 'paused'; + state.effects.push({ key: 'retire-unknown', kind: 'retire', state: 'unknown', owner: task.owner, + taskId: task.id, scope: 'cleanup', scopeId: task.owner, ownerEpoch: 1, controlEpoch: 1, + createdAt: source.snapshot.observedAt, updatedAt: source.snapshot.observedAt, requestDigest: 'b'.repeat(64) }); + assert.equal(activities(source, now).get(task.owner), 'uncertain'); + assert.equal(state.workerQuiescence, 'unverified'); + state.effects[0].state = 'succeeded'; + assert.equal(activities(source, now).get(task.owner), 'idle'); +}); + +test('static markers distinguish unconfirmed running work, retained lifecycles and unresolved effects', () => { + const node = change => { + const input = swarmPreview(), source = input.sources[0], state = source.snapshot.snapshot; + change?.(source, state); + const parsed = parseSwarm(input), index = new SwarmIndex(parsed); + return index.nodeFor({ sourceId: source.id, kind: 'cell', id: 'cell-1' }); + }; + const marker = change => markerOf(node(change)); + const pausedEvidence = activityEvidence(node((_source, state) => { state.control.status = 'paused'; })); + assert.match(pausedEvidence, /running-work evidence unconfirmed/); + assert.doesNotMatch(pausedEvidence, /owned external outcome unknown/); + assert.equal(marker(), 'recent'); + assert.equal(marker((_source, state) => { state.control.status = 'paused'; }), 'uncertain'); + assert.equal(marker((_source, state) => { state.tasks[0].leaseExpiry = new Date(Date.now() - 1000).toISOString(); }), 'uncertain'); + assert.equal(marker((_source, state) => { state.cells[1].status = 'retired'; }), 'quiet'); + assert.equal(marker((_source, state) => { state.cells[1].status = 'reserved'; }), 'quiet'); + assert.equal(marker(source => { source.status = 'stale'; }), 'quiet'); + assert.equal(marker((_source, state) => { state.tasks[0].status = 'completed'; }), 'idle'); + assert.equal(marker((source, state) => { + source.status = 'stale'; state.control.status = 'paused'; state.cells[1].status = 'retired'; state.tasks[0].status = 'completed'; + state.effects.push({ key: 'owned-unknown', kind: 'retire', state: 'unknown', owner: 'cell-1', taskId: 'implementation', + scope: 'cleanup', scopeId: 'cell-1', ownerEpoch: 1, controlEpoch: 3, createdAt: source.snapshot.observedAt, + updatedAt: source.snapshot.observedAt, requestDigest: 'b'.repeat(64) }); + }), 'uncertain'); + assert.equal(marker((source, state) => { + state.effects.push({ key: 'other-owner-unknown', kind: 'retire', state: 'unknown', owner: 'cell-2', taskId: 'retire-operation', + scope: 'cleanup', scopeId: 'cell-2', ownerEpoch: 1, controlEpoch: 3, createdAt: source.snapshot.observedAt, + updatedAt: source.snapshot.observedAt, requestDigest: 'b'.repeat(64) }); + }), 'recent'); +}); +test('consumer drops private extensions and fails closed on capabilities/identity/duplicate records', () => { + const input = swarmPreview(); input.token = 'private'; input.sources[0].origin = 'private'; input.sources[0].snapshot.snapshot.tasks[2].candidate.path = 'private'; input.sources[0].snapshot.snapshot.paidBudget = { token: 'private' }; + assert.equal(JSON.stringify(parseSwarm(input)).includes('private'), false); + input.sources[0].snapshot.capabilities.commands = true; assert.throws(() => parseSwarm(input)); + input.sources[0].snapshot.capabilities.commands = false; input.sources[0].snapshot.factoryId = 'wrong'; assert.throws(() => parseSwarm(input)); + input.sources[0].snapshot.factoryId = input.sources[0].factoryId; input.sources.push(input.sources[0]); assert.throws(() => parseSwarm(input)); +}); +test('unavailable source remains in the registry without invented cells; historical hashes are preserved', () => { + const input = swarmPreview(); input.sources[1].snapshot = null; input.sources[1].status = 'unavailable'; + const swarm = parseSwarm(input), index = new SwarmIndex(swarm); + assert.equal(index.sources.size, 3); + assert.equal([...index.nodes.values()].filter(node => node.source.id === input.sources[1].id).length, 1); + const task = swarm.sources[0].snapshot.snapshot.tasks.find(task => task.status === 'cancelled'); + assert.equal(task.review.candidateDigest, task.candidate.sha256); +}); diff --git a/vite.config.ts b/vite.config.ts index 1d0abe4..d22f250 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ input: { main: resolve(__dirname, 'index.html'), lobby: resolve(__dirname, 'lobby.html'), + swarm: resolve(__dirname, 'swarm.html'), }, }, },