Site build: the bench scrub is self-contained Node (runs on Vercel and CI), fails on any private string
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
81fa6619d9
commit
6b644a644e
5 changed files with 90 additions and 38 deletions
|
|
@ -4,6 +4,8 @@ Text, files and tasks between Josh's devices without Gmail: the Mac, PC1, PC2 an
|
|||
|
||||
**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`.
|
||||
|
||||
**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 |
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// is never rendered here.
|
||||
// Runs on every deploy (Vercel build command) and locally with `node build.mjs`.
|
||||
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { scrubBench } from './scrub.mjs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
|
@ -135,11 +135,8 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c
|
|||
|
||||
let built = [];
|
||||
if (existsSync(join(docs, 'bench-log.md'))) {
|
||||
// the public /bench page renders a SCRUBBED copy (site/scrub-bench.sh: the public repository's export rules plus
|
||||
// site-only ones); the build fails rather than publish a private name, host or address (review round 4, R4.6.1)
|
||||
const scrubbed = join(here, '.bench-scrubbed.md');
|
||||
execSync(`"${join(here, 'scrub-bench.sh')}" "${join(docs, 'bench-log.md')}" "${scrubbed}"`, { stdio: 'inherit' });
|
||||
const src = readFileSync(scrubbed, 'utf8');
|
||||
// the public /bench page renders a SCRUBBED copy (site/scrub.mjs; the build fails on any private string, R4.6.1)
|
||||
const src = scrubBench(readFileSync(join(docs, 'bench-log.md'), 'utf8'));
|
||||
const { html, toc } = md(src);
|
||||
writeFileSync(join(here, 'bench.html'), page('Igneum engineering log', 'Every Igneum benchmark and test, with the hardware and the commands that produced it. Prototype numbers are labelled as such.', html, toc,
|
||||
'Every measurement the project has made, newest at the bottom, written by the people and agents who ran it, with the commands and hardware. Prototype numbers are not mining numbers and say so.',
|
||||
|
|
|
|||
25
site/forbidden-strings.txt
Normal file
25
site/forbidden-strings.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
DESKTOP-[A-Z0-9]{7}
|
||||
MacBook
|
||||
192\.168\.
|
||||
100\.[0-9]+\.[0-9]+\.[0-9]+
|
||||
\+0100
|
||||
\bBST\b
|
||||
/Users/
|
||||
C:\\Users
|
||||
~/Desktop
|
||||
log-intake
|
||||
LOG_INTAKE
|
||||
intake[_-]?key
|
||||
Tailscale
|
||||
tailscale
|
||||
ts\.net
|
||||
Josh
|
||||
Hetzner
|
||||
igneum-seed
|
||||
/root/
|
||||
/opt/igneum
|
||||
dl\.igneum
|
||||
intake id
|
||||
CLAUDE\.md
|
||||
DESKTOP-KMCV
|
||||
\bhcloud\b
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Scrubs a copy of docs/bench-log.md for the public /bench page: the same rules the public repository export applies
|
||||
# (tools/sync.sh scrub + scrub_benchlog + the private sync.local.sed), plus the site-only rules below. Exits 1 if any
|
||||
# forbidden string survives, so build.mjs refuses to render a page that names private machines, hosts or people.
|
||||
# site/scrub-bench.sh <in.md> <out.md>
|
||||
set -euo pipefail
|
||||
IN="$1"; OUT="$2"; PUB="${IGNEUM_PUBLIC_REPO:-$HOME/Projects/igneum-public}"
|
||||
cp "$IN" "$OUT"
|
||||
if [ -f "$PUB/tools/sync.sh" ]; then
|
||||
FNS="$(mktemp)"; sed -n '/^scrub() {/,/^}/p; /^scrub_benchlog() {/,/^}/p' "$PUB/tools/sync.sh" > "$FNS"
|
||||
# shellcheck disable=SC1090
|
||||
source "$FNS"; rm -f "$FNS"
|
||||
scrub "$OUT"; scrub_benchlog "$OUT"
|
||||
[ -f "$PUB/tools/sync.local.sed" ] && perl -pi -f "$PUB/tools/sync.local.sed" "$OUT"
|
||||
else
|
||||
echo "scrub-bench: public repository scrub rules not found at $PUB; refusing to render /bench" >&2; exit 1
|
||||
fi
|
||||
perl -pi -e '
|
||||
s/\bJosh\x27s\b/the maintainers\x27/g; s/\bJosh\b/the maintainers/g;
|
||||
s/igneum-seed-\d+/the seed node/g; s/\bHetzner\b/the cloud provider/g; s/\bhcloud\b/the cloud CLI/g;
|
||||
s/DESKTOP-[A-Z0-9]{7}/<pc-hostname>/g; s#/root/[A-Za-z0-9_./-]*#<server path>#g; s#/opt/igneum[A-Za-z0-9_./-]*#<server path>#g;
|
||||
s/dl\.igneum\.network[^ )`]*/the downloads host/g; s/,? ?log intake id \d+//g; s/\bintake id \d+\b/intake/g;
|
||||
s/\bCLAUDE\.md\b/the design document/g; s/\(CLAUDE\.md, /(the design document, /g;
|
||||
s/\b(\d{1,2}):(\d{2})(:\d{2})? BST\b/sprintf("%02d:%s%s UTC",($1+23)%24,$2,$3\/\/"")/ge; s/\(local time, UTC\+1\)/(UTC)/g; s/\bBST\b/UTC/g;
|
||||
s/\b(\d+) (?:cloud provider|Hetzner) nodes\b/$1 cloud nodes/g;
|
||||
' "$OUT"
|
||||
PAT="$(dirname "$0")/../tools/ci/forbidden-strings.txt"
|
||||
EXTRA='Josh|Hetzner|igneum-seed|/root/|/opt/igneum|DESKTOP-[A-Z0-9]{7}|dl\.igneum|intake id|CLAUDE\.md|\bBST\b|192\.168\.|DESKTOP-KMCV'
|
||||
PATS="$(mktemp)"; [ -f "$PAT" ] && grep -vE '^\s*(#|$)' "$PAT" > "$PATS" || true; echo "$EXTRA" | tr '|' '\n' >> "$PATS"
|
||||
HITS=$(grep -nE -f "$PATS" "$OUT" || true); rm -f "$PATS"
|
||||
if [ -n "$HITS" ]; then echo "scrub-bench: forbidden strings remain:" >&2; echo "$HITS" | head -20 >&2; exit 1; fi
|
||||
echo "scrub-bench: clean ($(wc -l < "$OUT") lines)"
|
||||
60
site/scrub.mjs
Normal file
60
site/scrub.mjs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Scrub for the public /bench page (review round 4, R4.6.1). The same rules the public repository export applies
|
||||
// (its sync.sh generic scrub and bench-log rules, and the maintainers' local rules), plus site-only ones, in plain Node
|
||||
// so the build runs the same on this Mac, on Vercel and on the CI runner. The build fails if any pattern in
|
||||
// site/forbidden-strings.txt survives, so a private name, host or address can never reach the page.
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const utc = (h, m, s) => `${String((Number(h) + 23) % 24).padStart(2, '0')}:${m}${s || ''} UTC`;
|
||||
const RULES = [
|
||||
// machines: model names only
|
||||
[/the PC node at 192\.168\.[0-9.]+/g, 'the RTX 5090 node on the LAN'],
|
||||
[/\bthe PC node\b/g, 'the RTX 5090 node'],
|
||||
[/\bWindows PC\b/g, 'an RTX 5090 on Windows'],
|
||||
[/\bthe PC's\b/g, "the RTX 5090 machine's"],
|
||||
[/\bthe PC\b/g, 'the RTX 5090 machine'],
|
||||
[/\bPC (joins|start|period)\b/g, 'RTX 5090 $1'],
|
||||
[/\(Mac side only;/g, '(Apple M5 Max side only;'],
|
||||
[/\bthe Mac's\b/g, "the Apple M5 Max's"],
|
||||
[/\bthe Mac\b/g, 'the Apple M5 Max'],
|
||||
[/\bJosh's\b/g, "the maintainers'"],
|
||||
[/\bJosh\b/g, 'the maintainers'],
|
||||
[/ Not deployed to Vercel tonight\./g, ''],
|
||||
// addresses, hosts, paths
|
||||
[/192\.168\.[0-9]+\.[0-9]+/g, '<lan-ip>'],
|
||||
[/\b100\.[0-9]+\.[0-9]+\.[0-9]+\b/g, '<overlay-ip>'],
|
||||
[/DESKTOP-[A-Z0-9]{7}/g, '<pc-hostname>'],
|
||||
[/igneum-seed-\d+/g, 'the seed node'],
|
||||
[/\bHetzner\b/g, 'the cloud provider'],
|
||||
[/\bhcloud\b/g, 'the cloud CLI'],
|
||||
[/\/root\/[A-Za-z0-9_./-]*/g, '<server path>'],
|
||||
[/\/opt\/igneum[A-Za-z0-9_./-]*/g, '<server path>'],
|
||||
[/dl\.igneum\.network[^ )`]*/g, 'the downloads host'],
|
||||
[/,? ?log intake id \d+/g, ''],
|
||||
[/\bintake id \d+\b/g, 'intake'],
|
||||
[/~\/Desktop\//g, '`'], [/``/g, '`'],
|
||||
[/~\/\.cargo\/bin\/cargo/g, 'cargo'],
|
||||
[/\/Users\/[A-Za-z0-9_.-]+/g, '~'],
|
||||
[/C:\\Users\\[A-Za-z0-9_.-]+/g, '%USERPROFILE%'],
|
||||
// unpublished documents
|
||||
[/`docs\/fud-ledger\.md`/g, 'the break ledger (kept by the maintainers, not yet published)'],
|
||||
[/\(CLAUDE\.md, /g, '(the design document, '],
|
||||
[/\bCLAUDE\.md\b/g, 'the design document'],
|
||||
// local times to UTC (the log is written in UTC+1)
|
||||
[/2,193 blocks at 22:35;/g, '2,193 blocks at 21:35 UTC;'],
|
||||
[/(\d{1,2}:\d{2}(?::\d{2})? UTC) = \d{1,2}:\d{2} B[S]T/g, '$1'],
|
||||
[/(\d{1,2}):(\d{2})(:\d{2})? to (\d{1,2}):(\d{2})(:\d{2})? B[S]T/g, (_, h1, m1, s1, h2, m2, s2) => `${utc(h1, m1, s1).replace(' UTC', '')} to ${utc(h2, m2, s2)}`],
|
||||
[/(\d{1,2}):(\d{2})(:\d{2})? B[S]T/g, (_, h, m, s) => utc(h, m, s)],
|
||||
[/\(local time, UTC\+1\)/g, '(UTC)'],
|
||||
[/\bB[S]T\b/g, 'UTC'],
|
||||
];
|
||||
export function scrubBench(text) {
|
||||
let out = text;
|
||||
for (const [re, rep] of RULES) out = out.replace(re, rep);
|
||||
const pats = readFileSync(join(here, 'forbidden-strings.txt'), 'utf8').split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
||||
const hits = [];
|
||||
out.split('\n').forEach((line, i) => { for (const p of pats) if (new RegExp(p).test(line)) { hits.push(`${i + 1}: ${p}`); break; } });
|
||||
if (hits.length) throw new Error(`scrub: forbidden strings remain on the bench page:\n${hits.slice(0, 20).join('\n')}`);
|
||||
return out;
|
||||
}
|
||||
Loading…
Reference in a new issue