# Handoff — CluPilot (Stand 2026-07-28, Abend)

**Für:** frische Claude-Code-Session
**Zuerst lesen:** Abschnitt 0 dieses Dokuments, dann `CLAUDE.md` (**R22 und R23 zuerst**), dann `git log --oneline -15`.
**Vorgänger:** `docs/handoffs/2026-07-28-operator-identity-handoff.md` (Umgebung, Stack, Befehle — gilt weiter).

---

## 0. Vor allem anderen: wie in dieser Sitzung gearbeitet wurde, und warum das falsch war

Der Nutzer hat mehrfach und zuletzt sehr deutlich gesagt: **jeder Prompt dauert mindestens eine Stunde, der Tokenverbrauch ist enorm** — beim letzten Schritt rund eine Million. Das ist keine Stilfrage, das kostet ihn sein Wochenkontingent.

Die Ursachen lagen im Verfahren, nicht in den Aufgaben:

- Für jede Kleinigkeit wurde ein eigener Subagent mit Review, Fix-Runde und Re-Review gefahren.
- Der Codex-Review (R15) wurde bis zur Nullmeldung geschliffen — zehn Runden für eine Tabellenumstellung.
- Es wurden wiederholt Dateien geprüft, die seit dem letzten grünen Testlauf niemand angefasst hatte.
- Mutationstests flächendeckend statt nur an der Sicherheitsgrenze.

**R22 in `CLAUDE.md` ist die verbindliche Konsequenz. Halte dich daran, auch wenn ein Befund verlockend aussieht:**

1. Eine Prüfrunde, eine Fix-Runde, ein Re-Review über den Fix-Diff — danach parken, nicht weiterschleifen.
2. Codex/R15: höchstens **zwei Runden ohne P1**. Rest als Folgepunkt in den Merge Request.
3. **Nie Dateien prüfen, die seit dem letzten grünen Lauf unverändert sind.**
4. Mutationstests nur, wo ein Fehler Daten verliert, Rechte ausweitet oder Zugangsdaten preisgibt.
5. Aufgaben ohne Entwurfsentscheidung bekommen keinen eigenen Review.
6. Unabhängige Analysen parallel starten.

**Zusätzlich, aus dieser Sitzung gelernt:**

- **Kleine UI-Änderungen selbst machen.** Ein Sprachschlüssel, eine Beschriftung, eine Tailwind-Klasse: bauen, Suite laufen lassen, fertig. Ein Subagent dafür kostet mehr, als er bringt.
- **Erst messen, wo der Fehler liegt, dann handeln.** In dieser Sitzung wurden zweimal Schlüsse aus einem fehlerhaften eigenen Messaufbau gezogen und dem Nutzer als Befund über *sein* System verkauft (einmal ein leeres Passwort im Prüfskript, einmal fünfzehn Zeilen Log als „dein Browser erreicht den Container nicht"). Beides war falsch und hat ihn auf falsche Fährten geschickt.
- **Bei Umgebungsfragen zuerst klären, welche Umgebung gemeint ist.** dev und live haben getrennte Datenbanken, getrennte Netze und getrennte `.env`. Mehrere Runden gingen dafür drauf, das zu verwechseln.

---

## 1. Stand

`main` steht auf **`64efe45`**, gepusht, **924 Tests grün**, Arbeitsbaum sauber.
Live folgt `main` über Konsole → Einstellungen → *Jetzt aktualisieren*.

Seit dem letzten Handoff gelandet:

| Commit | Inhalt |
|---|---|
| `1641454` | Abgelaufene Betreiber-Sitzung führt zur Anmeldung statt zu einer 500 |
| `c2681f2` | Vollbild-Wartungsansicht auf jeder Konsolenseite während eines Updates |
| `973fcb3` | **R23**: native `confirm()`-Dialoge durch die eigenen Modale ersetzt, sechs Stellen, Scan-Test |
| `f055479` | Update: *Prüfen* von *Aktualisieren* getrennt, Fortschritt und Countdown |
| `bda61ca` | Feste Kopf-/Fußzeile in beiden Shells, zwei umbrechende Beschriftungen gekürzt |
| `64efe45` | 419 lädt still neu statt Overlay; Offline-Banner mit „Verbindung wiederhergestellt" |

---

## 2. Offene Punkte, mit dem was bereits gemessen wurde

### 2.1 Update-Prüfung startet einen Timer, den niemand wollte — **falsch umgesetzt**

Der Nutzer, wörtlich: *„ich suche nur nach neuen Updates, was ist das da für ein Timer, den ich nicht gestartet habe? Ich wollte nur prüfen. Beim Prüfen soll in dem Moment die Abfrage kommen, nicht in 5 Minuten — das ist ja Unsinn, wenn das so gemacht worden ist."*

Sein Screenshot zeigt nach Klick auf *Nach Aktualisierungen suchen*: Badge „Prüfung läuft", Knopf *Jetzt aktualisieren* aktiv, und **„Startet in 3:44"**.

Was schiefging: Die Prüfung wurde als Anforderungsdatei umgesetzt, die derselbe Host-Agent auf **derselben Zeitschleife** abholt wie ein echtes Update (`deploy/update-agent.sh`, `CHECK_INTERVAL_MINUTES`). Eine reine Abfrage darf nicht in einer Warteschlange landen — sie soll sofort antworten. Zusätzlich liest der Countdown wie „gleich startet ein Update", obwohl nur geprüft wird.

**Zu entwerfen, nicht bloß zu flicken.** Denkbare Richtungen — bewusst offen gelassen, weil die richtige Antwort von `update-agent.sh` abhängt:
- Die Prüfung ohne Agent machen (ein `git ls-remote`/`fetch` aus dem Container, falls das Repo von dort erreichbar ist) und sofort antworten.
- Oder den Agenten für Prüfanforderungen unabhängig vom Timer aufwecken.
- In jedem Fall: **kein Countdown bei einer reinen Prüfung.**

Anmerkung: `CHECK_INTERVAL_MINUTES` steht im Repo bereits auf `1`, greift auf einem Server aber erst nach erneutem `deploy/install-agent.sh` — der Wert lebt im systemd-Timer. Auf live standen deshalb noch 5 Minuten.

### 2.2 Rechenzentren: Kürzel nicht änderbar, Bezeichnung fehlt

Zwei Wünsche:
1. **Das Kürzel (`fsn`, `hel`) lässt sich beim Anlegen setzen, aber nicht mehr bearbeiten.** Soll änderbar sein. Vorsicht: Es ist vermutlich Fremdschlüssel oder Anzeigeschlüssel an anderen Stellen — vor dem Bauen prüfen, was daran hängt (Hosts, Bestellungen), und ob eine Änderung nachziehen muss.
2. **Ein optionales Feld für das konkrete Rechenzentrum**, z. B. `fsn-dc-15`, damit bei der Auswahl erkennbar ist, welches gemeint ist.

Betroffen: `App\Livewire\Admin\EditDatacenter` + `resources/views/livewire/admin/edit-datacenter.blade.php`, Migration für das neue Feld, `Datacenter`-Modell.

### 2.3 500 beim VPN-Konfigurations-Download auf live — **weiterhin ungeklärt**

Auf dev funktioniert der Download. Auf live: Passwort eingeben → 500.

**Was bereits ausgeschlossen ist:**
- Nicht der Null-Operator-Fehler — der Fix (`1641454`) ist auf live, die Fußzeile im Screenshot zeigte `c2681f2`.
- `ConfigVault::decrypt()` wirft nicht, es gibt `null` zurück → das ergäbe eine saubere Meldung, keine 500.
- Auf dev hat der Peer **kein gespeichertes Konfigurat** (`config_secret` leer, weil er vor dem Setzen von `VPN_CONFIG_KEY` angelegt wurde) — dort kommt deshalb die Meldung „nicht lesbar", nicht der Fehlerfall von live.

**Was fehlt:** eine **aktuelle** Protokollzeile vom Live-Server zum Zeitpunkt der 500. Das vom Nutzer geschickte Protokoll ist vom **27.07. 04:16** und betrifft `artisan optimize`, nicht den Download — siehe 2.4. Frage gezielt danach:

```bash
docker compose exec -T app tail -100 storage/logs/laravel.log | grep -B2 -A20 "local.ERROR"
```

Und zusätzlich: ist `VPN_CONFIG_KEY` auf live gesetzt, und ist es **derselbe** wie beim Anlegen des Peers? Ein gewechselter Schlüssel macht bestehende Konfigurationen unlesbar.

### 2.4 `route:cache` scheitert auf live — eigener, echter Fund

Aus dem Protokoll des Nutzers, 2026-07-27 04:16:

```
production.ERROR: Unable to prepare route [/] for serialization.
Another route has already been assigned name [admin.overview].
```

Das passiert in `php artisan optimize` → `route:cache`. **Schlägt das fehl, bleibt die Anwendung ohne gültigen Route-Cache** — ein plausibler Grund für 500er, die nur auf live auftreten, weil dev kein `optimize` fährt.

Auf dev **nicht reproduzierbar**: `route:cache` läuft mit `ADMIN_HOST_EXCLUSIVE=true` und einem einzigen Hostnamen sauber durch (geprüft).

Verdacht, nicht bewiesen: live hat mehrere Einträge in `ADMIN_HOSTS`, und die Registrierung in `routes/web.php` vergibt dabei zweimal denselben Namen. Der Block registriert seit dieser Sitzung **zwei** Gruppen je Host (Gast + authentifiziert). Erste Diagnose auf live:

```bash
docker compose exec -T app php artisan tinker --execute="echo json_encode(config('admin_access.hosts')).PHP_EOL;"
docker compose exec -T app php artisan route:cache
```

### 2.5 Live-Konfiguration, die noch aussteht

- **`SECRETS_KEY`** — ohne ihn speichert die Konsole *keine* Zugangsdaten (Postfächer, Stripe, DNS, Monitoring). Erzeugen mit `head -c 32 /dev/urandom | base64`, in die `.env`, danach `config:clear` **und** `docker compose restart queue queue-provisioning scheduler reverb` (die langlaufenden Prozesse lesen die `.env` nur beim Start).
- **`MAIL_SCHEME=tls`** ist ungültig — Symfony kennt nur `smtp` und `smtps`. `.env.example` ist korrigiert, die `.env` der Server nicht.
- **`deploy/install-agent.sh`** einmal neu laufen lassen, damit das 1-Minuten-Intervall greift.

### 2.6 Aus dem vorigen Handoff weiterhin offen

22 Konsolen-Blades aufs Designsystem · Support-Warteschlange in der Konsole (jetzt baubar, Absender existieren) · Startseite ins Blade · E-Mail-Vorlagen · verschoben gespeicherte Wartungsfenster · Mitarbeiterverwaltung als Ansicht · Passwort-Zurücksetzen für Operatoren.

---

## 3. Fallen, die diese Sitzung Zeit gekostet haben

- **`actingAs()` ruft `Auth::shouldUse()`** und macht den genannten Guard zum Standard — damit verschwindet jeder Unterschied zwischen `auth()->user()` und `auth()->guard('x')->user()`. Hat zwei echte Fehler verdeckt. Für Guard-Nachweise mit `Auth::guard('x')->login()` anmelden.
- **Pest: `toThrow(SomeInterface::class)`** degradiert still zu einem String-Vergleich, weil `class_exists()` für Interfaces `false` ist. Nie gegen Interface-Namen prüfen.
- **SQLite verdeckt Migrationsfehler**, die MariaDB zeigt (Transaktionen um DDL, doppelt gelöste Constraints, zusammenfallende Auto-Increment-Werte). Migrationen gegen die echte dev-Datenbank prüfen.
- **`<x-ui.button>` hat keinen Icon-Slot** — ein `<x-slot:icon>` wird still verschluckt. Icon in den Standard-Slot mit `class="size-4"`.
- **Es gibt kein `layouts.guest`** — Auth-Seiten benutzen `layouts.portal`.
- **`users` hat keine `uuid`-Spalte**; `Customer` und `Operator` schon. Ein Kunde hängt über **`customers.user_id`** am Benutzer, nicht über `users.customer_id`.
- **Mailserver sperren IPs** nach wiederholten Fehlanmeldungen. Ein Testversand mit falschem Passwort zählt darauf ein; danach kommen **Timeouts**, keine Ablehnungen. In dieser Sitzung war genau das die Ursache für „live geht gar nichts mehr" — vom Nutzer per Whitelist gelöst.

---

## 4. Was der Nutzer erwartet

Er baut ein Produkt und will Ergebnisse sehen, keine Verfahren. Konkret:

- **Zuerst liefern, dann erklären.** Kein Vorlauf aus Analyse, wenn die Aufgabe klein ist.
- **Kein Verfahren um seiner selbst willen.** Wenn eine Änderung zwei Zeilen groß ist, ist sie zwei Zeilen groß.
- **Ehrlich sein, wenn etwas nicht geht.** Er nimmt ein „das kann ich von hier nicht prüfen, gib mir bitte diese eine Ausgabe" besser auf als eine Vermutung, die sich als falsch herausstellt.
- **Umgebung immer benennen.** dev oder live — sonst reden beide Seiten aneinander vorbei.
