A live review report (web-ui-system-darkmode, commit 4b2047e) flagged
two uncertainties a text-only diff review genuinely can't close: no
actual rendering checked, and contrast only "visually plausibilized"
from hex values. Adds a fixed, narrowly-scoped screenshot.py: given a
commit hash and a repo-relative .html path, it extracts that exact file
via `git show <hash>:<path>` (never the live working tree, which may
have moved on by review time) and renders it headless in both light and
dark prefers-color-scheme, for the Reviewer to inspect via its own Read
tool.
Wired only into run_review()'s Claude branch -- Codex's --sandbox
read-only blocks every write, so Chromium has nowhere to put a
screenshot there. --allowedTools gets a second, dynamically-built entry
pinning the exact resolved script invocation, same fixed-command
precedent as the existing Bash(git *) grant; the script re-validates
its own arguments regardless.
Two things verified live before wiring anything up, not assumed:
--force-prefers-color-scheme has no effect in this Chromium build
(--blink-settings=preferredColorScheme=<0|1> does, confirmed by actual
pixel color); and --allowedTools does honor a fixed-script pattern the
same way it honors Bash(git *). New pkgs.chromium dependency, run with
--no-sandbox since NoNewPrivileges=true already forbids the privilege
escalation Chromium's own setuid sandbox needs -- containment comes
from the outer systemd sandbox instead, a deliberate trade-off noted in
both the intent doc and README.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016w25covboHfcYuLXBAvX3g
102 lines
5.5 KiB
Markdown
102 lines
5.5 KiB
Markdown
# Intent: Visuelle Browser-Verifikation für den Board-Reviewer
|
|
|
|
## Ziel
|
|
|
|
Der Reviewer-Bericht für `web-ui-system-darkmode` (Commit `4b2047e`)
|
|
listete zwei "Unsicherheiten", die ein reines Text-Diff-Review nicht
|
|
schließen kann: kein tatsächliches Rendering geprüft (nur die
|
|
CSS-Variablenstruktur), keine berechnete WCAG-Kontrastprüfung, nur
|
|
"visuell plausibilisiert anhand der gewählten Hex-Werte". Live-Diskussion
|
|
auf dem Board selbst (10.09.2026): dem Reviewer eine echte visuelle
|
|
Prüfmethode für HTML/CSS-Änderungen geben, statt nur aus dem Quelltext auf
|
|
das Rendering zu schließen.
|
|
|
|
## Umfang
|
|
|
|
- Neues, fixes Skript `board-agent/screenshot.py`: nimmt ausschließlich
|
|
einen Commit-Hash und einen Repo-relativen `.html`-Pfad entgegen,
|
|
extrahiert den exakten Dateiinhalt zum genannten Commit über `git show
|
|
<hash>:<pfad>` (nie den möglicherweise inzwischen weitergelaufenen
|
|
Arbeitsbaum), rendert ihn headless per Chromium je einmal mit
|
|
`prefers-color-scheme: light` und `: dark` und gibt beide PNG-Pfade auf
|
|
stdout aus.
|
|
- **Empirisch verifiziert (10.09.2026), nicht angenommen:**
|
|
`--force-prefers-color-scheme` hat in der eingesetzten Chromium-Version
|
|
headless keinerlei Effekt (identischer Output für beide Werte); erst
|
|
`--blink-settings=preferredColorScheme=<0|1>` schaltet tatsächlich um
|
|
(0 = dark, 1 = light, per gemessenem Pixelwert bestätigt, nicht aus der
|
|
Enum-Reihenfolge angenommen).
|
|
- `run_review()`s Claude-Zweig bekommt einen neuen, dynamisch aus
|
|
`BOARD_SCREENSHOT_SCRIPT`/`sys.executable` zusammengesetzten
|
|
`--allowedTools`-Eintrag, der ausschließlich genau diesen einen
|
|
Skriptaufruf erlaubt (gleiches "Wrapper erzwingt, Modell schlägt vor"-
|
|
Muster wie der bestehende `Bash(git *)`-Eintrag) — das Skript
|
|
validiert seine beiden Argumente zusätzlich selbst (Commit-Hash-Form,
|
|
`.html`-Endung, kein absoluter Pfad, kein `..`), unabhängig vom
|
|
Allowlist-Muster.
|
|
- `REVIEW_PROMPT_TEMPLATE` bekommt einen neuen, laufzeitabhängigen
|
|
`{visual_verification}`-Abschnitt: für Claude die vollständige
|
|
Gebrauchsanweisung inklusive des vorausgefüllten Befehls (Commit-Hash
|
|
bereits eingesetzt, der Reviewer ergänzt nur noch den Dateipfad); für
|
|
Codex/OpenRouter bleibt er leer.
|
|
- Nur für die Claude-Runtime verdrahtet: Codex' `run_review()`-Zweig
|
|
läuft mit `--sandbox read-only`, das jeden Schreibzugriff verweigert
|
|
(verifiziert, siehe `board-agent/README.md`, "Why read-only, not a
|
|
scoped writable dir") — Chromium hätte dort keinen Ort, um einen
|
|
Screenshot abzulegen, also wird das Werkzeug dort erst gar nicht
|
|
angeboten.
|
|
- Neue Nix-Abhängigkeit `pkgs.chromium` (`BOARD_CHROMIUM_BIN`). Läuft mit
|
|
`--no-sandbox`, da Chromiums eigener Setuid-Sandbox die
|
|
Rechteausweitung braucht, die `NoNewPrivileges = true` auf
|
|
`board-agent-worker.service` bereits verbietet — Eindämmung kommt in
|
|
diesem Modus vollständig vom äußeren systemd-Sandbox
|
|
(`ProtectSystem=strict`, `PrivateTmp`), nicht von Chromiums eigener
|
|
Prozessisolation. Bewusster Kompromiss, kein Versehen: der gerenderte
|
|
Inhalt ist in der Praxis ausschließlich dieses Repos eigene, bereits
|
|
geprüfte Commits, nie Drittinhalte.
|
|
- Ein fehlgeschlagener Screenshot-Aufruf (z. B. weil die Sandbox
|
|
Chromium doch nicht starten lässt) ist laut Prompt kein Fehler des
|
|
Reviews selbst — der Reviewer vermerkt das knapp unter
|
|
"Unsicherheiten" und bewertet den Rest des Diffs normal weiter.
|
|
|
|
## Explizit ausgeschlossen
|
|
|
|
- Keine automatische, berechnete WCAG-Kontrastprüfung (Diskussion
|
|
ausdrücklich zugunsten des Browser-Screenshots vertagt) — der Reviewer
|
|
beurteilt Kontrast weiterhin visuell aus dem Screenshot, nicht
|
|
rechnerisch exakt. Eigener, separater Folgeintent, falls gewünscht.
|
|
- Kein Einsatz für Codex/OpenRouter-Reviews (siehe oben, Sandbox-Grund).
|
|
- Keine Ausweitung auf beliebige URLs oder externe Seiten — das Skript
|
|
akzeptiert ausschließlich einen Repo-relativen `.html`-Pfad und lehnt
|
|
jeden anderen selbst ab.
|
|
- Keine Chromium-eigene Sandbox (`--no-sandbox` ist zwingend unter den
|
|
bestehenden systemd-Härtungsoptionen) — kein Versuch, das zu umgehen,
|
|
da es dafür eine echte Kompromittierung der bestehenden
|
|
`NoNewPrivileges`-Einstellung bräuchte.
|
|
|
|
## Annahmen
|
|
|
|
- Board-getriggerte Reviews betreffen aktuell ausschließlich
|
|
`board.html` als einzige eigenständige HTML/CSS-Oberfläche dieses
|
|
Repos (per Operator-Analyse zum Darkmode-Auftrag bereits bestätigt) —
|
|
das Skript ist aber nicht auf diesen einen Dateinamen fest verdrahtet,
|
|
falls eine zweite Oberfläche dazukommt.
|
|
- Ein einzelnes, aus dem Commit extrahiertes HTML-Dokument ist
|
|
selbstständig renderbar (kein externer Asset-Import über relative
|
|
Pfade) — trifft auf `board.html` zu (vollständig inline CSS/JS), ist
|
|
aber keine allgemeingültige Eigenschaft jeder künftigen HTML-Datei in
|
|
diesem Repo.
|
|
|
|
## Offene Risiken
|
|
|
|
- Noch nicht gegen den echten `board-agent-worker.service`-Sandbox
|
|
live verifiziert (nur isoliert außerhalb des systemd-Sandbox getestet,
|
|
10.09.2026) — falls eine dort zusätzlich aktive Härtungsoption
|
|
(`SystemCallFilter` o. Ä.) Chromium doch blockiert, greift der im
|
|
Prompt vorgesehene "kein Fehler, nur Unsicherheiten-Vermerk"-Fallback,
|
|
aber das ist noch nicht am echten Dienst beobachtet worden.
|
|
- `pkgs.chromium` ist eine vergleichsweise schwere neue Abhängigkeit für
|
|
ein sonst bewusst kleines, forkbares Grundsystem (siehe
|
|
`docs/operator-prompt.md` §0) — explizit in Kauf genommen für diesen
|
|
konkreten Anwendungsfall, kein Freibrief für weitere
|
|
Browser-Automatisierung ohne erneute Abwägung.
|