# Build notes (start: @CRM sandbox, 14 Oct 2026, Linux, Node 22)
- `npm i --ignore-scripts` leaves better-sqlite3 unbuilt: Ruflo falls back to sql.js and WARNS "persistence is not guaranteed". Run `npm ci` WITH scripts (or `npm rebuild better-sqlite3`) before any memory POC.
- Ruflo auto-starts a background daemon on some commands ("Started Ruflo background daemon ... stop: ruflo daemon stop"). No switch to disable it was found; always run `npx ruflo daemon stop` in a cleanup step (CI and local).
- `ruflo memory export` returned `count: 0` after a successful `memory store` (export reads a different store than store writes to: .claude/memory.db vs .swarm/memory.db). `memory import` failed with `policy-anchor-log-missing` on a fresh project. WORKAROUND THAT WORKED: copy the DB files (`.swarm/memory.db`, `.swarm/agentdb-memory.db`, `.claude/memory.db`, `ruvector.db`) into a fresh folder: `memory retrieve` / `memory list` return the entry. POC2 therefore vaults the DB files (binary) plus a readable `memory list` dump.
- `ruflo policy init|rule|budget` refuse without an interactive TTY ("policy administration requires an interactive local terminal"). Do not work around this; POC1 uses the library engine (`AgenticPolicyEngine` from `@claude-flow/security/policy/index`), an in-memory engine of our own, which is the documented library surface.
- Ruflo's default policy mode on a fresh project is `legacy`: `policy evaluate` of `email.send` -> `allowed`, `legacy-default-allow` (evidence/ruflo-policy-legacy.json).

# Build notes (continued: Claude Code session in SGit-AI__Website__Ruv, 14-15 Oct 2026, Node 22, Python 3.13, sgit-ai 0.21.0)
- The pack had no `package-lock.json`, so `npm ci` refused to run. `npm install` with the pinned versions generated one; it is committed so CI's `npm ci` works.
- sgit-ai 0.21.0 flags matched `docs/01-sgit-cheatsheet.md` (checked with `--help` for create, clone, write, publish, history log, branch, vault serve). Global `--token` goes before the subcommand; `create`/`clone`/`push` also accept it.
- `sgit clone` of a vault with zero commits fails ("Nothing to clone: this vault has no branch index and no named ref"). Seed every new vault with a README commit + push before any script clones it (done for both vaults).
- sgit's default named branch is called `current`, not `main`. `poc2 run.sh` now switches to `${MAIN_BRANCH:-current}` before merging the per-role branch (verified with `sgit branch list`).
- POC2 roundtrip ran locally (two writers, fresh clone, same `ruflo memory list` answer): `evidence/poc2-run.txt`, `evidence/poc2-history.json`. CI jobs are wired but need the three secrets.
- `sgit publish --visibility public` writes a plaintext surface (cover.json, index.html loader, manifest.json, the read key file) to `.sg_vault/publish/`; the 81 store objects are referenced, not copied, so a static host serves the loader only. The read-only web UI link (`https://dev.vault.sgraph.ai/en-gb/#<read key>:<vault id>`) opens the contents; verified in headless Chromium. The surface is copied to `site/vault/`.
- MetaHarness 0.4.17 scaffold (`vertical:agentics`, host `claude-code`) emitted 18 files: `.claude/settings.json` permissions (allow 3 / deny 2) + 2 MCP servers, CLAUDE.md prose rules, 4 agents, 2 skills, a signed manifest. It did NOT emit the governance policy document, deny-by-default statement, timeouts, call limits or audit flag its README describes. `metaharness score . --json` worked (harnessFit 63, toolSafety 95, est. $0.048/run).
- Infographics: OpenRouter image-output chat models, direct API (`site/build/gen_infographics.py`). `google/gemini-nano-banana-2.1` was rate-limited upstream (HTTP 429) every time; `google/gemini-3-pro-image` (labelled diagrams, ~$0.14, ~20 s) and `google/gemini-2.5-flash-image` (concept slides, ~$0.04, ~7 s) worked. 12 images, $1.36 total (`site/build/infographics-log.json`). The dev.tools.sgraph.ai infographic-gen page (v0.1.37) hit the same 429 through the browser API.
- Mermaid 11.12.0 from cdnjs (11.4.1 is not on cdnjs). A site-wide "copy" button injected into every `<pre>` was corrupting the diagram sources before Mermaid parsed them (every diagram showed "Syntax error in text"); `pre.mermaid` is now excluded. Timeline periods cannot contain `:` and sequence participant aliases cannot contain quotes.
- Ruflo's `policy init|rule` still refuse without a TTY (not bypassed); POC6 is a runbook on the site.

# Review fixes and the talk section (Claude Code session, 15 Oct 2026, Node 22, headless Chromium via playwright-core 1.56.1)
- An external review (15 Oct) was checked against the data before acting: `data/report.json` has 6 of 11 actions stopped by the ABP (4 denied, 2 approval_required), 5 allowed. The home page said 5 stopped and use-cases said 3 denied; both fixed, and the demo script now names the researcher's credential read.
- Horizontal overflow reproduced at 390 px (index 359, pocs 635, ruv-stack 611, architecture 180, app 198) and at 1366 px (pocs 399), exactly as reported. After the fixes (wrapping header on phones, `pre`/`code` wrap, `min-width:0` on grid children, tables inside scroll wrappers): 0 on every page at both widths.
- pocs.html and replicate.html rendered extra `h1`s from included markdown; included headings are now demoted one level (1 `h1` per page).
- Infographics: the pages now load WebP (quality 90) instead of PNG: 11.0 MB -> 0.7 MB for the 12 images. PNG originals kept; `gen_infographics.py` writes both.
- Talk player (`site/talk/`): tested in headless Chromium: all 14 beats of the 10-minute timeline render at their seconds; the ABP and POC1 tables are read from `data/abp.json` and `data/report.json` (11 rows, 4 denied, 2 approval); the live-app beat typed its question and got the `send.message.world: no` answer; an audience window followed the presenter's clock and keys; rehearsal saved a file; the offline file opened from `file://` with images inlined and the app beat falling back to the recorded screenshot; no page errors. Bug found and fixed in testing: two players with different timelines in one browser shared a sync channel.

# Studio run 1 (POC7: a Ruflo hive-mind with sgit vaults as its bus and its evidence; Claude Code session, 15 Oct 2026)
- `ruflo hive-mind` 3.56.10 works headless: init (hierarchical-mesh, byzantine), spawn one specialist per role (8), task (7), broadcast, `memory -a set|get|list|delete` (not `store`: the CLI rejects it), status. Trace: `evidence/poc7-trace.jsonl`.
- Ruflo anchors each project's policy ledger outside the project, in `~/.config/ruflo/policy-trust/<hash>/ledger-anchor-head.json`. Deleting the work folder and re-running at the same path made every hive call fail with `policy-anchor-log-missing`. Not bypassed (rule 4): `hive.sh` now uses a fresh `work-<epoch>` folder per run.
- Call times went from about 0.4-0.5 s (idle machine) to 0.5-1.3 s while seven builder subagents ran in parallel (load average 14).
- The seven versions were built by Claude Code subagents of this session (one per task), not by `hive-mind work`/`spawn --claude` (those launch Claude Code with permission prompts skipped by default). Their token counts and wall clock are in `evidence/poc7-builders.json`.
- The SG_SEND access token present in this session's environment was not used (the session's permission policy refused it), so no vault was created or pushed; `lane.sh` and step 7 of the brief are untested against a server.
- `hive-mind memory -a set` printed "[OK] Set ... in shared memory" but `memory -a get` on the next CLI call said "Key not found"; `.claude-flow/hive-mind/state.json` held only `broadcasts` under `sharedMemory`. Broadcasts persist, so `close.sh` records each delivery as a broadcast and reads the seven back from the state file. The state copy in `evidence/poc7-hive-state.json` has the local `hiveToken` removed.
- Final run (idle machine): 31 Ruflo calls, none failed, 15.3 s total, 392-572 ms each. Builders: 629,857 tokens for seven versions, 4 min 44 s to 9 min 20 s each, in parallel. MP4s recorded from the live pages with Playwright (1280x720, SwiftShader WebGL) and ffmpeg (H.264).

# iPad fixes for the talk (15 Oct 2026)
- The player used a regex lookbehind (`(?<=…)`); Safari before 16.4 rejects the whole script over it, so on an older iPad the player would not run at all. Replaced.
- Touch: on-screen buttons (previous, play/pause, next, notes, fullscreen; 44 px targets), swipe left/right on the slide between beats (pointer events), tap the intro to start; slides scroll when they do not fit; in portrait the speaker notes sit under the slide. Fullscreen falls back to `webkitRequestFullscreen`.
- talk/index.html: "Read the script", one card per beat in a native horizontal scroll with snap points (a finger swipe needs no script), and "The evening" from the organisers' welcome deck (agenda, the 7:00 pm slot, who is in the room as aggregate counts). The deck itself is not published on the site: it carries other people's photos and LinkedIn codes.
- Tested in Chromium with iPad emulation (touch, 1180x820 and 820x1180, real touch events via CDP): swipes and buttons move between beats, play/pause works, the reader swipes, no page errors, no sideways scroll. Not tested on real Safari/WebKit (not installed here).

# Themes and the slide frame (v0.1.7, 15 Oct 2026)
- The five themes are copied from https://newsroom.sgit.ai/assets/themes/themes.css (the token blocks, unchanged) into `site/assets/themes.css`; `site/assets/theme.js` applies the stored choice before paint (key `ruv.theme`; an old `theme=dark|light` maps to Night/Newsprint; `?theme=` overrides for one page). site.css now has no colour literals: every colour is a token.
- The player and the live app read the same tokens; the player passes its theme to the embedded app (`?theme=` plus live updates), so slide 7 no longer turns white in a dark theme. Terminal blocks keep their own dark colours in every theme (the newsroom's one exception).
- Slide frame: brand strip and title at the same position on all 14 slides (measured: title at x=55, y=60 in both Night and Newsprint at 1366x768, review mode), no slide without a title. Tested in headless Chromium; the five themes switch on the site and the player follows.

- CORRECTION 15 Oct 2026 (run r4-hero editor): the MetaHarness note above says its README "describes" a governance policy document; the pinned metaharness 0.4.17 README has no "governance". Do not repeat it; its README describes permissions and a signed witness manifest (`harness sign`).

- 15 Oct 2026 (run r6-talk, worker ruvector): RuVector README screenshot for the talk slide `site/talk/slides/ruvector/` taken from the real page, github.com/ruvnet/ruvector/blob/main/README.md (HTTP 200; the file page showed last commit d31f296), with headless Chromium (playwright-core) through the session's HTTPS proxy, with TLS verification left on. Clip: the "Security and governance" section, 700 px viewport, 2x → `site/assets/img/talk/ruvector-readme-security.webp`. Lines quoted on the slide, exactly as in the README (also checked against raw.githubusercontent.com/ruvnet/ruvector/main/README.md, lines 575, 577, 472-473): "RVF witnesses and hash linked logs are tamper evident. They do not encrypt memory content or prevent an authorized process from reading it." / "A delete from the live store does not automatically remove copies in snapshots, branches, replicas, exports, or hosted memory. Define retention and erasure across every copy." / capability map: "Full snapshots", "Copy on write branches" ("Isolated memory experiments without full copies"). sgit figures from site/data/vaults.json (4 runs with delete_check: 0 of 160 probed fragments found on the server; 0 store paths left and clone after delete failed, 4 of 4; r5-market: 8 commits, fresh clone of 28 files identical).

# Sealed, published run vaults (run r14-examples, worker mechanic, 16 Oct 2026, sgit-ai 0.21.0)
- New runner ending `ruvsgit.py seal RUN` (after `close`; `delete` stays the ending for private runs). Proven with run `example-01-hello` (vault l2yxakym, published at `site/vaults/example-01-hello/`); its numbers are in `evidence/ruv-sgit/example-01-hello.json` (`published`, `seal_check`).
- Read key: `sgit init --existing` prints only the vault key (no read key). `sgit vault derive-keys` prints the read key but takes the vault key on argv (no `-`/stdin) and also prints the write key, so the runner does not use it. `sgit publish --visibility public --yes` writes an empty file named `sgit_public_read_<64 hex>` into `.sg_vault/publish/`; the runner takes the read key from that file name and keeps `sgit_public_read_<hex>:<vault id>` in the run folder (`read.key`, 0600).
- Layout: the publish surface (`index.html`, `manifest.json`, `cover.json`, the read-key file) plus every manifest object at `api/vault/read/<vault id>/bare/{data,refs,indexes,keys}/…` (the manifest's `layout: api-path`; the path the sgit.ai static-hosting doc and `sgit vault serve`'s virtual api/ route use). Tested: `sgit clone <read-key>:<id> --base-url http://127.0.0.1:<port>/vaults/<run>/` from a plain `python3 -m http.server` (it first POSTs `api/vault/batch/<id>`, gets 501, and falls back to GETs) and from `sgit vault serve site/vaults/<run>`; files identical. A flat layout (`bare/` beside index.html) also clones (sgit tries the api path, gets 404, then `bare/…`), but the api-path one needs no fallback.
- Writes after the seal: a clone made with the read key refuses `sgit commit` ("This vault was cloned read-only. To write, re-clone with the full vault key."); the vault key file and the working copy (whose `.sg_vault/local/` held the key and the server token) are deleted; `sgit vault delete-on-remote --yes --json` removed it from the local server (it leaves a `deleted.json` tombstone), and a clone with the key from the server failed afterwards.
- Differs from the conductor's note: the `index.html` that sgit 0.21.0 emits is a placeholder loader (no script; "implementation owned by the Web team"), so a browser opening `vaults/<run>/` sees an explanation page, not the files. Its documented contract is the viewer URL `…/vaults/<run>/#sgit_public_read_<hex>:<id>` (key in the #fragment, never sent to the host; else the read-key file beside the page), which the runner records as `viewer_url` for when the loader ships. The official SG/Vault UI (`dev.vault.sgraph.ai/#<read key>`, used by sgit.ai's demo pages via `vault-ui-embed.js`) reads from the SG/Send API, not from our static host, so it cannot open these vaults. Today the working way in is `sgit clone`.
- The placeholder loader's comment contains the literal text `sgit_private_vault_*` (it tells the loader to refuse vault keys), so a plain grep for `sgit_private_vault_` in `site/vaults` matches `index.html`; it is not a key. The validator looks for the key shape (`sgit_private_vault_` followed by key characters) in every file under `site/vaults`, refuses any `local/`, `.sg_vault`, `vault_key`, `token`, `.pem`/`.key` file there, checks every manifest object against its sha256, refuses files the manifest does not list, and now also refuses `sgit_private_read_` keys anywhere in the site.

# Sealed vaults on the sgraph vault server (run r15-publish, worker publisher, 17 Oct 2026, sgit-ai 0.21.0)
- Dinis, 17 Oct: push the example vaults to the sgraph vault server (dev.send.sgraph.ai, opened in dev.vault.sgraph.ai), keep only the reference in the repository. `ruvsgit.py seal RUN` now does, in order: `sgit remote add --default sgraph https://dev.send.sgraph.ai` → `sgit push --remote sgraph --token <publish token>` → `sgit publish --visibility public --yes .` (local only: it names a file after the READ key, as before) → `sgit clone --base-url https://dev.send.sgraph.ai - <tmp>` with the read key on stdin, compared with the run (plaintext files AND the encrypted store: every file under `.sg_vault/bare/{data,refs,indexes,keys}` byte-identical, so every named branch ref and the whole history are on the server) → `sgit commit` and `sgit push` from that clone (both refused) → the vault UI opened in headless Chromium (`poc/ruv-sgit/vault-ui-check.mjs`, screenshot `evidence/ruv-sgit/<run>-vault-ui.png`) → `sgit vault delete-on-remote --base-url http://127.0.0.1:<port> --yes --json` (the local copy) and a clone with the vault key from the local server afterwards (fails) → the vault key file and the working copy are deleted. The ledger records only `published: {server, vault_id, read_key, viewer_url, clone, sealed_at, files, bytes}` plus `seal_check`.
- What sgit did: the first `push --remote sgraph` printed "Re-syncing vault structure to server" and uploaded the whole bare store (not only the current branch): a fresh read-only clone had the same 4 refs (2 named branches `current` and `w-x`, 2 clone branches), the branch index and every object (tested first on a throwaway vault, e096koak, then deleted from both servers with `vault delete-on-remote --remote sgraph` / `--base-url`). `sgit vault move --to` exists but rotates the key (new identity); not needed here.
- Differs from the note we were given: (1) the read key is not printed by any command that keeps the write key off the screen except `sgit publish --visibility public` (file name); `sgit vault derive-keys` takes the vault key on argv and prints the write key too, so it is still not used. (2) After `remote add --default`, a bare `delete-on-remote` would hit the sgraph server, so the local delete pins `--base-url`. (3) `sgit branch list` in a read-only clone fails (`No such file or directory: ./.sg_vault/local/vault_key`); the branch check is done on the bare store instead. (4) The vault UI shows a `.vault-settings.json` ("Untitled Vault", created at view time) that is not in the vault: the clone has exactly the delivered files; the UI makes it up in the browser. (5) The viewer URL works as `https://dev.vault.sgraph.ai/en-gb/#sgit_public_read_<hex>:<id>` (the form in site/data/vault-public.json; sgit.ai's pages use `/#…%3A…`); the embed (sgit.ai's `vault-ui-embed.js` protocol) wants the credential as `sgit_rk1_<hex>:<id>`, posted after `vault-embed-ready`, and worked from an http://127.0.0.1 parent.
- The access token for the server is kept like the other secrets (`$RUVSGIT_HOME/publish.token`, 0600, or `RUVSGIT_PUBLISH_TOKEN`), never in the repository; seal refuses to run without it.
- Runs (RUVSGIT_HOME=~/.ruv-sgit-publisher, a local server on 8092, default ledger and evidence paths): `ruvsgit.py open example-01-hello-v2 poc/ruv-sgit/runs/example-01-hello-v2.spec.json; deliver example-01-hello-v2 hello writer <hello/>; close …; seal example-01-hello-v2` → vault q4hynr3y: 3 commits, 3 files, 2,708 B; fresh clone identical (21 store objects, 2 named branches), writes refused, vault UI opened. `open example-07-meta poc/ruv-sgit/runs/example-07-meta.spec.json; deliver … mechanic builder; deliver … writer writer; close; seal` → vault nxmykn2w: 4 commits, 10 files, 115,913 B; 36 store objects, 3 named branches. The first seal of example-07-meta stopped at the UI check (a navigation inside the UI destroyed the page context; the script now retries) after the push; the key was kept, and the second `seal` found "Nothing to push", passed every check and sealed it (its `push_ms` is that no-op push).
- example-07-meta's worker files are r14-examples' deliveries, recovered with a read-only clone of r14's static copy (`sgit clone <read key> --base-url http://127.0.0.1:<port>/vaults/r14-examples/`) before `site/vaults/` was removed; each file is byte-identical to git 1f56515 (ruvsgit.py, BRIEF.md, validate.py, ledger.schema.md, the example-01 spec and evidence; getting-started.md is also identical to HEAD). Checked again from the sgraph server after the seal: `sgit clone <read key>` (no --base-url: the default is dev.send.sgraph.ai) → the same files.
- example-01-hello and r14-examples keep their ledger entries (numbers, read key, vault id) with `superseded_by` and `static_copy_removed`; their `published.path`, `viewer_url` and `clone` (all pointing at site/vaults/) are gone. Their objects stay in git history up to d9b9ab3.
- Left on the sgraph server: a test run `pubtest-1` (vault 1uu36fx7: the example-01 spec and a one-line test file), sealed while testing the new seal; its vault key is destroyed so it cannot be deleted, and its read key was never published (it is only in ~/.ruv-sgit-publisher).

## 17 Oct 2026: a web search for article No. 11 (run r20-voice-article)
Dinis asked for a quick web search on how the browser's built-in speech synthesis works, how common it is and good examples. CLAUDE.md limits send.endpoint.allowed to package registries, GitHub, the sgit server and rUv's repos; this search goes beyond that list because Dinis asked for it, for this article only. Findings and their source URLs are in evidence/r20-voice-research.md.

## 21 Oct 2026: a web search for the founding providers' bios (run r21-bios)
Dinis chose, on 21 Oct, to draft his and Mondweep Chakravorty's bios from their public pages. The search goes beyond CLAUDE.md's send.endpoint.allowed list because Dinis asked for it, for this purpose only. Only public, professional facts are kept, each with its source URL, in evidence/r21-provider-bios.md; both approve their bio before it is final.
