Compare commits

...

6 commits

Author SHA1 Message Date
igneum-labs
20215f0274 Merge scrub-3 ff0d0dde into master (gate: green on ff0d0dde, recorded by tools/ci/pre-push.sh; landed on the box mirror under the exception declared by main: main's ruling, 7 Oct 2026 19:5x UK: the GitHub account is suspended, lanes land on the box mirror's master, the box gate stamp is the verdict; GitHub gets the fast-forward when it answers) 2026-10-07 20:46:40 +00:00
igneum-labs
21d956e4bd Merge ship-docs-0321 a7ea56cd into master (gate: green on a7ea56cd, recorded by tools/ci/pre-push.sh; the full gate runs in CI on this merge) 2026-10-07 20:40:42 +00:00
igneum-labs
ff0d0dde61 Scrub: the founder list leaves the repository in every encoding; the never-push founder check decodes base64, hex and .b64 blobs too (7 October 2026, 21:4x UK)
tools/ci/founder-strings.b64 was the founder check's pattern list base64-encoded: a grep could not read it, any reader of the public host could (git.igneum.network was public from 21:26 UK; read off at 21:36). The list now lives only in a private file (~/.config/igneum/founder-strings on the Mac, /srv/discord-hooks/founder-strings on build-1 for the Discord guard; $IGNEUM_FOUNDER_STRINGS overrides) and site/forbidden-strings.txt carries no encoded copy. Readers: founder-strings-check.sh (skips with a line where the file is absent; the Mac's hook is the guard), site/scrub.mjs and launch-gates-check.mjs (the private file's patterns added where it exists), discord-hooks.mjs (three locations; the test writes a fixture list and loads the module after it), fresh-repo.sh (the private file; drops tools/ci/founder-strings.b64 from every commit; rewrites the base64 of every list regex out of every blob and scans for it).

The check's second pass (main's addition): every base64 literal of 24 characters or more, every hex literal of 24 or more and every *.b64 file is decoded and scanned, so no encoding hides a term again; the self-test plants each fixture sample in plain text, in a .b64 file, as a base64 literal and as a hex literal, each caught and named, and a tree without the list skips with its line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 20:40:36 +00:00
igneum-labs
a7ea56cd7b rule 15 at six places; release 0.3.22: the 0.3.22 Windows take 2 fault and the skip, the version miss's second layer, the 0.3.23 pairs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 20:35:17 +00:00
igneum-labs
f51da33a65 tools/ci/release-version-check.sh (rule 15): a release-0.3.N branch reads 0.3.N in Cargo.toml, Cargo.lock and version.h from its first commit; self-test on the 0.3.23 shape; wired into the pre-push gate
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 20:29:07 +00:00
igneum-labs
ffbd88ab25 release rules 15 (the version bump as the release branch's first commit, gated); release 0.3.22 section 11: the 0.3.23 cut at f7645269 with the fold and the version miss, the 0.3.22 Windows take 1 fault and take 2, the Devnet 3 passes, the LG-4 shape on PC 2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 20:27:39 +00:00
11 changed files with 180 additions and 76 deletions

View file

@ -112,3 +112,13 @@ Three commits on 96161037: bd710a36 the sub-version 3 re-pin (byte 7, igneum-pow
**The steward's two items before the public repository opens, both closed:** tools/exec-attacks/lib/common.mjs MINER_KEY is the public Anvil/Hardhat default account 1 key (five harnesses use it; balance 0 and nonce 0 on both chains at 20:55 and 20:59 BST; the fix is a chain-id guard in the five harnesses and a README line, a 0.3.24 steward row); app/igneum-wallet/src/hd.rs's KEY in one history commit is the public Hardhat default account 0 key in a removed test. **The steward's two items before the public repository opens, both closed:** tools/exec-attacks/lib/common.mjs MINER_KEY is the public Anvil/Hardhat default account 1 key (five harnesses use it; balance 0 and nonce 0 on both chains at 20:55 and 20:59 BST; the fix is a chain-id guard in the five harnesses and a README line, a 0.3.24 steward row); app/igneum-wallet/src/hd.rs's KEY in one history commit is the public Hardhat default account 0 key in a removed test.
**Proven share, the second hour (20:59 BST):** cumulative 0.468 (1,848 of 3,952 shards since DAA 0); the hour's own segments 0.84 (1,094 of 1,304), the shortfall mostly the 34a2dbaa sweep's one-minute node restart per prover; eleven provers (five 3060s at about 85 s a segment, two 3090s, three 4090s, one A5000), a twelfth starting; the next hour's own segments should read one and the founder's 24 hours count from the first that does. **The pool hour** counts from 20:33:28 BST (dn3-pool-b's first OPEN SHARE on the 017e7037 daemons a357c581; WRONG HASH 0 since), the pool split's condition. **Proven share, the second hour (20:59 BST):** cumulative 0.468 (1,848 of 3,952 shards since DAA 0); the hour's own segments 0.84 (1,094 of 1,304), the shortfall mostly the 34a2dbaa sweep's one-minute node restart per prover; eleven provers (five 3060s at about 85 s a segment, two 3090s, three 4090s, one A5000), a twelfth starting; the next hour's own segments should read one and the founder's 24 hours count from the first that does. **The pool hour** counts from 20:33:28 BST (dn3-pool-b's first OPEN SHARE on the 017e7037 daemons a357c581; WRONG HASH 0 since), the pool split's condition.
## 11. The 0.3.23 cut (21:1x to 21:3x BST): the fold, the version miss, the LG-4 shape
**release-0.3.23 = 05c2ddfe on the mirror:** 2f27ecb1 (the rights step 332b82eb with the deferred rule and the WebView2 right, the unattended driver install 50cdb8e4) + check-labels-23 e6f9ef0b + ota-token-23 757d7870 + the Windows pin to 2720d8d2 + dpi-23 ad407b13 (per-monitor DPI; BUILD-APP.bat, host.rc, host.manifest, host.cpp) + network-23 b18a5b0f (the first-run network step: the manifest's default_network, the testnet card present and refused until testnet_open, `--testnet --netsuffix=1` with the seeds as --addpeer; view.test.mjs resolved as the union of the check-labels and network tests) + install-close-23 5140e6fe (main's fold inside 15 minutes: the installer's stop step ends the window host first by path and waits up to 30 s for the unlock, CloseApplicationsFilter and SetupMutex in the .iss, install-running.flag holds every relaunch, the engine prints EXIT update and the host ends instead of restarting; tests known-failed first on tonight's PC 2 sequence) + the version bump 05c2ddfe. App gate GREEN on build-1 at every step (last 284 + 35 + 8), UI 86, pre-push 59. The version miss (rule 15): the branch carried 0.3.22 in every place until 21:26 BST, caught by the build-server lane before the host job; the exes and kit from 1c5323be are void. The Mac DMG re-cut under the lock on the 2720d8d2 Mac pair (igneumd efff01cd, igneum-miner 45c4c68f); the 0.3.23 host from the 05c2ddfe kit in the PC 1 slot after the hash lane's floor grid; the manifest carries default_network devnet-3 and keeps the floor file; the 0.3.23 Windows smoke is the first read of install-close-23 over a running app (the installer's own stop step, no pre-stop by the job) and of the OTA-return line through the app's own path.
**The 0.3.22 Windows smoke, take 1 FAIL (20:54 BST), the job shape:** the installer 5ac3dc62 built clean on PC 2 (every exe 0.3.22, the MSVC host 4bc25b7f through the host gate), but the job's silent install over the running 0.3.21 app sent api/quit, the engine stopped, the window host (the same pid since 19:14 BST) never stopped and its restart ladder relaunched the old engine 10 s later, Inno could not replace the locked host with its message box suppressed, every file stayed 0.3.21. Take 2 in the 0.3.10 smoke shape (the job stops the app by pid first, installs, asserts every file's version and sha, starts the host, reads the app alive at 60 s on 0.3.22 with stdin open). The defect is install-close-23 in 0.3.23. Devnet 3: the 34a2dbaa pass complete on every node by 21:17 BST (thirty-two read-backs incl. hub-1's and pool-1's second nodes); the 2720d8d2 pass started 21:17:23 BST with dn3-g1, the pair whole (igneumd df6476ed, miner dfdc6883, paired: program id 571131ccbd6e0de9 equal with c29f33bb at epoch 2).
**LG-4 (the founder: "use PC 1 or PC 2 or get another machine"; main's ruling):** on PC 2 tonight after both smokes, a fresh Windows user account created by job (no Igneum state, no card cache), the three timed steps (download and install, sync to the tip, dataset build to the first accepted share) as FIRST-SHARE lines, ten runs with the app uninstalled and the profile wiped between runs, no click anywhere, the rows on /evidence labelled "PC 2, fresh user account, not a fresh image, 7 October 2026", the 9-of-10 under-10-minutes bar read against them; macOS the same way as a fresh user account on the Mac tomorrow; a rented Windows VM only if the account shape fails the bar for a reason an image would change. PC 1 stays the founder's desk.
**0.3.22 Windows take 2 FAIL (21:13 BST) and the skip:** the job's detached helper was ended with the job's process tree before its first sleep ran out (Start-Process stays in the runner's tree; a survivor needs Win32_Process.Create or a scheduled task); nothing was installed, every file still 0.3.21, the payload untouched. Ruling: no take 3; the 0.3.22 Windows entry is skipped (the Mac and HiveOS entries stand); the first install over a running app is 0.3.23's installer with install-close-23 in the simplest job shape (Start-Process -Wait; the installer's own stop step and the install-running flag do the work), its smoke read the gate line for both; the Discord card publishes on the 0.3.23 Windows entry. **The version miss, second layer (21:30 BST):** the box cross of 0.3.23's exes failed in build.rs because igneum-app.rc still read 0.3.22 (Info.plist and the installer's AppVersion too); fixed at release-0.3.23 = 7c7489ac, the rule 15 check extended to six places and green on the tree; the 0.3.23 node pairs under /srv/artefacts/0323-2720d8d2/ (hands f2cf6a87/fb147dd1, seed 3edaf83d/be5ca735, win 8326d78a/38c74545) with the engine string.

View file

@ -18,3 +18,4 @@ Every cut of the Igneum Miner app and its node runs under these. The dated plan
11. **Kill by pid, never by name,** on the shared Mac; a merge worktree never checks out master. 11. **Kill by pid, never by name,** on the shared Mac; a merge worktree never checks out master.
12. **The Discord card only when every platform is live.** Live manifest changes beyond the binaries (a moved consensus floor) go out only on the founder's explicit word, staged beside the release with their digest and a one-line diff. 12. **The Discord card only when every platform is live.** Live manifest changes beyond the binaries (a moved consensus floor) go out only on the founder's explicit word, staged beside the release with their digest and a one-line diff.
13. **Ship on green:** no calendar waits; when the gates are green, publish and state the clock time (UK). Checkpoints are for slips, not for waiting. 13. **Ship on green:** no calendar waits; when the gates are green, publish and state the clock time (UK). Checkpoints are for slips, not for waiting.
15. **The version bump is the release branch's first commit (7 October 2026, after the 0.3.23 miss).** When a release-0.3.N branch opens, its first commit moves the six version places (app/igneum-app/Cargo.toml and Cargo.lock, app/windows/version.h, app/igneum-app/resources/igneum-app.rc's four fields, packaging/mac/app/Info.plist, the installer's AppVersion), never left to the cut: release-0.3.23 opened at 4cdcab31 and carried 0.3.22 in every place until 21:26 BST, so the app exes and the window host crossed from its first closed tip read 0.3.22 and were void. Gate check: on a push to release-0.3.N the pre-push gate (tools/ci/release-version-check.sh, self-test on tonight's shape first) reads all six places against the branch name and goes red on any mismatch; the .rc was the second layer of the same miss (the box cross failed in build.rs at 21:30 BST).

View file

@ -13,16 +13,9 @@ intake[_-]?key
Tailscale Tailscale
tailscale tailscale
ts\.net ts\.net
# the founder's name, logins and the earlier businesses, base64-encoded so the list is not itself a hit (a `b64:` line is decoded # the founder's name, logins and the earlier businesses are NOT in this file in any encoding (a base64 line is a disclosure to any reader, found
# and compiled case-insensitive by site/scrub.mjs and tools/ci/launch-gates-check.mjs; the same patterns live in tools/ci/founder-strings.b64) # 7 October 2026, 21:3x UK, on the public host): they live in the private list ~/.config/igneum/founder-strings, read by site/scrub.mjs,
b64:[encoded-pattern-removed] # tools/ci/launch-gates-check.mjs and tools/ci/founder-strings-check.sh where it exists; the Mac's pre-push hook is the guard on every push.
b64:[encoded-pattern-removed]
b64:[encoded-pattern-removed]
b64:[encoded-pattern-removed]
b64:[encoded-pattern-removed]
b64:[encoded-pattern-removed]
b64:[encoded-pattern-removed]
b64:[encoded-pattern-removed]
Hetzner Hetzner
igneum-seed igneum-seed
/root/ /root/

View file

@ -3,6 +3,7 @@
// so the build runs the same on this Mac, on Vercel and on the CI runner. The build fails if any pattern in // 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. // site/forbidden-strings.txt survives, so a private name, host or address can never reach the page.
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { join, dirname } from 'node:path'; import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url)); const here = dirname(fileURLToPath(import.meta.url));
@ -59,13 +60,21 @@ const RULES = [
[/\(local time, UTC\+1\)/g, '(UTC)'], [/\(local time, UTC\+1\)/g, '(UTC)'],
[/\bB[S]T\b/g, 'UTC'], [/\bB[S]T\b/g, 'UTC'],
]; ];
// the private founder list (~/.config/igneum/founder-strings or $IGNEUM_FOUNDER_STRINGS; perl regex, a tab, a sample per row; # comments);
// absent here: no founder patterns from this reader (the Mac's pre-push hook carries the file and is the guard)
function founderPatternsFromFile() {
try {
const f = process.env.IGNEUM_FOUNDER_STRINGS || join(homedir(), '.config', 'igneum', 'founder-strings');
return readFileSync(f, 'utf8').split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')).map((l) => new RegExp(l.split('\t')[0], 'i'));
} catch { return []; }
}
export function scrubBench(text) { export function scrubBench(text) {
let out = text; let out = text;
for (const [re, rep] of RULES) out = out.replace(re, rep); 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 pats = readFileSync(join(here, 'forbidden-strings.txt'), 'utf8').split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'))
.map(l => (l.startsWith('b64:') ? new RegExp(Buffer.from(l.slice(4), 'base64').toString('utf8'), 'i') : new RegExp(l))); // a b64: line is an encoded, case-insensitive pattern .map(l => new RegExp(l)).concat(founderPatternsFromFile()); // plus the private founder list where it exists
const hits = []; const hits = [];
out.split('\n').forEach((line, i) => { for (const p of pats) if (p.test(line)) { hits.push(`${i + 1}: ${p.source.startsWith('(?<!') || p.flags.includes('i') ? '(an encoded founder pattern)' : p.source}`); break; } }); out.split('\n').forEach((line, i) => { for (const p of pats) if (p.test(line)) { hits.push(`${i + 1}: ${p.flags.includes('i') ? '(a founder pattern from the private list)' : p.source}`); break; } });
if (hits.length) throw new Error(`scrub: forbidden strings remain on the bench page:\n${hits.slice(0, 20).join('\n')}`); if (hits.length) throw new Error(`scrub: forbidden strings remain on the bench page:\n${hits.slice(0, 20).join('\n')}`);
return out; return out;
} }

View file

@ -1,37 +1,49 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# No founder name, personal login, earlier business or personal address in any tracked text file (the pre-public scrub, # No founder name, personal login, earlier business or personal address in any tracked text file, in plain text OR in an encoding
# 7 October 2026, main's item (4): forbidden strings over tracked files on every merge, known-failed first). The identity # (the pre-public scrub, 7 October 2026; a never-push class since 20:5x UK: every push, every branch). The identity check
# check (tools/ci/identity-check.sh) reads the public EXPORT list; this one reads EVERY tracked file, because the repository # (tools/ci/identity-check.sh) reads the public EXPORT list; this one reads EVERY tracked file, because the repository itself
# itself goes public at the testnet (docs/fud-fixes.md section 5). # is public (git.igneum.network).
# #
# The patterns are not written in this tree in plain text: a plaintext list would be the hit it looks for. They live # The patterns are NOT in the repository in any form. They live in a private file, ~/.config/igneum/founder-strings
# base64-encoded in tools/ci/founder-strings.b64 (one decoded line per pattern: perl regex, a tab, a sample the self-test plants; # ($IGNEUM_FOUNDER_STRINGS overrides; one row per pattern: perl regex, a tab, a sample the self-test plants; # comments), on the
# case-insensitive; # comments ignored) # Mac that pushes. A base64 copy in the tree (tools/ci/founder-strings.b64, 19:5x to 21:3x UK) was a disclosure to any reader of
# and are decoded into a private temporary file at run time. The login's pre-rename spelling is in the list too (main's ruling, # the public host and is gone from every commit. Where the file is absent (a hosted CI runner, a box) the check prints a skip
# 7 October 2026: the public tree names igneum-labs only); the fresh-repository step rewrites it in the history (tools/repo/fresh-repo.sh). # line and passes; the Mac's pre-push hook, which has the file, is the guard.
# #
# tools/ci/founder-strings-check.sh # exit 1 with file:line for every hit over the tracked text files # Two passes over every tracked text file: the plain text, then every encoded blob decoded and scanned (base64 literals of 24
# tools/ci/founder-strings-check.sh --self-test # a fixture tree with one hit per pattern class fails and names the file; a clean # characters or more, every *.b64 file whole, hex literals of 24 characters or more), so an encoding never hides a term again.
# # fixture passes; the encoded list decodes to at least five patterns #
# tools/ci/founder-strings-check.sh # exit 1 with file:line for every hit (decoded hits say so); exit 0 with a skip line without the list
# tools/ci/founder-strings-check.sh --self-test # with a FIXTURE list of made-up names (never the real file): a clean tree passes; each
# # sample planted in plain text, in a .b64 file, as a base64 literal and as a hex literal is
# # caught and names its file; a tree without the list skips with the line
set -euo pipefail set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)" HERE="$(cd "$(dirname "$0")" && pwd)"
LIST="$HERE/founder-strings.b64" LIST="${IGNEUM_FOUNDER_STRINGS:-$HOME/.config/igneum/founder-strings}"
REPO="${FOUNDER_CHECK_REPO:-$(cd "$HERE/../.." && pwd)}" REPO="${FOUNDER_CHECK_REPO:-$(cd "$HERE/../.." && pwd)}"
decode() { # [samples]: the regexes (or, with "samples", the sample per regex), one per line, into a 0600 file whose name is printed rows() { grep -vE '^\s*(#|$)' "$LIST"; } # the live rows
local f col=1; [ "${1:-}" = samples ] && col=2 scan() { # <repo> <list>: plain pass, then the decoded pass; prints "file:line:text" or "file:line:(decoded <kind>) text"; exit 1 on any hit
f="$(mktemp)"; chmod 600 "$f" local repo="$1" list="$2" pats hits
base64 -d < "$LIST" | grep -vE '^\s*(#|$)' | cut -f"$col" > "$f" pats="$(mktemp)"; chmod 600 "$pats"; grep -vE '^\s*(#|$)' "$list" | cut -f1 > "$pats"
echo "$f" hits="$(cd "$repo" && git ls-files -z -- . ':!*.png' ':!*.jpg' ':!*.jpeg' ':!*.gif' ':!*.ico' ':!*.woff' ':!*.woff2' ':!*.ttf' ':!*.pdf' ':!*.zip' ':!*.bin' \
}
scan() { # <repo>: every tracked text file against the decoded list; prints file:line:text, exit 1 on any hit (perl: the Mac's grep has no -P)
local repo="$1" pats hits
pats="$(decode)"
hits="$(cd "$repo" && git ls-files -z -- . ':!*.png' ':!*.jpg' ':!*.jpeg' ':!*.gif' ':!*.ico' ':!*.woff' ':!*.woff2' ':!*.ttf' ':!*.pdf' ':!*.zip' ':!*.bin' ':!*.b64' \
| xargs -0 perl -e ' | xargs -0 perl -e '
use MIME::Base64 qw(decode_base64);
my $pf = shift @ARGV; open(my $ph, "<", $pf) or die; my @pats = map { chomp; qr/$_/i } grep { /\S/ } <$ph>; close $ph; my $pf = shift @ARGV; open(my $ph, "<", $pf) or die; my @pats = map { chomp; qr/$_/i } grep { /\S/ } <$ph>; close $ph;
for my $f (@ARGV) { next unless -f $f && -T $f; open(my $h, "<", $f) or next; my $n = 0; sub hit { my ($t) = @_; for my $p (@pats) { return 1 if $t =~ $p } 0 }
while (my $l = <$h>) { $n++; for my $p (@pats) { if ($l =~ $p) { chomp $l; print "$f:$n:" . substr($l, 0, 160) . "\n"; last } } } close $h; } for my $f (@ARGV) {
next unless -f $f && -T $f; open(my $h, "<", $f) or next; my $n = 0; my $whole = "";
while (my $l = <$h>) {
$n++; $whole .= $l;
if (hit($l)) { chomp $l; print "$f:$n:" . substr($l, 0, 160) . "\n"; next }
# the encoded pass on this line: base64 literals and hex literals of 24 characters or more
while ($l =~ /([A-Za-z0-9+\/]{24,}={0,2})/g) { my $d = decode_base64($1); next unless length $d; if (hit($d)) { print "$f:$n:(decoded base64) " . substr($d =~ s/[^\x20-\x7e]/./gr, 0, 120) . "\n"; last } }
while ($l =~ /\b([0-9a-fA-F]{24,})\b/g) { my $x = $1; next if length($x) % 2; my $d = pack("H*", $x); if (hit($d)) { print "$f:$n:(decoded hex) " . substr($d =~ s/[^\x20-\x7e]/./gr, 0, 120) . "\n"; last } }
}
close $h;
# a .b64 file as one blob
if ($f =~ /\.b64$/) { (my $b = $whole) =~ s/\s+//g; my $d = decode_base64($b); if (length $d && hit($d)) { print "$f:1:(decoded .b64 file) " . substr($d =~ s/[^\x20-\x7e]/./gr, 0, 120) . "\n" } }
}
' "$pats" 2>/dev/null || true)" ' "$pats" 2>/dev/null || true)"
rm -f "$pats" rm -f "$pats"
if [ -n "$hits" ]; then printf '%s\n' "$hits" | sed 's/^/founder-strings: /' >&2; return 1; fi if [ -n "$hits" ]; then printf '%s\n' "$hits" | sed 's/^/founder-strings: /' >&2; return 1; fi
@ -39,28 +51,34 @@ scan() { # <repo>: every tracked text file against the decoded list; prints fi
} }
if [ "${1:-}" = "--self-test" ]; then if [ "${1:-}" = "--self-test" ]; then
n="$(base64 -d < "$LIST" | grep -vcE '^\s*(#|$)')" top="$(mktemp -d)"; trap 'rm -rf "$top"' EXIT; fx="$top/repo"; mkdir -p "$fx"
[ "$n" -ge 5 ] || { echo "self-test failed: the encoded list decodes to $n pattern(s), expected at least 5"; exit 1; } fixture="$top/list"; printf '# fixture\n\\bfoundername\\b\tfoundername\n\\bsurnamex\\b\tSurnamex\n\\bbiznamez\\b\tbiznamez\n' > "$fixture"
fx="$(mktemp -d)"; trap 'rm -rf "$fx"' EXIT ( cd "$fx" && git init -q -b master . ); mkdir -p "$fx/docs" "$fx/tools/ci" "$fx/site"
( cd "$fx" && git init -q -b master . )
mkdir -p "$fx/docs"
# a clean tree: the standing login, the project, a neutral owner word
printf 'Commit as igneum-labs. The founder decided on 5 October 2026. Igneum Labs LTD, DIFC.\n' > "$fx/docs/clean.md" printf 'Commit as igneum-labs. The founder decided on 5 October 2026. Igneum Labs LTD, DIFC.\n' > "$fx/docs/clean.md"
printf 'aGVsbG8gd29ybGQsIG5vdGhpbmcgaGVyZQ==\n' > "$fx/tools/ci/clean.b64" # "hello world, nothing here"
( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m c ) ( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m c )
FOUNDER_CHECK_REPO="$fx" bash "$0" >/dev/null 2>&1 || { echo "self-test failed: a clean tree was reported"; exit 1; } fails=0
# one hit per pattern class, each from the encoded list's own sample column, so this script never spells them; every hit scan "$fx" "$fixture" >/dev/null 2>&1 || { echo "self-test failed: a clean tree (with a harmless .b64) was reported"; fails=1; }
# must name its file i=0
samples="$(decode samples)"; i=0; fails=0 while IFS=$'\t' read -r re sample; do
while IFS= read -r word; do i=$((i + 1)); [ -n "$sample" ] || continue
i=$((i + 1)); [ -n "$word" ] || continue for kind in plain b64file base64 hex; do
printf 'a line that names %s in passing\n' "$word" > "$fx/docs/hit-$i.md" case "$kind" in
( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "h$i" ) plain) f="docs/hit-$i.md"; printf 'a line that names %s in passing\n' "$sample" > "$fx/$f" ;;
if out="$(FOUNDER_CHECK_REPO="$fx" bash "$0" 2>&1)"; then echo "self-test failed: pattern $i was not caught"; fails=1 b64file) f="tools/ci/hit-$i.b64"; printf 'a line that names %s in passing\n' "$sample" | base64 > "$fx/$f" ;;
else case "$out" in *"docs/hit-$i.md"*) ;; *) echo "self-test failed: the hit for pattern $i did not name its file: $out"; fails=1 ;; esac; fi base64) f="site/hit-$i.mjs"; printf 'const X = "%s";\n' "$(printf 'a line that names %s in passing' "$sample" | base64 | tr -d '\n')" > "$fx/$f" ;;
rm -f "$fx/docs/hit-$i.md"; ( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "r$i" ) hex) f="site/hit-$i-hex.mjs"; printf 'const H = "%s";\n' "$(printf 'a line that names %s in passing' "$sample" | xxd -p | tr -d '\n')" > "$fx/$f" ;;
done < "$samples"; rm -f "$samples" esac
# a binary file carrying a pattern is not read (images are not text) ( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "h" )
[ "$fails" = 0 ] && echo "self-test passed: a clean tree passes; every pattern class in the encoded list is caught in a fixture file and named; the list decodes to $n patterns" if out="$(scan "$fx" "$fixture" 2>&1)"; then echo "self-test failed: pattern $i was not caught as $kind"; fails=1
else case "$out" in *"$f"*) ;; *) echo "self-test failed: the $kind hit for pattern $i did not name $f: $out"; fails=1 ;; esac; fi
rm -f "$fx/$f"; ( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "r" )
done
done < <(grep -vE '^\s*(#|$)' "$fixture")
# without a list: the skip line, exit 0
out="$(IGNEUM_FOUNDER_STRINGS="$fx/no-such-list" FOUNDER_CHECK_REPO="$fx" bash "$0" 2>&1)" && case "$out" in *"skipped, no private list"*) ;; *) echo "self-test failed: no skip line without the list: $out"; fails=1 ;; esac || { echo "self-test failed: a tree without the list did not pass with a skip line"; fails=1; }
[ "$fails" = 0 ] && echo "self-test passed: a clean tree passes; every fixture pattern is caught in plain text, in a .b64 file, as a base64 literal and as a hex literal, each naming its file; without the private list the check skips with its line"
exit $fails exit $fails
fi fi
scan "$REPO" && echo "founder-strings: no founder name, personal login, earlier business or personal address in any tracked text file" if [ ! -s "$LIST" ]; then echo "founder-strings: skipped, no private list at $LIST (the Mac's pre-push hook carries the list and is the guard; a runner or box has none)"; exit 0; fi
scan "$REPO" "$LIST" && echo "founder-strings: no founder name, personal login, earlier business or personal address in any tracked text file, plain or encoded ($(rows | wc -l | tr -d ' ') patterns from the private list)"

View file

@ -12,7 +12,8 @@
// node tools/ci/launch-gates-check.mjs --self-test # a gate row without a check fails; a handoff with the founder's name fails // node tools/ci/launch-gates-check.mjs --self-test # a gate row without a check fails; a handoff with the founder's name fails
import { existsSync, readFileSync, mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs'; import { existsSync, readFileSync, mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os'; import { tmpdir } from 'node:os';
import path from 'node:path'; import path, { join } from 'node:path';
import { homedir } from 'node:os';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
const HERE = path.dirname(fileURLToPath(import.meta.url)); const HERE = path.dirname(fileURLToPath(import.meta.url));
@ -21,11 +22,18 @@ const GO = 'docs/plans/testnet-go.md';
const PACK = 'docs/plans/launch-pack.md'; const PACK = 'docs/plans/launch-pack.md';
const INCOME = 'docs/analysis/income-tiers.md'; const INCOME = 'docs/analysis/income-tiers.md';
// the private founder list (~/.config/igneum/founder-strings or $IGNEUM_FOUNDER_STRINGS; perl regex, a tab, a sample per row; # comments);
// absent here: no founder patterns from this reader (the Mac's pre-push hook carries the file and is the guard)
function founderPatternsFromFile() {
try {
const f = process.env.IGNEUM_FOUNDER_STRINGS || join(homedir(), '.config', 'igneum', 'founder-strings');
return readFileSync(f, 'utf8').split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')).map((l) => new RegExp(l.split('\t')[0], 'i'));
} catch { return []; }
}
function patterns(root) { function patterns(root) {
const read = f => { try { return readFileSync(path.join(root, f), 'utf8'); } catch { return ''; } }; const read = f => { try { return readFileSync(path.join(root, f), 'utf8'); } catch { return ''; } };
const lines = [...read('site/forbidden-strings.txt').split('\n'), ...read('tools/ci/forbidden-strings.txt').split('\n')]; const lines = [...read('site/forbidden-strings.txt').split('\n'), ...read('tools/ci/forbidden-strings.txt').split('\n')];
return lines.map(l => l.trim()).filter(l => l && !l.startsWith('#')) return lines.map(l => l.trim()).filter(l => l && !l.startsWith('#')).map(l => new RegExp(l)).concat(root === process.cwd() || root === ROOT ? founderPatternsFromFile() : []); // plus the private founder list for the real tree
.map(l => (l.startsWith('b64:') ? new RegExp(Buffer.from(l.slice(4), 'base64').toString('utf8'), 'i') : new RegExp(l))); // b64: = an encoded founder pattern
} }
export function gateRows(text) { export function gateRows(text) {
@ -100,7 +108,7 @@ function selfTest() {
const fx = mkdtempSync(path.join(tmpdir(), 'launch-gates-')); const fx = mkdtempSync(path.join(tmpdir(), 'launch-gates-'));
try { try {
for (const d of ['docs/plans', 'docs/analysis', 'site', 'tools/ci', 'tools/launch']) mkdirSync(path.join(fx, d), { recursive: true }); for (const d of ['docs/plans', 'docs/analysis', 'site', 'tools/ci', 'tools/launch']) mkdirSync(path.join(fx, d), { recursive: true });
writeFileSync(path.join(fx, 'site/forbidden-strings.txt'), 'b64:XGJmb3VuZGVybmFtZVxi\n'); // an encoded, case-insensitive pattern for a made-up name writeFileSync(path.join(fx, 'site/forbidden-strings.txt'), '(?i)\\bfoundername\\b\n'.replace('(?i)', '')); // a made-up name as a plain pattern (the private list is not read for a fixture root)
writeFileSync(path.join(fx, 'tools/ci/forbidden-strings.txt'), '/Users/\n'); writeFileSync(path.join(fx, 'tools/ci/forbidden-strings.txt'), '/Users/\n');
writeFileSync(path.join(fx, 'tools/launch/x.mjs'), ''); writeFileSync(path.join(fx, 'tools/launch/x.mjs'), '');
const sentences = Array.from({ length: 8 }, (_, i) => `${i + 1}. sentence (8.3 sentence ${i + 1})`).join('\n'); const sentences = Array.from({ length: 8 }, (_, i) => `${i + 1}. sentence (8.3 sentence ${i + 1})`).join('\n');
@ -117,8 +125,8 @@ function selfTest() {
r = run(fx); if (!r.problems.some(p => /does not exist/.test(p))) throw new Error('self-test: a check naming a missing script passed'); r = run(fx); if (!r.problems.some(p => /does not exist/.test(p))) throw new Error('self-test: a check naming a missing script passed');
writeFileSync(path.join(fx, GO), good); writeFileSync(path.join(fx, GO), good);
// the founder's name in the handoff, an em dash, a missing sentence // the founder's name in the handoff, an em dash, a missing sentence
writeFileSync(path.join(fx, PACK), goodPack.replace('1. sentence', '1. Foundername says')); writeFileSync(path.join(fx, PACK), goodPack.replace('1. sentence', '1. foundername says'));
r = run(fx); if (!r.problems.some(p => /forbidden pattern .*foundername/i.test(p))) throw new Error('self-test: the founder\'s name in the handoff passed (the encoded pattern did not match case-insensitively)'); r = run(fx); if (!r.problems.some(p => /forbidden pattern .*foundername/i.test(p))) throw new Error('self-test: the founder\'s name in the handoff passed');
writeFileSync(path.join(fx, PACK), goodPack.replace('2. sentence', '2. a \u2014 dash')); writeFileSync(path.join(fx, PACK), goodPack.replace('2. sentence', '2. a \u2014 dash'));
r = run(fx); if (!r.problems.some(p => /em dash/.test(p))) throw new Error('self-test: an em dash in the handoff passed'); r = run(fx); if (!r.problems.some(p => /em dash/.test(p))) throw new Error('self-test: an em dash in the handoff passed');
writeFileSync(path.join(fx, PACK), goodPack.replace('(8.3 sentence 5)', '')); writeFileSync(path.join(fx, PACK), goodPack.replace('(8.3 sentence 5)', ''));

View file

@ -83,6 +83,8 @@ never_push_checks() {
# the third never-push class (7 October 2026, 20:5x UK): a founder name on ANY branch, because every branch went to the public host's # the third never-push class (7 October 2026, 20:5x UK): a founder name on ANY branch, because every branch went to the public host's
# mirror and a branch green-stamped before the check existed carried one onto master through the deferred merge # mirror and a branch green-stamped before the check existed carried one onto master through the deferred merge
run "no founder name, personal login, earlier business or personal address in any tracked text file (the pre-public scrub; self-test first, encoded list)" bash -c 'bash tools/ci/founder-strings-check.sh --self-test && bash tools/ci/founder-strings-check.sh' run "no founder name, personal login, earlier business or personal address in any tracked text file (the pre-public scrub; self-test first, encoded list)" bash -c 'bash tools/ci/founder-strings-check.sh --self-test && bash tools/ci/founder-strings-check.sh'
# rule 15 (7 October 2026): a release branch reads its own version in Cargo.toml, Cargo.lock and version.h from its first commit
run "release-version: a release-0.3.N branch reads 0.3.N in Cargo.toml, Cargo.lock and version.h (self-test first)" bash -c 'bash tools/ci/release-version-check.sh --self-test && bash tools/ci/release-version-check.sh'
} }
tree_checks() { tree_checks() {

View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Rule 15 (7 October 2026, after the 0.3.23 miss): the version bump is a release branch's first commit. release-0.3.23 opened
# at 4cdcab31 and carried 0.3.22 in Cargo.toml, Cargo.lock and app/windows/version.h until 21:26 BST, so the app exes and the
# window host crossed from its first closed tip read 0.3.22 and were void. On a push to release-0.3.N this check reads the
# six version places (Cargo.toml, Cargo.lock, version.h, igneum-app.rc's four fields, Info.plist, the installer's AppVersion) against the branch name and goes red on a mismatch; a branch that is not release-* passes.
#
# tools/ci/release-version-check.sh [<branch>] # the current branch when omitted; exit 1 with the mismatch named
# tools/ci/release-version-check.sh --self-test # a release-0.3.23 tree reading 0.3.22 fails on every place; a matching tree passes; a feature branch passes
set -euo pipefail
cd "$(dirname "$0")/../.."
check() { # <branch> <cargo toml> <cargo lock> <version.h> [<igneum-app.rc> <Info.plist> <Igneum-Miner.iss>] -> prints each mismatch, exit 1 if any
local branch="$1" toml="$2" lock="$3" vh="$4" rc="${5:-}" plist="${6:-}" iss="${7:-}" want bad=0
case "$branch" in release-*) want="${branch#release-}" ;; *) echo "release-version: $branch is not a release branch; nothing to check"; return 0 ;; esac
case "$want" in *.*.*) ;; *) echo "release-version: $branch is not a three-part version (rule 1)"; return 1 ;; esac
local got_toml got_lock got_h got_rc
got_toml=$(awk -F'"' '/^version = "/{print $2; exit}' "$toml")
got_lock=$(awk '/^name = "igneum-app"$/{f=1;next} f&&/^version = /{gsub(/"/,"",$3); print $3; exit}' "$lock")
got_h=$(awk -F'"' '/IGNEUM_HOST_VERSION_STR/{print $2; exit}' "$vh")
got_rc=$(awk '/IGNEUM_HOST_VERSION_RC/{print $3; exit}' "$vh" | tr -d ' ')
[ "$got_toml" = "$want" ] || { echo "release-version: $toml reads $got_toml, the branch is $branch"; bad=1; }
[ "$got_lock" = "$want" ] || { echo "release-version: $lock reads $got_lock for igneum-app, the branch is $branch"; bad=1; }
[ "$got_h" = "$want" ] || { echo "release-version: $vh IGNEUM_HOST_VERSION_STR reads $got_h, the branch is $branch"; bad=1; }
[ "$got_rc" = "$(echo "$want" | tr . ,),0" ] || { echo "release-version: $vh IGNEUM_HOST_VERSION_RC reads $got_rc, want $(echo "$want" | tr . ,),0"; bad=1; }
if [ -n "$rc" ]; then
local n; n=$(grep -cE "^(FILEVERSION|PRODUCTVERSION) +$(echo "$want" | tr . ,),0\$|VALUE \"(FileVersion|ProductVersion)\", +\"$want\"" "$rc" || true)
[ "$n" = 4 ] || { echo "release-version: $rc carries $n of 4 version fields at $want (build.rs refuses the cross otherwise)"; bad=1; }
grep -qF "<string>$want</string>" "$plist" || { echo "release-version: $plist does not read $want"; bad=1; }
grep -qF "#define AppVersion \"$want\"" "$iss" || { echo "release-version: $iss AppVersion does not read $want"; bad=1; }
fi
[ $bad = 0 ] && echo "release-version: $branch reads $want in Cargo.toml, Cargo.lock, version.h${rc:+, igneum-app.rc, Info.plist and the installer}"
return $bad
}
if [ "${1:-}" = "--self-test" ]; then
T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
printf '[package]\nname = "igneum-app"\nversion = "0.3.22"\n' > "$T/Cargo.toml"
printf '[[package]]\nname = "igneum-app"\nversion = "0.3.22"\n' > "$T/Cargo.lock"
printf '#define IGNEUM_HOST_VERSION_STR "0.3.22"\n#define IGNEUM_HOST_VERSION_RC 0,3,22,0\n' > "$T/version.h"
out=$(check release-0.3.23 "$T/Cargo.toml" "$T/Cargo.lock" "$T/version.h" 2>&1 || true)
[ "$(printf '%s\n' "$out" | grep -cF ' reads 0.3.22')" = 3 ] && printf '%s\n' "$out" | grep -q 'VERSION_RC reads 0,3,22,0' || { echo "self-test: the 0.3.23 miss was not caught on every place"; printf '%s\n' "$out"; exit 1; }
sed -i.bak 's/0\.3\.22/0.3.23/g; s/0,3,22,0/0,3,23,0/' "$T/Cargo.toml" "$T/Cargo.lock" "$T/version.h"
check release-0.3.23 "$T/Cargo.toml" "$T/Cargo.lock" "$T/version.h" >/dev/null || { echo "self-test: a matching tree failed"; exit 1; }
check driver-check "$T/Cargo.toml" "$T/Cargo.lock" "$T/version.h" >/dev/null || { echo "self-test: a feature branch failed"; exit 1; }
echo "self-test passed: the 0.3.23 shape fails on every place, a matching release tree passes, a feature branch passes"; exit 0
fi
BRANCH="${1:-$(git rev-parse --abbrev-ref HEAD)}"
check "$BRANCH" app/igneum-app/Cargo.toml app/igneum-app/Cargo.lock app/windows/version.h app/igneum-app/resources/igneum-app.rc packaging/mac/app/Info.plist packaging/windows/Igneum-Miner.iss

View file

@ -49,12 +49,18 @@ const STATE_FILE_LIVE = process.env.IGNEUM_DISCORD_STATE || path.join(os.homedir
// ---------- guards ---------- // ---------- guards ----------
// Forbidden in any post: the founder's name and logins, rented-box and build hosts, machine ids, internal paths, IP addresses, // Forbidden in any post: the founder's name and logins, rented-box and build hosts, machine ids, internal paths, IP addresses,
// a standalone 32-hex token (a sha256 is 64 hex and passes), any dl.igneum.network path outside /public/, any mention. // a standalone 32-hex token (a sha256 is 64 hex and passes), any dl.igneum.network path outside /public/, any mention.
// The founder's name, logins and the earlier businesses come from tools/ci/founder-strings.b64 (base64, so no tracked file // The founder's name, logins and the earlier businesses come from the PRIVATE list (never a tracked file in any encoding: a base64
// spells them; the first three decoded patterns are the founder, the rest the earlier businesses). fs is read once at load. // copy in the tree was a disclosure on the public host, 7 October 2026, 21:3x UK): $IGNEUM_FOUNDER_STRINGS, else
const FOUNDER_LIST = path.join(HERE, '..', 'ci', 'founder-strings.b64'); // ~/.config/igneum/founder-strings (the Mac), else /srv/discord-hooks/founder-strings (build-1, beside the webhook env). One row per
export function founderPatterns(file = FOUNDER_LIST) { // pattern: perl regex, a tab, a sample. Absent everywhere: no founder rows (the host that posts carries the file).
const rows = Buffer.from(fs.readFileSync(file, 'utf8'), 'base64').toString('utf8').split('\n').filter((l) => l.trim() && !l.startsWith('#')).map((l) => l.split('\t')); export function founderListPath(env = process.env) {
return rows.map(([re, sample], i) => ({ re: new RegExp(re, 'i'), sample, why: i < 3 ? "the founder's name or login" : i < 8 ? 'an earlier business' : "the founder's address" })); for (const f of [env.IGNEUM_FOUNDER_STRINGS, path.join(os.homedir(), '.config', 'igneum', 'founder-strings'), '/srv/discord-hooks/founder-strings']) if (f && fs.existsSync(f)) return f;
return '';
}
export function founderPatterns(file = founderListPath()) {
if (!file) return [];
const rows = fs.readFileSync(file, 'utf8').split('\n').filter((l) => l.trim() && !l.startsWith('#')).map((l) => l.split('\t'));
return rows.map(([re, sample], i) => ({ re: new RegExp(re, 'i'), sample, why: i < 3 ? "the founder's name or login" : i < 8 ? 'an earlier business' : i === 8 ? "the founder's address" : 'the login before its rename' }));
} }
export const FORBIDDEN = [ export const FORBIDDEN = [
...founderPatterns().map(({ re, why }) => ({ re, why })), ...founderPatterns().map(({ re, why }) => ({ re, why })),

View file

@ -7,11 +7,14 @@ import fs from 'node:fs';
import os from 'node:os'; import os from 'node:os';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { // the founder guard reads a private list; the test writes a FIXTURE list of made-up names and points the module at it before loading
import { mkdtempSync as _mkd, writeFileSync as _wf } from 'node:fs'; import { tmpdir as _tmp } from 'node:os'; import { join as _join } from 'node:path';
{ const d = _mkd(_join(_tmp(), 'founder-fixture-')); _wf(_join(d, 'list'), '\\bfoundername\\b\tFoundername\n\\bsurnamex\\b\tSurnamex\n\\bloginx\\b\tloginx\n\\bbiz1\\b\tbiz1\n\\bbiz2\\b\tbiz2\n\\bbiz3\\b\tbiz3\n\\bbiz4\\b\tbiz4\n\\bbiz5\\b\tbiz5\n\\bmail@x\\.y\\b\tmail@x.y\n\\boldlogin\\b\toldlogin\n'); process.env.IGNEUM_FOUNDER_STRINGS = _join(d, 'list'); }
const {
shapePulse, shapeDigest, shapeWeekly, shapeRelease, shapeIncidentOpen, shapeIncidentResolve, changedLinesFromPlan, plainLine, shapePulse, shapeDigest, shapeWeekly, shapeRelease, shapeIncidentOpen, shapeIncidentResolve, changedLinesFromPlan, plainLine,
guardText, guardPayload, embed, embedLength, LIMITS, snapshot, devnet2Line, versionShare, watchPass, WATCH, WATCH_CONDITIONS, guardText, guardPayload, embed, embedLength, LIMITS, snapshot, devnet2Line, versionShare, watchPass, WATCH, WATCH_CONDITIONS,
Poster, postWebhook, dueNow, ukStamp, londonParts, fmtHash, fmtDur, fmtDelta, fmtChangePct, renderPreview, ICON, EMBER, Poster, postWebhook, dueNow, ukStamp, londonParts, fmtHash, fmtDur, fmtDelta, fmtChangePct, renderPreview, ICON, EMBER,
shapeFeed, feedMilestones, shapeMilestone, hashOriginLine, runFeed, FEED_MILESTONES, founderPatterns } from './discord-hooks.mjs'; shapeFeed, feedMilestones, shapeMilestone, hashOriginLine, runFeed, FEED_MILESTONES, founderPatterns } = await import('./discord-hooks.mjs');
const HERE = path.dirname(fileURLToPath(import.meta.url)); const HERE = path.dirname(fileURLToPath(import.meta.url));
const fx = name => JSON.parse(fs.readFileSync(path.join(HERE, 'fixtures', 'discord', `${name}.json`), 'utf8')); const fx = name => JSON.parse(fs.readFileSync(path.join(HERE, 'fixtures', 'discord', `${name}.json`), 'utf8'));

View file

@ -27,7 +27,9 @@ set -euo pipefail
export TZ=UTC export TZ=UTC
HERE="$(cd "$(dirname "$0")" && pwd)" HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)" ROOT="$(cd "$HERE/../.." && pwd)"
STANDING_LOGIN="${IGNEUM_STANDING_LOGIN:-$(base64 -d < "$(cd "$(dirname "$0")/../.." && pwd)/tools/ci/founder-strings.b64" | grep -vE '^#' | sed -n '10p' | cut -f2)}" # the login's pre-rename spelling, from the encoded list (no tracked file spells it) FOUNDER_LIST="${IGNEUM_FOUNDER_STRINGS:-$HOME/.config/igneum/founder-strings}" # the PRIVATE list (perl regex, tab, sample per row); never a tracked file in any encoding
[ -s "$FOUNDER_LIST" ] || { echo "fresh-repo: no private founder list at $FOUNDER_LIST (the Mac holds it; the rewrite needs its rows)" >&2; exit 1; }
STANDING_LOGIN="${IGNEUM_STANDING_LOGIN:-$(grep -vE '^#' "$FOUNDER_LIST" | sed -n '10p' | cut -f2)}" # row 10: the login's pre-rename spelling
ORG="igneum-network" ORG="igneum-network"
SOURCE="" WORK="" NEW_REPO="$ORG/igneum-core" NEW_LOGIN="" PUBLIC_CLAUDE="" CLEAN=0 SOURCE="" WORK="" NEW_REPO="$ORG/igneum-core" NEW_LOGIN="" PUBLIC_CLAUDE="" CLEAN=0
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
@ -90,14 +92,14 @@ PERSONAL_EMAILS="$(printf '%s\n' "$PERSONAL_PAIRS" | awk -F'|' 'NF==2{print $2}'
PERSONAL_NAMES="$(printf '%s\n' "$PERSONAL_PAIRS" | awk -F'|' 'NF==2 && $1 ~ / /{print $1}' | grep -v -E "^($STANDING_LOGIN|$TARGET_LOGIN)$" | sort -u || true)" # a name has a space; a login is not a name PERSONAL_NAMES="$(printf '%s\n' "$PERSONAL_PAIRS" | awk -F'|' 'NF==2 && $1 ~ / /{print $1}' | grep -v -E "^($STANDING_LOGIN|$TARGET_LOGIN)$" | sort -u || true)" # a name has a space; a login is not a name
# the founder's first name, surname and second login from the encoded list (rows 1 to 3, sample column), so the rules never depend on # the founder's first name, surname and second login from the encoded list (rows 1 to 3, sample column), so the rules never depend on
# which commits a given mirror carries (7 Oct 2026: build-1's mirror holds none of the 40 personal-identity commits) # which commits a given mirror carries (7 Oct 2026: build-1's mirror holds none of the 40 personal-identity commits)
LIST_ROWS="$(base64 -d < "$ROOT/tools/ci/founder-strings.b64" | grep -vE '^#')" LIST_ROWS="$(grep -vE '^#' "$FOUNDER_LIST")"
LIST_FIRST="$(printf '%s\n' "$LIST_ROWS" | sed -n '1p' | cut -f2 | awk '{print $1}')"; LIST_LAST="$(printf '%s\n' "$LIST_ROWS" | sed -n '2p' | cut -f2)"; LIST_SECOND="$(printf '%s\n' "$LIST_ROWS" | sed -n '3p' | cut -f2)" LIST_FIRST="$(printf '%s\n' "$LIST_ROWS" | sed -n '1p' | cut -f2 | awk '{print $1}')"; LIST_LAST="$(printf '%s\n' "$LIST_ROWS" | sed -n '2p' | cut -f2)"; LIST_SECOND="$(printf '%s\n' "$LIST_ROWS" | sed -n '3p' | cut -f2)"
FIRST_NAMES="$( { printf '%s\n' "$PERSONAL_NAMES" | awk 'NF>=2{print $1}'; printf '%s\n' "$LIST_FIRST"; } | grep . | sort -u)" FIRST_NAMES="$( { printf '%s\n' "$PERSONAL_NAMES" | awk 'NF>=2{print $1}'; printf '%s\n' "$LIST_FIRST"; } | grep . | sort -u)"
LAST_NAMES="$( { printf '%s\n' "$PERSONAL_NAMES" | awk 'NF>=2{print $NF}'; printf '%s\n' "$LIST_LAST"; } | grep . | sort -u)" LAST_NAMES="$( { printf '%s\n' "$PERSONAL_NAMES" | awk 'NF>=2{print $NF}'; printf '%s\n' "$LIST_LAST"; } | grep . | sort -u)"
SECOND_LOGINS="$( { printf '%s\n' "$PERSONAL_EMAILS" | sed -nE 's/^[0-9]+\+([A-Za-z0-9-]+)@users\.noreply\.github\.com$/\1/p'; printf '%s\n' "$LIST_SECOND"; } | grep . | grep -v -E "^($STANDING_LOGIN|$TARGET_LOGIN)$" | sort -u || true)" SECOND_LOGINS="$( { printf '%s\n' "$PERSONAL_EMAILS" | sed -nE 's/^[0-9]+\+([A-Za-z0-9-]+)@users\.noreply\.github\.com$/\1/p'; printf '%s\n' "$LIST_SECOND"; } | grep . | grep -v -E "^($STANDING_LOGIN|$TARGET_LOGIN)$" | sort -u || true)"
# the other businesses named in the plan (brand names, not people), from the encoded list tools/ci/founder-strings.b64 (rows 4 to 8; # the other businesses named in the plan (brand names, not people), from the encoded list tools/ci/founder-strings.b64 (rows 4 to 8;
# no tracked file spells them: the founder-strings check reads every tracked file) # no tracked file spells them: the founder-strings check reads every tracked file)
OTHER_BUSINESSES="$(base64 -d < "$ROOT/tools/ci/founder-strings.b64" | grep -vE '^#' | sed -n '4,8p' | cut -f1 | sed -E 's/\\b//g' | paste -sd'|' -)" OTHER_BUSINESSES="$(grep -vE '^#' "$FOUNDER_LIST" | sed -n '4,8p' | cut -f1 | sed -E 's/\\b//g' | paste -sd'|' -)"
[ -n "$OTHER_BUSINESSES" ] || { echo "fresh-repo: the encoded founder list decoded to no business names" >&2; exit 1; } [ -n "$OTHER_BUSINESSES" ] || { echo "fresh-repo: the encoded founder list decoded to no business names" >&2; exit 1; }
# the secrets: whichever of the four files exist, plus every dated copy the rotation left behind # the secrets: whichever of the four files exist, plus every dated copy the rotation left behind
# (log-intake-key.old-<date>, dl-token.old-<date>: rotation phase 2 section 5 step 2 renames the .next files to the # (log-intake-key.old-<date>, dl-token.old-<date>: rotation phase 2 section 5 step 2 renames the .next files to the
@ -157,6 +159,12 @@ while IFS= read -r login; do
printf '\\b%s\\b\n' "$login" >> "$IDENT" printf '\\b%s\\b\n' "$login" >> "$IDENT"
done <<< "$SECOND_LOGINS" done <<< "$SECOND_LOGINS"
printf 'regex:(?i)\\b(%s)\\b==>[other-business]\n' "$OTHER_BUSINESSES" >> "$REPLACE" printf 'regex:(?i)\\b(%s)\\b==>[other-business]\n' "$OTHER_BUSINESSES" >> "$REPLACE"
# the encoded forms: the base64 of every list regex (site/forbidden-strings.txt carried them as b64: lines until 21:3x UK on 7 October 2026)
while IFS= read -r re; do
[ -n "$re" ] || continue; b="$(printf '%s' "$re" | base64 | tr -d '\n')"
printf 'literal:%s==>[encoded-pattern-removed]\n' "$b" >> "$REPLACE"
printf '%s\n' "$b" | sed 's/[][\\.*^$?+(){}|/]/\\&/g' >> "$IDENT"
done < <(printf '%s\n' "$LIST_ROWS" | cut -f1)
printf '\\b(%s)\\b\n' "$OTHER_BUSINESSES" >> "$IDENT" printf '\\b(%s)\\b\n' "$OTHER_BUSINESSES" >> "$IDENT"
say "rules: $(grep -c . "$REPLACE") replace lines, $(grep -c . "$MAILMAP") mailmap lines, $(grep -c . "$IDENT") identity patterns, $(grep -c . "$SECRETS") secret patterns (files under $RULES, removed at exit)" say "rules: $(grep -c . "$REPLACE") replace lines, $(grep -c . "$MAILMAP") mailmap lines, $(grep -c . "$IDENT") identity patterns, $(grep -c . "$SECRETS") secret patterns (files under $RULES, removed at exit)"
@ -169,7 +177,7 @@ scan_blobs() {
| perl -ne 'BEGIN { open(P, "<", shift) or die; @p = map { chomp; qr/$_/i } grep { /\S/ } <P>; $n = 0 } for my $p (@p) { if ($_ =~ $p) { $n++; last } } END { print "$n\n" }' "$1" | perl -ne 'BEGIN { open(P, "<", shift) or die; @p = map { chomp; qr/$_/i } grep { /\S/ } <P>; $n = 0 } for my $p (@p) { if ($_ =~ $p) { $n++; last } } END { print "$n\n" }' "$1"
} }
scan_meta() { git log --all --format='%an%n%ae%n%cn%n%ce%n%s%n%b' | perl -ne 'BEGIN { open(P, "<", shift) or die; @p = map { chomp; qr/$_/i } grep { /\S/ } <P>; $n = 0 } for my $p (@p) { if ($_ =~ $p) { $n++; last } } END { print "$n\n" }' "$1"; } scan_meta() { git log --all --format='%an%n%ae%n%cn%n%ce%n%s%n%b' | perl -ne 'BEGIN { open(P, "<", shift) or die; @p = map { chomp; qr/$_/i } grep { /\S/ } <P>; $n = 0 } for my $p (@p) { if ($_ =~ $p) { $n++; last } } END { print "$n\n" }' "$1"; }
DROPPED=(docs/review) # the ledger, its fixes file and the ledger page are published with the repository (decision of 5 October 2026, docs/fud-fixes.md section 5); the review folder stays internal DROPPED=(docs/review tools/ci/founder-strings.b64) # the encoded founder list (19:5x to 21:3x UK, 7 October 2026) leaves every commit # the ledger, its fixes file and the ledger page are published with the repository (decision of 5 October 2026, docs/fud-fixes.md section 5); the review folder stays internal
counts() { # <label> counts() { # <label>
local label="$1" local label="$1"
say "" say ""