Hand off with the effort rule at the top, not the feature list
tests / pest (pull_request) Failing after 7m21s
Details
tests / assets (pull_request) Successful in 23s
Details
tests / release (pull_request) Has been skipped
Details
tests / pest (push) Failing after 7m18s
Details
tests / assets (push) Successful in 27s
Details
tests / release (push) Has been skipped
Details
tests / pest (pull_request) Failing after 7m21s
Details
tests / assets (pull_request) Successful in 23s
Details
tests / release (pull_request) Has been skipped
Details
tests / pest (push) Failing after 7m18s
Details
tests / assets (push) Successful in 27s
Details
tests / release (push) Has been skipped
Details
parent
75cb45bcbc
commit
9fc74eedc3
|
|
@ -0,0 +1,151 @@
|
||||||
|
# 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`.
|
||||||
Loading…
Reference in New Issue