hub v0.137.0 source + burn-down round 2 in felhom.eu: R-277 R-581 R-600 R-544 R-855 R-134 R-92 R-292 R-599 R-725 R-728 (hub), R-819 R-857 R-555 R-364 R-587 (gates/tools), R-571 R-129 R-124-runbook (docs); 28 rows closed incl. catalog + agent v0.147.0 rows, R-350 merged into R-132, R-888 opened, R-887 mechanism (249 -> 222)
gates / gates (push) Successful in 2m3s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-05 19:43:37 +02:00
parent e0d884565f
commit 557629d2bf
65 changed files with 2680 additions and 122 deletions
+17
View File
@@ -1,3 +1,20 @@
## gates + tools — burn-down round 2 (2026-10-05)
- **R-819:** `check_stands.py` rule 3 also accepts an id found in `CLOSED-ITEMS.md`; registered as the `stands` gate in
`repo_gates.py` with three decoys (the „check the stand's status agrees" half is not built — too vague to build).
- **R-857:** `golden_currency_gate.py` reads a bake directory with a suffix after its date and, for two bakes of one
version, the newest bake log; `RUNBOOK-manual-build.md`: re-vouch at once after a same-version re-bake (CASE 16, 17).
- **R-555:** `wire_contract_gate.py` strips Go and template comments before matching receiver tokens (a tag named only in
a comment no longer counts as decoded); 6 fields that this surfaced are allow-listed — 4 with recorded reasons, 2
(`stacks.deployed`, `storage.decommissioned`) filed as R-888 for a decision. Decoy added; exemption removed.
- **R-364:** `scripts/hu_grep.py` — search Hungarian text by bytes, refuse a zero unless an ASCII anchor hits and a
negative control misses (`test_hu_grep.py`; `REUSE.md`).
- **R-587:** `iso/build-felhom-iso.sh` refuses a RELEASE build while a `*.rootpw.txt` sits in its output dir; the
build-deploy skill's publish block stops on one (`iso/test/test_rootpw_guard.py`, under a BusyBox PATH).
- **R-725 (ISO half):** the bound-banner glyph (`iso/felhom-bootstrap.sh`, golden `bound.hu.txt`;
`iso/test/test_console_glyphs.py`). Ships with the next ISO.
- **R-208:** `test_dockerfile_arg_order.py` (hub + controller Dockerfiles).
## hub-db-backup — the snapshot time is read the BusyBox way too (2026-10-05, CI fix)
The new `script-tests` gate ran `test_hub_db_backup.py` on the CI runner (Alpine, BusyBox `date`) for the first time,
+17 -3
View File
@@ -10,7 +10,10 @@ WHAT IT CONVICTS ON (each is a FAIL, exit 1):
1. an entry with NO source — §4 rule 1: that is a defect, not a claim
2. an `evidence:` path that does not resolve — a citation nobody opened
3. a `register:` id absent from OPEN-ITEMS.md — a dangling register reference
3. a `register:` id in neither OPEN-ITEMS.md nor CLOSED-ITEMS.md — a dangling register
reference. A CLOSED row is a valid citation (R-819): a stand that is walked or built is often
walked BECAUSE its row closed, and the 2026-10-03 triage moved most cited rows there. Reading
only the open register convicted every finished row the page leaned on.
4. a `capability-map:` anchor not found — the row it derives from has moved or gone
5. status `walked` with no `evidence:` source — THE LOAD-BEARING ONE. "Walked" means done end
to end on real hardware with evidence on file. If no evidence document is cited, the page is
@@ -30,6 +33,7 @@ import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DEFAULT = os.path.join(ROOT, "documentation", "architecture", "where-felhom-stands.yaml")
REGISTER = os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md")
CLOSED = os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md")
CAPMAP = os.path.join(ROOT, "documentation", "architecture", "00-capability-map.md")
DOCS = os.path.join(ROOT, "documentation")
@@ -69,6 +73,7 @@ def main():
path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT
claims = load(path)
register = open(REGISTER, encoding="utf-8").read()
closed = open(CLOSED, encoding="utf-8").read()
# Normalise the map before matching: its row labels carry ** and ` markup, so a literal
# substring probe against the raw file fails on text that is plainly there. Matching the
# rendered words is what the citation means.
@@ -78,6 +83,7 @@ def main():
print("check_stands — %d claim(s) in %s" % (len(claims), os.path.relpath(path, ROOT)))
fails = []
closed_cites = []
for c in claims:
cid = c["id"]
@@ -88,8 +94,15 @@ def main():
if not os.path.exists(os.path.join(DOCS, ref)):
fails.append("%s: evidence path does not resolve: documentation/%s" % (cid, ref))
elif kind == "register":
if not re.search(r"\*\*%s\*\*" % re.escape(ref), register):
fails.append("%s: register id %s is not in OPEN-ITEMS.md" % (cid, ref))
# A row's id cell is `| **R-NNN** |`; the closing ** keeps R-27 from matching R-273.
row = r"\*\*%s\*\*" % re.escape(ref)
if re.search(row, register):
pass
elif re.search(row, closed):
closed_cites.append("%s → %s" % (cid, ref))
else:
fails.append("%s: register id %s is in neither OPEN-ITEMS.md nor CLOSED-ITEMS.md"
% (cid, ref))
elif kind == "capability-map":
probe = " ".join(ref.split()[:4])
probe = re.sub(r"[`*]", "", probe)
@@ -103,6 +116,7 @@ def main():
for c in claims:
counts[c.get("status")] = counts.get(c.get("status"), 0) + 1
print(" statuses: " + ", ".join("%s=%d" % kv for kv in sorted(counts.items())))
print(" register citations resolved in CLOSED-ITEMS.md: %d" % len(closed_cites))
if fails:
print("\nCONVICTED — %d problem(s):" % len(fails))
-3
View File
@@ -39,9 +39,6 @@ EXEMPT = {
("felhom.eu", "hostinstall"):
"R-426 — no plausible decoy constructed yet. It asserts installer invariants against a "
"shell script; a legitimate decoy needs a shape a real edit would produce.",
("felhom.eu", "wire-contract"):
"R-426 — 607 lines comparing emitted fields to receiver structs across two repos; a decoy "
"needs a Go edit, which this sweep was forbidden from making.",
("felhom.eu", "due-checks"):
"R-426 — no legitimate decoy constructed; the attempt in the sweep was a no-op and its "
"verdict was withdrawn rather than reported.",
+15 -4
View File
@@ -142,8 +142,10 @@ WAIVER_MAX_DAYS = 14
# `## v0.206.0 — …` on the FIRST such line: the CHANGELOG is newest-first by convention.
RELEASED_RE = re.compile(r"^##\s+v(\d+)\.(\d+)\.(\d+)\b")
# `golden-0.205.0-2026-08-07/` — the bake-evidence directory the runbook's §4.1 produces.
EVIDENCE_RE = re.compile(r"^golden-(\d+)\.(\d+)\.(\d+)-\d{4}-\d{2}-\d{2}$")
# `golden-0.205.0-2026-08-07/` — the bake-evidence directory the runbook's §4.1 produces — and a same-version
# RE-BAKE, `golden-0.292.0-2026-10-04-rebake/` (R-857): an optional `-<suffix>` after the date. The re-bake
# directory used to be invisible to this regex, so the gate kept reporting the FIRST bake's sha.
EVIDENCE_RE = re.compile(r"^golden-(\d+)\.(\d+)\.(\d+)-(\d{4}-\d{2}-\d{2})(-[a-z0-9][a-z0-9-]*)?$")
def released_versions():
@@ -226,7 +228,12 @@ def newest_baked():
if sha is None:
rejected.append("%s (%s)" % (name, why))
continue
found.append((tuple(int(g) for g in m.groups()), "%s [sha %s…]" % (name, sha[:12])))
# Order (R-857): version, then bake date, then a suffixed re-bake after the plain bake of the same
# date (a re-bake directory is made after the first one), then the name. A tuple of the version
# alone tied two bakes of one version and picked whichever sorted first.
version = tuple(int(g) for g in m.groups()[:3])
key = (version, m.group(4), 1 if m.group(5) else 0, name)
found.append((key, "%s [sha %s…]" % (name, sha[:12])))
if rejected:
print(" NOT counted as bakes — a directory name is not a bake (R-410):")
for r in sorted(rejected):
@@ -235,7 +242,11 @@ def newest_baked():
return None, ("no golden-<VER>-<DATE>/ directory under %s holds a bake log with a "
"GOLDEN_SHA256 line" % EVIDENCE_DIR)
found.sort()
return found[-1]
key, label = found[-1]
same = [lbl for k, lbl in found if k[0] == key[0]]
if len(same) > 1:
print(" %d bakes of %s on file; the NEWEST is read: %s" % (len(same), ".".join(map(str, key[0])), label))
return key[0], label
WAIVER_KEY_RE = re.compile(r"^\s*([a-z_]+)\s*:\s*(.*?)\s*$")
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""hu_grep.py — count the lines holding a Hungarian (accented) string, and REFUSE to say "0" untested (R-364).
Usage: python3 scripts/hu_grep.py PATTERN --anchor ASCII_TEXT [--negative TEXT] PATH [PATH ...]
Exit 0 found (prints the count and each file:line) · 1 a TESTED zero · 2 REFUSED (the zero is not evidence)
WHY. Accented-text search is an instrument that silently transforms its input. Three near-misses are on record:
`ssh → pct exec → bash -c` mangled a pattern (2026-07-20); `kubectl exec … sh -c grep` returned 0 for three
strings that WERE there (2026-08-13); `tar -tf` printed `őszibarack.md` as `\\305\\221szibarack.md` (2026-08-21).
Each time the zero looked exactly like "absent". Discipline alone failed, so the judgement lives here:
* the file bytes are read by Python, never through a shell, and matched as UTF-8 bytes;
* a pattern that ARRIVED transformed — octal escapes like `\\305\\221`, or U+FFFD — is refused outright;
* for a pattern with any byte >= 0x80, a ZERO is reported only when
- the ASCII ANCHOR (text you know is in these files) is found — the instrument can see the files; and
- the NEGATIVE control (default: a string nobody writes) returns zero — the instrument can say no; and
- the same pattern in the other Unicode normal form (NFC <-> NFD) is also absent — an "ő" typed as one
code point does not match "o" + combining mark, and that zero would be a false one.
Otherwise it prints REFUSED with the reason and exits 2.
An ASCII pattern is a plain count (exit 1 on zero) — the transformations above do not touch it.
Pure Python, stdlib only: runs on the BusyBox CI runner and on any box with python3.
"""
import argparse
import os
import re
import sys
import unicodedata
DEFAULT_NEGATIVE = "zz-hu-grep-negative-control-qx7"
OCTAL_ESCAPE = re.compile(r"\\[0-3][0-7]{2}")
def iter_files(paths):
for p in paths:
if os.path.isdir(p):
for dp, dns, fns in os.walk(p):
dns[:] = sorted(d for d in dns if d not in (".git", "node_modules", "__pycache__"))
for f in sorted(fns):
yield os.path.join(dp, f)
else:
yield p
def hits(needle, paths):
"""[(path, line_no)] for every line whose bytes contain needle (a str, matched as UTF-8 bytes)."""
b = needle.encode("utf-8")
out = []
for f in iter_files(paths):
try:
with open(f, "rb") as fh:
for n, line in enumerate(fh, 1):
if b in line:
out.append((f, n))
except OSError as e:
raise SystemExit("REFUSED: cannot read %s (%s) — an unreadable file is not an absent string" % (f, e))
return out
def judge(pattern, anchor, negative, paths):
"""(exit_code, message, found_lines)."""
if "�" in pattern or OCTAL_ESCAPE.search(pattern):
return 2, ("REFUSED: the pattern arrived TRANSFORMED (%r) — octal escapes or U+FFFD mean a shell or a "
"listing re-encoded it. Pass the real characters." % pattern), []
found = hits(pattern, paths)
if found:
return 0, "%d line(s)" % len(found), found
if all(ord(c) < 0x80 for c in pattern):
return 1, "0 line(s)", []
# A zero for an accented pattern: only with all three controls behaving.
if not anchor or any(ord(c) >= 0x80 for c in anchor):
return 2, "REFUSED: an accented pattern needs --anchor with ASCII text known to be in these files", []
a = hits(anchor, paths)
if not a:
return 2, ("REFUSED: the anchor %r was not found either — the instrument cannot see these files, so "
"its zero is not evidence" % anchor), []
if hits(negative, paths):
return 2, "REFUSED: the negative control %r was FOUND — this instrument cannot say no" % negative, []
for form in ("NFC", "NFD"):
other = unicodedata.normalize(form, pattern)
if other != pattern:
h = hits(other, paths)
if h:
return 2, ("REFUSED: 0 for the pattern as typed, but %d line(s) hold its %s form — the files and "
"the pattern use different Unicode normal forms" % (len(h), form)), []
return 1, ("0 line(s) — a TESTED zero: anchor %r found on %d line(s), negative control 0, other normal "
"form 0" % (anchor, len(a))), []
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("pattern")
ap.add_argument("paths", nargs="+")
ap.add_argument("--anchor", default="")
ap.add_argument("--negative", default=DEFAULT_NEGATIVE)
a = ap.parse_args(argv)
rc, msg, found = judge(a.pattern, a.anchor, a.negative, a.paths)
for f, n in found:
print("%s:%d" % (f, n))
print(msg)
return rc
if __name__ == "__main__":
sys.exit(main())
+18
View File
@@ -145,6 +145,24 @@ while [[ $# -gt 0 ]]; do
*) die "unknown argument: $1 (see --help)" ;;
esac
done
# ── Root-password guard (R-587, 2026-10-05) ──────────────────────────────────────────────────────────────
# A non-release build writes <iso>.rootpw.txt (a plaintext root password) into the out directory, and a
# RELEASE build's out directory is the public bucket's publish source. Only the publish command's
# `--include "felhom-installer-<VER>*"` kept two such files off a world-readable bucket — by an accident
# of naming. So a release build REFUSES to start while any *.rootpw.txt sits in its out directory.
# POSIX sh on purpose (no [[ ]]): scripts/iso/test/test_rootpw_guard.py runs this function under the
# BusyBox sh of the CI runner. No bypass flag: shred the file (`shred -u`), then build.
rootpw_guard() {
if $RELEASE && [ -d "$OUT_DIR" ]; then
_rp="$(find "$OUT_DIR" -maxdepth 1 -name '*.rootpw.txt' 2>/dev/null | head -1)"
if [ -n "$_rp" ]; then
die "root-password guard: $_rp is in the out directory ($OUT_DIR), which a release is published FROM. Shred it (shred -u) before building a release — a root password must never sit beside a public upload (R-587)."
fi
fi
return 0
}
rootpw_guard
# ── Clean-tree gate (R-730, 2026-09-30) ────────────────────────────────────────────────────────────────
# The manifest names the commit an image was built from. ISO 1.29.0's names 8d539f9, but the published image
# carries a menu fix committed 35 minutes AFTER the build (31eeb36): it was built from an uncommitted tree, so
+1 -1
View File
@@ -96,7 +96,7 @@ print_pairing_banner() {
print_bound_banner() {
set_console_font
{ printf '\n================================================\n'
printf ' Felhom — a doboz össze van kötve. ✔\n\n'
printf ' Felhom — a doboz össze van kötve.\n\n'
printf ' A beállítás magától folytatódik, ez néhány percig tart.\n'
printf ' A vezérlőpult címét az e-mailben kapott levél tartalmazza.\n\n'
printf ' Ezen a gépen nincs több teendőd.\n\n'
+1 -1
View File
@@ -1,5 +1,5 @@
================================================
Felhom — a doboz össze van kötve. ✔
Felhom — a doboz össze van kötve.
A beállítás magától folytatódik, ez néhány percig tart.
A vezérlőpult címét az e-mailben kapott levél tartalmazza.
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""R-725: every console banner felhom-bootstrap.sh paints uses only glyphs the console font draws.
The box console loads a Latin-2 font (Lat2-Terminus16 and friends, set_console_font). A glyph outside it is
drawn as something else: the linked banner ended in "✔", and the household saw "a doboz össze van kötve. V".
This test reads every printf literal in the script and the banner goldens the ISO harness compares against,
and refuses any character outside ASCII + the Hungarian letters + the em dash (which the banners already use
and which renders). Pure file reads: runs on the BusyBox CI runner.
Run: python3 scripts/iso/test/test_console_glyphs.py"""
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
ISO = os.path.dirname(HERE)
ALLOWED_NON_ASCII = set("áéíóöőúüűÁÉÍÓÖŐÚÜŰ—")
PRINTF = re.compile(r"""printf\s+(?:'([^']*)'|"([^"]*)")""")
def bad_glyphs(text):
return sorted({c for c in text if ord(c) > 127 and c not in ALLOWED_NON_ASCII})
def main():
failures, checked = [], 0
script = os.path.join(ISO, "felhom-bootstrap.sh")
with open(script, encoding="utf-8") as fh:
for n, line in enumerate(fh, 1):
for m in PRINTF.finditer(line):
checked += 1
bad = bad_glyphs(m.group(1) or m.group(2) or "")
if bad:
failures.append("felhom-bootstrap.sh:%d: %s" % (n, " ".join(bad)))
gdir = os.path.join(HERE, "golden")
goldens = sorted(f for f in os.listdir(gdir) if f.endswith(".txt"))
for g in goldens:
with open(os.path.join(gdir, g), encoding="utf-8") as fh:
bad = bad_glyphs(fh.read())
if bad:
failures.append("golden/%s: %s" % (g, " ".join(bad)))
# Scope is a fact: a reader that found no banners checked nothing.
if checked < 20 or not goldens:
print("FAIL: read only %d printf literals and %d goldens — the scope is wrong" % (checked, len(goldens)))
return 1
# Positive control: the checker convicts the glyph that started this row.
if bad_glyphs("a doboz össze van kötve. ✔") != ["✔"]:
print("FAIL: the checker does not convict the check-mark glyph")
return 1
if failures:
print("FAIL: console glyphs the Latin-2 font cannot draw (R-725):\n " + "\n ".join(failures))
return 1
print("OK: %d printf literals + %d goldens use only console-safe glyphs" % (checked, len(goldens)))
return 0
if __name__ == "__main__":
sys.exit(main())
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""R-587: a RELEASE ISO build refuses to start while a *.rootpw.txt sits in its out directory.
The out directory is the public bucket's publish source; a root-password file there was kept off the
bucket only by the publish command's --include pattern, by an accident of naming.
HOW IT RUNS ANYWHERE. The build script is bash and needs docker; the CI runner has neither (Alpine +
BusyBox + python3 + git). So this test lifts the `rootpw_guard` function out of build-felhom-iso.sh
VERBATIM and runs it under `sh` with a stub `die` — the function is written in POSIX sh for exactly
this. It also checks, statically, that the script CALLS the guard before the build does anything else
of consequence (the clean-tree gate and the docker preflight), because a guard that is defined and not
called is the seam-built-never-wired shape.
Run: python3 scripts/iso/test/test_rootpw_guard.py"""
import os
import re
import shutil
import subprocess
import sys
import tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
BUILD = os.path.join(os.path.dirname(HERE), "build-felhom-iso.sh")
def guard_source():
text = open(BUILD, encoding="utf-8").read()
m = re.search(r"^rootpw_guard\(\) \{\n.*?^\}\n", text, re.S | re.M)
if not m:
raise SystemExit("FAIL: rootpw_guard() not found in build-felhom-iso.sh")
return text, m.group(0)
def run_guard(func, release, out_dir):
script = ('die() { echo "DIE: $1"; exit 1; }\nRELEASE=%s\nOUT_DIR=%s\n%s\nrootpw_guard\necho PROCEEDED\n'
% ("true" if release else "false", out_dir, func))
p = subprocess.run(["sh", "-c", script], capture_output=True, text=True)
return p.returncode, p.stdout + p.stderr
def main():
fails = []
text, func = guard_source()
tmp = tempfile.mkdtemp(prefix="rootpw-guard-")
try:
open(os.path.join(tmp, "felhom-installer-9.9.9-pve9.2-1.iso"), "w").close()
# 1 — the genuine article: a release with a clean out dir proceeds.
rc, out = run_guard(func, True, tmp)
if rc != 0 or "PROCEEDED" not in out:
fails.append("clean out dir: a release build was refused (rc=%d): %s" % (rc, out))
# 2 — the decoy: a stray rootpw file (named so the publish --include would MISS it) refuses.
stray = os.path.join(tmp, "felhom-pve-9.2-1-v1.24.0-nested.iso.rootpw.txt")
open(stray, "w").close()
rc, out = run_guard(func, True, tmp)
if rc == 0 or "PROCEEDED" in out or "rootpw.txt" not in out:
fails.append("stray rootpw file: the release build was NOT refused (rc=%d): %s" % (rc, out))
# 3 — a non-release build in the same dir proceeds (it is the producer of these files, not a publish).
rc, out = run_guard(func, False, tmp)
if rc != 0 or "PROCEEDED" not in out:
fails.append("non-release build was refused (rc=%d): %s" % (rc, out))
finally:
shutil.rmtree(tmp, ignore_errors=True)
# 4 — wired: the guard is CALLED, and before the clean-tree gate and the docker preflight.
call = re.search(r"^rootpw_guard\s*$", text, re.M)
later = [re.search(r"^clean_tree_gate\s*$", text, re.M), re.search(r"^command -v docker", text, re.M)]
if not call or any(m is None or m.start() < call.start() for m in later):
fails.append("rootpw_guard is not called before clean_tree_gate and the docker preflight")
if fails:
print("FAIL (R-587):\n " + "\n ".join(fails))
return 1
print("OK: a release build refuses a *.rootpw.txt in its out dir; a clean dir and a non-release build proceed")
return 0
if __name__ == "__main__":
sys.exit(main())
+5
View File
@@ -24,6 +24,8 @@ Gates, in order (all must pass; **non-zero exit on any failure**):
13. observations a REPORT.md observation with no register row behind it (R-389)
14. register-shape a register row whose state cell was eaten, a duplicated id, or a blank line
splitting the table — the register mis-stating how many findings exist (R-627)
14b. stands where-felhom-stands.yaml: every claim cites a source that resolves, and every
'walked' cites a walk (R-819; it was red and ran in no runner)
15. script-tests every Python test suite under scripts/ (found by a walk), by exit code (R-885)
16. decoy-coverage every registered gate in all four repos has a decoy, or a named exemption (R-421)
@@ -154,6 +156,9 @@ GATES = [
# nothing noticed until a person read the file. R-254 had been missing its state cell since
# 2026-08-08 — 45 days — for the same reason. Fast: one file read.
("register-shape", os.path.join(SCRIPTS, "register_shape_gate.py"), [], True, False),
# R-819 — check_stands.py guarded the operator's "where Felhom stands" page and was RED for weeks,
# registered in no runner, so nobody saw it (the R-29 shape). Fast: three file reads.
("stands", os.path.join(SCRIPTS, "check_stands.py"), [], True, False),
# R-885 — the Python test suites under scripts/ (found by a walk) ran only by hand; a change that broke the
# 15 tests of the DooPlex hub-DB scripts, or the 73 of the instructions gate, reached main green. ~20 s.
("script-tests", os.path.join(SCRIPTS, "script_tests_gate.py"), [ROOT], True, False),
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""R-208: in hub/Dockerfile no per-build ARG (VERSION, BUILD_TIME, GIT_COMMIT) is declared above the
module-download RUN of the same stage.
WHY. An ARG in scope is part of every later RUN's cache key. Declared above `RUN go mod download`, a fresh
--build-arg VERSION invalidates the download layer on every build — measured: 208 download records, each used
once, ~440 MB of dead cache per build. Declared below it, the download is CACHED until go.mod/go.sum change.
Pure text read; runs on the BusyBox CI runner. Run: python3 scripts/test_dockerfile_arg_order.py"""
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PER_BUILD = {"VERSION", "BUILD_TIME", "GIT_COMMIT"}
def violations(text):
"""Per stage: a per-build ARG that appears before that stage's `go mod download` RUN."""
out, stage, seen_args, downloaded = [], 0, [], False
for n, raw in enumerate(text.splitlines(), 1):
line = raw.strip()
if re.match(r"(?i)^FROM\s", line):
stage, seen_args, downloaded = stage + 1, [], False
continue
m = re.match(r"(?i)^ARG\s+([A-Za-z_][A-Za-z0-9_]*)", line)
if m and m.group(1) in PER_BUILD and not downloaded:
seen_args.append((n, m.group(1)))
if re.match(r"(?i)^RUN\s.*\bgo mod download\b", line):
downloaded = True
out.extend("line %d: ARG %s above the module download (stage %d)" % (n2, a, stage) for n2, a in seen_args)
return out
def main():
# Decoy first: the pre-fix shape must convict, or this test checks nothing.
decoy = "FROM golang AS b\nARG VERSION=dev\nCOPY go.mod ./\nRUN go mod download || true\nRUN go build\n"
if not violations(decoy):
print("FAIL: the decoy (ARG VERSION above go mod download) was not convicted")
return 1
path = os.path.join(ROOT, "hub", "Dockerfile")
text = open(path, encoding="utf-8").read()
if not re.search(r"(?m)^RUN\s.*\bgo mod download\b", text):
print("FAIL: hub/Dockerfile has no `go mod download` RUN — update this test with the new layout")
return 1
bad = violations(text)
if bad:
print("FAIL: hub/Dockerfile (R-208):\n " + "\n ".join(bad))
return 1
print("OK: hub/Dockerfile declares its per-build ARGs below the module download")
return 0
if __name__ == "__main__":
sys.exit(main())
+52
View File
@@ -53,6 +53,12 @@ COVERS = {
"(2026-10-03) an old-shape row under the new header, a near-miss category, an "
"old rank tag as Sev, an undefined state word, and a pipe outside backticks"),
"decoy-coverage": "a gate registered in a runner with no decoy and no exemption (its red-proof)",
"wire-contract": ("R-555: an emitted tag whose name the receiver carries ONLY in a // and a /* */ comment "
"must convict (it passed for months as `language` did); the genuine article — the same "
"name in a struct tag beside a `//` inside a string literal — must pass"),
"stands": ("R-819: a stand citing a register id that exists in NEITHER register, and a stand "
"marked 'walked' whose only source is a register row (a green dot from a label); "
"plus the genuine article — a stand citing only a CLOSED row, which must PASS"),
"site": "R-423: a NEW page under website/ that PAGES does not list (the gate used to scan 7 of 9)",
"script-tests": ("R-885: FIVE cases in scripts/test_script_tests_gate.py, run from here: a suite that PRINTS "
"OK and exits 1 (label without fact), a failing suite three levels deep (scope is a walk), "
@@ -486,6 +492,52 @@ _rsg_case("pipe-outside-backticks",
_HEALTHY.replace("**A finding.**", "**A finding.** owner: CC | operator"),
must_convict=True, expect_rule="RULE 5")
# ── wire-contract (R-555) ────────────────────────────────────────────────────────────────────────
#
# The real receiver (the hub tree) with ONE received tag taken out of its token set and put back only
# through a file that names it in prose. The gate runs end to end against the real emitters, so the
# decoy needs the controller and agent clones beside this one (CI fetches both; a missing clone is
# INCONCLUSIVE and fails this suite, never a pass). `repo_size_bytes` is `offsite.repo_size_bytes`,
# which the hub really decodes, so the genuine case passes for the right reason.
_WC = r"""
import os, sys, tempfile
sys.path.insert(0, "scripts")
import wire_contract_gate as g
T, mode = "repo_size_bytes", sys.argv[1]
tmp = tempfile.mkdtemp(prefix="r555-")
body = {"comment": "package prose\n// %s is described here, in prose only.\n/* and %s again */\n" % (T, T),
"genuine": "package prose\nvar u = \"http://x\" // not a comment start inside a string\n"
"type X struct { A int `json:\"%s\"` }\n" % T}[mode]
open(os.path.join(tmp, "prose.go"), "w").write(body)
orig, hub = g.receiver_tokens, os.path.abspath(g.REPOS["hub"])
g.receiver_tokens = lambda p: ((orig(p) - {T}) | orig(tmp)) if os.path.abspath(p) == hub else orig(p)
rc, conv = g.run(quiet=True)
print("rc=%d convicted_T=%s" % (rc, any(T in str(c) for c in conv)))
sys.exit(0 if rc == 0 else (10 if any(T in str(c) for c in conv) else 11))
"""
for _mode, _want in (("comment", 10), ("genuine", 0)):
ran += 1
_p = subprocess.run([sys.executable, "-c", _WC, _mode], cwd=ROOT, capture_output=True, text=True)
if _p.returncode != _want:
fails.append("wire-contract/%s: rc=%d, want %d (10 = the comment-only tag convicted, 0 = passed)\n%s"
% (_mode, _p.returncode, _want, (_p.stdout + _p.stderr)[-500:]))
else:
print(" ok %-20s %s" % ("wire-contract/" + _mode,
"decoy rejected" if _want else "genuine accepted"))
# ── stands (R-819) ───────────────────────────────────────────────────────────────────────────────
#
# check_stands.py was red and in no runner. Its decoys are stand files written as a session would write
# them, checked with the real registers. The genuine case is the one R-819 fixed: a citation of a row
# that CLOSED (R-273, the never-tagged agent) is a valid source, not a dangling one.
_STANDS = os.path.join(ROOT, "documentation", "architecture", "zz-r819-decoy-stands.yaml")
_stand = lambda status, ref: (u"claims:\n - id: decoy.stand\n title: \"decoy\"\n status: %s\n"
u" sources:\n - register: %s\n" % (status, ref))
decoy("stands/dangling-id", "check_stands.py", plant_file(_STANDS, _stand("built", "R-99999")), args=(_STANDS,))
decoy("stands/walked-no-walk", "check_stands.py", plant_file(_STANDS, _stand("walked", "R-273")), args=(_STANDS,))
decoy("stands/closed-row-ok", "check_stands.py", plant_file(_STANDS, _stand("built", "R-273")), args=(_STANDS,),
expect="pass")
print()
if fails:
for f in fails:
+39
View File
@@ -118,6 +118,7 @@ def main():
print("CASE 4 ok: the tree is unchanged; the gate's verdict is the same as the baseline")
fails += waiver_cases()
fails += rebake_cases()
if fails:
print()
@@ -245,4 +246,42 @@ def waiver_cases():
return fails
# ── R-857: a golden baked TWICE under one version ───────────────────────────────────────────────
#
# 2026-10-04: 0.292.0 was re-baked into `golden-0.292.0-2026-10-04-rebake/`. The gate's regex did not
# match the suffix, so it kept reporting the FIRST bake's sha (d6cf8b33…) while the registry held the
# second. The CONSEQUENCE asserted: the newer bake's sha is the one reported.
def rebake_cases():
fails = []
OLD_SHA, NEW_SHA = "d6" * 32, "79" * 32
env, tmp = synthetic_tree(["9.9.9"], "9.9.9", None)
try:
ev = env["GOLDEN_GATE_EVIDENCE_DIR"]
shutil.rmtree(os.path.join(ev, "golden-9.9.9-2026-01-01"))
for name, sha in (("golden-9.9.9-2026-01-04", OLD_SHA), ("golden-9.9.9-2026-01-04-rebake", NEW_SHA)):
os.makedirs(os.path.join(ev, name))
with io.open(os.path.join(ev, name, "bake.log"), "w", encoding="utf-8") as fh:
fh.write("[golden] upload OK (HTTP 201)\nGOLDEN_SHA256=%s\n" % sha)
rc, out = run_gate_env(env)
if rc != 0:
fails.append("CASE 16 (R-857): the gate failed on a current golden baked twice (rc=%d)\n%s" % (rc, out[-400:]))
elif NEW_SHA[:12] not in out or OLD_SHA[:12] in out.split("newest golden baked")[-1].splitlines()[0]:
fails.append("CASE 16 (R-857): two bakes of one version — the gate did not report the RE-BAKE's sha\n%s"
% out[-400:])
else:
print("CASE 16 ok (R-857): of two bakes of one version, the re-bake's sha is the one reported")
# A later DATE of the same version beats an earlier suffixed one.
os.makedirs(os.path.join(ev, "golden-9.9.9-2026-01-05"))
with io.open(os.path.join(ev, "golden-9.9.9-2026-01-05", "bake.log"), "w", encoding="utf-8") as fh:
fh.write("GOLDEN_SHA256=%s\n" % ("ab" * 32))
rc, out = run_gate_env(env)
if ("ab" * 32)[:12] not in out:
fails.append("CASE 17 (R-857): a later-dated bake of the same version was not preferred\n%s" % out[-400:])
else:
print("CASE 17 ok (R-857): a later-dated bake of the same version wins")
finally:
shutil.rmtree(tmp, ignore_errors=True)
return fails
sys.exit(main())
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""R-364: hu_grep.py never reports an untested zero for an accented pattern.
Each case asserts the CONSEQUENCE an operator would act on: a present string is counted; a transformed
pattern, a blind instrument, or a normal-form mismatch is REFUSED (exit 2), never a quiet 0; and a real
absence, with every control behaving, is a 0 (exit 1). Pure Python — runs on the BusyBox CI runner.
Run: python3 scripts/test_hu_grep.py"""
import os
import shutil
import sys
import tempfile
import unicodedata
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import hu_grep # noqa: E402
fails = []
def case(label, want_rc, pattern, anchor, files, want_in=""):
tmp = tempfile.mkdtemp(prefix="hu-grep-")
try:
for name, body in files.items():
with open(os.path.join(tmp, name), "w", encoding="utf-8") as fh:
fh.write(body)
rc, msg, _ = hu_grep.judge(pattern, anchor, hu_grep.DEFAULT_NEGATIVE, [tmp])
finally:
shutil.rmtree(tmp, ignore_errors=True)
if rc != want_rc or want_in not in msg:
fails.append("%s: rc=%d msg=%r, want rc=%d containing %r" % (label, rc, msg, want_rc, want_in))
else:
print("ok %-34s rc=%d %s" % (label, rc, msg[:70]))
PAGE = {"page.html": "<p>Ha lejárt, kérj újat az alábbi gombbal.</p>\n<p>Felhom</p>\n"}
case("present accented string counted", 0, "kérj újat", "Felhom", PAGE, "1 line")
case("octal-escaped pattern REFUSED", 2, "k\\303\\251rj", "Felhom", PAGE, "TRANSFORMED")
case("U+FFFD pattern REFUSED", 2, "k�rj", "Felhom", PAGE, "TRANSFORMED")
case("blind instrument REFUSED", 2, "ügyfélszolgálat", "NotInTheFile", PAGE, "anchor")
case("no anchor given REFUSED", 2, "ügyfélszolgálat", "", PAGE, "--anchor")
case("NFD file, NFC pattern REFUSED", 2, "ügyfélszolgálat", "Felhom",
{"nfd.txt": unicodedata.normalize("NFD", "Felhom ügyfélszolgálat\n")}, "NFD form")
case("tested zero is a zero", 1, "ügyfélszolgálat", "Felhom", PAGE, "TESTED zero")
case("ascii pattern plain zero", 1, "nothere", "", PAGE, "0 line")
if fails:
print("\nFAIL:\n " + "\n ".join(fails))
sys.exit(1)
print("\nhu_grep: OK — no untested zero for an accented pattern")
+70 -1
View File
@@ -280,6 +280,30 @@ ALLOWLIST = {
"and a fact displayed with nothing to compare it to is decoration."),
(_CH, "backup.last_db_dump"): _R264,
(_CH, "backup.last_integrity_check"): _R264,
# ---- R-555 (2026-10-05): surfaced when comments stopped counting as receivers ----
#
# Each of these passed for months because its NAME occurred hub-side only in a comment. No hub struct
# decodes any of them. Listed so the drop is visible; none is known to be needed by a hub surface.
# **Delete an entry when a hub struct starts decoding the field.**
(_AH, "audit_tail"): (
"R-555: ignored on purpose — hub/internal/api/handler.go's hostReportPayload comment names it "
"among the fields the hub does not decode (backups/restore_tests/pbs_snapshots/audit_tail)."),
(_AH, "guests.spec"): (
"R-555: the parent of guests.spec.disk_bytes / memory_bytes above — sizing is hub-owned intent, "
"not mirrored reality, so the object is not decoded either."),
(_CH, "backup.integrity_ok"): (
"R-555 / R-331: no producer since disk-tier restic moved to the agent (slice 8C); the Backup card "
"dropped it rather than re-sourcing it (hub/internal/web/backup_card.go)."),
(_CH, "backup.repo_size_mb"): (
"R-555 / R-331: no producer since slice 8C; the Backup card reads offsite.repo_size_bytes "
"instead (hub/internal/web/backup_card.go)."),
(_CH, "stacks.deployed"): (
"R-555: surfaced 2026-10-05 — the hub decodes no deployed-app list from the report; whether a hub "
"surface needs it is not decided. Filed as R-888 for the operator to decide."),
(_CH, "storage.decommissioned"): (
"R-555: surfaced 2026-10-05 — the hub decodes no per-drive decommissioned marker from the report; "
"whether a hub surface needs it is not decided. Filed as R-888 for the operator to decide."),
}
# A node whose IMMEDIATE CHILDREN are still checked but whose DEEPER descendants are not, because the
@@ -446,6 +470,48 @@ def walk(by_dir, by_name, start_dir, start_type, seen=None, prefix=""):
TOKEN_RE = re.compile(r"[A-Za-z0-9_]+")
TEMPLATE_COMMENT_RE = re.compile(r"\{\{-?\s*/\*.*?\*/\s*-?\}\}", re.S)
def strip_go_comments(src):
"""Go source with every // and /* */ comment removed; string, raw-string and rune literals kept.
R-555: a field whose name appeared hub-side only in a COMMENT counted as received — `language`
passed because configs.go said "this page's existing language". A comment is prose, not a decoder.
A small lexer rather than a regex, because a regex cannot tell `//` in "http://…" or a backtick
struct tag from a comment, and the struct tags are exactly what this gate reads.
"""
out, i, n = [], 0, len(src)
while i < n:
c = src[i]
if c == "/" and i + 1 < n and src[i + 1] == "/":
j = src.find("\n", i)
i = n if j < 0 else j # keep the newline
continue
if c == "/" and i + 1 < n and src[i + 1] == "*":
j = src.find("*/", i + 2)
i = n if j < 0 else j + 2
out.append(" ")
continue
if c in "\"'`":
j = i + 1
while j < n and src[j] != c:
if c != "`" and src[j] == "\\":
j += 1
elif c != "`" and src[j] == "\n":
break # an unterminated literal ends at the line
j += 1
out.append(src[i:j + 1])
i = j + 1
continue
out.append(c)
i += 1
return "".join(out)
def strip_template_comments(src):
"""An HTML template with every {{/* … */}} action removed (R-555)."""
return TEMPLATE_COMMENT_RE.sub(" ", src)
def receiver_tokens(repo_root):
@@ -479,7 +545,10 @@ def receiver_tokens(repo_root):
p = os.path.join(dp, f)
try:
with open(p, encoding="utf-8", errors="replace") as fh:
toks.update(TOKEN_RE.findall(fh.read()))
src = fh.read()
# R-555: comments are stripped first — a name in prose is not a decoder.
src = strip_go_comments(src) if f.endswith(".go") else strip_template_comments(src)
toks.update(TOKEN_RE.findall(src))
except OSError as e:
# never swallowed: an unreadable source file makes the answer unknown, not "absent"
die("wire-contract gate INCONCLUSIVE: cannot read %s: %s" % (p, e))