ISO 1.29.0 source + an English download page (R-559 slice 4, source only)
gates / gates (push) Successful in 23s

THE IMAGE IS NOT BUILT AND NOT PUBLISHED BY THIS COMMIT. Publishing to
iso.felhom.eu is public and irreversible and its runbook requires a proof
install on BOTH menu entries plus the 16-criterion gate run against the exact
uploaded bytes. That is the operator's step. The download pages therefore still
name 1.28.0 - the image that is actually published - and a new site gate
refuses the two pages naming different files or hashes.

Three texts a person meets before any dashboard become bilingual: Hungarian
block first, byte for byte as before, then English, inside the same frame. The
pairing banner, the bound banner, /etc/issue (and the postinst's byte-coupled
copy), plus an English half on the GRUB entries.

The Hungarian is a GOLDEN, not a grep: test/golden/*.hu.txt were captured from
the script at 183727db9c before one English line existed, and the harness
asserts each banner's first N lines are exactly the golden. Red-proofed by one
changed byte, by an "a" planted in the English block, and by an over-wide line.

R-586, found on the way in: running the harness UNCHANGED at the base commit
failed two R-496 checks. The script paints with `>`, which truncates a FILE but
is a no-op on a console device; ISO 1.28.0's new bound banner (c033b3b) paints
straight after the pairing one and wiped it before the check read it. c033b3b
did not touch the harness, and nobody saw it because the harness is in no gate
and no CI run. Fixed with a FIFO; production code untouched. The harness being
ungated is still open.

The release gate's G16 required every Felhom string to be Hungarian and would
have STOPPED this publication. Operator ruling 1b of 2026-09-17 supersedes that
scope, so G16 is rewritten rather than waived: Hungarian FIRST, pinned by the
golden, each secret named once per language.

letoltes.html changes by four lines only. The English link is not in the nav -
the nav is a shared block site_gates.py pins across every page, and the gate
convicted the first attempt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-18 17:40:29 +02:00
parent 183727db9c
commit eb1ae37095
18 changed files with 445 additions and 21 deletions
+84 -2
View File
@@ -26,6 +26,32 @@ FAKE=/work/fakebin; rm -rf "$FAKE"; mkdir -p "$FAKE"
CALLS=/work/curl.log
export PATH="$FAKE:$PATH"
fail=0
# --- console capture (R-586) -------------------------------------------------------------------
#
# THE SCRIPT WRITES EACH BANNER WITH `>`, WHICH TRUNCATES A FILE. On a real console that is a
# character device and truncation is a no-op — every paint simply appears. Pointed at a plain FILE,
# as this harness did until now, each banner ERASES the one before it.
#
# That is not hypothetical: ISO 1.28.0 (commit c033b3b, R-535) added print_bound_banner, which paints
# right after the pairing banner in the same invocation. From that commit onward
# `grep 'Párosító kód' /work/console.out` looked for a banner the NEXT paint had already wiped, and
# the two R-496 checks below have been FAILING ever since. Nobody saw it: this harness is not in CI
# and is not in repo_gates.py, so it runs only when someone runs it.
#
# A FIFO restores the device semantics: opening it with `>` truncates nothing, and a background
# reader accumulates every paint. `console_reset` starts a fresh capture per scenario.
CONSOLE_FIFO=/work/console.fifo
console_reset() {
[ -n "${CONSOLE_CAT_PID:-}" ] && kill "$CONSOLE_CAT_PID" 2>/dev/null
rm -f /work/console.out "$CONSOLE_FIFO"
mkfifo "$CONSOLE_FIFO"
# The reader holds the FIFO open so the script's short-lived `>` opens never block or EOF it.
( while :; do cat "$CONSOLE_FIFO" >> /work/console.out 2>/dev/null || true; done ) &
CONSOLE_CAT_PID=$!
: > /work/console.out
}
console_settle() { sleep 0.2; } # let the reader drain before a check reads the file
say() { echo "TEST: $*"; }
check() { if eval "$2"; then echo " ok: $1"; else echo " FAIL: $1"; fail=1; fi; }
@@ -200,12 +226,68 @@ FELHOM_HUB_URL=https://hub.example
ENV
echo 204 > /work/poll-mode
echo 3 > /work/sleep.flip # after 3 in-script waits the hub "binds" (poll flips to 200)
rm -f /work/console.out
env FELHOM_CONSOLE_DEV=/work/console.out FELHOM_ISSUE_FILE=/work/issue bash "$BSTRAP"; rc=$?
console_reset
env FELHOM_CONSOLE_DEV="$CONSOLE_FIFO" FELHOM_ISSUE_FILE=/work/issue bash "$BSTRAP"; rc=$?
console_settle
# R-496: the pairing banner names the secret the way the self-bind mail and page do (R-323).
check "R-496: banner painted to the console seam" "grep -q 'Párosító kód' /work/console.out"
check "R-496: banner names the Tulajdonosi jelmondat" "grep -q 'Tulajdonosi jelmondat' /work/console.out"
check "R-496: banner no longer says 'jelszavad'" "! grep -q 'jelszavad' /work/console.out"
# ===== R-559 (slice 4): the console is BILINGUAL — Hungarian frozen, English real, both fit 80 cols =====
#
# THE HUNGARIAN IS A GOLDEN, NOT A GREP. /work/golden/*.hu.txt were captured from the script at
# 183727db9c44, before a single English line was added. A grep for one phrase would pass a banner
# whose other nine lines had been reworded; the golden is the whole block, byte for byte.
#
# The golden is the banner WITHOUT its closing frame line, because the English block is appended
# INSIDE the frame and the closing line therefore moves down. So the assertion is: the banner's first
# N lines are exactly the golden, where N is the golden's own length.
split_banners() {
# $1 = the console capture. Writes /work/b.pairing and /work/b.bound, each the whole banner from
# its opening frame line to its closing one inclusive. Frames are counted, not guessed: the
# capture holds exactly two banners and therefore four frame lines.
awk -v out=/work/b '
/^=+$/ { f++
name = (f<=2 ? "pairing" : "bound")
print > (out "." name)
next }
{ if (f==1 || f==3) print > (out "." (f==1 ? "pairing" : "bound")) }
' "$1"
}
hu_head_matches() { # $1 = banner file, $2 = golden file
local n; n=$(wc -l < "$2")
head -n "$n" "$1" | diff -u "$2" - >/dev/null
}
split_banners /work/console.out
for b in pairing bound; do
check "R-559: the $b banner's Hungarian block is byte-identical to the golden" \
"hu_head_matches /work/b.$b /work/golden/$b.hu.txt"
# The English block is everything after the Hungarian block, above the closing frame.
n=$(wc -l < "/work/golden/$b.hu.txt")
tail -n +$((n+1)) "/work/b.$b" | grep -v '^={40,}$' > "/work/en.$b"
check "R-559: the $b English block exists" "[ -s /work/en.$b ]"
# ASCII-fragment search with BOTH controls (rule 9.8): the English block must carry no Hungarian
# letter, and the Hungarian block must carry one — or the check is matching nothing at all.
check "R-559: the $b English block has no Hungarian letter" "! grep -q '[áéíóöőúüűÁÉÍÓÖŐÚÜŰ]' /work/en.$b"
check "R-559: CONTROL — the $b Hungarian block does have one" "grep -q '[áéíóöőúüűÁÉÍÓÖŐÚÜŰ]' /work/golden/$b.hu.txt"
# 80 columns is the console's width, counted in CHARACTERS not bytes: the Hungarian lines are
# multi-byte, so `wc -c` would convict them wrongly.
check "R-559: every $b line fits 80 columns" \
"[ \"\$(awk '{ print length(\$0) }' /work/b.$b | sort -rn | head -1)\" -le 80 ]"
done
# The pairing code appears once per language — a person reads one block, and must find it there.
check "R-559: the pairing code appears in BOTH blocks" \
"[ \"\$(grep -c 'TST-CDE' /work/b.pairing)\" -eq 2 ]"
check "R-559: the bound banner carries no pairing code" "! grep -q 'TST-CDE' /work/b.bound"
# /etc/issue: the Hungarian half frozen, the English half added, still no ő/ű and still no admin URL.
check "R-559: /etc/issue still opens with the golden Hungarian" \
"head -n \$(wc -l < /work/golden/issue.hu.txt) /work/issue | diff -q /work/golden/issue.hu.txt - >/dev/null"
check "R-559: /etc/issue has an English half" "grep -q 'Felhom home server' /work/issue"
check "R-559: /etc/issue English half has no Hungarian letter" \
"! sed -n '/Felhom home server/,\$p' /work/issue | grep -q '[áéíóöőúüűÁÉÍÓÖŐÚÜŰ]'"
check "R-559: /etc/issue names no admin URL" "! grep -q '8006' /work/issue"
check "single invocation ran to done (exit 0)" "[ $rc -eq 0 ]"
check "POSTed /appliance/register" "grep -q '/appliance/register' $CALLS"
check "appliance token persisted 0600" "[ -f /etc/felhom/.bootstrap-done ] || { [ -f /etc/felhom/appliance-token ] && [ \"\$(stat -c %a /etc/felhom/appliance-token)\" = 600 ]; }"