igneum/relay
igneum-labs 7eed16a29a Pre-public scrub, the text pass (7 October 2026, 19:5x UK): no founder name, personal login, earlier business or personal address in any tracked text file, and a gate check that keeps it so
The sweep (main's item 1): 199 tracked text files, 783 lines. The founder's full name, first name and possessive become "the founder" (sentence starts capitalised); the lowercase operating-system user name in WSL paths and commands becomes <user>; the second owner login becomes "the second owner login"; the three earlier businesses and the two other brands become "the other business", "the earlier entity", "the earlier business" and "another brand"; the Chrome profile rule names the igneum.network profile, not the profile's label. The standing commit login igneum-labs is not a founder term here: the fresh-repository step renames it in the history (docs/plans/history-rewrite.md, tools/repo/fresh-repo.sh).

The patterns never appear in plain text in the tree (a plaintext list would be the hit): tools/ci/founder-strings.b64 (perl regex, tab, a sample per row) is read by tools/ci/founder-strings-check.sh (every tracked text file, perl, known-failed first: the self-test plants each row's sample in a fixture and the hit must name the file), by tools/community/discord-hooks.mjs (the guard's founder and business rows; the test takes its fixtures from the samples) and by tools/repo/fresh-repo.sh (the business names of the rewrite rules). site/forbidden-strings.txt carries the same patterns as b64: lines, decoded case-insensitive by site/scrub.mjs and tools/ci/launch-gates-check.mjs (whose fixture now plants an encoded made-up name). The check runs in the gate's tree checks on every merge.

Not in this commit, by main's word: the 105 commit messages and 40 personal-identity commits that need the history rewrite (listed, not run), and the secrets found by gitleaks over the history (reported with owners).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 18:39:50 +00:00
..
api Relay: the X23 tree on master (the signed and tagged run, lib/handler.mjs, guard, the per-machine secret), the start-app kind, the v3 agent as the per-user logon task IgneumRelayService with install-agent.ps1 and the hidden loop, the per-install hostname on registration, the signing tools/relay.mjs so no lane posts an unsigned run (the live relay refuses one since the 7 October 2026 deploy), tools/console.mjs and build-job.mjs on the header tier (X24), the playbooks on RELAY_DL_BASE (X26) and wsl-setup's sudo for apt-get and dpkg only (X28), the new playbooks (start-app, agent-install, pc2-crash-collect, boot-check, freeze-check), and the deploy record with the rollback line (docs/plans/relay-deploy-2026-10-07.md: production igneum-relay-iabqarnby, previous bgy767z40) 2026-10-07 14:47:59 +00:00
clients Pre-public scrub, the text pass (7 October 2026, 19:5x UK): no founder name, personal login, earlier business or personal address in any tracked text file, and a gate check that keeps it so 2026-10-07 18:39:50 +00:00
lib Relay: the X23 tree on master (the signed and tagged run, lib/handler.mjs, guard, the per-machine secret), the start-app kind, the v3 agent as the per-user logon task IgneumRelayService with install-agent.ps1 and the hidden loop, the per-install hostname on registration, the signing tools/relay.mjs so no lane posts an unsigned run (the live relay refuses one since the 7 October 2026 deploy), tools/console.mjs and build-job.mjs on the header tier (X24), the playbooks on RELAY_DL_BASE (X26) and wsl-setup's sudo for apt-get and dpkg only (X28), the new playbooks (start-app, agent-install, pc2-crash-collect, boot-check, freeze-check), and the deploy record with the rollback line (docs/plans/relay-deploy-2026-10-07.md: production igneum-relay-iabqarnby, previous bgy767z40) 2026-10-07 14:47:59 +00:00
playbooks Pre-public scrub, the text pass (7 October 2026, 19:5x UK): no founder name, personal login, earlier business or personal address in any tracked text file, and a gate check that keeps it so 2026-10-07 18:39:50 +00:00
test Relay: the X23 tree on master (the signed and tagged run, lib/handler.mjs, guard, the per-machine secret), the start-app kind, the v3 agent as the per-user logon task IgneumRelayService with install-agent.ps1 and the hidden loop, the per-install hostname on registration, the signing tools/relay.mjs so no lane posts an unsigned run (the live relay refuses one since the 7 October 2026 deploy), tools/console.mjs and build-job.mjs on the header tier (X24), the playbooks on RELAY_DL_BASE (X26) and wsl-setup's sudo for apt-get and dpkg only (X28), the new playbooks (start-app, agent-install, pc2-crash-collect, boot-check, freeze-check), and the deploy record with the rollback line (docs/plans/relay-deploy-2026-10-07.md: production igneum-relay-iabqarnby, previous bgy767z40) 2026-10-07 14:47:59 +00:00
.gitignore Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network) 2026-10-04 10:04:30 +00:00
apple-touch-icon.png Brand: one master mark (black square, no circle) for every icon, favicon and profile picture 2026-10-04 11:39:55 +00:00
favicon-32.png Brand: one master mark (black square, no circle) for every icon, favicon and profile picture 2026-10-04 11:39:55 +00:00
favicon.ico Brand: one master mark (black square, no circle) for every icon, favicon and profile picture 2026-10-04 11:39:55 +00:00
index.html Brand: one master mark (black square, no circle) for every icon, favicon and profile picture 2026-10-04 11:39:55 +00:00
package-lock.json Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network) 2026-10-04 10:04:30 +00:00
package.json Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network) 2026-10-04 10:04:30 +00:00
README.md Pre-public scrub, the text pass (7 October 2026, 19:5x UK): no founder name, personal login, earlier business or personal address in any tracked text file, and a gate check that keeps it so 2026-10-07 18:39:50 +00:00
robots.txt Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network) 2026-10-04 10:04:30 +00:00
ui.html Console: the job-channel ping (MF-11, 7 October 2026). Every 0.3.21 app names itself on its wake long-poll (machine, version, last job); /wake records one row per machine in relay_wake_seen before it holds; the Machines tab reads "job channel polled N ago, last job X" and, after 15 minutes without a poll, "job channel silent since <time>, last job X" in red, whatever the uploads say (PC 2 lost power at 10:46Z and nothing said so until a person read the intake). Tests in relay/test/wake.test.mjs. 2026-10-07 14:10:59 +00:00
vercel.json relay: /wake long-poll for the apps' remote jobs (public GET held 45 s, authenticated POST of the stamp) 2026-10-05 08:22:02 +00:00

Igneum relay and console

Text, files and tasks between the founder's devices without Gmail: the Mac, PC1, PC2 and the phone post to one feed and read from it. Vercel project igneum-relay, served at https://relay.igneum.network. Built 4 October 2026. Since the evening of 4 October 2026 the same private page is the Igneum console (ui.html): seven tabs, phone first, refreshed every 15 s. The relay feed and drop box are its last tab.

Scope since 4 October 2026 (afternoon): the relay stays for the Mac and for humans (notes, files, tasks for a person or a Claude session on a PC). Commands and files for the PCs themselves go over the line to the Igneum Miner app instead: signed jobs published next to the update manifest (packaging/ota/publish-jobs.sh, read back with tools/jobs.mjs, documented in packaging/ota/README.md, "Remote jobs"). The app jobs replace the PC agent (igneum-agent.bat): PC 2 has no Claude session and nobody at the keyboard, and both PCs report the same hostname (DESKTOP-KMCV30N), which the relay's registration cannot tell apart; the app's per-install machine id can. The playbooks under relay/playbooks/ stay as the relay form of the same runs (shard-test.ps1 is the model for a run job) and are parse-checked by windows.yml.

The console

Tab Shows Source
Machines one card per machine: app and node version, height (daa), synced, hash rate, accepted blocks, peers, faults, power and temperatures, last seen; red after 3 min without an upload, grey "stopped (quit|update)" when the app's last upload ends on its own quit lines; a worker card whose STATUS line is over 180 s old is marked stale and left out of the machine total; the OTA state is the newest update line the app logged (downloading, downloaded, staged, installing, updated, failed, current); worker labels nvidia, amd, mac, metal, opencl, other and intel Neon miner_logs (the log intake in site/api/log.mjs): newest upload per label, the last 20 KB parsed server side (miner STATUS lines, node log, the app's stability: lines)
Jobs the signed jobs file with per-machine status (queued, running, done + exit code) and the result line; tap a run for the full upload igneum-jobs.json on the downloads host (fetched server side with DL_TOKEN), results from miner_logs rows whose run_id is job-<id>-<machine>
Builds the OTA manifest (version, notes, platforms, sizes), the last CI fetch, build events, the downloads folder listing igneum-app-latest.json and igneum-windows-ci.json on the downloads host; console_items kind build (posted by packaging/windows/fetch-ci-artifacts.sh and packaging/ota/publish-manifest.sh) and key dl (tools/console.mjs sync-dl)
Chain blocks, identities, hash estimate, difficulty, last lock, finality state, peers, blocks per minute sparkline, events, Hetzner results https://igneum.network/api/live fetched server side; console_items key hetzner (sync-hetzner)
Work log what the agents and the main session post, merged with every relay item, newest first console_items kinds log, build, note; the relay feed
Results the bench log entries (heading + first paragraph), newest first; the FUD ledger counts by status console_items kind bench and key ledger, written by tools/console.mjs sync-bench from docs/bench-log.md and docs/fud-ledger.md
Relay the feed and the drop box, unchanged relay_items, relay_machines

The console function is api/console.mjs, reached through the rewrite /r/<token>/c/<fn>. Every GET answer is cached 10 s in the function instance. No secret reaches the client: the token in the path is the only auth, and DL_TOKEN (the downloads folder) lives in the project env and is used only server side. No GitHub token anywhere: build events come from the Mac-side scripts.

Mac: node tools/console.mjs post --kind log --title "..." --body "..." writes one work-log item (kinds log, build, note); log, machines, chain, jobs, builds, results print the tabs; sync-bench, sync-dl, sync-hetzner or sync push the file-derived data; url prints the link.

The app log (label win-<id8> or mac-<id8>) reaches the intake since b8b349a (4 Oct 2026, 0.3.3); apps before that show app ?. The parsers (labels, miner tail, app tail, the stale mark) live in relay/lib/parse.mjs with no dependencies, and relay/lib/auth.mjs holds the constant-time secret compare; node --test relay/test/parse.test.mjs relay/test/auth.test.mjs runs their tests, and CI runs them in the site job.

The secrets, since 5 October 2026 (night): three tiers, headers, no token in a URL

Secret Where it lives Sent as May
the console token (20 base32 characters) ~/.config/igneum/relay-token, RELAY_TOKEN on the project the x-relay-token header from every tool and client; the URL path /r/<token>/ only for the phone's page (ui.html) and the calls that page makes (/r/<token>/api/<fn>, /r/<token>/c/<fn>, /r/<token>/wake) everything, except that a run task also needs the run signature below
the relay key (48 characters, the relay's own since 4 October 2026) ~/.config/igneum/relay-key, RELAY_KEY x-igneum-key read the feed, items, files, inbox, machines; post notes, files, results; register; ack; done. Never task, run, name, role, secret, delete
the log-intake key (inside every shipped package) LOG_INTAKE_KEY, LOG_INTAKE_KEY_NEXT x-igneum-key only upload and a drop of kind file or text: the PC apps' build-job outputs (jobrun.rs). No reads, no results, nothing else. RELAY_INTAKE_COMPAT=0 on the project closes this tier once the apps carry a relay key of their own
the run key (Ed25519, node tools/relay.mjs keygen) ~/.config/igneum/relay-run-key (seed, 0600) and .pub; the public half as RELAY_RUN_PUB on the project flags.sig on every run task, over the canonical text in relay/lib/guard.mjs (runCanon: machine, nonce, body sha256, the elevated, reboot_continue and reboot flags) without a verifying signature the relay answers 401 to a run; without RELAY_RUN_PUB every run is refused
a machine secret per PC (64 hex, node tools/relay.mjs secret PC1) ~/.config/igneum/relay-machines/<name> on the Mac; machine-secret.txt beside the agent on that PC (from make-clients.sh --machine); its sha256 on the relay (relay_machines.secret_hash) x-machine-secret on register, result, done; flags.mac on every run (an HMAC-SHA256 tag the agent verifies before it executes, because Windows PowerShell 5.1 has no Ed25519) a result's from is the machine the secret proves (a mismatch is 403); a bound hostname cannot register without it; the agent runs nothing whose tag does not verify, and never the same nonce twice

Review round 4 (X23) found one tier with every power: the intake key inside every package queued PowerShell on PC 1 as administrator. Now the token alone cannot queue a run either: the Mac signs it with the run key (checked by the relay) and tags it with the target's secret (checked by the agent). Compare is constant time (lib/auth.mjs). Blob file URLs carry a random segment and a random suffix; they are not listed anywhere. 120 calls a minute per IP, 10 failed authentications a minute per IP, then 429.

Rotation (what each secret's change breaks, and the order)

Rotate How What stops, until Order
the console token new value in ~/.config/igneum/relay-token and RELAY_TOKEN; a new zip per PC (make-clients.sh --machine) carried by hand; the phone gets the new link every PC client and the phone page, until the new zip and link are in place; the Mac tools at once (they read the file) any time; the old zip on a PC is dead the moment the project env changes
the relay key new value in relay-key and RELAY_KEY; new zips the PC clients' reads and reports until the new zip; tools/build-job.mjs falls back to the token with the token, same zip
the intake key LOG_INTAKE_KEY_NEXT on the relay AND the site intake first, repackage every app with the next key (packaged-config.sh), publish; then move _NEXT to LOG_INTAKE_KEY; then RELAY_INTAKE_COMPAT=0 once no shipped app uploads build outputs with it build-job uploads from every app that still carries the old key; log uploads from every shipped package until it updates the long one: apps update over days, so both values are accepted during the window (docs/plans/rotation-phase-2.md)
the run key node tools/relay.mjs keygen after moving the old relay-run-key aside; RELAY_RUN_PUB on the project every run queued with the old key is refused at the relay; nothing on a PC changes (the PCs hold no run key) any time, in one step
a machine secret node tools/relay.mjs secret PC1 --rotate (rebinds the sha256), make-clients.sh --machine PC1, carry the zip that PC's results and registration until the new zip is there; queued run tasks tagged with the old secret are refused by the agent per machine, any time

What the 5 October rotation already did: the relay token (4 October), the intake key and the dl token (.old-2026-10-05 beside the live files). What is still the founder's: the hosted igneum-relay-clients.zip off the downloads host (it holds the old token and key; dead values now, but the file is the shape X23 names), RELAY_RUN_PUB on the project after keygen, one secret per PC and the zips carried by hand.

What is stored where

Thing Where Limit
Items (text, title, who, kind, flags, read and done marks) Neon table relay_items (database igneum) body 1 MB
Machines (name, hostname, role, GPU and WSL facts, last seen) Neon table relay_machines
Files Vercel Blob store igneum-relay (public URLs with random path and suffix, London) 50 MB per file through a client token; 4 MB when pushed through the function
The token and keys ~/.config/igneum/relay-token, relay-key, relay-run-key, relay-machines/<name>; project env (RELAY_TOKEN, RELAY_KEY, RELAY_RUN_PUB, LOG_INTAKE_KEY, LOG_INTAKE_KEY_NEXT) never in the repo
Retention rows older than 30 days are deleted with their blobs, checked on a feed read at most every 10 minutes per instance; delete removes the blob with the row (X26) 30 days

Kinds: text (a note), file, task (for a person or a Claude session on a PC), run (a script the agent executes; signed and tagged, see above), result (what a task produced, linked by task_id; from is the machine the secret proves, flags.unbound marks one from a machine with no secret yet). Roles: miner, prover, bench, mac, phone.

API (/api/relay?fn=<name> with the headers above; /r/<token>/api/<fn> from the phone's page only)

Call Does
GET feed?since=&before=&machine=&limit= items newest first (50 by default, 100 at most) plus every machine with its unread count and whether it is bound
GET item?id= one item with its full body
GET file?id=[&download=1] 302 to the file
`GET inbox?machine=PC1&kind=run task`
POST inbox {machine, kind, ack:true} the same list, marked read (the agents use this)
GET machines names, roles, hostnames, last seen, bound
POST drop JSON {from,to,kind,title,body,file_name,file_url,size,task_id,flags}; or raw bytes with Content-Type: application/octet-stream and x-file-name (4 MB cap). A result needs x-machine-secret when its machine is bound
POST task same fields; kind task or run; run needs one named machine and flags {elevated, reboot_continue, reboot, nonce, sig, mac}; tools/relay.mjs run fills the last three in. 401 without a verifying sig, 409 on a reused nonce
POST upload {name,size} returns a one-hour Blob client token and put_url; PUT the bytes there, then drop with the returned url
POST ack {ids} POST done {id,exit_code} POST delete {id} marks; delete takes the blob with the row (token only)
POST register {hostname,info} a machine checks in; with x-machine-secret the secret names it whatever the hostname says (both PCs report DESKTOP-KMCV30N); info.user and info.dir are dropped
POST secret {name, secret_hash} binds a machine to the sha256 of its secret (token only; tools/relay.mjs secret does it)
POST name {hostname,name} POST role {name,role} naming and roles, from the Mac (token only)

Tests: node --test relay/test/guard.test.mjs relay/test/handler.test.mjs relay/test/clients.test.mjs (the rules, the handler against a fake database and fake blobs, the clients' shape), beside the parse, auth and wake suites; CI runs all six.

Wake (api/wake.mjs, 0.3.6, 5 October 2026): GET /wake?since=<stamp> is public (the apps hold no token) and rate limited, 30 a minute per IP. It holds up to 45 s and answers {stamp, at, added, changed, held_ms} the moment the stored stamp differs from since, else the unchanged stamp at the deadline; without since it answers at once. POST /r/<token>/wake {stamp, added} (or POST /wake with x-relay-token or x-igneum-key) records the stamp; packaging/ota/publish-jobs.sh sends it after every verified deploy, with the ids it added. One row per stamp in Neon table relay_wake (created by the first POST); tools/jobs.mjs status reads the rows for the woken latency. The function's maxDuration is 60 s (vercel.json). Tests: relay/test/wake.test.mjs drives the handler with a fake database and clock.

Mac

node tools/relay.mjs (feed), read <id>, drop "<text>"|<file>, task PC2 "title" [file], run PC2 "title" script.ps1 [--elevated] [--reboot-continue] [--reboot], keygen, secret PC2, watch, inbox PC1 [--ack], machines, role PC2 prover, name DESKTOP-XYZ PC2, ack|done|rm <id>, url. Playbooks live in relay/playbooks/; a playbook reads the downloads base from $env:RELAY_DL_BASE (bash: $RELAY_DL_BASE), which the agent holds; run refuses a body that says __DL_BASE__ or carries the dl token (X26). A script asks for a restart by printing RELAY-REBOOT on a line of its own, and only a task queued with --reboot or --reboot-continue restarts the PC.

PCs

relay/clients/make-clients.sh --machine PC1 bakes the URL, key, token, downloads base and that PC's secret into copies of the clients and writes ~/Desktop/igneum-relay-clients-PC1.zip. Carry it by hand; never through the downloads host. Unzip anywhere on the PC. send.bat for people and Claude sessions (see CLAUDE-PC.md), igneum-agent.bat for the automatic runner: double-click once, leave it open. It registers the PC (GPUs, WSL, nvcc; no username, no folder), polls every 20 s, checks each run task's tag and nonce against its secret (a task that fails is answered with exit 77 and nothing of it runs), runs the rest in order, posts a result (exit code, last 64 KB inline, full log as a file when longer) and marks it done. A script that prints RELAY-REBOOT on its own line, in a task queued with --reboot or --reboot-continue, triggers shutdown /r /t 10; with reboot_continue the agent arms a logon task (highest privileges, RunOnce as a fallback) for that one restart and re-runs the task after it with RELAY_PASS incremented. The agent removes the logon task and the RunOnce key every time it starts and when it exits (Ctrl+C included; a closed window is caught by the next start). Nothing is armed on an ordinary start (X25). The PC must sign in by itself for a restart to be unattended.

An unknown hostname that registers appears in the feed with a "name this machine" box, or node tools/relay.mjs name <hostname> PC2. PC1 is DESKTOP-KMCV30N.

The agent as a logon task, start-app, and the job-channel ping (MF-11, 7 October 2026)

PC 2 lost power at 10:46Z on 7 October 2026 (Kernel-Power 41, no bugcheck) and sat dark until a hand pressed the button at 13:37Z; its relay agent had been dead since 6 October (the one-shot logon task of X25 exited at every boot behind a UAC prompt), so nothing could reach it. Three things close the class:

Piece What Where
the agent as a logon task install-agent.ps1 (elevated, once per PC) registers IgneumRelayService from IgneumRelayService.xml: at this user's logon, highest run level, interactive, restarted on failure every minute up to 999 times, no time limit, one instance; its action is the hidden loop igneum-agent-service.ps1, which restarts igneum-agent.ps1 15 s after any exit and logs to %LOCALAPPDATA%\igneum-relay\logs\agent-service.log. The agent's own X25 disarm removes only the one-shot IgneumRelayAgent; the service task is never touched. install-agent.ps1 -Remove takes it off relay/clients/, in the zip from make-clients.sh --machine <name>
start-app node tools/relay.mjs start-app PC2: a task kind signed and tagged exactly like run (the relay checks sig, mac and the nonce; the token tier only), delivered in the agent's run inbox. An agent from v3 executes nothing from its body: its own Start-App starts the installed Igneum Miner as the user (an elevated agent goes through a one-off /RL LIMITED task, so the app never runs elevated), waits up to 90 s for an engine to answer api/state, and posts a result with the version and the seconds. The body is relay/playbooks/start-app.ps1, the same thing for an agent from before the kind. It never sends quit, pause or resume relay/lib/relay.mjs (KINDS, AGENT_KINDS), lib/handler.mjs, clients/igneum-agent.ps1, tools/relay.mjs
the ping every app wake request (0.3.21) carries machine=<id8>&v=<version>&job=<last job id>; /wake records one row per machine in relay_wake_seen before it holds; the console's Machines tab reads it as "job channel polled N ago, last job X" and, after 15 minutes without a poll, "job channel silent since relay/lib/wake.mjs (recordSeen, seenList, pingState, PING_SILENT_S), api/console.mjs, ui.html

Tests: node --test relay/test/service.test.mjs relay/test/handler.test.mjs relay/test/wake.test.mjs.

Deploy

cd relay && npx --yes vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes --scope igneum

Env on the project: DATABASE_URL, RELAY_KEY, RELAY_TOKEN, RELAY_RUN_PUB (the run key's public half; without it every run is refused), LOG_INTAKE_KEY and LOG_INTAKE_KEY_NEXT (the intake tier; RELAY_INTAKE_COMPAT=0 closes it), BLOB_READ_WRITE_TOKEN (added by vercel blob create-store), DL_TOKEN (the downloads folder token, for the console; added 4 Oct 2026). DNS: relay CNAME cname.vercel-dns.com in the deSEC zone. The first secret or feed call after the deploy adds relay_machines.secret_hash (ADD COLUMN IF NOT EXISTS, no long lock).

Untested until a PC runs it (4 Oct 2026)

send.ps1, igneum-agent.ps1 and the five PowerShell playbooks were written and syntax-reviewed on the Mac (no pwsh here). The bash twin agent.sh and send.sh ran end to end against the live relay on 4 October. The 5 October (night) rewrite of all four clients (headers, the machine tag check, the disarm, POST inbox, -K) is covered by relay/test/clients.test.mjs on the Mac and by the PowerShell 5.1 parse in windows.yml on the next push; it has not run on a PC. Expect a first-run fix on Windows: Start-Process -Wait exit codes through the wrapper, wsl --install --no-launch on pass 2, the RunOnce path after a reboot, and schtasks /Query /TN IgneumRelayAgent on PC 1 (owed: it must return nothing after the new agent's first start).