diff --git a/site/forbidden-strings.txt b/site/forbidden-strings.txt index 899d1506..ac88ce39 100644 --- a/site/forbidden-strings.txt +++ b/site/forbidden-strings.txt @@ -13,16 +13,9 @@ intake[_-]?key Tailscale tailscale 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 -# 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) -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] -b64:[encoded-pattern-removed] +# 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 +# 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, +# 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. Hetzner igneum-seed /root/ diff --git a/site/scrub.mjs b/site/scrub.mjs index 9faab0bd..73cf152d 100644 --- a/site/scrub.mjs +++ b/site/scrub.mjs @@ -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 // site/forbidden-strings.txt survives, so a private name, host or address can never reach the page. import { readFileSync } from 'node:fs'; +import { homedir } from 'node:os'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); @@ -59,13 +60,21 @@ const RULES = [ [/\(local time, UTC\+1\)/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) { 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('#')) - .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 = []; - out.split('\n').forEach((line, i) => { for (const p of pats) if (p.test(line)) { hits.push(`${i + 1}: ${p.source.startsWith('(? { 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')}`); return out; } diff --git a/tools/ci/founder-strings-check.sh b/tools/ci/founder-strings-check.sh index 1b507bb7..ecdce35b 100755 --- a/tools/ci/founder-strings-check.sh +++ b/tools/ci/founder-strings-check.sh @@ -1,37 +1,49 @@ #!/usr/bin/env bash -# No founder name, personal login, earlier business or personal address in any tracked text file (the pre-public scrub, -# 7 October 2026, main's item (4): forbidden strings over tracked files on every merge, known-failed first). The identity -# check (tools/ci/identity-check.sh) reads the public EXPORT list; this one reads EVERY tracked file, because the repository -# itself goes public at the testnet (docs/fud-fixes.md section 5). +# No founder name, personal login, earlier business or personal address in any tracked text file, in plain text OR in an encoding +# (the pre-public scrub, 7 October 2026; a never-push class since 20:5x UK: every push, every branch). The identity check +# (tools/ci/identity-check.sh) reads the public EXPORT list; this one reads EVERY tracked file, because the repository itself +# 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 -# base64-encoded in tools/ci/founder-strings.b64 (one decoded line per pattern: perl regex, a tab, a sample the self-test plants; -# case-insensitive; # comments ignored) -# 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, -# 7 October 2026: the public tree names igneum-labs only); the fresh-repository step rewrites it in the history (tools/repo/fresh-repo.sh). +# The patterns are NOT in the repository in any form. They live in a private file, ~/.config/igneum/founder-strings +# ($IGNEUM_FOUNDER_STRINGS overrides; one row per pattern: perl regex, a tab, a sample the self-test plants; # comments), on the +# 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 +# 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 +# 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 -# tools/ci/founder-strings-check.sh --self-test # a fixture tree with one hit per pattern class fails and names the file; a clean -# # fixture passes; the encoded list decodes to at least five patterns +# Two passes over every tracked text file: the plain text, then every encoded blob decoded and scanned (base64 literals of 24 +# characters or more, every *.b64 file whole, hex literals of 24 characters or more), so an encoding never hides a term again. +# +# 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 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)}" -decode() { # [samples]: the regexes (or, with "samples", the sample per regex), one per line, into a 0600 file whose name is printed - local f col=1; [ "${1:-}" = samples ] && col=2 - f="$(mktemp)"; chmod 600 "$f" - base64 -d < "$LIST" | grep -vE '^\s*(#|$)' | cut -f"$col" > "$f" - echo "$f" -} -scan() { # : 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' \ +rows() { grep -vE '^\s*(#|$)' "$LIST"; } # the live rows +scan() { # : plain pass, then the decoded pass; prints "file:line:text" or "file:line:(decoded ) text"; exit 1 on any hit + local repo="$1" list="$2" pats hits + pats="$(mktemp)"; chmod 600 "$pats"; grep -vE '^\s*(#|$)' "$list" | cut -f1 > "$pats" + hits="$(cd "$repo" && git ls-files -z -- . ':!*.png' ':!*.jpg' ':!*.jpeg' ':!*.gif' ':!*.ico' ':!*.woff' ':!*.woff2' ':!*.ttf' ':!*.pdf' ':!*.zip' ':!*.bin' \ | 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; - for my $f (@ARGV) { next unless -f $f && -T $f; open(my $h, "<", $f) or next; my $n = 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; } + sub hit { my ($t) = @_; for my $p (@pats) { return 1 if $t =~ $p } 0 } + 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)" rm -f "$pats" if [ -n "$hits" ]; then printf '%s\n' "$hits" | sed 's/^/founder-strings: /' >&2; return 1; fi @@ -39,28 +51,34 @@ scan() { # : every tracked text file against the decoded list; prints fi } if [ "${1:-}" = "--self-test" ]; then - n="$(base64 -d < "$LIST" | grep -vcE '^\s*(#|$)')" - [ "$n" -ge 5 ] || { echo "self-test failed: the encoded list decodes to $n pattern(s), expected at least 5"; exit 1; } - fx="$(mktemp -d)"; trap 'rm -rf "$fx"' EXIT - ( cd "$fx" && git init -q -b master . ) - mkdir -p "$fx/docs" - # a clean tree: the standing login, the project, a neutral owner word + top="$(mktemp -d)"; trap 'rm -rf "$top"' EXIT; fx="$top/repo"; mkdir -p "$fx" + fixture="$top/list"; printf '# fixture\n\\bfoundername\\b\tfoundername\n\\bsurnamex\\b\tSurnamex\n\\bbiznamez\\b\tbiznamez\n' > "$fixture" + ( cd "$fx" && git init -q -b master . ); mkdir -p "$fx/docs" "$fx/tools/ci" "$fx/site" 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 ) - FOUNDER_CHECK_REPO="$fx" bash "$0" >/dev/null 2>&1 || { echo "self-test failed: a clean tree was reported"; exit 1; } - # one hit per pattern class, each from the encoded list's own sample column, so this script never spells them; every hit - # must name its file - samples="$(decode samples)"; i=0; fails=0 - while IFS= read -r word; do - i=$((i + 1)); [ -n "$word" ] || continue - printf 'a line that names %s in passing\n' "$word" > "$fx/docs/hit-$i.md" - ( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "h$i" ) - if out="$(FOUNDER_CHECK_REPO="$fx" bash "$0" 2>&1)"; then echo "self-test failed: pattern $i was not caught"; fails=1 - 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 - 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" ) - done < "$samples"; rm -f "$samples" - # a binary file carrying a pattern is not read (images are not text) - [ "$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" + fails=0 + scan "$fx" "$fixture" >/dev/null 2>&1 || { echo "self-test failed: a clean tree (with a harmless .b64) was reported"; fails=1; } + i=0 + while IFS=$'\t' read -r re sample; do + i=$((i + 1)); [ -n "$sample" ] || continue + for kind in plain b64file base64 hex; do + case "$kind" in + plain) f="docs/hit-$i.md"; printf 'a line that names %s in passing\n' "$sample" > "$fx/$f" ;; + b64file) f="tools/ci/hit-$i.b64"; printf 'a line that names %s in passing\n' "$sample" | base64 > "$fx/$f" ;; + 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" ;; + 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" ;; + esac + ( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "h" ) + 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 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)" diff --git a/tools/ci/launch-gates-check.mjs b/tools/ci/launch-gates-check.mjs index 19106610..3f99dcf2 100644 --- a/tools/ci/launch-gates-check.mjs +++ b/tools/ci/launch-gates-check.mjs @@ -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 import { existsSync, readFileSync, mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs'; 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'; 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 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) { 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')]; - return lines.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))); // b64: = an encoded founder pattern + 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 } export function gateRows(text) { @@ -100,7 +108,7 @@ function selfTest() { const fx = mkdtempSync(path.join(tmpdir(), 'launch-gates-')); try { 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/launch/x.mjs'), ''); 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'); writeFileSync(path.join(fx, GO), good); // 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')); - 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)'); + 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'); 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'); writeFileSync(path.join(fx, PACK), goodPack.replace('(8.3 sentence 5)', '')); diff --git a/tools/community/discord-hooks.mjs b/tools/community/discord-hooks.mjs index 678e339a..936a2292 100755 --- a/tools/community/discord-hooks.mjs +++ b/tools/community/discord-hooks.mjs @@ -49,12 +49,18 @@ const STATE_FILE_LIVE = process.env.IGNEUM_DISCORD_STATE || path.join(os.homedir // ---------- guards ---------- // 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. -// The founder's name, logins and the earlier businesses come from tools/ci/founder-strings.b64 (base64, so no tracked file -// spells them; the first three decoded patterns are the founder, the rest the earlier businesses). fs is read once at load. -const FOUNDER_LIST = path.join(HERE, '..', 'ci', 'founder-strings.b64'); -export function founderPatterns(file = FOUNDER_LIST) { - const rows = Buffer.from(fs.readFileSync(file, 'utf8'), 'base64').toString('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' : "the founder's address" })); +// The founder's name, logins and the earlier businesses come from the PRIVATE list (never a tracked file in any encoding: a base64 +// copy in the tree was a disclosure on the public host, 7 October 2026, 21:3x UK): $IGNEUM_FOUNDER_STRINGS, else +// ~/.config/igneum/founder-strings (the Mac), else /srv/discord-hooks/founder-strings (build-1, beside the webhook env). One row per +// pattern: perl regex, a tab, a sample. Absent everywhere: no founder rows (the host that posts carries the file). +export function founderListPath(env = process.env) { + 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 = [ ...founderPatterns().map(({ re, why }) => ({ re, why })), diff --git a/tools/community/discord-hooks.test.mjs b/tools/community/discord-hooks.test.mjs index d82cd8a2..ac7b85ac 100644 --- a/tools/community/discord-hooks.test.mjs +++ b/tools/community/discord-hooks.test.mjs @@ -7,11 +7,14 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; 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, guardText, guardPayload, embed, embedLength, LIMITS, snapshot, devnet2Line, versionShare, watchPass, WATCH, WATCH_CONDITIONS, 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 fx = name => JSON.parse(fs.readFileSync(path.join(HERE, 'fixtures', 'discord', `${name}.json`), 'utf8')); diff --git a/tools/repo/fresh-repo.sh b/tools/repo/fresh-repo.sh index 22ad54be..72d11167 100755 --- a/tools/repo/fresh-repo.sh +++ b/tools/repo/fresh-repo.sh @@ -27,7 +27,9 @@ set -euo pipefail export TZ=UTC HERE="$(cd "$(dirname "$0")" && 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" SOURCE="" WORK="" NEW_REPO="$ORG/igneum-core" NEW_LOGIN="" PUBLIC_CLAUDE="" CLEAN=0 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 # 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) -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)" 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)" 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; # 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; } # the secrets: whichever of the four files exist, plus every dated copy the rotation left behind # (log-intake-key.old-, dl-token.old-: 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" done <<< "$SECOND_LOGINS" 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" 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/ }

; $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/ }

; $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() { #