Files
felhom-controller/controller/scripts/emoji_gate.py
T
admin faef8f8a91 v0.308.0 (R-554, held-back-able): recovery-info.txt points at the dashboard's Restore page and the recovery code; the first-boot setup wizard is deleted
Operator ruling 2026-10-12 (S27, 09 §3 decision 222). The operator approves the Hungarian text before
release. A box whose first ingestion failed now serves one bilingual waiting page and retries every
minute instead of the wizard. Own commit so the lead can hold it back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-12 15:38:28 +02:00

95 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""D1 §10 Python emoji gate — Windows grep silently fails to match multibyte emoji (the D0
grep-gate zero was a false negative). This scans templates by Unicode codepoint.
Run from controller/: python scripts/emoji_gate.py
Exit 1 if any emoji/pictograph remains in the web templates.
"""
import io, os, sys, unicodedata
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import i18n_bundle # noqa: E402
ROOTS = [
os.path.join("internal", "web", "templates"),
]
# Codepoint ranges that count as "emoji / pictographs / dingbats" for UI-copy purposes.
# Deliberately does NOT flag Hungarian letters, typographic quotes/dashes, the middle dot (·),
# arrows used as affordances (→ ↗ ↻ ↑ ↓), the multiplication sign (×), or box-drawing.
def is_emoji(ch):
o = ord(ch)
ranges = [
(0x1F300, 0x1FAFF), # Misc symbols & pictographs, emoticons, transport, supplemental, symbols-ext
(0x2600, 0x26FF), # Misc symbols (☀ ⚙ ⚠ ☁ …)
(0x2700, 0x27BF), # Dingbats (✅ ✂ ✈ ✏ ✓? no — see allow)
(0x1F000, 0x1F0FF), # Mahjong/dominoes/cards
(0xFE00, 0xFE0F), # Variation selectors (emoji presentation)
(0x1F1E6, 0x1F1FF), # Regional indicators
]
if any(a <= o <= b for a, b in ranges):
return True
return False
# Dingbat codepoints that are legitimate UI glyphs (checkmarks/crosses used as plain text marks,
# not emoji). We keep these OUT of the ban — they render as monochrome text, not color emoji.
ALLOW = set("✓✗✔✘•●○■▶") # ✓ ✗ ✔ ✘ • ● ○ ■ ▶
def scan(path):
# v0.247.0: the copy of a converted template lives in the i18n bundle -- scan every language's
# expansion, so an emoji in a translation is caught where it would render.
hits = []
for lang in i18n_bundle.LANGS:
for h in _scan_text(i18n_bundle.read_template(path, lang)):
if h not in hits:
hits.append(h)
return hits
def _scan_text(text):
hits = []
for lineno, line in enumerate(text.split("\n"), 1):
for ch in line:
if ch in ALLOW:
continue
if is_emoji(ch):
try:
name = unicodedata.name(ch)
except ValueError:
name = "U+%04X" % ord(ch)
hits.append((lineno, ch, name))
return hits
def _html_files(root):
# R-421 (2026-09-01): AT ANY DEPTH. This was `os.listdir`, one level only. There are no
# template subdirectories today, so the gate was green and correct — and would have stayed
# green the moment anyone added `templates/partials/`, which is an ordinary thing to do.
# Measured: a planted template in a new partials/ directory passed every listdir-based gate
# and was caught by the two that already used os.walk. See AUDIT-gate-decoys-2026-09-01.md.
out = []
for dirpath, _dirs, names in os.walk(root):
for fn in sorted(names):
if fn.endswith('.html'):
out.append(os.path.join(dirpath, fn))
return sorted(out)
def main():
total = 0
for root in ROOTS:
for path in _html_files(root):
fn = os.path.relpath(path, root)
for lineno, ch, name in scan(path):
total += 1
print("%s:%d %s %s" % (fn, lineno, ch, name))
if total:
print("EMOJI GATE FAILED: %d emoji found" % total)
sys.exit(1)
print("emoji gate OK — no emoji in web templates")
if __name__ == "__main__":
main()