152 lines
7.0 KiB
Markdown
152 lines
7.0 KiB
Markdown
# Handoff — CluPilot (Stand 2026-07-28)
|
||
|
||
**Für:** frische Claude-Code-Session
|
||
**Zuerst lesen:** dieses Dokument, dann `CLAUDE.md` (**R22 zuerst**, dann R18–R21).
|
||
**Vorgänger:** `docs/handoffs/2026-07-27-portal-design-handoff.md` (Umgebung, Stack, Befehle — gilt unverändert).
|
||
|
||
---
|
||
|
||
## 0. Das Wichtigste zuerst: R22
|
||
|
||
Die letzte Sitzung hat für eine Tabellenumstellung **zehn Codex-Runden und rund
|
||
zwölf Stunden** gebraucht und einen erheblichen Teil des Wochenkontingents des
|
||
Nutzers verbraucht. Die Funde waren echt, die Priorisierung war falsch.
|
||
|
||
**R22 in `CLAUDE.md` ist die Konsequenz und bindend.** Kurzfassung:
|
||
|
||
- Eine Prüfrunde, eine Fix-Runde, ein Re-Review über den Fix-Diff. Danach parken.
|
||
- Codex/R15: höchstens **zwei Runden ohne P1**, Rest als Folgepunkt in den MR.
|
||
- **Nie Dateien prüfen, die seit dem letzten grünen Testlauf unverändert sind.**
|
||
- Mutationstests nur an der Grenze zu Datenverlust, Rechteausweitung, Preisgabe.
|
||
- Aufgaben ohne Entwurfsentscheidung bekommen keinen eigenen Review.
|
||
- Unabhängige Analysen parallel starten, nicht nacheinander.
|
||
|
||
Der Nutzer wartet auf ein Ergebnis, nicht auf ein Verfahren.
|
||
|
||
---
|
||
|
||
## 1. Stand
|
||
|
||
**Zwei Merge Requests offen, beide gegen `main`:**
|
||
|
||
| | |
|
||
|---|---|
|
||
| [PR #2](https://git.bave.dev/boban/CluPilotCloud/pulls/2) | `feat/operator-identity` → `main`. **Enthält PR #1 mit.** Das ist der einzige, der gemergt werden muss. |
|
||
| [PR #1](https://git.bave.dev/boban/CluPilotCloud/pulls/1) | `feat/mailboxes` → `main`. Durch #2 überflüssig, kann geschlossen werden. |
|
||
|
||
**885 Tests grün.** Zweig `feat/operator-identity` ist gepusht, Arbeitsbaum sauber.
|
||
|
||
**`main` hat den Stand noch nicht** — PR #2 muss gemergt werden, bevor der
|
||
Live-Server über Konsole → Einstellungen → Update aktualisiert werden kann.
|
||
|
||
---
|
||
|
||
## 2. Was fertig ist
|
||
|
||
### 2.1 Betreiber-Identität (die ursprüngliche Meldung)
|
||
|
||
Der Nutzer meldete: Admin-Login wird mit dem App-Login geteilt, zeigt
|
||
„Registrieren", das führt in eine 404, und der Serverstandort-Text ist zu lang.
|
||
|
||
- **Eigene Tabelle `operators`**, eigener Guard `operator`, eigene Anmeldeseite
|
||
(`App\Livewire\Auth\OperatorLogin`) mit eigenem 2FA-Ablauf. Fortify bleibt
|
||
beim Portal.
|
||
- Alle 17 Spatie-Berechtigungen und 6 Rollen sind von `web` nach `operator`
|
||
**umgezogen**, nicht verdoppelt. `User` hat keine Rollen mehr.
|
||
- **Registrieren-Link und 404 sind an der Ursache weg**: `RestrictAdminHost::SHARED`
|
||
führt nur noch `livewire/*`, `up` und `broadcasting/auth`. `/register` ist auf
|
||
dem Konsolen-Host keine Route mehr.
|
||
- Serverstandort: nur noch `EU` (deckt sich mit `docs/design/tpl-home.html`).
|
||
- Impersonation über **signierte Einmal-URL** gegen den Portal-Host (Cookies
|
||
sind host-gebunden, ein `Auth::login()` über zwei Hosts konnte nie funktionieren).
|
||
- 2FA-Pflicht als Schalter des Inhabers, mit eigener Einrichtungsseite
|
||
`admin.two-factor-setup` und Aussperr-Schutz.
|
||
- **R21** in `CLAUDE.md`, erzwungen durch `tests/Feature/IdentitySeparationTest.php`.
|
||
|
||
### 2.2 Postfächer (im selben MR)
|
||
|
||
Adressen als Datensätze statt eines fest verdrahteten SMTP-Zugangs: Tabelle
|
||
`mailboxes`, kuratierte Zweckliste, `From`/`Reply-To`, Konsolenseite mit
|
||
`mail.manage`, Testversand, der bewusst am `MAIL_MAILER=log` vorbeigeht.
|
||
|
||
---
|
||
|
||
## 3. Live-Gang — Reihenfolge nicht vertauschen
|
||
|
||
1. **PR #2 mergen.**
|
||
2. **`SECRETS_KEY` erzeugen** und in `.env` eintragen:
|
||
`head -c 32 /dev/urandom | base64`
|
||
Ohne den speichert die Zugangsdaten-Seite **gar nichts** — auch Stripe, DNS
|
||
und Monitoring nicht. Er ist auf keinem Server gesetzt.
|
||
3. **Update über Konsole → Einstellungen → Update anfordern** (VPN-only, nicht per SSH).
|
||
Die Migration läuft dabei mit.
|
||
4. **Postfach-Passwörter eintragen, Testversand je Postfach.**
|
||
5. **Erst dann** `MAIL_SCHEME=smtp` (aktuell steht dort `tls`, was Symfony
|
||
ablehnt) und `MAIL_MAILER=smtp`.
|
||
|
||
Andersherum bricht die Bereitstellung eines zahlenden Kunden ab: `CloudReady`
|
||
wird bewusst synchron verschickt, damit ein Mailfehler den Schritt wiederholt
|
||
statt das Passwort zu verlieren.
|
||
|
||
### Was die Migration mit den Konten macht
|
||
|
||
Passwort-Hash und 2FA werden **unverändert übernommen** — dasselbe Passwort wie
|
||
bisher. Vor jeder Änderung prüft sie vorab, ob jemand Betreiber *und* Kunde ist,
|
||
und bricht dann ab, ohne etwas angefasst zu haben, mit allen betroffenen Adressen.
|
||
|
||
**Live hat betreibereigene VPN-Peers** (WireGuard auf zwei Geräten). `vpn_peers.user_id`
|
||
ist `ON DELETE RESTRICT` — die Migration schreibt die Werte um, bevor sie löscht.
|
||
Das war der Fund, der den Live-Gang gerettet hat; er ist getestet, aber auf dev
|
||
nie mit echten Zeilen durchgespielt worden. **Vor dem Live-Update eine Probe auf
|
||
dev mit echten VPN-Peer-Zeilen fahren.**
|
||
|
||
Auf dev ist der Umzug bereits erfolgt: `operators` enthält
|
||
`admin@clupilot.local` und `boban.blaskovic@gmail.com`, `users` nur noch
|
||
Kundenkonten.
|
||
|
||
---
|
||
|
||
## 4. Offene Punkte
|
||
|
||
### 4.1 Als Folgepunkte geparkt (aus PR #2, bewusst nicht gebaut)
|
||
|
||
- Mitarbeiterverwaltung als eigene Konsolen-Ansicht (`staff.manage` existiert).
|
||
- Passwort-Zurücksetzen für Operatoren (`passwords.operators` ist konfiguriert,
|
||
aber unverdrahtet — mit 2FA-Pflicht ist der einzige Weg zurück die Kommandozeile).
|
||
- `RequireOperatorTwoFactor` fehlt in Livewires `addPersistentMiddleware()`: eine
|
||
bereits offene Konsolenseite arbeitet nach dem Umlegen des Schalters weiter.
|
||
- `IconLayoutTest` prüft R18 nur für `<x-ui.nav-item>`, nicht für `<x-ui.button>`.
|
||
- `Secrets::test()` hat keinen `isUsable()`-Schutz — bei kaputtem `SECRETS_KEY`
|
||
wirft der Testknopf ungefangen. Vorbestehend.
|
||
|
||
### 4.2 Aus dem vorigen Handoff weiterhin offen
|
||
|
||
- 22 Konsolen-Blades auf das Designsystem.
|
||
- Support-Warteschlange in der Konsole (**jetzt baubar**, die Absender existieren).
|
||
- Startseite ins Blade (`docs/design/tpl-home.html`).
|
||
- E-Mail-Vorlagen: Bestellbestätigung, Störungsmeldung.
|
||
- Verschoben gespeicherte Wartungsfenster (Entscheidung des Nutzers, siehe
|
||
Vorgänger-Handoff §3.4).
|
||
|
||
---
|
||
|
||
## 5. Fallen, die diese Sitzung 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()` — das hat zwei echte Fehler verdeckt. Wer eine
|
||
Guard-Unterscheidung beweisen will, meldet mit `Auth::guard('x')->login()` an.
|
||
- **Pest: `toThrow(SomeInterface::class)`** degradiert still zu einem
|
||
String-Vergleich auf der Fehlermeldung, weil `class_exists()` für Interfaces
|
||
`false` ist. Nie gegen Interface-Namen prüfen. Siehe Nutzer-Memory
|
||
`pest-tothrow-interface-trap`.
|
||
- **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`.
|