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
⬡ brains
diff --git a/package-lock.json b/package-lock.json
index 2df3070..ebae3d2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,14 +9,144 @@
"version": "0.3.15",
"license": "MIT",
"dependencies": {
- "three": "^0.169.0"
+ "three": "0.169.0"
},
"devDependencies": {
- "@types/three": "^0.169.0",
+ "@types/three": "0.169.0",
+ "jsdom": "26.1.0",
"typescript": "^5.5.4",
"vite": "^5.4.0"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -847,6 +977,82 @@
"dev": true,
"license": "BSD-3-Clause"
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/cssstyle": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -908,6 +1114,114 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsdom": {
+ "version": "26.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.1.1",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.1",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/meshoptimizer": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
@@ -915,6 +1229,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/nanoid": {
"version": "3.3.15",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
@@ -934,6 +1255,26 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/nwsapi": {
+ "version": "2.2.28",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.28.tgz",
+ "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -970,6 +1311,16 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/rollup": {
"version": "4.62.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
@@ -1015,6 +1366,33 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1025,12 +1403,65 @@
"node": ">=0.10.0"
}
},
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/three": {
"version": "0.169.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.169.0.tgz",
"integrity": "sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w==",
"license": "MIT"
},
+ "node_modules/tldts": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^6.1.86"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tough-cookie": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^6.1.32"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -1104,6 +1535,106 @@
"optional": true
}
}
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",
+ "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
}
}
}
diff --git a/package.json b/package.json
index 0399f6c..ef2a0c2 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"standalone": "npm run build && npm install --prefix manager && npm run standalone --prefix manager",
"release": "node scripts/release.mjs",
"test:release": "node --test test/release-arguments.test.mjs",
+ "test:swarm": "node --test test/swarm.test.mjs test/insights.test.mjs test/insights-dom.test.mjs",
"dockerbuild": "gh workflow run brain-release.yml -R flujo-app/brain --ref main"
},
"keywords": [
@@ -23,6 +24,7 @@
"license": "MIT",
"devDependencies": {
"@types/three": "0.169.0",
+ "jsdom": "26.1.0",
"typescript": "^5.5.4",
"vite": "^5.4.0"
},
diff --git a/src/main.ts b/src/main.ts
index 602f228..d46b8e8 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -10,6 +10,9 @@ import { AiDock } from './ui/aichat';
import type { BrainGraph, NodeChatMessage } from './types';
const VIEW_KEY = 'brain-view';
+// Brain Online supplies its authenticated observation host when serving /viewer/.
+const swarmLink = document.getElementById('swarm-link') as HTMLAnchorElement | null;
+if (swarmLink && /^\/viewer\/(?:index\.html)?$/.test(location.pathname)) swarmLink.href = '/factory/swarm';
function setBadge(text: string, connected: boolean) {
const badge = document.getElementById('source-badge');
diff --git a/src/swarm/insights-model.ts b/src/swarm/insights-model.ts
new file mode 100644
index 0000000..f3c0a7b
--- /dev/null
+++ b/src/swarm/insights-model.ts
@@ -0,0 +1,98 @@
+/** Versioned read-only detail projection. Shared verbatim with the private host. */
+export interface Operation { id: string; label: string; state: string; observedAt: string }
+export interface Machine { key: string; provider: 'modal' | 'fly'; accountId: string | null; appId: string | null; resourceId: string | null; runId: string | null; cellId: string | null; evidenceDigest: string | null; label: string; kind: 'machine' | 'app' | 'run'; state: string; freshness: 'retained' | 'provider_observed' | 'unavailable'; observedAt: string; region: string | null; operations: Operation[] }
+export interface FlowNode { id: string; label: string; type: string }
+export interface Flow { id: string; name: string; nodes: FlowNode[]; edges: Array<{ source: string; target: string }> }
+export interface Message { id: string; role: 'user' | 'assistant' | 'tool'; content: string; truncated: boolean; at: string | null; nodeId: string | null; tools: Array<{ id: string; name: string; arguments: string }> }
+export interface Conversation { id: string; title: string; status: string; flowId: string | null; currentNodeId: string | null; updatedAt: string; tokens: number | null; estimatedCostUsd: number | null; totalMessages: number; truncated: boolean; messages: Message[] }
+export interface Worker { id: string; label: string; cellId: string | null; machineKey: string | null; availability: 'observed' | 'unavailable'; observedAt: string; conversationStatus: 'observed' | 'unavailable' | 'not_configured'; conversations: Conversation[]; flows: Flow[] }
+export interface Budget { revision: number; limitCents: number; heldCents: number; availableCents: number; meteredCents: number | null; incomplete: boolean; reservations: Array<{ id: string; provider: string; state: string; heldCents: number }> }
+export interface ObservationSource { sourceId: string; factoryId: string; observedAt: string; modalStatus: 'observed' | 'unavailable' | 'not_configured'; flyStatus: 'observed' | 'unavailable' | 'not_configured'; machines: Machine[]; workers: Worker[]; budget: Budget | null }
+export interface Observatory { schemaVersion: 1; scope: 'factory-observatory'; commands: false; observedAt: string; sources: ObservationSource[] }
+const record = (v: unknown): Record => { if (!v || typeof v !== 'object' || Array.isArray(v)) throw new Error('Invalid detail observation.'); return v as Record; };
+const text = (v: unknown, max = 128): string => { if (typeof v !== 'string' || !v.length || v.length > max) throw new Error('Invalid detail text.'); return v; };
+const body = (v: unknown, max: number): string => { if (typeof v !== 'string' || v.length > max) throw new Error('Detail content exceeds its bound.'); return v; };
+const integer = (v: unknown): number => { if (!Number.isSafeInteger(v) || (v as number) < 0) throw new Error('Invalid detail count.'); return v as number; };
+const nullable = (v: unknown): string | null => v === null ? null : text(v);
+const time = (v: unknown): string => { const s = text(v, 64); if (!/^\d{4}-\d{2}-\d{2}T/.test(s) || !Number.isFinite(Date.parse(s))) throw new Error('Invalid detail time.'); return s; };
+const choice = (v: unknown, values: readonly T[]): T => { if (!values.includes(v as T)) throw new Error('Invalid detail category.'); return v as T; };
+const list = (v: unknown, max: number): unknown[] => { if (!Array.isArray(v) || v.length > max) throw new Error('Detail observation exceeds its bound.'); return v; };
+const unique = (ids: string[]) => { if (new Set(ids).size !== ids.length) throw new Error('Duplicate detail identity.'); };
+const status = (v: unknown) => choice(v, ['observed', 'unavailable', 'not_configured']);
+export const machineKey = (provider: string, accountId: string | null, runId: string | null, resourceId: string | null, appId: string | null = null): string => JSON.stringify([provider, accountId, appId, runId, resourceId]);
+
+/** Unknown fields, worker URLs, credentials, system prompts and arbitrary node properties never cross this boundary. */
+export function parseObservatory(value: unknown, allowed: Array<{ id: string; factoryId: string }>): Observatory {
+ const input = record(value);
+ if (input.schemaVersion !== 1 || input.scope !== 'factory-observatory' || input.commands !== false) throw new Error('Unsupported detail authority.');
+ const sources = list(input.sources, 32).map(v => {
+ const s = record(v), sourceId = text(s.sourceId), factoryId = text(s.factoryId);
+ if (!allowed.some(a => a.id === sourceId && a.factoryId === factoryId)) throw new Error('Detail source is not registered.');
+ const machines = list(s.machines, 128).map(v => {
+ const m = record(v), provider = choice(m.provider, ['modal', 'fly']), accountId = nullable(m.accountId), appId = nullable(m.appId), resourceId = nullable(m.resourceId), runId = nullable(m.runId);
+ const key = body(m.key, 512);
+ if ((!resourceId && !runId) || key !== machineKey(provider, accountId, runId, resourceId, appId)) throw new Error('Invalid machine namespace.');
+ const evidenceDigest = nullable(m.evidenceDigest); if (evidenceDigest !== null && !/^[a-f0-9]{64}$/.test(evidenceDigest)) throw new Error('Invalid machine evidence.');
+ const operations = list(m.operations, 1000).map(v => { const o = record(v); return { id: text(o.id), label: text(o.label), state: text(o.state), observedAt: time(o.observedAt) }; });
+ unique(operations.map(o => o.id));
+ return { key, provider, accountId, appId, resourceId, runId, cellId: nullable(m.cellId), evidenceDigest, label: text(m.label), kind: choice(m.kind, ['machine', 'app', 'run']), state: text(m.state), freshness: choice(m.freshness, ['retained', 'provider_observed', 'unavailable']), observedAt: time(m.observedAt), region: nullable(m.region), operations };
+ });
+ unique(machines.map(m => m.key));
+ const workers = list(s.workers, 16).map(v => {
+ const w = record(v), flows = list(w.flows, 32).map(v => {
+ const f = record(v), nodes = list(f.nodes, 512).map(v => { const n = record(v); return { id: text(n.id), label: text(n.label), type: text(n.type) }; });
+ unique(nodes.map(n => n.id));
+ const edges = list(f.edges, 2048).map(v => { const e = record(v); const edge = { source: text(e.source), target: text(e.target) }; if (!nodes.some(n => n.id === edge.source) || !nodes.some(n => n.id === edge.target)) throw new Error('Unbound flow edge.'); return edge; });
+ return { id: text(f.id), name: text(f.name), nodes, edges };
+ });
+ unique(flows.map(f => f.id));
+ const conversations = list(w.conversations, 12).map(v => {
+ const c = record(v), messages = list(c.messages, 100).map(v => {
+ const m = record(v), tools = list(m.tools, 64).map(v => { const t = record(v); return { id: text(t.id), name: text(t.name), arguments: body(t.arguments, 4096) }; });
+ if (typeof m.truncated !== 'boolean') throw new Error('Invalid message window.');
+ return { id: text(m.id), role: choice(m.role, ['user', 'assistant', 'tool']), content: body(m.content, 16384), truncated: m.truncated, at: m.at === null ? null : time(m.at), nodeId: nullable(m.nodeId), tools };
+ });
+ unique(messages.map(m => m.id));
+ const totalMessages = integer(c.totalMessages);
+ if (typeof c.truncated !== 'boolean' || totalMessages < messages.length) throw new Error('Invalid transcript window.');
+ const cost = c.estimatedCostUsd;
+ if (cost !== null && (typeof cost !== 'number' || !Number.isFinite(cost) || cost < 0)) throw new Error('Invalid estimated cost.');
+ return { id: text(c.id), title: text(c.title, 256), status: text(c.status), flowId: nullable(c.flowId), currentNodeId: nullable(c.currentNodeId), updatedAt: time(c.updatedAt), tokens: c.tokens === null ? null : integer(c.tokens), estimatedCostUsd: cost as number | null, totalMessages, truncated: c.truncated, messages };
+ });
+ unique(conversations.map(c => c.id));
+ const availability = choice(w.availability, ['observed', 'unavailable']), conversationStatus = status(w.conversationStatus);
+ if (((availability === 'unavailable' || conversationStatus !== 'observed') && conversations.length) || (availability === 'unavailable' && flows.length)) throw new Error('Unavailable transcript must be erased.');
+ return { id: text(w.id), label: text(w.label), cellId: nullable(w.cellId), machineKey: w.machineKey === null ? null : body(w.machineKey, 512), availability, observedAt: time(w.observedAt), conversationStatus, conversations, flows };
+ });
+ unique(workers.map(w => w.id));
+ let budget: Budget | null = null;
+ if (s.budget !== null) {
+ const b = record(s.budget), limitCents = integer(b.limitCents), heldCents = integer(b.heldCents), availableCents = integer(b.availableCents);
+ if (availableCents !== Math.max(0, limitCents - heldCents) || typeof b.incomplete !== 'boolean') throw new Error('Invalid paid budget.');
+ const reservations = list(b.reservations, 1000).map(v => { const r = record(v); return { id: text(r.id), provider: text(r.provider), state: text(r.state), heldCents: integer(r.heldCents) }; });
+ unique(reservations.map(r => r.id));
+ if (reservations.reduce((n, r) => n + r.heldCents, 0) !== heldCents) throw new Error('Inconsistent paid reservations.');
+ budget = { revision: integer(b.revision), limitCents, heldCents, availableCents, meteredCents: b.meteredCents === null ? null : integer(b.meteredCents), incomplete: b.incomplete, reservations };
+ }
+ return { sourceId, factoryId, observedAt: time(s.observedAt), modalStatus: status(s.modalStatus), flyStatus: status(s.flyStatus), machines, workers, budget };
+ });
+ unique(sources.map(s => s.sourceId));
+ return { schemaVersion: 1, scope: 'factory-observatory', commands: false, observedAt: time(input.observedAt), sources };
+}
+
+/** Independent paid-ledger floors; unavailable private detail is never retained. */
+export class ObservationFloors {
+ private paid = new Map();
+ reset() { this.paid.clear(); }
+ accept(value: unknown, allowed: Array<{ id: string; factoryId: string }>): Observatory {
+ const keys = new Set(allowed.map(s => JSON.stringify([s.id, s.factoryId])));
+ for (const key of this.paid.keys()) if (!keys.has(key)) this.paid.delete(key);
+ const next = parseObservatory(value, allowed);
+ for (const s of next.sources) if (s.budget) {
+ const key = JSON.stringify([s.sourceId, s.factoryId]), floor = this.paid.get(key) ?? -1;
+ if (s.budget.revision < floor) s.budget = null;
+ else this.paid.set(key, s.budget.revision);
+ }
+ return next;
+ }
+}
diff --git a/src/swarm/insights-preview.ts b/src/swarm/insights-preview.ts
new file mode 100644
index 0000000..40b0685
--- /dev/null
+++ b/src/swarm/insights-preview.ts
@@ -0,0 +1,28 @@
+import { machineKey, type Observatory } from './insights-model';
+
+/** Explicit design data only. Never used after a failed live read. */
+export function observatoryPreview(sources: Array<{ id: string; factoryId: string }>): Observatory {
+ const now = new Date().toISOString(), earlier = new Date(Date.now() - 120000).toISOString();
+ return { schemaVersion: 1, scope: 'factory-observatory', commands: false, observedAt: now,
+ sources: sources.map((s, i) => {
+ const flyKey = machineKey('fly', 'sample-account/sample-app', null, 'sample-machine');
+ return { sourceId: s.id, factoryId: s.factoryId, observedAt: now, modalStatus: 'observed', flyStatus: 'observed',
+ budget: { revision: 12, limitCents: 10000, heldCents: 4200, availableCents: 5800, meteredCents: null, incomplete: true, reservations: [{ id: 'sample-modal-hold', provider: 'modal', state: 'started', heldCents: 2500 }, { id: 'sample-fly-hold', provider: 'fly', state: 'started', heldCents: 1700 }] },
+ machines: [
+ { key: machineKey('modal', null, 'sample-run', 'sample-modal-app'), provider: 'modal', accountId: null, appId: null, cellId: null, evidenceDigest: null, resourceId: 'sample-modal-app', runId: 'sample-run', label: 'Model inference', kind: 'app', state: 'current state unknown', freshness: 'retained', observedAt: now, region: null, operations: [{ id: 'deploy', label: 'deploy', state: 'succeeded', observedAt: earlier }, { id: 'generation', label: 'flujo-generation', state: i === 2 ? 'unknown' : 'succeeded', observedAt: now }] },
+ { key: flyKey, provider: 'fly', accountId: 'sample-account/sample-app', appId: null, cellId: null, evidenceDigest: null, resourceId: 'sample-machine', runId: null, label: 'Development worker', kind: 'machine', state: i === 1 ? 'stopped' : 'started', freshness: 'provider_observed', observedAt: now, region: 'iad', operations: [] },
+ ],
+ workers: [{ id: 'sample-worker', label: 'Implementation worker', cellId: null, machineKey: flyKey, availability: 'observed', observedAt: now, conversationStatus: 'observed',
+ flows: [{ id: 'sample-flow', name: 'Plan → build → review', nodes: [{ id: 'start', label: 'Receive goal', type: 'start' }, { id: 'build', label: 'Implement change', type: 'process' }, { id: 'review', label: 'Review result', type: 'process' }, { id: 'finish', label: 'Deliver', type: 'finish' }], edges: [{ source: 'start', target: 'build' }, { source: 'build', target: 'review' }, { source: 'review', target: 'finish' }] }],
+ conversations: [{ id: 'sample-conversation', title: 'Build the Observatory details', status: i === 1 ? 'completed' : 'running', flowId: 'sample-flow', currentNodeId: 'build', updatedAt: now, tokens: 18420, estimatedCostUsd: 0.82, totalMessages: 4, truncated: false,
+ messages: [
+ { id: 'm1', role: 'user', truncated: false, content: 'Show analytics, the timeline, worker nodes, conversations and both providers in one readable view.', at: earlier, nodeId: null, tools: [] },
+ { id: 'm2', role: 'assistant', truncated: false, content: 'I’m checking the source contracts and the worker’s current flow before adding the detail views.', at: earlier, nodeId: 'build', tools: [{ id: 'tool1', name: 'Bash', arguments: '{"command":"npm run build"}' }] },
+ { id: 'm3', role: 'tool', truncated: false, content: 'Build passed. The worker node and transcript stay scoped to this conversation.', at: now, nodeId: 'build', tools: [] },
+ { id: 'm4', role: 'assistant', truncated: false, content: 'The new views are ready for visual verification. Modal receipts remain historical; the Fly machine has a separate provider observation.', at: now, nodeId: 'build', tools: [] },
+ ] }],
+ }],
+ };
+ }),
+ };
+}
diff --git a/src/swarm/insights.css b/src/swarm/insights.css
new file mode 100644
index 0000000..6ecd5f6
--- /dev/null
+++ b/src/swarm/insights.css
@@ -0,0 +1,222 @@
+/* THE DECK — the instruments below the field.
+ * One long page with a numbered spine, never tabs: the reading order is the
+ * order of evidence. Figures are serif, identities and labels are mono, and
+ * the careful compliance wording lives in Evidence drawers instead of the
+ * reading flow. Every chart is drawn from observed records in SVG. */
+.swarm-shell{height:72vh;min-height:620px}
+.observatory-insights{position:relative;z-index:21;border-top:1px solid var(--rule);color:var(--text);
+ background:linear-gradient(180deg,#080d16,#06090f 40%,#05070c)}
+.observatory-insights::before{content:'';position:absolute;inset:0 0 auto;height:420px;pointer-events:none;
+ background:radial-gradient(70% 100% at 12% 0%,rgba(127,209,222,.09),transparent 62%),radial-gradient(60% 90% at 86% 4%,rgba(155,184,232,.07),transparent 60%)}
+
+/* ── Head: the O, the scope, the readings ───────────────────────────────── */
+.deck-head{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;
+ gap:clamp(16px,2.4vw,28px);padding:clamp(34px,5vw,58px) clamp(18px,4vw,64px) 0}
+.deck-mark{display:grid;place-items:center;width:56px;height:56px}
+.deck-ident{min-width:0}
+.deck-ident .insight-kicker{margin:0 0 9px}
+.deck-ident h1{font:400 clamp(28px,3.6vw,44px)/1 var(--serif);letter-spacing:-.035em;margin:0;color:#eef4fb;overflow-wrap:anywhere}
+.insight-kicker{font:var(--kicker);letter-spacing:.2em;color:var(--accent);margin:0;text-transform:uppercase}
+.insight-freshness{font:10px/1.7 var(--mono);letter-spacing:.06em;color:var(--quiet);margin:10px 0 0}
+.deck-scope{display:grid;gap:7px;justify-items:end;font:9px var(--mono);letter-spacing:.16em;color:var(--faint);text-transform:uppercase}
+.deck-scope select{padding:9px 11px;max-width:260px;font:11px var(--mono);color:var(--muted)}
+
+/* The readings band: one lead dial, five hairline-separated figures. */
+.deck-vitals{grid-column:1/-1;margin-top:clamp(24px,3vw,38px)}
+.metrics-grid{display:grid;grid-template-columns:248px repeat(5,minmax(0,1fr));border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
+.metric-card{padding:22px 20px;border-left:1px solid var(--rule);min-width:0;transition:background .2s var(--ease)}
+.metric-card:first-child{border-left:0;padding-left:0}
+.metric-card:hover{background:rgba(127,209,222,.035)}
+.metric-card .insight-kicker{color:var(--quiet);letter-spacing:.14em}
+.metric-card strong{display:block;font:400 clamp(30px,3.2vw,42px)/1 var(--serif);letter-spacing:-.03em;margin:14px 0 10px;color:#f0f7ff;font-weight:400;overflow-wrap:anywhere}
+.metric-card small{display:block;font:9px/1.7 var(--mono);letter-spacing:.07em;color:var(--faint);text-transform:uppercase}
+.metric-lead{display:grid;grid-template-rows:auto auto auto;align-content:start}
+.vital-dial{position:relative;width:132px;height:132px;display:grid;place-items:center;margin:14px 0 12px}
+.vital-dial .dial-art{position:absolute;inset:0;width:100%;height:100%}
+.vital-dial strong{margin:0;font-size:clamp(34px,3.6vw,46px);text-shadow:0 2px 26px #04060b}
+
+/* ── Body: numbered spine + the long page ───────────────────────────────── */
+.deck-body{display:grid;grid-template-columns:166px minmax(0,1fr);gap:clamp(22px,4vw,58px);padding:clamp(26px,4vw,46px) clamp(18px,4vw,64px) 76px;position:relative}
+.deck-spine{position:sticky;top:20px;align-self:start;padding-left:1px}
+.deck-spine::before{content:'VIEWS';display:block;font:9px var(--mono);letter-spacing:.2em;color:var(--faint);padding:0 0 12px 13px}
+.deck-spine ol{list-style:none;margin:0;padding:0;display:grid;gap:1px}
+.spine-link{display:grid;grid-template-columns:auto minmax(0,1fr);gap:11px;align-items:baseline;padding:12px 10px 12px 13px;
+ border-left:1px solid var(--rule);color:var(--muted);font:400 13px/1.2 var(--serif);letter-spacing:.01em;
+ transition:color .16s var(--ease),background .16s var(--ease),border-color .16s var(--ease),padding-left .16s var(--ease)}
+.spine-link:hover{padding-left:17px}
+.spine-link i{font-style:normal;font:9px var(--mono);letter-spacing:.1em;color:var(--faint)}
+.spine-link:hover{color:var(--text);border-left-color:var(--rule-strong);background:rgba(127,209,222,.04)}
+.spine-link[aria-current]{color:#eafcff;border-left-color:var(--accent);background:linear-gradient(90deg,rgba(127,209,222,.13),transparent 86%)}
+.spine-link[aria-current] i{color:var(--accent)}
+.deck-sections{min-width:0}
+.insight-section{padding:30px 0 44px;border-top:1px solid var(--rule);scroll-margin-top:20px}
+.insight-section:first-child{border-top:0;padding-top:4px}
+.section-head{margin-bottom:22px}
+.section-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 16px}
+.section-numeral{font:9px var(--mono);letter-spacing:.2em;color:var(--faint)}
+.insight-section h2{font:400 clamp(24px,2.7vw,34px)/1 var(--serif);letter-spacing:-.035em;margin:0;color:#eef4fb}
+.insight-subtitle{font:10px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--quiet);margin:0}
+.insight-meta{font:10px/1.7 var(--mono);color:var(--quiet);margin:0}
+.insight-empty{font:10px var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--faint);border:1px dashed var(--rule);border-radius:3px;padding:16px;margin:12px 0}
+.insight-warning{color:var(--warn);font:10px/1.7 var(--mono)}
+.insight-content h3{font:400 18px var(--serif);margin:0;color:#e4edf7}
+.insight-content h4{font:400 17px var(--serif);margin:10px 0 6px;color:#e4edf7}
+.insight-content code{font:10px/1.6 var(--mono);color:var(--quiet);overflow-wrap:anywhere}
+
+/* ── 01 Ledger ──────────────────────────────────────────────────────────── */
+.budget-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,330px),1fr));gap:18px}
+.budget-card{border:1px solid var(--rule);background:linear-gradient(180deg,rgba(14,20,31,.7),rgba(9,13,21,.7));border-radius:4px;padding:22px;position:relative;overflow:hidden}
+.budget-card::before{content:'';position:absolute;left:0;top:0;width:36px;height:1px;background:var(--accent);opacity:.8}
+.budget-heading{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
+.budget-heading h3{margin-right:auto}
+.budget-total{font:400 clamp(26px,2.6vw,34px)/1 var(--serif);letter-spacing:-.03em;color:#f0f7ff;margin:16px 0 12px}
+.budget-art{display:block;width:100%;height:32px}
+.budget-legend{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:0;margin:14px 0 0;font:10px var(--mono)}
+.budget-legend dt{font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin:0 0 5px}
+.budget-legend dd{margin:0 10px 0 0;color:#dce7f4}
+
+/* ── 02 Timeline ────────────────────────────────────────────────────────── */
+.ribbon{border:1px solid var(--rule);border-radius:3px;background:rgba(9,13,21,.6);padding:12px 12px 6px}
+.ribbon-art{display:block;width:100%;height:84px}
+.ribbon-axis{display:flex;justify-content:space-between;gap:12px;font:9px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--faint);margin:8px 2px 0}
+.ribbon-legend{display:flex;flex-wrap:wrap;gap:8px 20px;list-style:none;margin:14px 0 22px;padding:0;font:9px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--quiet)}
+.ribbon-legend li{display:flex;align-items:center;gap:8px}
+.ribbon-legend i{width:9px;height:9px;border-radius:2px;background:var(--signal)}
+.ribbon-legend .lane-1 i{background:#9ed7a6}
+.ribbon-legend .lane-2 i{background:#b6a6ea}
+.insight-timeline{list-style:none;padding:0;margin:0;max-height:480px;overflow:auto}
+.insight-timeline li{display:grid;grid-template-columns:14px 78px minmax(0,1fr);gap:16px;padding:11px 0;border-bottom:1px solid var(--rule);position:relative}
+.insight-timeline li::before{content:'';width:7px;height:7px;border-radius:50%;background:var(--signal);margin-top:5px;opacity:.85}
+.insight-timeline .lane-1::before{background:#9ed7a6}
+.insight-timeline .lane-2::before{background:#b6a6ea}
+.insight-timeline time{font:10px/1.7 var(--mono);letter-spacing:.06em;color:var(--faint);cursor:help}
+.insight-timeline strong{display:block;font:11px/1.6 var(--mono);font-weight:400;color:#dde7f3;letter-spacing:.04em}
+.insight-timeline small{display:block;font:9px/1.7 var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--faint);margin-top:3px}
+.insight-timeline p{font:12.5px/1.65 var(--sans);margin:6px 0 0;color:var(--muted);overflow-wrap:anywhere}
+
+/* ── 03 Flow ────────────────────────────────────────────────────────────── */
+.worker-card{border:1px solid var(--rule);border-radius:4px;background:rgba(10,15,24,.6);padding:22px;margin-bottom:16px}
+.worker-heading{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}
+.worker-title h3{margin:0}
+.worker-title .insight-meta{margin-top:5px;letter-spacing:.12em;text-transform:uppercase;font-size:9px}
+.worker-chips{display:flex;flex-wrap:wrap;gap:7px;justify-content:flex-end}
+.worker-current{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;margin:18px 0 4px;padding-top:16px;border-top:1px solid var(--rule)}
+.worker-current strong{font:400 19px/1.3 var(--serif);color:#eaf2fa;letter-spacing:-.015em}
+.worker-flow-name{font:10px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--quiet)}
+.flow-nodes{margin:16px 0 10px;padding:14px 2px 6px;overflow-x:auto;border-top:1px solid var(--rule)}
+.flow-art{display:block;max-width:100%;height:auto;min-width:420px}
+.flow-art-label{font:13px var(--sans);fill:#dde7f3}
+.flow-art-type{font:8px var(--mono);letter-spacing:.18em;fill:var(--faint)}
+.flow-art-badge{font:8px var(--mono);letter-spacing:.18em;fill:var(--accent)}
+.flow-art-node.is-current .flow-art-label{fill:#f2fbff}
+.flow-foot{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;font:9px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
+
+/* ── 04 Transcript ──────────────────────────────────────────────────────── */
+.insight-conversations input[type=search]{max-width:420px;margin-bottom:18px;font:11px var(--mono);padding:10px 12px}
+.conversation-layout{display:grid;grid-template-columns:252px minmax(0,1fr);gap:18px;align-items:start}
+.conversation-list{max-height:640px;overflow:auto;display:grid;gap:1px}
+.conversation-list button{width:100%;padding:14px 14px 14px 15px;text-align:left;font:13px/1.45 var(--sans);color:var(--muted);
+ border:0;border-left:1px solid var(--rule);border-radius:0;background:transparent}
+.conversation-list button small{display:block;margin-top:6px;font:9px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
+.conversation-list button:hover{background:rgba(127,209,222,.05);border-left-color:var(--rule-strong);color:var(--text)}
+.conversation-list button[aria-pressed=true]{color:#eafcff;border-left-color:var(--accent);background:linear-gradient(90deg,rgba(127,209,222,.12),transparent 88%)}
+.transcript{border:1px solid var(--rule);border-radius:4px;max-height:720px;overflow:auto;background:linear-gradient(180deg,rgba(11,16,26,.85),rgba(7,11,18,.85));padding:24px 26px}
+.transcript>header{border-bottom:1px solid var(--rule);padding-bottom:14px;margin-bottom:6px;position:sticky;top:-24px;background:#080c14;z-index:2}
+.transcript>header h3{font:400 21px var(--serif);letter-spacing:-.02em}
+.transcript>header .insight-meta{margin:6px 0 10px}
+.transcript-chips{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:10px}
+.transcript>header code{display:block;margin-bottom:10px}
+.transcript>header button{font:9px var(--mono);letter-spacing:.14em;text-transform:uppercase;padding:6px 10px}
+.transcript-message{padding:18px 0 18px 16px;border-bottom:1px solid var(--rule);position:relative}
+.transcript-message::before{content:'';position:absolute;left:0;top:22px;bottom:22px;width:1px;background:var(--rule-strong);opacity:.7}
+.message-assistant::before{background:var(--signal)}
+.message-user::before{background:#c8bcff}
+.message-tool::before{background:#e0bb80}
+.transcript-message header{display:flex;gap:14px;justify-content:space-between;align-items:baseline}
+.transcript-message strong{font:9px var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--signal);font-weight:400}
+.message-user strong{color:#c8bcff}
+.message-tool strong{color:#e0bb80}
+.transcript-message header small{font:9px var(--mono);letter-spacing:.08em;color:var(--faint)}
+.message-content{white-space:pre-wrap;overflow-wrap:anywhere;font:13.5px/1.8 var(--sans);color:#d4dde9;margin:10px 0 0}
+.transcript-tool{border:1px solid var(--rule);border-left:2px solid #e0bb8099;padding:11px 13px;margin-top:12px;background:rgba(16,24,37,.75);border-radius:3px}
+.transcript-tool summary{font:9px var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
+.transcript-tool pre{font:11px/1.65 var(--mono);white-space:pre-wrap;overflow-wrap:anywhere;color:var(--quiet);margin:10px 0 0}
+
+/* ── 05 Providers ───────────────────────────────────────────────────────── */
+.provider-grid{display:grid;grid-template-columns:1fr 1fr;gap:24px}
+.provider-column{min-width:0}
+.provider-head{display:flex;align-items:baseline;gap:12px;border-bottom:1px solid var(--rule);padding-bottom:12px;margin-bottom:14px}
+.provider-head h3{display:flex;align-items:center;gap:10px}
+.provider-head h3::before{content:'';width:10px;height:10px;border-radius:2px;transform:rotate(45deg);background:#a8c697}
+.provider-fly .provider-head h3::before{background:#b1a4eb;border-radius:50%;transform:none}
+.provider-head .insight-meta{margin-left:auto;letter-spacing:.12em;text-transform:uppercase;font-size:9px}
+.machine-card,.reservation-card{border:1px solid var(--rule);border-radius:4px;background:rgba(11,16,26,.62);padding:18px;margin-bottom:12px}
+/* A provider receipt is a slip: a perforated top edge, then the figures. */
+.machine-card{position:relative;padding-top:20px}
+.machine-card::before{content:'';position:absolute;left:0;right:0;top:0;height:3px;
+ background:repeating-linear-gradient(90deg,var(--rule-strong) 0 4px,transparent 4px 9px);opacity:.75}
+.machine-card.is-retained::before{opacity:.4}
+.machine-top{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:12px}
+.machine-scope{font:9px var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--faint);margin-left:auto}
+.machine-card h4{margin:0 0 6px}
+.machine-state{font:11px var(--mono);letter-spacing:.08em;text-transform:uppercase;color:#d7e2f0;margin:0 0 8px}
+.machine-facts{list-style:none;display:flex;flex-wrap:wrap;gap:6px 10px;margin:12px 0 0;padding:0;font:9px var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--quiet)}
+.machine-facts li{border:1px solid var(--rule);border-radius:2px;padding:4px 7px;overflow-wrap:anywhere}
+.machine-card>small,.reservation-card>small{display:block;margin-top:12px;font:9px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
+.machine-receipts{margin-top:14px;border-top:1px solid var(--rule);padding-top:12px}
+.machine-receipts summary{font:9px var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--quiet)}
+.machine-receipts p{display:flex;justify-content:space-between;gap:12px;margin:8px 0 0;font:10px var(--mono)}
+.machine-receipts p b{font-weight:400;color:#d7e2f0}
+.machine-receipts code{display:block;margin-top:8px}
+.reservation-card{border-style:dashed;background:rgba(14,18,28,.45)}
+.reservation-card p{display:flex;justify-content:space-between;gap:12px;margin:8px 0 0;font:10px var(--mono);color:var(--quiet)}
+.reservation-card p b{font-weight:400;color:#d7e2f0}
+
+/* ── Responsive framing ─────────────────────────────────────────────────── */
+@media(max-width:1280px){
+ .metrics-grid{grid-template-columns:220px repeat(3,minmax(0,1fr))}
+ .metric-lead{grid-row:span 2}
+ .metric-card:nth-child(6){grid-column:span 2}
+ .metric-card:nth-child(n+5){border-top:1px solid var(--rule)}
+}
+@media(max-width:1000px){
+ .deck-body{grid-template-columns:minmax(0,1fr);gap:18px}
+ .deck-spine{position:sticky;top:0;z-index:30;margin:0 -16px;padding:10px 16px;
+ background:rgba(7,10,17,.92);backdrop-filter:blur(12px);border-bottom:1px solid var(--rule)}
+ .deck-spine::before{display:none}
+ .deck-spine ol{grid-auto-flow:column;grid-auto-columns:max-content;overflow:auto;gap:6px}
+ .spine-link{border-left:0;border-bottom:1px solid var(--rule);padding:8px 12px;white-space:nowrap}
+ .spine-link[aria-current]{border-left:0;border-bottom-color:var(--accent);background:rgba(127,209,222,.1)}
+ .insight-section{scroll-margin-top:66px}
+ .conversation-layout{grid-template-columns:1fr}
+ .conversation-list{max-height:240px}
+ .provider-grid{grid-template-columns:1fr}
+}
+@media(max-width:800px){
+ .swarm-shell{height:auto;min-height:0}
+ .deck-head{grid-template-columns:auto minmax(0,1fr);gap:14px}
+ .deck-scope{grid-column:1/-1;justify-items:start;margin-top:4px}
+ .deck-scope select{max-width:100%;width:100%}
+ .metrics-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
+ .metric-card{padding:16px 14px;border-top:1px solid var(--rule)}
+ .metric-card:nth-child(odd){border-left:0;padding-left:0}
+ .metric-lead{grid-column:1/-1;grid-row:auto}
+ .metric-card:nth-child(6){grid-column:auto}
+ .vital-dial{width:112px;height:112px}
+ .insight-timeline li{grid-template-columns:14px minmax(0,1fr);gap:6px 12px}
+ .insight-timeline time{grid-column:2}
+ .worker-chips{justify-content:flex-start}
+ .transcript{padding:18px}
+ .transcript>header{top:-18px}
+ .transcript-message header{display:block}
+ .budget-legend{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 0}
+}
+@media(max-width:520px){
+ .deck-head{padding-top:28px}
+ .deck-ident h1{font-size:26px}
+ .metric-card{padding:14px 12px}
+ .metric-card strong{font-size:30px;margin:10px 0 8px}
+ .machine-facts{font-size:8.5px}
+ .worker-card,.budget-card,.machine-card,.reservation-card{padding:16px}
+ .ribbon-art{height:66px}
+}
diff --git a/src/swarm/insights.ts b/src/swarm/insights.ts
new file mode 100644
index 0000000..3e10901
--- /dev/null
+++ b/src/swarm/insights.ts
@@ -0,0 +1,308 @@
+import './insights.css';
+import { ObservationFloors, type Observatory, type ObservationSource, type Worker, type Conversation } from './insights-model';
+import { activities, type Swarm, type Selection } from './model';
+import { arcDial, budgetRule, flowDiagram, oMark, ribbon, sourceHue, type Segment } from './marks';
+
+const el = (tag: T, text?: string, cls?: string): HTMLElementTagNameMap[T] => { const n = document.createElement(tag); if (text !== undefined) n.textContent = text; if (cls) n.className = cls; return n; };
+const btn = (label: string, action: () => void) => { const b = el('button', label); b.type = 'button'; b.onclick = action; return b; };
+const time = (v: string) => new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'medium' }).format(new Date(v));
+const atTime = (v: string) => new Intl.DateTimeFormat(undefined, { timeStyle: 'medium' }).format(new Date(v));
+const money = (c: number) => `$${(c / 100).toFixed(2)}`;
+const key = (s: ObservationSource, w: Worker, c: Conversation) => JSON.stringify([s.sourceId, s.factoryId, w.id, c.id]);
+const empty = (): Observatory => ({ schemaVersion: 1, scope: 'factory-observatory', commands: false, observedAt: new Date().toISOString(), sources: [] });
+/** Short, factual badge. The long wording lives in its tooltip and in the
+ * section's Evidence drawer, never in the reading flow. */
+const chip = (text: string, tone?: string, title?: string) => { const s = el('span', text, tone ? `chip chip-${tone}` : 'chip'); if (title) s.title = title; return s; };
+const drawer = (summary: string, lines: string[]) => { const d = el('details', undefined, 'evidence-drawer'); d.append(el('summary', summary)); for (const line of lines) d.append(el('p', line)); return d; };
+const pad = (n: number) => String(n).padStart(2, '0');
+
+/** Section order is the reading order: the deck is one long page, never tabs. */
+const SECTIONS: Array<[string, string, string, string[]]> = [
+ ['analytics', 'Ledger', 'Paid budget per registered authority.', ['Held, limit and metered figures come from the paid ledger at its own revision. A logical cell allocation is a separate measure.', 'Final paid billing is not established by this contract.']],
+ ['history', 'Timeline', 'Controller events, provider receipts and transcript stamps.', ['Each lane keeps its own provenance and is not merged into a single source of truth.', 'Controller history starts when this view connects; snapshot state does not reconstruct earlier events.']],
+ ['workers', 'Flow', 'Recorded flow structure and the reported execution position.', ['The marked node comes from the conversation. Every other node is flow definition: completion, idle state and worker health are not inferred.', 'Machine-to-worker and cell-to-worker relationships stay unbound unless they were recorded.']],
+ ['conversations', 'Transcript', 'The recorded conversation, including tool calls.', ['Messages and tool arguments are displayed inside a bounded window; a shortened record is marked where it occurs.']],
+ ['machines', 'Providers', 'Modal and Fly observations.', ['Provider observations are independent of cell lifecycle and task leases, and provider state does not establish worker health.', 'A reservation is spending evidence. It does not identify or prove a running machine.']],
+];
+
+export function mountInsights(onSelect: (selection: Selection | null) => void) {
+ let swarm: Swarm, selection: Selection | null = null, detail = empty(), chosen: string | null = null, filter = '', signature = '', temporalSignature = '', transcriptSignature = '', sourceSignature = '', transcriptKey: string | null = null, clock = Date.now();
+ const floors = new ObservationFloors();
+ const root = el('section', undefined, 'observatory-insights'); root.id = 'observatory-insights'; root.setAttribute('aria-label', 'Observatory instruments');
+ const head = el('header', undefined, 'deck-head');
+ const markSlot = el('div', undefined, 'deck-mark');
+ const ident = el('div', undefined, 'deck-ident');
+ const scopeTitle = el('h1', 'All registered sources');
+ const freshness = el('p', 'Waiting for observations', 'insight-freshness'); freshness.setAttribute('role', 'status');
+ ident.append(el('p', 'OBSERVATORY INSTRUMENTS', 'insight-kicker'), scopeTitle, freshness);
+ const sourcePicker = el('select'); sourcePicker.setAttribute('aria-label', 'Observation source');
+ sourcePicker.onchange = () => onSelect(sourcePicker.value ? { sourceId: sourcePicker.value, kind: 'source', id: sourcePicker.value } : null);
+ const scope = el('label', undefined, 'deck-scope'); scope.append(el('span', 'SCOPE'), sourcePicker);
+ const vitals = el('div', undefined, 'deck-vitals');
+ head.append(markSlot, ident, scope, vitals);
+ const body = el('div', undefined, 'deck-body');
+ const spine = el('nav', undefined, 'deck-spine'); spine.setAttribute('aria-label', 'Observation views');
+ const spineList = el('ol'); spine.append(spineList);
+ const column = el('div', undefined, 'deck-sections');
+ const panels = new Map(), links = new Map();
+ for (const [index, [id, label, note, evidence]] of SECTIONS.entries()) {
+ const link = el('a', undefined, 'spine-link'); link.href = `#insights-${id}`;
+ // The URL fragment belongs to the authority selection, not section navigation.
+ link.onclick = event => { event.preventDefault(); document.getElementById(`insights-${id}`)!.scrollIntoView({ block: 'start' }); };
+ link.append(el('i', pad(index + 1)), el('span', label));
+ const item = el('li'); item.append(link); spineList.append(item); links.set(id, link);
+ const section = el('section', undefined, `insight-section insight-${id}`); section.id = `insights-${id}`;
+ const header = el('header', undefined, 'section-head');
+ const line = el('div', undefined, 'section-line');
+ line.append(el('span', pad(index + 1), 'section-numeral'), el('h2', label), el('p', note, 'insight-subtitle'));
+ header.append(line, drawer('Evidence', evidence));
+ const content = el('div', undefined, 'insight-content'); panels.set(id, content);
+ section.append(header, content); column.append(section);
+ }
+ body.append(spine, column); root.append(head, body);
+ document.querySelector('#swarm-app > footer')!.before(root);
+ const shortcut = btn('Instruments', () => root.scrollIntoView({ block: 'start' })); shortcut.id = 'swarm-instruments';
+ document.getElementById('swarm-find')!.after(shortcut);
+ if (typeof IntersectionObserver === 'function') {
+ const watcher = new IntersectionObserver(entries => {
+ for (const entry of entries) {
+ const link = links.get(entry.target.id.replace('insights-', ''));
+ if (link && entry.isIntersecting) { for (const other of links.values()) other.removeAttribute('aria-current'); link.setAttribute('aria-current', 'true'); }
+ }
+ }, { rootMargin: '-15% 0px -70% 0px' });
+ for (const id of links.keys()) watcher.observe(document.getElementById(`insights-${id}`)!);
+ }
+ const search = el('input'); search.type = 'search'; search.placeholder = 'Find a conversation'; search.setAttribute('aria-label', 'Find a conversation'); search.oninput = () => { filter = search.value; paintConversations(); };
+ const conversationContent = panels.get('conversations')!, conversationBody = el('div', undefined, 'conversation-layout'); conversationContent.append(search, conversationBody);
+ function scoped() { return detail.sources.filter(s => !selection || s.sourceId === selection.sourceId); }
+ function allConversations() { return scoped().flatMap(s => s.workers.flatMap(w => w.conversations.map(c => ({ s, w, c, key: key(s, w, c) })))); }
+ function currentConversation() { const list = allConversations(); return list.find(c => c.key === chosen) ?? list[0]; }
+ function scopeLabel(s: ObservationSource) { return swarm.sources.find(a => a.id === s.sourceId)?.label ?? s.sourceId; }
+ function recent(observedAt: string) { const age = clock - Date.parse(observedAt); return swarm.sample || (age >= 0 && age <= 15000); }
+ function sources() { return swarm.sources.filter(s => !selection || s.id === selection.sourceId); }
+ function timeSignature() {
+ return JSON.stringify([
+ sources().map(s => [s.status === 'observed' && !!s.snapshot && recent(s.snapshot.observedAt), [...activities(s, clock, swarm.sample).values()].filter(a => a === 'recent').length]),
+ scoped().map(s => [s.workers.map(w => recent(w.observedAt)), s.machines.map(m => recent(m.observedAt))]),
+ ]);
+ }
+ /** One reading: label, figure, and the shortest honest qualifier. */
+ function card(label: string, value: string, note: string, extra = '') { const c = el('article', undefined, `metric-card${extra ? ` ${extra}` : ''}`); c.append(el('p', label, 'insight-kicker'), el('strong', value), el('small', note)); return c; }
+ function paintAnalytics() {
+ const content = panels.get('analytics')!; content.replaceChildren();
+ const observed = sources(), details = scoped();
+ const tasks = observed.flatMap(s => s.snapshot?.snapshot.tasks ?? []), effects = observed.flatMap(s => s.snapshot?.snapshot.effects ?? []);
+ const activity = observed.flatMap(s => [...activities(s, clock, swarm.sample).values()]);
+ const leased = activity.filter(a => a === 'recent').length;
+ const conversations = allConversations(), knownTokens = conversations.filter(v => v.c.tokens !== null);
+ const stale = observed.filter(s => s.status !== 'observed' || !s.snapshot || !recent(s.snapshot.observedAt)).length;
+ const grid = el('div', undefined, 'metrics-grid');
+ const leadCard = card('Recent leased cells', String(leased), `of ${activity.length} recorded cells · records, not host health`, 'metric-lead');
+ const dial = el('div', undefined, 'vital-dial'); dial.append(arcDial(leased, activity.length), leadCard.querySelector('strong')!);
+ leadCard.insertBefore(dial, leadCard.querySelector('small'));
+ grid.append(leadCard,
+ card('Sources', String(observed.length), `${stale} stale or unavailable`),
+ card('Tasks', String(tasks.length), `${tasks.filter(t => t.status === 'running').length} running · ${tasks.filter(t => t.status === 'delivered').length} delivered · ${tasks.filter(t => t.status === 'completed').length} completed`),
+ card('Unknown effects', String(effects.filter(e => e.state === 'unknown').length), 'awaiting reconciliation'),
+ card('Conversations', String(conversations.length), `${conversations.filter(v => v.c.status === 'running' && recent(v.w.observedAt)).length} reported running · ${details.reduce((n, s) => n + s.workers.length, 0)} workers`),
+ card('Reported tokens', knownTokens.length ? knownTokens.reduce((n, v) => n + v.c.tokens!, 0).toLocaleString() : 'Unknown', `${knownTokens.length} reported totals · nested runs may overlap`));
+ vitals.replaceChildren(grid);
+ const ledger = el('div', undefined, 'budget-grid');
+ for (const s of details) if (s.budget) {
+ const b = s.budget, panel = el('article', undefined, 'budget-card');
+ const heading = el('div', undefined, 'budget-heading');
+ heading.append(el('h3', scopeLabel(s)), chip(`REV ${b.revision}`, 'quiet', 'Paid ledger revision'));
+ if (b.incomplete) heading.append(chip('BILLING INCOMPLETE', 'warn', 'Some charges have not been reported yet.'));
+ panel.append(heading, el('p', money(b.heldCents), 'budget-total'), budgetRule(b.heldCents, b.limitCents, b.meteredCents));
+ const legend = el('dl', undefined, 'budget-legend');
+ for (const [term, value] of [['held', money(b.heldCents)], ['limit', money(b.limitCents)], ['available', money(b.availableCents)], ['metered', b.meteredCents === null ? 'unknown' : money(b.meteredCents)]] as Array<[string, string]>) {
+ const reading = el('div'); reading.append(el('dt', term), el('dd', value)); legend.append(reading);
+ }
+ panel.append(legend);
+ if (b.heldCents > b.limitCents) panel.append(el('p', `${money(b.heldCents - b.limitCents)} over committed`, 'insight-warning'));
+ ledger.append(panel);
+ }
+ if (!ledger.childElementCount) ledger.append(el('p', 'No paid budget observation.', 'insight-empty'));
+ content.append(ledger);
+ }
+ const LANES = ['var(--signal)', '#9ed7a6', '#b6a6ea'], LANE_NAMES = ['controller', 'provider receipt', 'transcript'];
+ function paintHistory() {
+ const content = panels.get('history')!, rows: Array<{ at: string; lane: 0 | 1 | 2; label: string; source: string; subject: string }> = [];
+ for (const s of sources()) for (const e of s.events) rows.push({ at: e.observedAt, lane: 0, label: e.type, source: `${s.label} · controller #${e.seq}`, subject: e.subject });
+ for (const s of scoped()) for (const m of s.machines) for (const o of m.operations) rows.push({ at: o.observedAt, lane: 1, label: `${o.label} · ${o.state}`, source: `${scopeLabel(s)} · ${m.provider} receipt`, subject: m.runId ?? m.resourceId ?? '' });
+ for (const { s, w, c } of allConversations()) for (const m of c.messages) if (m.at) rows.push({ at: m.at, lane: 2, label: `${m.role}${m.tools.length ? ` · ${m.tools.map(t => t.name).join(', ')}` : ''}`, source: `${scopeLabel(s)} · ${w.label}`, subject: m.content.slice(0, 180) || 'Tool call' });
+ rows.sort((a, b) => Date.parse(b.at) - Date.parse(a.at)); content.replaceChildren();
+ if (!rows.length) { content.append(el('p', 'No timestamped record observed.', 'insight-empty')); return; }
+ const chart = el('div', undefined, 'ribbon');
+ chart.append(ribbon(rows.map(r => ({ at: Date.parse(r.at), lane: r.lane })), LANES));
+ const axis = el('div', undefined, 'ribbon-axis');
+ axis.append(el('span', atTime(rows[rows.length - 1]!.at)), el('span', `${rows.length} records`), el('span', atTime(rows[0]!.at)));
+ const legend = el('ul', undefined, 'ribbon-legend');
+ for (const [index, name] of LANE_NAMES.entries()) { const item = el('li', undefined, `lane-${index}`); item.append(el('i'), el('span', `${name} · ${rows.filter(r => r.lane === index).length}`)); legend.append(item); }
+ content.append(chart, axis, legend);
+ const list = el('ol', undefined, 'insight-timeline');
+ for (const row of rows.slice(0, 200)) {
+ // Every record sits inside the observed window, so the clock carries the
+ // reading and the full stamp stays available on the element itself.
+ const item = el('li', undefined, `lane-${row.lane}`); const stamp = el('time', atTime(row.at)); stamp.dateTime = row.at; stamp.title = time(row.at);
+ const text = el('div'); text.append(el('strong', row.label), el('small', row.source), el('p', row.subject));
+ item.append(stamp, text); list.append(item);
+ }
+ if (rows.length > 200) content.append(el('p', `newest 200 of ${rows.length}`, 'insight-meta'));
+ content.append(list);
+ }
+ function paintWorkers() {
+ const content = panels.get('workers')!; content.replaceChildren(); const selected = currentConversation();
+ for (const s of scoped()) for (const w of s.workers) {
+ const c = selected?.w === w ? selected.c : w.conversations[0], f = w.flows.find(f => f.id === c?.flowId), section = el('article', undefined, 'worker-card');
+ const fresh = w.availability === 'observed' && recent(w.observedAt);
+ const heading = el('div', undefined, 'worker-heading');
+ const title = el('div', undefined, 'worker-title');
+ title.append(el('h3', w.label), el('p', scopeLabel(s), 'insight-meta'));
+ const marks = el('div', undefined, 'worker-chips');
+ marks.append(chip(w.availability === 'observed' && !recent(w.observedAt) ? 'stale observation' : w.availability, fresh ? 'live' : 'quiet', `Worker observation read ${time(w.observedAt)}`));
+ marks.append(chip(w.cellId ? `cell ${w.cellId}` : 'no cell binding', 'quiet', 'Recorded FACTORY cell binding'));
+ marks.append(chip(w.machineKey ? 'machine bound' : 'no machine binding', 'quiet', 'Recorded machine binding'));
+ heading.append(title, marks); section.append(heading);
+ if (!c) section.append(el('p', w.conversationStatus === 'not_configured' ? 'No conversation registered.' : 'Conversation access unavailable · transcript and position cleared.', 'insight-empty'));
+ else {
+ const current = el('div', undefined, 'worker-current');
+ current.append(el('strong', c.title), chip(c.status, c.status === 'running' && fresh ? 'live' : 'quiet'), el('span', `${f?.name ?? c.flowId ?? 'flow unknown'}`, 'worker-flow-name'));
+ section.append(current);
+ if (!f) section.append(el('p', 'Flow structure unavailable · no topology inferred.', 'insight-empty'));
+ else {
+ const badge = c.currentNodeId ? c.status === 'running' && fresh ? 'REPORTED RUNNING HERE' : 'LAST RECORDED POSITION' : null;
+ const graph = el('div', undefined, 'flow-nodes'); graph.setAttribute('role', 'img');
+ graph.setAttribute('aria-label', `${f.name}: ${f.nodes.length} recorded nodes, ${f.edges.length} recorded edges${c.currentNodeId ? `, reported position ${c.currentNodeId}` : ''}. Nodes: ${f.nodes.map(n => `${n.label} (${n.type}, ${n.id})`).join('; ')}. Edges: ${f.edges.map(e => `${e.source} to ${e.target}`).join('; ')}`);
+ graph.append(flowDiagram(f, c.currentNodeId, badge));
+ const foot = el('p', undefined, 'flow-foot');
+ foot.append(chip(badge ?? 'FLOW DEFINITION', badge === 'REPORTED RUNNING HERE' ? 'live' : 'quiet', 'Position is reported by the conversation, not measured from the worker.'),
+ el('span', `${f.nodes.length} nodes · ${f.edges.length} edges · position ${c.currentNodeId ?? 'unknown'}`));
+ section.append(graph, foot);
+ }
+ }
+ content.append(section);
+ }
+ if (!content.childElementCount) content.append(el('p', 'No FLUJO worker registered with this view.', 'insight-empty'));
+ }
+ function paintConversations() {
+ const selected = currentConversation(), stamp = JSON.stringify([allConversations().map(v => [v.key, v.c]), selected?.key, filter]);
+ if (stamp === transcriptSignature) return; transcriptSignature = stamp;
+ const previousTranscript = conversationBody.querySelector('.transcript'), oldScroll = previousTranscript?.scrollTop ?? 0, atEnd = !previousTranscript || previousTranscript.scrollHeight - previousTranscript.clientHeight - previousTranscript.scrollTop < 40;
+ conversationBody.replaceChildren();
+ const list = el('div', undefined, 'conversation-list');
+ for (const v of allConversations().filter(v => `${v.c.title} ${v.c.id} ${v.w.label}`.toLowerCase().includes(filter.toLowerCase()))) {
+ const b = btn(v.c.title, () => { clock = Date.now(); chosen = v.key; paintConversations(); paintWorkers(); });
+ b.setAttribute('aria-pressed', String(v.key === selected?.key));
+ b.append(el('small', `${v.c.status} · ${v.w.label}`)); list.append(b);
+ }
+ if (!list.childElementCount) list.append(el('p', filter ? 'No matching conversation.' : 'No accessible conversation observed.', 'insight-empty'));
+ conversationBody.append(list);
+ if (!selected) return;
+ const { s, w, c } = selected, transcript = el('article', undefined, 'transcript'), h = el('header');
+ const chips = el('div', undefined, 'transcript-chips');
+ chips.append(chip(c.status, c.status === 'running' && recent(w.observedAt) ? 'live' : 'quiet'), chip(`${c.messages.length}/${c.totalMessages} messages`, 'quiet', 'Displayed messages of the recorded total'));
+ if (c.truncated) chips.append(chip('bounded window', 'warn', 'Older messages are outside the display bound.'));
+ if (c.tokens !== null) chips.append(chip(`${c.tokens.toLocaleString()} tokens`, 'quiet', 'Reported by the conversation'));
+ if (c.estimatedCostUsd !== null) chips.append(chip(`$${c.estimatedCostUsd.toFixed(3)} est.`, 'quiet', 'Estimated model cost, not paid billing'));
+ h.append(el('h3', c.title), el('p', `${scopeLabel(s)} · ${w.label} · updated ${time(c.updatedAt)}`, 'insight-meta'), chips, el('code', c.id), btn('Latest ↓', () => { transcript.scrollTop = transcript.scrollHeight; }));
+ transcript.append(h);
+ for (const m of c.messages) {
+ const message = el('section', undefined, `transcript-message message-${m.role}`), label = el('header');
+ label.append(el('strong', m.role), el('small', [m.at ? atTime(m.at) : 'time not recorded', m.nodeId ? `node ${m.nodeId}` : ''].filter(Boolean).join(' · ')));
+ message.append(label);
+ if (m.content) message.append(el('p', m.content, 'message-content'));
+ if (m.truncated) message.append(el('p', 'shortened to the display bound', 'insight-warning'));
+ for (const t of m.tools) { const tool = el('details', undefined, 'transcript-tool'); tool.open = true; tool.append(el('summary', `Tool · ${t.name}`), el('pre', t.arguments || 'Arguments not recorded')); message.append(tool); }
+ transcript.append(message);
+ }
+ if (!c.messages.length) transcript.append(el('p', 'No displayable message recorded.', 'insight-empty'));
+ conversationBody.append(transcript);
+ transcript.scrollTop = selected.key !== transcriptKey || atEnd ? transcript.scrollHeight : oldScroll; transcriptKey = selected.key;
+ }
+ function paintMachines() {
+ const content = panels.get('machines')!; content.replaceChildren(); const grid = el('div', undefined, 'provider-grid');
+ for (const provider of ['modal', 'fly'] as const) {
+ const column = el('section', undefined, `provider-column provider-${provider}`), machineRows = scoped().flatMap(s => s.machines.filter(m => m.provider === provider).map(m => ({ s, m })));
+ const head = el('div', undefined, 'provider-head');
+ head.append(el('h3', provider === 'modal' ? 'Modal' : 'Fly.io'), el('p', `${machineRows.length} ${provider === 'modal' ? 'apps / runs' : 'machines'}`, 'insight-meta'));
+ column.append(head);
+ for (const { s, m } of machineRows) {
+ const live = m.freshness === 'provider_observed', fresh = live && recent(m.observedAt);
+ const c = el('article', undefined, `machine-card${m.freshness === 'retained' ? ' is-retained' : ''}`);
+ const top = el('div', undefined, 'machine-top');
+ top.append(chip(live ? fresh ? 'PROVIDER OBSERVATION' : 'STALE PROVIDER OBSERVATION' : m.freshness === 'retained' ? 'HISTORICAL RECEIPTS' : 'UNAVAILABLE', fresh ? 'live' : m.freshness === 'retained' ? 'hist' : 'quiet',
+ m.freshness === 'retained' ? `${m.provider === 'modal' ? 'Local journal read' : 'Retained receipt timestamp'} ${time(m.observedAt)}. Current container count and machine health are unknown.` : `Provider read ${time(m.observedAt)}. Provider state does not establish worker health.`), el('span', scopeLabel(s), 'machine-scope'));
+ c.append(top, el('h4', m.label), el('p', m.state, 'machine-state'), el('code', m.resourceId ?? m.runId ?? ''));
+ const facts = el('ul', undefined, 'machine-facts');
+ for (const fact of [m.kind, m.region ?? 'region unknown', m.accountId ?? 'account not supplied', m.appId ? `app ${m.appId}` : '', m.runId ? `run ${m.runId}` : '', m.cellId ? `cell ${m.cellId}` : ''].filter(Boolean)) facts.append(el('li', fact));
+ c.append(facts, el('small', `${m.freshness === 'retained' ? m.provider === 'modal' ? 'journal read' : 'retained receipt' : 'provider read'} ${time(m.observedAt)}`));
+ if (m.operations.length) {
+ const receipts = el('details', undefined, 'machine-receipts'); receipts.open = true; receipts.append(el('summary', `${m.operations.length} recorded operations`));
+ for (const o of m.operations) { const row = el('p', undefined, o.state === 'unknown' ? 'insight-warning' : 'insight-meta'); row.append(el('span', o.label), el('b', o.state)); receipts.append(row); }
+ c.append(receipts);
+ }
+ if (m.evidenceDigest) { const evidence = el('details', undefined, 'machine-receipts'); evidence.open = true; evidence.append(el('summary', 'Evidence digest'), el('code', m.evidenceDigest)); c.append(evidence); }
+ column.append(c);
+ }
+ for (const s of scoped()) {
+ const reservations = s.budget?.reservations.filter(r => r.provider === provider) ?? [];
+ if (reservations.length) {
+ const holds = el('article', undefined, 'reservation-card'); holds.append(el('h4', `${scopeLabel(s)} · reservations`));
+ for (const r of reservations) { const row = el('p', undefined, 'insight-meta'); row.append(el('span', r.id), el('b', `${money(r.heldCents)} · ${r.state}`)); holds.append(row); }
+ holds.append(el('small', 'spending evidence · not proof of a running machine')); column.append(holds);
+ }
+ const observed = provider === 'modal' ? s.modalStatus : s.flyStatus;
+ if (observed !== 'observed') column.append(el('p', `${scopeLabel(s)} · ${observed === 'not_configured' ? 'no machine observer configured' : 'machine observations unavailable'}`, 'insight-empty'));
+ }
+ if (!machineRows.length) column.append(el('p', 'Machine identity and current state unknown until an owned provider observation is connected.', 'insight-empty'));
+ grid.append(column);
+ }
+ content.append(grid);
+ }
+ /** The O is the registry: one arc per registered authority, in the same
+ * deterministic hue the field uses. The mouth core is filled only when a
+ * running lease was counted in the current scope. */
+ function paintMark() {
+ const segments: Segment[] = swarm.sources.map(s => ({ color: sourceHue(s.id), broken: s.status !== 'observed' }));
+ const leased = sources().some(s => [...activities(s, clock, swarm.sample).values()].some(a => a === 'recent'));
+ const mark = oMark(segments, leased, 56);
+ mark.setAttribute('role', 'img');
+ mark.removeAttribute('aria-hidden');
+ mark.setAttribute('aria-label', `${swarm.sources.length} registered authorities · ${leased ? 'recent leased-cell evidence in scope' : 'no recent leased-cell evidence in scope'}; running leases require recent heartbeat/source evidence, with uncertain and retained records excluded`);
+ markSlot.replaceChildren(mark);
+ }
+ function paint() { paintMark(); paintAnalytics(); paintHistory(); paintWorkers(); paintConversations(); paintMachines(); }
+ function temporal() { paintMark(); paintAnalytics(); paintWorkers(); paintMachines(); }
+ return {
+ observe(value: unknown, next: Swarm) {
+ swarm = next;
+ try { detail = floors.accept(value === undefined ? empty() : value, next.sources); }
+ catch { detail = empty(); freshness.textContent = 'Detail observation rejected · private detail cleared'; }
+ },
+ render(next: Swarm, nextSelection: Selection | null) {
+ swarm = next; selection = nextSelection; clock = Date.now();
+ const sourceStamp = JSON.stringify(next.sources.map(s => [s.id, s.factoryId, s.label]));
+ if (sourceStamp !== sourceSignature) {
+ sourceSignature = sourceStamp; sourcePicker.replaceChildren();
+ const all = el('option', 'All registered sources'); all.value = ''; sourcePicker.append(all);
+ for (const s of next.sources) { const o = el('option', s.label); o.value = s.id; sourcePicker.append(o); }
+ }
+ sourcePicker.value = selection?.sourceId ?? '';
+ detail.sources = detail.sources.filter(s => next.sources.some(a => a.id === s.sourceId && a.factoryId === s.factoryId));
+ const selected = currentConversation(); if (chosen && !allConversations().some(v => v.key === chosen)) chosen = selected?.key ?? null;
+ const scopeName = selection ? swarm.sources.find(s => s.id === selection!.sourceId)?.label ?? 'source' : 'All registered sources';
+ scopeTitle.textContent = scopeName;
+ root.style.setProperty('--accent', selection ? sourceHue(selection.sourceId) : '#7fd1de');
+ freshness.textContent = `${swarm.sample ? 'Sample data · design preview' : 'Live read-only · 5s'} · ${scoped().length ? `detail read ${atTime(detail.observedAt)}` : 'worker detail unavailable'}`;
+ const stamp = JSON.stringify([swarm, detail, selection]), signatureTime = timeSignature();
+ if (stamp !== signature) { signature = stamp; temporalSignature = signatureTime; paint(); }
+ else if (signatureTime !== temporalSignature) { temporalSignature = signatureTime; temporal(); }
+ },
+ tick() {
+ if (!swarm || !signature) return;
+ clock = Date.now(); const signatureTime = timeSignature();
+ if (signatureTime !== temporalSignature) { temporalSignature = signatureTime; temporal(); }
+ },
+ };
+}
diff --git a/src/swarm/main.ts b/src/swarm/main.ts
new file mode 100644
index 0000000..9adaeca
--- /dev/null
+++ b/src/swarm/main.ts
@@ -0,0 +1,344 @@
+import './swarm.css';
+import './observatory.css';
+import { activities, nodeKey, parseSwarm, selectionFragment, selectionFromFragment, SwarmIndex, type Node, type Selection, type Source, type Swarm } from './model';
+import { readSwarmJson } from './transport';
+import { activityEvidence, markerLegend, markerOf, markerTitle, SwarmRenderer, sourceColor, type Marker } from './renderer';
+import { swarmPreview } from './preview';
+import { mountInsights } from './insights';
+import { observatoryPreview } from './insights-preview';
+import { oMark } from './marks';
+
+const $ = (id: string): HTMLElement => document.getElementById(id)!;
+const element = (tag: T, text?: string, className?: string): HTMLElementTagNameMap[T] => {
+ const result = document.createElement(tag); if (text !== undefined) result.textContent = text; if (className) result.className = className; return result;
+};
+const button = (text: string, action: () => void, pressed = false): HTMLButtonElement => { const result = element('button', text); result.type = 'button'; result.setAttribute('aria-pressed', String(pressed)); result.onclick = action; return result; };
+const markerChip = (marker: Marker, className: string): HTMLSpanElement => {
+ // The rail, the legend and the field all read the same marker vocabulary.
+ const chip = element('span', undefined, `marker ${className}`);
+ chip.dataset.marker = marker; chip.title = `${markerTitle[marker]} — ${markerLegend[marker]}`;
+ return chip;
+};
+const timestamp = (value: string): string => new Intl.DateTimeFormat(undefined, { timeZone: 'America/Bogota', dateStyle: 'medium', timeStyle: 'medium' }).format(new Date(value));
+const taskLabel = (status: string) => status === 'completed' ? 'operation completed' : status;
+/** Short badge in the flow; the careful wording lives in its tooltip. */
+const chip = (text: string, tone?: string, title?: string): HTMLSpanElement => { const node = element('span', text, tone ? `chip chip-${tone}` : 'chip'); if (title) node.title = title; return node; };
+/** Compliance and provenance prose, folded out of the reading flow. */
+const drawer = (summary: string, lines: string[]): HTMLDetailsElement => { const node = element('details', undefined, 'evidence-drawer'); node.append(element('summary', summary)); for (const line of lines) node.append(element('p', line)); return node; };
+const figure = (value: string, label: string): HTMLSpanElement => { const node = element('span', undefined, 'figure'); node.append(element('b', value), element('i', label)); return node; };
+const empty = (): Swarm => ({ schemaVersion: 1, scope: 'operator-source-registry', observedAt: new Date().toISOString(), commands: false, sources: [] });
+const query = new URLSearchParams(location.search), embedded = query.get('embed') === '1', channel = query.get('channel') ?? '';
+const insights = mountInsights(next => select(next, false));
+const preview = !embedded && query.get('preview') === '1';
+let swarm = empty(), index = new SwarmIndex(swarm), selection: Selection | null = null, page = 0, frameSequence = 0, lastStats = 0;
+let renderer: SwarmRenderer;
+let mode: '3d' | '2d' = query.get('view') === '2d' || ((navigator.hardwareConcurrency ?? 8) <= 4) ? '2d' : '3d';
+let lastActivity = '', follow = false, firstObservation = true;
+const selectionHash = (): Selection | null => selectionFromFragment(swarm, location.hash);
+
+function validSelection(value: Selection): boolean {
+ const source = index.sources.get(value.sourceId); if (!source) return false;
+ return value.kind === 'source' ? value.id === source.id : value.kind === 'cell' ? source.snapshot?.snapshot.cells.some(cell => cell.id === value.id) === true : value.kind === 'task' ? source.snapshot?.snapshot.tasks.some(task => task.id === value.id) === true : source.snapshot?.snapshot.effects.some(effect => effect.key === value.id) === true;
+}
+function select(next: Selection | null, move = true): void {
+ if (next && !validSelection(next)) return;
+ const previousNode = focusNode()?.key;
+ selection = next; page = 0;
+ document.body.classList.toggle('has-selection', !!next);
+ const source = next ? index.sources.get(next.sourceId)! : null;
+ // The selected authority tints the instruments with its deterministic hue.
+ document.body.style.setProperty('--accent', source ? sourceColor(source.id) : '#7fd1de');
+ const hash = next && source ? selectionFragment(source, next) : '';
+ history.replaceState(null, '', `${location.pathname}${location.search}${hash ? `#${hash}` : ''}`);
+ redraw(); if (move && (!next || !['task', 'effect'].includes(next.kind) || focusNode()?.key !== previousNode)) frameSelection();
+ if (embedded) window.parent.postMessage({ protocol: 'brain-swarm/1', type: 'select', channel, selection: next && source ? { ...next, factoryId: source.factoryId } : null }, location.origin);
+}
+function sourceStatus(source: Source): string {
+ const age = source.snapshot ? Date.now() - Date.parse(source.snapshot.observedAt) : Infinity;
+ return source.status === 'observed' && !swarm.sample && age > 15000 ? 'stale' : source.status;
+}
+function focusNode(): Node | undefined { return selection ? index.nodeFor(selection) : undefined; }
+function frameSelection(): void {
+ const node = focusNode();
+ if (node && (!node.cell || node.cell.role === 'coordinator')) renderer.frame(undefined, node.source.id);
+ else renderer.frame(node?.point);
+}
+
+let statsTimer: ReturnType | undefined;
+function createRenderer(): void {
+ clearTimeout(statsTimer);
+ lastStats = 0;
+ renderer?.dispose();
+ const canvas = element('canvas'); canvas.id = 'swarm-canvas'; canvas.tabIndex = 0; canvas.setAttribute('aria-label', 'Spatial swarm. Use navigation buttons to select cells; Home fits the whole swarm.');
+ $('swarm-canvas').replaceWith(canvas);
+ renderer = new SwarmRenderer(canvas, $('swarm-labels'), mode); mode = renderer.mode;
+ document.body.dataset.view = mode;
+ (document.getElementById('swarm-mode') as HTMLSelectElement).value = mode;
+ renderer.onPick = node => select(node.selection);
+ renderer.onStats = stats => {
+ const diagnostics = $('swarm-diagnostics');
+ Object.assign(diagnostics.dataset, { mode: stats.mode, visible: String(stats.visible), indexed: String(index.nodes.size), labels: String(stats.labels), draws: String(stats.draws), frames: String(stats.submissions), cpuMs: stats.cpuMs.toFixed(3) });
+ const text = `${stats.mode.toUpperCase()} · ${stats.visible} drawn / ${index.nodes.size} indexed nodes · ${stats.labels} labels · ${stats.draws} GL draw calls · ${stats.cpuMs.toFixed(2)} ms CPU submission · ${stats.submissions} frames`;
+ const publish = () => { statsTimer = undefined; lastStats = performance.now(); diagnostics.textContent = text; };
+ clearTimeout(statsTimer);
+ const remaining = 500 - (performance.now() - lastStats);
+ if (lastStats && remaining > 0) statsTimer = setTimeout(publish, remaining);
+ else publish();
+ };
+ redraw(); frameSelection();
+}
+
+function paintNavigation(): void {
+ const sourceList = $('source-list'); sourceList.replaceChildren();
+ for (const source of index.sources.values()) {
+ const item = button(source.label, () => select({ sourceId: source.id, kind: 'source', id: source.id }), selection?.sourceId === source.id);
+ const status = sourceStatus(source), cells = source.snapshot?.snapshot.cells ?? [];
+ const evidenced = cells.reduce((sum, cell) => sum + (index.nodes.get(nodeKey(source, 'cell', cell.id))?.activity === 'recent' ? 1 : 0), 0);
+ item.className = `source-record source-${status}`;
+ // Exactly the renderer's deterministic plate colour, never a list position.
+ item.style.setProperty('--accent', sourceColor(source.id));
+ item.dataset.status = status;
+ const marker: Marker = status === 'observed' ? 'plate' : 'quiet';
+ item.dataset.marker = marker;
+ item.replaceChildren(markerChip(marker, 'source-dot'), element('strong', source.label, 'source-name'),
+ element('small', `${status} · ${cells.length} recorded cells${evidenced ? ` · ${evidenced} with a running lease` : ''}`));
+ item.title = `${source.factoryId} · ${markerLegend[marker]}`; sourceList.append(item);
+ }
+ const focus = focusNode(), children = focus ? index.children.get(focus.key) ?? [] : [];
+ $('branch-heading').textContent = focus ? `DELEGATION · ${children.length}` : 'DELEGATION';
+ $('branch-list').replaceChildren(); $('branch-pages').replaceChildren();
+ for (const key of children.slice(page * 128, (page + 1) * 128)) {
+ const node = index.nodes.get(key)!;
+ const item = button(node.label, () => select(node.selection), key === focus?.key);
+ const marker = markerOf(node);
+ item.className = `branch-record activity-${node.activity}`;
+ item.dataset.marker = marker;
+ item.style.setProperty('--accent', sourceColor(node.source.id));
+ item.title = `${node.source.factoryId} / ${node.label} · ${markerLegend[marker]}`;
+ item.replaceChildren(markerChip(marker, 'branch-dot'), element('strong', node.label, 'branch-name'),
+ element('small', `${node.cell?.role ?? 'source'} · ${node.cell?.status ?? sourceStatus(node.source)} · ${markerTitle[marker].toLowerCase()} · ${index.children.get(key)?.length ?? 0} children`));
+ $('branch-list').append(item);
+ }
+ if (children.length > 128) {
+ const previous = button('Previous children', () => { page--; redraw(); }); previous.disabled = page === 0;
+ const next = button('Next children', () => { page++; redraw(); }); next.disabled = (page + 1) * 128 >= children.length;
+ $('branch-pages').append(previous, element('p', `Children ${page * 128 + 1}–${Math.min(children.length, (page + 1) * 128)} of ${children.length}`), next);
+ }
+ const path = $('swarm-path'); path.replaceChildren(button('Swarm', () => select(null)));
+ if (focus) {
+ const ancestors = index.ancestors(focus.key), shown = ancestors.length > 6 ? [ancestors[0]!, ...ancestors.slice(-4)] : ancestors;
+ if (ancestors.length > 6) path.append(element('span', `${ancestors.length - 5} intermediate levels`));
+ for (const node of shown) path.append(element('span', '/'), button(node.label, () => select(node.selection), node.key === focus.key));
+ }
+ paintSearch();
+}
+function paintSearch(): void {
+ const query = (document.getElementById('swarm-search') as HTMLInputElement).value, results = $('search-results'); results.replaceChildren();
+ const matches = index.search(query); if (!query.trim()) return;
+ results.append(element('p', `${matches.length} matches across all indexed sources. Refine the search to reach a specific identity.`));
+ for (const match of matches.slice(0, 60)) {
+ const source = index.sources.get(match.sourceId)!;
+ const item = button(`${match.id} · ${source.label} · ${match.kind}`, () => select(match)); item.title = `${source.factoryId} / ${match.id}`; results.append(item);
+ }
+}
+function evidenceList(container: HTMLElement, pairs: Array<[string, string]>): void {
+ const list = element('dl'); for (const [key, value] of pairs) list.append(element('dt', key), element('dd', value)); container.append(list);
+}
+function paintInspector(): void {
+ const container = $('swarm-inspect'); container.replaceChildren();
+ container.style.removeProperty('--accent');
+ if (!selection) {
+ container.append(element('p', 'MARKERS', 'evidence-kicker'), element('h2', 'Select an authority.'));
+ const guide = element('ul', undefined, 'marker-guide');
+ for (const marker of ['plate', 'idle', 'recent', 'uncertain', 'quiet'] as Marker[]) {
+ const row = element('li');
+ row.append(markerChip(marker, 'guide-dot'), element('strong', markerTitle[marker]), element('small', markerLegend[marker]));
+ guide.append(row);
+ }
+ container.append(guide, drawer('Evidence rules', ['Markers describe recorded observations only. They never establish host health, physical worker activity or quiescence.', 'Shape rather than animation carries each state, so every marker stays readable with reduced motion.', 'This view follows observations. It cannot start, stop or resume workers.']));
+ return;
+ }
+ const source = index.sources.get(selection.sourceId)!;
+ container.style.setProperty('--accent', sourceColor(source.id));
+ container.append(element('p', selection.kind === 'source' ? 'REGISTERED AUTHORITY' : `RECORDED ${selection.kind.toUpperCase()}`, 'evidence-kicker'),
+ element('div', `${sourceStatus(source)} · ${source.label}`, 'source-state'), element('h2', selection.kind === 'source' ? source.label : selection.id));
+ evidenceList(container, [['Factory identity', source.factoryId], ['Registry source', source.id]]);
+ const envelope = source.snapshot;
+ if (!envelope) { container.append(chip('no observation available', 'warn', 'No cells or execution state are inferred for this registered source.')); return; }
+ const state = envelope.snapshot;
+ if (sourceStatus(source) !== 'observed') container.append(chip('retained observation', 'warn', 'Activity is unconfirmed until this authority is read successfully again.'));
+ if (selection.kind === 'source') {
+ container.append(element('p', state.control.mission));
+ evidenceList(container, [['Scope', envelope.scope], ['Observed build', envelope.buildRevision], ['Controller revision', String(envelope.revision)], ['Snapshot read', timestamp(envelope.observedAt)], ['Controller admission', `${state.control.status} · epoch ${state.control.epoch}`], ['Recorded work', `${state.tasks.length} tasks · ${state.effects.length} effects`], ['Worker quiescence', 'unverified']]);
+ container.append(element('h3', 'Recorded tasks'));
+ appendTasks(container, source, state.tasks);
+ container.append(drawer('Evidence', ['Provider host identity, worker health and final paid billing are not established by this contract.', 'Worker quiescence is unverified: a recorded observation never proves a stopped worker.']));
+ } else if (selection.kind === 'cell') {
+ const cell = state.cells.find(cell => cell.id === selection!.id)!;
+ container.append(element('p', cell.purpose));
+ const node = index.nodeFor(selection)!;
+ evidenceList(container, [['Role / lifecycle', `${cell.role} / ${cell.status}`], ['Recorded parent', cell.parentId ?? 'Local root'], ['Heartbeat', timestamp(cell.heartbeat)], ['Activity evidence', activityEvidence({ ...node, activity: activities(source, Date.now(), swarm.sample).get(cell.id) ?? 'idle' })], ['Logical allocation', `$${(cell.allocationCents / 100).toFixed(2)} · logical-allocation`], ['Worker quiescence', 'unverified']]);
+ container.append(element('h3', 'Owned tasks')); appendTasks(container, source, state.tasks.filter(task => task.owner === cell.id));
+ container.append(element('h3', 'Owned external effects')); appendEffects(container, source, state.effects.filter(effect => effect.owner === cell.id));
+ if (cell.parentId && !state.cells.some(parent => parent.id === cell.parentId)) container.append(chip('parent absent from this observation', 'warn', 'Its position does not prove a delegation link.'));
+ } else if (selection.kind === 'task') {
+ const task = state.tasks.find(task => task.id === selection!.id)!;
+ if (task.status === 'completed') container.append(chip('operation completed', 'quiet', 'Software review and delivery are not established by this status.'));
+ if (task.status === 'cancelled') container.append(chip('cancelled', 'warn', 'Abandoned work or unmet acceptance. Candidate and review hashes remain historical evidence.'));
+ evidenceList(container, [['Task status', taskLabel(task.status)], ['Project / branch', `${task.projectId} / ${task.branch}`], ['Attempt', String(task.attempt)], ['Lease expiry', task.leaseExpiry ? timestamp(task.leaseExpiry) : 'None recorded'], ['Specification SHA256', task.specDigest], ['Candidate SHA256', task.candidate?.sha256 ?? 'None recorded'], ['Review verdict', task.review ? task.review.accepted ? 'Recorded accepted review' : 'Recorded rejected review' : 'None recorded'], ['Review evidence SHA256', task.review?.evidenceDigest ?? 'None recorded'], ['Reviewed candidate SHA256', task.review?.candidateDigest ?? 'None recorded'], ['Reviewed spec SHA256', task.review?.specDigest ?? 'None recorded'], ['Review attempt', task.review ? String(task.review.attempt) : 'None recorded']]);
+ if (task.owner) container.append(button(`Inspect owner · ${task.owner}`, () => select({ sourceId: source.id, kind: 'cell', id: task.owner! })));
+ appendEffects(container, source, state.effects.filter(effect => effect.taskId === task.id));
+ } else {
+ const effect = state.effects.find(effect => effect.key === selection!.id)!;
+ evidenceList(container, [['Kind / outcome', `${effect.kind} / ${effect.state}`], ['Scope', `${effect.scope} / ${effect.scopeId}`], ['Owner epochs', `${effect.ownerEpoch} / controller ${effect.controlEpoch}`], ['Created', timestamp(effect.createdAt)], ['Updated', timestamp(effect.updatedAt)], ['Request SHA256', effect.requestDigest]]);
+ container.append(button(`Inspect owner · ${effect.owner}`, () => select({ sourceId: source.id, kind: 'cell', id: effect.owner })));
+ if (effect.taskId) container.append(button(`Inspect task · ${effect.taskId}`, () => select({ sourceId: source.id, kind: 'task', id: effect.taskId! })));
+ if (effect.state === 'unknown') container.append(chip('outcome unknown', 'warn', 'Do not infer success, retry eligibility or stopped workers.'));
+ }
+ if (index.issues.length) container.append(drawer('Observation issues', index.issues.slice(0, 3)));
+}
+function appendTasks(container: HTMLElement, source: Source, tasks: NonNullable['snapshot']['tasks']): void {
+ if (!tasks.length) container.append(element('p', 'No task recorded.', 'evidence-empty'));
+ for (const task of tasks.slice(0, 50)) container.append(button(`${task.id} · ${taskLabel(task.status)}`, () => select({ sourceId: source.id, kind: 'task', id: task.id })));
+ if (tasks.length > 50) container.append(element('p', `${tasks.length} tasks indexed · search reaches each identity`, 'evidence-empty'));
+}
+function appendEffects(container: HTMLElement, source: Source, effects: NonNullable['snapshot']['effects']): void {
+ for (const effect of effects.slice(0, 50)) container.append(button(`${effect.key} · ${effect.state}`, () => select({ sourceId: source.id, kind: 'effect', id: effect.key })));
+ if (effects.length > 50) container.append(element('p', `${effects.length} effects indexed · search reaches each identity`, 'evidence-empty'));
+}
+function paintTracking(): void {
+ const source = selection ? index.sources.get(selection.sourceId) : null;
+ $('tracking-scope').textContent = source ? `${source.label} · recorded controller events` : 'Choose a source';
+ const container = $('tracking-list'); container.replaceChildren();
+ if (!source?.events.length) { container.append(element('p', 'No controller event collected in this view.', 'evidence-empty')); return; }
+ for (const event of source.events.slice(-30).reverse()) {
+ const row = element('div', undefined, 'tracking-row'); const time = element('time', timestamp(event.observedAt)); time.dateTime = event.observedAt;
+ row.append(time, element('span', `${event.seq} · ${event.type}`), element('span', event.subject)); container.append(row);
+ }
+}
+function redraw(): void {
+ const focus = focusNode(), visible = index.visible(focus?.key ?? null, page);
+ renderer?.setNodes(visible, focus?.key ?? null);
+ document.body.classList.toggle('has-selection', !!selection);
+ paintNavigation(); paintInspector(); paintTracking(); insights.render(swarm, selection);
+ document.body.classList.toggle('field-empty', !swarm.sources.length);
+ const accented = selection ? index.sources.get(selection.sourceId) : null;
+ document.body.style.setProperty('--accent', accented ? sourceColor(accented.id) : '#7fd1de');
+ const cells = swarm.sources.reduce((sum, source) => sum + (source.snapshot?.snapshot.cells.length ?? 0), 0), tasks = swarm.sources.reduce((sum, source) => sum + (source.snapshot?.snapshot.tasks.length ?? 0), 0);
+ const recentLeased = [...index.nodes.values()].filter(node => node.cell && markerOf(node) === 'recent').length;
+ const recentFigure = figure(String(recentLeased), 'recent leased');
+ recentFigure.title = 'Cells with an unexpired running lease and recent heartbeat/source evidence. Uncertain, unknown-outcome and retained records are excluded; this is not a full lease census.';
+ $('swarm-counts').replaceChildren(figure(String(swarm.sources.length), 'sources'), figure(cells.toLocaleString(), 'cells'), figure(tasks.toLocaleString(), 'tasks'), recentFigure);
+ const stale = swarm.sources.filter(source => sourceStatus(source) !== 'observed').length;
+ // The masthead O carries the registry: one arc per authority, filled mouth
+ // only while recent leased-cell evidence exists somewhere in the swarm.
+ const mark = oMark(swarm.sources.map(source => ({ color: sourceColor(source.id), broken: sourceStatus(source) !== 'observed' })), recentLeased > 0, 30);
+ mark.setAttribute('role', 'img');
+ mark.removeAttribute('aria-hidden');
+ mark.setAttribute('aria-label', `${swarm.sources.length} registered authorities · ${recentLeased} cells with an unexpired running lease and recent heartbeat/source evidence; uncertain and retained records excluded`);
+ $('brand-mark').replaceChildren(mark);
+ $('swarm-status').textContent = swarm.sample ? `Sample data · design preview · ${cells.toLocaleString()} cells` : swarm.sources.length ? `Live · 5s observations · ${stale} stale` : 'Observation unavailable · no registered facts';
+ $('swarm-status').classList.toggle('sample-notice', !!swarm.sample);
+}
+function observe(value: unknown): void {
+ const wasFirst = firstObservation;
+ const next = parseSwarm(value), previous = new Map(swarm.sources.map(source => [source.id, source]));
+ // Standalone and embedded readers both preserve each authority's own floor.
+ next.sources = next.sources.map(source => {
+ const old = previous.get(source.id);
+ if (!old || old.factoryId !== source.factoryId) return source;
+ if (!source.snapshot || (old.snapshot && source.snapshot.revision < old.snapshot.revision)) return { ...source, snapshot: old.snapshot, status: old.snapshot ? 'stale' : 'unavailable', events: old.events };
+ return source;
+ });
+ insights.observe((value as { observatory?: unknown }).observatory, next);
+ swarm = next; index = new SwarmIndex(next);
+ if (selection && (previous.get(selection.sourceId)?.factoryId !== index.sources.get(selection.sourceId)?.factoryId || !validSelection(selection))) select(null, false);
+ if (firstObservation) { const wanted = selectionHash(); if (wanted && validSelection(wanted)) selection = wanted; firstObservation = false; }
+ const active = [...index.nodes.values()].filter(node => node.activity === 'recent').map(node => node.key).sort().join('|');
+ const changedActivity = active && active !== lastActivity; lastActivity = active;
+ redraw();
+ if (follow && changedActivity) { const node = [...index.nodes.values()].find(node => node.activity === 'recent'); if (node) select(node.selection); }
+ else if (wasFirst) frameSelection();
+}
+
+createRenderer();
+($('swarm-search') as HTMLInputElement).oninput = paintSearch;
+($('swarm-mode') as HTMLSelectElement).onchange = event => { mode = (event.target as HTMLSelectElement).value as '3d' | '2d'; createRenderer(); };
+($('swarm-follow') as HTMLInputElement).onchange = event => { follow = (event.target as HTMLInputElement).checked; };
+$('swarm-home').onclick = () => select(null);
+$('swarm-close').onclick = () => select(null, false);
+$('swarm-explore').onclick = () => { const hidden = document.body.classList.toggle('navigation-hidden'); $('swarm-explore').setAttribute('aria-expanded', String(!hidden)); frameSelection(); };
+$('swarm-find').onclick = () => { document.body.classList.remove('navigation-hidden'); document.body.classList.add('search-open'); $('swarm-search').focus(); };
+window.addEventListener('keydown', event => {
+ if (event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement) return;
+ if (event.key === '/' || ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k')) { event.preventDefault(); document.body.classList.remove('navigation-hidden'); document.body.classList.add('search-open'); $('swarm-search').focus(); }
+ if (event.key === 'Escape' || event.key === 'Backspace') { const node = focusNode(), parent = node?.parent && index.nodes.get(node.parent); event.preventDefault(); select(parent ? parent.selection : null); }
+ if ((event.key === '[' || event.key === ']') && swarm.sources.length) {
+ const position = Math.max(0, swarm.sources.findIndex(source => source.id === selection?.sourceId)), source = swarm.sources[(position + (event.key === '[' ? -1 : 1) + swarm.sources.length) % swarm.sources.length]!;
+ event.preventDefault(); select({ sourceId: source.id, kind: 'source', id: source.id });
+ }
+ if (event.target === $('swarm-canvas') && selection) {
+ const node = focusNode();
+ if (event.key === 'ArrowUp') { const parent = node?.parent && index.nodes.get(node.parent); if (parent) { event.preventDefault(); select(parent.selection); } }
+ if (event.key === 'ArrowDown') { const child = node && index.nodes.get(index.children.get(node.key)?.[0] ?? ''); if (child) { event.preventDefault(); select(child.selection); } }
+ if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight') && node?.parent) {
+ const siblings = index.children.get(node.parent) ?? [], current = siblings.indexOf(node.key), next = siblings[(current + (event.key === 'ArrowLeft' ? -1 : 1) + siblings.length) % siblings.length], sibling = next && index.nodes.get(next);
+ if (sibling) { event.preventDefault(); select(sibling.selection); }
+ }
+ }
+});
+window.addEventListener('hashchange', () => { const wanted = selectionHash(); if (!wanted || validSelection(wanted)) select(wanted); });
+$('swarm-stage').addEventListener('swarm-renderer-lost', () => { mode = '2d'; createRenderer(); $('swarm-status').textContent += ' · WebGL unavailable; 2D map active'; });
+const expiryTimer = setInterval(() => {
+ // Reclassify evidence in linear time; no force-layout/rebuild on every frame.
+ let changed = false;
+ for (const source of index.sources.values()) {
+ if (source.status === 'observed' && sourceStatus(source) === 'stale') { source.status = 'stale'; changed = true; }
+ const activity = activities(source, Date.now(), swarm.sample);
+ for (const cell of source.snapshot?.snapshot.cells ?? []) { const node = index.nodes.get(nodeKey(source, 'cell', cell.id)); if (node && node.activity !== (activity.get(cell.id) ?? 'idle')) { node.activity = activity.get(cell.id) ?? 'idle'; changed = true; } }
+ }
+ if (changed) redraw();
+ else insights.tick();
+}, 2000);
+
+if (embedded) {
+ if (!/^[a-zA-Z0-9_-]{16,128}$/.test(channel) || window.parent === window) $('swarm-status').textContent = 'Invalid viewer host channel';
+ else {
+ window.addEventListener('message', event => {
+ if (event.source !== window.parent || event.origin !== location.origin || event.data?.protocol !== 'brain-swarm/1' || event.data.channel !== channel) return;
+ if (event.data.type === 'ping') { window.parent.postMessage({ protocol: 'brain-swarm/1', type: 'ready', channel }, location.origin); return; }
+ if (event.data.type !== 'observe' || !Number.isSafeInteger(event.data.sequence) || event.data.sequence <= frameSequence) return;
+ try { observe(event.data.model); frameSequence = event.data.sequence; }
+ catch { $('swarm-status').textContent = 'Host observation rejected · existing facts retained'; }
+ });
+ window.parent.postMessage({ protocol: 'brain-swarm/1', type: 'ready', channel }, location.origin);
+ }
+} else if (preview) {
+ const count = Number(query.get('cells') ?? 54);
+ const sample = swarmPreview(Number.isSafeInteger(count) && count >= 3 && count <= 10000 ? count : 54);
+ observe({ ...sample, observatory: observatoryPreview(sample.sources) });
+} else {
+ // Same-origin registered bridge only; never accept a remote destination/token.
+ const sourcePath = query.get('source') ?? '/api/factory/swarm';
+ let stopped = false, timer: ReturnType, controller: AbortController | null = null;
+ const path = new URL(sourcePath, location.origin);
+ if (!sourcePath.startsWith('/') || sourcePath.startsWith('//') || path.origin !== location.origin || path.search || path.hash) $('swarm-status').textContent = 'A same-origin read bridge is required';
+ else {
+ const poll = async () => {
+ if (document.hidden) { timer = setTimeout(() => void poll(), 5000); return; }
+ controller = new AbortController(); const deadline = setTimeout(() => controller?.abort(), 12000);
+ try {
+ const response = await fetch(path.pathname, { cache: 'no-store', signal: controller.signal, credentials: 'same-origin' });
+ if ([401, 403, 404].includes(response.status)) { observe(empty()); throw new Error('Observation access unavailable'); }
+ if (!response.ok) throw new Error('Swarm bridge unavailable');
+ const body = await readSwarmJson(response);
+ if (!stopped) observe(body);
+ } catch {
+ if (!stopped) { swarm.sources = swarm.sources.map(source => ({ ...source, status: source.snapshot ? 'stale' : 'unavailable' })); index = new SwarmIndex(swarm); redraw(); $('swarm-status').textContent = 'Observation unavailable · retained facts are stale'; }
+ } finally { clearTimeout(deadline); if (!stopped) timer = setTimeout(() => void poll(), 5000); }
+ };
+ void poll();
+ }
+ window.addEventListener('pagehide', () => { stopped = true; controller?.abort(); clearTimeout(timer); });
+}
+window.addEventListener('pagehide', () => { clearInterval(expiryTimer); clearTimeout(statsTimer); renderer.dispose(); });
diff --git a/src/swarm/marks.ts b/src/swarm/marks.ts
new file mode 100644
index 0000000..0761c5a
--- /dev/null
+++ b/src/swarm/marks.ts
@@ -0,0 +1,179 @@
+/** Local presentation helpers for the Observatory.
+ *
+ * Every mark here is drawn from data that was already observed and parsed:
+ * nothing invents a measurement, and nothing animates on its own. The one
+ * recurring form is the O — a ring that opens into a mouth. The Observatory
+ * only ever speaks about records, so the mouth is the brand. */
+
+const NS = 'http://www.w3.org/2000/svg';
+
+export const svgNode = (tag: K, attributes: Record = {}): SVGElementTagNameMap[K] => {
+ const node = document.createElementNS(NS, tag);
+ for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, String(value));
+ return node;
+};
+const frame = (viewBox: string, className: string): SVGSVGElement =>
+ svgNode('svg', { viewBox, class: className, focusable: 'false', 'aria-hidden': 'true', preserveAspectRatio: 'xMidYMid meet' });
+const polar = (cx: number, cy: number, r: number, turn: number) =>
+ [cx + r * Math.cos(turn * Math.PI * 2 - Math.PI / 2), cy + r * Math.sin(turn * Math.PI * 2 - Math.PI / 2)] as const;
+const arcPath = (cx: number, cy: number, r: number, from: number, to: number): string => {
+ const span = Math.max(0, Math.min(0.9999, to - from));
+ const [x1, y1] = polar(cx, cy, r, from), [x2, y2] = polar(cx, cy, r, from + span);
+ return `M ${x1.toFixed(2)} ${y1.toFixed(2)} A ${r} ${r} 0 ${span > 0.5 ? 1 : 0} 1 ${x2.toFixed(2)} ${y2.toFixed(2)}`;
+};
+
+export interface Segment { color: string; broken?: boolean }
+
+/** The O: a registry ring that opens into a mouth.
+ * Each ring segment is one registered authority in its deterministic hue; a
+ * broken segment is a retained or unavailable record. The mouth core is filled
+ * only when the caller counted recent leased-cell evidence — it is a count,
+ * not a heartbeat, and it never animates by itself. */
+export function oMark(segments: Segment[], speaking: boolean, size = 44): SVGSVGElement {
+ const root = frame('0 0 64 64', 'o-mark');
+ root.setAttribute('width', String(size));
+ root.setAttribute('height', String(size));
+ root.dataset.speaking = String(speaking);
+ const count = Math.max(1, segments.length), gap = segments.length > 1 ? Math.min(0.02, 0.35 / count) : 0;
+ for (let i = 0; i < count && segments.length; i++) {
+ const seg = segments[i]!;
+ const arc = svgNode('path', { d: arcPath(32, 32, 27, i / count + gap / 2, (i + 1) / count - gap / 2), stroke: seg.color, 'stroke-width': 2, fill: 'none', 'stroke-linecap': 'round' });
+ if (seg.broken) { arc.setAttribute('stroke-dasharray', '2.4 3.4'); arc.setAttribute('opacity', '.6'); }
+ root.append(arc);
+ }
+ if (!segments.length) root.append(svgNode('circle', { cx: 32, cy: 32, r: 27, stroke: 'var(--rule-strong)', 'stroke-width': 1.5, fill: 'none', 'stroke-dasharray': '2 4' }));
+ root.append(svgNode('circle', { class: 'o-bezel', cx: 32, cy: 32, r: 21.5, stroke: 'var(--rule)', 'stroke-width': 1, fill: 'none' }));
+ // An open mouth, not an eye: a shallow upper lip over a deep lower one.
+ root.append(svgNode('path', { class: 'o-lip o-lip-upper', d: 'M 18.5 31 Q 32 26.4 45.5 31', fill: 'none', stroke: 'currentColor', 'stroke-width': 1.7, 'stroke-linecap': 'round' }));
+ root.append(svgNode('path', { class: 'o-lip o-lip-lower', d: 'M 18.5 31 Q 32 44 45.5 31', fill: 'none', stroke: 'currentColor', 'stroke-width': 1.7, 'stroke-linecap': 'round' }));
+ const core = svgNode('path', { class: 'o-core', d: 'M 25.6 36.6 Q 32 42.4 38.4 36.6', fill: speaking ? 'currentColor' : 'none', stroke: 'currentColor', 'stroke-width': 1.2, 'stroke-linecap': 'round', opacity: speaking ? '.9' : '.34' });
+ root.append(core);
+ for (const [i, turn] of [-0.055, 0, 0.055].entries()) {
+ const [x1, y1] = polar(32, 32, 31, 0.25 + turn), [x2, y2] = polar(32, 32, 31 + (i === 1 ? 4.5 : 3), 0.25 + turn);
+ root.append(svgNode('line', { class: 'o-say', x1: x1.toFixed(2), y1: y1.toFixed(2), x2: x2.toFixed(2), y2: y2.toFixed(2), stroke: 'currentColor', 'stroke-width': 1, 'stroke-linecap': 'round', opacity: i === 1 ? '.5' : '.26' }));
+ }
+ return root;
+}
+
+/** One reading drawn as an arc: a part of a counted whole, never health. */
+export function arcDial(value: number, total: number, tone = 'var(--accent)'): SVGSVGElement {
+ const root = frame('0 0 120 120', 'dial-art');
+ const share = total > 0 ? Math.max(0, Math.min(1, value / total)) : 0;
+ root.append(svgNode('path', { d: arcPath(60, 60, 49, 0, 0.9999), fill: 'none', stroke: 'var(--rule-strong)', 'stroke-width': 5, opacity: '.3', 'stroke-linecap': 'round' }));
+ for (let i = 0; i < 48; i++) {
+ const [x1, y1] = polar(60, 60, 41, i / 48), [x2, y2] = polar(60, 60, i % 4 === 0 ? 35 : 38, i / 48);
+ root.append(svgNode('line', { x1: x1.toFixed(2), y1: y1.toFixed(2), x2: x2.toFixed(2), y2: y2.toFixed(2), stroke: i / 48 < share ? tone : 'var(--rule-strong)', 'stroke-width': i % 4 === 0 ? 1 : 0.6, opacity: i / 48 < share ? '.8' : '.34' }));
+ }
+ if (share > 0) root.append(svgNode('path', { d: arcPath(60, 60, 49, 0, Math.max(share, 0.005)), fill: 'none', stroke: tone, 'stroke-width': 5, 'stroke-linecap': 'round' }));
+ return root;
+}
+
+/** Observed record density across the window that was actually observed. */
+export interface RibbonPoint { at: number; lane: 0 | 1 | 2 }
+export function ribbon(points: RibbonPoint[], lanes: string[]): SVGSVGElement {
+ const width = 960, height = 80, columns = 72;
+ const root = frame(`0 0 ${width} ${height}`, 'ribbon-art');
+ root.setAttribute('preserveAspectRatio', 'none');
+ const times = points.map(point => point.at);
+ const min = Math.min(...times), max = Math.max(...times), span = Math.max(1, max - min);
+ const buckets = Array.from({ length: columns }, () => [0, 0, 0]);
+ for (const point of points) buckets[Math.min(columns - 1, Math.max(0, Math.round(((point.at - min) / span) * (columns - 1))))]![point.lane]++;
+ const peak = Math.max(1, ...buckets.map(bucket => bucket[0]! + bucket[1]! + bucket[2]!)), step = width / columns;
+ for (let i = 0; i <= 8; i++) root.append(svgNode('line', { x1: ((width / 8) * i).toFixed(2), y1: 4, x2: ((width / 8) * i).toFixed(2), y2: height - 8, stroke: 'var(--rule)', 'stroke-width': 1, opacity: i % 8 === 0 ? '.9' : '.55' }));
+ for (let i = 0; i < columns; i += 2) root.append(svgNode('line', { x1: (i * step + step / 2).toFixed(2), y1: height - 12, x2: (i * step + step / 2).toFixed(2), y2: height - 8, stroke: 'var(--rule)', 'stroke-width': 1, opacity: '.5' }));
+ for (let i = 0; i < columns; i++) {
+ let base = height - 9;
+ for (let lane = 0; lane < 3; lane++) {
+ const value = buckets[i]![lane]!;
+ if (!value) continue;
+ const bar = Math.max(3, (value / peak) * (height - 20));
+ root.append(svgNode('rect', { x: (i * step + 1.2).toFixed(2), y: (base - bar).toFixed(2), width: Math.max(1.6, step - 2.4).toFixed(2), height: bar.toFixed(2), fill: lanes[lane]!, opacity: '.8' }));
+ base -= bar + 1.5;
+ }
+ }
+ root.append(svgNode('line', { x1: 0, y1: height - 8, x2: width, y2: height - 8, stroke: 'var(--rule-strong)', 'stroke-width': 1 }));
+ return root;
+}
+
+/** The paid ledger drawn as one measured rule, not a progress bar. */
+export function budgetRule(heldCents: number, limitCents: number, meteredCents: number | null): SVGSVGElement {
+ const width = 600, height = 32, root = frame(`0 0 ${width} ${height}`, 'budget-art');
+ root.setAttribute('preserveAspectRatio', 'none');
+ const scale = Math.max(1, limitCents, heldCents), at = (cents: number) => Math.max(0, Math.min(width, (cents / scale) * width));
+ root.append(svgNode('rect', { x: 0, y: 12, width, height: 10, fill: 'var(--plate-solid)', stroke: 'var(--rule)', 'stroke-width': 1 }));
+ root.append(svgNode('rect', { x: 0, y: 12, width: at(Math.min(heldCents, limitCents)).toFixed(2), height: 10, fill: 'var(--accent)', opacity: '.8' }));
+ if (heldCents > limitCents) root.append(svgNode('rect', { x: at(limitCents).toFixed(2), y: 12, width: Math.max(1, at(heldCents) - at(limitCents)).toFixed(2), height: 10, fill: 'var(--warn)', opacity: '.85' }));
+ for (let i = 0; i <= 20; i++) root.append(svgNode('line', { x1: ((width / 20) * i).toFixed(2), y1: 5, x2: ((width / 20) * i).toFixed(2), y2: i % 5 === 0 ? 10 : 8.5, stroke: 'var(--rule-strong)', 'stroke-width': 1 }));
+ root.append(svgNode('line', { x1: at(limitCents).toFixed(2), y1: 3, x2: at(limitCents).toFixed(2), y2: 29, stroke: 'var(--text)', 'stroke-width': 1, opacity: '.7' }));
+ if (meteredCents !== null) root.append(svgNode('line', { x1: at(meteredCents).toFixed(2), y1: 11, x2: at(meteredCents).toFixed(2), y2: 27, stroke: 'var(--signal)', 'stroke-width': 1.5, 'stroke-dasharray': '2 3' }));
+ return root;
+}
+
+/** Recorded flow structure drawn as a real graph with its recorded edges.
+ * Only the node the conversation reports is marked; the rest are definitions. */
+export interface DiagramNode { id: string; label: string; type: string }
+export interface DiagramFlow { nodes: DiagramNode[]; edges: Array<{ source: string; target: string }> }
+export function flowDiagram(flow: DiagramFlow, currentId: string | null, currentBadge: string | null): SVGSVGElement {
+ const W = 168, H = 56, GX = 58, GY = 22, PAD = 16, TOP = 26;
+ const depth = new Map(flow.nodes.map(node => [node.id, 0]));
+ for (let pass = 0; pass < Math.min(flow.nodes.length, 48); pass++) {
+ let changed = false;
+ for (const edge of flow.edges) {
+ const next = (depth.get(edge.source) ?? 0) + 1;
+ if (depth.has(edge.target) && next > depth.get(edge.target)! && next < flow.nodes.length) { depth.set(edge.target, next); changed = true; }
+ }
+ if (!changed) break;
+ }
+ const columns = new Map();
+ for (const node of flow.nodes) { const at = depth.get(node.id) ?? 0; columns.set(at, [...(columns.get(at) ?? []), node]); }
+ // Cycles and disconnected components can leave gaps in the bounded ranks.
+ const ranks = [...columns.keys()].sort((a, b) => a - b);
+ const rows = Math.max(1, ...[...columns.values()].map(column => column.length));
+ const place = new Map();
+ for (const [column, rank] of ranks.entries()) for (const [row, node] of columns.get(rank)!.entries()) {
+ const nodes = columns.get(rank)!;
+ place.set(node.id, { x: PAD + column * (W + GX), y: TOP + row * (H + GY) + ((rows - nodes.length) * (H + GY)) / 2 });
+ }
+ const width = PAD * 2 + Math.max(0, columns.size - 1) * (W + GX) + W, height = TOP + rows * (H + GY) + PAD;
+ const root = frame(`0 0 ${width} ${height}`, 'flow-art');
+ root.setAttribute('width', String(width));
+ root.setAttribute('height', String(height));
+ const id = `flow-arrow-${Math.random().toString(36).slice(2, 9)}`;
+ const marker = svgNode('marker', { id, viewBox: '0 0 8 8', refX: 7.5, refY: 4, markerWidth: 5, markerHeight: 5, orient: 'auto-start-reverse' });
+ marker.append(svgNode('path', { d: 'M 0.5 1 L 7.5 4 L 0.5 7 z', fill: 'var(--rule-strong)' }));
+ const defs = svgNode('defs'); defs.append(marker); root.append(defs);
+ for (const edge of flow.edges) {
+ const a = place.get(edge.source), b = place.get(edge.target);
+ if (!a || !b) continue;
+ const x1 = a.x + W, y1 = a.y + H / 2, x2 = b.x, y2 = b.y + H / 2;
+ const d = x2 >= x1
+ ? `M ${x1} ${y1} C ${x1 + GX * 0.62} ${y1}, ${x2 - GX * 0.62} ${y2}, ${x2} ${y2}`
+ : `M ${a.x + W / 2} ${a.y + H} C ${a.x + W / 2} ${a.y + H + 30}, ${b.x + W / 2} ${b.y + H + 30}, ${b.x + W / 2} ${b.y + H}`;
+ root.append(svgNode('path', { class: 'flow-art-edge', d, fill: 'none', stroke: 'var(--rule-strong)', 'stroke-width': 1, 'marker-end': `url(#${id})`, opacity: '.85' }));
+ }
+ for (const node of flow.nodes) {
+ const at = place.get(node.id)!, current = node.id === currentId;
+ const group = svgNode('g', { class: `flow-art-node${current ? ' is-current' : ''}`, transform: `translate(${at.x} ${at.y})` });
+ const title = svgNode('title'); title.textContent = `${node.label} · ${node.type} · ${node.id}${current && currentBadge ? ` · ${currentBadge}` : ''}`; group.append(title);
+ group.append(svgNode('rect', { x: 0.5, y: 0.5, width: W - 1, height: H - 1, rx: 3, fill: current ? 'var(--plate-lit)' : 'var(--plate-solid)', stroke: current ? 'var(--accent)' : 'var(--rule)', 'stroke-width': 1 }));
+ group.append(svgNode('rect', { x: 0.5, y: 0.5, width: 2, height: H - 1, fill: current ? 'var(--accent)' : 'var(--rule-strong)' }));
+ const label = svgNode('text', { x: 15, y: 25, class: 'flow-art-label' });
+ label.textContent = node.label.length > 21 ? `${node.label.slice(0, 20)}…` : node.label;
+ const type = svgNode('text', { x: 15, y: 41, class: 'flow-art-type' });
+ type.textContent = node.type.toUpperCase();
+ group.append(label, type);
+ if (current && currentBadge) {
+ const badge = svgNode('text', { x: 1, y: -10, class: 'flow-art-badge' });
+ badge.textContent = currentBadge;
+ group.append(badge);
+ }
+ root.append(group);
+ }
+ return root;
+}
+
+/** The deterministic authority palette, mirrored verbatim from renderer.ts so
+ * the field, the atlas rail and the instrument deck cannot disagree about a
+ * source's hue. test/swarm.test.mjs asserts the two readers stay identical. */
+const hues = ['#7fd1de', '#9bb8e8', '#c2a7e8', '#e8a7b8', '#e8c98a', '#8fd9b6'];
+export const sourceHue = (id: string): string => hues[[...id].reduce((sum, char) => (sum * 31 + char.charCodeAt(0)) >>> 0, 0) % hues.length]!;
diff --git a/src/swarm/model.ts b/src/swarm/model.ts
new file mode 100644
index 0000000..1ac541d
--- /dev/null
+++ b/src/swarm/model.ts
@@ -0,0 +1,194 @@
+/** Public read-only transport, shared structurally with the Brain Online adapter.
+ * No provider locations, credentials, commands or arbitrary navigation URLs. */
+export interface Cell { id: string; parentId: string | null; depth: number; role: 'coordinator' | 'developer' | 'verifier' | 'watcher'; status: 'reserved' | 'ready' | 'retired'; purpose: string; heartbeat: string; allocationCents: number; budgetBasis: 'logical-allocation' }
+export interface Task { id: string; projectId: string; branch: string; status: 'ready' | 'running' | 'review' | 'verified' | 'delivered' | 'rejected' | 'completed' | 'cancelled'; owner: string | null; attempt: number; leaseExpiry: string | null; specDigest: string; candidate: { sha256: string } | null; review: { accepted: boolean; reviewerId: string; evidenceDigest: string; candidateDigest: string; specDigest: string; attempt: number } | null }
+export interface Effect { key: string; kind: 'provision' | 'flow_call' | 'message' | 'flow_cancel' | 'retire' | 'delivery' | 'worker_wake' | 'worker_sleep'; state: 'accepted' | 'running' | 'unknown' | 'succeeded' | 'cancelled' | 'not_applied'; owner: string; taskId: string | null; scope: 'task' | 'project' | 'cleanup' | 'worker'; scopeId: string; ownerEpoch: number; controlEpoch: number; createdAt: string; updatedAt: string; requestDigest: string }
+export interface Change { seq: number; type: string; subject: string; observedAt: string }
+export interface Snapshot {
+ schemaVersion: 1; factoryId: string; revision: number; cursor: string; observedAt: string; scope: 'local-coordinator'; buildRevision: string;
+ capabilities: { snapshot: true; events: true; commands: false };
+ snapshot: { control: { status: 'active' | 'paused'; epoch: number; mission: string }; cells: Cell[]; tasks: Task[]; effects: Effect[]; workerQuiescence: 'unverified' };
+}
+export interface Source { id: string; label: string; factoryId: string; status: 'observed' | 'stale' | 'unavailable'; snapshot: Snapshot | null; events: Change[] }
+export interface Swarm { schemaVersion: 1; scope: 'operator-source-registry'; observedAt: string; commands: false; sources: Source[]; sample?: boolean }
+export interface Selection { sourceId: string; kind: 'source' | 'cell' | 'task' | 'effect'; id: string }
+export interface Point { x: number; y: number; z: number }
+export interface Node { key: string; selection: Selection; source: Source; cell: Cell | null; label: string; parent: string | null; point: Point; activity: 'idle' | 'recent' | 'uncertain' }
+
+const record = (value: unknown): Record => { if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid observation.'); return value as Record; };
+const text = (value: unknown, max = 128): string => { if (typeof value !== 'string' || value.length > max) throw new Error('Invalid observation text.'); return value; };
+const id = (value: unknown): string => { const result = text(value); if (!result) throw new Error('Missing identity.'); return result; };
+const num = (value: unknown): number => { if (!Number.isSafeInteger(value) || (value as number) < 0) throw new Error('Invalid observation sequence.'); return value as number; };
+const date = (value: unknown): string => { const result = text(value, 64); if (!/^\d{4}-\d{2}-\d{2}T/.test(result) || !Number.isFinite(Date.parse(result))) throw new Error('Invalid observation time.'); return result; };
+const digest = (value: unknown): string => { const result = text(value); if (!/^[a-f0-9]{64}$/.test(result)) throw new Error('Invalid evidence hash.'); return result; };
+const choice = (value: unknown, values: readonly T[]): T => { if (!values.includes(value as T)) throw new Error('Unsupported observation value.'); return value as T; };
+const array = (value: unknown, max: number): unknown[] => { if (!Array.isArray(value) || value.length > max) throw new Error('Observation exceeds its bounds.'); return value; };
+const optionalId = (value: unknown): string | null => value === null ? null : id(value);
+const unique = (values: string[]) => { if (new Set(values).size !== values.length) throw new Error('Duplicate observation identity.'); };
+
+/** Positive allowlist even when running outside Brain Online's validated host. */
+export function parseSwarm(value: unknown): Swarm {
+ const input = record(value);
+ if (input.schemaVersion !== 1 || input.scope !== 'operator-source-registry' || input.commands !== false) throw new Error('Unsupported swarm observation.');
+ const sources = array(input.sources, 32).map(value => {
+ const source = record(value), sourceId = id(source.id), factoryId = id(source.factoryId);
+ if (!/^[a-zA-Z0-9_-]{1,64}$/.test(sourceId)) throw new Error('Invalid registered source.');
+ let snapshot: Snapshot | null = null;
+ if (source.snapshot !== null) {
+ const envelope = record(source.snapshot), state = record(envelope.snapshot), control = record(state.control), caps = record(envelope.capabilities);
+ if (envelope.schemaVersion !== 1 || envelope.factoryId !== factoryId || envelope.scope !== 'local-coordinator' || caps.commands !== false || caps.snapshot !== true || caps.events !== true || state.workerQuiescence !== 'unverified') throw new Error('Unsupported FACTORY authority.');
+ const cells = array(state.cells, 10000).map(value => {
+ const cell = record(value);
+ return { id: id(cell.id), parentId: optionalId(cell.parentId), depth: num(cell.depth),
+ role: choice(cell.role, ['coordinator', 'developer', 'verifier', 'watcher']), status: choice(cell.status, ['reserved', 'ready', 'retired']),
+ heartbeat: date(cell.heartbeat), purpose: text(cell.purpose, 512), allocationCents: num(cell.allocationCents), budgetBasis: choice(cell.budgetBasis, ['logical-allocation']) };
+ });
+ const tasks = array(state.tasks, 10000).map(value => {
+ const task = record(value), review = task.review === null ? null : record(task.review);
+ if (review && typeof review.accepted !== 'boolean') throw new Error('Invalid review.');
+ return { id: id(task.id), projectId: id(task.projectId), branch: text(task.branch, 256),
+ status: choice(task.status, ['ready', 'running', 'review', 'verified', 'delivered', 'rejected', 'completed', 'cancelled']),
+ attempt: num(task.attempt), owner: optionalId(task.owner), leaseExpiry: task.leaseExpiry === null ? null : date(task.leaseExpiry), specDigest: digest(task.specDigest),
+ candidate: task.candidate === null ? null : { sha256: digest(record(task.candidate).sha256) },
+ review: review ? { accepted: review.accepted as boolean, reviewerId: id(review.reviewerId), evidenceDigest: digest(review.evidenceDigest), candidateDigest: digest(review.candidateDigest), specDigest: digest(review.specDigest), attempt: num(review.attempt) } : null };
+ });
+ const effects = array(state.effects, 10000).map(value => {
+ const effect = record(value);
+ return { key: id(effect.key), kind: choice(effect.kind, ['provision', 'flow_call', 'message', 'flow_cancel', 'retire', 'delivery', 'worker_wake', 'worker_sleep']), state: choice(effect.state, ['accepted', 'running', 'unknown', 'succeeded', 'cancelled', 'not_applied']),
+ owner: id(effect.owner), taskId: optionalId(effect.taskId), scope: choice(effect.scope, ['task', 'project', 'cleanup', 'worker']), scopeId: id(effect.scopeId),
+ ownerEpoch: num(effect.ownerEpoch), controlEpoch: num(effect.controlEpoch), createdAt: date(effect.createdAt), updatedAt: date(effect.updatedAt), requestDigest: digest(effect.requestDigest) };
+ });
+ unique(cells.map(cell => cell.id)); unique(tasks.map(task => task.id)); unique(effects.map(effect => effect.key));
+ const cursor = text(envelope.cursor, 32), buildRevision = text(envelope.buildRevision, 40);
+ if (!/^[A-Za-z0-9_-]{1,32}$/.test(cursor) || !(buildRevision === 'unknown' || /^[a-f0-9]{40}$/.test(buildRevision))) throw new Error('Invalid provenance.');
+ snapshot = { schemaVersion: 1, factoryId, revision: num(envelope.revision), cursor, observedAt: date(envelope.observedAt), scope: 'local-coordinator', buildRevision,
+ capabilities: { snapshot: true, events: true, commands: false }, snapshot: { control: { status: choice(control.status, ['active', 'paused']), epoch: num(control.epoch), mission: text(control.mission, 2048) }, cells, tasks, effects, workerQuiescence: 'unverified' } };
+ }
+ const status = choice(source.status, ['observed', 'stale', 'unavailable']);
+ if ((status === 'observed' || status === 'stale') && !snapshot) throw new Error('Missing recorded source.');
+ const events = array(source.events ?? [], 1000).map(value => { const event = record(value); return { seq: num(event.seq), type: id(event.type), subject: text(event.subject), observedAt: date(event.observedAt) }; });
+ return { id: sourceId, label: id(source.label), factoryId, status, snapshot, events };
+ });
+ unique(sources.map(source => source.id));
+ return { schemaVersion: 1, scope: 'operator-source-registry', commands: false, observedAt: date(input.observedAt), sources, sample: input.sample === true };
+}
+
+export const nodeKey = (source: Pick, kind: string, localId: string) => JSON.stringify([source.id, source.factoryId, kind, localId]);
+export const selectionKey = (source: Source, selection: Selection) => nodeKey(source, selection.kind, selection.id);
+export const selectionFragment = (source: Pick, selection: Selection): string => new URLSearchParams({ source: source.id, factory: source.factoryId, kind: selection.kind, id: selection.id }).toString();
+/** A saved link must name the authority, not just a reusable registry alias. */
+export function selectionFromFragment(swarm: Swarm, fragment: string): Selection | null {
+ const params = new URLSearchParams(fragment.replace(/^#/, ''));
+ if (['source', 'factory', 'kind', 'id'].some(key => params.getAll(key).length !== 1)) return null;
+ const source = swarm.sources.find(source => source.id === params.get('source'));
+ if (!source || source.factoryId !== params.get('factory')) return null;
+ const kind = params.get('kind'), localId = params.get('id')!;
+ const state = source.snapshot?.snapshot;
+ const valid = kind === 'source' ? localId === source.id : kind === 'cell' ? state?.cells.some(cell => cell.id === localId) : kind === 'task' ? state?.tasks.some(task => task.id === localId) : kind === 'effect' && state?.effects.some(effect => effect.key === localId);
+ return valid ? { sourceId: source.id, kind: kind as Selection['kind'], id: localId } : null;
+}
+const hash = (value: string) => { let result = 2166136261; for (let i = 0; i < value.length; i++) result = Math.imul(result ^ value.charCodeAt(i), 16777619); return (result >>> 0) / 4294967296; };
+
+/** O(cells + tasks + effects), including expiry independent of read time. */
+export function activities(source: Source, now: number, sample = false): Map {
+ const state = source.snapshot?.snapshot, result = new Map();
+ if (!state) return result;
+ const running = new Map();
+ for (const task of state.tasks) if (task.status === 'running' && task.owner) running.set(task.owner, Math.min(running.get(task.owner) ?? Infinity, task.leaseExpiry ? Date.parse(task.leaseExpiry) : 0));
+ const unknown = new Set(state.effects.filter(effect => effect.state === 'unknown').map(effect => effect.owner));
+ const observedAge = now - Date.parse(source.snapshot!.observedAt);
+ for (const cell of state.cells) {
+ const expiry = running.get(cell.id), heartbeatAge = now - Date.parse(cell.heartbeat);
+ // Task completion does not resolve an external operation's unknown outcome.
+ result.set(cell.id, unknown.has(cell.id) ? 'uncertain' : expiry === undefined ? 'idle' : source.status !== 'observed' || (!sample && (observedAge > 15000 || observedAge < -5000)) || state.control.status !== 'active' || cell.status !== 'ready' || heartbeatAge < 0 || heartbeatAge > 60000 || expiry <= now ? 'uncertain' : 'recent');
+ }
+ return result;
+}
+
+/** Full searchable index, stable source clusters and hierarchy. No force solver. */
+export class SwarmIndex {
+ readonly nodes = new Map();
+ readonly children = new Map();
+ readonly sources = new Map();
+ readonly issues: string[] = [];
+ constructor(readonly swarm: Swarm, now = Date.now()) {
+ const sorted = [...swarm.sources].sort((a, b) => a.id.localeCompare(b.id));
+ sorted.forEach((source, index) => {
+ this.sources.set(source.id, source);
+ const angle = index * 2.3999632297, radius = sorted.length === 1 ? 0 : 95 * Math.sqrt(index + 1);
+ const sourceKey = nodeKey(source, 'source', source.id), centre = { x: Math.cos(angle) * radius, y: 0, z: Math.sin(angle) * radius };
+ this.nodes.set(sourceKey, { key: sourceKey, source, cell: null, selection: { sourceId: source.id, kind: 'source', id: source.id }, label: source.label, parent: null, point: { ...centre, y: -18 }, activity: 'idle' });
+ const cells = source.snapshot?.snapshot.cells ?? [], byId = new Map(cells.map(cell => [cell.id, cell]));
+ const activity = activities(source, now, swarm.sample);
+ const resolved = new Map(), visiting = new Set();
+ // Iterative ancestor traversal handles a 10,000-level chain without stack overflow.
+ const locate = (cell: Cell): Point => {
+ if (resolved.has(cell.id)) return resolved.get(cell.id)!;
+ const path: Cell[] = []; let cursor: Cell | undefined = cell;
+ while (cursor && !resolved.has(cursor.id) && !visiting.has(cursor.id)) {
+ visiting.add(cursor.id); path.push(cursor); cursor = cursor.parentId ? byId.get(cursor.parentId) : undefined;
+ }
+ if (cursor && visiting.has(cursor.id)) this.issues.push(`${source.label}: delegation cycle at ${cursor.id}`);
+ let point = cursor && resolved.get(cursor.id) || centre;
+ for (const next of path.reverse()) {
+ const phase = hash(nodeKey(source, 'cell', next.id)) * Math.PI * 2;
+ const distance = next.parentId ? Math.max(8, 38 / Math.sqrt(Math.min(next.depth + 1, 12))) : 0;
+ point = { x: point.x + Math.cos(phase) * distance, y: point.y + Math.sin(phase * 1.7) * distance * .4 + (next.parentId ? 5 : 0), z: point.z + Math.sin(phase) * distance };
+ resolved.set(next.id, point); visiting.delete(next.id);
+ }
+ return resolved.get(cell.id)!;
+ };
+ for (const cell of cells) {
+ const key = nodeKey(source, 'cell', cell.id), parent = cell.parentId && byId.has(cell.parentId) ? nodeKey(source, 'cell', cell.parentId) : sourceKey;
+ if (cell.parentId && !byId.has(cell.parentId)) this.issues.push(`${source.label}: parent of ${cell.id} is not observed`);
+ this.nodes.set(key, { key, source, cell, selection: { sourceId: source.id, kind: 'cell', id: cell.id }, label: cell.id, parent, point: locate(cell), activity: activity.get(cell.id) ?? 'idle' });
+ const children = this.children.get(parent) ?? []; children.push(key); this.children.set(parent, children);
+ }
+ });
+ for (const children of this.children.values()) children.sort();
+ }
+ ancestors(key: string): Node[] {
+ const path: Node[] = [], seen = new Set(); let node = this.nodes.get(key);
+ while (node && !seen.has(node.key)) { seen.add(node.key); path.push(node); node = node.parent ? this.nodes.get(node.parent) : undefined; }
+ return path.reverse();
+ }
+ /** Every indexed identity remains navigable; only the drawing is bounded. */
+ visible(focus: string | null, page = 0, limit = 600): Node[] {
+ if (this.nodes.size <= limit) return [...this.nodes.values()];
+ const keys = new Set([...this.nodes.values()].filter(node => !node.cell).map(node => node.key));
+ if (focus) {
+ for (const node of this.ancestors(focus).slice(-32)) keys.add(node.key);
+ const nearby = this.children.get(focus) ?? [];
+ const pageKeys = nearby.slice(page * 128, (page + 1) * 128);
+ for (const key of pageKeys) keys.add(key);
+ if (!this.nodes.get(focus)?.cell) for (const root of pageKeys) for (const key of (this.children.get(root) ?? []).slice(0, 12)) keys.add(key);
+ const parent = this.nodes.get(focus)?.parent;
+ if (parent) for (const key of (this.children.get(parent) ?? []).slice(0, 128)) keys.add(key);
+ } else {
+ for (const source of this.sources.values()) {
+ for (const root of (this.children.get(nodeKey(source, 'source', source.id)) ?? []).slice(0, 128)) {
+ keys.add(root);
+ for (const key of (this.children.get(root) ?? []).slice(0, 12)) keys.add(key);
+ }
+ }
+ }
+ return [...keys].slice(0, limit).map(key => this.nodes.get(key)!).filter(Boolean);
+ }
+ search(query: string): Selection[] {
+ const needle = query.trim().toLowerCase(), matches: Selection[] = [];
+ if (!needle) return matches;
+ for (const node of this.nodes.values()) if (`${node.label} ${node.source.label} ${node.source.factoryId} ${node.cell?.purpose ?? ''}`.toLowerCase().includes(needle)) matches.push(node.selection);
+ for (const source of this.sources.values()) {
+ for (const task of source.snapshot?.snapshot.tasks ?? []) if (`${task.id} ${task.projectId} ${task.branch}`.toLowerCase().includes(needle)) matches.push({ sourceId: source.id, kind: 'task', id: task.id });
+ for (const effect of source.snapshot?.snapshot.effects ?? []) if (`${effect.key} ${effect.kind}`.toLowerCase().includes(needle)) matches.push({ sourceId: source.id, kind: 'effect', id: effect.key });
+ }
+ return matches;
+ }
+ nodeFor(selection: Selection): Node | undefined {
+ const source = this.sources.get(selection.sourceId); if (!source) return;
+ if (selection.kind === 'cell' || selection.kind === 'source') return this.nodes.get(selectionKey(source, selection));
+ const state = source.snapshot?.snapshot;
+ const owner = selection.kind === 'task' ? state?.tasks.find(task => task.id === selection.id)?.owner : state?.effects.find(effect => effect.key === selection.id)?.owner;
+ return this.nodes.get(nodeKey(source, owner ? 'cell' : 'source', owner ?? source.id));
+ }
+}
diff --git a/src/swarm/observatory.css b/src/swarm/observatory.css
new file mode 100644
index 0000000..175f881
--- /dev/null
+++ b/src/swarm/observatory.css
@@ -0,0 +1,259 @@
+/* THE OBSERVATORY — art direction for the recorded FACTORY field.
+ * One deep field holds every authority. Instruments float above it on hairline
+ * glass plates with engraved corners, so the spatial record stays the subject
+ * and the controls read as optics laid on top of it. Emphasis follows evidence:
+ * source hues are deterministic, amber is uncertainty, grey is a retained
+ * record, and nothing moves unless a person moves it. */
+
+/* ── Masthead ───────────────────────────────────────────────────────────── */
+.swarm-top{position:relative;z-index:20;height:64px;padding:0 24px;display:flex;gap:16px;align-items:center;
+ border-bottom:1px solid var(--rule);background:linear-gradient(#0a0e16,#070a11)}
+.swarm-top::after{content:'';position:absolute;left:0;right:0;bottom:-1px;height:1px;background:linear-gradient(90deg,transparent,rgba(127,209,222,.3),transparent);opacity:.5}
+.swarm-brand{display:flex;align-items:center;gap:12px;font:400 20px/1 var(--serif);letter-spacing:-.035em;color:#e4ecf6;white-space:nowrap}
+.swarm-brand b{color:#b7d9e8;font-weight:400}
+.brand-mark{display:grid;place-items:center;width:30px;height:30px;flex:0 0 auto}
+.swarm-top button{font:10px var(--mono);letter-spacing:.1em;text-transform:uppercase;padding:7px 11px}
+#swarm-status{flex:1;font:10px/1.5 var(--mono);color:var(--muted);text-align:right;letter-spacing:.02em}
+.read-only{font:9px var(--mono);letter-spacing:.14em;color:var(--quiet);border:1px solid var(--rule);border-radius:2px;padding:4px 7px;white-space:nowrap;cursor:help}
+
+/* ── The field ──────────────────────────────────────────────────────────── */
+.swarm-shell{display:block;position:relative;height:calc(100vh - 110px);min-height:640px}
+.swarm-space{position:absolute;inset:0;overflow:hidden;background:
+ radial-gradient(130% 92% at 50% 128%,#12243390 0%,transparent 62%),
+ radial-gradient(90% 70% at 74% 18%,#1b1f3c66 0%,transparent 58%),
+ radial-gradient(80% 60% at 16% 30%,#10303a55 0%,transparent 60%),
+ linear-gradient(#060911,#04060b)}
+.field-backdrop{position:absolute;inset:0;z-index:0;pointer-events:none}
+.field-backdrop>span{position:absolute;inset:0;display:block}
+.field-nebula{background:conic-gradient(from 210deg at 46% 54%,#0e3a4733,#1d2a5a2e,#3a1f452b,#0e3a4733);filter:blur(72px);opacity:.5}
+.field-dust{opacity:.5;background:
+ radial-gradient(1px 1px at 12% 18%,#cfe9f2aa,transparent 60%),
+ radial-gradient(1px 1px at 72% 12%,#bcd6ff99,transparent 60%),
+ radial-gradient(1px 1px at 38% 74%,#e4d8ff88,transparent 60%),
+ radial-gradient(1px 1px at 86% 62%,#cfe9f299,transparent 60%),
+ repeating-radial-gradient(circle at 20% 30%,#9fc4d41a 0 1px,transparent 1px 90px),
+ repeating-radial-gradient(circle at 78% 68%,#aab8e018 0 1px,transparent 1px 124px)}
+.field-horizon{top:auto;bottom:16%;height:1px;background:linear-gradient(90deg,transparent,#5e8fa233 18%,#9fd3e155 50%,#5e8fa233 82%,transparent);opacity:.55}
+/* A measured floor: hairline rules receding to the horizon. Pure geometry,
+ * drawn once, never animated — it gives the field scale without decoration. */
+.field-grid{top:auto;bottom:0;height:16%;opacity:.3;
+ background:repeating-linear-gradient(90deg,transparent 0 46px,rgba(159,211,225,.16) 46px 47px),
+ repeating-linear-gradient(0deg,transparent 0 22px,rgba(159,211,225,.1) 22px 23px);
+ -webkit-mask-image:linear-gradient(#0000,#000);mask-image:linear-gradient(#0000,#000)}
+.field-vignette{background:radial-gradient(120% 100% at 50% 46%,transparent 38%,#04060bcc 100%)}
+#swarm-stage{position:absolute;inset:0;z-index:1;height:100%;min-height:0;overflow:hidden}
+#swarm-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;touch-action:none;background:transparent}
+#swarm-labels{position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:2}
+
+/* ── The glass plate: hairline, inner light, engraved corners ───────────── */
+.swarm-navigation,.swarm-inspector,.swarm-timeline,.swarm-diagnostics,.space-controls,#swarm-path{
+ background:var(--plate);backdrop-filter:blur(15px) saturate(1.12);-webkit-backdrop-filter:blur(15px) saturate(1.12);
+ border:1px solid var(--rule);box-shadow:0 26px 60px -32px #000d,inset 0 1px 0 rgba(190,220,240,.07)}
+.swarm-navigation,.swarm-inspector{position:absolute;z-index:12}
+.swarm-navigation::before,.swarm-inspector::after{content:'';position:absolute;width:9px;height:9px;pointer-events:none;opacity:.55}
+.swarm-navigation::before{left:0;top:0;border-left:1px solid var(--accent);border-top:1px solid var(--accent)}
+.swarm-inspector::after{right:0;top:0;border-right:1px solid var(--accent);border-top:1px solid var(--accent)}
+.section-title{font:var(--kicker);letter-spacing:.18em;color:var(--quiet);text-transform:uppercase}
+
+/* ── Atlas rail ─────────────────────────────────────────────────────────── */
+.swarm-navigation{left:22px;top:22px;bottom:84px;width:292px;padding:20px 18px;overflow:hidden auto}
+.navigation-hidden .swarm-navigation{display:none}
+.search-label{display:grid;gap:8px;font:9px var(--mono);letter-spacing:.14em;color:var(--quiet);text-transform:uppercase}
+input[type=search]{width:100%;font:12px var(--mono);background:#060a11;border:1px solid var(--rule);padding:10px 11px;color:var(--text)}
+input[type=search]::placeholder{color:#4e5b6d}
+#search-results{max-height:28vh;overflow:auto;margin-top:12px}
+#search-results p{font:9px/1.6 var(--mono);letter-spacing:.06em;color:var(--quiet);margin:0 0 6px;text-transform:uppercase}
+#search-results button{display:block;width:100%;text-align:left;border:0;border-bottom:1px solid rgba(150,180,210,.08);border-radius:0;font:11px var(--mono);padding:9px 2px;margin:0;color:var(--muted);overflow-wrap:anywhere}
+.rail-heading{display:flex;align-items:center;gap:10px;margin:24px 0 10px}
+.rail-heading::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,var(--rule),transparent)}
+#source-list{margin:0;display:grid}
+#source-list button{display:grid;grid-template-columns:auto minmax(0,1fr);grid-template-rows:auto auto;column-gap:11px;align-items:center;
+ width:100%;text-align:left;margin:0;padding:12px 10px 11px 13px;border:0;border-bottom:1px solid rgba(150,180,210,.08);border-radius:0;background:transparent;position:relative}
+#source-list button::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--accent);opacity:.5;transition:opacity .16s var(--ease),box-shadow .16s var(--ease)}
+#source-list button:hover::before,#source-list button[aria-pressed=true]::before{opacity:1;box-shadow:0 0 12px var(--accent)}
+#source-list button[aria-pressed=true]{background:linear-gradient(90deg,rgba(127,209,222,.1),transparent 74%)}
+.source-dot{grid-row:1/3}
+.source-name{font:400 16px/1.25 var(--serif);letter-spacing:-.015em;color:#e6eef7;overflow-wrap:anywhere}
+#source-list button small{grid-column:2;font:9px var(--mono);letter-spacing:.07em;color:var(--quiet);margin-top:4px;text-transform:uppercase}
+#source-list button.source-stale,#source-list button.source-unavailable{--accent:#7d8798;color:var(--muted)}
+#source-list button.source-stale .source-name,#source-list button.source-unavailable .source-name{color:#aab6c6}
+#source-list button.source-unavailable::before{background:repeating-linear-gradient(180deg,#6b7789 0 3px,transparent 3px 7px);opacity:.85}
+#branch-list{max-height:28vh;overflow:auto;display:grid}
+#branch-list button{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:9px;align-items:baseline;width:100%;text-align:left;
+ border:0;border-bottom:1px solid rgba(150,180,210,.07);border-radius:0;background:transparent;padding:9px 4px;margin:0}
+.branch-dot{width:9px;height:9px;transform:translateY(-1px)}
+.branch-name{font:11px var(--mono);color:#cdd9e8;overflow-wrap:anywhere}
+#branch-list button small{grid-column:2;display:block;font:9px var(--mono);color:var(--quiet);margin-top:4px}
+#branch-list button[aria-pressed=true] .branch-name{color:#eafcff}
+#branch-pages{font:9px/1.7 var(--mono);color:var(--quiet);margin-top:10px}
+#branch-pages button{font:9px var(--mono);letter-spacing:.08em;padding:6px 8px;margin:6px 6px 0 0;text-transform:uppercase}
+#branch-pages p{margin:8px 0 0}
+.scope-note{font:9px/1.6 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--faint);border-top:1px solid var(--rule);padding-top:13px;margin:18px 0 0;cursor:help}
+
+/* ── Sextant bar ────────────────────────────────────────────────────────── */
+.space-toolbar{position:absolute;z-index:14;top:22px;left:336px;right:26px;display:flex;gap:14px;align-items:flex-start;justify-content:space-between;pointer-events:none}
+.has-selection .space-toolbar{right:422px}
+.space-toolbar>*{pointer-events:auto}
+#swarm-path{display:flex;flex-wrap:wrap;align-items:center;gap:3px;font:10px var(--mono);padding:5px 9px;border-radius:999px;max-width:min(560px,56%)}
+#swarm-path button{font:10px var(--mono);padding:4px 8px;border:0;border-radius:999px;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--quiet)}
+#swarm-path button:hover{color:var(--text);background:rgba(127,209,222,.08)}
+#swarm-path button[aria-pressed=true]{color:#eafcff;background:rgba(127,209,222,.13)}
+#swarm-path span{color:#45505f;font-size:9px;padding:0 1px}
+.space-controls{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:999px;font:10px var(--mono);color:var(--muted)}
+.space-controls button{font:9px var(--mono);letter-spacing:.1em;text-transform:uppercase;padding:6px 9px;border-radius:999px}
+.control-field,.follow-control{display:flex;align-items:center;gap:6px;font:9px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--quiet)}
+.space-controls select{font:10px var(--mono);padding:5px 6px;background:#070b12;border:1px solid var(--rule);color:var(--muted)}
+.follow-control input{accent-color:var(--accent);width:12px;height:12px}
+
+/* ── Caption: the field speaks in figures, not sentences ────────────────── */
+.space-caption{position:absolute;z-index:6;left:338px;bottom:92px;max-width:min(560px,46vw);pointer-events:none;color:var(--muted)}
+/* A reading scrim: field labels may drift behind the caption, so the figures
+ * keep a soft floor to sit on instead of colliding with the record. */
+.space-caption::before{content:'';position:absolute;z-index:-1;inset:-22px -70px -26px -26px;
+ background:radial-gradient(118% 128% at 2% 100%,rgba(4,6,11,.92),rgba(4,6,11,.62) 48%,transparent 78%)}
+.navigation-hidden .space-caption{left:32px}
+.navigation-hidden .space-toolbar{left:26px}
+.caption-kicker{font:var(--kicker);letter-spacing:.26em;color:var(--faint);margin:0 0 14px;text-transform:uppercase}
+.field-counts{display:flex;flex-wrap:wrap;align-items:flex-end;gap:0;border-top:1px solid var(--rule);padding-top:14px}
+.figure{display:grid;gap:3px;padding:0 20px 0 0}
+.figure+.figure{border-left:1px solid var(--rule);padding-left:20px}
+.figure b{font:400 clamp(26px,3.1vw,42px)/.95 var(--serif);letter-spacing:-.035em;color:#eaf2fa;font-weight:400;text-shadow:0 2px 30px #04060b}
+.figure i{font:9px var(--mono);font-style:normal;letter-spacing:.2em;text-transform:uppercase;color:var(--faint)}
+.has-selection .field-counts .figure b{font-size:clamp(20px,2.1vw,28px)}
+.field-legend{display:flex;flex-wrap:wrap;gap:6px 16px;list-style:none;margin:16px 0 0;padding:14px 0 0;border-top:1px solid var(--rule);
+ font:9px var(--mono);letter-spacing:.1em;color:var(--quiet);text-transform:uppercase;pointer-events:auto}
+.field-legend li{display:flex;align-items:center;gap:7px;cursor:help}
+.legend-note{font:9px/1.6 var(--mono);letter-spacing:.1em;color:var(--faint);margin:10px 0 0;text-transform:uppercase}
+.has-selection .field-legend,.has-selection .legend-note{display:none}
+
+/* ── Static status markers ──────────────────────────────────────────────────
+ * The same five engraved forms the field draws, rebuilt in pure CSS for the
+ * legend, the atlas rail and the reading guide. Shape alone identifies state:
+ * nothing animates, and nothing depends on glow to be read. */
+.marker{position:relative;box-sizing:border-box;display:inline-block;flex:none;width:10px;height:10px;border-radius:50%;background:none}
+.marker::after,.marker::before{content:'';position:absolute;box-sizing:border-box}
+.marker[data-marker=plate]{border:1px solid var(--accent);opacity:.95}
+.marker[data-marker=plate]::after{inset:2.5px;border:1px solid var(--accent);border-radius:1px;transform:rotate(45deg);opacity:.72}
+.marker[data-marker=idle]{border:1px solid var(--accent);opacity:.62}
+.marker[data-marker=recent]{border:1px solid var(--accent)}
+.marker[data-marker=recent]::after{inset:2.5px;border-radius:50%;background:var(--accent)}
+.marker[data-marker=uncertain]{border:1px solid var(--warn)}
+.marker[data-marker=uncertain]::after{left:1.5px;right:1.5px;top:50%;height:1px;margin-top:-.5px;background:var(--warn)}
+.marker[data-marker=quiet]{border:1px dashed #7d8798;opacity:.9}
+.field-legend .marker{width:9px;height:9px}
+
+/* ── Field labels ───────────────────────────────────────────────────────── */
+.swarm-label{position:absolute;top:0;left:0;font:10px var(--mono);letter-spacing:.04em;color:#a2b6c7;white-space:nowrap;max-width:168px;overflow:hidden;text-overflow:ellipsis;
+ padding:2px 6px;border-radius:2px;background:rgba(5,8,14,.42);text-shadow:0 1px 6px #04060b}
+.swarm-label:not(.source-label)[data-marker]::before{content:'';display:inline-block;box-sizing:border-box;width:7px;height:7px;margin-right:6px;border-radius:50%;vertical-align:.5px}
+.swarm-label[data-marker=idle]::before{border:1px solid var(--accent);opacity:.6}
+.swarm-label[data-marker=recent]::before{border:1px solid var(--accent);box-shadow:inset 0 0 0 1.6px #060a11,inset 0 0 0 4px var(--accent)}
+.swarm-label[data-marker=uncertain]::before{border:1px solid var(--warn);background:linear-gradient(var(--warn),var(--warn)) center/4px 1px no-repeat}
+.swarm-label[data-marker=quiet]::before{border:1px dashed #7d8798}
+.swarm-label.active{color:#d6f4fb;box-shadow:inset 0 0 0 1px rgba(127,209,222,.22)}
+.swarm-label[data-marker=uncertain]{color:#e3c79c}
+.swarm-label[data-marker=quiet]{color:#8c99ac}
+.swarm-label.source-label{display:flex;flex-direction:column;align-items:center;gap:2px;font:400 18px/1.1 var(--serif);letter-spacing:-.01em;color:#dcecf4;background:transparent;padding:0;max-width:220px}
+.swarm-label.source-label i{font:9px var(--mono);font-style:normal;letter-spacing:.2em;text-transform:uppercase;color:var(--accent);opacity:.75}
+.swarm-label.selected{color:#f1fdff;background:rgba(127,209,222,.12);box-shadow:inset 0 0 0 1px rgba(127,209,222,.4)}
+
+/* ── Evidence sheet ─────────────────────────────────────────────────────── */
+.swarm-inspector{display:none;right:22px;top:22px;bottom:84px;width:382px;padding:22px 24px 28px;overflow:hidden auto;z-index:16}
+.swarm-inspector::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;background:linear-gradient(180deg,var(--accent),transparent 72%);opacity:.85}
+.has-selection .swarm-inspector{display:block}
+.swarm-inspector>.section-title{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--rule);padding-bottom:13px;margin-bottom:18px;color:var(--muted)}
+#swarm-close{font-size:17px;line-height:1;padding:2px 7px;color:var(--quiet);border:0}
+.evidence-kicker{font:var(--kicker)!important;letter-spacing:.22em!important;color:var(--accent)!important;margin:0 0 6px!important;text-transform:uppercase}
+.source-state{font:9px var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--quiet);margin-bottom:4px}
+.swarm-inspector h2{font:400 clamp(21px,2vw,27px)/1.14 var(--serif);letter-spacing:-.03em;margin:6px 0 16px;color:#eaf2fa;overflow-wrap:anywhere}
+.swarm-inspector h3{font:9px var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--quiet);border-top:1px solid var(--rule);padding-top:15px;margin:26px 0 4px;font-weight:400}
+.swarm-inspector p{font:11.5px/1.7 var(--sans);color:var(--muted);overflow-wrap:anywhere}
+.swarm-inspector .chip{margin:4px 6px 4px 0}
+.evidence-empty{font:10px var(--mono)!important;letter-spacing:.08em;color:var(--faint)!important;text-transform:uppercase}
+.swarm-inspector dl{display:block;font:11px/1.55 var(--mono);margin:14px 0 0}
+.swarm-inspector dt{color:var(--quiet);padding-top:10px;margin:0;border-top:1px solid rgba(150,180,210,.09);font-size:9px;letter-spacing:.11em;text-transform:uppercase}
+.swarm-inspector dd{margin:4px 0 10px;color:#d6e1ee;overflow-wrap:anywhere}
+.swarm-inspector button:not(#swarm-close){display:block;width:100%;text-align:left;font:10.5px var(--mono);border:0;border-bottom:1px solid rgba(150,180,210,.09);border-radius:0;padding:10px 2px;margin:0;color:#bcd8e0;overflow-wrap:anywhere;position:relative}
+.swarm-inspector button:not(#swarm-close):hover{color:#eafcff;background:rgba(127,209,222,.06);padding-left:7px}
+.marker-guide{list-style:none;margin:14px 0 4px;padding:0;display:grid;gap:1px}
+.marker-guide li{display:grid;grid-template-columns:12px minmax(0,1fr);gap:4px 11px;align-items:center;padding:11px 0;border-top:1px solid rgba(150,180,210,.09)}
+.marker-guide .guide-dot{grid-row:1/3}
+.marker-guide strong{font:9px var(--mono);font-weight:400;letter-spacing:.13em;text-transform:uppercase;color:#dce7f2}
+.marker-guide small{font:10px/1.5 var(--mono);color:var(--quiet)}
+
+/* ── Tracking ledger, measurements, footer ──────────────────────────────── */
+.swarm-timeline{position:absolute;z-index:15;bottom:22px;left:338px;width:min(430px,calc(100% - 820px));padding:12px 16px;border-radius:3px}
+.swarm-timeline summary{display:flex;gap:10px;align-items:baseline;margin:0;font:var(--kicker);letter-spacing:.17em;color:var(--muted);text-transform:uppercase}
+.swarm-timeline summary span{font:9px var(--mono);letter-spacing:.04em;color:var(--faint);text-transform:none}
+#tracking-list{font:10px/1.6 var(--mono);max-height:180px;overflow:auto;padding-top:10px}
+#tracking-list p{color:var(--faint);margin:0}
+.tracking-row{display:grid;grid-template-columns:auto auto minmax(0,1fr);gap:12px;padding:7px 0;border-bottom:1px solid rgba(150,180,210,.07);color:#c3cfdd}
+.tracking-row time{color:var(--faint);font-size:9px}
+.swarm-measurements{position:absolute;z-index:17;right:24px;bottom:26px;max-width:320px;text-align:right;font:9px var(--mono);color:var(--faint)}
+.swarm-measurements summary{letter-spacing:.16em;text-transform:uppercase}
+.swarm-diagnostics{font:9px/1.85 var(--mono);margin-top:10px;padding:11px 12px;color:var(--muted);text-align:left;overflow-wrap:anywhere;border-radius:3px}
+.sample-notice{color:#e3bf85!important}
+footer{position:relative;z-index:20;min-height:46px;display:flex;flex-wrap:wrap;align-items:center;gap:7px 18px;
+ border-top:1px solid var(--rule);font:9px/1.6 var(--mono);letter-spacing:.1em;color:var(--faint);padding:10px 26px;background:#070a11;text-transform:uppercase}
+footer b{color:var(--warn);font-weight:400}
+.foot-o{width:9px;height:9px;border-radius:50%;border:1px solid var(--rule-strong);position:relative}
+.foot-o::after{content:'';position:absolute;left:1.5px;right:1.5px;top:50%;height:1px;margin-top:-.5px;background:var(--rule-strong)}
+
+/* ── Responsive framing ─────────────────────────────────────────────────── */
+@media(max-width:1320px){
+ .swarm-timeline{width:min(400px,calc(100% - 780px))}
+ .swarm-inspector{width:340px}
+ .has-selection .space-toolbar{right:378px}
+}
+@media(max-width:1120px){
+ .swarm-navigation{width:250px;left:16px;top:16px;padding:18px 15px}
+ .space-toolbar{left:280px;right:18px;top:16px}
+ .has-selection .space-toolbar{right:312px}
+ .swarm-inspector{width:286px;right:16px;top:72px;padding:20px}
+ .space-caption{left:282px;bottom:100px;max-width:min(420px,46vw)}
+ .swarm-timeline{left:280px;width:calc(100% - 620px)}
+ .field-legend{gap:5px 12px}
+ .figure{padding-right:14px}
+ .figure+.figure{padding-left:14px}
+}
+@media(max-width:900px){
+ .swarm-timeline{display:none}
+ .space-caption{bottom:34px}
+}
+@media(max-width:800px){
+ .swarm-top{height:auto;min-height:68px;flex-wrap:wrap;gap:10px;padding:12px 16px;position:sticky;top:0}
+ #swarm-status{order:4;flex-basis:100%;text-align:left;font-size:9px}
+ .read-only{margin-left:auto}
+ .swarm-shell{height:auto;min-height:0;display:block}
+ .swarm-space{position:relative;height:clamp(420px,62vh,620px)}
+ .space-toolbar,.has-selection .space-toolbar{top:12px;left:12px;right:12px;flex-wrap:wrap;gap:8px}
+ #swarm-path{max-width:100%}
+ .swarm-navigation{position:relative;left:auto;top:auto;bottom:auto;width:auto;margin:14px 12px 0;max-height:none;padding:18px 16px;backdrop-filter:none;background:var(--plate-solid)}
+ .navigation-hidden .swarm-navigation{display:none}
+ #search-results{max-height:none}
+ #source-list{grid-auto-flow:column;grid-auto-columns:minmax(184px,1fr);overflow:auto;gap:10px;padding-bottom:6px}
+ #source-list button{border-bottom:0;border:1px solid var(--rule);padding:12px}
+ #branch-list{max-height:220px}
+ .space-caption,.navigation-hidden .space-caption{left:16px;right:16px;bottom:20px;max-width:none}
+ .field-legend,.has-selection .field-legend{display:flex}
+ .legend-note,.has-selection .legend-note{display:block}
+ .swarm-measurements{right:14px;bottom:14px;max-width:62%}
+ .swarm-inspector,.has-selection .swarm-inspector{display:block;position:relative;right:auto;left:auto;top:auto;bottom:auto;width:auto;margin:14px 12px;padding:22px;backdrop-filter:none;background:var(--plate-solid)}
+ footer{padding:14px 16px}
+}
+@media(max-width:520px){
+ .swarm-brand{font-size:18px}
+ #swarm-find{margin-left:auto}
+ .read-only{display:none}
+ .swarm-space{height:clamp(460px,60vh,560px)}
+ .space-controls{flex-wrap:wrap}
+ .field-legend{gap:4px 10px;font-size:8.5px;letter-spacing:.05em}
+ .legend-note{display:none}
+ .caption-kicker{display:none}
+ .figure b{font-size:22px}
+ .figure,.figure+.figure{padding:0 10px}
+ .swarm-inspector h2{font-size:22px}
+}
+@media(prefers-reduced-motion:reduce){
+ .field-nebula{filter:none;opacity:.32}
+}
diff --git a/src/swarm/preview.ts b/src/swarm/preview.ts
new file mode 100644
index 0000000..a7cfdbf
--- /dev/null
+++ b/src/swarm/preview.ts
@@ -0,0 +1,24 @@
+import type { Swarm, Source, Cell } from './model';
+
+/** Opt-in fixtures only; never used to replace a failed live read. */
+export function swarmPreview(totalCells = 54): Swarm {
+ const now = new Date().toISOString(), future = new Date(Date.now() + 5 * 60000).toISOString(), digest = 'a'.repeat(64);
+ const labels = ['FACTORY', 'Independent review', 'Delivery watch'];
+ const sources: Source[] = labels.map((label, sourceIndex) => {
+ const count = Math.floor(totalCells / 3) + (sourceIndex < totalCells % 3 ? 1 : 0);
+ const cells: Cell[] = Array.from({ length: Math.max(1, count) }, (_, index) => ({
+ id: index === 0 ? 'root' : `cell-${index}`, parentId: index === 0 ? null : index <= 4 ? 'root' : `cell-${Math.floor((index - 1) / 4)}`,
+ depth: index === 0 ? 0 : Math.floor(Math.log(index * 3 + 1) / Math.log(4)), role: index === 0 ? 'coordinator' : index % 5 === 0 ? 'verifier' : index % 7 === 0 ? 'watcher' : 'developer',
+ status: index % 17 === 16 ? 'retired' : 'ready', heartbeat: now,
+ purpose: index === 0 ? 'Coordinate the recorded local delegation.' : `Inspect the work assigned to ${label} cell ${index}.`,
+ allocationCents: Math.floor(10000 / 4 ** (index === 0 ? 0 : Math.floor(Math.log(index * 3 + 1) / Math.log(4)))), budgetBasis: 'logical-allocation',
+ }));
+ const tasks = cells.length > 1 ? [{ id: 'implementation', projectId: 'flujo', branch: 'codex/sample', status: 'running' as const, owner: 'cell-1', attempt: 1, leaseExpiry: future, specDigest: digest, candidate: null, review: null },
+ { id: 'retire-operation', projectId: 'flujo', branch: 'codex/sample-retire', status: 'completed' as const, owner: cells.length > 2 ? 'cell-2' : 'root', attempt: 1, leaseExpiry: null, specDigest: digest, candidate: null, review: null },
+ { id: 'exploration', projectId: 'flujo', branch: 'codex/sample-history', status: 'cancelled' as const, owner: 'root', attempt: 2, leaseExpiry: null, specDigest: digest, candidate: { sha256: digest }, review: { accepted: false, reviewerId: 'root', evidenceDigest: digest, candidateDigest: digest, specDigest: digest, attempt: 2 } }] : [];
+ return { id: `source-${sourceIndex + 1}`, label, factoryId: `design-authority-${sourceIndex + 1}`, status: 'observed', events: [],
+ snapshot: { schemaVersion: 1, factoryId: `design-authority-${sourceIndex + 1}`, scope: 'local-coordinator', revision: 42 + sourceIndex, cursor: 'NDI', observedAt: now, buildRevision: 'unknown', capabilities: { snapshot: true, events: true, commands: false },
+ snapshot: { control: { status: sourceIndex === 1 ? 'paused' : 'active', epoch: 3, mission: 'Improve FLUJO, one accepted delivery at a time.' }, cells, tasks, effects: sourceIndex === 2 ? [{ key: 'pending-effect', kind: 'flow_call', state: 'unknown', owner: 'cell-1', taskId: 'implementation', scope: 'task', scopeId: 'implementation', ownerEpoch: 1, controlEpoch: 3, createdAt: now, updatedAt: now, requestDigest: digest }] : [], workerQuiescence: 'unverified' } } };
+ });
+ return { schemaVersion: 1, scope: 'operator-source-registry', observedAt: now, commands: false, sources, sample: true };
+}
diff --git a/src/swarm/renderer.ts b/src/swarm/renderer.ts
new file mode 100644
index 0000000..0b2b8cc
--- /dev/null
+++ b/src/swarm/renderer.ts
@@ -0,0 +1,432 @@
+import { BufferGeometry, Color, Float32BufferAttribute, LineBasicMaterial, LineSegments, PerspectiveCamera, Points, Scene, ShaderMaterial, Vector3, WebGLRenderer } from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import type { Node, Point, Source } from './model';
+
+/** Deterministic authority palette. The navigation indicators read the same
+ * function, so a plate in the field and its index entry cannot disagree. */
+const hues = ['#7fd1de', '#9bb8e8', '#c2a7e8', '#e8a7b8', '#e8c98a', '#8fd9b6'];
+export const sourceHue = (id: string) => hues[[...id].reduce((sum, char) => (sum * 31 + char.charCodeAt(0)) >>> 0, 0) % hues.length]!;
+/** Shared with the atlas rail; identical input gives an identical colour. */
+export const sourceColor = sourceHue;
+export const uncertainHue = '#e0a85c';
+export const retiredHue = '#7d8798';
+
+/** The five static status forms. Shape, not motion or brightness alone,
+ * carries the meaning, so every state stays recognisable with reduced motion:
+ * plate - a registered authority's engraved disc. Its centre is a hollow
+ * keystone, never a filled core: a plate is not worker activity.
+ * idle - observed record, no running lease: a single open hairline ring.
+ * recent - unexpired running lease on a fresh heartbeat: ring plus a
+ * separated inner core (bullseye). Observation, not host health.
+ * uncertain - unconfirmed running-work evidence or an owned unknown outcome:
+ * amber ring struck through.
+ * quiet - retained record (retired, reserved, stale or unavailable):
+ * a broken dashed ring with no core. */
+export type Marker = 'plate' | 'idle' | 'recent' | 'uncertain' | 'quiet';
+// Canonical envelopes are replaced on observation; clock ticks only update
+// source availability/activity. Cache once per envelope, with no retained owner
+// after that observation is collected and no per-cell scan of all effects.
+const unknownOwners = new WeakMap, Set>();
+export function hasUnknownOutcome(source: Source, cellId: string): boolean {
+ const envelope = source.snapshot; if (!envelope) return false;
+ let owners = unknownOwners.get(envelope);
+ if (!owners) { owners = new Set(envelope.snapshot.effects.filter(effect => effect.state === 'unknown').map(effect => effect.owner)); unknownOwners.set(envelope, owners); }
+ return owners.has(cellId);
+}
+export const markerOf = (node: Node): Marker => !node.cell ? node.source.status === 'observed' ? 'plate' : 'quiet'
+ // An unknown owned effect outranks completion, retirement and pause.
+ : hasUnknownOutcome(node.source, node.cell.id) ? 'uncertain'
+ : node.source.status !== 'observed' || node.cell.status === 'retired' || node.cell.status === 'reserved' ? 'quiet'
+ : node.activity === 'uncertain' ? 'uncertain'
+ : node.activity === 'recent' ? 'recent' : 'idle';
+export const markerForm: Record = { plate: 0, idle: 1, recent: 2, uncertain: 3, quiet: 4 };
+/** Wording is deliberately about the record, never about physical health. */
+export const markerLegend: Record = {
+ plate: 'registered authority · not worker activity',
+ idle: 'observed record · no running lease',
+ recent: 'running lease · recent heartbeat recorded',
+ uncertain: 'running-work evidence unconfirmed or owned outcome unknown',
+ quiet: 'retained record · retired, reserved or stale',
+};
+export const markerTitle: Record = {
+ plate: 'Authority plate', idle: 'Observed idle', recent: 'Recent leased work', uncertain: 'Uncertain', quiet: 'Retained record',
+};
+export function activityEvidence(node: Node): string {
+ if (node.cell && hasUnknownOutcome(node.source, node.cell.id)) return 'owned external outcome unknown · unresolved by completion or retirement';
+ if (node.source.status !== 'observed') return 'retained observation · current activity unconfirmed';
+ if (node.cell?.status === 'retired' || node.cell?.status === 'reserved') return `${node.cell.status} record · worker activity unverified`;
+ return node.activity === 'uncertain' ? 'running-work evidence unconfirmed · inspect controller, lease and heartbeat'
+ : node.activity === 'recent' ? 'running lease unexpired · heartbeat recent in this observation'
+ : 'observed record · no running lease';
+}
+export const markerColor = (node: Node): string => {
+ const marker = markerOf(node);
+ if (marker === 'uncertain') return uncertainHue;
+ if (marker === 'quiet' || node.source.status !== 'observed') return retiredHue;
+ return sourceHue(node.source.id);
+};
+const tint = markerColor;
+
+/** One batched point program: engraved plates, cell rings, cores and haloes.
+ * Depth attenuation keeps a deep field legible without a second pass. */
+const vertexShader = `attribute float aSize; attribute float aActive; attribute float aForm; attribute float aSelected;
+varying vec3 vColor; varying float vForm; varying float vSelected; varying float vFade; uniform float uTime;
+void main(){
+ vColor=color; vForm=aForm; vSelected=aSelected;
+ vec4 p=modelViewMatrix*vec4(position,1.);
+ float depth=max(1.,-p.z);
+ vFade=clamp(1.25-depth/5200.,.34,1.);
+ // Optional breathing, a courtesy only: aActive is zero under reduced motion
+ // and recent work is already distinguished by its static bullseye form.
+ float breath=1.+aActive*.07*sin(uTime*2.+position.x*.12);
+ gl_PointSize=clamp(aSize*breath*(1.+aSelected*.26)*200./depth,7.,78.);
+ gl_Position=projectionMatrix*p;
+}`;
+const fragmentShader = `varying vec3 vColor; varying float vForm; varying float vSelected; varying float vFade;
+void main(){
+ vec2 uv=gl_PointCoord-.5; float d=length(uv)*2.;
+ if(d>1.)discard;
+ float plate=step(vForm,.5);
+ float idle=step(.5,vForm)*step(vForm,1.5);
+ float recent=step(1.5,vForm)*step(vForm,2.5);
+ float uncertain=step(2.5,vForm)*step(vForm,3.5);
+ float quiet=step(3.5,vForm);
+ // One shared engraved outline; quiet records break it into nine dashes.
+ float ring=smoothstep(.47,.55,d)*(1.-smoothstep(.65,.73,d));
+ float dash=step(.42,fract(atan(uv.y,uv.x)*1.43239));
+ float outline=ring*(1.-quiet*dash);
+ // Recent: a separated core inside the ring reads as a bullseye at any size.
+ float core=recent*(1.-smoothstep(.25,.33,d));
+ // Uncertain: the ring is struck through, a caution glyph rather than a glow.
+ float bar=uncertain*(1.-smoothstep(.06,.10,abs(uv.y)*2.))*(1.-smoothstep(.30,.42,abs(uv.x)*2.));
+ // Authority plate: wide bezel and a hollow keystone; deliberately no core.
+ float bezel=plate*smoothstep(.84,.90,d)*(1.-smoothstep(.96,1.,d));
+ float keystone=plate*(1.-smoothstep(.03,.06,abs((abs(uv.x)+abs(uv.y))*2.-.36)));
+ float halo=exp(-d*d*3.4)*(.05+.13*recent+.04*plate);
+ vec2 q=abs(uv)*2.;
+ float spike=vSelected*max(exp(-q.x*22.),exp(-q.y*22.))*(1.-smoothstep(.2,1.,d))*.5;
+ float select=vSelected*smoothstep(.80,.88,d)*(1.-smoothstep(.95,1.,d));
+ float weight=outline*(recent+idle*.62+uncertain*.85+quiet*.5+plate*.78);
+ vec3 lit=mix(vColor,vec3(.93,.99,1.),clamp(core*.5+spike*.7+select*.8,0.,1.));
+ float alpha=(weight+core*.95+bar*.9+bezel*.6+keystone*.5+halo+spike*.6+select*.95)*vFade;
+ gl_FragColor=vec4(lit,clamp(alpha,0.,1.));
+}`;
+
+/** Two semantic draw batches, bounded labels, no postprocessing or idle orbit.
+ * Rendering is demand-driven; evidenced activity pulses at no more than 30fps. */
+export class SwarmRenderer {
+ private gl: WebGLRenderer | null = null;
+ private ctx: CanvasRenderingContext2D | null = null;
+ private scene = new Scene();
+ private camera = new PerspectiveCamera(46, 1, .1, 20000);
+ private controls: OrbitControls | null = null;
+ private points: Points | null = null;
+ private lines: LineSegments | null = null;
+ private nodes: Node[] = [];
+ private selected: string | null = null;
+ private frameId = 0;
+ private active = false;
+ private lastFrame = 0;
+ private width = 1;
+ private height = 1;
+ private zoom = 1;
+ private pan = { x: 0, y: 0 };
+ private projected = new Map();
+ private drag: { x: number; y: number; startX: number; startY: number; moved: boolean } | null = null;
+ private observer: ResizeObserver;
+ private disposeInput: () => void;
+ private disposed = false;
+ private motion = matchMedia('(prefers-reduced-motion: reduce)');
+ private labelElements: HTMLSpanElement[] = [];
+ private labelSizes = new Map();
+ private submissions = 0;
+ private lastSubmissionMs = 0;
+ private flight: { start: number; fromPosition: Vector3; fromTarget: Vector3; toPosition: Vector3; toTarget: Vector3 } | null = null;
+ private lastFit: { point?: Point; sourceId?: string } | null = null;
+ readonly mode: '3d' | '2d';
+ onPick: (node: Node) => void = () => undefined;
+ onStats: (stats: { mode: string; visible: number; labels: number; draws: number; submissions: number; cpuMs: number }) => void = () => undefined;
+
+ constructor(private canvas: HTMLCanvasElement, private labels: HTMLElement, requested: '3d' | '2d') {
+ if (requested === '3d') {
+ try {
+ this.gl = new WebGLRenderer({ canvas, antialias: false, powerPreference: 'low-power', alpha: true });
+ this.gl.setPixelRatio(Math.min(devicePixelRatio || 1, 1.25));
+ this.gl.setClearColor(0x05070c, 0);
+ this.camera.position.set(52, 128, 196);
+ this.controls = new OrbitControls(this.camera, canvas);
+ this.controls.enableDamping = false;
+ this.controls.enablePan = true;
+ this.controls.minDistance = 8;
+ this.controls.maxDistance = 10000;
+ this.controls.maxPolarAngle = Math.PI * .92;
+ this.controls.addEventListener('change', this.request);
+ canvas.addEventListener('webglcontextlost', this.contextLost);
+ } catch { this.gl = null; }
+ }
+ if (!this.gl) {
+ // A failed GL creation may still bind its context; replace the canvas.
+ const replacement = canvas.cloneNode(false) as HTMLCanvasElement;
+ canvas.replaceWith(replacement); this.canvas = replacement;
+ this.ctx = replacement.getContext('2d');
+ if (!this.ctx) throw new Error('No canvas renderer is available.');
+ }
+ this.mode = this.gl ? '3d' : '2d';
+ this.observer = new ResizeObserver(this.resize);
+ this.observer.observe(this.canvas.parentElement!);
+ document.addEventListener('visibilitychange', this.visibility);
+ this.motion.addEventListener('change', this.motionChanged);
+ this.disposeInput = this.wireInput();
+ this.resize();
+ }
+ private contextLost = (event: Event) => { event.preventDefault(); this.active = false; this.canvas.dispatchEvent(new CustomEvent('swarm-renderer-lost', { bubbles: true })); };
+ private visibility = () => { if (document.hidden) { cancelAnimationFrame(this.frameId); this.frameId = 0; } else this.request(); };
+ private motionChanged = () => {
+ if (this.motion.matches) {
+ this.flight = null;
+ cancelAnimationFrame(this.frameId); this.frameId = 0;
+ }
+ this.setNodes(this.nodes, this.selected);
+ };
+ private resize = () => {
+ const rect = this.canvas.parentElement!.getBoundingClientRect();
+ this.width = Math.max(1, rect.width); this.height = Math.max(1, rect.height);
+ if (this.gl) { this.gl.setSize(this.width, this.height, false); this.camera.aspect = this.width / this.height; this.camera.updateProjectionMatrix(); }
+ else { const ratio = Math.min(devicePixelRatio || 1, 1.25); this.canvas.width = this.width * ratio; this.canvas.height = this.height * ratio; this.ctx!.setTransform(ratio, 0, 0, ratio, 0, 0); }
+ this.viewport();
+ if (this.lastFit) this.frame(this.lastFit.point, this.lastFit.sourceId);
+ this.request();
+ };
+ /** Fit observations into the area left by visible instruments. The backdrop
+ * remains full bleed, while atlas/evidence/caption plates do not hide focus. */
+ private viewport(): { x: number; y: number; width: number; height: number } {
+ const stage = this.canvas.parentElement!.getBoundingClientRect();
+ let left = 20, right = this.width - 20, top = 24, bottom = this.height - 24;
+ const overlaps = (rect: DOMRect) => rect.width > 0 && rect.height > 0 && rect.top < stage.bottom && rect.bottom > stage.top;
+ const atlas = document.querySelector('.swarm-navigation')?.getBoundingClientRect();
+ if (atlas && overlaps(atlas) && atlas.left < stage.left + this.width / 2) left = Math.max(left, atlas.right - stage.left + 20);
+ const evidence = document.querySelector('.swarm-inspector')?.getBoundingClientRect();
+ if (evidence && overlaps(evidence) && evidence.right > stage.left + this.width / 2) right = Math.min(right, evidence.left - stage.left - 20);
+ const toolbar = document.querySelector('.space-toolbar')?.getBoundingClientRect();
+ if (toolbar && overlaps(toolbar)) top = Math.max(top, toolbar.bottom - stage.top + 20);
+ const caption = document.querySelector('.space-caption')?.getBoundingClientRect();
+ if (caption && overlaps(caption)) bottom = Math.min(bottom, caption.top - stage.top - 20);
+ const width = Math.max(120, right - left), height = Math.max(120, bottom - top);
+ const x = left + width / 2, y = top + height / 2;
+ if (this.gl) this.camera.setViewOffset(this.width, this.height, this.width / 2 - x, this.height / 2 - y, this.width, this.height);
+ return { x, y, width, height };
+ }
+ setNodes(nodes: Node[], selected: string | null): void {
+ this.nodes = nodes; this.selected = selected;
+ this.viewport();
+ // The plan view has no animated shader; it only needs observation/input redraws.
+ this.active = !!this.gl && !this.motion.matches && nodes.some(node => node.activity === 'recent');
+ if (this.gl) {
+ const pos: number[] = [], colors: number[] = [], sizes: number[] = [], active: number[] = [], form: number[] = [], chosen: number[] = [];
+ const linkPos: number[] = [], linkColors: number[] = [];
+ const byKey = new Map(nodes.map(node => [node.key, node]));
+ // Static engraved source plates share the delegation line batch. They are
+ // decorative reference rings, not edges, authority or activity, and no
+ // cross-authority line is ever emitted.
+ for (const source of nodes.filter(node => !node.cell)) {
+ const color = new Color(sourceHue(source.source.id)), base = source.point.y;
+ const faint = source.source.status !== 'observed' ? .035 : .085;
+ for (const [ri, radius] of [30, 56, 84].entries()) {
+ const segments = 80, weight = faint * (ri === 1 ? 1.35 : 1);
+ for (let i = 0; i < segments; i++) for (const t of [i / segments, (i + 1) / segments]) {
+ const angle = t * Math.PI * 2;
+ linkPos.push(source.point.x + Math.cos(angle) * radius, base, source.point.z + Math.sin(angle) * radius);
+ linkColors.push(color.r * weight, color.g * weight, color.b * weight);
+ }
+ }
+ // Graduated ticks on the outer ring read as an instrument bezel.
+ for (let i = 0; i < 24; i++) {
+ const angle = i / 24 * Math.PI * 2, inner = i % 6 === 0 ? 78 : 81.5, weight = faint * 1.9;
+ linkPos.push(source.point.x + Math.cos(angle) * inner, base, source.point.z + Math.sin(angle) * inner);
+ linkColors.push(color.r * weight, color.g * weight, color.b * weight);
+ linkPos.push(source.point.x + Math.cos(angle) * 88, base, source.point.z + Math.sin(angle) * 88);
+ linkColors.push(color.r * weight * .25, color.g * weight * .25, color.b * weight * .25);
+ }
+ }
+ for (const node of nodes) {
+ pos.push(node.point.x, node.point.y, node.point.z);
+ const own = new Color(tint(node)), color = new Color(node.key === selected ? '#e6fff8' : tint(node));
+ colors.push(color.r, color.g, color.b);
+ const marker = markerOf(node);
+ // Evidenced and uncertain records are also drawn slightly larger, so
+ // the distinction survives a small point without relying on motion.
+ const emphasis = marker === 'recent' ? 1.3 : marker === 'uncertain' ? 1.16 : marker === 'quiet' ? .92 : 1;
+ sizes.push((!node.cell ? 21 : node.key === selected ? 17 : node.cell.role === 'coordinator' ? 13.5 : node.cell.role === 'verifier' ? 10 : 8.5) * (node.cell ? emphasis : 1));
+ active.push(!this.motion.matches && marker === 'recent' ? 1 : 0);
+ form.push(markerForm[marker]); chosen.push(node.key === selected ? 1 : 0);
+ const parent = node.parent ? byKey.get(node.parent) : undefined;
+ if (parent?.cell && node.cell?.parentId) {
+ // Lifted delegation ribbons fading parent to child, one line batch.
+ const from = new Color(tint(parent)), lit = node.key === selected || parent.key === selected;
+ for (let i = 0; i < 10; i++) for (const t of [i / 10, (i + 1) / 10]) {
+ const arc = Math.sin(t * Math.PI), intensity = (lit ? .72 : .19) * (.45 + .55 * arc);
+ linkPos.push(parent.point.x + (node.point.x - parent.point.x) * t, parent.point.y + (node.point.y - parent.point.y) * t + arc * 3.4, parent.point.z + (node.point.z - parent.point.z) * t);
+ linkColors.push((from.r + (own.r - from.r) * t) * intensity, (from.g + (own.g - from.g) * t) * intensity, (from.b + (own.b - from.b) * t) * intensity);
+ }
+ }
+ }
+ const geometry = new BufferGeometry();
+ geometry.setAttribute('position', new Float32BufferAttribute(pos, 3)); geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));
+ geometry.setAttribute('aSize', new Float32BufferAttribute(sizes, 1)); geometry.setAttribute('aActive', new Float32BufferAttribute(active, 1));
+ geometry.setAttribute('aForm', new Float32BufferAttribute(form, 1));
+ geometry.setAttribute('aSelected', new Float32BufferAttribute(chosen, 1));
+ if (this.points) { this.points.geometry.dispose(); this.points.geometry = geometry; }
+ else { this.points = new Points(geometry, new ShaderMaterial({ vertexShader, fragmentShader, vertexColors: true, transparent: true, depthWrite: false, uniforms: { uTime: { value: 0 } } })); this.scene.add(this.points); }
+ const links = new BufferGeometry(); links.setAttribute('position', new Float32BufferAttribute(linkPos, 3)); links.setAttribute('color', new Float32BufferAttribute(linkColors, 3));
+ if (this.lines) { this.lines.geometry.dispose(); this.lines.geometry = links; }
+ else { this.lines = new LineSegments(links, new LineBasicMaterial({ vertexColors: true, transparent: true, opacity: .92, depthWrite: false })); this.scene.add(this.lines); }
+ }
+ const priority = nodes.filter(node => node.key === selected).concat(nodes.filter(node => !node.cell || node.cell.role === 'coordinator'), nodes.filter(node => node.cell && node.activity === 'recent'), nodes.filter(node => node.cell && node.cell.role !== 'coordinator').sort((a, b) => a.cell!.depth - b.cell!.depth));
+ const labelled = [...new Map(priority.map(node => [node.key, node])).values()].slice(0, 24);
+ this.labels.replaceChildren(); this.labelElements = [];
+ for (const node of labelled) {
+ const span = document.createElement('span');
+ const marker = markerOf(node);
+ span.className = `swarm-label${!node.cell ? ' source-label' : ''}${node.key === selected ? ' selected' : ''}${marker === 'recent' ? ' active' : ''}`;
+ span.dataset.marker = marker;
+ span.textContent = node.label.length > 24 ? `${node.label.slice(0, 22)}…` : node.label;
+ span.title = `${node.source.factoryId} / ${node.label} · ${markerLegend[marker]}`;
+ span.dataset.key = node.key;
+ span.style.setProperty('--accent', sourceHue(node.source.id));
+ if (!node.cell) { const kicker = document.createElement('i'); kicker.textContent = node.source.status === 'observed' ? 'authority' : node.source.status; span.append(kicker); }
+ this.labels.append(span); this.labelElements.push(span);
+ }
+ // Measure once per observation, rather than reading layout on every frame.
+ this.labelSizes = new Map(this.labelElements.map(label => {
+ const rect = label.getBoundingClientRect();
+ return [label, { width: rect.width, height: rect.height }];
+ }));
+ this.request();
+ }
+ frame(point?: Point, sourceId?: string): void {
+ this.lastFit = { point, sourceId };
+ const viewport = this.viewport();
+ const nodes = sourceId ? this.nodes.filter(node => node.source.id === sourceId) : this.nodes;
+ const center = point ?? nodes.reduce((sum, node) => ({ x: sum.x + node.point.x / Math.max(1, nodes.length), y: sum.y + node.point.y / Math.max(1, nodes.length), z: sum.z + node.point.z / Math.max(1, nodes.length) }), { x: 0, y: 0, z: 0 });
+ const radius = point ? 35 : Math.max(sourceId ? 92 : 48, ...nodes.map(node => Math.hypot(node.point.x - center.x, node.point.z - center.z)));
+ if (this.gl) {
+ const distance = radius * Math.max((point ? 2.7 : sourceId ? 1.45 : 1.1) * this.height / viewport.height, 2.4 * this.height / viewport.width);
+ const target = new Vector3(center.x, center.y + (point ? 0 : 6), center.z);
+ const position = new Vector3(center.x + distance * .34, center.y + distance * (point ? .42 : .52), center.z + distance * .86);
+ if (this.motion.matches) { this.controls!.target.copy(target); this.camera.position.copy(position); this.controls!.update(); }
+ else this.flight = { start: performance.now(), fromPosition: this.camera.position.clone(), fromTarget: this.controls!.target.clone(), toPosition: position, toTarget: target };
+ } else { this.zoom = Math.min(viewport.width, viewport.height) / (radius * 2.8); this.pan = { x: viewport.x - this.width / 2 - center.x * this.zoom, y: viewport.y - this.height / 2 - center.z * this.zoom }; }
+ this.request();
+ }
+ request = (): void => { if (!this.disposed && !document.hidden && !this.frameId) this.frameId = requestAnimationFrame(this.draw); };
+ private project = (point: Point): { x: number; y: number; size: number } => {
+ if (!this.gl) return { x: this.width / 2 + point.x * this.zoom + this.pan.x, y: this.height / 2 + point.z * this.zoom + this.pan.y, size: 10 };
+ const vector = new Vector3(point.x, point.y, point.z).project(this.camera);
+ return { x: (vector.x + 1) * this.width / 2, y: (1 - vector.y) * this.height / 2, size: vector.z >= 1 || vector.z < -1 ? 0 : 12 };
+ };
+ private draw = (time: number): void => {
+ this.frameId = 0;
+ if (this.disposed || document.hidden) return;
+ if ((this.active || this.flight) && time - this.lastFrame < 1000 / 30) { this.request(); return; }
+ this.lastFrame = time; const start = performance.now(); this.projected.clear();
+ if (this.flight) {
+ const progress = Math.min(1, (time - this.flight.start) / 760), eased = 1 - (1 - progress) ** 3;
+ this.camera.position.lerpVectors(this.flight.fromPosition, this.flight.toPosition, eased); this.controls!.target.lerpVectors(this.flight.fromTarget, this.flight.toTarget, eased); this.controls!.update();
+ if (progress >= 1) this.flight = null;
+ }
+ for (const node of this.nodes) this.projected.set(node.key, this.project(node.point));
+ if (this.gl) { if (this.points) this.points.material.uniforms.uTime!.value = time / 1000; this.gl.render(this.scene, this.camera); }
+ else this.paint2d();
+ const occupied: Array<{ left: number; right: number; top: number; bottom: number }> = [];
+ for (const label of this.labelElements) {
+ const point = this.projected.get(label.dataset.key!); label.hidden = !point?.size || point.x < 0 || point.x > this.width || point.y < 0 || point.y > this.height;
+ if (point && point.y > this.height - 145) label.hidden = true;
+ if (point && !label.hidden) {
+ const size = this.labelSizes.get(label)!;
+ const rect = { left: point.x - size.width / 2 - 4, right: point.x + size.width / 2 + 4, top: point.y + 16, bottom: point.y + 20 + size.height };
+ if (occupied.some(other => rect.left < other.right && rect.right > other.left && rect.top < other.bottom && rect.bottom > other.top)) label.hidden = true;
+ else occupied.push(rect);
+ }
+ if (point) label.style.transform = `translate(${point.x}px,${point.y + 18}px) translateX(-50%)`;
+ }
+ this.submissions++; this.lastSubmissionMs = performance.now() - start;
+ this.onStats({ mode: this.mode, visible: this.nodes.length, labels: this.labelElements.length, draws: this.gl?.info.render.calls ?? 0, submissions: this.submissions, cpuMs: this.lastSubmissionMs });
+ if (this.active || this.flight) this.request();
+ };
+ /** 2D fallback: the same index, the same deterministic colours, plan view. */
+ private paint2d(): void {
+ const context = this.ctx!;
+ context.clearRect(0, 0, this.width, this.height);
+ for (const source of this.nodes.filter(node => !node.cell)) {
+ const point = this.projected.get(source.key)!, hue = sourceHue(source.source.id);
+ context.lineWidth = 1;
+ for (const radius of [30, 56, 84]) { context.strokeStyle = `${hue}${radius === 56 ? '22' : '16'}`; context.beginPath(); context.arc(point.x, point.y, radius * this.zoom, 0, Math.PI * 2); context.stroke(); }
+ context.strokeStyle = `${hue}2c`;
+ for (let i = 0; i < 24; i++) {
+ const angle = i / 24 * Math.PI * 2, inner = (i % 6 === 0 ? 78 : 81.5) * this.zoom, outer = 88 * this.zoom;
+ context.beginPath(); context.moveTo(point.x + Math.cos(angle) * inner, point.y + Math.sin(angle) * inner); context.lineTo(point.x + Math.cos(angle) * outer, point.y + Math.sin(angle) * outer); context.stroke();
+ }
+ }
+ for (const node of this.nodes) {
+ const parent = node.parent && this.projected.get(node.parent), point = this.projected.get(node.key)!;
+ if (!parent || !node.cell?.parentId) continue;
+ const lit = node.key === this.selected || node.parent === this.selected;
+ context.beginPath(); context.moveTo(parent.x, parent.y);
+ context.quadraticCurveTo((parent.x + point.x) / 2 + 7, (parent.y + point.y) / 2 - 13, point.x, point.y);
+ context.strokeStyle = lit ? `${tint(node)}c0` : `${tint(node)}30`; context.lineWidth = lit ? 1.4 : 1; context.stroke();
+ }
+ for (const node of this.nodes) {
+ const point = this.projected.get(node.key)!, hue = tint(node), marker = markerOf(node);
+ const scale = marker === 'recent' ? 1.3 : marker === 'uncertain' ? 1.16 : marker === 'quiet' ? .92 : 1;
+ const radius = (!node.cell ? 14 : node.key === this.selected ? 10 : node.cell.role === 'coordinator' ? 9 : node.cell.role === 'verifier' ? 6 : 5) * (node.cell ? scale : 1);
+ const bloom = marker === 'recent' ? 2.6 : marker === 'plate' ? 3.2 : 1.9;
+ const gradient = context.createRadialGradient(point.x, point.y, 0, point.x, point.y, radius * bloom);
+ gradient.addColorStop(0, `${hue}${marker === 'idle' || marker === 'quiet' ? '44' : '88'}`); gradient.addColorStop(.34, `${hue}33`); gradient.addColorStop(1, `${hue}00`);
+ context.fillStyle = gradient; context.beginPath(); context.arc(point.x, point.y, radius * bloom, 0, Math.PI * 2); context.fill();
+ context.setLineDash(marker === 'quiet' ? [2.4, 2.4] : []);
+ context.strokeStyle = marker === 'idle' ? `${hue}b0` : marker === 'quiet' ? `${hue}a0` : hue;
+ context.lineWidth = marker === 'plate' ? 1.4 : marker === 'recent' ? 1.3 : 1;
+ context.beginPath(); context.arc(point.x, point.y, radius * .78, 0, Math.PI * 2); context.stroke();
+ context.setLineDash([]);
+ if (marker === 'plate') {
+ // Bezel plus a hollow keystone: an authority, never evidenced activity.
+ context.strokeStyle = `${hue}66`; context.beginPath(); context.arc(point.x, point.y, radius * 1.5, 0, Math.PI * 2); context.stroke();
+ context.beginPath(); context.moveTo(point.x, point.y - radius * .32); context.lineTo(point.x + radius * .32, point.y); context.lineTo(point.x, point.y + radius * .32); context.lineTo(point.x - radius * .32, point.y); context.closePath(); context.stroke();
+ }
+ if (marker === 'recent') { context.fillStyle = hue; context.beginPath(); context.arc(point.x, point.y, radius * .34, 0, Math.PI * 2); context.fill(); }
+ if (marker === 'uncertain') { context.strokeStyle = hue; context.lineWidth = 1.2; context.beginPath(); context.moveTo(point.x - radius * .46, point.y); context.lineTo(point.x + radius * .46, point.y); context.stroke(); }
+ if (node.key === this.selected) {
+ context.strokeStyle = '#d2fff0'; context.lineWidth = 1.2;
+ context.beginPath(); context.arc(point.x, point.y, radius + 5, 0, Math.PI * 2); context.stroke();
+ context.beginPath(); context.moveTo(point.x - radius - 11, point.y); context.lineTo(point.x - radius - 3, point.y); context.moveTo(point.x + radius + 3, point.y); context.lineTo(point.x + radius + 11, point.y); context.stroke();
+ }
+ }
+ }
+ private wireInput(): () => void {
+ const down = (event: PointerEvent) => { this.flight = null; this.drag = { x: event.clientX, y: event.clientY, startX: event.clientX, startY: event.clientY, moved: false }; if (!this.gl) this.canvas.setPointerCapture(event.pointerId); };
+ const move = (event: PointerEvent) => {
+ if (!this.drag) return; if (Math.hypot(event.clientX - this.drag.startX, event.clientY - this.drag.startY) > 5) this.drag.moved = true;
+ if (!this.gl) { this.pan.x += event.clientX - this.drag.x; this.pan.y += event.clientY - this.drag.y; this.request(); }
+ this.drag.x = event.clientX; this.drag.y = event.clientY;
+ };
+ const up = (event: PointerEvent) => {
+ if (this.drag && !this.drag.moved) {
+ const rect = this.canvas.getBoundingClientRect(), x = event.clientX - rect.left, y = event.clientY - rect.top;
+ let best: Node | null = null, distance = 20;
+ for (const node of this.nodes) { const point = this.projected.get(node.key); if (!point?.size) continue; const next = Math.hypot(point.x - x, point.y - y); if (next < distance) { distance = next; best = node; } }
+ if (best) this.onPick(best);
+ }
+ this.drag = null;
+ };
+ const wheel = (event: WheelEvent) => { if (this.gl) return; event.preventDefault(); const multiplier = Math.exp(-event.deltaY * .001); this.zoom = Math.max(.01, Math.min(30, this.zoom * multiplier)); this.pan.x *= multiplier; this.pan.y *= multiplier; this.request(); };
+ const key = (event: KeyboardEvent) => { if (event.key === 'Home') { event.preventDefault(); this.frame(); } };
+ this.canvas.addEventListener('pointerdown', down); this.canvas.addEventListener('pointermove', move); window.addEventListener('pointerup', up); this.canvas.addEventListener('wheel', wheel, { passive: false }); this.canvas.addEventListener('keydown', key);
+ return () => { this.canvas.removeEventListener('pointerdown', down); this.canvas.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); this.canvas.removeEventListener('wheel', wheel); this.canvas.removeEventListener('keydown', key); };
+ }
+ dispose(): void {
+ this.disposed = true; cancelAnimationFrame(this.frameId); this.observer.disconnect(); this.disposeInput(); document.removeEventListener('visibilitychange', this.visibility);
+ this.motion.removeEventListener('change', this.motionChanged);
+ this.canvas.removeEventListener('webglcontextlost', this.contextLost); this.controls?.removeEventListener('change', this.request); this.controls?.dispose();
+ this.points?.geometry.dispose(); this.points?.material.dispose(); this.lines?.geometry.dispose(); this.lines?.material.dispose(); this.gl?.dispose(); this.labels.replaceChildren(); this.labelSizes.clear();
+ }
+}
diff --git a/src/swarm/swarm.css b/src/swarm/swarm.css
new file mode 100644
index 0000000..32f60ee
--- /dev/null
+++ b/src/swarm/swarm.css
@@ -0,0 +1,72 @@
+/* THE OBSERVATORY — tokens, reset and controls.
+ * One ink, one hairline, three type voices: a serif for figures and names,
+ * a mono for labels and identities, a sans for recorded prose. Colour is
+ * evidence: the source hue is deterministic, amber is uncertainty, grey is a
+ * retained record. Nothing here animates on its own. */
+:root{
+ --void:#04060b; --ink:#070a11; --deep:#0a0f18;
+ --plate:rgba(12,17,27,.78); --plate-solid:#0b1019; --plate-lit:rgba(127,209,222,.1);
+ --rule:rgba(150,180,210,.14); --rule-strong:rgba(160,196,226,.3);
+ --text:#e9eef7; --muted:#a6b3c7; --quiet:#8995a7; --faint:#7c899d;
+ --signal:#7fd1de; --accent:#7fd1de; --warn:#e0a85c; --hist:#b4bcce;
+ --serif:ui-serif,'Iowan Old Style','Palatino Linotype',Palatino,Georgia,serif;
+ --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
+ --sans:Inter,ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
+ --kicker:9px/1.1 var(--mono);
+ --ease:cubic-bezier(.2,.7,.3,1);
+ font-family:var(--sans); color:var(--text); background:var(--void); color-scheme:dark; font-synthesis:none;
+ -webkit-font-smoothing:antialiased;
+}
+*{box-sizing:border-box}
+body{margin:0;background:var(--void);color:var(--text);overflow-x:hidden}
+#swarm-app{position:relative;isolation:isolate}
+button,input,select{font:inherit;font-family:inherit}
+button{cursor:pointer;padding:7px 10px;border-radius:3px;background:transparent;border:1px solid var(--rule);color:var(--muted);
+ transition:color .16s var(--ease),border-color .16s var(--ease),background .16s var(--ease),transform .16s var(--ease)}
+button:hover{background:rgba(127,209,222,.07);border-color:var(--rule-strong);color:var(--text)}
+button:active{transform:translateY(.5px)}
+button[aria-pressed=true]{color:var(--text);border-color:var(--accent);background:rgba(127,209,222,.1)}
+button:disabled{opacity:.35;cursor:default}
+select,input{border-radius:3px;background:var(--plate-solid);border:1px solid var(--rule);color:var(--text)}
+button:focus-visible,input:focus-visible,select:focus-visible,canvas:focus-visible,summary:focus-visible,a:focus-visible{outline:1px solid var(--accent);outline-offset:3px;border-radius:2px}
+a{color:#a9cfdb;text-decoration:none}
+kbd{font:9px var(--mono);border:1px solid var(--rule);border-radius:2px;padding:1px 3px;color:var(--faint);margin-left:5px}
+summary{cursor:pointer}
+::selection{background:rgba(127,209,222,.28)}
+/* Hairline scrollbars so a scrolling plate still reads as one surface. */
+*{scrollbar-width:thin;scrollbar-color:rgba(160,196,226,.22) transparent}
+*::-webkit-scrollbar{width:8px;height:8px}
+*::-webkit-scrollbar-thumb{background:rgba(160,196,226,.2);border-radius:8px}
+*::-webkit-scrollbar-track{background:transparent}
+
+/* ── The O ──────────────────────────────────────────────────────────────────
+ * The mark is a mouth: the Observatory only ever speaks about records. The
+ * lips part on hover or keyboard focus — a deliberate flourish on a link, not
+ * a report of execution — and hold still under reduced motion. */
+.o-mark{display:block;color:var(--signal);overflow:visible}
+.o-mark .o-lip{transition:transform .5s var(--ease);transform-origin:32px 32px}
+.o-mark .o-core,.o-mark .o-say,.o-mark .o-bezel{transition:opacity .4s var(--ease)}
+a:hover .o-mark .o-lip-upper,a:focus-visible .o-mark .o-lip-upper{transform:translateY(-2.2px)}
+a:hover .o-mark .o-lip-lower,a:focus-visible .o-mark .o-lip-lower{transform:translateY(2.2px)}
+a:hover .o-mark .o-say,a:focus-visible .o-mark .o-say{opacity:.8}
+.o-mark[data-speaking=true]{filter:drop-shadow(0 0 10px rgba(127,209,222,.35))}
+
+/* ── Badges ─────────────────────────────────────────────────────────────── */
+.chip{display:inline-flex;align-items:center;gap:6px;font:9px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;
+ color:var(--muted);border:1px solid var(--rule);border-radius:999px;padding:5px 9px;white-space:nowrap;cursor:help}
+.chip-live{color:#bfeef5;border-color:rgba(127,209,222,.46)}
+.chip-live::before{content:'';width:5px;height:5px;border-radius:50%;background:var(--signal);box-shadow:0 0 0 3px rgba(127,209,222,.14)}
+.chip-warn{color:#e8c79a;border-color:rgba(224,168,92,.4);background:rgba(224,168,92,.07)}
+.chip-hist{color:var(--hist);border-style:dashed}
+.chip-quiet{color:var(--quiet)}
+.chip+.chip{margin-left:0}
+
+/* ── Evidence drawer: the careful wording, folded out of the reading flow ── */
+.evidence-drawer{border-top:1px solid var(--rule);margin-top:14px;padding-top:10px}
+.evidence-drawer>summary{font:9px var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--faint);list-style:none;display:flex;align-items:center;gap:8px}
+.evidence-drawer>summary::-webkit-details-marker{display:none}
+.evidence-drawer>summary::before{content:'+';font:10px var(--mono);color:var(--quiet);border:1px solid var(--rule);border-radius:50%;width:14px;height:14px;display:grid;place-items:center}
+.evidence-drawer[open]>summary::before{content:'–'}
+.evidence-drawer>summary:hover{color:var(--muted)}
+.evidence-drawer p{font:11px/1.75 var(--sans);color:var(--quiet);margin:10px 0 0;max-width:72ch}
+@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important;scroll-behavior:auto!important}}
diff --git a/src/swarm/transport.ts b/src/swarm/transport.ts
new file mode 100644
index 0000000..b984bbb
--- /dev/null
+++ b/src/swarm/transport.ts
@@ -0,0 +1,15 @@
+/** Bound streamed bytes before JSON decoding, including multibyte UTF-8. */
+export async function readSwarmJson(response: Response, maxBytes = 32 * 1024 * 1024): Promise {
+ if (!response.body) throw new Error('Swarm observation has no body');
+ const reader = response.body.getReader(), decoder = new TextDecoder('utf-8', { fatal: true });
+ const chunks: string[] = []; let bytes = 0;
+ try {
+ while (true) {
+ const { done, value } = await reader.read(); if (done) break;
+ bytes += value.byteLength;
+ if (bytes > maxBytes) throw new Error('Swarm observation exceeds its bound');
+ chunks.push(decoder.decode(value, { stream: true }));
+ }
+ return JSON.parse(chunks.join('') + decoder.decode());
+ } finally { await reader.cancel().catch(() => undefined); }
+}
diff --git a/swarm.html b/swarm.html
new file mode 100644
index 0000000..890e709
--- /dev/null
+++ b/swarm.html
@@ -0,0 +1,58 @@
+
+
+O · Observatory
+
+
+
+ O Observatory
+ Atlas
+ Find /
+ 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 execution records, not host health worker 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'),
},
},
},