Adds section 11. The sharpest result of the whole sweep is in it: my own decoy-coverage gate identified a repository by its DIRECTORY NAME and went blind the first time CI ran it, because the act-runner checks out into a folder called hostexecutor. The gate written that morning to catch name-for-fact was matching a name, in the first ten lines of its own main loop (R-428). The other cause was my push ordering - two repos citing R-421 pushed before felhom.eu carried the row - which instructions_gate convicted exactly as designed.
15 KiB
REPORT — the decoy sweep: can a gate be fooled by a label? (2026-09-01, R-421)
The survey — every gate, its shape, and whether a decoy passed BEFORE this session
| gate | runner(s) | meant to prove | actually matched | shape | decoy passed? |
|---|---|---|---|---|---|
| emoji | ctrl | no emoji in UI copy | codepoints, listdir scope |
1 | YES → fixed |
| native-confirm | ctrl | no OS-modal dialogs | JS regex, listdir scope |
1 | YES → fixed |
| app-row-dedup | ctrl | one row markup | regex + not in src, listdir |
1, 2 | YES ×2 → fixed |
| template-id | ctrl | JS ids resolve | id sets, listdir scope |
1 | YES → fixed |
| secret-markup | ctrl | no secret in markup | template actions, listdir |
1 | YES → fixed |
| retrieval-promise | ctrl | promises registered | stems, listdir scope |
1 | YES → fixed |
| hub-confirm | eu | no OS-modal dialogs | JS regex, listdir scope |
1 | YES → fixed |
| manifest-bearer | eu | no bearer literals | 64-hex, listdir scope |
1 | YES → fixed |
| observations | eu, ctrl, agent | a finding is filed | FILED: anywhere in body |
2 | YES → fixed (R-419) |
| debug-routes | ctrl | controls resolve | raw text, comments included | 2, 3 | YES → fixed |
| closed-register | eu | closed rows are closed | verdict cell; skipped unparseable rows | 2 | YES → fixed |
| site | eu | pages well-formed | a 7-entry PAGES list |
1 | YES → R-423 |
| one-register | eu | open work registered | state cell; idea escapes |
2 | YES → R-424 |
| offbox-rename | ctrl | branding retired | a fixed 3-entry FILES list |
1 | YES → R-425 |
| reuse-refs | eu, ctrl, agent | citations resolve | only 7 extensions | 1 | YES → R-422 |
| mojibake | ctrl | no mojibake | bytes, os.walk |
5 | NO — the control |
| docker-v | ctrl | -v mounts safe |
argv, os.walk |
5 | NO |
| image-pins | catalog | no floating tags | real image: refs |
5 | NO |
| golden-currency | eu | a golden was baked | GOLDEN_SHA256 in the log |
5 | NO (R-410's fix holds) |
| golden-notice | ctrl | ditto, mirrored | imports the gate above | 5 | NO |
| instructions | eu, ctrl, agent | instruction files sane | effective text | 5 | NO |
| hub-copy | eu | retired names gone | os.walk over hub/internal |
5 | NO |
| release-complete | agent | a release is complete | tag + ancestry + HTTP HEAD | 5 | NO |
| hostinstall | eu | installer invariants | — | ? | UNKNOWN |
| wire-contract | eu | emitted fields decode | — | ? | UNKNOWN |
| due-checks | eu | dated checks fire | — | ? | UNKNOWN |
| published | agent | versions published | — | ? | UNKNOWN |
| image-resolvable | catalog | images exist | docker manifest inspect |
5 | UNKNOWN |
| volume-persistence | catalog | data survives | runs containers, diffs | 5 | UNKNOWN |
1. Gate count
29 distinct scripts, 35 registrations — reuse-refs, instructions and observations are one
script each registered in three runners (35 − 6 = 29). Agrees with the task's 29. Runner counts
13 / 14 / 5 / 3 also match.
2. Three numbers
19 sound · 16 holes · 6 unknown. (Sound + holes exceeds 29 because 6 of the 16 were fixed and are now counted sound; the after-state is 29 = 19 sound + 4 open holes + 6 unknown.)
- Fooled: 16. Fixed this session: 10. Left open with a row: 4 (+2: R-427, R-426).
- UNKNOWN: 6 — no plausible decoy was constructed. Named in §4. Not called sound.
3. Every live hole, its decoy, its fix, its row
| gate | decoy (the label without the fact) | fix | row |
|---|---|---|---|
| emoji, native-confirm, app-row-dedup, template-id, secret-markup, retrieval-promise, hub-confirm, manifest-bearer | one file planted in a new partials/ (or overlays/) subdirectory, carrying exactly what each gate hunts |
os.listdir → os.walk |
R-421 |
| observations | "it carries no FILED: and no NOT-A-FINDING: marker" — prose about the markers |
marker must start a line or follow a sentence boundary; inline code spans stripped | R-419 CLOSED |
| debug-routes | a live dispatcher case commented out; the button survives | strip Go and HTML comments before matching | R-421 |
| app-row-dedup (2nd) | <!-- {{template "app_list_row"}} --> |
strip HTML comments in the MUST_USE check | R-421 |
| closed-register | a row with no state cell — four existed, two written the day before | unreadable row now CONVICTS, was a warning | R-421 |
| reuse-refs | a non-existent .md citation |
not fixed — needs a false-positive pass over 4 repos | R-422 |
| site | a new website/*.html absent from PAGES |
not fixed — needs the exemptions rethought | R-423 |
| one-register | a defect parked under state idea |
not fixed — declared in its own docstring | R-424 |
| offbox-rename | banned branding in a new offbox template | not fixed — fixed FILES list |
R-425 |
The one cause behind eight: scope set by os.listdir, one level. Green and correct today —
blind the moment anyone adds a subdirectory. mojibake and docker-v already walked, caught the
identical planted file, and are the control that proves the cause was the listing, not the decoy.
4. Gates with no plausible decoy — the honest unknown
| gate | why not |
|---|---|
| hostinstall | asserts installer invariants against a shell script; a legitimate decoy needs a shape a real edit would produce |
| wire-contract | 607 lines comparing emitted fields to receiver structs across two repos; needs a Go edit, forbidden here |
| due-checks | my attempt was a no-op; its verdict was withdrawn, not reported |
| published | network gate; needs a fake registry |
| image-resolvable | needs a container runtime and the network |
| volume-persistence | 877 lines that actually run containers and diff them |
This list is itself the finding (R-426 group d): six gates whose soundness is untested, not established.
5. Files and commits
| repo | commit | what |
|---|---|---|
felhom-agent |
205e22b |
CHANGELOG + CLAUDE.md pointer. No gate changed. |
app-catalog-felhom.eu |
29edad9 |
CHANGELOG + CLAUDE.md pointer. No gate changed. |
felhom-controller |
681cc66 |
7 gates fixed, test_gate_decoys.py (10 decoys), CHANGELOG, .claude/rules/gates.md |
felhom.eu |
574f5df |
4 gates fixed, test_gate_decoys.py (12 decoys), decoy_coverage_gate.py, audit, register, CONTEXT, CLAUDE.md |
6. The meta-gate's exemption list — 20 names, owned by R-426
(a) covered in the sweep, not yet in a suite: hub-copy, instructions (eu), docker-v,
image-pins.
(b) blocked by an open hole, so cannot be asserted as rejecting: site (R-423), one-register
(R-424), offbox-rename (R-425).
(c) shared scripts, counted in felhom.eu: reuse-refs ×2, instructions ×2, observations ×2
(controller + agent).
(d) no plausible decoy yet: hostinstall, wire-contract, due-checks, published,
image-resolvable, volume-persistence.
Red-proof run: a fake gate registered with no decoy → the meta-gate exits 1 and names
felhom.eu / brand-new-gate. Reverted byte-identical.
7. Which of the five defining rows closed
| row | outcome |
|---|---|
| R-419 | CLOSED — fixed and pinned, verified in both directions |
| R-410 | already closed; its fix re-verified by decoy (the empty dir is rejected and named) |
| R-400 | already closed; but its gate had a second door — a commented-out case — now shut |
| R-378 | stays open. Its row sits in OPEN-ITEMS.md with a verdict reading CLOSED 2026-08-22; it is one of the twelve in R-427 and moving it is a judgement I did not make |
| R-94 | already closed; not re-verified — it is a test, not a registered gate, and outside this sweep's denominator |
8. All four runners, final
all felhom.eu gates OK (14 gates, incl. the new decoy-coverage)
all controller gates OK (14 gates)
all agent gates OK (5 gates)
all catalog gates OK (3 gates)
9. No version, no image, no golden
No product code was touched. No version was bumped. No image was built. No golden is owed.
golden_currency_gate.py exits 0; golden and fleet floor remain 0.232.0 and current.
10. Register
Before: OPEN 172 · CLOSED 160. After: OPEN 178 · CLOSED 161. Closed R-419. Filed R-421 (the class), R-422, R-423, R-424, R-425, R-426 (the exemption list), R-427 (the mirrored gap). Also repaired four malformed CLOSED rows (R-404, R-417, R-399, R-400) that no gate had been able to read.
11. CI — four red runs after the first push, all mine, all now green
The first push of every repo except the catalog went RED, while all four runners were green locally. The workflow's own alarm text calls that "a finding about the gates themselves", and it was right twice.
| repo | final job | sha | result |
|---|---|---|---|
| felhom.eu | run 300 | 2d88776 |
success |
| felhom-controller | job 493 | 2298388 |
success |
| felhom-agent | job 492 | 4586f0f |
success |
| app-catalog-felhom.eu | job 486 | 29edad9 |
success (green first time) |
Cause 1 — R-428, and it is the sweep's sharpest result. decoy_coverage_gate.py identified a
repository by os.path.basename(root). Gitea's act-runner checks the repo out into a directory named
hostexecutor, so the gate reported "unknown repo 'hostexecutor'" and went INCONCLUSIVE. The
gate written that morning to catch instruments matching a NAME instead of a FACT was matching a name,
in the first ten lines of its own main loop. It now identifies a repo by which registered runner
FILE exists under the root. Verified under a renamed directory: 14 gates found where the name-based
version found none.
Cause 2 — my push ordering. felhom-agent and felhom-controller cite R-421, and I pushed them
before felhom.eu carried that row, so instructions_gate correctly convicted "cites R-421,
which appears in neither register". The register lives in felhom.eu; a repo citing a new row must
be pushed after it. Not a gate defect — the gate did exactly its job, on me.
Cause 3 — a missing input. CI fetches two sibling clones; the meta-gate walks four runners. The catalog fetch was added rather than letting the gate skip, which is the reasoning already written into the two fetch steps beside it.
12. Observations, and my own mistakes by name
-
The gates were the one part of this project nothing had ever checked, and 16 of 29 could be fooled. FILED: R-421 — the class row, with the four shapes.
-
Scope is a fact. Eight holes were one call:
os.listdirwhereos.walkwas meant. Three of the four remaining holes are hand-maintained lists that narrowed as their subject grew. FILED: R-421. -
closed_register_gate.pywaved through four rows it could not parse — two of them written by my own session the day before, closing R-404 and R-417. They were malformed and therefore exempt from the only check that reads that file. FILED: R-421 (fixed: unreadable now convicts). -
Twelve open rows carry a closed-looking verdict, and I did not move them. FILED: R-427.
-
My mistake — I announced a "significant finding" (36 vs 44 template files) that was an artefact of my own comparison, web-only top-level against both-roots-any-depth. Corrected within one command by running
find -mindepth 2, which returned nothing. NOT-A-FINDING: my arithmetic, not the code's; the real hole was found a different way minutes later and the corrected count is in the audit. -
My mistake — five of my decoys were illegitimate and I withdrew rather than counted them: an inert Compose
x-image:field; a CHANGELOG heading that did not actually hide the release (HEAD_RE.searchscans the whole file); a half-built decoy that commented out one of two partial calls; a malformed table row that convicted for a structural reason; and several planted files carrying nothing the gate hunts for. NOT-A-FINDING: this is the standard working as intended — a decoy nobody would write proves nothing, and each was rebuilt or dropped. They are named in the audit because an unrecorded withdrawn decoy reads as a gate never tested. -
My mistake — I trusted
rc == 0as "hole" for a gate where it is not the question.golden-currencyexits 0 whether or not it counted the fake, because currency was fine either way. I re-ran it reading the OUTPUT and it was sound. NOT-A-FINDING: caught before it reached the survey table; it is the same class as the sweep itself — an instrument answering a question next to the one asked. -
My mistake — I let bash expand backticks in an unquoted heredoc and wrote four mangled CHANGELOG paragraphs. Caught by reading the file back, repaired in place. NOT-A-FINDING: a shell quoting error of mine, corrected in the same minute, with the repaired text read back and verified on disk.
-
My own meta-gate identified a repository by its DIRECTORY NAME and went blind on its first CI run. FILED: R-428 — kept as the class's best example: it was written that morning, for exactly this, by a session with the four shapes on screen.
-
My mistake — I pushed two repos citing R-421 before the register carried the row, so CI convicted them. The register lives in
felhom.eu. NOT-A-FINDING: an ordering error of mine that the instructions gate caught precisely as designed; the rule now stated in the commit is that a repo citing a new row is pushed after felhom.eu. -
felhom-agenthas no__pycache__gitignore, like the controller before yesterday. Left alone. NOT-A-FINDING: untracked build noise, not a defect in a gate, and out of this sweep's scope; it is one line for whoever next touches that repo. -
My mistake — my first R-419 fix was too strict and rejected GENUINE markers. It anchored a marker to a line start or a bare
., which misses the commonest real shape: a bolded sentence followed by a bolded marker (...them.** **FILED: R-427**). It was caught by the fixed gate convicting the very report that documents it, on two of its own nine observations. Emphasis is now normalised away before matching, and the decoy suite re-run to confirm the R-419 decoy and a backticked mention are still refused. NOT-A-FINDING: exactly the both-directions check the task demands, working — a gate that rejects the decoy AND the genuine article is worse than the hole it replaced, and this one was caught inside the same session by its own suite, not in the wild.