R-426: hostinstall gate gets decoys; two live holes closed, exemption removed

The R-185 check counted the bare word `felhom-backup-target-apply grant`, so the
dry-run echo stood in for a deleted real grant (2 resolutions, 2 "grants"); now
only path-qualified invocations at a command start count. The age check matched
a commented-out install; now comment lines are excluded. Decoys (plus a version
const in a new hub sub-package, and a comment naming the identifier that must
pass) live in test_gate_decoys.py; all three convicting decoys were seen to PASS
against the pre-fix gate. EXEMPT drops `felhom.eu/hostinstall`.

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-06 01:04:56 +02:00
parent 70304a53df
commit c6c6cb7676
3 changed files with 54 additions and 5 deletions
-3
View File
@@ -36,9 +36,6 @@ EXEMPT = {
("felhom.eu", "one-register"):
"R-424 — a real defect parked under state `idea` is invisible. Declared in the gate's own "
"docstring as residual hole 1; the decoy passes today.",
("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", "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.",
+10 -2
View File
@@ -109,7 +109,10 @@ else:
# ── 2. age package (F-10) ───────────────────────────────────────────────────────
# must match the REAL install invocation, not the log_dry echo (red-proof-hardened twice:
# a prefix regex matched "agekit", then a loose one matched the dry-run print line).
if re.search(r'DEBIAN_FRONTEND=noninteractive apt-get install -y -q age\b', src):
# R-426 (2026-10-06): and not a COMMENTED-OUT one — `# DEBIAN_FRONTEND=… age` (a line disabled while
# debugging) matched the bare search, so the label stayed and the install was gone. Decoy:
# test_gate_decoys.py `hostinstall/age-commented-out`.
if re.search(r'^[^#\n]*DEBIAN_FRONTEND=noninteractive apt-get install -y -q age\b', src, re.M):
ok("age is in the installed package set")
else:
fail("`age` install not found (F-10 — the fresh-box escrow ceremony dies without it)")
@@ -221,7 +224,12 @@ if not fn:
else:
body = fn.group(1)
resolutions = len(re.findall(r'BACKUP_TARGET_RESOLVED="\$BACKUP_TARGET_ID"', body))
grants = len(re.findall(r'felhom-backup-target-apply grant', body))
# R-426 (2026-10-06): count only REAL invocations — the path-qualified binary at the start of a
# command line. The bare name also matched the `log_dry "felhom-backup-target-apply grant …"`
# echo, so deleting the Scenario-F arm's real grant (R-185's exact regression) left 2 resolutions
# against 2 "grants" — the dry-run label — and this check printed ok. Decoy: test_gate_decoys.py
# `hostinstall/R-185-dry-run-echo-only`.
grants = len(re.findall(r'^[ \t]*/usr/local/sbin/felhom-backup-target-apply grant\b', body, re.M))
if resolutions == 0:
fail("configure_backup_target no longer resolves BACKUP_TARGET_ID anywhere — re-read it")
elif grants >= resolutions:
+44
View File
@@ -72,6 +72,8 @@ COVERS = {
"their sentences. Also: a prefix of the real sentence, the OLD Hungarian "
"quote, a reworded bundle, a deleted key, and the scope case — an absent "
"controller clone must be INCONCLUSIVE, never a pass"),
"hostinstall": ("R-426: R-185's regression with only its dry-run echo left, the age install commented "
"out, and a version const in a new hub sub-package; a comment naming the identifier passes"),
"hub-copy": ("R-558: an English retrieval promise in the NEW bundle (the sentences moved "
"out of templates.go, so the surface list had to move with them), the same "
"in Hungarian, and an INNOCENT control using the identical verbs without the "
@@ -393,6 +395,48 @@ decoy("site/unlisted-page", "site_gates.py",
plant_file(os.path.join(ROOT, "website", "zz-r423-decoy.html"),
u"\ufeff<!DOCTYPE html><html><body><nav></nav><footer></footer></body></html>\n"))
def replace_in(path, old, new):
"""Mutate one exact span of a real file; restore it byte-for-byte. A span that is no longer there makes
the decoy UNBUILDABLE, which is a failure of this suite — never a quietly skipped case."""
def _plant():
backup = io.open(path, encoding="utf-8").read()
assert backup.count(old) == 1, "%s: the decoy's anchor is gone or ambiguous — rebuild the decoy" % path
io.open(path, "w", encoding="utf-8").write(backup.replace(old, new, 1))
return lambda: io.open(path, "w", encoding="utf-8").write(backup)
return _plant
# ── hostinstall (R-426, 2026-10-06) ──────────────────────────────────────────────────────────────
#
# Each decoy is a one-line edit a real session would make to felhom-host-install.sh, leaving the LABEL
# the gate used to read while removing the fact. The first two PASSED before 2026-10-06 (live holes):
# * R-185's own regression — the Scenario-F arm loses its real grant but keeps its dry-run echo. The
# gate counted the bare word, so the echo stood in for the grant (2 resolutions, 2 "grants").
# * the `age` install commented out while debugging — the bare search matched the comment.
# * a host-install version const in a NEW hub sub-package — the scope is a walk, not a file list.
# The genuine article is the unmodified tree, which the runner's own `hostinstall` row passes on every push.
_HI = os.path.join(ROOT, "scripts", "felhom-host-install.sh")
decoy("hostinstall/R-185-dry-run-echo-only", "hostinstall_gates.py",
replace_in(_HI,
' /usr/local/sbin/felhom-backup-target-apply grant "$BACKUP_TARGET_ID" \\\n'
' || die "backup target grant failed on the pre-existing target',
' true \\\n'
' || die "backup target grant failed on the pre-existing target'))
_AGE_CMD = (" DEBIAN_FRONTEND=noninteractive apt-get install -y -q age >/dev/null 2>&1 \\\n"
" || { apt-get update -q >/dev/null 2>&1; DEBIAN_FRONTEND=noninteractive apt-get install -y -q age >/dev/null 2>&1; } \\\n"
" || die \"failed to install the 'age' package (the escrow ceremony needs it)\"\n")
decoy("hostinstall/age-commented-out", "hostinstall_gates.py",
replace_in(_HI, _AGE_CMD, "".join("#" + l[1:] + "\n" for l in _AGE_CMD.splitlines())))
decoy("hostinstall/hub-version-const-subpkg", "hostinstall_gates.py",
plant_file(os.path.join(ROOT, "hub", "internal", "web", "zzr426decoy", "v.go"),
u'package zzr426decoy\n\nconst hostInstallVersion = "1.32.0"\n'))
# the genuine article beside the third: a comment that merely NAMES the identifier must pass (configs.go
# carries exactly such a note), so the scope widening is not bought with false convictions.
decoy("hostinstall/comment-names-identifier", "hostinstall_gates.py",
plant_file(os.path.join(ROOT, "hub", "internal", "web", "zzr426decoy", "v.go"),
u'package zzr426decoy\n\n// hostInstallVersion was deleted on purpose (R-94).\n'),
expect="accept")
# ── script-tests (R-885) ─────────────────────────────────────────────────────────────────────────
#
# Its decoys are whole fake repos, so they live in their own file and are RUN from here (guide-quote's shape).